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.
320 lines
9.2 KiB
320 lines
9.2 KiB
|
1 month ago
|
using AuroraDesk.Core.Entities;
|
||
|
|
using AuroraDesk.Core.Interfaces;
|
||
|
|
using AuroraDesk.Presentation.ViewModels.Base;
|
||
|
|
using ReactiveUI;
|
||
|
|
using System;
|
||
|
|
using System.Collections.ObjectModel;
|
||
|
|
using System.Reactive;
|
||
|
|
using System.Reactive.Linq;
|
||
|
|
using Microsoft.Extensions.Logging;
|
||
|
|
|
||
|
|
namespace AuroraDesk.Presentation.ViewModels.Pages;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 节点画布页面 ViewModel
|
||
|
|
/// </summary>
|
||
|
|
public class NodeCanvasPageViewModel : RoutableViewModel
|
||
|
|
{
|
||
|
|
private readonly INodeCanvasService _nodeCanvasService;
|
||
|
|
private readonly ILogger<NodeCanvasPageViewModel>? _logger;
|
||
|
|
private Node? _selectedNode;
|
||
|
|
private ConnectionPoint? _connectingSourcePoint;
|
||
|
|
private bool _isConnecting;
|
||
|
|
private double _canvasOffsetX;
|
||
|
|
private double _canvasOffsetY;
|
||
|
|
private double _canvasZoom = 1.0;
|
||
|
|
|
||
|
|
public ObservableCollection<Node> Nodes => _nodeCanvasService.Nodes;
|
||
|
|
public ObservableCollection<Connection> Connections => _nodeCanvasService.Connections;
|
||
|
|
|
||
|
|
private ObservableCollection<NodeTemplate> _nodeTemplates = new();
|
||
|
|
public ObservableCollection<NodeTemplate> NodeTemplates
|
||
|
|
{
|
||
|
|
get => _nodeTemplates;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _nodeTemplates, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 选中的节点
|
||
|
|
/// </summary>
|
||
|
|
public Node? SelectedNode
|
||
|
|
{
|
||
|
|
get => _selectedNode;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _selectedNode, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 正在连接的源连接点
|
||
|
|
/// </summary>
|
||
|
|
public ConnectionPoint? ConnectingSourcePoint
|
||
|
|
{
|
||
|
|
get => _connectingSourcePoint;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _connectingSourcePoint, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 是否正在连接模式
|
||
|
|
/// </summary>
|
||
|
|
public bool IsConnecting
|
||
|
|
{
|
||
|
|
get => _isConnecting;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _isConnecting, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 画布偏移X
|
||
|
|
/// </summary>
|
||
|
|
public double CanvasOffsetX
|
||
|
|
{
|
||
|
|
get => _canvasOffsetX;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _canvasOffsetX, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 画布偏移Y
|
||
|
|
/// </summary>
|
||
|
|
public double CanvasOffsetY
|
||
|
|
{
|
||
|
|
get => _canvasOffsetY;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _canvasOffsetY, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 画布缩放
|
||
|
|
/// </summary>
|
||
|
|
public double CanvasZoom
|
||
|
|
{
|
||
|
|
get => _canvasZoom;
|
||
|
|
set => this.RaiseAndSetIfChanged(ref _canvasZoom, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 命令
|
||
|
|
public ReactiveCommand<Unit, Unit> ClearCanvasCommand { get; }
|
||
|
|
public ReactiveCommand<Node, Unit> DeleteNodeCommand { get; }
|
||
|
|
public ReactiveCommand<ConnectionPoint, Unit> StartConnectionCommand { get; }
|
||
|
|
public ReactiveCommand<ConnectionPoint, Unit> CompleteConnectionCommand { get; }
|
||
|
|
public ReactiveCommand<Connection, Unit> DeleteConnectionCommand { get; }
|
||
|
|
public ReactiveCommand<(double x, double y), Unit> AddNodeCommand { get; }
|
||
|
|
public ReactiveCommand<(NodeTemplate template, double x, double y), Unit> AddNodeFromTemplateCommand { get; }
|
||
|
|
|
||
|
|
public NodeCanvasPageViewModel(
|
||
|
|
IScreen screen,
|
||
|
|
INodeCanvasService nodeCanvasService,
|
||
|
|
ILogger<NodeCanvasPageViewModel>? logger = null) : base(screen, "node-canvas")
|
||
|
|
{
|
||
|
|
_nodeCanvasService = nodeCanvasService;
|
||
|
|
_logger = logger;
|
||
|
|
|
||
|
|
// 初始化节点模板
|
||
|
|
InitializeNodeTemplates();
|
||
|
|
|
||
|
|
// 初始化命令
|
||
|
|
ClearCanvasCommand = ReactiveCommand.Create(ClearCanvas);
|
||
|
|
DeleteNodeCommand = ReactiveCommand.Create<Node>(DeleteNode);
|
||
|
|
StartConnectionCommand = ReactiveCommand.Create<ConnectionPoint>(StartConnection);
|
||
|
|
CompleteConnectionCommand = ReactiveCommand.Create<ConnectionPoint>(CompleteConnection);
|
||
|
|
DeleteConnectionCommand = ReactiveCommand.Create<Connection>(DeleteConnection);
|
||
|
|
AddNodeCommand = ReactiveCommand.Create<(double x, double y)>(AddNode);
|
||
|
|
AddNodeFromTemplateCommand = ReactiveCommand.Create<(NodeTemplate template, double x, double y)>(AddNodeFromTemplate);
|
||
|
|
|
||
|
|
// 监听选中节点变化
|
||
|
|
this.WhenAnyValue(x => x.SelectedNode)
|
||
|
|
.Subscribe(node =>
|
||
|
|
{
|
||
|
|
// 取消其他节点的选中状态
|
||
|
|
foreach (var n in Nodes)
|
||
|
|
{
|
||
|
|
n.IsSelected = n == node;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
_logger?.LogInformation("NodeCanvasPageViewModel 已创建");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 初始化节点模板
|
||
|
|
/// </summary>
|
||
|
|
private void InitializeNodeTemplates()
|
||
|
|
{
|
||
|
|
NodeTemplates.Add(new NodeTemplate
|
||
|
|
{
|
||
|
|
Name = "power-splitter",
|
||
|
|
DisplayName = "功分器",
|
||
|
|
Description = "3功分器",
|
||
|
|
Content = "3功分42",
|
||
|
|
InputCount = 1,
|
||
|
|
OutputCount = 3,
|
||
|
|
Width = 120,
|
||
|
|
Height = 80
|
||
|
|
});
|
||
|
|
|
||
|
|
NodeTemplates.Add(new NodeTemplate
|
||
|
|
{
|
||
|
|
Name = "basic-node",
|
||
|
|
DisplayName = "基础节点",
|
||
|
|
Description = "基础节点模板",
|
||
|
|
Content = "节点",
|
||
|
|
InputCount = 1,
|
||
|
|
OutputCount = 1,
|
||
|
|
Width = 100,
|
||
|
|
Height = 60
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 添加节点到画布
|
||
|
|
/// </summary>
|
||
|
|
private void AddNode((double x, double y) position)
|
||
|
|
{
|
||
|
|
// 确保位置在合理范围内(至少为正数)
|
||
|
|
var x = Math.Max(0, position.x);
|
||
|
|
var y = Math.Max(0, position.y);
|
||
|
|
|
||
|
|
var node = new Node
|
||
|
|
{
|
||
|
|
X = x,
|
||
|
|
Y = y,
|
||
|
|
Title = $"节点 {Nodes.Count + 1}",
|
||
|
|
Content = "3功分42",
|
||
|
|
Width = 120,
|
||
|
|
Height = 80
|
||
|
|
};
|
||
|
|
|
||
|
|
// 添加连接点(右侧输出点)
|
||
|
|
for (int i = 0; i < 3; i++)
|
||
|
|
{
|
||
|
|
node.ConnectionPoints.Add(new ConnectionPoint
|
||
|
|
{
|
||
|
|
Label = (i + 1).ToString(),
|
||
|
|
Type = ConnectionPointType.Output,
|
||
|
|
Index = i,
|
||
|
|
Node = node
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 添加连接点(左侧输入点)
|
||
|
|
node.ConnectionPoints.Add(new ConnectionPoint
|
||
|
|
{
|
||
|
|
Label = "",
|
||
|
|
Type = ConnectionPointType.Input,
|
||
|
|
Index = 0,
|
||
|
|
Node = node
|
||
|
|
});
|
||
|
|
|
||
|
|
_nodeCanvasService.AddNode(node);
|
||
|
|
SelectedNode = node;
|
||
|
|
_logger?.LogDebug("添加节点: {NodeId} 在位置 ({X}, {Y})", node.Id, position.x, position.y);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 删除节点
|
||
|
|
/// </summary>
|
||
|
|
private void DeleteNode(Node node)
|
||
|
|
{
|
||
|
|
if (node == null) return;
|
||
|
|
|
||
|
|
_nodeCanvasService.RemoveNode(node);
|
||
|
|
if (SelectedNode == node)
|
||
|
|
{
|
||
|
|
SelectedNode = null;
|
||
|
|
}
|
||
|
|
_logger?.LogDebug("删除节点: {NodeId}", node.Id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 开始连接
|
||
|
|
/// </summary>
|
||
|
|
private void StartConnection(ConnectionPoint connectionPoint)
|
||
|
|
{
|
||
|
|
if (connectionPoint == null || connectionPoint.Type != ConnectionPointType.Output)
|
||
|
|
return;
|
||
|
|
|
||
|
|
ConnectingSourcePoint = connectionPoint;
|
||
|
|
IsConnecting = true;
|
||
|
|
_logger?.LogDebug("开始连接,源连接点: {PointId}", connectionPoint.Id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 完成连接
|
||
|
|
/// </summary>
|
||
|
|
private void CompleteConnection(ConnectionPoint targetPoint)
|
||
|
|
{
|
||
|
|
if (targetPoint == null || ConnectingSourcePoint == null)
|
||
|
|
{
|
||
|
|
CancelConnection();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (targetPoint.Type != ConnectionPointType.Input)
|
||
|
|
{
|
||
|
|
CancelConnection();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var success = _nodeCanvasService.CreateConnection(ConnectingSourcePoint, targetPoint);
|
||
|
|
if (success)
|
||
|
|
{
|
||
|
|
_logger?.LogDebug("连接成功: {SourceId} -> {TargetId}",
|
||
|
|
ConnectingSourcePoint.Id, targetPoint.Id);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_logger?.LogWarning("连接失败: {SourceId} -> {TargetId}",
|
||
|
|
ConnectingSourcePoint.Id, targetPoint.Id);
|
||
|
|
}
|
||
|
|
|
||
|
|
CancelConnection();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 取消连接
|
||
|
|
/// </summary>
|
||
|
|
public void CancelConnection()
|
||
|
|
{
|
||
|
|
ConnectingSourcePoint = null;
|
||
|
|
IsConnecting = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 删除连接
|
||
|
|
/// </summary>
|
||
|
|
private void DeleteConnection(Connection connection)
|
||
|
|
{
|
||
|
|
if (connection == null) return;
|
||
|
|
|
||
|
|
_nodeCanvasService.RemoveConnection(connection);
|
||
|
|
_logger?.LogDebug("删除连接: {ConnectionId}", connection.Id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 清空画布
|
||
|
|
/// </summary>
|
||
|
|
private void ClearCanvas()
|
||
|
|
{
|
||
|
|
_nodeCanvasService.Clear();
|
||
|
|
SelectedNode = null;
|
||
|
|
_logger?.LogDebug("清空画布");
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 更新节点位置
|
||
|
|
/// </summary>
|
||
|
|
public void UpdateNodePosition(Node node, double x, double y)
|
||
|
|
{
|
||
|
|
_nodeCanvasService.UpdateNodePosition(node, x, y);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 从模板添加节点到画布
|
||
|
|
/// </summary>
|
||
|
|
private void AddNodeFromTemplate((NodeTemplate template, double x, double y) args)
|
||
|
|
{
|
||
|
|
var node = args.template.CreateNode(args.x, args.y);
|
||
|
|
_nodeCanvasService.AddNode(node);
|
||
|
|
SelectedNode = node;
|
||
|
|
_logger?.LogDebug("从模板添加节点: {TemplateName} 在位置 ({X}, {Y})",
|
||
|
|
args.template.Name, args.x, args.y);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|