You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
717 lines
25 KiB
717 lines
25 KiB
using ReactiveUI.Avalonia;
|
|
using ReactiveUI;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Presenters;
|
|
using Avalonia.Controls.Shapes;
|
|
using Avalonia.Input;
|
|
using Avalonia;
|
|
using Avalonia.Media;
|
|
using Avalonia.Collections;
|
|
using Avalonia.VisualTree;
|
|
using AuroraDesk.Presentation.ViewModels.Pages;
|
|
using AuroraDesk.Core.Entities;
|
|
using System.Linq;
|
|
using System.Reactive.Disposables;
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Interactivity;
|
|
|
|
namespace AuroraDesk.Presentation.Views.Pages;
|
|
|
|
public partial class NodeCanvasPageView : ReactiveUserControl<NodeCanvasPageViewModel>
|
|
{
|
|
private Canvas? _canvasContainer;
|
|
private Canvas? _gridBackgroundLayer;
|
|
private ScrollViewer? _canvasScrollViewer;
|
|
private Node? _draggedNode;
|
|
private Point? _dragStartPoint;
|
|
private ConnectionPoint? _hoveredConnectionPoint;
|
|
private Path? _tempConnectionLine;
|
|
private bool _isDraggingCanvas;
|
|
private Point? _canvasDragStartPoint;
|
|
private double _canvasDragStartOffsetX;
|
|
private double _canvasDragStartOffsetY;
|
|
private bool _isSpaceKeyPressed;
|
|
private const double GridSize = 20;
|
|
|
|
|
|
public NodeCanvasPageView()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
AvaloniaXamlLoader.Load(this);
|
|
|
|
// 延迟初始化,等待加载完成
|
|
this.Loaded += (s, e) =>
|
|
{
|
|
_canvasContainer = this.FindControl<Canvas>("CanvasContainer");
|
|
_gridBackgroundLayer = this.FindControl<Canvas>("GridBackgroundLayer");
|
|
_canvasScrollViewer = this.FindControl<ScrollViewer>("CanvasScrollViewer");
|
|
|
|
if (_canvasContainer != null)
|
|
{
|
|
_canvasContainer.PointerPressed += OnCanvasPointerPressed;
|
|
_canvasContainer.PointerMoved += OnCanvasPointerMoved;
|
|
_canvasContainer.PointerReleased += OnCanvasPointerReleased;
|
|
|
|
// 监听 Canvas 尺寸变化,更新网格
|
|
_canvasContainer.SizeChanged += (sender, args) => DrawGridBackground();
|
|
}
|
|
|
|
// 在 ScrollViewer 上添加事件支持
|
|
if (_canvasScrollViewer != null)
|
|
{
|
|
// 添加鼠标滚轮缩放支持(需要按住Ctrl键)
|
|
_canvasScrollViewer.PointerWheelChanged += OnScrollViewerPointerWheelChanged;
|
|
// 添加画布拖动支持(鼠标中键或空格键+左键)
|
|
_canvasScrollViewer.PointerPressed += OnScrollViewerPointerPressed;
|
|
_canvasScrollViewer.PointerMoved += OnScrollViewerPointerMoved;
|
|
_canvasScrollViewer.PointerReleased += OnScrollViewerPointerReleased;
|
|
}
|
|
|
|
// 添加键盘事件监听(空格键)
|
|
var topLevel = TopLevel.GetTopLevel(this);
|
|
if (topLevel != null)
|
|
{
|
|
topLevel.KeyDown += OnKeyDown;
|
|
topLevel.KeyUp += OnKeyUp;
|
|
}
|
|
|
|
// 绘制网格背景
|
|
DrawGridBackground();
|
|
|
|
// 监听连接变化,更新连接线
|
|
if (ViewModel != null)
|
|
{
|
|
// 监听 Connections 集合变化
|
|
ViewModel.WhenAnyValue(x => x.Connections)
|
|
.Subscribe(_ => UpdateConnectionLines());
|
|
|
|
// 监听 Nodes 集合变化
|
|
ViewModel.WhenAnyValue(x => x.Nodes)
|
|
.Subscribe(nodes =>
|
|
{
|
|
if (nodes != null)
|
|
{
|
|
UpdateConnectionLines();
|
|
|
|
// 为现有节点订阅位置变化
|
|
foreach (var node in nodes)
|
|
{
|
|
SubscribeToNodePositionChanges(node);
|
|
}
|
|
|
|
// 订阅集合内容变化事件
|
|
nodes.CollectionChanged += (sender, e) =>
|
|
{
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (Node node in e.NewItems)
|
|
{
|
|
SubscribeToNodePositionChanges(node);
|
|
}
|
|
}
|
|
|
|
UpdateConnectionLines();
|
|
|
|
// 强制刷新 ItemsControl 并更新节点位置
|
|
var itemsControl = this.FindControl<ItemsControl>("NodesItemsControl");
|
|
if (itemsControl != null)
|
|
{
|
|
Avalonia.Threading.Dispatcher.UIThread.Post(async () =>
|
|
{
|
|
itemsControl.InvalidateMeasure();
|
|
itemsControl.InvalidateArrange();
|
|
|
|
// 等待容器创建
|
|
await Task.Delay(100);
|
|
|
|
// 手动更新所有节点的 Canvas 位置(重试机制)
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
UpdateNodePositions(itemsControl, nodes);
|
|
if (i < 2) await Task.Delay(50);
|
|
}
|
|
|
|
// 如果有新节点,滚动到第一个新节点位置
|
|
if (e.NewItems != null && e.NewItems.Count > 0)
|
|
{
|
|
var firstNode = e.NewItems[0] as Node;
|
|
if (firstNode != null)
|
|
{
|
|
await Task.Delay(50);
|
|
ScrollToNode(firstNode);
|
|
}
|
|
}
|
|
}, Avalonia.Threading.DispatcherPriority.Normal);
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
ViewModel.WhenAnyValue(x => x.IsConnecting, x => x.ConnectingSourcePoint)
|
|
.Subscribe(_ => UpdateConnectionLines());
|
|
}
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理模板双击事件,添加节点到画布
|
|
/// </summary>
|
|
private void OnTemplateButtonDoubleTapped(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is Button button && button.DataContext is NodeTemplate template && ViewModel != null)
|
|
{
|
|
ViewModel.AddNodeToCanvasCommand.Execute(template).Subscribe();
|
|
}
|
|
e.Handled = true;
|
|
}
|
|
|
|
|
|
private void UpdateConnectionLines()
|
|
{
|
|
if (_canvasContainer == null || ViewModel == null) return;
|
|
|
|
// 移除临时连接线
|
|
if (_tempConnectionLine != null && _canvasContainer.Children.Contains(_tempConnectionLine))
|
|
{
|
|
_canvasContainer.Children.Remove(_tempConnectionLine);
|
|
_tempConnectionLine = null;
|
|
}
|
|
|
|
// 更新所有连接线
|
|
var connections = ViewModel.Connections.ToList();
|
|
foreach (var connection in connections)
|
|
{
|
|
UpdateConnectionLine(connection);
|
|
}
|
|
|
|
// 如果正在连接,显示临时连接线
|
|
if (ViewModel.IsConnecting && ViewModel.ConnectingSourcePoint != null)
|
|
{
|
|
ShowTempConnectionLine(ViewModel.ConnectingSourcePoint);
|
|
}
|
|
}
|
|
|
|
private void UpdateConnectionLine(Connection connection)
|
|
{
|
|
if (_canvasContainer == null || connection.SourcePoint == null || connection.TargetPoint == null)
|
|
return;
|
|
|
|
var sourcePoint = GetConnectionPointPosition(connection.SourcePoint);
|
|
var targetPoint = GetConnectionPointPosition(connection.TargetPoint);
|
|
|
|
// 查找或创建连接线Path
|
|
var path = _canvasContainer.Children.OfType<Path>()
|
|
.FirstOrDefault(p => p.Tag == connection);
|
|
|
|
if (path == null)
|
|
{
|
|
path = new Path
|
|
{
|
|
Stroke = new SolidColorBrush(Color.FromRgb(52, 152, 219)),
|
|
StrokeThickness = 2,
|
|
Tag = connection
|
|
};
|
|
_canvasContainer.Children.Add(path);
|
|
}
|
|
|
|
// 创建贝塞尔曲线
|
|
var geometry = new PathGeometry();
|
|
var figure = new PathFigure
|
|
{
|
|
StartPoint = sourcePoint
|
|
};
|
|
|
|
var controlPoint1 = new Point(sourcePoint.X + 50, sourcePoint.Y);
|
|
var controlPoint2 = new Point(targetPoint.X - 50, targetPoint.Y);
|
|
var bezierSegment = new BezierSegment
|
|
{
|
|
Point1 = controlPoint1,
|
|
Point2 = controlPoint2,
|
|
Point3 = targetPoint
|
|
};
|
|
|
|
figure.Segments.Add(bezierSegment);
|
|
geometry.Figures.Add(figure);
|
|
path.Data = geometry;
|
|
}
|
|
|
|
private void ShowTempConnectionLine(ConnectionPoint sourcePoint)
|
|
{
|
|
if (_canvasContainer == null) return;
|
|
|
|
var startPoint = GetConnectionPointPosition(sourcePoint);
|
|
var endPoint = new Point(startPoint.X + 100, startPoint.Y);
|
|
|
|
if (_tempConnectionLine == null)
|
|
{
|
|
_tempConnectionLine = new Path
|
|
{
|
|
Stroke = new SolidColorBrush(Color.FromRgb(52, 152, 219)),
|
|
StrokeThickness = 2,
|
|
StrokeDashArray = new AvaloniaList<double> { 5, 5 }
|
|
};
|
|
_canvasContainer.Children.Add(_tempConnectionLine);
|
|
}
|
|
|
|
var geometry = new PathGeometry();
|
|
var figure = new PathFigure { StartPoint = startPoint };
|
|
var lineSegment = new LineSegment { Point = endPoint };
|
|
figure.Segments.Add(lineSegment);
|
|
geometry.Figures.Add(figure);
|
|
_tempConnectionLine.Data = geometry;
|
|
}
|
|
|
|
private Point GetConnectionPointPosition(ConnectionPoint point)
|
|
{
|
|
if (point.Node == null || _canvasContainer == null)
|
|
return new Point(0, 0);
|
|
|
|
var nodeX = point.Node.X;
|
|
var nodeY = point.Node.Y;
|
|
var nodeHeight = point.Node.Height;
|
|
|
|
// 计算连接点在Canvas上的绝对位置
|
|
if (point.Type == ConnectionPointType.Input)
|
|
{
|
|
// 输入点在左侧,垂直居中
|
|
var inputIndex = point.Node.ConnectionPoints
|
|
.Where(p => p.Type == ConnectionPointType.Input)
|
|
.OrderBy(p => p.Index)
|
|
.ToList()
|
|
.IndexOf(point);
|
|
|
|
var spacing = nodeHeight / (point.Node.ConnectionPoints.Count(p => p.Type == ConnectionPointType.Input) + 1);
|
|
return new Point(nodeX, nodeY + spacing * (inputIndex + 1));
|
|
}
|
|
else
|
|
{
|
|
// 输出点在右侧,垂直排列
|
|
var outputIndex = point.Node.ConnectionPoints
|
|
.Where(p => p.Type == ConnectionPointType.Output)
|
|
.OrderBy(p => p.Index)
|
|
.ToList()
|
|
.IndexOf(point);
|
|
|
|
var spacing = nodeHeight / (point.Node.ConnectionPoints.Count(p => p.Type == ConnectionPointType.Output) + 1);
|
|
return new Point(nodeX + point.Node.Width, nodeY + spacing * (outputIndex + 1));
|
|
}
|
|
}
|
|
|
|
private void OnCanvasPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
if (_canvasContainer == null || ViewModel == null) return;
|
|
|
|
var point = e.GetPosition(_canvasContainer);
|
|
var source = e.Source as Control;
|
|
var properties = e.GetCurrentPoint(_canvasContainer).Properties;
|
|
|
|
// 检查是否是画布拖动(鼠标中键或空格键+左键)
|
|
bool isMiddleButton = properties.IsMiddleButtonPressed;
|
|
|
|
if (isMiddleButton || (_isSpaceKeyPressed && properties.IsLeftButtonPressed))
|
|
{
|
|
// 开始拖动画布
|
|
_isDraggingCanvas = true;
|
|
_canvasDragStartPoint = e.GetPosition(_canvasScrollViewer);
|
|
_canvasDragStartOffsetX = ViewModel.CanvasOffsetX;
|
|
_canvasDragStartOffsetY = ViewModel.CanvasOffsetY;
|
|
if (_canvasScrollViewer != null)
|
|
{
|
|
_canvasScrollViewer.Cursor = new Cursor(StandardCursorType.Hand);
|
|
}
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
|
|
// 检查是否点击了连接点
|
|
var connectionPoint = FindConnectionPoint(source);
|
|
if (connectionPoint != null)
|
|
{
|
|
HandleConnectionPointClick(connectionPoint);
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
|
|
// 检查是否点击了节点(但不是连接点)
|
|
var node = FindNode(source);
|
|
if (node != null && connectionPoint == null)
|
|
{
|
|
ViewModel.SelectedNode = node;
|
|
_draggedNode = node;
|
|
_dragStartPoint = point;
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
|
|
// 点击空白区域,取消选中和连接
|
|
ViewModel.SelectedNode = null;
|
|
ViewModel.CancelConnection();
|
|
|
|
// 注意:点击画布空白处不再自动添加节点,用户需要从组件库拖拽或点击空白处两次来添加节点
|
|
// 这样可以确保属性面板始终显示正确的状态(未选中对象)
|
|
}
|
|
|
|
private void OnCanvasPointerMoved(object? sender, PointerEventArgs e)
|
|
{
|
|
if (_canvasContainer == null || ViewModel == null) return;
|
|
|
|
var point = e.GetPosition(_canvasContainer);
|
|
|
|
// 拖动画布
|
|
if (_isDraggingCanvas && _canvasDragStartPoint.HasValue && _canvasScrollViewer != null)
|
|
{
|
|
var currentPoint = e.GetPosition(_canvasScrollViewer);
|
|
var deltaX = currentPoint.X - _canvasDragStartPoint.Value.X;
|
|
var deltaY = currentPoint.Y - _canvasDragStartPoint.Value.Y;
|
|
|
|
// 考虑缩放因子,调整偏移量
|
|
var zoomFactor = ViewModel.CanvasZoom;
|
|
ViewModel.CanvasOffsetX = _canvasDragStartOffsetX + deltaX / zoomFactor;
|
|
ViewModel.CanvasOffsetY = _canvasDragStartOffsetY + deltaY / zoomFactor;
|
|
return;
|
|
}
|
|
|
|
// 拖拽节点
|
|
if (_draggedNode != null && _dragStartPoint.HasValue)
|
|
{
|
|
var deltaX = point.X - _dragStartPoint.Value.X;
|
|
var deltaY = point.Y - _dragStartPoint.Value.Y;
|
|
ViewModel.UpdateNodePosition(_draggedNode,
|
|
_draggedNode.X + deltaX,
|
|
_draggedNode.Y + deltaY);
|
|
_dragStartPoint = point;
|
|
}
|
|
|
|
// 如果正在连接,更新临时连接线
|
|
if (ViewModel.IsConnecting && ViewModel.ConnectingSourcePoint != null)
|
|
{
|
|
var sourcePos = GetConnectionPointPosition(ViewModel.ConnectingSourcePoint);
|
|
if (_tempConnectionLine != null)
|
|
{
|
|
var geometry = new PathGeometry();
|
|
var figure = new PathFigure { StartPoint = sourcePos };
|
|
var lineSegment = new LineSegment { Point = point };
|
|
figure.Segments.Add(lineSegment);
|
|
geometry.Figures.Add(figure);
|
|
_tempConnectionLine.Data = geometry;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnCanvasPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
{
|
|
if (_isDraggingCanvas)
|
|
{
|
|
_isDraggingCanvas = false;
|
|
_canvasDragStartPoint = null;
|
|
if (_canvasScrollViewer != null)
|
|
{
|
|
_canvasScrollViewer.Cursor = Cursor.Default;
|
|
}
|
|
}
|
|
|
|
_draggedNode = null;
|
|
_dragStartPoint = null;
|
|
}
|
|
|
|
private void HandleConnectionPointClick(ConnectionPoint connectionPoint)
|
|
{
|
|
if (ViewModel == null) return;
|
|
|
|
if (ViewModel.IsConnecting)
|
|
{
|
|
// 完成连接
|
|
if (connectionPoint.Type == ConnectionPointType.Input)
|
|
{
|
|
ViewModel.CompleteConnectionCommand.Execute(connectionPoint).Subscribe();
|
|
}
|
|
else
|
|
{
|
|
// 取消当前连接,开始新连接
|
|
ViewModel.CancelConnection();
|
|
ViewModel.StartConnectionCommand.Execute(connectionPoint).Subscribe();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 开始连接
|
|
if (connectionPoint.Type == ConnectionPointType.Output)
|
|
{
|
|
ViewModel.StartConnectionCommand.Execute(connectionPoint).Subscribe();
|
|
}
|
|
}
|
|
}
|
|
|
|
private Node? FindNode(Control? control)
|
|
{
|
|
if (control == null || ViewModel == null) return null;
|
|
|
|
// 向上查找直到找到包含Node的容器
|
|
var current = control;
|
|
while (current != null)
|
|
{
|
|
if (current.DataContext is Node node && ViewModel.Nodes.Contains(node))
|
|
{
|
|
return node;
|
|
}
|
|
current = current.Parent as Control;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private ConnectionPoint? FindConnectionPoint(Control? control)
|
|
{
|
|
if (control == null) return null;
|
|
|
|
var current = control;
|
|
while (current != null)
|
|
{
|
|
if (current.DataContext is ConnectionPoint point)
|
|
{
|
|
return point;
|
|
}
|
|
current = current.Parent as Control;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void DrawGridBackground()
|
|
{
|
|
if (_gridBackgroundLayer == null || _canvasContainer == null) return;
|
|
|
|
// 清空之前的网格线
|
|
_gridBackgroundLayer.Children.Clear();
|
|
|
|
var width = _canvasContainer.Bounds.Width > 0 ? _canvasContainer.Bounds.Width : 2000;
|
|
var height = _canvasContainer.Bounds.Height > 0 ? _canvasContainer.Bounds.Height : 2000;
|
|
|
|
// 创建网格线画笔
|
|
var gridBrush = new SolidColorBrush(Color.FromRgb(224, 224, 224)); // #E0E0E0
|
|
|
|
// 绘制垂直线
|
|
for (double x = 0; x <= width; x += GridSize)
|
|
{
|
|
var line = new Line
|
|
{
|
|
StartPoint = new Point(x, 0),
|
|
EndPoint = new Point(x, height),
|
|
Stroke = gridBrush,
|
|
StrokeThickness = 0.5
|
|
};
|
|
_gridBackgroundLayer.Children.Add(line);
|
|
}
|
|
|
|
// 绘制水平线
|
|
for (double y = 0; y <= height; y += GridSize)
|
|
{
|
|
var line = new Line
|
|
{
|
|
StartPoint = new Point(0, y),
|
|
EndPoint = new Point(width, y),
|
|
Stroke = gridBrush,
|
|
StrokeThickness = 0.5
|
|
};
|
|
_gridBackgroundLayer.Children.Add(line);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新所有节点的 Canvas 位置
|
|
/// </summary>
|
|
private void UpdateNodePositions(ItemsControl itemsControl, System.Collections.ObjectModel.ObservableCollection<Node> nodes)
|
|
{
|
|
if (itemsControl == null || nodes == null) return;
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
UpdateSingleNodePosition(itemsControl, node);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 订阅节点位置属性变化
|
|
/// </summary>
|
|
private void SubscribeToNodePositionChanges(Node node)
|
|
{
|
|
if (node == null) return;
|
|
|
|
node.WhenAnyValue(x => x.X, x => x.Y)
|
|
.Subscribe(pos =>
|
|
{
|
|
var itemsControl = this.FindControl<ItemsControl>("NodesItemsControl");
|
|
if (itemsControl != null)
|
|
{
|
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
{
|
|
UpdateSingleNodePosition(itemsControl, node);
|
|
}, Avalonia.Threading.DispatcherPriority.Normal);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新单个节点的 Canvas 位置(备用方案,主要依赖数据绑定)
|
|
/// </summary>
|
|
private void UpdateSingleNodePosition(ItemsControl itemsControl, Node node)
|
|
{
|
|
if (itemsControl == null || node == null) return;
|
|
|
|
try
|
|
{
|
|
var allPresenters = itemsControl.GetVisualDescendants().OfType<ContentPresenter>().ToList();
|
|
ContentPresenter? container = allPresenters.FirstOrDefault(cp => cp.Content == node)
|
|
?? allPresenters.FirstOrDefault(cp => cp.DataContext == node);
|
|
|
|
if (container == null)
|
|
{
|
|
foreach (var presenter in allPresenters)
|
|
{
|
|
var border = presenter.GetVisualChildren().OfType<Border>().FirstOrDefault();
|
|
if (border?.DataContext == node)
|
|
{
|
|
container = presenter;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (container != null)
|
|
{
|
|
Canvas.SetLeft(container, node.X);
|
|
Canvas.SetTop(container, node.Y);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 忽略异常,位置更新主要依赖数据绑定
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 滚动到指定节点的位置
|
|
/// </summary>
|
|
private void ScrollToNode(Node node)
|
|
{
|
|
if (node == null || _canvasScrollViewer == null || ViewModel == null) return;
|
|
|
|
try
|
|
{
|
|
// 考虑缩放和偏移
|
|
var scaledX = (node.X + ViewModel.CanvasOffsetX) * ViewModel.CanvasZoom;
|
|
var scaledY = (node.Y + ViewModel.CanvasOffsetY) * ViewModel.CanvasZoom;
|
|
|
|
// 获取 ScrollViewer 的可视区域大小
|
|
var viewportWidth = _canvasScrollViewer.Viewport.Width;
|
|
var viewportHeight = _canvasScrollViewer.Viewport.Height;
|
|
|
|
// 计算目标滚动位置(使节点居中)
|
|
var targetOffsetX = scaledX - viewportWidth / 2;
|
|
var targetOffsetY = scaledY - viewportHeight / 2;
|
|
|
|
// 限制在有效范围内
|
|
targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, _canvasScrollViewer.Extent.Width - viewportWidth));
|
|
targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, _canvasScrollViewer.Extent.Height - viewportHeight));
|
|
|
|
_canvasScrollViewer.Offset = new Vector(targetOffsetX, targetOffsetY);
|
|
}
|
|
catch
|
|
{
|
|
// 忽略滚动异常
|
|
}
|
|
}
|
|
|
|
private void OnScrollViewerPointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
|
{
|
|
if (ViewModel == null || e.KeyModifiers != KeyModifiers.Control) return;
|
|
|
|
// 按住Ctrl键时,使用滚轮进行缩放
|
|
e.Handled = true;
|
|
|
|
var delta = e.Delta.Y;
|
|
if (delta > 0)
|
|
{
|
|
ViewModel.ZoomInCommand.Execute().Subscribe();
|
|
}
|
|
else if (delta < 0)
|
|
{
|
|
ViewModel.ZoomOutCommand.Execute().Subscribe();
|
|
}
|
|
}
|
|
|
|
private void OnScrollViewerPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
{
|
|
if (_canvasScrollViewer == null || ViewModel == null) return;
|
|
|
|
var properties = e.GetCurrentPoint(_canvasScrollViewer).Properties;
|
|
bool isMiddleButton = properties.IsMiddleButtonPressed;
|
|
|
|
if (isMiddleButton || (_isSpaceKeyPressed && properties.IsLeftButtonPressed))
|
|
{
|
|
_isDraggingCanvas = true;
|
|
_canvasDragStartPoint = e.GetPosition(_canvasScrollViewer);
|
|
_canvasDragStartOffsetX = ViewModel.CanvasOffsetX;
|
|
_canvasDragStartOffsetY = ViewModel.CanvasOffsetY;
|
|
_canvasScrollViewer.Cursor = new Cursor(StandardCursorType.Hand);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void OnScrollViewerPointerMoved(object? sender, PointerEventArgs e)
|
|
{
|
|
if (!_isDraggingCanvas || _canvasScrollViewer == null || ViewModel == null || !_canvasDragStartPoint.HasValue)
|
|
return;
|
|
|
|
var currentPoint = e.GetPosition(_canvasScrollViewer);
|
|
var deltaX = currentPoint.X - _canvasDragStartPoint.Value.X;
|
|
var deltaY = currentPoint.Y - _canvasDragStartPoint.Value.Y;
|
|
|
|
// 考虑缩放因子,调整偏移量
|
|
var zoomFactor = ViewModel.CanvasZoom;
|
|
ViewModel.CanvasOffsetX = _canvasDragStartOffsetX + deltaX / zoomFactor;
|
|
ViewModel.CanvasOffsetY = _canvasDragStartOffsetY + deltaY / zoomFactor;
|
|
}
|
|
|
|
private void OnScrollViewerPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
{
|
|
if (_isDraggingCanvas && _canvasScrollViewer != null)
|
|
{
|
|
_isDraggingCanvas = false;
|
|
_canvasDragStartPoint = null;
|
|
_canvasScrollViewer.Cursor = Cursor.Default;
|
|
}
|
|
}
|
|
|
|
private void OnKeyDown(object? sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.Space)
|
|
{
|
|
_isSpaceKeyPressed = true;
|
|
if (_canvasScrollViewer != null)
|
|
{
|
|
_canvasScrollViewer.Cursor = new Cursor(StandardCursorType.Hand);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnKeyUp(object? sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.Space)
|
|
{
|
|
_isSpaceKeyPressed = false;
|
|
if (_canvasScrollViewer != null && !_isDraggingCanvas)
|
|
{
|
|
_canvasScrollViewer.Cursor = Cursor.Default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|