diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs
index 9dabd59..2329ad3 100644
--- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs
@@ -3,6 +3,9 @@ using System.Collections.ObjectModel;
namespace Ghost.Editor.View.Controls.Docking;
+///
+/// Base class for containers that can hold other dock modules.
+///
public abstract class DockContainer : DockModule
{
private readonly ObservableCollection _children = new();
@@ -20,6 +23,11 @@ public abstract class DockContainer : DockModule
}
public virtual void AddChild(DockModule module)
+ {
+ InsertChild(_children.Count, module);
+ }
+
+ public virtual void InsertChild(int index, DockModule module)
{
ArgumentNullException.ThrowIfNull(module);
@@ -42,7 +50,8 @@ public abstract class DockContainer : DockModule
module.Owner?.RemoveChild(module);
module.Owner = this;
- _children.Add(module);
+ module.Root = Root;
+ _children.Insert(index, module);
}
public virtual void RemoveChild(DockModule module)
@@ -52,6 +61,16 @@ public abstract class DockContainer : DockModule
if (_children.Remove(module))
{
module.Owner = null;
+ module.Root = null;
+ CheckCleanup();
+ }
+ }
+
+ protected virtual void CheckCleanup()
+ {
+ if (_children.Count == 0)
+ {
+ Owner?.RemoveChild(this);
}
}
@@ -60,9 +79,18 @@ public abstract class DockContainer : DockModule
foreach (var child in _children)
{
child.Owner = null;
+ child.Root = null;
}
_children.Clear();
}
+
+ protected override void OnRootChanged()
+ {
+ foreach (var child in _children)
+ {
+ child.Root = Root;
+ }
+ }
protected virtual void OnChildrenUpdated() { }
}
diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs
index 0d24f6d..90fd4da 100644
--- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockGroup.cs
@@ -4,6 +4,9 @@ using Microsoft.UI.Xaml.Data;
namespace Ghost.Editor.View.Controls.Docking;
+///
+/// A container that displays its children (documents) as tabs.
+///
[TemplatePart(Name = PART_TAB_VIEW, Type = typeof(TabView))]
public partial class DockGroup : DockContainer
{
@@ -27,9 +30,31 @@ public partial class DockGroup : DockContainer
base.AddChild(module);
}
+ public override void InsertChild(int index, DockModule module)
+ {
+ ArgumentNullException.ThrowIfNull(module);
+
+ if (module is not DockDocument)
+ {
+ throw new ArgumentException($"{nameof(DockGroup)} only accepts {nameof(DockDocument)} children.", nameof(module));
+ }
+
+ base.InsertChild(index, module);
+ }
+
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
+
+ if (_tabView != null)
+ {
+ _tabView.TabDragStarting -= OnTabDragStarting;
+ _tabView.TabDroppedOutside -= OnTabDroppedOutside;
+ _tabView.DragOver -= OnDragOver;
+ _tabView.Drop -= OnDrop;
+ _tabView.DragLeave -= OnDragLeave;
+ }
+
_tabView = GetTemplateChild(PART_TAB_VIEW) as TabView;
if (_tabView != null)
@@ -49,7 +74,6 @@ public partial class DockGroup : DockContainer
if (args.Tab.Tag is DockDocument doc)
{
args.Data.Properties.Add("DockDocument", doc);
- doc.Detach();
}
}
@@ -65,7 +89,7 @@ public partial class DockGroup : DockContainer
{
if (e.DataView.Properties.ContainsKey("DockDocument"))
{
- e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
+ e.AcceptedOperation = global::Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
Root?.ShowHighlight(this, e.GetPosition(this));
}
}
diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs
index 38bc044..f0a6631 100644
--- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockModule.cs
@@ -2,14 +2,32 @@ using Microsoft.UI.Xaml.Controls;
namespace Ghost.Editor.View.Controls.Docking;
+///
+/// Base class for all dockable modules in the docking system.
+///
public abstract class DockModule : Control
{
public DockContainer? Owner { get; internal set; }
+ private DockingLayout? _root;
+
///
/// Gets or sets the root docking layout this module belongs to.
///
- public DockingLayout? Root { get; internal set; }
+ public virtual DockingLayout? Root
+ {
+ get => _root;
+ internal set
+ {
+ if (_root != value)
+ {
+ _root = value;
+ OnRootChanged();
+ }
+ }
+ }
+
+ protected virtual void OnRootChanged() { }
public void Detach()
{
diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs
index 7420275..dd573a6 100644
--- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs
@@ -41,6 +41,27 @@ public class DockPanel : DockContainer
UpdateLayoutStructure();
}
+ protected override void CheckCleanup()
+ {
+ if (Children.Count == 0)
+ {
+ base.CheckCleanup();
+ }
+ else if (Children.Count == 1)
+ {
+ var child = Children[0];
+ var owner = Owner;
+
+ if (owner != null)
+ {
+ int index = owner.Children.IndexOf(this);
+ owner.RemoveChild(this);
+ child.Detach();
+ owner.InsertChild(index, child);
+ }
+ }
+ }
+
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DockPanel)d).UpdateLayoutStructure();
@@ -68,7 +89,7 @@ public class DockPanel : DockContainer
if (i < Children.Count - 1)
{
_grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
- var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Columns, Width = SPLITTER_THICKNESS };
+ var splitter = new CommunityToolkit.WinUI.Controls.GridSplitter { ResizeDirection = CommunityToolkit.WinUI.Controls.GridSplitter.GridResizeDirection.Columns, Width = SPLITTER_THICKNESS };
Grid.SetColumn(splitter, i * 2 + 1);
_grid.Children.Add(splitter);
}
@@ -86,7 +107,7 @@ public class DockPanel : DockContainer
if (i < Children.Count - 1)
{
_grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
- var splitter = new GridSplitter { ResizeDirection = GridSplitter.GridResizeDirection.Rows, Height = SPLITTER_THICKNESS };
+ var splitter = new CommunityToolkit.WinUI.Controls.GridSplitter { ResizeDirection = CommunityToolkit.WinUI.Controls.GridSplitter.GridResizeDirection.Rows, Height = SPLITTER_THICKNESS };
Grid.SetRow(splitter, i * 2 + 1);
_grid.Children.Add(splitter);
}
diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs
index c6115b5..4e1f2e1 100644
--- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs
+++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs
@@ -28,9 +28,7 @@ public class DockingLayout : Control
set => SetValue(RootPanelProperty, value);
}
- // Used in Task 5 for drag and drop highlight
private Canvas? _overlayCanvas;
- // Used in Task 5 for drag and drop highlight
private DockRegionHighlight? _highlight;
public DockingLayout()
@@ -74,11 +72,6 @@ public class DockingLayout : Control
/// The target group to add the document to. If null, a suitable group will be found or created.
public void AddDocument(DockDocument document, DockTarget target, DockGroup? targetGroup = null)
{
- if (target != DockTarget.Center)
- {
- throw new NotImplementedException("Target docking will be implemented in Task 5");
- }
-
if (targetGroup != null && targetGroup.Root != this)
{
throw new ArgumentException("targetGroup does not belong to this DockingLayout");
@@ -100,7 +93,77 @@ public class DockingLayout : Control
}
}
- targetGroup.AddChild(document);
+ if (target == DockTarget.Center)
+ {
+ targetGroup.AddChild(document);
+ }
+ else
+ {
+ SplitGroup(targetGroup, document, target);
+ }
+ }
+
+ private void SplitGroup(DockGroup targetGroup, DockDocument doc, DockTarget target)
+ {
+ var parentPanel = targetGroup.Owner as DockPanel;
+ var newGroup = new DockGroup();
+ newGroup.AddChild(doc);
+
+ var orientation = (target == DockTarget.Left || target == DockTarget.Right) ? Orientation.Horizontal : Orientation.Vertical;
+
+ if (parentPanel == null)
+ {
+ // targetGroup is the root
+ var newPanel = new DockPanel { Orientation = orientation };
+ RootPanel = newPanel;
+
+ if (target == DockTarget.Left || target == DockTarget.Top)
+ {
+ newPanel.AddChild(newGroup);
+ newPanel.AddChild(targetGroup);
+ }
+ else
+ {
+ newPanel.AddChild(targetGroup);
+ newPanel.AddChild(newGroup);
+ }
+ }
+ else
+ {
+ int index = parentPanel.Children.IndexOf(targetGroup);
+
+ if (parentPanel.Orientation == orientation)
+ {
+ // Same orientation, just insert
+ if (target == DockTarget.Left || target == DockTarget.Top)
+ {
+ parentPanel.InsertChild(index, newGroup);
+ }
+ else
+ {
+ parentPanel.InsertChild(index + 1, newGroup);
+ }
+ }
+ else
+ {
+ // Different orientation, need a new sub-panel
+ targetGroup.Detach();
+ var newPanel = new DockPanel { Orientation = orientation };
+
+ if (target == DockTarget.Left || target == DockTarget.Top)
+ {
+ newPanel.AddChild(newGroup);
+ newPanel.AddChild(targetGroup);
+ }
+ else
+ {
+ newPanel.AddChild(targetGroup);
+ newPanel.AddChild(newGroup);
+ }
+
+ parentPanel.InsertChild(index, newPanel);
+ }
+ }
}
private static DockGroup? FindFirstDockGroup(DockContainer container)
@@ -125,7 +188,7 @@ public class DockingLayout : Control
return null;
}
- internal void ShowHighlight(DockGroup targetGroup, Windows.Foundation.Point position)
+ internal void ShowHighlight(DockGroup targetGroup, global::Windows.Foundation.Point position)
{
if (_highlight == null || _overlayCanvas == null) return;
@@ -147,10 +210,10 @@ public class DockingLayout : Control
}
var transform = targetGroup.TransformToVisual(_overlayCanvas);
- var point = transform.TransformPoint(new Windows.Foundation.Point(x, y));
+ var point = transform.TransformPoint(new global::Windows.Foundation.Point(x, y));
- Canvas.SetLeft(_highlight, point.X);
- Canvas.SetTop(_highlight, point.Y);
+ Microsoft.UI.Xaml.Controls.Canvas.SetLeft(_highlight, point.X);
+ Microsoft.UI.Xaml.Controls.Canvas.SetTop(_highlight, point.Y);
_highlight.Width = width;
_highlight.Height = height;
}
@@ -160,45 +223,24 @@ public class DockingLayout : Control
if (_highlight != null) _highlight.Visibility = Visibility.Collapsed;
}
- internal void HandleDrop(DockDocument doc, DockGroup targetGroup, Windows.Foundation.Point position)
+ internal void HandleDrop(DockDocument doc, DockGroup targetGroup, global::Windows.Foundation.Point position)
{
HideHighlight();
var target = CalculateDockTarget(targetGroup, position);
+ doc.Detach();
+
if (target == DockTarget.Center)
{
targetGroup.AddChild(doc);
}
else
{
- // Split logic: create new DockPanel, move targetGroup and doc into it
- var parentPanel = targetGroup.Owner as DockPanel;
- if (parentPanel != null)
- {
- int index = parentPanel.Children.IndexOf(targetGroup);
- targetGroup.Detach();
-
- var newPanel = new DockPanel { Orientation = (target == DockTarget.Left || target == DockTarget.Right) ? Orientation.Horizontal : Orientation.Vertical };
- var newGroup = new DockGroup();
- newGroup.AddChild(doc);
-
- if (target == DockTarget.Left || target == DockTarget.Top)
- {
- newPanel.AddChild(newGroup);
- newPanel.AddChild(targetGroup);
- }
- else
- {
- newPanel.AddChild(targetGroup);
- newPanel.AddChild(newGroup);
- }
-
- parentPanel.Children.Insert(index, newPanel);
- }
+ SplitGroup(targetGroup, doc, target);
}
}
- private DockTarget CalculateDockTarget(DockGroup group, Windows.Foundation.Point position)
+ private DockTarget CalculateDockTarget(DockGroup group, global::Windows.Foundation.Point position)
{
double w = group.ActualWidth;
double h = group.ActualHeight;
@@ -214,6 +256,7 @@ public class DockingLayout : Control
internal void CreateFloatingWindow(DockDocument doc)
{
+ doc.Detach();
// To be implemented in Task 6
}
}