diff --git a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs index 64681c0..df2f142 100644 --- a/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs +++ b/src/Editor/Ghost.Editor/View/Controls/DockLayout.cs @@ -297,6 +297,128 @@ public sealed partial class DockLayout : Control return tabView; } + private object? _draggedItem; + private DockPanelNode? _sourceNode; + private DockPosition _currentDropPosition = DockPosition.None; + + private void TabView_TabDragStarting(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDragStartingEventArgs args) + { + _draggedItem = args.Item; + _sourceNode = sender.Tag as DockPanelNode; + args.Data.Properties.Add(DRAG_PROPERTY_DOCK_TAB, _draggedItem); // Identify our drag + } + + private void TabView_DragOver(object sender, DragEventArgs e) + { + if (e.DataView.Properties.ContainsKey(DRAG_PROPERTY_DOCK_TAB) && sender is FrameworkElement targetElement) + { + e.AcceptedOperation = global::Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move; + + var position = e.GetPosition(targetElement); + var newPosition = DockMath.CalculateDockPosition(targetElement.ActualWidth, targetElement.ActualHeight, position.X, position.Y, DROP_EDGE_THRESHOLD); + + if (newPosition != _currentDropPosition) + { + _currentDropPosition = newPosition; + UpdateDropOverlay(targetElement, _currentDropPosition); + } + } + } + + private void TabView_DragLeave(object sender, DragEventArgs e) + { + ClearOverlayState(); + } + + private void ClearOverlayState() + { + if (_dropTargetOverlay != null) + { + _dropTargetOverlay.Visibility = Visibility.Collapsed; + } + _currentDropPosition = DockPosition.None; + } + + private void ClearDragOperationState() + { + ClearOverlayState(); + _draggedItem = null; + _sourceNode = null; + } + + private void UpdateDropOverlay(FrameworkElement targetElement, DockPosition position) + { + if (_dropTargetOverlay == null) return; + if (position == DockPosition.None) + { + _dropTargetOverlay.Visibility = Visibility.Collapsed; + return; + } + + var transform = targetElement.TransformToVisual(this); + var bounds = transform.TransformBounds(new global::Windows.Foundation.Rect(0, 0, targetElement.ActualWidth, targetElement.ActualHeight)); + + _dropTargetOverlay.Visibility = Visibility.Visible; + _dropTargetOverlay.Width = double.NaN; + _dropTargetOverlay.Height = double.NaN; + + switch (position) + { + case DockPosition.Center: + _dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom); + break; + case DockPosition.Left: + _dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - (bounds.Left + bounds.Width / 2), ActualHeight - bounds.Bottom); + break; + case DockPosition.Right: + _dropTargetOverlay.Margin = new Thickness(bounds.Left + bounds.Width / 2, bounds.Top, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom); + break; + case DockPosition.Top: + _dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top, ActualWidth - bounds.Right, ActualHeight - (bounds.Top + bounds.Height / 2)); + break; + case DockPosition.Bottom: + _dropTargetOverlay.Margin = new Thickness(bounds.Left, bounds.Top + bounds.Height / 2, ActualWidth - bounds.Right, ActualHeight - bounds.Bottom); + break; + } + } + + private void TabView_Drop(object sender, DragEventArgs e) + { + if (_dropTargetOverlay != null) _dropTargetOverlay.Visibility = Visibility.Collapsed; + + if (_draggedItem == null || _sourceNode == null || !(sender is FrameworkElement targetElement) || !(targetElement.Tag is DockPanelNode targetNode)) + { + ClearDragOperationState(); + return; + } + + if (_currentDropPosition == DockPosition.None) + { + ClearDragOperationState(); + return; + } + + if (_sourceNode == targetNode && _currentDropPosition == DockPosition.Center) + { + ClearDragOperationState(); + return; // Reordering within same tab is handled natively by TabView + } + + if (Root == null) + { + ClearDragOperationState(); + return; + } + + // 1. Execute mutation + if (DockMutationEngine.TryApplyDropMutation(Root, targetNode, _sourceNode, _draggedItem, _currentDropPosition)) + { + DockMutationEngine.CleanupEmptyNodes(_sourceNode); + } + + ClearDragOperationState(); + } + private void TabView_TabDroppedOutside(Microsoft.UI.Xaml.Controls.TabView sender, Microsoft.UI.Xaml.Controls.TabViewTabDroppedOutsideEventArgs args) { if (_sourceNode != null && _draggedItem != null) diff --git a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs index c494038..8fe57ba 100644 --- a/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs +++ b/src/Editor/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs @@ -41,9 +41,9 @@ internal sealed partial class EngineEditorWindow : WindowEx 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.Contains(args.Item)) + if (sender.TabItems is System.Collections.IList list && list.Contains(args.Item)) { - var result = TabTearOffService.TryTearOffTab((System.Collections.IList)sender.TabItems, args.Item, sender); + var result = TabTearOffService.TryTearOffTab(list, args.Item, sender); if (!result.IsSuccess) {