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;
}
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.
EditorApplication.SetDispatcherQueue(DispatcherQueue.GetForCurrentThread());
// NOTE: We must call DispatcherQueue.GetForCurrentThread() on the UI thread before any await.
EditorApplication.SetDispatcherQueue(DispatcherQueue.GetForCurrentThread());
var splashWindow = new SplashWindow();
splashWindow.Activate();
Window = splashWindow;
var splashWindow = new SplashWindow();
splashWindow.Activate();
Window = splashWindow;
await Host.StartAsync();
await ActivationHandler.HandleAsync(arguments);
await Host.StartAsync();
await ActivationHandler.HandleAsync(arguments);
splashWindow.Hide();
splashWindow.Hide();
var editorWindow = new EngineEditorWindow();
editorWindow.Activate();
Window = editorWindow;
var editorWindow = new EngineEditorWindow();
editorWindow.Activate();
Window = editorWindow;
splashWindow.Close();
splashWindow.Close();
}
catch (Exception ex)
{
Environment.Exit(ex.HResult);
}
}
private void OnClosed(object? sender, WindowEventArgs args)
@@ -153,7 +160,7 @@ public partial class App : Application
}
finally
{
//Environment.Exit(0);
Environment.Exit(0);
}
}

View File

@@ -1,7 +1,7 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="using:Ghost.Editor.Core">
@@ -42,6 +42,11 @@
<Setter Property="TabWidthMode" Value="Compact" />
</Style>
<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 -->
<Style

View File

@@ -115,48 +115,39 @@ public partial class DockGroup : DockContainer
var selectedDoc = _tabView.SelectedItem is TabViewItem selectedItem ? selectedItem.Tag as DockDocument : null;
// Detach all existing tabs
foreach (var item in _tabView.TabItems)
// Remove tabs that are no longer in Children
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;
}
}
_tabView.TabItems.Clear();
TabViewItem? newSelectedItem = null;
foreach (var child in Children)
{
if (child is DockDocument doc)
{
var tabItem = new TabViewItem
if (!Children.Contains(doc))
{
Tag = doc
};
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;
tabItem.Content = null;
_tabView.TabItems.RemoveAt(i);
}
}
}
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)
{

View File

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

View File

@@ -1,6 +1,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using CommunityToolkit.WinUI.Controls;
using Windows.Foundation;
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.
/// </summary>
[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 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>
/// Represents a visual highlight for a docking region.
/// </summary>
public class DockRegionHighlight : Control
public partial class DockRegionHighlight : Control
{
public DockRegionHighlight()
{

View File

@@ -8,7 +8,7 @@ namespace Ghost.Editor.View.Controls.Docking;
/// </summary>
[TemplatePart(Name = PART_OVERLAY_CANVAS, Type = typeof(Canvas))]
[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_HIGHLIGHT = "PART_Highlight";

View File

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

View File

@@ -49,6 +49,9 @@ internal sealed partial class ProjectBrowser : UserControl
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
{
_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)