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
{
///
/// 起始节点
///
public NodeModelBase StartNode { get; set; }
///
/// 目标节点
///
public NodeModelBase EndNode { get; set; }
///
/// 来源于起始节点的(控制点)类型
///
public JunctionType JoinTypeOfStart { get; set; }
///
/// 连接到目标节点的(控制点)类型
///
public JunctionType JoinTypeOfEnd { get; set; }
///
/// 连接类型
///
public ConnectionType Type { get; set; }
}
public interface IJunctionNode
{
string BoundNodeGuid { get; }
}
///
/// 连接点
///
public class JunctionNode : IJunctionNode
{
///
/// 连接点类型
///
public JunctionType JunctionType { get; }
///
/// 对应的视图对象
///
public NodeModelBase NodeModel { get; set; }
///
///
///
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
///
/// 连接控件,表示控件的连接关系
///
public class ConnectionControl : Shape
{
private readonly IFlowEnvironment environment;
///
/// 初始化连接控件
///
///
///
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();
}
///
/// 所在的画布
///
public Canvas Canvas { get; }
///
/// 连接类型
///
public ConnectionType Type { get; }
///
/// 起始控件
///
public NodeControlBase Start { get; set; }
///
/// 结束控件
///
public NodeControlBase End { get; set; }
///
/// 配置连接曲线的右键菜单
///
///
private void ConfigureLineContextMenu(ConnectionControl connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ContextMenu = contextMenu;
connection.ContextMenu = contextMenu;
}
///
/// 删除该连线
///
///
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);
}
///
/// 移除
///
public void RemoveFromCanvas()
{
Canvas.Children.Remove(this); // 移除线
}
///
/// 重新绘制
///
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); // 置底
}
///
/// 控件重绘事件
///
///
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); // 获取起始节点的中心位置
}
///
/// 根据连接类型指定颜色
///
///
///
///
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
}
}