using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using TeamAAS.FlowEditor.Models; namespace TeamAAS.FlowEditor.Controls { /// /// 节点拖拽手柄 - 处理节点选择和拖动 /// public class MoveThumb : Thumb { private FlowCanvas _canvas; // 拖拽锚点:起点时鼠标在画布坐标系的位置 + 各节点的起始坐标。 // 用 Mouse.GetPosition(画布) 实时计算位移,而不是累加 DragDelta—— // 后者在 PerMonitorV2 + 高 DPI 下是物理像素,会导致节点移动速度 ≠ 鼠标(4K 200% 缩放下节点跑飞)。 private Point _dragStartMouseOnCanvas; private readonly List _dragNodes = new List(); private readonly List _startXs = new List(); private readonly List _startYs = new List(); private int _diagCounter; // TODO 拖拽诊断,问题定位后删除 public MoveThumb() { DragStarted += OnDragStartedHandler; DragDelta += OnDragDeltaHandler; DragCompleted += OnDragCompletedHandler; } private FlowCanvas FindCanvas() { if (_canvas != null) return _canvas; DependencyObject parent = VisualTreeHelper.GetParent(this); while (parent != null && !(parent is FlowCanvas)) parent = VisualTreeHelper.GetParent(parent); _canvas = parent as FlowCanvas; return _canvas; } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { // 选择由 FlowCanvas.OnMouseLeftButtonDown 统一处理(含 Ctrl 多选) base.OnMouseLeftButtonDown(e); } private void OnDragStartedHandler(object sender, DragStartedEventArgs e) { var canvas = FindCanvas(); var node = DataContext as FlowNode; if (canvas == null || node == null) return; _dragStartMouseOnCanvas = Mouse.GetPosition(canvas); _dragNodes.Clear(); _startXs.Clear(); _startYs.Clear(); var selectedNodes = canvas.GetSelectedNodes(); if (selectedNodes != null && selectedNodes.Count > 1 && selectedNodes.Contains(node)) { foreach (var selNode in selectedNodes) { _dragNodes.Add(selNode); _startXs.Add(selNode.X); _startYs.Add(selNode.Y); } } else { _dragNodes.Add(node); _startXs.Add(node.X); _startYs.Add(node.Y); } // TODO 拖拽诊断,问题定位后删除 AppLogger.Info($"[DragDiag] started node={node.NodeName} mouseOnCanvas=({_dragStartMouseOnCanvas.X:F1},{_dragStartMouseOnCanvas.Y:F1}) nodePos=({node.X:F1},{node.Y:F1})", "DragDiag"); } private void OnDragDeltaHandler(object sender, DragDeltaEventArgs e) { var node = DataContext as FlowNode; if (node == null) return; var canvas = FindCanvas(); // 流程运行中 / 只读监控模式禁止移动节点 if (canvas != null && (canvas.IsRunning || canvas.IsReadOnly)) return; if (_dragNodes.Count == 0) return; // 鼠标相对拖拽起点的位移(画布本地坐标,DPI / 缩放换算均正确) var mouse = Mouse.GetPosition(canvas); double dx = mouse.X - _dragStartMouseOnCanvas.X; double dy = mouse.Y - _dragStartMouseOnCanvas.Y; // TODO 拖拽诊断,问题定位后删除 if ((++_diagCounter % 10) == 0) AppLogger.Info($"[DragDiag] delta mouse=({mouse.X:F1},{mouse.Y:F1}) d=({dx:F1},{dy:F1}) node0=({_dragNodes[0].X:F1},{_dragNodes[0].Y:F1}) startPos=({_startXs[0]:F1},{_startYs[0]:F1})", "DragDiag"); if (_dragNodes.Count > 1) { // 整组硬墙语义:先预判每个成员的目标位置, // 只要有一个成员越出画布边界、或与非同组成员的节点碰撞, // 整组本次一动不动(碰壁即停),之后把鼠标拖回自由方向才能继续移动。 // 同组成员以相同位移整体平移、相对位置不变,彼此不可能相撞,故不算障碍。 for (int i = 0; i < _dragNodes.Count; i++) { double sx = _startXs[i] + dx; double sy = _startYs[i] + dy; if (sx < 0 || sy < 0 || canvas.CheckNodeCollision(_dragNodes[i], Math.Max(0, sx), Math.Max(0, sy), _dragNodes)) return; } for (int i = 0; i < _dragNodes.Count; i++) { _dragNodes[i].X = Math.Max(0, _startXs[i] + dx); _dragNodes[i].Y = Math.Max(0, _startYs[i] + dy); } canvas.UpdateCanvasSize(); return; } // 单选拖拽 double newX = Math.Max(0, _startXs[0] + dx); double newY = Math.Max(0, _startYs[0] + dy); // 节点碰撞检测 if (canvas != null) { if (canvas.CheckNodeCollision(node, newX, newY)) { // 尝试只移动X if (!canvas.CheckNodeCollision(node, newX, node.Y)) node.X = newX; // 尝试只移动Y else if (!canvas.CheckNodeCollision(node, node.X, newY)) node.Y = newY; return; } // 对齐引导线检测 canvas.CheckAlignment(node, ref newX, ref newY); } node.X = newX; node.Y = newY; // 自动拉线预览 canvas?.UpdateAutoConnectPreview(node); // 通知画布更新大小 canvas?.UpdateCanvasSize(); } private void OnDragCompletedHandler(object sender, DragCompletedEventArgs e) { var canvas = FindCanvas(); if (canvas == null) return; canvas.ClearAlignLines(); // 自动拉线:仅单节点拖拽时生效(多节点移动不触发) var node = DataContext as FlowNode; var selectedNodes = canvas.GetSelectedNodes(); if (node != null && (selectedNodes == null || selectedNodes.Count <= 1)) canvas.TryAutoConnect(node); } } }