fix(dock): migrate primary editor to DockLayout and add persistent sizing
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Core.Controls.Internal.Docking;
|
||||
@@ -22,15 +24,48 @@ public partial class DockGroupNode : DockNode
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<DockNode> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of sizes for the children.
|
||||
/// </summary>
|
||||
public ObservableCollection<Microsoft.UI.Xaml.GridLength> Sizes { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DockGroupNode"/> class.
|
||||
/// </summary>
|
||||
public DockGroupNode()
|
||||
{
|
||||
Children = new ReadOnlyObservableCollection<DockNode>(_children);
|
||||
_children.CollectionChanged += OnChildrenChanged;
|
||||
Orientation = Orientation.Horizontal;
|
||||
}
|
||||
|
||||
private void OnChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Maintain Sizes collection to match Children
|
||||
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
|
||||
{
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
{
|
||||
Sizes.Insert(e.NewStartingIndex + i, new Microsoft.UI.Xaml.GridLength(1, Microsoft.UI.Xaml.GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
||||
{
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
{
|
||||
Sizes.RemoveAt(e.OldStartingIndex);
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
Sizes.Clear();
|
||||
foreach (var _ in _children)
|
||||
{
|
||||
Sizes.Add(new Microsoft.UI.Xaml.GridLength(1, Microsoft.UI.Xaml.GridUnitType.Star));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a child node to this group, enforcing tree invariants.
|
||||
/// </summary>
|
||||
|
||||
@@ -208,18 +208,20 @@ public sealed partial class DockLayout : Control
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
var width = (i < groupNode.Sizes.Count) ? groupNode.Sizes[i] : new GridLength(1, GridUnitType.Star);
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition
|
||||
{
|
||||
Width = new GridLength(1, GridUnitType.Star),
|
||||
Width = width,
|
||||
MinWidth = MIN_PANE_SIZE
|
||||
});
|
||||
Grid.SetColumn((FrameworkElement)childUI, i * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var height = (i < groupNode.Sizes.Count) ? groupNode.Sizes[i] : new GridLength(1, GridUnitType.Star);
|
||||
grid.RowDefinitions.Add(new RowDefinition
|
||||
{
|
||||
Height = new GridLength(1, GridUnitType.Star),
|
||||
Height = height,
|
||||
MinHeight = MIN_PANE_SIZE
|
||||
});
|
||||
Grid.SetRow((FrameworkElement)childUI, i * 2);
|
||||
@@ -297,6 +299,7 @@ public sealed partial class DockLayout : Control
|
||||
}
|
||||
|
||||
private DockPosition _currentDropPosition = DockPosition.None;
|
||||
private FrameworkElement? _lastTargetElement;
|
||||
|
||||
private record DockDragPayload(object Item, DockPanelNode SourceNode);
|
||||
|
||||
@@ -322,9 +325,10 @@ public sealed partial class DockLayout : Control
|
||||
var position = e.GetPosition(targetElement);
|
||||
var newPosition = DockMath.CalculateDockPosition(targetElement.ActualWidth, targetElement.ActualHeight, position.X, position.Y, DROP_EDGE_THRESHOLD);
|
||||
|
||||
if (newPosition != _currentDropPosition)
|
||||
if (newPosition != _currentDropPosition || targetElement != _lastTargetElement)
|
||||
{
|
||||
_currentDropPosition = newPosition;
|
||||
_lastTargetElement = targetElement;
|
||||
UpdateDropOverlay(targetElement, _currentDropPosition);
|
||||
}
|
||||
}
|
||||
@@ -332,6 +336,7 @@ public sealed partial class DockLayout : Control
|
||||
|
||||
private void TabView_DragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
_lastTargetElement = null;
|
||||
ClearOverlayState();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@ internal sealed partial class DockWindow : WindowEx
|
||||
|
||||
PART_DockLayout.Root = rootGroup;
|
||||
PART_DockLayout.TabTornOff += OnTabTornOff;
|
||||
|
||||
((System.Collections.Specialized.INotifyCollectionChanged)rootGroup.Children).CollectionChanged += (s, e) =>
|
||||
{
|
||||
if (rootGroup.Children.Count == 0)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OnTabTornOff(object? sender, TabTornOffEventArgs e)
|
||||
|
||||
@@ -66,85 +66,7 @@
|
||||
|
||||
<!-- Editor -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ghost:NavigationTabView
|
||||
x:Name="PART_HierarchyTabView"
|
||||
Grid.Column="0"
|
||||
Width="350"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
TabDroppedOutside="OnTabDroppedOutside">
|
||||
<ghost:NavigationTabView.TabItems>
|
||||
<TabViewItem Header="Hierarchy">
|
||||
<TabViewItem.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</TabViewItem.IconSource>
|
||||
<controls:Hierarchy />
|
||||
</TabViewItem>
|
||||
</ghost:NavigationTabView.TabItems>
|
||||
</ghost:NavigationTabView>
|
||||
|
||||
<ghost:NavigationTabView
|
||||
x:Name="PART_SceneTabView"
|
||||
Grid.Column="1"
|
||||
TabDroppedOutside="OnTabDroppedOutside">
|
||||
<ghost:NavigationTabView.TabItems>
|
||||
<ee:ScenePage Header="Scene">
|
||||
<ee:ScenePage.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</ee:ScenePage.IconSource>
|
||||
</ee:ScenePage>
|
||||
</ghost:NavigationTabView.TabItems>
|
||||
</ghost:NavigationTabView>
|
||||
|
||||
<ghost:NavigationTabView
|
||||
x:Name="PART_InspectorTabView"
|
||||
Grid.Column="2"
|
||||
Width="350"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
TabDroppedOutside="OnTabDroppedOutside">
|
||||
<ghost:NavigationTabView.TabItems>
|
||||
<ee:InspectorPage Header="Inspector">
|
||||
<ee:InspectorPage.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</ee:InspectorPage.IconSource>
|
||||
</ee:InspectorPage>
|
||||
</ghost:NavigationTabView.TabItems>
|
||||
</ghost:NavigationTabView>
|
||||
</Grid>
|
||||
|
||||
<ghost:NavigationTabView
|
||||
x:Name="PART_BottomTabView"
|
||||
Grid.Row="1"
|
||||
Height="350"
|
||||
TabDroppedOutside="OnTabDroppedOutside">
|
||||
<ghost:NavigationTabView.TabItems>
|
||||
<TabViewItem Header="Project">
|
||||
<TabViewItem.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</TabViewItem.IconSource>
|
||||
<controls:ProjectBrowser />
|
||||
</TabViewItem>
|
||||
<TabViewItem Header="Console">
|
||||
<TabViewItem.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</TabViewItem.IconSource>
|
||||
<ee:ConsolePage />
|
||||
</TabViewItem>
|
||||
</ghost:NavigationTabView.TabItems>
|
||||
</ghost:NavigationTabView>
|
||||
<controls:DockLayout x:Name="PART_DockLayout" />
|
||||
</Grid>
|
||||
|
||||
<!-- Status Bar -->
|
||||
|
||||
@@ -38,31 +38,41 @@ internal sealed partial class EngineEditorWindow : WindowEx
|
||||
|
||||
SetTitleBar(PART_TitleBar);
|
||||
this.CenterOnScreen();
|
||||
|
||||
InitializeDockLayout();
|
||||
}
|
||||
|
||||
private void InitializeDockLayout()
|
||||
{
|
||||
var root = new DockGroupNode { Orientation = Orientation.Horizontal };
|
||||
|
||||
var leftGroup = new DockGroupNode { Orientation = Orientation.Vertical };
|
||||
var hierarchyPanel = new DockPanelNode();
|
||||
hierarchyPanel.Items.Add(new TabViewItem { Header = "Hierarchy", Content = new Hierarchy() });
|
||||
leftGroup.AddChild(hierarchyPanel);
|
||||
|
||||
var centerGroup = new DockGroupNode { Orientation = Orientation.Vertical };
|
||||
var scenePanel = new DockPanelNode();
|
||||
scenePanel.Items.Add(new ScenePage { Header = "Scene" });
|
||||
centerGroup.AddChild(scenePanel);
|
||||
|
||||
var rightGroup = new DockGroupNode { Orientation = Orientation.Vertical };
|
||||
var inspectorPanel = new DockPanelNode();
|
||||
inspectorPanel.Items.Add(new InspectorPage { Header = "Inspector" });
|
||||
rightGroup.AddChild(inspectorPanel);
|
||||
|
||||
root.AddChild(leftGroup);
|
||||
root.AddChild(centerGroup);
|
||||
root.AddChild(rightGroup);
|
||||
|
||||
PART_DockLayout.Root = root;
|
||||
PART_DockLayout.TabTornOff += (s, e) => App.CreateAndShowDockWindow(e.TabContent);
|
||||
}
|
||||
|
||||
private void OnTabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args)
|
||||
{
|
||||
// For static tabs in EngineEditorWindow, we remove the item from TabItems
|
||||
if (sender.TabItems is System.Collections.IList list)
|
||||
{
|
||||
if (list.Contains(args.Item))
|
||||
{
|
||||
var result = TabTearOffService.TryTearOffTab(list, args.Item, (tab) =>
|
||||
{
|
||||
App.CreateAndShowDockWindow(tab);
|
||||
}, sender);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
Logger.LogWarning($"Tab tear-off failed: {result.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemInfo = args.Item is FrameworkElement fe ? fe.GetType().Name : args.Item?.ToString() ?? "unknown";
|
||||
Logger.LogWarning($"OnTabDroppedOutside: Item '{itemInfo}' not found in source TabView (Items count: {list.Count}).");
|
||||
}
|
||||
}
|
||||
// This is now handled by DockLayout.TabTornOff for dynamic tabs.
|
||||
// If we still have static tabs, we'd handle them here.
|
||||
}
|
||||
|
||||
private void MainGrid_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user