fix(dock): address reviewer feedback on drag-and-drop highlighting
This commit is contained in:
@@ -17,13 +17,15 @@ public sealed partial class DockLayout : Control
|
|||||||
{
|
{
|
||||||
private const string PART_ROOT_GRID = "PART_RootGrid";
|
private const string PART_ROOT_GRID = "PART_RootGrid";
|
||||||
private const string PART_DROP_TARGET_OVERLAY = "PART_DropTargetOverlay";
|
private const string PART_DROP_TARGET_OVERLAY = "PART_DropTargetOverlay";
|
||||||
|
private const string DRAG_PROPERTY_DOCK_TAB = "DockTab";
|
||||||
private const double MIN_PANE_SIZE = 100;
|
private const double MIN_PANE_SIZE = 100;
|
||||||
private const double SPLITTER_THICKNESS = 4;
|
private const double SPLITTER_THICKNESS = 4;
|
||||||
|
private const double DROP_EDGE_THRESHOLD = 0.25;
|
||||||
|
|
||||||
private FrameworkElement? _dropTargetOverlay;
|
private FrameworkElement? _dropTargetOverlay;
|
||||||
private readonly HashSet<DockNode> _subscribedNodes = new();
|
private readonly HashSet<DockNode> _subscribedNodes = new();
|
||||||
|
|
||||||
internal enum DockPosition { Center, Top, Bottom, Left, Right, None }
|
public enum DockPosition { Center, Top, Bottom, Left, Right, None }
|
||||||
|
|
||||||
public DockLayout()
|
public DockLayout()
|
||||||
{
|
{
|
||||||
@@ -32,6 +34,19 @@ public sealed partial class DockLayout : Control
|
|||||||
Unloaded += OnUnloaded;
|
Unloaded += OnUnloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the dock position based on the relative position within a target element.
|
||||||
|
/// Precedence: Left/Right win over Top/Bottom at corners.
|
||||||
|
/// </summary>
|
||||||
|
public static DockPosition CalculateDockPosition(double width, double height, double x, double y, double threshold)
|
||||||
|
{
|
||||||
|
if (x < width * threshold) return DockPosition.Left;
|
||||||
|
if (x > width * (1 - threshold)) return DockPosition.Right;
|
||||||
|
if (y < height * threshold) return DockPosition.Top;
|
||||||
|
if (y > height * (1 - threshold)) return DockPosition.Bottom;
|
||||||
|
return DockPosition.Center;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Root != null)
|
if (Root != null)
|
||||||
@@ -302,38 +317,40 @@ public sealed partial class DockLayout : Control
|
|||||||
{
|
{
|
||||||
_draggedItem = args.Item;
|
_draggedItem = args.Item;
|
||||||
_sourceNode = sender.Tag as DockPanelNode;
|
_sourceNode = sender.Tag as DockPanelNode;
|
||||||
args.Data.Properties.Add("DockTab", _draggedItem); // Identify our drag
|
args.Data.Properties.Add(DRAG_PROPERTY_DOCK_TAB, _draggedItem); // Identify our drag
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TabView_DragOver(object sender, DragEventArgs e)
|
private void TabView_DragOver(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.DataView.Properties.ContainsKey("DockTab") && sender is FrameworkElement targetElement)
|
if (e.DataView.Properties.ContainsKey(DRAG_PROPERTY_DOCK_TAB) && sender is FrameworkElement targetElement)
|
||||||
{
|
{
|
||||||
e.AcceptedOperation = global::Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
|
e.AcceptedOperation = global::Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
|
||||||
|
|
||||||
var position = e.GetPosition(targetElement);
|
var position = e.GetPosition(targetElement);
|
||||||
double width = targetElement.ActualWidth;
|
var newPosition = CalculateDockPosition(targetElement.ActualWidth, targetElement.ActualHeight, position.X, position.Y, DROP_EDGE_THRESHOLD);
|
||||||
double height = targetElement.ActualHeight;
|
|
||||||
|
|
||||||
double edgeThreshold = 0.25; // 25% of edge triggers split
|
if (newPosition != _currentDropPosition)
|
||||||
|
{
|
||||||
if (position.X < width * edgeThreshold) _currentDropPosition = DockPosition.Left;
|
_currentDropPosition = newPosition;
|
||||||
else if (position.X > width * (1 - edgeThreshold)) _currentDropPosition = DockPosition.Right;
|
UpdateDropOverlay(targetElement, _currentDropPosition);
|
||||||
else if (position.Y < height * edgeThreshold) _currentDropPosition = DockPosition.Top;
|
}
|
||||||
else if (position.Y > height * (1 - edgeThreshold)) _currentDropPosition = DockPosition.Bottom;
|
|
||||||
else _currentDropPosition = DockPosition.Center;
|
|
||||||
|
|
||||||
UpdateDropOverlay(targetElement, _currentDropPosition);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TabView_DragLeave(object sender, DragEventArgs e)
|
private void TabView_DragLeave(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
ClearDragState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearDragState()
|
||||||
{
|
{
|
||||||
if (_dropTargetOverlay != null)
|
if (_dropTargetOverlay != null)
|
||||||
{
|
{
|
||||||
_dropTargetOverlay.Visibility = Visibility.Collapsed;
|
_dropTargetOverlay.Visibility = Visibility.Collapsed;
|
||||||
_currentDropPosition = DockPosition.None;
|
|
||||||
}
|
}
|
||||||
|
_currentDropPosition = DockPosition.None;
|
||||||
|
_draggedItem = null;
|
||||||
|
_sourceNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateDropOverlay(FrameworkElement targetElement, DockPosition position)
|
private void UpdateDropOverlay(FrameworkElement targetElement, DockPosition position)
|
||||||
@@ -375,6 +392,7 @@ public sealed partial class DockLayout : Control
|
|||||||
// Add a dummy TabView_Drop method so it compiles, we will implement it in Task 6
|
// Add a dummy TabView_Drop method so it compiles, we will implement it in Task 6
|
||||||
private void TabView_Drop(object sender, DragEventArgs e)
|
private void TabView_Drop(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
|
ClearDragState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
protected override void OnApplyTemplate()
|
||||||
|
|||||||
62
src/Test/Ghost.UnitTest/DockLayoutTest.cs
Normal file
62
src/Test/Ghost.UnitTest/DockLayoutTest.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Ghost.Editor.View.Controls;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace Ghost.UnitTest;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class DockLayoutTest
|
||||||
|
{
|
||||||
|
private const double THRESHOLD = 0.25;
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_Center()
|
||||||
|
{
|
||||||
|
// 100x100, threshold 0.25 -> Center is [25, 75]
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 50, 50, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Center, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_Left()
|
||||||
|
{
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 10, 50, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Left, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_Right()
|
||||||
|
{
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 90, 50, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Right, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_Top()
|
||||||
|
{
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 50, 10, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Top, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_Bottom()
|
||||||
|
{
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 50, 90, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Bottom, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_CornerPrecedence_LeftTop()
|
||||||
|
{
|
||||||
|
// (10, 10) is in both Left and Top zones.
|
||||||
|
// Current implementation: Left/Right win over Top/Bottom.
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 10, 10, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Left, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCalculateDockPosition_CornerPrecedence_RightBottom()
|
||||||
|
{
|
||||||
|
var pos = DockLayout.CalculateDockPosition(100, 100, 90, 90, THRESHOLD);
|
||||||
|
Assert.AreEqual(DockLayout.DockPosition.Right, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Editor\Ghost.Editor.Core\Ghost.Editor.Core.csproj" />
|
<ProjectReference Include="..\..\Editor\Ghost.Editor.Core\Ghost.Editor.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Editor\Ghost.Editor\Ghost.Editor.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user