feat(docking): improve tab management and error handling

Refactored DockGroup to only remove obsolete TabViewItems and restore tab selection more reliably. Updated DockGroup.xaml to enable tab reordering and add-tab button. Switched to CommunityToolkit.WinUI.Controls for GridSplitter and added a style for it. Made DockPanel, DockRegionHighlight, and DockingLayout partial classes. In App.xaml.cs, wrapped initialization in a try-catch to exit on error, and ensured process exit on window close. Improved ProjectBrowser scrollbar behavior and layout settings.
This commit is contained in:
2026-03-29 16:07:18 +09:00
parent 15870ffe89
commit d15bd22743
9 changed files with 71 additions and 64 deletions

View File

@@ -116,6 +116,8 @@ public partial class App : Application
return; return;
} }
try
{
EditorApplication.Initialize(Host.Services, arguments.ProjectPath, arguments.ProjectName); EditorApplication.Initialize(Host.Services, arguments.ProjectPath, arguments.ProjectName);
// NOTE: We must call DispatcherQueue.GetForCurrentThread() on the UI thread before any await. // NOTE: We must call DispatcherQueue.GetForCurrentThread() on the UI thread before any await.
@@ -136,6 +138,11 @@ public partial class App : Application
splashWindow.Close(); splashWindow.Close();
} }
catch (Exception ex)
{
Environment.Exit(ex.HResult);
}
}
private void OnClosed(object? sender, WindowEventArgs args) private void OnClosed(object? sender, WindowEventArgs args)
{ {
@@ -153,7 +160,7 @@ public partial class App : Application
} }
finally finally
{ {
//Environment.Exit(0); Environment.Exit(0);
} }
} }

View File

@@ -1,7 +1,7 @@
<ResourceDictionary <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.UI.Xaml.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:ghost="using:Ghost.Editor.Controls" xmlns:ghost="using:Ghost.Editor.Controls"
xmlns:local="using:Ghost.Editor.Core"> xmlns:local="using:Ghost.Editor.Core">
@@ -42,6 +42,11 @@
<Setter Property="TabWidthMode" Value="Compact" /> <Setter Property="TabWidthMode" Value="Compact" />
</Style> </Style>
<Style TargetType="NumberBox" /> <Style TargetType="NumberBox" />
<Style TargetType="controls:GridSplitter">
<Setter Property="MinHeight" Value="2" />
<Setter Property="MinWidth" Value="2" />
<Setter Property="Background" Value="{ThemeResource AcrylicBackgroundFillColorBaseBrush}" />
</Style>
<!-- Named Style --> <!-- Named Style -->
<Style <Style

View File

@@ -115,48 +115,39 @@ public partial class DockGroup : DockContainer
var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null; var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null;
// Detach all existing tabs // Remove tabs that are no longer in Children
foreach (var item in _tabView.TabItems) for (int i = _tabView.TabItems.Count - 1; i >= 0; i--)
{ {
if (item is TabViewItem tabItem) if (_tabView.TabItems[i] is TabViewItem tabItem && tabItem.Tag is DockDocument doc)
{
if (!Children.Contains(doc))
{ {
tabItem.Content = null; tabItem.Content = null;
_tabView.TabItems.RemoveAt(i);
}
} }
} }
_tabView.TabItems.Clear();
TabViewItem? newSelectedItem = null;
// Add new tabs that aren't in TabItems yet
foreach (var child in Children) foreach (var child in Children)
{ {
if (child is DockDocument doc) if (child is DockDocument doc && !_tabView.TabItems.Any(t => t is TabViewItem item && item.Tag.Equals(doc)))
{ {
var tabItem = new TabViewItem var tabItem = new TabViewItem { Tag = doc, Content = doc };
{
Tag = doc
};
tabItem.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding tabItem.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding
{ {
Source = doc, Source = doc,
Path = new PropertyPath(nameof(DockDocument.Title)), Path = new PropertyPath(nameof(DockDocument.Title)),
Mode = Microsoft.UI.Xaml.Data.BindingMode.OneWay Mode = BindingMode.OneWay
}); });
tabItem.Content = doc;
_tabView.TabItems.Add(tabItem); _tabView.TabItems.Add(tabItem);
if (doc == selectedDoc)
{
newSelectedItem = tabItem;
}
} }
} }
if (newSelectedItem != null) // Restore selection
if (selectedDoc != null && _tabView.TabItems.FirstOrDefault(t => t is TabViewItem item && item.Tag.Equals(selectedDoc)) is TabViewItem newSelected)
{ {
_tabView.SelectedItem = newSelectedItem; _tabView.SelectedItem = newSelected;
} }
else if (_tabView.TabItems.Count > 0) else if (_tabView.TabItems.Count > 0)
{ {

View File

@@ -7,15 +7,15 @@
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="local:DockGroup"> <ControlTemplate TargetType="local:DockGroup">
<Grid>
<TabView <TabView
x:Name="PART_TabView" x:Name="PART_TabView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
AllowDrop="True" AllowDrop="True"
CanDragTabs="True" CanDragTabs="True"
CanReorderTabs="False" CanReorderTabs="True"
IsAddTabButtonVisible="False" /> IsAddTabButtonVisible="True"
</Grid> TabWidthMode="Compact" />
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>

View File

@@ -1,6 +1,7 @@
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using CommunityToolkit.WinUI.Controls; using CommunityToolkit.WinUI.Controls;
using Windows.Foundation;
namespace Ghost.Editor.View.Controls.Docking; namespace Ghost.Editor.View.Controls.Docking;
@@ -8,7 +9,7 @@ namespace Ghost.Editor.View.Controls.Docking;
/// A container that can host multiple dock modules with splitters. /// A container that can host multiple dock modules with splitters.
/// </summary> /// </summary>
[TemplatePart(Name = PART_GRID, Type = typeof(Grid))] [TemplatePart(Name = PART_GRID, Type = typeof(Grid))]
public class DockPanel : DockContainer public partial class DockPanel : DockContainer
{ {
private const string PART_GRID = "PART_Grid"; private const string PART_GRID = "PART_Grid";
private const double SPLITTER_THICKNESS = 4; private const double SPLITTER_THICKNESS = 4;
@@ -116,5 +117,7 @@ public class DockPanel : DockContainer
} }
} }
} }
UpdateLayout();
} }
} }

