From 1d48784a1c44680317be791475df757d8846b71d Mon Sep 17 00:00:00 2001 From: Misaki Date: Sat, 28 Mar 2026 22:35:43 +0900 Subject: [PATCH] fix(docking): improve structural integrity and add null validation --- .../View/Controls/Docking/DockContainer.cs | 40 ++++++++++++++++++- .../View/Controls/Docking/DockPanel.cs | 10 +++-- .../View/Controls/Docking/DockingLayout.cs | 2 + 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs index b616121..8e160eb 100644 --- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs +++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockContainer.cs @@ -9,6 +9,7 @@ namespace Ghost.Editor.View.Controls.Docking; public abstract class DockContainer : DockModule { private readonly ObservableCollection _children = new(); + protected bool _isCleaningUp; /// /// Gets the collection of child modules. /// @@ -81,10 +82,43 @@ public abstract class DockContainer : DockModule { module.Owner = null; module.Root = null; - CheckCleanup(); + if (!_isCleaningUp) + { + CheckCleanup(); + } } } + public virtual void ReplaceChild(DockModule oldChild, DockModule newChild) + { + ArgumentNullException.ThrowIfNull(oldChild); + ArgumentNullException.ThrowIfNull(newChild); + int index = _children.IndexOf(oldChild); + if (index < 0) throw new ArgumentException("oldChild not found"); + + // Detach newChild from its current owner if any + newChild.Owner?.RemoveChild(newChild); + + // Remove oldChild without triggering cleanup + _isCleaningUp = true; + try + { + _children.RemoveAt(index); + oldChild.Owner = null; + oldChild.Root = null; + + newChild.Owner = this; + newChild.Root = Root; + _children.Insert(index, newChild); + } + finally + { + _isCleaningUp = false; + } + OnChildrenUpdated(); + CheckCleanup(); + } + protected virtual void CheckCleanup() { if (_children.Count == 0) @@ -104,6 +138,10 @@ public abstract class DockContainer : DockModule child.Root = null; } _children.Clear(); + if (!_isCleaningUp) + { + CheckCleanup(); + } } protected override void OnRootChanged() diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs index 4f15ada..a3a1503 100644 --- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs +++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockPanel.cs @@ -57,10 +57,12 @@ public class DockPanel : DockContainer if (owner != null) { - int index = owner.Children.IndexOf(this); - owner.RemoveChild(this); - child.Detach(); - owner.InsertChild(index, child); + owner.ReplaceChild(this, child); + } + else if (Root != null && Root.RootPanel == this) + { + // If this is the root panel, we can't easily replace it if the child is a DockGroup, + // because RootPanel must be a DockPanel. So we just leave it. } } } diff --git a/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs b/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs index f41cfe7..db2d82a 100644 --- a/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs +++ b/src/Editor/Ghost.Editor/View/Controls/Docking/DockingLayout.cs @@ -72,6 +72,8 @@ 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) { + ArgumentNullException.ThrowIfNull(document); + if (targetGroup != null && targetGroup.Root != this) { throw new ArgumentException("targetGroup does not belong to this DockingLayout");