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,25 +116,32 @@ public partial class App : Application
return; return;
} }
EditorApplication.Initialize(Host.Services, arguments.ProjectPath, arguments.ProjectName); try
{
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.
EditorApplication.SetDispatcherQueue(DispatcherQueue.GetForCurrentThread()); EditorApplication.SetDispatcherQueue(DispatcherQueue.GetForCurrentThread());
var splashWindow = new SplashWindow(); var splashWindow = new SplashWindow();
splashWindow.Activate(); splashWindow.Activate();
Window = splashWindow; Window = splashWindow;
await Host.StartAsync(); await Host.StartAsync();
await ActivationHandler.HandleAsync(arguments); await ActivationHandler.HandleAsync(arguments);
splashWindow.Hide(); splashWindow.Hide();
var editorWindow = new EngineEditorWindow(); var editorWindow = new EngineEditorWindow();
editorWindow.Activate(); editorWindow.Activate();
Window = editorWindow; Window = editorWindow;
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)
{ {
tabItem.Content = null; if (!Children.Contains(doc))
}
}
_tabView.TabItems.Clear();
TabViewItem? newSelectedItem = null;
foreach (var child in Children)
{
if (child is DockDocument doc)
{
var tabItem = new TabViewItem
{ {
Tag = doc tabItem.Content = null;
}; _tabView.TabItems.RemoveAt(i);
tabItem.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding
{
Source = doc,
Path = new PropertyPath(nameof(DockDocument.Title)),
Mode = Microsoft.UI.Xaml.Data.BindingMode.OneWay
});
tabItem.Content = doc;
_tabView.TabItems.Add(tabItem);
if (doc == selectedDoc)
{
newSelectedItem = tabItem;
} }
} }
} }
if (newSelectedItem != null) // Add new tabs that aren't in TabItems yet
foreach (var child in Children)
{ {
_tabView.SelectedItem = newSelectedItem; if (child is DockDocument doc && !_tabView.TabItems.Any(t => t is TabViewItem item && item.Tag.Equals(doc)))
{
var tabItem = new TabViewItem { Tag = doc, Content = doc };
tabItem.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding
{
Source = doc,
Path = new PropertyPath(nameof(DockDocument.Title)),
Mode = BindingMode.OneWay
});
_tabView.TabItems.Add(tabItem);
}
}
// Restore selection
if (selectedDoc != null && _tabView.TabItems.FirstOrDefault(t => t is TabViewItem item && item.Tag.Equals(selectedDoc)) is TabViewItem newSelected)
{
_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)