View File

@@ -5,7 +5,7 @@ namespace Ghost.Editor.View.Controls.Docking;
/// <summary> /// <summary>
/// Represents a visual highlight for a docking region. /// Represents a visual highlight for a docking region.
/// </summary> /// </summary>
public class DockRegionHighlight : Control public partial class DockRegionHighlight : Control
{ {
public DockRegionHighlight() public DockRegionHighlight()
{ {

View File

@@ -8,7 +8,7 @@ namespace Ghost.Editor.View.Controls.Docking;
/// </summary> /// </summary>
[TemplatePart(Name = PART_OVERLAY_CANVAS, Type = typeof(Canvas))] [TemplatePart(Name = PART_OVERLAY_CANVAS, Type = typeof(Canvas))]
[TemplatePart(Name = PART_HIGHLIGHT, Type = typeof(DockRegionHighlight))] [TemplatePart(Name = PART_HIGHLIGHT, Type = typeof(DockRegionHighlight))]
public class DockingLayout : Control public partial class DockingLayout : Control
{ {
private const string PART_OVERLAY_CANVAS = "PART_OverlayCanvas"; private const string PART_OVERLAY_CANVAS = "PART_OverlayCanvas";
private const string PART_HIGHLIGHT = "PART_Highlight"; private const string PART_HIGHLIGHT = "PART_Highlight";

View File

@@ -144,8 +144,6 @@
DoubleTapped="PART_FilesView_DoubleTapped" DoubleTapped="PART_FilesView_DoubleTapped"
IsDoubleTapEnabled="True" IsDoubleTapEnabled="True"
ItemsSource="{x:Bind ViewModel.Files, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Files, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollMode="Disabled"
SelectionChanged="PART_FilesView_SelectionChanged" SelectionChanged="PART_FilesView_SelectionChanged"
SelectionMode="Single"> SelectionMode="Single">
<ItemsView.ItemTemplate> <ItemsView.ItemTemplate>
@@ -191,7 +189,7 @@
</ItemsView.ItemTemplate> </ItemsView.ItemTemplate>
<ItemsView.Layout> <ItemsView.Layout>
<UniformGridLayout <UniformGridLayout
ItemsStretch="Fill" ItemsStretch="None"
MinColumnSpacing="4" MinColumnSpacing="4"
MinItemWidth="72" MinItemWidth="72"
MinRowSpacing="4" /> MinRowSpacing="4" />

View File

@@ -49,6 +49,9 @@ internal sealed partial class ProjectBrowser : UserControl
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e) private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
{ {
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged; _inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
// HACK: Scroll a little bit to trigger ScrollView to update it's scrollbar visibility. Otherwise the scrollbar will show a incorrect state when docking layout changes.
PART_FilesView.ScrollView.ScrollBy(1, 0);
} }
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e) private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)