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.
 
 
 
 

547 lines
19 KiB

using ReactiveUI.Avalonia;
using ReactiveUI;
using Avalonia.Markup.Xaml;
using Avalonia.Controls;
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 Avalonia.Layout;
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 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.AddHandler(DragDrop.DragOverEvent, OnCanvasDragOver);
_canvasContainer.AddHandler(DragDrop.DropEvent, OnCanvasDrop);
// 监听 Canvas 尺寸变化,更新网格
_canvasContainer.SizeChanged += (sender, args) => DrawGridBackground();
}
// 在 ScrollViewer 上也添加拖拽事件,确保可以接收从左侧拖拽的组件
if (_canvasScrollViewer != null)
{
_canvasScrollViewer.AddHandler(DragDrop.DragOverEvent, OnCanvasDragOver);
_canvasScrollViewer.AddHandler(DragDrop.DropEvent, OnCanvasDrop);
}
// 设置组件库的拖拽支持
SetupTemplateDragAndDrop();
// 绘制网格背景
DrawGridBackground();
// 监听连接变化,更新连接线
if (ViewModel != null)
{
ViewModel.WhenAnyValue(x => x.Connections)
.Subscribe(_ => UpdateConnectionLines());
ViewModel.WhenAnyValue(x => x.Nodes)
.Subscribe(nodes =>
{
UpdateConnectionLines();
UpdateNodesOnCanvas(nodes);
// 调试:检查节点数量
System.Diagnostics.Debug.WriteLine($"节点数量: {nodes?.Count ?? 0}");
if (nodes != null)
{
foreach (var node in nodes)
{
System.Diagnostics.Debug.WriteLine($"节点: {node.Title}, 位置: ({node.X}, {node.Y})");
}
}
});
ViewModel.WhenAnyValue(x => x.IsConnecting, x => x.ConnectingSourcePoint)
.Subscribe(_ => UpdateConnectionLines());
}
};
}
private void SetupTemplateDragAndDrop()
{
// 延迟设置,等待模板加载完成
if (ViewModel != null)
{
ViewModel.WhenAnyValue(x => x.NodeTemplates)
.Subscribe(_ =>
{
// 使用延迟来确保UI已渲染
System.Threading.Tasks.Task.Delay(100).ContinueWith(_ =>
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
SetupTemplateItems();
});
});
});
}
}
private void SetupTemplateItems()
{
// 查找所有模板项
var templateItems = this.GetVisualDescendants()
.OfType<Border>()
.Where(b => b.Name == "TemplateItem")
.ToList();
foreach (var item in templateItems)
{
SetupTemplateItemDrag(item);
}
}
private void SetupTemplateItemDrag(Border item)
{
if (item.DataContext is NodeTemplate template)
{
item.PointerPressed += async (sender, args) =>
{
if (args.GetCurrentPoint(item).Properties.IsLeftButtonPressed)
{
// 设置允许拖拽的光标
item.Cursor = new Cursor(StandardCursorType.DragMove);
var dataTransfer = new DataObject();
dataTransfer.Set("NodeTemplate", template);
// 执行拖拽操作
var result = await DragDrop.DoDragDrop(args, dataTransfer, DragDropEffects.Copy);
// 恢复光标
item.Cursor = Cursor.Default;
}
};
}
}
private void OnCanvasDragOver(object? sender, DragEventArgs e)
{
if (e.Data.GetDataFormats().Contains("NodeTemplate"))
{
e.DragEffects = DragDropEffects.Copy;
}
}
private void OnCanvasDrop(object? sender, DragEventArgs e)
{
if (_canvasContainer == null || ViewModel == null) return;
if (e.Data.GetDataFormats().Contains("NodeTemplate") &&
e.Data.Get("NodeTemplate") is NodeTemplate template)
{
// 获取相对于 Canvas 的位置
var point = e.GetPosition(_canvasContainer);
ViewModel.AddNodeFromTemplateCommand.Execute((template, point.X, point.Y)).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 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();
// 在点击位置添加新节点(仅在左键点击时)
if (e.GetCurrentPoint(_canvasContainer).Properties.IsLeftButtonPressed)
{
// 调试:输出点击位置
System.Diagnostics.Debug.WriteLine($"点击位置: ({point.X}, {point.Y})");
System.Diagnostics.Debug.WriteLine($"Canvas大小: {_canvasContainer.Bounds.Width} x {_canvasContainer.Bounds.Height}");
ViewModel.AddNodeCommand.Execute((point.X, point.Y)).Subscribe();
}
}
private void OnCanvasPointerMoved(object? sender, PointerEventArgs e)
{
if (_canvasContainer == null || ViewModel == null) return;
var point = e.GetPosition(_canvasContainer);
// 拖拽节点
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)
{
_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);
}
}
private void UpdateNodesOnCanvas(System.Collections.ObjectModel.ObservableCollection<Node>? nodes)
{
if (_canvasContainer == null || nodes == null) return;
// 查找 ItemsControl
var itemsControl = this.FindControl<ItemsControl>("NodesItemsControl");
if (itemsControl == null)
{
itemsControl = _canvasContainer.GetVisualDescendants()
.OfType<ItemsControl>()
.FirstOrDefault();
}
if (itemsControl != null)
{
// 强制更新 ItemsControl 的布局
itemsControl.InvalidateMeasure();
itemsControl.InvalidateArrange();
// 延迟更新,确保容器已生成
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
try
{
foreach (var item in itemsControl.Items)
{
if (item is Node node)
{
// 在 Avalonia 中,通过遍历视觉树来查找对应的容器
var container = itemsControl.GetVisualDescendants()
.OfType<ContentControl>()
.FirstOrDefault(cc => cc.Content == node);
if (container is ContentControl contentControl)
{
Canvas.SetLeft(contentControl, node.X);
Canvas.SetTop(contentControl, node.Y);
System.Diagnostics.Debug.WriteLine($"设置节点 {node.Title} 位置: ({node.X}, {node.Y})");
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新节点位置时出错: {ex.Message}");
}
}, Avalonia.Threading.DispatcherPriority.Normal);
}
}
}