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);
}
}
}