准备区分节点、参数、返回值的连接,做个备份

This commit is contained in:
fengjiayi
2024-10-23 19:22:27 +08:00
parent a495a34413
commit 0666f0b2c1
36 changed files with 1497 additions and 756 deletions

View File

@@ -0,0 +1,93 @@
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Serein.Workbench.Node.View
{
public abstract class JunctionControlBase : UserControl
{
public double _MyWidth = 20;
public double _MyHeight = 20;
protected JunctionControlBase()
{
//this.Width = 20;
//this.Height = 20;
this.MouseDown += ControlPointBase_MouseDown;
this.MouseMove += ControlPointBase_MouseMove; ;
}
#region
public static readonly DependencyProperty NodeGuidProperty =
DependencyProperty.Register("NodeGuid", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
/// <summary>
/// 所在的节点
/// </summary>
public string NodeGuid
{
get { return (string)GetValue(NodeGuidProperty); }
set { SetValue(NodeGuidProperty, value.ToString()); }
}
#endregion
#region
public static readonly DependencyProperty JunctionTypeProperty =
DependencyProperty.Register("JunctionType", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
public JunctionType JunctionType
{
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
set { SetValue(JunctionTypeProperty, value.ToString()); }
}
#endregion
public abstract void Render();
private void ControlPointBase_MouseMove(object sender, MouseEventArgs e)
{
if (GlobalJunctionData.MyGlobalData is null) return;
GlobalJunctionData.MyGlobalData.ChangingJunction = this;
}
/// <summary>
/// 在碰撞点上按下鼠标控件开始进行移动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ControlPointBase_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
GlobalJunctionData.MyGlobalData = new ConnectingData();
var myDataType = GlobalJunctionData.MyGlobalData;
myDataType.StartJunction = this;
var canvas = MainWindow.GetParentOfType<Canvas>(this);
myDataType.StartPoint = this.TranslatePoint(new Point(this.Width /2 , this.Height /2 ), canvas);
myDataType.VirtualLine = new MyLine(canvas, new Line // 虚拟线
{
Stroke = Brushes.OldLace,
StrokeThickness = 2
});
}
e.Handled = true;
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
namespace Serein.Workbench.Node.View
{
#region Model
public class MyLine
{
public MyLine(Canvas canvas, Line line)
{
Canvas = canvas;
VirtualLine = line;
canvas?.Children.Add(line);
}
public Canvas Canvas { get; set; }
public Line VirtualLine { get; set; }
public void Remove()
{
Canvas?.Children.Remove(VirtualLine);
}
}
public class ConnectingData
{
public JunctionControlBase StartJunction { get; set; }
public JunctionControlBase ChangingJunction { get; set; }
public Point StartPoint { get; set; }
public MyLine VirtualLine { get; set; }
}
public static class GlobalJunctionData
{
private static ConnectingData? myGlobalData;
public static ConnectingData? MyGlobalData
{
get => myGlobalData;
set
{
if (myGlobalData == null)
{
myGlobalData = value;
}
}
}
public static bool IsCreatingConnection => myGlobalData is not null;
public static bool CanCreate => myGlobalData?.ChangingJunction.Equals(myGlobalData?.StartJunction) == false;
public static void OK()
{
myGlobalData?.VirtualLine.Remove();
myGlobalData = null;
}
}
#endregion
}

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows;
using Serein.Workbench.Node.View;
using System.Windows.Controls;
using Serein.Library;
using System.Windows.Data;
namespace Serein.Workbench.Node.View
{
public abstract class NodeJunctionViewBase : ContentControl, IDisposable
{
public NodeJunctionViewBase()
{
var transfromGroup = new TransformGroup();
transfromGroup.Children.Add(_Translate);
RenderTransform = transfromGroup;
}
/// <summary>
/// 每个连接器都有一个唯一标识符Guid用于标识连接器。
/// </summary>
public Guid Guid
{
get => (Guid)GetValue(GuidProperty);
set => SetValue(GuidProperty, value);
}
public static readonly DependencyProperty GuidProperty = DependencyProperty.Register(
nameof(Guid),
typeof(Guid),
typeof(NodeJunctionViewBase), // NodeConnectorContent
new PropertyMetadata(Guid.Empty));
/// <summary>
/// 连接器当前的连接数,表示有多少条 NodeLink 连接到此连接器。该属性为只读。
/// </summary>
public int ConnectedCount
{
get => (int)GetValue(ConnectedCountProperty);
private set => SetValue(ConnectedCountPropertyKey, value);
}
public static readonly DependencyPropertyKey ConnectedCountPropertyKey = DependencyProperty.RegisterReadOnly(
nameof(ConnectedCount),
typeof(int),
typeof(NodeJunctionViewBase), // NodeConnectorContent
new PropertyMetadata(0));
public static readonly DependencyProperty ConnectedCountProperty = ConnectedCountPropertyKey.DependencyProperty;
/// <summary>
/// 布尔值,指示此连接器是否有任何连接。
/// </summary>
public bool IsConnected
{
get => (bool)GetValue(IsConnectedProperty);
private set => SetValue(IsConnectedPropertyKey, value);
}
public static readonly DependencyPropertyKey IsConnectedPropertyKey = DependencyProperty.RegisterReadOnly(
nameof(IsConnected),
typeof(bool),
typeof(NodeJunctionViewBase), // NodeConnectorContent
new PropertyMetadata(false));
public static readonly DependencyProperty IsConnectedProperty = IsConnectedPropertyKey.DependencyProperty;
/// <summary>
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
/// </summary>
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
nameof(Stroke),
typeof(Brush),
typeof(NodeJunctionViewBase), // NodeConnectorContent
new FrameworkPropertyMetadata(Brushes.Blue));
/// <summary>
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
/// </summary>
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
nameof(StrokeThickness),
typeof(double),
typeof(NodeJunctionViewBase), // NodeConnectorContent
new FrameworkPropertyMetadata(1.0));
/// <summary>
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
/// </summary>
public Brush Fill
{
get => (Brush)GetValue(FillProperty);
set => SetValue(FillProperty, value);
}
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
nameof(Fill),
typeof(Brush),
typeof(NodeJunctionViewBase),// NodeConnectorContent
new FrameworkPropertyMetadata(Brushes.Gray));
/// <summary>
/// 指示该连接器是否可以与其他连接器进行连接。
/// </summary>
public bool CanConnect
{
get => (bool)GetValue(CanConnectProperty);
set => SetValue(CanConnectProperty, value);
}
public static readonly DependencyProperty CanConnectProperty = DependencyProperty.Register(
nameof(CanConnect),
typeof(bool),
typeof(NodeJunctionViewBase),// NodeConnectorContent
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender));
private Point _Position = new Point();
/// <summary>
/// 该连接器的当前坐标(位置)。
/// </summary>
public Point Position
{
get => _Position;
set => UpdatePosition(value);
}
/// <summary>
/// (重要数据)表示连接器所属的节点。
/// </summary>
public NodeModelBase NodeModel { get; private set; } = null;
/// <summary>
/// 该连接器所连接的所有 NodeLink 的集合。
/// </summary>
public IEnumerable<ConnectionControl> NodeLinks => _NodeLinks;
List<ConnectionControl> _NodeLinks = new List<ConnectionControl>();
protected abstract FrameworkElement ConnectorControl { get; }
TranslateTransform _Translate = new TranslateTransform();
void UpdatePosition(Point pos)
{
_Position = pos;
_Translate.X = _Position.X;
_Translate.Y = _Position.Y;
InvalidateVisual();
}
/// <summary>
/// 将 NodeLink 添加到连接器,并更新 ConnectedCount 和 IsConnected。
/// </summary>
/// <param name="nodeLink"></param>
public void Connect(ConnectionControl nodeLink)
{
_NodeLinks.Add(nodeLink);
ConnectedCount = _NodeLinks.Count;
IsConnected = ConnectedCount > 0;
}
/// <summary>
/// 断开与某个 NodeLink 的连接,更新连接状态。
/// </summary>
/// <param name="nodeLink"></param>
public void Disconnect(ConnectionControl nodeLink)
{
_NodeLinks.Remove(nodeLink);
ConnectedCount = _NodeLinks.Count;
IsConnected = ConnectedCount > 0;
}
/// <summary>
/// 获取连接器相对于指定 Canvas 的位置。
/// </summary>
/// <param name="canvas"></param>
/// <param name="xScaleOffset"></param>
/// <param name="yScaleOffset"></param>
/// <returns></returns>
public Point GetContentPosition(Canvas canvas, double xScaleOffset = 0.5, double yScaleOffset = 0.5)
{
// it will be shifted Control position if not called UpdateLayout().
ConnectorControl.UpdateLayout();
var transformer = ConnectorControl.TransformToVisual(canvas);
var x = ConnectorControl.ActualWidth * xScaleOffset;
var y = ConnectorControl.ActualHeight * yScaleOffset;
return transformer.Transform(new Point(x, y));
}
/// <summary>
/// 更新与此连接器相连的所有 NodeLink 的位置。这个方法是抽象的,要求子类实现。
/// </summary>
/// <param name="canvas"></param>
public abstract void UpdateLinkPosition(Canvas canvas);
/// <summary>
/// 用于检查此连接器是否可以与另一个连接器相连接,要求子类实现。
/// </summary>
/// <param name="connector"></param>
/// <returns></returns>
public abstract bool CanConnectTo(NodeJunctionViewBase connector);
/// <summary>
/// 释放连接器相关的资源,包括样式、绑定和已连接的 NodeLink
/// </summary>
public void Dispose()
{
// You need to clear Style.
// Because implemented on style for binding.
Style = null;
// Clear binding for subscribing source changed event from old control.
// throw exception about visual tree ancestor different if you not clear binding.
BindingOperations.ClearAllBindings(this);
var nodeLinks = _NodeLinks.ToArray();
// it must instance to nodeLinks because change node link collection in NodeLink Dispose.
foreach (var nodeLink in nodeLinks)
{
// nodeLink.Dispose();
}
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
namespace Serein.Workbench.Node.View
{
public class ArgJunctionControl : JunctionControlBase
{
public ArgJunctionControl()
{
base.JunctionType = JunctionType.ArgData;
Render();
}
#region
public static readonly DependencyProperty ArgIndexProperty =
DependencyProperty.Register("ArgIndex", typeof(int), typeof(ArgJunctionControl), new PropertyMetadata(default(int)));
/// <summary>
/// 所在的节点
/// </summary>
public int ArgIndex
{
get { return (int)GetValue(ArgIndexProperty); }
set { SetValue(ArgIndexProperty, value); }
}
#endregion
public override void Render()
{
if(double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
var ellipse = new Ellipse
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Orange,
ToolTip = "入参"
};
Content = ellipse;
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
namespace Serein.Workbench.Node.View
{
public class ExecuteJunctionControl : JunctionControlBase
{
//public override JunctionType JunctionType { get; } = JunctionType.Execute;
public ExecuteJunctionControl()
{
base.JunctionType = JunctionType.Execute;
Render();
}
public override void Render()
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
var rect = new Rectangle
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Green,
ToolTip = "方法执行"
};
Content = rect;
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
namespace Serein.Workbench.Node.View
{
public class NextStepJunctionControl : JunctionControlBase
{
//public override JunctionType JunctionType { get; } = JunctionType.NextStep;
public NextStepJunctionControl()
{
base.JunctionType = JunctionType.NextStep;
Render();
}
public override void Render()
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
var rect = new Rectangle
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Blue,
ToolTip = "下一个方法值"
};
Content = rect;
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Windows.Media;
using System.Windows.Shapes;
using Serein.Library;
namespace Serein.Workbench.Node.View
{
public class ResultJunctionControl : JunctionControlBase
{
//public override JunctionType JunctionType { get; } = JunctionType.ReturnData;
public ResultJunctionControl()
{
base.JunctionType = JunctionType.ReturnData;
Render();
}
public override void Render()
{
if (double.IsNaN(base.Width))
{
base.Width = base._MyWidth;
}
if (double.IsNaN(base.Height))
{
base.Height = base._MyHeight;
}
var rect = new Rectangle
{
Width = base.Width,
Height = base.Height,
Fill = Brushes.Red,
ToolTip = "返回值"
};
Content = rect;
}
}
}

View File

@@ -0,0 +1,80 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Node.ViewModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Serein.Workbench.Node.View
{
/// <summary>
/// 节点控件基类(控件)
/// </summary>
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
{
public NodeControlViewModelBase ViewModel { get; set; }
protected NodeControlBase()
{
this.Background = Brushes.Transparent;
}
protected NodeControlBase(NodeControlViewModelBase viewModelBase)
{
ViewModel = viewModelBase;
this.Background = Brushes.Transparent;
this.DataContext = viewModelBase;
SetBinding();
}
public void SetBinding()
{
// 绑定 Canvas.Left
Binding leftBinding = new Binding("X")
{
Source = ViewModel.NodeModel.Position, // 如果 X 属性在当前 DataContext 中
Mode = BindingMode.TwoWay
};
BindingOperations.SetBinding(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);
}
}
//public class FLowNodeObObservableCollection<T> : ObservableCollection<T>
//{
// public void AddRange(IEnumerable<T> items)
// {
// foreach (var item in items)
// {
// this.Items.Add(item);
// }
// OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
// }
//}
}

View File

@@ -0,0 +1,320 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Extension;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using Color = System.Windows.Media.Color;
using ColorConverter = System.Windows.Media.ColorConverter;
using Point = System.Windows.Point;
namespace Serein.Workbench.Node.View
{
#region
public class ConnectionModelBase
{
/// <summary>
/// 起始节点
/// </summary>
public NodeModelBase StartNode { get; set; }
/// <summary>
/// 目标节点
/// </summary>
public NodeModelBase EndNode { get; set; }
/// <summary>
/// 来源于起始节点的(控制点)类型
/// </summary>
public JunctionType JoinTypeOfStart { get; set; }
/// <summary>
/// 连接到目标节点的(控制点)类型
/// </summary>
public JunctionType JoinTypeOfEnd { get; set; }
/// <summary>
/// 连接类型
/// </summary>
public ConnectionType Type { get; set; }
}
public interface IJunctionNode
{
string BoundNodeGuid { get; }
}
/// <summary>
/// 连接点
/// </summary>
public class JunctionNode : IJunctionNode
{
/// <summary>
/// 连接点类型
/// </summary>
public JunctionType JunctionType { get; }
/// <summary>
/// 对应的视图对象
/// </summary>
public NodeModelBase NodeModel { get; set; }
/// <summary>
///
/// </summary>
public string BoundNodeGuid { get => NodeModel.Guid; }
}
/*
* 有1个Execute
* 有1个NextStep
* 有0~65535个入参 ushort
* 有1个ReturnData(void方法返回null)
*
* Execute // 执行这个方法
* 只接受 NextStep 的连接
* ArgData
* 互相之间不能连接,只能接受 Execute、ReturnData 的连接
* Execute表示从 Execute所在节点 获取数据
* ReturnData 表示从对应节点获取数据
* ReturnData:
* 只能发起主动连接,且只能连接到 ArgData
* NextStep
* 只能连接连接 Execute
*
*
*
*/
#endregion
/// <summary>
/// 连接控件,表示控件的连接关系
/// </summary>
public class ConnectionControl : Shape
{
private readonly IFlowEnvironment environment;
/// <summary>
/// 初始化连接控件
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
public ConnectionControl(IFlowEnvironment environment,
Canvas Canvas,
ConnectionType Type,
NodeControlBase Start,
NodeControlBase End)
{
this.environment = environment;
this.Canvas = Canvas;
this.Type = Type;
this.Start = Start;
this.End = End;
InitElementPoint();
}
/// <summary>
/// 所在的画布
/// </summary>
public Canvas Canvas { get; }
/// <summary>
/// 连接类型
/// </summary>
public ConnectionType Type { get; }
/// <summary>
/// 起始控件
/// </summary>
public NodeControlBase Start { get; set; }
/// <summary>
/// 结束控件
/// </summary>
public NodeControlBase End { get; set; }
/// <summary>
/// 配置连接曲线的右键菜单
/// </summary>
/// <param name="line"></param>
private void ConfigureLineContextMenu(ConnectionControl connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ContextMenu = contextMenu;
connection.ContextMenu = contextMenu;
}
/// <summary>
/// 删除该连线
/// </summary>
/// <param name="line"></param>
private void DeleteConnection(ConnectionControl connection)
{
var connectionToRemove = connection;
if (connectionToRemove is null)
{
return;
}
// 获取起始节点与终止节点,消除映射关系
var fromNodeGuid = connectionToRemove.Start.ViewModel.NodeModel.Guid;
var toNodeGuid = connectionToRemove.End.ViewModel.NodeModel.Guid;
environment.RemoveConnectAsync(fromNodeGuid, toNodeGuid, connection.Type);
}
/// <summary>
/// 移除
/// </summary>
public void RemoveFromCanvas()
{
Canvas.Children.Remove(this); // 移除线
}
/// <summary>
/// 重新绘制
/// </summary>
public void AddOrRefreshLine()
{
this.InvalidateVisual();
}
public void InitElementPoint()
{
leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心
rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心
brush = GetLineColor(Type); // 线条颜色
hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 1.0); // 默认可视化Pen
visualPen.Freeze(); // Freeze以提高性能
ConfigureLineContextMenu(this); // 设置连接右键事件
linkSize = 4; // 整线条粗细
Canvas.Children.Add(this); // 添加线
Grid.SetZIndex(this, -9999999); // 置底
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
RefreshPoint(Canvas, this.Start, this.End); // 刷新坐标
DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize, brush); // 刷新线条显示位置
}
private readonly StreamGeometry streamGeometry = new StreamGeometry();
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
private Pen hitVisiblePen; // 初始化碰撞检测线
private Pen visualPen; // 默认可视化Pen
private Point startPoint; // 连接线的起始节点
private Point endPoint; // 连接线的终点
private Brush brush; // 线条颜色
double linkSize; // 根据缩放比例调整线条粗细
protected override Geometry DefiningGeometry => streamGeometry;
#region
public void RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement)
{
endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
}
/// <summary>
/// 根据连接类型指定颜色
/// </summary>
/// <param name="currentConnectionType"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static SolidColorBrush GetLineColor(ConnectionType currentConnectionType)
{
return currentConnectionType switch
{
ConnectionType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")),
ConnectionType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")),
ConnectionType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")),
ConnectionType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
_ => throw new Exception(),
};
}
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end,
double linkSize,
Brush brush,
bool isHitTestVisible = false,
double strokeThickness = 1.0,
bool isMouseOver = false,
double dashOffset = 0.0)
{
// 控制点的计算逻辑
double power = 8 * 8; // 控制贝塞尔曲线的“拉伸”强度
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
// 计算拉伸程度k拉伸与水平夹角正相关
var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0);
// 如果起点x大于终点x增加额外的偏移量避免重叠
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
streamGeometry.Clear();
using (var context = streamGeometry.Open())
{
context.BeginFigure(start, true, false); // 曲线起点
context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
// 绘制碰撞检测线
//if (true)
//{
// //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness);
// //hitVisiblePen.Freeze();
// drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry);
//}
//else
//{
//}
}
#endregion
}
}