fix(dock): refactor mutation logic and fix AddChild regression
This commit is contained in:
@@ -39,6 +39,7 @@ public partial class DockGroupNode : DockNode
|
|||||||
/// <exception cref="InvalidOperationException">Thrown if adding the node would create a cycle or if adding self.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if adding the node would create a cycle or if adding self.</exception>
|
||||||
public void AddChild(DockNode node)
|
public void AddChild(DockNode node)
|
||||||
{
|
{
|
||||||
|
if (_children.Contains(node)) return;
|
||||||
InsertChild(_children.Count, node);
|
InsertChild(_children.Count, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -401,44 +401,42 @@ public sealed partial class DockLayout : Control
|
|||||||
return; // Reordering within same tab is handled natively by TabView
|
return; // Reordering within same tab is handled natively by TabView
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Prepare mutation plan
|
// 1. Execute mutation
|
||||||
bool isHorizontalSplit = _currentDropPosition == DockPosition.Left || _currentDropPosition == DockPosition.Right;
|
if (TryApplyDropMutation(targetNode, _sourceNode, _draggedItem, _currentDropPosition))
|
||||||
bool isAfter = _currentDropPosition == DockPosition.Right || _currentDropPosition == DockPosition.Bottom;
|
|
||||||
|
|
||||||
// 2. Execute mutation
|
|
||||||
if (_currentDropPosition == DockPosition.Center)
|
|
||||||
{
|
{
|
||||||
if (!_sourceNode.Items.Remove(_draggedItem))
|
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
targetNode.Items.Add(_draggedItem);
|
|
||||||
CleanupEmptyNodes(_sourceNode);
|
CleanupEmptyNodes(_sourceNode);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
ClearDragOperationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the tree mutation for a drop operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the mutation was applied; otherwise, false.</returns>
|
||||||
|
private bool TryApplyDropMutation(DockPanelNode targetNode, DockPanelNode sourceNode, object item, DockPosition position)
|
||||||
{
|
{
|
||||||
|
if (position == DockPosition.Center)
|
||||||
|
{
|
||||||
|
if (!sourceNode.Items.Remove(item)) return false;
|
||||||
|
targetNode.Items.Add(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Split scenario
|
// Split scenario
|
||||||
|
bool isHorizontalSplit = position == DockPosition.Left || position == DockPosition.Right;
|
||||||
|
bool isAfter = position == DockPosition.Right || position == DockPosition.Bottom;
|
||||||
|
|
||||||
var parentGroup = targetNode.Parent;
|
var parentGroup = targetNode.Parent;
|
||||||
if (parentGroup == null)
|
if (parentGroup == null)
|
||||||
{
|
{
|
||||||
// Target is root or only child of root.
|
// Target is root or only child of root.
|
||||||
// We must ensure we can wrap it.
|
// In a valid tree, a Panel must have a parent Group (Root is always a Group).
|
||||||
if (Root == null)
|
if (Root == null) return false;
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If targetNode is the only child of Root, we wrap it.
|
|
||||||
if (Root.Children.Count == 1 && ReferenceEquals(Root.Children[0], targetNode))
|
if (Root.Children.Count == 1 && ReferenceEquals(Root.Children[0], targetNode))
|
||||||
{
|
{
|
||||||
// Remove from source first
|
if (!sourceNode.Items.Remove(item)) return false;
|
||||||
if (!_sourceNode.Items.Remove(_draggedItem))
|
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newGroup = new DockGroupNode
|
var newGroup = new DockGroupNode
|
||||||
{
|
{
|
||||||
@@ -446,7 +444,7 @@ public sealed partial class DockLayout : Control
|
|||||||
};
|
};
|
||||||
|
|
||||||
var newNode = new DockPanelNode();
|
var newNode = new DockPanelNode();
|
||||||
newNode.Items.Add(_draggedItem);
|
newNode.Items.Add(item);
|
||||||
|
|
||||||
Root.RemoveChild(targetNode);
|
Root.RemoveChild(targetNode);
|
||||||
Root.AddChild(newGroup);
|
Root.AddChild(newGroup);
|
||||||
@@ -461,42 +459,25 @@ public sealed partial class DockLayout : Control
|
|||||||
newGroup.AddChild(newNode);
|
newGroup.AddChild(newNode);
|
||||||
newGroup.AddChild(targetNode);
|
newGroup.AddChild(targetNode);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CleanupEmptyNodes(_sourceNode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int targetIndex = parentGroup.Children.IndexOf(targetNode);
|
int targetIndex = parentGroup.Children.IndexOf(targetNode);
|
||||||
if (targetIndex < 0)
|
if (targetIndex < 0) return false;
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from source
|
if (!sourceNode.Items.Remove(item)) return false;
|
||||||
if (!_sourceNode.Items.Remove(_draggedItem))
|
|
||||||
{
|
|
||||||
ClearDragOperationState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((isHorizontalSplit && parentGroup.Orientation == Orientation.Horizontal) ||
|
if ((isHorizontalSplit && parentGroup.Orientation == Orientation.Horizontal) ||
|
||||||
(!isHorizontalSplit && parentGroup.Orientation == Orientation.Vertical))
|
(!isHorizontalSplit && parentGroup.Orientation == Orientation.Vertical))
|
||||||
{
|
{
|
||||||
// Same orientation, just insert next to it
|
|
||||||
var newNode = new DockPanelNode();
|
var newNode = new DockPanelNode();
|
||||||
newNode.Items.Add(_draggedItem);
|
newNode.Items.Add(item);
|
||||||
parentGroup.InsertChild(isAfter ? targetIndex + 1 : targetIndex, newNode);
|
parentGroup.InsertChild(isAfter ? targetIndex + 1 : targetIndex, newNode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Different orientation, need to replace targetNode with a new group
|
|
||||||
parentGroup.RemoveChild(targetNode);
|
parentGroup.RemoveChild(targetNode);
|
||||||
|
|
||||||
var newGroup = new DockGroupNode
|
var newGroup = new DockGroupNode
|
||||||
@@ -505,7 +486,7 @@ public sealed partial class DockLayout : Control
|
|||||||
};
|
};
|
||||||
|
|
||||||
var newNode = new DockPanelNode();
|
var newNode = new DockPanelNode();
|
||||||
newNode.Items.Add(_draggedItem);
|
newNode.Items.Add(item);
|
||||||
|
|
||||||
if (isAfter)
|
if (isAfter)
|
||||||
{
|
{
|
||||||
@@ -521,11 +502,7 @@ public sealed partial class DockLayout : Control
|
|||||||
parentGroup.InsertChild(targetIndex, newGroup);
|
parentGroup.InsertChild(targetIndex, newGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
CleanupEmptyNodes(_sourceNode);
|
return true;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearDragOperationState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupEmptyNodes(DockNode node)
|
private void CleanupEmptyNodes(DockNode node)
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class DockingMutationTest
|
|||||||
{
|
{
|
||||||
var onlyChild = group1.Children[0];
|
var onlyChild = group1.Children[0];
|
||||||
var parent = group1.Parent;
|
var parent = group1.Parent;
|
||||||
|
Assert.IsNotNull(parent);
|
||||||
int index = parent.Children.IndexOf(group1);
|
int index = parent.Children.IndexOf(group1);
|
||||||
|
|
||||||
parent.RemoveChild(group1);
|
parent.RemoveChild(group1);
|
||||||
|
|||||||
Reference in New Issue
Block a user