From afb1882fbd9ce6a0b78a0155350959540525d6de Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sat, 4 Jan 2025 22:25:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E8=BF=9E=E6=8E=A5=E6=97=B6?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=E7=BA=BF=E7=9A=84=E8=A1=A8=E7=8E=B0=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Api/INodeContainerControl.cs | 32 ++ Serein.Workbench.Avalonia/Api/INodeControl.cs | 24 +- .../Node/ViewModels/ActionNodeViewModel.cs | 1 + .../Node/ViewModels/NodeViewModelBase.cs | 119 ++++++- .../Custom/Node/Views/ActionNodeView.axaml | 13 +- .../Custom/Node/Views/ActionNodeView.axaml.cs | 15 +- .../Custom/Node/Views/NodeControlBase.cs | 57 ++++ .../Custom/Views/ConnectionLineShape.cs | 75 +++-- .../Custom/Views/NodeConnectionLineView.cs | 279 ++++++++++++++++ .../Custom/Views/NodeContainerView.axaml.cs | 6 +- .../Custom/Views/NodeJunctionView.axaml.cs | 267 ++++++++++------ .../Views/ParameterDetailsInfoView.axaml | 18 +- .../LibraryMethodInfoDataTemplate.cs | 8 +- .../Model/ConnectingData.cs | 69 ++-- .../Model/NodeConnectionLine.cs | 41 +++ .../Serein.Workbench.Avalonia.csproj | 1 + .../Services/NodeOperationService.cs | 301 +++++++++++++++--- .../Views/MainView.axaml | 2 +- 18 files changed, 1075 insertions(+), 253 deletions(-) create mode 100644 Serein.Workbench.Avalonia/Api/INodeContainerControl.cs create mode 100644 Serein.Workbench.Avalonia/Custom/Node/Views/NodeControlBase.cs create mode 100644 Serein.Workbench.Avalonia/Custom/Views/NodeConnectionLineView.cs create mode 100644 Serein.Workbench.Avalonia/Model/NodeConnectionLine.cs diff --git a/Serein.Workbench.Avalonia/Api/INodeContainerControl.cs b/Serein.Workbench.Avalonia/Api/INodeContainerControl.cs new file mode 100644 index 0000000..b826be2 --- /dev/null +++ b/Serein.Workbench.Avalonia/Api/INodeContainerControl.cs @@ -0,0 +1,32 @@ +using Serein.Workbench.Avalonia.Custom.Node.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Avalonia.Api +{ + /// + /// 约束具有容器功能的节点控件应该有什么方法 + /// + public interface INodeContainerControl + { + /// + /// 放置一个节点 + /// + /// + bool PlaceNode(NodeControlBase nodeControl); + + /// + /// 取出一个节点 + /// + /// + bool TakeOutNode(NodeControlBase nodeControl); + + /// + /// 取出所有节点(用于删除容器) + /// + void TakeOutAll(); + } +} diff --git a/Serein.Workbench.Avalonia/Api/INodeControl.cs b/Serein.Workbench.Avalonia/Api/INodeControl.cs index ed1158f..06770d2 100644 --- a/Serein.Workbench.Avalonia/Api/INodeControl.cs +++ b/Serein.Workbench.Avalonia/Api/INodeControl.cs @@ -7,17 +7,17 @@ using System.Threading.Tasks; namespace Serein.Workbench.Avalonia.Api { - internal interface INodeControl - { - /// - /// 对应的节点实体 - /// - NodeModelBase NodeModelBase { get; } + //internal interface INodeControl + //{ + // /// + // /// 对应的节点实体 + // /// + // NodeModelBase NodeModelBase { get; } - /// - /// 初始化使用的方法,设置节点实体 - /// - /// - void SetNodeModel(NodeModelBase nodeModel); - } + // /// + // /// 初始化使用的方法,设置节点实体 + // /// + // /// + // void SetNodeModel(NodeModelBase nodeModel); + //} } diff --git a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs index 0429f57..9ef41a3 100644 --- a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs +++ b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs @@ -15,6 +15,7 @@ namespace Serein.Workbench.Avalonia.Custom.Node.ViewModels [ObservableProperty] private SingleActionNode? nodeMoel; + internal override NodeModelBase NodeModelBase { get => NodeMoel ?? throw new NotImplementedException(); set => NodeMoel = (SingleActionNode)value; } diff --git a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs index 27b878f..d55f24f 100644 --- a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs +++ b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs @@ -1,5 +1,10 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Controls; +using Avalonia.Media; +using CommunityToolkit.Mvvm.ComponentModel; using Serein.Library; +using Serein.Workbench.Avalonia.Api; +using Serein.Workbench.Avalonia.Custom.Node.Views; +using Serein.Workbench.Avalonia.Custom.Views; using Serein.Workbench.Avalonia.ViewModels; using System; using System.Collections.Generic; @@ -15,5 +20,117 @@ namespace Serein.Workbench.Avalonia.Custom.Node.ViewModels internal abstract class NodeViewModelBase : ViewModelBase { internal abstract NodeModelBase NodeModelBase { get; set; } + + private Canvas NodeCanvas; + + /// + /// 如果该节点放置在了某个容器节点,就会记录这个容器节点 + /// + private INodeContainerControl NodeContainerControl { get; } + + public NodeModelBase NodeModel { get; set; } + + /// + /// 记录与该节点控件有关的所有连接 + /// + private readonly List connectionControls = new List(); + + //public NodeControlViewModelBase ViewModel { get; set; } + + + + public void SetNodeModel(NodeModelBase nodeModel) => this.NodeModel = nodeModel; + + + /// + /// 添加与该节点有关的连接后,记录下来 + /// + /// + public void AddCnnection(NodeConnectionLineView connection) + { + connectionControls.Add(connection); + } + + /// + /// 删除了连接之后,还需要从节点中的记录移除 + /// + /// + public void RemoveConnection(NodeConnectionLineView connection) + { + connectionControls.Remove(connection); + //connection.Remote(); + } + + /// + /// 删除所有连接 + /// + public void RemoveAllConection() + { + foreach (var connection in this.connectionControls) + { + //connection.Remote(); + } + } + + /// + /// 更新与该节点有关的数据 + /// + public void UpdateLocationConnections() + { + foreach (var connection in this.connectionControls) + { + //connection.RefreshLine(); // 主动更新连线位置 + } + } + + + /// + /// 设置绑定: + /// Canvas.X and Y : 画布位置 + /// + public void SetBinding() + { + /* // 绑定 Canvas.Left + Binding leftBinding = new Binding("X") + { + Source = ViewModel.NodeModel.Position, // 如果 X 属性在当前 DataContext 中 + Mode = BindingMode.TwoWay + }; + BindingOperations.Apply(this, Canvas.LeftProperty, leftBinding); + + // 绑定 Canvas.Top + Binding topBinding = new Binding("Y") + { + Source = ViewModel.NodeModel.Position, // 如果 Y 属性在当前 DataContext 中 + Mode = BindingMode.TwoWay + }; + BindingOperations.SetBinding(this, Canvas.TopProperty, topBinding);*/ + } + + /// + /// 穿透视觉树获取指定类型的第一个元素 + /// + /// + /// + /// + //protected T FindVisualChild(DependencyObject parent) where T : DependencyObject + //{ + // for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) + // { + // var child = VisualTreeHelper.GetChild(parent, i); + // if (child is T typedChild) + // { + // return typedChild; + // } + + // var childOfChild = FindVisualChild(child); + // if (childOfChild != null) + // { + // return childOfChild; + // } + // } + // return null; + //} + } } diff --git a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml index e24738a..372387a 100644 --- a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml +++ b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml @@ -1,15 +1,16 @@ - @@ -17,7 +18,7 @@ - + @@ -42,8 +43,8 @@ - - + + - + diff --git a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs index 099852e..c669790 100644 --- a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs +++ b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs @@ -2,25 +2,22 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Serein.Library; +using Serein.NodeFlow.Model; using Serein.Workbench.Avalonia.Api; using Serein.Workbench.Avalonia.Custom.Node.ViewModels; namespace Serein.Workbench.Avalonia.Custom.Node.Views; -public partial class ActionNodeView : UserControl, INodeControl +public partial class ActionNodeView : NodeControlBase { private ActionNodeViewModel _vm; + + public ActionNodeView() { InitializeComponent(); - _vm = App.GetService(); - DataContext = _vm; + //_vm = App.GetService(); + //DataContext = _vm; } - NodeModelBase INodeControl.NodeModelBase => _vm.NodeModelBase ?? throw new System.NotImplementedException(); // ڵ - - void INodeControl.SetNodeModel(NodeModelBase nodeModel) // ڵ - { - _vm.NodeModelBase = nodeModel; - } } \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Node/Views/NodeControlBase.cs b/Serein.Workbench.Avalonia/Custom/Node/Views/NodeControlBase.cs new file mode 100644 index 0000000..151799d --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Node/Views/NodeControlBase.cs @@ -0,0 +1,57 @@ +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Media; +using Serein.Library; +using Serein.Workbench.Avalonia.Api; +using Serein.Workbench.Avalonia.Custom.Views; +using Serein.Workbench.Avalonia.Model; +using Serein.Workbench.Avalonia.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Avalonia.Custom.Node.Views +{ + public class NodeControlBase : UserControl + { + protected NodeControlBase() + { + this.Background = Brushes.Transparent; + } + + /// + /// 放置在某个节点容器中 + /// + public void PlaceToContainer(INodeContainerControl nodeContainerControl) + { + //this.nodeContainerControl = nodeContainerControl; + //NodeCanvas.Children.Remove(this); // 临时从画布上移除 + //var result = nodeContainerControl.PlaceNode(this); + //if (!result) // 检查是否放置成功,如果不成功,需要重新添加回来 + //{ + // NodeCanvas.Children.Add(this); // 从画布上移除 + + //} + } + + /// + /// 从某个节点容器取出 + /// + public void TakeOutContainer() + { + //var result = nodeContainerControl.TakeOutNode(this); // 从控件取出 + //if (result) // 移除成功时才添加到画布上 + //{ + // NodeCanvas.Children.Add(this); // 重新添加到画布上 + // if (nodeContainerControl is NodeControlBase containerControl) + // { + // NodeModel.Position.X = NodeModel.Position.X + containerControl.Width + 10; + // NodeModel.Position.Y = NodeModel.Position.Y; + // } + //} + + } + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs b/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs index 60f822f..bf79df9 100644 --- a/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs +++ b/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs @@ -23,23 +23,22 @@ namespace Serein.Workbench.Avalonia.Custom.Views { private readonly double strokeThickness; - /// - /// 确定起始坐标和目标坐标、外光样式的曲线 + /// 确定起始坐标和目标坐标、外观样式的曲线 /// - /// 起始坐标 - /// 结束坐标 + /// 起始坐标 + /// 结束坐标 /// 颜色 /// 是否为虚线 - public ConnectionLineShape(Point start, - Point end, + public ConnectionLineShape(Point left, + Point right, Brush brush, bool isDotted = false, bool isTop = false) { this.brush = brush; - startPoint = start; - endPoint = end; + this.leftPoint = left; + this.rightPoint = right; this.strokeThickness = 4; InitElementPoint(isDotted, isTop); InvalidateVisual(); // 触发重绘 @@ -50,9 +49,10 @@ namespace Serein.Workbench.Avalonia.Custom.Views { //hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线 //hitVisiblePen.Freeze(); // Freeze以提高性能 + visualPen = new Pen(brush, 3.0); // 默认可视化Pen opacity = 1.0d; - var dashStyle = new DashStyle(); + //var dashStyle = new DashStyle(); if (isDotted) { @@ -71,31 +71,44 @@ namespace Serein.Workbench.Avalonia.Custom.Views /// /// 更新线条落点位置 /// - /// - /// - public void UpdatePoints(Point start, Point end) + /// + /// + public void UpdatePoint(Point left, Point right, Brush? brush = null) { - startPoint = start; - endPoint = end; + if(brush is not null) + { + visualPen = new Pen(brush, 3.0); // 默认可视化Pen + } + this.leftPoint = left; + this.rightPoint = right; InvalidateVisual(); // 触发重绘 } /// /// 更新线条落点位置 /// - /// - public void UpdateEndPoints(Point point) + /// + public void UpdateRightPoint(Point right, Brush? brush = null) { - endPoint = point; + if (brush is not null) + { + visualPen = new Pen(brush, 3.0); // 默认可视化Pen + } + this.rightPoint = right; InvalidateVisual(); // 触发重绘 } + /// /// 更新线条起点位置 /// - /// - public void UpdateStartPoints(Point point) + /// + public void UpdateLeftPoints(Point left, Brush? brush = null) { - startPoint = point; + if (brush is not null) + { + visualPen = new Pen(brush, 3.0); // 默认可视化Pen + } + this.leftPoint = left; InvalidateVisual(); // 触发重绘 } @@ -106,7 +119,7 @@ namespace Serein.Workbench.Avalonia.Custom.Views public override void Render(DrawingContext drawingContext) { // 刷新线条显示位置 - DrawBezierCurve(drawingContext, startPoint, endPoint); + DrawBezierCurve(drawingContext, leftPoint, rightPoint); } #region 重绘 @@ -116,8 +129,8 @@ namespace Serein.Workbench.Avalonia.Custom.Views private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心 //private Pen hitVisiblePen; // 初始化碰撞检测线 private Pen visualPen; // 默认可视化Pen - private Point startPoint; // 连接线的起始节点 - private Point endPoint; // 连接线的终点 + private Point leftPoint; // 连接线的起始节点 + private Point rightPoint; // 连接线的终点 private Brush brush; // 线条颜色 private double opacity; // 透明度 @@ -135,8 +148,8 @@ namespace Serein.Workbench.Avalonia.Custom.Views private Vector startToEnd; private int i = 0; private void DrawBezierCurve(DrawingContext drawingContext, - Point start, - Point end) + Point left, + Point right) { // 控制点的计算逻辑 double power = 140; // 控制贝塞尔曲线的“拉伸”强度 @@ -144,7 +157,7 @@ namespace Serein.Workbench.Avalonia.Custom.Views // 计算轴向向量与起点到终点的向量 //var axis = new Vector(1, 0); - startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo(); + startToEnd = (right.ToVector() - left.ToVector()).NormalizeTo(); @@ -163,10 +176,10 @@ namespace Serein.Workbench.Avalonia.Custom.Views pow = pow > 0 ? 0 : pow; var k = 1 - pow; // 如果起点x大于终点x,增加额外的偏移量,避免重叠 - var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0; + var bias = left.X > right.X ? Math.Abs(left.X - right.X) * 0.25 : 0; // 控制点的实际计算 - c0 = new Point(+(power + bias) * k + start.X, start.Y); - c1 = new Point(-(power + bias) * k + end.X, end.Y); + c0 = new Point(+(power + bias) * k + left.X, left.Y); + c1 = new Point(-(power + bias) * k + right.X, right.Y); // 准备StreamGeometry以用于绘制曲线 // why can't clearValue()? @@ -204,8 +217,8 @@ namespace Serein.Workbench.Avalonia.Custom.Views // streamGeometry.ClearValue("AvaloniaProperty"); using (var context = streamGeometry.Open()) { - context.BeginFigure(start, true); // start point of the bezier-line - context.CubicBezierTo(c0, c1, end, true); // drawing bezier-line + context.BeginFigure(left, true); // start point of the bezier-line + context.CubicBezierTo(c0, c1, right, true); // drawing bezier-line } drawingContext.DrawGeometry(null, visualPen, streamGeometry); diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeConnectionLineView.cs b/Serein.Workbench.Avalonia/Custom/Views/NodeConnectionLineView.cs new file mode 100644 index 0000000..b491f2e --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeConnectionLineView.cs @@ -0,0 +1,279 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.VisualTree; +using Serein.Library; +using Serein.Script.Node; +using Serein.Workbench.Avalonia.Extension; +using Serein.Workbench.Avalonia.Services; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Color = Avalonia.Media.Color; +using Point = Avalonia.Point; + +namespace Serein.Workbench.Avalonia.Custom.Views +{ + + + + public class NodeConnectionLineView + { + /// + /// 线条类别(方法调用) + /// + public ConnectionInvokeType ConnectionInvokeType { get; set; } = ConnectionInvokeType.IsSucceed; + /// + /// 线条类别(参数传递) + /// + public ConnectionArgSourceType ConnectionArgSourceType { get; set; } = ConnectionArgSourceType.GetOtherNodeData; + + + /// + /// 画布 + /// + private Canvas Canvas; + /// + /// 连接线的起点 + /// + private NodeJunctionView? LeftNodeJunctionView; + /// + /// 连接线的终点 + /// + private NodeJunctionView? RightNodeJunctionView; + + /// + /// 连接时显示的线 + /// + public ConnectionLineShape? ConnectionLineShape { get; private set; } + + public NodeConnectionLineView(Canvas canvas, + NodeJunctionView? leftNodeJunctionView, + NodeJunctionView? rightNodeJunctionView) + { + this.Canvas = canvas; + this.LeftNodeJunctionView = leftNodeJunctionView; + this.RightNodeJunctionView = rightNodeJunctionView; + } + + /// + /// 连接到终点 + /// + /// + public void ToEnd(NodeJunctionView endNodeJunctionView) + { + if((endNodeJunctionView.JunctionType == JunctionType.NextStep + || endNodeJunctionView.JunctionType == JunctionType.ReturnData) + && RightNodeJunctionView is not null + /*&& LeftNodeJunctionView is null*/ + /*&& !LeftNodeJunctionView.Equals(endNodeJunctionView)*/) + { + LeftNodeJunctionView = endNodeJunctionView; + RefreshLineDsiplay(); + return; + } + else if ((endNodeJunctionView.JunctionType == JunctionType.Execute + || endNodeJunctionView.JunctionType == JunctionType.ArgData) + && LeftNodeJunctionView is not null + /*&& RightNodeJunctionView is null*/ + /*&& !RightNodeJunctionView.Equals(endNodeJunctionView)*/) + { + RightNodeJunctionView = endNodeJunctionView; + RefreshLineDsiplay(); + return; + } + + + // + + //var leftPoint = GetPoint(LeftNodeJunctionView); + //var rightPoint = GetPoint(RightNodeJunctionView); + //var brush = GetBackgrounp(); + //ConnectionLineShape.UpdatePoint(leftPoint, rightPoint); + //CreateLineShape(startPoint, endPoint, brush); + } + + /// + /// 刷新线的显示 + /// + public void RefreshLineDsiplay() + { + if(LeftNodeJunctionView is null || RightNodeJunctionView is null) + { + return; + } + var leftPoint = GetPoint(LeftNodeJunctionView); + var rightPoint = GetPoint(RightNodeJunctionView); + if (ConnectionLineShape is null) + { + Debug.WriteLine("创建"); + CreateLineShape(leftPoint, rightPoint, GetBackgrounp()); + } + else + { + Debug.WriteLine("刷新"); + var brush = GetBackgrounp(); + ConnectionLineShape.UpdatePoint( leftPoint, rightPoint, brush); + } + } + + + /// + /// 刷新临时线的显示 + /// + public void RefreshRightPointOfTempLineDsiplay(Point rightPoint) + { + if(ConnectionLineShape is not null) + { + var brush = GetBackgrounp(); + ConnectionLineShape.UpdateRightPoint(rightPoint, brush); + return; + } + + if (LeftNodeJunctionView is not null) + { + var leftPoint = GetPoint(LeftNodeJunctionView); + var brush = GetBackgrounp(); + CreateLineShape(leftPoint, rightPoint, brush); + } + } + /// + /// 刷新临时线的显示 + /// + public void RefreshLeftPointOfTempLineDsiplay(Point leftPoint) + { + if(ConnectionLineShape is not null) + { + var brush = GetBackgrounp(); + ConnectionLineShape.UpdateLeftPoints(leftPoint, brush); + return; + } + + if (RightNodeJunctionView is not null) + { + var rightPoint = GetPoint(RightNodeJunctionView); + var brush = GetBackgrounp(); + CreateLineShape(leftPoint, rightPoint, brush); + } + } + + + + private static Point defaultPoint = new Point(0, 0); + int count; + private Point GetPoint(NodeJunctionView nodeJunctionView) + { + + var junctionSize = nodeJunctionView.GetTransformedBounds()!.Value.Bounds.Size; + Point junctionPoint; + if (nodeJunctionView.JunctionType == JunctionType.ArgData || nodeJunctionView.JunctionType == JunctionType.Execute) + { + junctionPoint = new Point(junctionSize.Width / 2 - 11, junctionSize.Height / 2); // 选择左侧 + } + else + { + junctionPoint = new Point(junctionSize.Width / 2 + 11, junctionSize.Height / 2); // 选择右侧 + } + if (nodeJunctionView.TranslatePoint(junctionPoint, Canvas) is Point point) + { + //myData.StartPoint = point; + return point; + } + else + { + return defaultPoint; + } + + //var point = nodeJunctionView.TranslatePoint(defaultPoint , Canvas); + //if(point is null) + //{ + // return defaultPoint; + //} + //else + //{ + // return point.Value; + // } + } + + private void CreateLineShape(Point leftPoint, Point rightPoint, Brush brush) + { + ConnectionLineShape = new ConnectionLineShape(leftPoint, rightPoint, brush); + Canvas.Children.Add(ConnectionLineShape); + } + + private JunctionOfConnectionType GetConnectionType() + { + return LeftNodeJunctionView.JunctionType.ToConnectyionType(); + } + + + /// + /// 获取背景颜色 + /// + /// + public Brush GetBackgrounp() + { + + if(LeftNodeJunctionView is null || RightNodeJunctionView is null) + { + return new SolidColorBrush(Color.Parse("#FF0000")); // 没有终点 + } + + // 判断连接控制点是否匹配 + if (!IsCanConnected()) + { + return new SolidColorBrush(Color.Parse("#FF0000")); + } + + + if (GetConnectionType() == JunctionOfConnectionType.Invoke) + { + return ConnectionInvokeType.ToLineColor(); // 调用 + } + else + { + return ConnectionArgSourceType.ToLineColor(); // 参数 + } + + } + + public bool IsCanConnected() + { + if (LeftNodeJunctionView is null + || RightNodeJunctionView is null) + { + return false; + } + if (LeftNodeJunctionView?.MyNode is null + || LeftNodeJunctionView.MyNode.Equals(RightNodeJunctionView.MyNode)) + return false; + + if (LeftNodeJunctionView.JunctionType.IsCanConnection(RightNodeJunctionView.JunctionType)) + { + return true; + } + else + { + return false; + } + } + + /// + /// 移除线 + /// + public void Remove() + { + if(ConnectionLineShape is null) + { + return; + } + Canvas.Children.Remove(ConnectionLineShape); + } + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs index 198a3f6..1c8e6d5 100644 --- a/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs @@ -98,6 +98,7 @@ public partial class NodeContainerView : UserControl { IsCanvasDragging = false; IsControlDragging = false; + nodeOperationService.ConnectingData.Reset(); } }; #endregion @@ -190,12 +191,11 @@ public partial class NodeContainerView : UserControl private void NodeContainerView_PointerMoved(object? sender, PointerEventArgs e) { - + // Ƿ var myData = nodeOperationService.ConnectingData; if (myData.IsCreateing) { var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsLeftButtonPressed); - //Debug.WriteLine("canvas ispass = " + isPass); if (isPass) { if (myData.Type == JunctionOfConnectionType.Invoke) @@ -208,7 +208,9 @@ public partial class NodeContainerView : UserControl _vm.IsConnectionArgSourceNode = true; // ӽڵĵùϵ } var currentPoint = e.GetPosition(PART_NodeContainer); + //myData.CurrentJunction?.InvalidateVisual(); myData.UpdatePoint(new Point(currentPoint.X - 5, currentPoint.Y - 5)); + e.Handled = true; return; } diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs index dbf6ee2..18fe1ff 100644 --- a/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs @@ -1,18 +1,14 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Media; using Serein.Library; -using Serein.Workbench.Avalonia.Views; -using System.Drawing; +using Serein.Library.Api; +using Serein.Workbench.Avalonia.Api; +using Serein.Workbench.Avalonia.Extension; using System; using Color = Avalonia.Media.Color; using Point = Avalonia.Point; -using System.Diagnostics; -using Avalonia.Threading; -using Serein.Workbench.Avalonia.Api; -using Serein.Workbench.Avalonia.Extension; namespace Serein.Workbench.Avalonia.Custom.Views; @@ -21,55 +17,6 @@ namespace Serein.Workbench.Avalonia.Custom.Views; /// public class NodeJunctionView : TemplatedControl { - private readonly INodeOperationService nodeOperationService; - - /// - /// RenderпԻ - /// - protected readonly StreamGeometry StreamGeometry = new StreamGeometry(); - - /// - /// ڲ鿴 - /// - private bool IsPreviewing; - - public NodeJunctionView() - { - nodeOperationService = App.GetService(); - this.PointerMoved += NodeJunctionView_PointerMoved; - this.PointerExited += NodeJunctionView_PointerExited; - - this.PointerPressed += NodeJunctionView_PointerPressed; - this.PointerReleased += NodeJunctionView_PointerReleased; - } - - private void NodeJunctionView_PointerReleased(object? sender, PointerReleasedEventArgs e) - { - nodeOperationService.ConnectingData.IsCreateing = false; - } - - private void NodeJunctionView_PointerPressed(object? sender, PointerPressedEventArgs e) - { - nodeOperationService.TryCreateConnectionOnJunction(this); // Կʼ - Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); - } - - - - /// - /// ȡؼϢ - /// - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - //if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm) - //{ - // //p_fm.SelectionChanged += ListBox_SelectionChanged; - // //p_fm.PointerExited += ListBox_PointerExited; - //} - } - public static readonly DirectProperty JunctionTypeProperty = AvaloniaProperty.RegisterDirect(nameof(JunctionType), o => o.JunctionType, (o, v) => o.JunctionType = v); @@ -88,6 +35,160 @@ public class NodeJunctionView : TemplatedControl get { return myNode; } set { SetAndRaise(MyNodeProperty, ref myNode, value); } } + + public static readonly DirectProperty ArgIndexProperty = + AvaloniaProperty.RegisterDirect(nameof(ArgIndex), o => o.ArgIndex, (o, v) => o.ArgIndex = v); + private int argIndex; + public int ArgIndex + { + get { return argIndex; } + set { SetAndRaise(ArgIndexProperty, ref argIndex, value); } + } + + + + private readonly INodeOperationService nodeOperationService; + private readonly IFlowEnvironment flowEnvironment; + + /// + /// RenderпԻ + /// + protected readonly StreamGeometry StreamGeometry = new StreamGeometry(); + + + + #region ¼ + + + public NodeJunctionView() + { + nodeOperationService = App.GetService(); + flowEnvironment = App.GetService(); + //this.PointerExited += NodeJunctionView_PointerExited; + this.PointerMoved += NodeJunctionView_PointerMoved; + this.PointerPressed += NodeJunctionView_PointerPressed; + this.PointerReleased += NodeJunctionView_PointerReleased; + } + + + public bool IsPreviewing { get; set; } + private Guid Guid = Guid.NewGuid(); + + private void NodeJunctionView_PointerMoved(object? sender, PointerEventArgs e) + { + if (!nodeOperationService.ConnectingData.IsCreateing) + return; + if (nodeOperationService.MainCanvas is not InputElement inputElement) + return; + var currentPoint = e.GetPosition(nodeOperationService.MainCanvas); + if (inputElement.InputHitTest(currentPoint) is NodeJunctionView junctionView) + { + RefreshDisplay(junctionView); + } + else + { + var oldNj = nodeOperationService.ConnectingData.CurrentJunction; + if (oldNj is not null) + { + oldNj.IsPreviewing = false; + oldNj.InvalidateVisual(); + } + } + } + + private void RefreshDisplay(NodeJunctionView junctionView) + { + var oldNj = nodeOperationService.ConnectingData.CurrentJunction; + if (oldNj is not null ) + { + if (junctionView.Equals(oldNj)) + { + return; + } + oldNj.IsPreviewing = false; + oldNj.InvalidateVisual(); + } + nodeOperationService.ConnectingData.CurrentJunction = junctionView; + if (!this.Equals(junctionView)) + { + + nodeOperationService.ConnectingData.TempLine?.ToEnd(junctionView); + } + junctionView.IsPreviewing = true; + junctionView.InvalidateVisual(); + } + + + + /// + /// Կʼ + /// + /// + /// + private void NodeJunctionView_PointerPressed(object? sender, PointerPressedEventArgs e) + { + nodeOperationService.TryCreateConnectionOnJunction(this); // Կʼ + } + private void NodeJunctionView_PointerReleased(object? sender, PointerReleasedEventArgs e) + { + CheckJunvtion(); + nodeOperationService.ConnectingData.Reset(); + } + + private void CheckJunvtion() + { + var myData = nodeOperationService.ConnectingData; + if(myData.StartJunction is null || myData.CurrentJunction is null) + { + return; + } + if(myData.StartJunction.MyNode is null || myData.CurrentJunction.MyNode is null) + { + return; + } + if (!myData.IsCanConnected()) + { + return; + } + + var canvas = nodeOperationService.MainCanvas; + + #region ùϵ + if (myData.Type == JunctionOfConnectionType.Invoke) + { + flowEnvironment.ConnectInvokeNodeAsync(myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid, + myData.StartJunction.JunctionType, + myData.CurrentJunction.JunctionType, + myData.ConnectionInvokeType); + } + #endregion + + #region Դϵ + else if (myData.Type == JunctionOfConnectionType.Arg) + { + var argIndex = 0; + if (myData.StartJunction.JunctionType == JunctionType.ArgData) + { + argIndex = myData.StartJunction.ArgIndex; + } + else if (myData.CurrentJunction.JunctionType == JunctionType.ArgData) + { + argIndex = myData.CurrentJunction.ArgIndex; + } + + flowEnvironment.ConnectArgSourceNodeAsync(myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid, + myData.StartJunction.JunctionType, + myData.CurrentJunction.JunctionType, + myData.ConnectionArgSourceType, + argIndex); + } + #endregion + + + } + + #endregion + #region ػUIӾ @@ -101,7 +202,8 @@ public class NodeJunctionView : TemplatedControl double width = 44; double height = 26; var background = GetBackgrounp(); - var pen = new Pen(Brushes.Black, 1); + var pen = new Pen(Brushes.Transparent, 1); + //var pen = nodeOperationService.ConnectingData.IsCreateing ? new Pen(background, 1) : new Pen(Brushes.Black, 1); // ı var connectorRect = new Rect(0, 0, width, height); @@ -132,32 +234,10 @@ public class NodeJunctionView : TemplatedControl context.LineTo(new Point(triangleCenterX, triangleCenterY + t), true); context.LineTo(new Point(triangleCenterX, triangleCenterY - t), true); } - drawingContext.DrawGeometry(background, new Pen(Brushes.Black, 1), pathGeometry); + drawingContext.DrawGeometry(background, pen, pathGeometry); } - #region ¼ - - private void NodeJunctionView_PointerExited(object? sender, PointerEventArgs e) - { - if (IsPreviewing) - { - IsPreviewing = false; - Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); - } - } - - private void NodeJunctionView_PointerMoved(object? sender, PointerEventArgs e) - { - if (!IsPreviewing) - { - IsPreviewing = true; - Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); - } - - } - - #endregion /// @@ -167,38 +247,25 @@ public class NodeJunctionView : TemplatedControl protected IBrush GetBackgrounp() { var myData = nodeOperationService.ConnectingData; - if (!myData.IsCreateing) + if (IsPreviewing == false || !myData.IsCreateing ) { - //Debug.WriteLine($"return color is {Brushes.BurlyWood}"); return new SolidColorBrush(Color.Parse("#76ABEE")); } - if (myData.IsCanConnected) + if (!myData.IsCanConnected()) { - if (myData.Type == JunctionOfConnectionType.Invoke) - { - return myData.ConnectionInvokeType.ToLineColor(); - } - else - { - return myData.ConnectionArgSourceType.ToLineColor(); - } - } - else - { - return Brushes.Red; + return new SolidColorBrush(Color.Parse("#FF0000")); } - if (IsPreviewing) + if (myData.Type == JunctionOfConnectionType.Invoke) { - //return new SolidColorBrush(Color.Parse("#04FC10")); - + return myData.ConnectionInvokeType.ToLineColor(); // } else { - //Debug.WriteLine($"return color is {Brushes.BurlyWood}"); - return new SolidColorBrush(Color.Parse("#76ABEE")); + return myData.ConnectionArgSourceType.ToLineColor(); // } + } #endregion diff --git a/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml index 785860c..f9b9064 100644 --- a/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml +++ b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml @@ -22,7 +22,7 @@ - + - + + + - + @@ -45,12 +47,16 @@ - - + + + + MinWidth="120" MaxWidth="300" + HorizontalAlignment="Left" VerticalAlignment="Center"> diff --git a/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs b/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs index 0e0b883..b513421 100644 --- a/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs +++ b/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs @@ -30,12 +30,7 @@ namespace Serein.Workbench.Avalonia.DataTemplates textBlock.Margin = new Thickness(2d, -6d, 2d, -6d); textBlock.FontSize = 12; textBlock.PointerPressed += TextBlock_PointerPressed; - //var stackPanel = new StackPanel(); - //stackPanel.Children.Add(textBlock); - //ToolTip toolTip = new ToolTip(); - //toolTip.FontSize = 12; - //toolTip.Content = mdInfo.MethodAnotherName; - //textBlock.Tag = mdInfo; + textBlock.Tag = mdInfo; return textBlock; } else @@ -43,7 +38,6 @@ namespace Serein.Workbench.Avalonia.DataTemplates var textBlock = new TextBlock() { Text = $"Binding 类型不为预期的[MethodDetailsInfo],而是[{param?.GetType()}]" }; textBlock.Margin = new Thickness(2d, -6d, 2d, -6d); textBlock.FontSize = 12; - textBlock.PointerPressed += TextBlock_PointerPressed; return textBlock; } diff --git a/Serein.Workbench.Avalonia/Model/ConnectingData.cs b/Serein.Workbench.Avalonia/Model/ConnectingData.cs index 26e945f..24e63ea 100644 --- a/Serein.Workbench.Avalonia/Model/ConnectingData.cs +++ b/Serein.Workbench.Avalonia/Model/ConnectingData.cs @@ -1,8 +1,10 @@ using Avalonia; +using Avalonia.Threading; using Serein.Library; using Serein.Workbench.Avalonia.Custom.Views; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -34,7 +36,7 @@ namespace Serein.Workbench.Avalonia.Model /// /// 线条样式 /// - public MyLine? TempLine { get; set; } + public NodeConnectionLineView? TempLine { get; set; } /// /// 线条类别(方法调用) @@ -49,59 +51,56 @@ namespace Serein.Workbench.Avalonia.Model /// 判断当前连接类型 /// public JunctionOfConnectionType? Type => StartJunction?.JunctionType.ToConnectyionType(); - + /// /// 是否允许连接 /// - - public bool IsCanConnected + public bool IsCanConnected() { - get + if (StartJunction is null + || CurrentJunction is null ) { + return false; + } + if (StartJunction?.MyNode is null + || StartJunction.MyNode.Equals(CurrentJunction.MyNode)) + return false; - if (StartJunction is null - || CurrentJunction is null - ) - { - return false; - } - if(StartJunction?.MyNode is null) - { - return false; - } - if (!StartJunction.MyNode.Equals(CurrentJunction.MyNode) - && StartJunction.JunctionType.IsCanConnection(CurrentJunction.JunctionType)) - { - return true; - } - else - { - return false; - } + if (StartJunction.JunctionType.IsCanConnection(CurrentJunction.JunctionType)) + { + return true; + } + else + { + return false; } } + + /// /// 更新临时的连接线 /// /// public void UpdatePoint(Point point) { - if (StartJunction is null - || CurrentJunction is null - ) + if (StartJunction is null || CurrentJunction is null ) + { + return; + } + if (IsCanConnected()) { return; } if (StartJunction.JunctionType == Library.JunctionType.Execute || StartJunction.JunctionType == Library.JunctionType.ArgData) { - TempLine?.Line.UpdateStartPoints(point); + TempLine?.RefreshLeftPointOfTempLineDsiplay(point); } else { - TempLine?.Line.UpdateEndPoints(point); + TempLine?.RefreshRightPointOfTempLineDsiplay(point); } } @@ -111,9 +110,17 @@ namespace Serein.Workbench.Avalonia.Model /// public void Reset() { + if(CurrentJunction is not null) + { + CurrentJunction.IsPreviewing = false; + Dispatcher.UIThread.InvokeAsync(CurrentJunction.InvalidateVisual, DispatcherPriority.Background); + } + if(StartJunction is not null) + { + StartJunction.IsPreviewing = false; + Dispatcher.UIThread.InvokeAsync(StartJunction.InvalidateVisual, DispatcherPriority.Background); + } IsCreateing = false; - StartJunction = null; - CurrentJunction = null; TempLine?.Remove(); ConnectionInvokeType = ConnectionInvokeType.IsSucceed; ConnectionArgSourceType = ConnectionArgSourceType.GetOtherNodeData; diff --git a/Serein.Workbench.Avalonia/Model/NodeConnectionLine.cs b/Serein.Workbench.Avalonia/Model/NodeConnectionLine.cs new file mode 100644 index 0000000..7a0c85a --- /dev/null +++ b/Serein.Workbench.Avalonia/Model/NodeConnectionLine.cs @@ -0,0 +1,41 @@ +using Avalonia.Controls; +using Serein.Workbench.Avalonia.Custom.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Avalonia.Model +{ + + /// + /// 绘制的线 + /// + public class NodeConnectionLine + { + /// + /// 将线条绘制出来(临时线) + /// + /// 放置画布 + /// 线的实体 + public NodeConnectionLine(Canvas canvas, ConnectionLineShape line) + { + Canvas = canvas; + Line = line; + canvas?.Children.Add(line); + } + + + public Canvas Canvas { get; } + public ConnectionLineShape Line { get; } + + /// + /// 移除线 + /// + public void Remove() + { + Canvas?.Children.Remove(Line); + } + } +} diff --git a/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj b/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj index ef5b5df..23d6ab0 100644 --- a/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj +++ b/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj @@ -19,6 +19,7 @@ + diff --git a/Serein.Workbench.Avalonia/Services/NodeOperationService.cs b/Serein.Workbench.Avalonia/Services/NodeOperationService.cs index 7608f82..e6079af 100644 --- a/Serein.Workbench.Avalonia/Services/NodeOperationService.cs +++ b/Serein.Workbench.Avalonia/Services/NodeOperationService.cs @@ -15,6 +15,7 @@ using Serein.Workbench.Avalonia.Extension; using Serein.Workbench.Avalonia.Model; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -81,12 +82,12 @@ namespace Serein.Workbench.Avalonia.Api internal class NodeViewCreateEventArgs : EventArgs { - internal NodeViewCreateEventArgs(INodeControl nodeControl, PositionOfUI position) + internal NodeViewCreateEventArgs(NodeControlBase nodeControl, PositionOfUI position) { this.NodeControl = nodeControl; this.Position = position; } - public INodeControl NodeControl { get; private set; } + public NodeControlBase NodeControl { get; private set; } public PositionOfUI Position { get; private set; } } @@ -112,11 +113,9 @@ namespace Serein.Workbench.Avalonia.Services { this.flowEnvironment = flowEnvironment; this.feefService = feefService; - - NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeView), typeof(ActionNodeViewModel)); // 注册动作节点 - ConnectingData = new ConnectingData(); feefService.OnNodeCreate += FeefService_OnNodeCreate; // 订阅运行环境创建节点事件 - + feefService.OnNodeConnectChange += FeefService_OnNodeConnectChange; // 订阅运行环境连接了节点事件 + NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeView), typeof(ActionNodeViewModel)); // 注册动作节点 // 手动加载项目 _ = Task.Run(async delegate @@ -133,17 +132,24 @@ namespace Serein.Workbench.Avalonia.Services } - public ConnectingData ConnectingData { get; private set; } + + #region 接口属性 + public ConnectingData ConnectingData { get; private set; } = new ConnectingData(); public Canvas MainCanvas { get; set; } - + #endregion #region 私有变量 /// /// 存储所有与节点有关的控件 /// - private Dictionary NodeControls { get; } = []; + private Dictionary NodeControls { get; } = []; + + /// + /// 存储所有连接 + /// + private List Connections { get; } = []; @@ -158,28 +164,16 @@ namespace Serein.Workbench.Avalonia.Services private readonly IFlowEEForwardingService feefService; #endregion + #region 节点操作事件 + /// /// 创建了节点控件 /// public event NodeViewCreateHandle OnNodeViewCreate; - /// - /// 创建节点控件 - /// - /// 控件类型 - /// 创建坐标 - /// 节点方法信息(基础节点传null) - public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position) - { - Task.Run(async () => - { - if (EnumHelper.TryConvertEnum(methodDetailsInfo.NodeType, out var nodeType)) - { - await flowEnvironment.CreateNodeAsync(nodeType, position, methodDetailsInfo); - } - }); - } + #endregion + #region 转发事件的处理 /// /// 从工作台事件转发器监听节点创建事件 @@ -225,6 +219,178 @@ namespace Serein.Workbench.Avalonia.Services } + + /// + /// 运行环境连接了节点事件 + /// + /// + /// + private void FeefService_OnNodeConnectChange(NodeConnectChangeEventArgs eventArgs) + { +#if false + string fromNodeGuid = eventArgs.FromNodeGuid; + string toNodeGuid = eventArgs.ToNodeGuid; + if (!TryGetControl(fromNodeGuid, out var fromNodeControl) + || !TryGetControl(toNodeGuid, out var toNodeControl)) + { + return; + } + + if (eventArgs.JunctionOfConnectionType == JunctionOfConnectionType.Invoke) + { + ConnectionInvokeType connectionType = eventArgs.ConnectionInvokeType; + #region 创建/删除节点之间的调用关系 + #region 创建连接 + if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接 + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); + return; + } + var startJunction = IFormJunction.NextStepJunction; + var endJunction = IToJunction.ExecuteJunction; + + startJunction.TransformToVisual(MainCanvas); + + // 添加连接 + var shape = new ConnectionLineShape( + FlowChartCanvas, + connectionType, + startJunction, + endJunction + ); + NodeConnectionLine nodeConnectionLine = new NodeConnectionLine(MainCanvas, shape); + + //if (toNodeControl is FlipflopNodeControl flipflopControl + // && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 + //{ + // NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + //} + + Connections.Add(nodeConnectionLine); + fromNodeControl.AddCnnection(shape); + toNodeControl.AddCnnection(shape); + } + #endregion +#if false + + #region 移除连接 + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接 + { + // 需要移除连接 + var removeConnections = Connections.Where(c => + c.Start.MyNode.Guid.Equals(fromNodeGuid) + && c.End.MyNode.Guid.Equals(toNodeGuid) + && (c.Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke + || c.End.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)) + .ToList(); + + + foreach (var connection in removeConnections) + { + Connections.Remove(connection); + fromNodeControl.RemoveConnection(connection); // 移除连接 + toNodeControl.RemoveConnection(connection); // 移除连接 + if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + { + JudgmentFlipFlopNode(control); // 连接关系变更时判断 + } + } + } + #endregion + +#endif + #endregion + } + else + { + #if false + ConnectionArgSourceType connectionArgSourceType = eventArgs.ConnectionArgSourceType; + #region 创建/删除节点之间的参数传递关系 + #region 创建连接 + if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接 + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的情况"); + return; + } + + JunctionControlBase startJunction = eventArgs.ConnectionArgSourceType switch + { + ConnectionArgSourceType.GetPreviousNodeData => IFormJunction.ReturnDataJunction, // 自身节点 + ConnectionArgSourceType.GetOtherNodeData => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + ConnectionArgSourceType.GetOtherNodeDataOfInvoke => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 + }; + + if (IToJunction.ArgDataJunction.Length <= eventArgs.ArgIndex) + { + _ = Task.Run(async () => + { + await Task.Delay(500); + FlowEnvironment_NodeConnectChangeEvemt(eventArgs); + }); + return; + } + JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex]; + LineType lineType = LineType.Bezier; + // 添加连接 + var connection = new ConnectionControl( + lineType, + FlowChartCanvas, + eventArgs.ArgIndex, + eventArgs.ConnectionArgSourceType, + startJunction, + endJunction, + IToJunction + ); + Connections.Add(connection); + fromNodeControl.AddCnnection(connection); + toNodeControl.AddCnnection(connection); + EndConnection(); // 环境触发了创建节点连接事件 + + + } + #endregion + #region 移除连接 + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接 + { + // 需要移除连接 + var removeConnections = Connections.Where(c => c.Start.MyNode.Guid.Equals(fromNodeGuid) + && c.End.MyNode.Guid.Equals(toNodeGuid)) + .ToList(); // 获取这两个节点之间的所有连接关系 + + + + foreach (var connection in removeConnections) + { + if (connection.End is ArgJunctionControl junctionControl && junctionControl.ArgIndex == eventArgs.ArgIndex) + { + // 找到符合删除条件的连接线 + Connections.Remove(connection); // 从本地记录中移除 + fromNodeControl.RemoveConnection(connection); // 从节点持有的记录移除 + toNodeControl.RemoveConnection(connection); // 从节点持有的记录移除 + } + + + //if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + //{ + // JudgmentFlipFlopNode(control); // 连接关系变更时判断 + //} + } + } + #endregion + #endregion +#endif + } +#endif + } + #endregion + + #region 私有方法 + /// /// 创建节点控件 /// @@ -234,7 +400,7 @@ namespace Serein.Workbench.Avalonia.Services /// 返回的节点对象 /// 是否创建成功 /// 无法创建节点控件 - private bool TryCreateNodeView(Type viewType, Type viewModelType, NodeModelBase nodeModel, out INodeControl? nodeView) + private bool TryCreateNodeView(Type viewType, Type viewModelType, NodeModelBase nodeModel, out NodeControlBase? nodeView) { if (string.IsNullOrEmpty(nodeModel.Guid)) { @@ -248,16 +414,16 @@ namespace Serein.Workbench.Avalonia.Services } viewModelBase.NodeModelBase = nodeModel; // 设置节点对象 var controlObj = Activator.CreateInstance(viewType); - if (controlObj is not INodeControl nodeControl) + if (controlObj is NodeControlBase nodeControl) { - nodeView = null; - return false; + nodeControl.DataContext = viewModelBase; + nodeView = nodeControl; + return true; } else { - nodeControl.SetNodeModel(nodeModel); - nodeView = nodeControl; - return true; + nodeView = null; + return false; } // 在其它地方验证过了,所以注释 @@ -276,6 +442,47 @@ namespace Serein.Workbench.Avalonia.Services //} } + private bool TryGetControl(string nodeGuid, out NodeControlBase nodeControl) + { + if (string.IsNullOrEmpty(nodeGuid)) + { + nodeControl = null; + return false; + } + if (!NodeControls.TryGetValue(nodeGuid, out nodeControl)) + { + nodeControl = null; + return false; + } + if (nodeControl is null) + { + return false; + } + return true; + } + + #endregion + + #region 操作接口对外暴露的接口 + + /// + /// 创建节点控件 + /// + /// 控件类型 + /// 创建坐标 + /// 节点方法信息(基础节点传null) + public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position) + { + Task.Run(async () => + { + if (EnumHelper.TryConvertEnum(methodDetailsInfo.NodeType, out var nodeType)) + { + await flowEnvironment.CreateNodeAsync(nodeType, position, methodDetailsInfo); + } + }); + } + + /// /// 尝试在连接控制点之间创建连接线 /// @@ -283,25 +490,23 @@ namespace Serein.Workbench.Avalonia.Services { if (MainCanvas is not null) { - var myData = ConnectingData; - var junctionSize = startJunction.GetTransformedBounds()!.Value.Bounds.Size; - var junctionPoint = new Point(junctionSize.Width / 2, junctionSize.Height / 2); - if (startJunction.TranslatePoint(junctionPoint, MainCanvas) is Point point) + ConnectingData.Reset(); + ConnectingData.IsCreateing = true; // 表示开始连接 + ConnectingData.StartJunction = startJunction; + ConnectingData.CurrentJunction = startJunction; + if(startJunction.JunctionType == JunctionType.NextStep || startJunction.JunctionType == JunctionType.ReturnData) { - myData.StartPoint = point; + + ConnectingData.TempLine = new NodeConnectionLineView(MainCanvas, startJunction, null); } else { - return; + ConnectingData.TempLine = new NodeConnectionLineView(MainCanvas,null ,startJunction); } - myData.Reset(); - myData.IsCreateing = true; // 表示开始连接 - myData.StartJunction = startJunction; - myData.CurrentJunction = startJunction; - var junctionOfConnectionType = startJunction.JunctionType.ToConnectyionType(); - ConnectionLineShape bezierLine; // 类别 + /*var junctionOfConnectionType = startJunction.JunctionType.ToConnectyionType(); + ConnectionLineShape bezierLine; Brush brushColor; // 临时线的颜色 if (junctionOfConnectionType == JunctionOfConnectionType.Invoke) { @@ -319,11 +524,13 @@ namespace Serein.Workbench.Avalonia.Services myData.StartPoint, brushColor, isTop: true); // 绘制临时的线 - + */ //Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线 - myData.TempLine = new MyLine(MainCanvas, bezierLine); + } - } + } + + #endregion } } diff --git a/Serein.Workbench.Avalonia/Views/MainView.axaml b/Serein.Workbench.Avalonia/Views/MainView.axaml index a26f1c7..fee1b0a 100644 --- a/Serein.Workbench.Avalonia/Views/MainView.axaml +++ b/Serein.Workbench.Avalonia/Views/MainView.axaml @@ -18,7 +18,7 @@ - +