fix(dock): prevent tab loss on invalid drops and improve tree cleanup
This commit is contained in:
@@ -389,18 +389,40 @@ public sealed partial class DockLayout : Control
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_currentDropPosition == DockPosition.None)
|
||||||
|
{
|
||||||
|
ClearDragOperationState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_sourceNode == targetNode && _currentDropPosition == DockPosition.Center)
|
if (_sourceNode == targetNode && _currentDropPosition == DockPosition.Center)
|
||||||
{
|
{
|
||||||
ClearDragOperationState();
|
ClearDragOperationState();
|
||||||
return; // Reordering within same tab is handled natively by TabView
|
return; // Reordering within same tab is handled natively by TabView
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Remove from source
|
// 1. Validate target for split
|
||||||
|
if (_currentDropPosition != DockPosition.Center && targetNode.Parent == null)
|
||||||
|
{
|
||||||
|
// If target has no parent, it must be the root (or wrapped in root).
|
||||||
|
// We can't split it without a parent group unless we wrap it.
|
||||||
|
if (ReferenceEquals(targetNode, Root) || (Root?.Children.Count == 1 && ReferenceEquals(Root.Children[0], targetNode)))
|
||||||
|
{
|
||||||
|
// This is allowed, we'll handle it by wrapping in a new group if needed
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClearDragOperationState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove from source
|
||||||
_sourceNode.Items.Remove(_draggedItem);
|
_sourceNode.Items.Remove(_draggedItem);
|
||||||
var sourceNodeCopy = _sourceNode; // Keep reference for cleanup
|
var sourceNodeCopy = _sourceNode; // Keep reference for cleanup
|
||||||
CleanupEmptyNodes(sourceNodeCopy);
|
CleanupEmptyNodes(sourceNodeCopy);
|
||||||
|
|
||||||
// 2. Add to target
|
// 3. Add to target
|
||||||
if (_currentDropPosition == DockPosition.Center)
|
if (_currentDropPosition == DockPosition.Center)
|
||||||
{
|
{
|
||||||
targetNode.Items.Add(_draggedItem);
|
targetNode.Items.Add(_draggedItem);
|
||||||
@@ -408,12 +430,45 @@ public sealed partial class DockLayout : Control
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Split scenario
|
// Split scenario
|
||||||
|
bool isHorizontalSplit = _currentDropPosition == DockPosition.Left || _currentDropPosition == DockPosition.Right;
|
||||||
|
bool isAfter = _currentDropPosition == DockPosition.Right || _currentDropPosition == DockPosition.Bottom;
|
||||||
|
|
||||||
var parentGroup = targetNode.Parent;
|
var parentGroup = targetNode.Parent;
|
||||||
if (parentGroup != null)
|
if (parentGroup == null)
|
||||||
|
{
|
||||||
|
// Target is root or only child of root. Wrap it.
|
||||||
|
var newRoot = new DockGroupNode
|
||||||
|
{
|
||||||
|
Orientation = isHorizontalSplit ? Orientation.Horizontal : Orientation.Vertical
|
||||||
|
};
|
||||||
|
|
||||||
|
var newNode = new DockPanelNode();
|
||||||
|
newNode.Items.Add(_draggedItem);
|
||||||
|
|
||||||
|
if (ReferenceEquals(targetNode, Root))
|
||||||
|
{
|
||||||
|
Root = newRoot;
|
||||||
|
}
|
||||||
|
else if (Root != null && Root.Children.Count == 1 && ReferenceEquals(Root.Children[0], targetNode))
|
||||||
|
{
|
||||||
|
Root.RemoveChild(targetNode);
|
||||||
|
Root.AddChild(newRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAfter)
|
||||||
|
{
|
||||||
|
newRoot.AddChild(targetNode);
|
||||||
|
newRoot.AddChild(newNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newRoot.AddChild(newNode);
|
||||||
|
newRoot.AddChild(targetNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int targetIndex = parentGroup.Children.IndexOf(targetNode);
|
int targetIndex = parentGroup.Children.IndexOf(targetNode);
|
||||||
bool isHorizontalSplit = _currentDropPosition == DockPosition.Left || _currentDropPosition == DockPosition.Right;
|
|
||||||
bool isAfter = _currentDropPosition == DockPosition.Right || _currentDropPosition == DockPosition.Bottom;
|
|
||||||
|
|
||||||
if ((isHorizontalSplit && parentGroup.Orientation == Orientation.Horizontal) ||
|
if ((isHorizontalSplit && parentGroup.Orientation == Orientation.Horizontal) ||
|
||||||
(!isHorizontalSplit && parentGroup.Orientation == Orientation.Vertical))
|
(!isHorizontalSplit && parentGroup.Orientation == Orientation.Vertical))
|
||||||
@@ -455,44 +510,46 @@ public sealed partial class DockLayout : Control
|
|||||||
ClearDragOperationState();
|
ClearDragOperationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupEmptyNodes(DockPanelNode panelNode)
|
private void CleanupEmptyNodes(DockNode node)
|
||||||
{
|
{
|
||||||
if (panelNode.Items.Count > 0) return;
|
if (node is DockPanelNode panelNode && panelNode.Items.Count > 0) return;
|
||||||
|
if (node is DockGroupNode groupNode && groupNode.Children.Count > 0 && groupNode.Children.Count != 1) return;
|
||||||
|
|
||||||
var parentGroup = panelNode.Parent;
|
var parentGroup = node.Parent;
|
||||||
if (parentGroup != null)
|
|
||||||
|
// Handle collapsing group with 1 child
|
||||||
|
if (node is DockGroupNode collapsingGroup && collapsingGroup.Children.Count == 1)
|
||||||
{
|
{
|
||||||
parentGroup.RemoveChild(panelNode);
|
var onlyChild = collapsingGroup.Children[0];
|
||||||
|
if (parentGroup != null)
|
||||||
// If group only has 1 child left, collapse it
|
|
||||||
if (parentGroup.Children.Count == 1)
|
|
||||||
{
|
{
|
||||||
var onlyChild = parentGroup.Children[0];
|
int groupIndex = parentGroup.Children.IndexOf(collapsingGroup);
|
||||||
var grandParent = parentGroup.Parent;
|
parentGroup.RemoveChild(collapsingGroup);
|
||||||
|
parentGroup.InsertChild(groupIndex, onlyChild);
|
||||||
if (grandParent != null)
|
CleanupEmptyNodes(parentGroup); // Recursively clean parent
|
||||||
|
}
|
||||||
|
else if (collapsingGroup == Root)
|
||||||
|
{
|
||||||
|
collapsingGroup.RemoveChild(onlyChild);
|
||||||
|
if (onlyChild is DockGroupNode newRootGroup)
|
||||||
{
|
{
|
||||||
int groupIndex = grandParent.Children.IndexOf(parentGroup);
|
Root = newRootGroup;
|
||||||
grandParent.RemoveChild(parentGroup);
|
|
||||||
grandParent.InsertChild(groupIndex, onlyChild);
|
|
||||||
}
|
}
|
||||||
else if (parentGroup == Root)
|
else
|
||||||
{
|
{
|
||||||
// If root is collapsing, the only child becomes the new root
|
var wrapperGroup = new DockGroupNode();
|
||||||
parentGroup.RemoveChild(onlyChild);
|
wrapperGroup.AddChild(onlyChild);
|
||||||
if (onlyChild is DockGroupNode newRootGroup)
|
Root = wrapperGroup;
|
||||||
{
|
|
||||||
Root = newRootGroup;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Wrap panel in a new group to keep Root as a GroupNode
|
|
||||||
var wrapperGroup = new DockGroupNode();
|
|
||||||
wrapperGroup.AddChild(onlyChild);
|
|
||||||
Root = wrapperGroup;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty node (Panel or Group)
|
||||||
|
if (parentGroup != null)
|
||||||
|
{
|
||||||
|
parentGroup.RemoveChild(node);
|
||||||
|
CleanupEmptyNodes(parentGroup); // Recursively clean parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user