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 { 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("CanvasContainer"); _gridBackgroundLayer = this.FindControl("GridBackgroundLayer"); _canvasScrollViewer = this.FindControl("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() .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() .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 { 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? nodes) { if (_canvasContainer == null || nodes == null) return; // 查找 ItemsControl var itemsControl = this.FindControl("NodesItemsControl"); if (itemsControl == null) { itemsControl = _canvasContainer.GetVisualDescendants() .OfType() .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() .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); } } }