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:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user