添加Avalonia项目的demo

This commit is contained in:
fengjiayi
2025-01-01 17:49:48 +08:00
parent 6c6d493f93
commit 28df2d8fce
61 changed files with 4404 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
using System;
using Avalonia;
namespace Serein.Workbench.Avalonia.Desktop;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
One for Windows with net8.0-windows TFM, one for MacOS with net8.0-macos and one with net8.0 TFM for Linux.-->
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Serein.Workbench.Avalonia\Serein.Workbench.Avalonia.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -0,0 +1,17 @@
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Api
{
/// <summary>
/// 流程事件管理,转发流程运行环境中触发的事件到工作台各个订阅者
/// </summary>
internal interface IFlowEEForwardingService : IFlowEnvironmentEvent
{
}
}

View File

@@ -0,0 +1,23 @@
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Api
{
internal interface INodeControl
{
/// <summary>
/// 对应的节点实体
/// </summary>
NodeModelBase NodeModelBase { get; }
/// <summary>
/// 初始化使用的方法,设置节点实体
/// </summary>
/// <param name="nodeModel"></param>
void SetNodeModel(NodeModelBase nodeModel);
}
}

View File

@@ -0,0 +1,52 @@
using Serein.Library;
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.Api
{
/// <summary>
/// 约束一个节点应该有哪些控制点
/// </summary>
public interface INodeJunction
{
/// <summary>
/// 方法执行入口控制点
/// </summary>
NodeJunctionView ExecuteJunction { get; }
/// <summary>
/// 执行完成后下一个要执行的方法控制点
/// </summary>
NodeJunctionView NextStepJunction { get; }
/// <summary>
/// 参数节点控制点
/// </summary>
NodeJunctionView[] ArgDataJunction { get; }
/// <summary>
/// 返回值控制点
/// </summary>
NodeJunctionView ReturnDataJunction { get; }
/// <summary>
/// 获取目标参数控制点用于防止wpf释放资源导致找不到目标节点返回-1,-1的坐标
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
NodeJunctionView GetJunctionOfArgData(int index)
{
var arr = ArgDataJunction;
if (index >= arr.Length)
{
return null;
}
return arr[index];
}
}
}

View File

@@ -0,0 +1,21 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Serein.Workbench.Avalonia.App"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--自定义控件:依赖信息-->
<ResourceInclude Source="/Custom/Views/FlowLibraryInfoView.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,139 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Env;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Custom.Node.ViewModels;
using Serein.Workbench.Avalonia.Custom.Node.Views;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using Serein.Workbench.Avalonia.Services;
using Serein.Workbench.Avalonia.ViewModels;
using Serein.Workbench.Avalonia.Views;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia;
public static class ServiceCollectionExtensions
{
/// <summary>
/// 注册ViewModel
/// </summary>
/// <param name="collection"></param>
public static void AddViewModelServices(this IServiceCollection collection)
{
collection.AddTransient<MainViewModel>(); // 主窗体
collection.AddTransient<MainMenuBarViewModel>(); // 主窗体菜单
collection.AddTransient<FlowLibrarysViewModel>(); // 依赖集合
collection.AddTransient<FlowLibraryMethodInfoViewModel>(); // 预览的方法信息
//collection.AddTransient<ParameterDetailsViewModel>(); // 节点参数信息
collection.AddTransient<NodeContainerViewModel>(); // 节点容器(画布)
collection.AddTransient<ActionNodeViewModel>(); // 节点容器(画布)
//collection.AddTransient<FlowLibraryInfoViewModel>(); // 依赖信息
}
public static void AddWorkbenchServices(this IServiceCollection collection)
{
collection.AddSingleton<IFlowEEForwardingService, FlowEEForwardingService>(); // 流程事件管理
collection.AddSingleton<IWorkbenchEventService, WorkbenchEventService>(); // 流程事件管理
collection.AddSingleton<INodeOperationService, NodeOperationService>(); // 节点操作管理
collection.AddSingleton<IKeyEventService, KeyEventService>(); // 按键事件管理
//collection.AddSingleton<FlowNodeControlService>(); // 流程节点控件管理
}
/// <summary>
/// 注册流程接口相关实例
/// </summary>
/// <param name="collection"></param>
public static void AddFlowServices(this IServiceCollection collection)
{
#region
Func<SynchronizationContext> getSyncContext = null;
Dispatcher.UIThread.Invoke(() =>
{
var uiContext = SynchronizationContext.Current; // 在UI线程上获取UI线程上下文信息
if(uiContext is not null)
{
getSyncContext = () => uiContext;
}
});
UIContextOperation? uIContextOperation = null;
uIContextOperation = new UIContextOperation(getSyncContext); // 封装一个调用UI线程的工具类
FlowEnvironmentDecorator flowEnvironmentDecorator = new FlowEnvironmentDecorator(uIContextOperation);
collection.AddSingleton<UIContextOperation>(uIContextOperation); // 注册UI线程操作上下文
collection.AddSingleton<IFlowEnvironment>(flowEnvironmentDecorator); // 注册运行环境
collection.AddSingleton<IFlowEnvironmentEvent>(flowEnvironmentDecorator); // 注册运行环境事件
//var strte = tcs.Task.ConfigureAwait(false).GetAwaiter().GetResult();
//if (strte) // 等待实例生成完成
//{
//}
#endregion
}
}
public partial class App : Application
{
private static IServiceProvider? ServiceProvider;
public static T GetService<T>() where T : class
{
return ServiceProvider?.GetService<T>() ?? throw new NullReferenceException();
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override async void OnFrameworkInitializationCompleted()
{
// 如果使用 CommunityToolkit则需要用下面一行移除 Avalonia 数据验证。
// 如果没有这一行,数据验证将会在 Avalonia 和 CommunityToolkit 中重复。
BindingPlugins.DataValidators.RemoveAt(0);
// 注册应用程序运行所需的所有服务
var collection = new ServiceCollection();
collection.AddWorkbenchServices();
collection.AddFlowServices();
collection.AddViewModelServices();
var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口
App.ServiceProvider = services;
var vm = App.ServiceProvider?.GetRequiredService<MainViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = vm
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = vm
};
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Commands
{
internal abstract class CommandBase
{
// CanExecuteChanged 事件
public event EventHandler CanExecuteChanged;
/// <summary>
/// 是否可以执行命令,子类可以重写这个方法来提供具体的可执行条件
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public virtual bool CanExecute(object parameter)
{
return true; // 默认实现返回 true表示命令可以执行
}
/// <summary>
/// 执行命令,子类可以重写这个方法来实现具体的命令逻辑
/// </summary>
/// <param name="parameter"></param>
public abstract void Execute(object parameter);
/// <summary>
/// 用于触发 CanExecuteChanged 事件
/// </summary>
protected void OnCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Commands
{
/// <summary>
/// 流程控制命令
/// </summary>
internal class MyCommand : CommandBase
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
/// <summary>
/// 构造函数接收执行动作和是否可执行的条件
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
/// <exception cref="ArgumentNullException"></exception>
public MyCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
/// <summary>
/// 重写 CanExecute 方法,基于 _canExecute 委托的结果来判断命令是否可执行
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public override bool CanExecute(object parameter)
{
return _canExecute?.Invoke() ?? true;
}
/// <summary>
/// 重写 Execute 方法,执行具体的命令逻辑
/// </summary>
/// <param name="parameter"></param>
public override void Execute(object parameter)
{
_execute();
}
}
}

View File

@@ -0,0 +1,127 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Avalonia;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.VisualTree;
namespace Serein.Workbench.Avalonia.Controls
{
/// <summary>
/// 实现拖动的控件
/// </summary>
public partial class DragControls : UserControl
{
/// <summary>
/// 记录上一次鼠标位置
/// </summary>
private Point lastMousePosition;
/// <summary>
/// 用于平滑更新坐标的计时器
/// </summary>
private DispatcherTimer _timer;
/// <summary>
/// 标记是否先启动了拖动
/// </summary>
private bool isDragging = false;
/// <summary>
/// 需要更新的坐标点
/// </summary>
private PixelPoint _targetPosition;
public DragControls()
{
InitializeComponent();
// 添加当前控件的事件监听
PointerPressed += OnPointerPressed;
PointerMoved += OnPointerMoved;
PointerReleased += OnPointerReleased;
// 初始化计时器
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(10)
};
_timer.Tick += OnTimerTick;
}
/// <summary>
/// 计时器事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTimerTick(object sender, EventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null && window.Position != _targetPosition)
{
// 更新坐标
window.Position = _targetPosition;
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
// 启动拖动
isDragging = true;
// 记录当前坐标
lastMousePosition = e.GetPosition(this);
e.Handled = true;
// 启动计时器
_timer.Start();
}
private void OnPointerReleased(object sender, PointerReleasedEventArgs e)
{
if (!isDragging) return;
// 停止拖动
isDragging = false;
e.Handled = true;
// 停止计时器
_timer.Stop();
}
private void OnPointerMoved(object sender, PointerEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
// 如果没有启动拖动,则不执行
if (!isDragging) return;
var currentMousePosition = e.GetPosition(this);
var offset = currentMousePosition - lastMousePosition;
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
// 记录当前坐标
_targetPosition = new PixelPoint(window.Position.X + (int)offset.X,
window.Position.Y + (int)offset.Y);
}
}
}
}

View File

@@ -0,0 +1,31 @@
using Avalonia.Data.Converters;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Converters
{
public class BoolToBrushConverter : IValueConverter
{
public IBrush TrueBrush { get; set; } = Brushes.LightBlue;
public IBrush FalseBrush { get; set; } = Brushes.Transparent;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? TrueBrush : FalseBrush;
}
return FalseBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return false;
}
}
}

View File

@@ -0,0 +1,42 @@
using Avalonia.Data;
using Avalonia.Data.Converters;
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Converters
{
internal class IsVisibleOfParameterConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if(value is ParameterDetails pd)
{
if (pd.ExplicitTypeName == "Value")
{
return false;
}
else
{
return true;
}
}
// converter used for the wrong type
return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,245 @@
using Avalonia.Controls.Shapes;
using Avalonia.Controls;
using Avalonia.Media;
using Serein.Workbench.Avalonia.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.VisualTree;
using Serein.Library;
namespace Serein.Workbench.Avalonia.Custom.Junction
{
public abstract class JunctionControlBase : Control
{
protected JunctionControlBase()
{
this.Width = 25;
this.Height = 20;
this.PointerPressed += JunctionControlBase_PointerPressed;
this.PointerMoved += JunctionControlBase_PointerMoved;
this.PointerExited += JunctionControlBase_PointerExited;
}
/*
#region 控件属性,所在的节点
public static readonly DependencyProperty NodeProperty =
DependencyProperty.Register(nameof(MyNode), typeof(NodeModelBase), typeof(JunctionControlBase), new PropertyMetadata(default(NodeModelBase)));
//public NodeModelBase NodeModel;
/// <summary>
/// 所在的节点
/// </summary>
public NodeModelBase MyNode
{
get { return (NodeModelBase)GetValue(NodeProperty); }
set { SetValue(NodeProperty, value); }
}
#endregion
#region 控件属性,连接器类型
public static readonly DependencyProperty JunctionTypeProperty =
DependencyProperty.Register(nameof(JunctionType), typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
/// <summary>
/// 控制点类型
/// </summary>
public JunctionType JunctionType
{
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
set { SetValue(JunctionTypeProperty, value.ToString()); }
}
#endregion
protected override Geometry DefiningGeometry => StreamGeometry;
*/
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
/// <summary>
/// 重绘方法
/// </summary>
/// <param name="drawingContext"></param>
public abstract void OnRender(DrawingContext drawingContext);
/// <summary>
/// 中心点
/// </summary>
public abstract Point MyCenterPoint { get; }
/// <summary>
/// 禁止连接
/// </summary>
private bool IsConnectionDisable;
/// <summary>
/// 处理鼠标悬停状态
/// </summary>
private bool _isMouseOver;
public bool IsMouseOver
{
get => _isMouseOver;
set
{
if (_isMouseOver != value)
{
//GlobalJunctionData.MyGlobalConnectingData.CurrentJunction = this;
_isMouseOver = value;
InvalidateVisual();
}
}
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
public override void Render(DrawingContext drawingContext)
{
OnRender(drawingContext);
}
/// <summary>
/// 获取背景颜色
/// </summary>
/// <returns></returns>
protected Brush GetBackgrounp()
{
return (Brush)Brushes.Transparent;
//var myData = GlobalJunctionData.MyGlobalConnectingData;
//if (!myData.IsCreateing)
//{
// return Brushes.Transparent;
//}
//if (IsMouseOver)
//{
// if (myData.IsCanConnected)
// {
// if (myData.Type == JunctionOfConnectionType.Invoke)
// {
// return myData.ConnectionInvokeType.ToLineColor();
// }
// else
// {
// return myData.ConnectionArgSourceType.ToLineColor();
// }
// }
// else
// {
// return Brushes.Red;
// }
//}
//else
//{
// return Brushes.Transparent;
//}
}
private object lockObj = new object();
/// <summary>
/// 控件获得鼠标焦点事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void JunctionControlBase_PointerMoved(object? sender, global::Avalonia.Input.PointerEventArgs e)
{
//if (!GlobalJunctionData.MyGlobalConnectingData.IsCreateing) return;
//if (IsMouseOver) return;
IsMouseOver = true;
this.InvalidateVisual();
}
/// <summary>
/// 控件失去鼠标焦点事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void JunctionControlBase_PointerExited(object? sender, global::Avalonia.Input.PointerEventArgs e)
{
IsMouseOver = false;
e.Handled = true;
}
/// <summary>
/// 在碰撞点上按下鼠标控件开始进行移动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void JunctionControlBase_PointerPressed(object? sender, global::Avalonia.Input.PointerPressedEventArgs e)
{
throw new NotImplementedException();
//if (e.LeftButton == MouseButtonState.Pressed)
//{
// var canvas = MainWindow.GetParentOfType<Canvas>(this);
// if (canvas != null)
// {
// var myData = GlobalJunctionData.MyGlobalConnectingData;
// myData.Reset();
// myData.IsCreateing = true; // 表示开始连接
// myData.StartJunction = this;
// myData.CurrentJunction = this;
// myData.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
// var junctionOfConnectionType = this.JunctionType.ToConnectyionType();
// ConnectionLineShape bezierLine; // 类别
// Brush brushColor; // 临时线的颜色
// if (junctionOfConnectionType == JunctionOfConnectionType.Invoke)
// {
// brushColor = ConnectionInvokeType.IsSucceed.ToLineColor();
// }
// else if (junctionOfConnectionType == JunctionOfConnectionType.Arg)
// {
// brushColor = ConnectionArgSourceType.GetOtherNodeData.ToLineColor();
// }
// else
// {
// return;
// }
// bezierLine = new ConnectionLineShape(LineType.Bezier,
// myData.StartPoint,
// myData.StartPoint,
// brushColor,
// isTop: true); // 绘制临时的线
// Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
// myData.MyLine = new MyLine(canvas, bezierLine);
// }
//}
//e.Handled = true;
}
/// <summary>
/// 获取起始控制点
/// </summary>
/// <returns></returns>
private Point GetStartPoint()
{
if (this.GetTransformedBounds() is TransformedBounds transformed)
{
var size = transformed.Bounds.Size;
return new Point(size.Width / 2, size.Height / 2); // 起始节点选择右侧边缘中心
}
else
{
return new Point(0, 0);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
using Serein.NodeFlow.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.ViewModels
{
internal partial class ActionNodeViewModel : NodeViewModelBase
{
[ObservableProperty]
private SingleActionNode? nodeMoel;
internal override NodeModelBase NodeModelBase
{ get => NodeMoel ?? throw new NotImplementedException(); set => NodeMoel = (SingleActionNode)value; }
public ActionNodeViewModel()
{
}
}
}

View File

@@ -0,0 +1,19 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
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.ViewModels
{
/// <summary>
/// 节点ViewModel基类
/// </summary>
internal abstract class NodeViewModelBase : ViewModelBase
{
internal abstract NodeModelBase NodeModelBase { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="100"
x:Class="Serein.Workbench.Avalonia.Custom.Node.Views.ActionNodeView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.Node.ViewModels"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:dtp="using:Serein.Workbench.Avalonia.DataTemplates"
Background="#C6EEF7"
x:DataType="vm:ActionNodeViewModel">
<Design.DataContext>
<vm:ActionNodeViewModel />
</Design.DataContext>
<Border>
<Grid RowDefinitions="25,*,*,*,*">
<!--调用控制点,方法名称,下一个方法调用控制点-->
<Grid x:Name="HeaderGrid" Grid.Row="0" ColumnDefinitions="auto,*,auto" VerticalAlignment="Center">
<cv:NodeJunctionView Grid.Column="0" JunctionType="Execute" MyNode="{Binding NodeMoel}" Width="30" Height="15" Margin="4,0,2,0" />
<StackPanel Grid.Column="1" Grid.RowSpan="2" >
<TextBlock Text="{Binding NodeMoel.DisplayName}" FontSize="17" HorizontalAlignment="Center">
<ToolTip.Tip>
<StackPanel >
<TextBlock Text="{Binding NodeMoel.MethodDetails}" FontSize="12"/>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
</StackPanel>
<cv:NodeJunctionView Grid.Column="2" JunctionType="NextStep" MyNode="{Binding NodeMoel}" Width="30" Height="15" Margin="2,0,8,0"/>
</Grid>
<!--入参信息-->
<StackPanel Grid.Row="1" Background="#E3FDFD">
<ItemsControl ItemsSource="{Binding NodeMoel.MethodDetails.ParameterDetailss}">
<ItemsControl.ItemTemplate>
<dtp:NodeMethodParameterInfoDataTemplate>
</dtp:NodeMethodParameterInfoDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,26 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Library;
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
{
private ActionNodeViewModel _vm;
public ActionNodeView()
{
InitializeComponent();
_vm = App.GetService<ActionNodeViewModel>();
DataContext = _vm;
}
NodeModelBase INodeControl.NodeModelBase => _vm.NodeModelBase ?? throw new System.NotImplementedException(); // ¶¯×÷½Úµã
void INodeControl.SetNodeModel(NodeModelBase nodeModel) // ¶¯×÷½Úµã
{
_vm.NodeModelBase = nodeModel;
}
}

View File

@@ -0,0 +1,63 @@
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia;
using Serein.Library;
using Serein.Workbench.Avalonia.ViewModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Collections;
using System.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Reflection;
namespace Serein.Workbench.Avalonia.Custom.ViewModels
{
internal partial class FlowLibraryInfoViewModel:ViewModelBase
{
/// <summary>
/// 依赖名称
/// </summary>
[ObservableProperty]
public string _libraryName;
private ObservableCollection<MethodDetailsInfo> activateMethods;
private ObservableCollection<MethodDetailsInfo> flipflopMethods;
/// <summary>
/// 动作节点
/// </summary>
public ObservableCollection<MethodDetailsInfo> ActivateMethods { get => activateMethods; set => SetProperty(ref activateMethods,value); }
/// <summary>
/// 触发器节点
/// </summary>
public ObservableCollection<MethodDetailsInfo> FlipflopMethods { get => flipflopMethods; set => SetProperty(ref activateMethods, value); }
///// <summary>
///// 加载项目信息
///// </summary>
///// <param name="libraryMds"></param>
//public void LoadLibraryInfo(LibraryMds libraryMds)
//{
// this.AssemblyName = libraryMds.AssemblyName;
//}
}
}

View File

@@ -0,0 +1,67 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
using Serein.Workbench.Avalonia.Services;
using Serein.Workbench.Avalonia.ViewModels;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Custom.ViewModels
{
internal partial class FlowLibraryMethodInfoViewModel : ViewModelBase
{
/// <summary>
/// 当前预览的方法信息
/// </summary>
[ObservableProperty]
private MethodDetailsInfo methodDetailsInfo;
private IWorkbenchEventService workbenchEventService;
public FlowLibraryMethodInfoViewModel()
{
workbenchEventService = App.GetService<IWorkbenchEventService>();
workbenchEventService.OnPreviewlMethodInfo += WorkbenchEventService_OnPreviewlMethodInfo;
methodDetailsInfo = new MethodDetailsInfo
{
AssemblyName = "wait selection...",
MethodAnotherName = "wait selection...",
MethodName = "wait selection...",
NodeType = "wait selection...",
ReturnTypeFullName = "wait selection...",
IsParamsArgIndex = -1,
ParameterDetailsInfos = []
};
}
private void WorkbenchEventService_OnPreviewlMethodInfo(PreviewlMethodInfoEventArgs eventArgs)
{
var mdInfo = eventArgs.MethodDetailsInfo;
MethodDetailsInfo = mdInfo;
Debug.WriteLine($"预览了 {mdInfo.AssemblyName } - {mdInfo.MethodAnotherName} 方法");
}
}
}

View File

@@ -0,0 +1,60 @@
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Services;
using Serein.Workbench.Avalonia.ViewModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Custom.ViewModels
{
internal partial class FlowLibrarysViewModel : ViewModelBase
{
/// <summary>
/// 运行环境
/// </summary>
private IFlowEnvironment flowEnvironment { get; }
/// <summary>
/// 流程运行环境事件服务
/// </summary>
private IFlowEEForwardingService feefService { get; }
/// <summary>
/// 运行环境加载的依赖
/// </summary>
public ObservableCollection<LibraryMds> LibraryList { get; } = new ObservableCollection<LibraryMds>();
public FlowLibrarysViewModel()
{
flowEnvironment = App.GetService<IFlowEnvironment>();
feefService = App.GetService<IFlowEEForwardingService>();
feefService.OnDllLoad += FeefService_OnDllLoad; ;
}
/// <summary>
/// 加载了依赖信息
/// </summary>
/// <param name="e"></param>
private void FeefService_OnDllLoad(LoadDllEventArgs e)
{
Debug.WriteLine(e.NodeLibraryInfo.AssemblyName + " count :" + e.MethodDetailss.Count);
var libraryMds = new LibraryMds { AssemblyName = e.NodeLibraryInfo.AssemblyName, Mds = e.MethodDetailss.ToArray() };
LibraryList.Add(libraryMds);
}
}
}

View File

@@ -0,0 +1,36 @@
using Microsoft.Extensions.DependencyInjection;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.ViewModels;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Custom.ViewModels
{
internal partial class MainMenuBarViewModel : ViewModelBase
{
private IFlowEnvironment flowEnvironment { get; }
public MainMenuBarViewModel()
{
flowEnvironment = App.GetService<IFlowEnvironment>();
var uiContextOperation = App.GetService<UIContextOperation>();
}
public void SaveProjectCommand()
{
flowEnvironment.SaveProject();
}
}
}

View File

@@ -0,0 +1,29 @@
using Avalonia.Controls;
using Serein.Library.Api;
using Serein.NodeFlow.Env;
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.ViewModels
{
internal class NodeContainerViewModel : ViewModelBase
{
/// <summary>
/// 正在创建方法调用关系的连接线
/// </summary>
public bool IsConnectionInvokeNode { get; set; } = false;
/// <summary>
/// 正在创建参数传递关系的连接线
/// </summary>
public bool IsConnectionArgSourceNode { get; set; } = false;
public NodeContainerViewModel()
{
}
}
}

View File

@@ -0,0 +1,74 @@
using Avalonia.Controls;
using Avalonia;
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using Serein.Workbench.Avalonia.ViewModels;
namespace Serein.Workbench.Avalonia.Custom.ViewModels
{
internal partial class ParameterDetailsViewModel : ViewModelBase
{
public ParameterDetails ParameterDetails { get; set; }
public ParameterDetailsViewModel()
{
}
public ParameterDetailsViewModel(ParameterDetails parameterDetails)
{
ParameterDetails = parameterDetails;
RefreshIsVisible();
// 监视“是否为显式参数”更改
ParameterDetails.PropertyChanged += (o, e) =>
{
if (nameof(ParameterDetails.IsExplicitData).Equals(e.PropertyName))
RefreshIsVisible();
};
}
private void RefreshIsVisible()
{
if (!ParameterDetails.IsExplicitData)
{
// 并非显式设置参数
IsVisibleA = true;
IsVisibleB = false;
IsVisibleC = false;
return;
}
if ("Value".Equals(ParameterDetails.ExplicitTypeName))
{
// 值类型
IsVisibleA = false;
IsVisibleB = true;
IsVisibleC = false;
}
else
{
// 选项类型
IsVisibleA = false;
IsVisibleB = false;
IsVisibleC = true;
}
}
[ObservableProperty]
private bool isVisibleA;
[ObservableProperty]
private bool isVisibleB;
[ObservableProperty]
private bool isVisibleC;
}
}

View File

@@ -0,0 +1,217 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Skia;
using Avalonia.Styling;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Extension;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Custom.Views
{
public class ConnectionLineShape : Control
{
private readonly double strokeThickness;
/// <summary>
/// 确定起始坐标和目标坐标、外光样式的曲线
/// </summary>
/// <param name="start">起始坐标</param>
/// <param name="end">结束坐标</param>
/// <param name="brush">颜色</param>
/// <param name="isDotted">是否为虚线</param>
public ConnectionLineShape(Point start,
Point end,
Brush brush,
bool isDotted = false,
bool isTop = false)
{
this.brush = brush;
startPoint = start;
endPoint = end;
this.strokeThickness = 4;
InitElementPoint(isDotted, isTop);
InvalidateVisual(); // 触发重绘
}
public void InitElementPoint(bool isDotted, bool isTop = false)
{
//hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
//hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 3.0); // 默认可视化Pen
opacity = 1.0d;
var dashStyle = new DashStyle();
if (isDotted)
{
opacity = 0.42d;
visualPen.DashStyle = new DashStyle(); // DashStyles.Dash; // 选择虚线样式
}
//visualPen.Freeze(); // Freeze以提高性能
linkSize = 4; // 整线条粗细
int zIndex = 999999;
this.ZIndex = zIndex;
//Panel.SetZIndex(this, zIndex); // 置底
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
public void UpdatePoints(Point start, Point end)
{
startPoint = start;
endPoint = end;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="point"></param>
public void UpdateEndPoints(Point point)
{
endPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条起点位置
/// </summary>
/// <param name="point"></param>
public void UpdateStartPoints(Point point)
{
startPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
public override void Render(DrawingContext drawingContext)
{
// 刷新线条显示位置
DrawBezierCurve(drawingContext, startPoint, endPoint);
}
#region
private 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; // 线条颜色
private double opacity; // 透明度
double linkSize; // 根据缩放比例调整线条粗细
//public void UpdateLineColor()
//{
// visualPen = new Pen(brush, 3.0); // 默认可视化Pen
// InvalidateVisual(); // 触发重绘
//}
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private int i = 0;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end)
{
// 控制点的计算逻辑
double power = 140; // 控制贝塞尔曲线的“拉伸”强度
drawingContext.PushOpacity(opacity);
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
//var dp = axis.DotProduct(startToEnd);
//dp = dp < 50 ? 50 : dp;
//var pow = Math.Max(0, dp) ;
//var k = 1 - Math.Pow(pow, 10.0);
//
//Debug.WriteLine("pow : " + pow);
//Debug.WriteLine("k : " + k);
//Debug.WriteLine("");
// 计算拉伸程度k拉伸与水平夹角正相关
var dp = axis.DotProduct(startToEnd) ;
var pow = Math.Pow(dp, 10.0);
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;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
// why can't clearValue()?
//streamGeometry.ClearValue(ThemeProperty);
//var streamGeometry = new StreamGeometry();
//if( i++ > 100 && streamGeometry is AvaloniaObject avaloniaObject)
//{
// var platformImplInfo = streamGeometry.GetType().GetProperty("PlatformImpl", BindingFlags.NonPublic | BindingFlags.Instance);
// var platformImpl = platformImplInfo?.GetValue(streamGeometry);
// var pathCache = platformImpl?.GetType().GetField("_bounds", BindingFlags.NonPublic | BindingFlags.Instance);
// if(pathCache is IDisposable disposable)
// {
// disposable.Dispose();
// }
// //pathCache?.Invoke(platformImpl, []);
// Debug.WriteLine("invoke => InvalidateCaches");
// i = 0;
// //public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged
// //private readonly ValueStore _values;
//}
// this is override "Render()" method
// display a bezier-line on canvas and follow the mouse in real time
// I don't want to re-instantiate StreamGeometry()
// because I want to reduce the number of GC
// but , it seems impossible to do so in avalonia
streamGeometry = new StreamGeometry();
// in wpf , this method allows display content to be cleared
// streamGeometry.Clear(); // this is wpf method
// in avalonia, why does this method need value of "AvaloniaProperty" data type ?
// but I don't know use what "AvaloniaProperty" to clear the displayed content
// if I don't clear the cache or re-instantiate it
// the canvas will display repeated lines , because exits cache inside streamGeometry
// 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
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
}
#endregion
}
}

View File

@@ -0,0 +1,56 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
xmlns:cv="using:Serein.Workbench.Avalonia.Custom.Views"
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
xmlns:dtp="using:Serein.Workbench.Avalonia.DataTemplates"
>
<!--预览-->
<Design.PreviewWith>
<StackPanel Width="400" Spacing="10">
<StackPanel Background="{DynamicResource SystemRegionBrush}">
<cv:FlowLibraryInfoView />
</StackPanel>
</StackPanel>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type cv:FlowLibraryInfoView}" TargetType="cv:FlowLibraryInfoView">
<Setter Property="Template">
<ControlTemplate>
<StackPanel>
<!--类库名称-->
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" Margin="2">
<TextBlock Text="library : " FontSize="18"></TextBlock>
<TextBlock Text="{TemplateBinding LibraryName}" FontSize="18"></TextBlock>
</StackPanel>
<!--Action Method Info-->
<!--动作节点方法信息-->
<ListBox x:Name="PART_ActionMethodInfos" ItemsSource="{TemplateBinding ActionMethods}" Background="#F1FBFB">
<ItemsControl.ItemTemplate>
<!--use custom DataTemplate create items -->
<!--使用自定义模板创建子项控件-->
<dtp:LibraryMethodInfoDataTemplate>
</dtp:LibraryMethodInfoDataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<!--Flipflop Method Info-->
<!--触发器节点方法信息-->
<ListBox x:Name="PART_FlipflopMethodInfos" ItemsSource="{TemplateBinding FlipflopMethods}" Background="#FBF8F1">
<ItemsControl.ItemTemplate>
<dtp:LibraryMethodInfoDataTemplate>
</dtp:LibraryMethodInfoDataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -0,0 +1,236 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Serein.Library;
using Serein.Workbench.Avalonia.Services;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibraryInfoView : TemplatedControl
{
private IWorkbenchEventService workbenchEventService;
public FlowLibraryInfoView()
{
workbenchEventService = App.GetService<IWorkbenchEventService>();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
// 如果改变的属性是 MdsProperty ,则加载方法信息
if (change.Property == MdsProperty)
{
if(change.NewValue is MethodDetailsInfo[] value)
{
onNext(value);
}
}
}
/// <summary>
/// 获取到控件信息
/// </summary>
/// <param name="e"></param>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
#region
if (e.NameScope.Find("PART_ActionMethodInfos") is ListBox p_am)
{
p_am.AddHandler(InputElement.PointerExitedEvent, ListBox_PointerExited);
p_am.AddHandler(SelectingItemsControl.SelectionChangedEvent, ListBox_SelectionChanged);
}
#endregion
#region
if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm)
{
p_fm.SelectionChanged += ListBox_SelectionChanged;
p_fm.PointerExited += ListBox_PointerExited;
}
#endregion
}
private void ListBox_SelectionChanged(object? o, SelectionChangedEventArgs e)
{
if (o is ListBox listBox && listBox.SelectedIndex > 0 && listBox.SelectedItem is MethodDetailsInfo mdInfo)
{
workbenchEventService.PreviewLibraryMethodInfo(mdInfo); // 通知其它地方预览了某个方法信息
}
}
private void ListBox_PointerExited(object? o, PointerEventArgs e)
{
if (o is ListBox listBox && listBox.SelectedIndex > -1)
{
listBox.SelectedIndex = -1; // 如果鼠标离开了,取消已选状态
}
}
/// <summary>
/// 将信息加载出来
/// </summary>
/// <param name="value"></param>
private void onNext(MethodDetailsInfo[] value)
{
if(value is null)
{
return;
}
var fmd = value.Where(item => nameof(NodeType.Flipflop).Equals(item.NodeType));
FlipflopMethods = new ObservableCollection<MethodDetailsInfo>(fmd);
var amd = value.Where(item => nameof(NodeType.Action).Equals(item.NodeType));
ActionMethods = new ObservableCollection<MethodDetailsInfo>(amd);
}
#region Template Public Property /
public static readonly DirectProperty<FlowLibraryInfoView, string> LibraryNameProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, string>(nameof(LibraryName), o => o.LibraryName, (o, v) => o.LibraryName = v);
public static readonly DirectProperty<FlowLibraryInfoView, MethodDetailsInfo[]> MdsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, MethodDetailsInfo[]>(nameof(Mds), o => o.Mds, (o, v) => o.Mds = v);
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> ActionMethodsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(ActionMethods), o => o.ActionMethods, (o, v) => o.ActionMethods = v);
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> FlipflopMethodsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(FlipflopMethods), o => o.FlipflopMethods, (o, v) => o.FlipflopMethods = v);
private string libraryName = string.Empty;
private ObservableCollection<MethodDetailsInfo> actionMethods;
private ObservableCollection<MethodDetailsInfo> flipflopMethods;
private MethodDetailsInfo[] mds = [];
public string LibraryName
{
get { return libraryName; }
set { SetAndRaise(LibraryNameProperty, ref libraryName, value); }
}
/*
public static readonly AttachedProperty<string> LibraryName2Property = AvaloniaProperty.RegisterAttached<FlowLibraryInfoView, Control, string>("LibraryName2");
public static string GetLibraryName2(Control element)
{
return element.GetValue(LibraryName2Property);
}
public static void SetLibraryName2(Control element, string value)
{
element.SetValue(LibraryName2Property, value);
}
*/
/// <summary>
/// Method Info
/// 方法信息
/// </summary>
public MethodDetailsInfo[] Mds
{
get { return mds; }
set
{
SetAndRaise(MdsProperty, ref mds, value);
}
}
/// <summary>
/// 动作节点的方法
/// </summary>
public ObservableCollection<MethodDetailsInfo> ActionMethods
{
get { return actionMethods; }
set
{
SetAndRaise(ActionMethodsProperty, ref actionMethods, value);
}
}
/// <summary>
/// 触发器节点的方法
/// </summary>
public ObservableCollection<MethodDetailsInfo> FlipflopMethods
{
get { return flipflopMethods; }
set
{
SetAndRaise(FlipflopMethodsProperty, ref flipflopMethods, value);
}
}
#endregion
}
public class ItemsChangeObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//launch an event Reset with name of property changed
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

View File

@@ -0,0 +1,40 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibraryMethodInfoView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:FlowLibraryMethodInfoViewModel">
<Design.DataContext>
<vm:FlowLibraryMethodInfoViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="StackPanel">
<Setter Property="Margin" Value="2,1,2,1" />
</Style>
</UserControl.Styles>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="节点类型 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.NodeType}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="方法描述 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.MethodAnotherName}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="方法名称 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.MethodName}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text=" 返 回 值 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.ReturnTypeFullName}"/>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,50 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using Serein.Workbench.Avalonia.Custom.Views;
using Serein.Workbench.Avalonia.Services;
using System.Diagnostics;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibraryMethodInfoView : UserControl
{
private FlowLibraryMethodInfoViewModel _vm;
public FlowLibraryMethodInfoView()
{
InitializeComponent();
_vm = App.GetService<FlowLibraryMethodInfoViewModel>();
DataContext = _vm;
//this.PointerPressed += FlowLibraryMethodInfoView_PointerPressed;
}
//private async void FlowLibraryMethodInfoView_PointerPressed(object? sender, PointerPressedEventArgs e)
//{
// if (_vm.MethodDetailsInfo is null)
// {
// return;
// }
// DataObject dragData = new DataObject();
// dragData.Set(DataFormats.Text, $"{_vm.MethodDetailsInfo.MethodAnotherName}");
// var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
// Debug.WriteLine("DoDrag :" + result);
// switch (result)
// {
// case DragDropEffects.Copy:
// Debug.WriteLine("文本来自 Copy");
// break;
// case DragDropEffects.Link:
// Debug.WriteLine("文本来自 Link");
// break;
// case DragDropEffects.None:
// Debug.WriteLine("拖拽操作被取消");
// break;
// }
//}
}

View File

@@ -0,0 +1,39 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibrarysView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:FlowLibrarysViewModel"
>
<Design.DataContext>
<vm:FlowLibrarysViewModel />
</Design.DataContext>
<UserControl.Resources>
<cv:FlowLibraryInfoView x:Key="FlowLibraryInfoView">
</cv:FlowLibraryInfoView>
</UserControl.Resources>
<!-- , DataType={x:Type vm:FlowLibrarysViewModel} -->
<!-- x:DataType="baselibrary:LibraryMds" -->
<!-- LibraryInfo="{Binding}"-->
<ScrollViewer HorizontalAlignment="Left" VerticalAlignment="Top">
<!--Displays dependecy information loaded from runtime environment-->
<!--显示从运行环境加载的所有依赖信息-->
<ItemsControl ItemsSource="{Binding LibraryList}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="baselibrary:LibraryMds" >
<cv:FlowLibraryInfoView LibraryName="{Binding AssemblyName}" Mds="{Binding Mds}"/>
<!--<StackPanel Background="{DynamicResource SystemRegionBrush}" Margin="2,2,2,8">
</StackPanel>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using System;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibrarysView : UserControl
{
public FlowLibrarysView()
{
InitializeComponent();
DataContext = App.GetService<FlowLibrarysViewModel>();
}
}

View File

@@ -0,0 +1,43 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.MainMenuBarView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
x:DataType="vm:MainMenuBarViewModel">
<UserControl.Styles>
<Style Selector="MenuItem">
<Setter Property="FontSize" Value="20" />
</Style>
</UserControl.Styles>
<Design.DataContext>
<vm:MainMenuBarViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Center" >
<StackPanel.Resources>
<SolidColorBrush x:Key="MenuFlyoutBackground">#FFFFFF</SolidColorBrush>
</StackPanel.Resources>
<Menu Background="Transparent">
<MenuItem Header="项目">
<MenuItem Header="保存项目" Command="{Binding SaveProjectCommand}"/>
<MenuItem Header="打开本地项目"/>
</MenuItem>
<MenuItem Header="调试">
<MenuItem Header="运行(从起始节点)"/>
<MenuItem Header="运行(从当前节点)"/>
<MenuItem Header="结束流程"/>
</MenuItem>
<MenuItem Header="视图">
<MenuItem Header="输出窗口"/>
<MenuItem Header="重置画布"/>
<MenuItem Header="定位节点"/>
</MenuItem>
<MenuItem Header="远程">
<MenuItem Header="启动远程服务" />
<MenuItem Header="连接远程环境" />
</MenuItem>
</Menu>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,16 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using System;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class MainMenuBarView : UserControl
{
public MainMenuBarView()
{
InitializeComponent();
DataContext = App.GetService<MainMenuBarViewModel>();
}
}

View File

@@ -0,0 +1,142 @@
using static System.Math;
namespace Avalonia.Controls.PanAndZoom;
/// <summary>
/// Avalonia Matrix helper methods.
/// </summary>
public static class MatrixHelper
{
/// <summary>
/// Creates a translation matrix using the specified offsets.
/// </summary>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation matrix.</returns>
public static Matrix Translate(double offsetX, double offsetY)
{
return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY);
}
/// <summary>
/// Prepends a translation around the center of provided matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend translation.</param>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation matrix.</returns>
public static Matrix TranslatePrepend(Matrix matrix, double offsetX, double offsetY)
{
return Translate(offsetX, offsetY) * matrix;
}
/// <summary>
/// Creates a matrix that scales along the x-axis and y-axis.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix Scale(double scaleX, double scaleY)
{
return new Matrix(scaleX, 0, 0, scaleY, 0.0, 0.0);
}
/// <summary>
/// Creates a matrix that is scaling from a specified center.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="centerX">The center X-coordinate of the scaling.</param>
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix ScaleAt(double scaleX, double scaleY, double centerX, double centerY)
{
return new Matrix(scaleX, 0, 0, scaleY, centerX - (scaleX * centerX), centerY - (scaleY * centerY));
}
/// <summary>
/// Prepends a scale around the center of provided matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend scale.</param>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="centerX">The center X-coordinate of the scaling.</param>
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix ScaleAtPrepend(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY)
{
return ScaleAt(scaleX, scaleY, centerX, centerY) * matrix;
}
/// <summary>
/// Creates a translation and scale matrix using the specified offsets and scales along the x-axis and y-axis.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation and scale matrix.</returns>
public static Matrix ScaleAndTranslate(double scaleX, double scaleY, double offsetX, double offsetY)
{
return new Matrix(scaleX, 0.0, 0.0, scaleY, offsetX, offsetY);
}
/// <summary>
/// Creates a skew matrix.
/// </summary>
/// <param name="angleX">Angle of skew along the X-axis in radians.</param>
/// <param name="angleY">Angle of skew along the Y-axis in radians.</param>
/// <returns>When the method completes, contains the created skew matrix.</returns>
public static Matrix Skew(float angleX, float angleY)
{
return new Matrix(1.0, Tan(angleX), Tan(angleY), 1.0, 0.0, 0.0);
}
/// <summary>
/// Creates a matrix that rotates.
/// </summary>
/// <param name="radians">Angle of rotation in radians. Angles are measured clockwise when looking along the rotation axis.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double radians)
{
double cos = Cos(radians);
double sin = Sin(radians);
return new Matrix(cos, sin, -sin, cos, 0, 0);
}
/// <summary>
/// Creates a matrix that rotates about a specified center.
/// </summary>
/// <param name="angle">Angle of rotation in radians.</param>
/// <param name="centerX">The center X-coordinate of the rotation.</param>
/// <param name="centerY">The center Y-coordinate of the rotation.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double angle, double centerX, double centerY)
{
return Translate(-centerX, -centerY) * Rotation(angle) * Translate(centerX, centerY);
}
/// <summary>
/// Creates a matrix that rotates about a specified center.
/// </summary>
/// <param name="angle">Angle of rotation in radians.</param>
/// <param name="center">The center of the rotation.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double angle, Vector center)
{
return Translate(-center.X, -center.Y) * Rotation(angle) * Translate(center.X, center.Y);
}
/// <summary>
/// Transforms a point by this matrix.
/// </summary>
/// <param name="matrix">The matrix to use as a transformation matrix.</param>
/// <param name="point">>The original point to apply the transformation.</param>
/// <returns>The result of the transformation for the input point.</returns>
public static Point TransformPoint(Matrix matrix, Point point)
{
return new Point(
(point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.M31,
(point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.M32);
}
}

View File

@@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.NodeContainerView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:NodeContainerViewModel"
Background="#F8FBF1"
DragDrop.AllowDrop="True">
<Design.DataContext>
<vm:NodeContainerViewModel />
</Design.DataContext>
<DockPanel>
<!--放置节点-->
<Canvas x:Name="PART_NodeContainer">
</Canvas>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,427 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Custom.ViewModels;
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 Point = Avalonia.Point;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class NodeContainerView : UserControl
{
private readonly NodeContainerViewModel _vm;
private readonly INodeOperationService nodeOperationService;
private readonly IKeyEventService keyEventService;
#region
/// <summary>
/// 是否正在预览节点控件
/// </summary>
private bool IsPreviewNodeControl;
/// <summary>
/// 标记是否正在尝试选取控件
/// </summary>
private bool IsSelectControl;
/// <summary>
/// 标记是否正在拖动控件
/// </summary>
private bool IsControlDragging;
/// <summary>
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging;
/// <summary>
/// 标记是否正在选取节点
/// </summary>
private bool IsSelectDragging;
/// <summary>
/// 当前选取的控件
/// </summary>
private readonly List<Control> selectNodeControls = [];
/// <summary>
/// 记录开始拖动节点控件时的鼠标位置
/// </summary>
private Point startControlDragPoint;
/// <summary>
/// 记录移动画布开始时的鼠标位置
/// </summary>
private Point startCanvasDragPoint;
/// <summary>
/// 记录开始选取节点控件时的鼠标位置
/// </summary>
private Point startSelectControolPoint;
/// <summary>
/// 组合变换容器
/// </summary>
private readonly TransformGroup canvasTransformGroup;
/// <summary>
/// 缩放画布
/// </summary>
private readonly ScaleTransform scaleTransform;
/// <summary>
/// 平移画布
/// </summary>
private readonly TranslateTransform translateTransform;
#endregion
public NodeContainerView()
{
InitializeComponent();
_vm= App.GetService<NodeContainerViewModel>();
DataContext = _vm;
#region UI相关的服务
keyEventService = App.GetService<IKeyEventService>();
nodeOperationService = App.GetService<INodeOperationService>();
nodeOperationService.MainCanvas = PART_NodeContainer;
nodeOperationService.OnNodeViewCreate += NodeOperationService_OnNodeViewCreate; // 处理事件
keyEventService.KeyUp += (k) =>
{
if (k == Key.Escape)
{
IsCanvasDragging = false;
IsControlDragging = false;
}
};
#endregion
#region UI事件
AddHandler(DragDrop.DropEvent, Drop); // 创建节点相关
PointerPressed += NodeContainerView_PointerPressed;
PointerReleased += NodeContainerView_PointerReleased;
PointerMoved += NodeContainerView_PointerMoved;
PointerWheelChanged += NodeContainerView_PointerWheelChanged;
#endregion
#region
canvasTransformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
translateTransform = new TranslateTransform();
canvasTransformGroup.Children.Add(scaleTransform);
canvasTransformGroup.Children.Add(translateTransform);
PART_NodeContainer.RenderTransform = canvasTransformGroup;
#endregion
}
#region
public Point GetPositionOfCanvas(PointerEventArgs e)
{
return e.GetPosition(PART_NodeContainer);
}
public Point GetPositionOfCanvas(DragEventArgs e)
{
return e.GetPosition(PART_NodeContainer);
}
#endregion
#region
#region
private void Drop(object? sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
{
var json = e.Data.GetText();
if (string.IsNullOrEmpty(json))
{
return;
}
var mdInfo = json.ToJsonObject<MethodDetailsInfo>();
if (mdInfo is not null)
{
var canvasDropPosition = GetPositionOfCanvas(e); // 更新画布落点
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
nodeOperationService.CreateNodeView(mdInfo, position); // 提交创建节点的请求
}
}
else // if (e.Data.Contains(DataFormats.FileNames))
{
var files = e.Data.GetFiles();
var str = files?.Select(f => f.Path);
if (str is not null)
{
}
}
}
#endregion
#region
private void NodeContainerView_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (IsPreviewNodeControl)
{
IsCanvasDragging = false;
e.Handled = true;
return;
}
if (!IsCanvasDragging)
{
IsCanvasDragging = true;
startCanvasDragPoint = e.GetPosition(this);
e.Handled = true;
}
}
private void NodeContainerView_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
IsCanvasDragging = false; // 不再拖动
}
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)
{
_vm.IsConnectionInvokeNode = true; // 正在连接节点的调用关系
}
else
{
_vm.IsConnectionArgSourceNode = true; // 正在连接节点的调用关系
}
var currentPoint = e.GetPosition(PART_NodeContainer);
myData.UpdatePoint(new Point(currentPoint.X - 5, currentPoint.Y - 5));
return;
}
}
if (IsCanvasDragging)
{
// 拖动画布
Point currentMousePosition = e.GetPosition(this);
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
startCanvasDragPoint = currentMousePosition;
}
}
// 缩放
private void NodeContainerView_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
var delta = e.Delta.Y;
if (delta < 0 && scaleTransform.ScaleX < 0.02) return;
if (delta > 0 && scaleTransform.ScaleY > 4.0) return;
// 缩放因子,根据滚轮方向调整
double zoomFactor = delta > 0 ? 1.23 : 0.78;
// 当前缩放比例
double oldScale = scaleTransform.ScaleX;
double newScale = oldScale * zoomFactor;
// 记录缩放前的鼠标位置
var mousePosition = GetPositionOfCanvas(e);
// 更新缩放比例
scaleTransform.ScaleX = newScale;
scaleTransform.ScaleY = newScale;
// 记录缩放后的鼠标位置
var newMousePosition = GetPositionOfCanvas(e);
// 更新 TranslateTransform确保以鼠标位置为中心进行缩放
var s_position = newMousePosition - mousePosition; // 计算偏移量
translateTransform.X += s_position.X * newScale; // 根据缩放比例进行偏移
translateTransform.Y += s_position.Y * newScale; // 根据缩放比例进行偏移
}
#endregion
#endregion
#region
/// <summary>
/// 拖拽创建控件
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
private bool NodeOperationService_OnNodeViewCreate(NodeViewCreateEventArgs eventArgs)
{
if (eventArgs.NodeControl is not Control control)
{
return false;
}
var position = eventArgs.Position;// 坐标
SetNodeEvent(control); // 设置该控件与画布交互的相关事件
DragControl(control, position.X, position.Y);
PART_NodeContainer.Children.Add(control);
return true;
}
/// <summary>
/// 设置节点与画布容器相关的操作事件
/// </summary>
/// <param name="nodeControl"></param>
private void SetNodeEvent(Control nodeControl)
{
nodeControl.PointerMoved += NodeControl_PointerMoved; ;
nodeControl.PointerExited += NodeControl_PointerExited;
nodeControl.PointerPressed += Block_MouseLeftButtonDown;
nodeControl.PointerMoved += Block_MouseMove;
nodeControl.PointerReleased += (s, e) => IsControlDragging = false;
}
#endregion
#region
/// <summary>
/// 移动控件
/// </summary>
/// <param name="nodeControl"></param>
/// <param name="x"></param>
/// <param name="y"></param>
private void DragControl(Control nodeControl, double x, double y)
{
Canvas.SetLeft(nodeControl, x);
Canvas.SetTop(nodeControl, y);
}
/// <summary>
/// 控件的鼠标左键按下事件,启动拖动操作。
/// </summary>
private void Block_MouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
{
var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsRightButtonPressed);
if (!isPass)
{
return;
}
if (sender is Control nodeControl)
{
IsControlDragging = true;
startControlDragPoint = GetPositionOfCanvas(e); // 记录鼠标按下时的位置
e.Handled = true; // 防止事件传播影响其他控件
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
private void Block_MouseMove(object? sender, PointerEventArgs e)
{
if (sender is not Control nodeControl)
{
return;
}
if (IsCanvasDragging)
return;
if (IsSelectControl)
return;
if (IsControlDragging) // 如果正在拖动控件
{
Point currentPosition = GetPositionOfCanvas(e); // 获取当前鼠标位置
// 单个移动
if (selectNodeControls.Count == 0 || !selectNodeControls.Contains(nodeControl))
{
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距
double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距
DragControl(nodeControl, newLeft, newTop);
}
// 批量移动
else
{
// 进行批量移动
// 获取旧位置
var oldLeft = Canvas.GetLeft(nodeControl);
var oldTop = Canvas.GetTop(nodeControl);
// 计算被选择控件的偏移量
var deltaX = /*(int)*/(currentPosition.X - startControlDragPoint.X);
var deltaY = /*(int)*/(currentPosition.Y - startControlDragPoint.Y);
// 移动被选择的控件
var newLeft = oldLeft + deltaX;
var newTop = oldTop + deltaY;
//this.EnvDecorator.MoveNode(nodeControlMain.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点
DragControl(nodeControl, newLeft, newTop);
// 计算控件实际移动的距离
var actualDeltaX = newLeft - oldLeft;
var actualDeltaY = newTop - oldTop;
// 移动其它选中的控件
foreach (var selectItemNode in selectNodeControls)
{
if (selectItemNode != nodeControl) // 跳过已经移动的控件
{
var otherNewLeft = Canvas.GetLeft(selectItemNode) + actualDeltaX;
var otherNewTop = Canvas.GetTop(selectItemNode) + actualDeltaY;
DragControl(selectItemNode, otherNewLeft, otherNewTop);
//this.EnvDecorator.MoveNode(nodeControl.ViewModel.NodeModel.Guid, otherNewLeft, otherNewTop); // 移动节点
}
}
// 更新节点之间线的连接位置
//foreach (var nodeControl in selectNodeControls)
//{
// //nodeControl.UpdateLocationConnections();
//}
}
startControlDragPoint = currentPosition; // 更新起始点位置
}
}
private void NodeControl_PointerExited(object? sender, PointerEventArgs e)
{
IsPreviewNodeControl = false;
}
private void NodeControl_PointerMoved(object? sender, PointerEventArgs e)
{
IsPreviewNodeControl = true;
}
#endregion
}

View File

@@ -0,0 +1,27 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Serein.Workbench.Avalonia.Custom.Views">
<!--
Additional resources
Using Control Themes:
https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes
Using Theme Variants:
https://docs.avaloniaui.net/docs/guides/styles-and-resources/how-to-use-theme-variants
-->
<Design.PreviewWith>
<StackPanel Width="400" Spacing="10">
<StackPanel Background="{DynamicResource SystemRegionBrush}">
<controls:NodeJunctionView />
</StackPanel>
</StackPanel>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type controls:NodeJunctionView}" TargetType="controls:NodeJunctionView">
<Setter Property="Template">
<ControlTemplate>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -0,0 +1,215 @@
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 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;
/// <summary>
/// 连接控制点
/// </summary>
public class NodeJunctionView : TemplatedControl
{
private readonly INodeOperationService nodeOperationService;
/// <summary>
/// Render方法中控制自绘内容
/// </summary>
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
/// <summary>
/// 正在查看
/// </summary>
private bool IsPreviewing;
public NodeJunctionView()
{
nodeOperationService = App.GetService<INodeOperationService>();
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);
}
/// <summary>
/// 获取到控件信息
/// </summary>
/// <param name="e"></param>
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<NodeJunctionView, JunctionType> JunctionTypeProperty =
AvaloniaProperty.RegisterDirect<NodeJunctionView, JunctionType>(nameof(JunctionType), o => o.JunctionType, (o, v) => o.JunctionType = v);
private JunctionType junctionType;
public JunctionType JunctionType
{
get { return junctionType; }
set { SetAndRaise(JunctionTypeProperty, ref junctionType, value); }
}
public static readonly DirectProperty<NodeJunctionView, NodeModelBase?> MyNodeProperty =
AvaloniaProperty.RegisterDirect<NodeJunctionView, NodeModelBase?>(nameof(MyNode), o => o.MyNode, (o, v) => o.MyNode = v);
private NodeModelBase? myNode;
public NodeModelBase? MyNode
{
get { return myNode; }
set { SetAndRaise(MyNodeProperty, ref myNode, value); }
}
#region UI视觉
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
public override void Render(DrawingContext drawingContext)
{
double width = 44;
double height = 26;
var background = GetBackgrounp();
var pen = new Pen(Brushes.Black, 1);
// 输入连接器的背景
var connectorRect = new Rect(0, 0, width, height);
drawingContext.DrawRectangle(Brushes.Transparent, new Pen(), connectorRect);
double circleCenterX = width / 2 ; // 中心 X 坐标
double circleCenterY = height / 2 ; // 中心 Y 坐标
//_myCenterPoint = new Point(circleCenterX - Width / 2, circleCenterY); // 中心坐标
// 定义圆形的大小和位置
var diameterCircle = width - 20;
Rect rect = new(4, 2, diameterCircle / 2, diameterCircle / 2);
var ellipse = new EllipseGeometry(rect);
drawingContext.DrawGeometry(background, pen, ellipse);
// 定义三角形的间距
double triangleCenterX = width / 2 - 2; // 三角形中心 X 坐标
double triangleCenterY = height / 2 -5; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
int t = 6;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - t), true);
context.LineTo(new Point(triangleCenterX + 8, triangleCenterY), true);
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);
}
#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
/// <summary>
/// 获取背景颜色
/// </summary>
/// <returns></returns>
protected IBrush GetBackgrounp()
{
var myData = nodeOperationService.ConnectingData;
if (!myData.IsCreateing)
{
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
return new SolidColorBrush(Color.Parse("#76ABEE"));
}
if (myData.IsCanConnected)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
{
return myData.ConnectionInvokeType.ToLineColor();
}
else
{
return myData.ConnectionArgSourceType.ToLineColor();
}
}
else
{
return Brushes.Red;
}
if (IsPreviewing)
{
//return new SolidColorBrush(Color.Parse("#04FC10"));
}
else
{
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
return new SolidColorBrush(Color.Parse("#76ABEE"));
}
}
#endregion
}

View File

@@ -0,0 +1,63 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="390" d:DesignHeight="40"
x:Class="Serein.Workbench.Avalonia.Custom.Views.ParameterDetailsInfoView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
x:DataType="vm:ParameterDetailsViewModel"
VerticalAlignment="Center">
<Design.DataContext>
<vm:ParameterDetailsViewModel/>
</Design.DataContext>
<StackPanel >
<StackPanel.Resources>
<converter:IsVisibleOfParameterConverter x:Key="visibleConverter"/>
</StackPanel.Resources>
<Grid ColumnDefinitions="20,40,90,auto" Margin="6,0,10,0">
<!--<ToolTip.Tip>
<StackPanel>
</StackPanel>
</ToolTip.Tip>-->
<!--<ToolTip Background="LightYellow" Foreground="#071042" Content="" />-->
<cv:NodeJunctionView Grid.Column="0" JunctionType="ArgData" MyNode="{Binding ParameterDetails.NodeModel}" Width="30" Height="15" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
<CheckBox Grid.Column="1" IsChecked="{Binding ParameterDetails.IsExplicitData, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" >
</CheckBox>
<!--<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Index, StringFormat='arg{0} '}" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />-->
<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Name}" FontSize="14"
HorizontalAlignment="Left" VerticalAlignment="Center"
ToolTip.Placement="Bottom" ToolTip.VerticalOffset="6">
<ToolTip.Tip>
<StackPanel>
<TextBlock Text="{Binding ParameterDetails}" FontSize="14" TextTrimming="None" TextWrapping="WrapWithOverflow"/>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
<TextBlock Grid.Column="3" IsVisible="{Binding IsVisibleA}" FontSize="14" Text=" [ 自动取参 ]" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox Grid.Column="3" IsVisible="{Binding IsVisibleB}" FontSize="14" Text="{Binding ParameterDetails.DataValue, Mode=TwoWay}" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
<ComboBox Grid.Column="3" IsVisible="{Binding IsVisibleC}"
ItemsSource="{Binding ParameterDetails.Items}"
SelectedValue="{Binding ParameterDetails.DataValue,Mode=OneTime}"
MinWidth="100" MaxWidth="300">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="14"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,31 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
namespace Serein.Workbench.Avalonia.Custom.Views;
internal partial class ParameterDetailsInfoView : UserControl
{
private readonly ParameterDetailsViewModel _vm;
public ParameterDetailsInfoView()
{
InitializeComponent();
var pd = new ParameterDetails();
pd.Name = "param name";
pd.IsParams = true;
pd.DataValue = "data value";
pd.Items = ["A","B","C"];
_vm = new (pd);
DataContext = _vm;
}
public ParameterDetailsInfoView(ParameterDetailsViewModel parameterDetailsViewModel)
{
InitializeComponent();
_vm = parameterDetailsViewModel;
DataContext = _vm;
}
}

View File

@@ -0,0 +1,87 @@
using Avalonia.Controls.Templates;
using Avalonia.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serein.Library;
using System.Diagnostics;
using MsBox.Avalonia.Enums;
using MsBox.Avalonia;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia;
using Serein.Library.Utils;
namespace Serein.Workbench.Avalonia.DataTemplates
{
/// <summary>
/// 方法信息模板
/// </summary>
internal class LibraryMethodInfoDataTemplate : IDataTemplate
{
public Control Build(object param)
{
if (param is MethodDetailsInfo mdInfo)
{
var textBlock = new TextBlock() { Text = mdInfo.MethodAnotherName };
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;
return textBlock;
}
else
{
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;
}
}
public bool Match(object data)
{
return data is MethodDetailsInfo;
}
private static void TextBlock_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is not TextBlock textBlock || textBlock.Tag is not MethodDetailsInfo mdInfo)
{
return;
}
var dragData = new DataObject(); // 设置需要传递的数据
dragData.Set(DataFormats.Text, mdInfo.ToJsonText());
_ = DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
//var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
//Debug.WriteLine("DoDrag :" + result);
//switch (result)
//{
// case DragDropEffects.Copy:
// Debug.WriteLine("文本来自 Copy");
// break;
// case DragDropEffects.Link:
// Debug.WriteLine("文本来自 Link");
// break;
// case DragDropEffects.None:
// Debug.WriteLine("拖拽操作被取消");
// break;
//}
}
}
}

View File

@@ -0,0 +1,39 @@
using Avalonia.Controls;
using Avalonia;
using Avalonia.Controls.Templates;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.Views;
using Serein.Workbench.Avalonia.Custom.ViewModels;
namespace Serein.Workbench.Avalonia.DataTemplates
{
internal class NodeMethodParameterInfoDataTemplate : IDataTemplate
{
public Control Build(object param)
{
if (param is ParameterDetails mdInfo)
{
var viewModel = new ParameterDetailsViewModel(mdInfo);
var view = new ParameterDetailsInfoView(viewModel);
return view;
}
else
{
var textBlock = new TextBlock() { Text = $"Binding 类型不为预期的[MethodDetailsInfo],而是[{param?.GetType()}]" };
textBlock.Margin = new Thickness(1d, -4d, 1d, -4d);
return textBlock;
}
}
public bool Match(object data)
{
return data is ParameterDetails;
}
}
}

View File

@@ -0,0 +1,55 @@
using Avalonia.Media;
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Extension
{
/// <summary>
/// 线条颜色
/// </summary>
public static class LineExtension
{
/// <summary>
/// 根据连接类型指定颜色
/// </summary>
/// <param name="currentConnectionType"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static SolidColorBrush ToLineColor(this ConnectionInvokeType currentConnectionType)
{
return currentConnectionType switch
{
ConnectionInvokeType.IsSucceed => new SolidColorBrush(Color.Parse("#04FC10")), // 04FC10 & 027E08
ConnectionInvokeType.IsFail => new SolidColorBrush(Color.Parse("#F18905")),
ConnectionInvokeType.IsError => new SolidColorBrush(Color.Parse("#FE1343")),
ConnectionInvokeType.Upstream => new SolidColorBrush(Color.Parse("#4A82E4")),
ConnectionInvokeType.None => new SolidColorBrush(Color.Parse("#56CEF6")),
_ => throw new Exception(),
};
}
/// <summary>
/// 根据连接类型指定颜色
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static SolidColorBrush ToLineColor(this ConnectionArgSourceType connection)
{
return connection switch
{
ConnectionArgSourceType.GetPreviousNodeData => new SolidColorBrush(Color.Parse("#56CEF6")), // 04FC10 & 027E08
ConnectionArgSourceType.GetOtherNodeData => new SolidColorBrush(Color.Parse("#56CEF6")),
ConnectionArgSourceType.GetOtherNodeDataOfInvoke => new SolidColorBrush(Color.Parse("#56CEF6")),
_ => throw new Exception(),
};
}
}
}

View File

@@ -0,0 +1,42 @@
using Avalonia;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Extension
{
public static class PointExtension
{
public static Point Add(this Point a, Point b)
{
return new Point(a.X + b.X, a.Y + b.Y);
}
public static Point Sub(this Point a, Point b)
{
return new Point(a.X - b.X, a.Y - b.Y);
}
public static Vector ToVector(this Point me)
{
return new Vector(me.X, me.Y);
}
}
public static class VectorExtension
{
public static double DotProduct(this Vector a, Vector b)
{
return a.X * b.X + a.Y * b.Y;
}
public static Vector NormalizeTo(this Vector v)
{
var temp = v;
temp.Normalize();
return temp;
}
}
}

View File

@@ -0,0 +1,32 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Extension
{
internal static class PointerExtension
{
public static bool JudgePointer(this
PointerEventArgs eventArgs,
object? sender,
PointerType pointerType,
Func<PointerPointProperties,bool> judgePointerFunc)
{
if(sender is not Visual visual)
{
return false;
}
if (eventArgs.Pointer.Type == pointerType) // 是否是否是指定的设备类型
{
var point = eventArgs.GetCurrentPoint(visual); // 获取到点击点
return judgePointerFunc.Invoke(point.Properties); // 判断是否属于某种类型的点击
}
return false;
}
}
}

View File

@@ -0,0 +1,125 @@
using Avalonia;
using Serein.Library;
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
{
/// <summary>
/// 节点之间连接线的相关控制方法
/// </summary>
public class ConnectingData
{
/// <summary>
/// 是否正在创建连线
/// </summary>
public bool IsCreateing { get; set; }
/// <summary>
/// 起始控制点
/// </summary>
public NodeJunctionView? StartJunction { get; set; }
/// <summary>
/// 当前的控制点
/// </summary>
public NodeJunctionView? CurrentJunction { get; set; }
/// <summary>
/// 开始坐标
/// </summary>
public Point StartPoint { get; set; }
/// <summary>
/// 线条样式
/// </summary>
public MyLine? TempLine { get; set; }
/// <summary>
/// 线条类别(方法调用)
/// </summary>
public ConnectionInvokeType ConnectionInvokeType { get; set; } = ConnectionInvokeType.IsSucceed;
/// <summary>
/// 线条类别(参数传递)
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get; set; } = ConnectionArgSourceType.GetOtherNodeData;
/// <summary>
/// 判断当前连接类型
/// </summary>
public JunctionOfConnectionType? Type => StartJunction?.JunctionType.ToConnectyionType();
/// <summary>
/// 是否允许连接
/// </summary>
public bool IsCanConnected
{
get
{
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;
}
}
}
/// <summary>
/// 更新临时的连接线
/// </summary>
/// <param name="point"></param>
public void UpdatePoint(Point point)
{
if (StartJunction is null
|| CurrentJunction is null
)
{
return;
}
if (StartJunction.JunctionType == Library.JunctionType.Execute
|| StartJunction.JunctionType == Library.JunctionType.ArgData)
{
TempLine?.Line.UpdateStartPoints(point);
}
else
{
TempLine?.Line.UpdateEndPoints(point);
}
}
/// <summary>
/// 重置
/// </summary>
public void Reset()
{
IsCreateing = false;
StartJunction = null;
CurrentJunction = null;
TempLine?.Remove();
ConnectionInvokeType = ConnectionInvokeType.IsSucceed;
ConnectionArgSourceType = ConnectionArgSourceType.GetOtherNodeData;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Model
{
internal class FlowLibraryInfo
{
}
}

View File

@@ -0,0 +1,40 @@
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
{
/// <summary>
/// 绘制的线
/// </summary>
public class MyLine
{
/// <summary>
/// 将线条绘制出来
/// </summary>
/// <param name="canvas">放置画布</param>
/// <param name="line">线的实体</param>
public MyLine(Canvas canvas, ConnectionLineShape line)
{
Canvas = canvas;
Line = line;
canvas?.Children.Add(line);
}
public Canvas Canvas { get; }
public ConnectionLineShape Line { get; }
/// <summary>
/// 移除线
/// </summary>
public void Remove()
{
Canvas?.Children.Remove(Line);
}
}
}

View File

@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<AvaloniaXaml Remove="Commands\**" />
<Compile Remove="Commands\**" />
<EmbeddedResource Remove="Commands\**" />
<None Remove="Commands\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Custom\ViewModels\FlowLibraryInfoViewModel.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.3" />
<PackageReference Include="Avalonia.Skia" Version="11.2.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.3" />
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\Library\Serein.Library.csproj" />
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\NodeFlow\Serein.NodeFlow.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Custom\Views\FlowLibraryMethodInfoView.axaml.cs">
<DependentUpon>FlowLibraryMethodInfoView1. axaml</DependentUpon>
</Compile>
<Compile Update="Custom\Views\MainMenuBarView.axaml.cs">
<DependentUpon>MainMenuBarView.axaml</DependentUpon>
</Compile>
<Compile Update="Custom\Views\FlowLibraryInfoView.axaml.cs">
<DependentUpon>FlowLibraryInfoView. axaml</DependentUpon>
</Compile>
<Compile Update="Custom\Views\NodeContainerView.axaml.cs">
<DependentUpon>NodeContainerView.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,345 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia;
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.NodeFlow;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serein.Library;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Api;
namespace Serein.Workbench.Avalonia.Services
{
internal class FlowEEForwardingService : IFlowEEForwardingService
{
/// <summary>
/// 流程运行环境
/// </summary>
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
/// <summary>
/// 转发流程运行环境各个事件的实现类
/// </summary>
/// <param name="flowEnvironment"></param>
/// <param name="flowNodeControlService"></param>
public FlowEEForwardingService(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
InitFlowEnvironmentEvent();
}
#region
/// <summary>
/// 加载了依赖文件事件
/// </summary>
public event LoadDllHandler? OnDllLoad;
/// <summary>
/// 项目加载完成事件
/// </summary>
public event ProjectLoadedHandler? OnProjectLoaded;
/// <summary>
/// 项目保存中事件
/// </summary>
public event ProjectSavingHandler? OnProjectSaving;
/// <summary>
/// 节点连接改变事件
/// </summary>
public event NodeConnectChangeHandler? OnNodeConnectChange;
/// <summary>
/// 节点创建事件
/// </summary>
public event NodeCreateHandler? OnNodeCreate;
/// <summary>
/// 节点移除事件
/// </summary>
public event NodeRemoveHandler? OnNodeRemove;
/// <summary>
/// 节点放置容器事件
/// </summary>
public event NodePlaceHandler? OnNodePlace;
/// <summary>
/// 节点取出事件
/// </summary>
public event NodeTakeOutHandler? OnNodeTakeOut;
/// <summary>
/// 流程起始节点改变事件
/// </summary>
public event StartNodeChangeHandler? OnStartNodeChange;
/// <summary>
/// 流程运行完毕事件
/// </summary>
public event FlowRunCompleteHandler? OnFlowRunComplete;
/// <summary>
/// 被监视的对象数据改变事件
/// </summary>
public event MonitorObjectChangeHandler? OnMonitorObjectChange;
/// <summary>
/// 节点中断状态改变事件
/// </summary>
public event NodeInterruptStateChangeHandler? OnNodeInterruptStateChange;
/// <summary>
/// 表达式中断触发事件
/// </summary>
public event ExpInterruptTriggerHandler? OnInterruptTrigger;
/// <summary>
/// 容器对象改变事件
/// </summary>
public event IOCMembersChangedHandler? OnIOCMembersChanged;
/// <summary>
/// 节点定位事件
/// </summary>
public event NodeLocatedHandler? OnNodeLocated;
/// <summary>
/// 节点移动事件
/// </summary>
public event NodeMovedHandler? OnNodeMoved;
/// <summary>
/// 运行环境输出事件
/// </summary>
public event EnvOutHandler? OnEnvOut;
#endregion
#region
private void InitFlowEnvironmentEvent()
{
flowEnvironmentEvent.OnDllLoad += FlowEnvironment_DllLoadEvent;
flowEnvironmentEvent.OnProjectSaving += EnvDecorator_OnProjectSaving;
flowEnvironmentEvent.OnProjectLoaded += FlowEnvironment_OnProjectLoaded;
flowEnvironmentEvent.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent;
flowEnvironmentEvent.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt;
flowEnvironmentEvent.OnNodeCreate += FlowEnvironment_NodeCreateEvent;
flowEnvironmentEvent.OnNodeRemove += FlowEnvironment_NodeRemoveEvent;
flowEnvironmentEvent.OnNodePlace += EnvDecorator_OnNodePlaceEvent;
flowEnvironmentEvent.OnNodeTakeOut += EnvDecorator_OnNodeTakeOutEvent;
flowEnvironmentEvent.OnFlowRunComplete += FlowEnvironment_OnFlowRunCompleteEvent;
flowEnvironmentEvent.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChangeEvent;
flowEnvironmentEvent.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChangeEvent;
flowEnvironmentEvent.OnInterruptTrigger += FlowEnvironment_OnInterruptTriggerEvent;
flowEnvironmentEvent.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChangedEvent;
flowEnvironmentEvent.OnNodeLocated += FlowEnvironment_OnNodeLocateEvent;
flowEnvironmentEvent.OnNodeMoved += FlowEnvironment_OnNodeMovedEvent;
flowEnvironmentEvent.OnEnvOut += FlowEnvironment_OnEnvOutEvent;
}
private void ResetFlowEnvironmentEvent()
{
flowEnvironmentEvent.OnDllLoad -= FlowEnvironment_DllLoadEvent;
flowEnvironmentEvent.OnProjectSaving -= EnvDecorator_OnProjectSaving;
flowEnvironmentEvent.OnProjectLoaded -= FlowEnvironment_OnProjectLoaded;
flowEnvironmentEvent.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent;
flowEnvironmentEvent.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt;
flowEnvironmentEvent.OnNodeCreate -= FlowEnvironment_NodeCreateEvent;
flowEnvironmentEvent.OnNodeRemove -= FlowEnvironment_NodeRemoveEvent;
flowEnvironmentEvent.OnNodePlace -= EnvDecorator_OnNodePlaceEvent;
flowEnvironmentEvent.OnNodeTakeOut -= EnvDecorator_OnNodeTakeOutEvent;
flowEnvironmentEvent.OnFlowRunComplete -= FlowEnvironment_OnFlowRunCompleteEvent;
flowEnvironmentEvent.OnMonitorObjectChange -= FlowEnvironment_OnMonitorObjectChangeEvent;
flowEnvironmentEvent.OnNodeInterruptStateChange -= FlowEnvironment_OnNodeInterruptStateChangeEvent;
flowEnvironmentEvent.OnInterruptTrigger -= FlowEnvironment_OnInterruptTriggerEvent;
flowEnvironmentEvent.OnIOCMembersChanged -= FlowEnvironment_OnIOCMembersChangedEvent;
flowEnvironmentEvent.OnNodeLocated -= FlowEnvironment_OnNodeLocateEvent;
flowEnvironmentEvent.OnNodeMoved -= FlowEnvironment_OnNodeMovedEvent;
flowEnvironmentEvent.OnEnvOut -= FlowEnvironment_OnEnvOutEvent;
}
#region
/// <summary>
/// 环境内容输出
/// </summary>
/// <param name="type"></param>
/// <param name="value"></param>
private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value)
{
//LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
}
/// <summary>
/// 需要保存项目
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void EnvDecorator_OnProjectSaving(ProjectSavingEventArgs eventArgs)
{
OnProjectSaving?.Invoke(eventArgs);
}
/// <summary>
/// 加载完成
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
{
OnProjectLoaded?.Invoke(eventArgs);
}
/// <summary>
/// 运行完成
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnFlowRunCompleteEvent(FlowEventArgs eventArgs)
{
SereinEnv.WriteLine(InfoType.INFO, "-------运行完成---------\r\n");
OnFlowRunComplete?.Invoke(eventArgs);
}
/// <summary>
/// 加载了DLL文件dll内容
/// </summary>
private void FlowEnvironment_DllLoadEvent(LoadDllEventArgs eventArgs)
{
OnDllLoad?.Invoke(eventArgs);
}
/// <summary>
/// 节点连接关系变更
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
{
OnNodeConnectChange?.Invoke(eventArgs);
}
/// <summary>
/// 节点移除事件
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_NodeRemoveEvent(NodeRemoveEventArgs eventArgs)
{
OnNodeRemove?.Invoke(eventArgs);
}
/// <summary>
/// 添加节点事件
/// </summary>
/// <param name="eventArgs">添加节点事件参数</param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs)
{
OnNodeCreate?.Invoke(eventArgs);
}
/// <summary>
/// 放置一个节点
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void EnvDecorator_OnNodePlaceEvent(NodePlaceEventArgs eventArgs)
{
OnNodePlace?.Invoke(eventArgs);
}
/// <summary>
/// 取出一个节点
/// </summary>
/// <param name="eventArgs"></param>
private void EnvDecorator_OnNodeTakeOutEvent(NodeTakeOutEventArgs eventArgs)
{
OnNodeTakeOut?.Invoke(eventArgs);
}
/// <summary>
/// 设置了流程起始控件
/// </summary>
/// <param name="oldNodeGuid"></param>
/// <param name="newNodeGuid"></param>
private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs)
{
OnStartNodeChange?.Invoke(eventArgs);
}
/// <summary>
/// 被监视的对象发生改变
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnMonitorObjectChangeEvent(MonitorObjectEventArgs eventArgs)
{
OnMonitorObjectChange?.Invoke(eventArgs);
}
/// <summary>
/// 节点中断状态改变。
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnNodeInterruptStateChangeEvent(NodeInterruptStateChangeEventArgs eventArgs)
{
OnNodeInterruptStateChange?.Invoke(eventArgs);
}
/// <summary>
/// 节点触发了中断
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnInterruptTriggerEvent(InterruptTriggerEventArgs eventArgs)
{
OnInterruptTrigger?.Invoke(eventArgs);
}
/// <summary>
/// IOC变更
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnIOCMembersChangedEvent(IOCMembersChangedEventArgs eventArgs)
{
OnIOCMembersChanged?.Invoke(eventArgs);
}
/// <summary>
/// 节点需要定位
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnNodeLocateEvent(NodeLocatedEventArgs eventArgs)
{
OnNodeLocated?.Invoke(eventArgs);
}
/// <summary>
/// 节点移动
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnNodeMovedEvent(NodeMovedEventArgs eventArgs)
{
OnNodeMoved?.Invoke(eventArgs);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,80 @@
using Avalonia.Controls;
using Avalonia.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Services
{
delegate void KeyDownEventHandler(Key key);
delegate void KeyUpEventHandler(Key key);
/// <summary>
/// 全局事件服务
/// </summary>
internal interface IKeyEventService
{
event KeyDownEventHandler KeyDown;
event KeyUpEventHandler KeyUp;
/// <summary>
/// 获取某个按键状态
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
bool GetKeyState(Key key);
/// <summary>
/// 设置某个按键的状态
/// </summary>
/// <param name="key"></param>
/// <param name="state"></param>
void SetKeyState(Key key, bool statestate);
}
/// <summary>
/// 管理按键状态
/// </summary>
internal class KeyEventService : IKeyEventService
{
/// <summary>
/// 按键按下
/// </summary>
public event KeyDownEventHandler KeyDown;
/// <summary>
/// 按键松开
/// </summary>
public event KeyUpEventHandler KeyUp;
public KeyEventService()
{
var arr = Enum.GetValues<Key>();
KeysState = new bool[arr.Length];
// 绑定快捷键
//HotKeyManager.SetHotKey(saveMenuItem, new KeyGesture(Key.S, KeyModifiers.Control));
}
private readonly bool[] KeysState;
public bool GetKeyState(Key key)
{
return KeysState[(int)key];
}
public void SetKeyState(Key key, bool state)
{
if (state)
{
KeyDown?.Invoke(key);
}
else
{
KeyUp?.Invoke(key);
}
//Debug.WriteLine($"按键事件:{key} - {state}");
KeysState[(int)key] = state;
}
}
}

View File

@@ -0,0 +1,329 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.VisualTree;
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Custom.Node.ViewModels;
using Serein.Workbench.Avalonia.Custom.Node.Views;
using Serein.Workbench.Avalonia.Custom.Views;
using Serein.Workbench.Avalonia.Extension;
using Serein.Workbench.Avalonia.Model;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Api
{
/// <summary>
/// 提供节点操作的接口
/// </summary>
internal interface INodeOperationService
{
/// <summary>
/// 连接数据
/// </summary>
ConnectingData ConnectingData { get; }
/// <summary>
/// 主画布
/// </summary>
Canvas MainCanvas { get; set; }
/// <summary>
/// 节点创建事件
/// </summary>
event NodeViewCreateHandle OnNodeViewCreate;
/// <summary>
/// 创建节点控件
/// </summary>
/// <param name="nodeType">控件类型</param>
/// <param name="position">创建坐标</param>
/// <param name="methodDetailsInfo">节点方法信息</param>
public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position);
/// <summary>
/// 尝试从连接控制点创建连接
/// </summary>
/// <param name="startJunction"></param>
void TryCreateConnectionOnJunction(NodeJunctionView startJunction);
}
#region
/// <summary>
/// 创建节点控件事件
/// </summary>
/// <param name="eventArgs"></param>
internal delegate bool NodeViewCreateHandle(NodeViewCreateEventArgs eventArgs);
/// <summary>
/// 创建节点控件事件参数
/// </summary>
internal class NodeViewCreateEventArgs : EventArgs
{
internal NodeViewCreateEventArgs(INodeControl nodeControl, PositionOfUI position)
{
this.NodeControl = nodeControl;
this.Position = position;
}
public INodeControl NodeControl { get; private set; }
public PositionOfUI Position { get; private set; }
}
#endregion
}
namespace Serein.Workbench.Avalonia.Services
{
/// <summary>
/// 节点操作相关服务
/// </summary>
internal class NodeOperationService : INodeOperationService
{
public NodeOperationService(IFlowEnvironment flowEnvironment,
IFlowEEForwardingService feefService)
{
this.flowEnvironment = flowEnvironment;
this.feefService = feefService;
NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeView), typeof(ActionNodeViewModel)); // 注册动作节点
ConnectingData = new ConnectingData();
feefService.OnNodeCreate += FeefService_OnNodeCreate; // 订阅运行环境创建节点事件
// 手动加载项目
_ = Task.Run(async delegate
{
await Task.Delay(1000);
var flowEnvironment = App.GetService<IFlowEnvironment>();
var filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
var projectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
var projectDfilePath = System.IO.Path.GetDirectoryName(filePath)!;
flowEnvironment.LoadProject(new FlowEnvInfo { Project = projectData }, projectDfilePath);
}, CancellationToken.None);
}
public ConnectingData ConnectingData { get; private set; }
public Canvas MainCanvas { get; set; }
#region
/// <summary>
/// 存储所有与节点有关的控件
/// </summary>
private Dictionary<string, INodeControl> NodeControls { get; } = [];
/// <summary>
/// 流程运行环境
/// </summary>
private readonly IFlowEnvironment flowEnvironment;
/// <summary>
/// 流程运行环境事件转发
/// </summary>
private readonly IFlowEEForwardingService feefService;
#endregion
/// <summary>
/// 创建了节点控件
/// </summary>
public event NodeViewCreateHandle OnNodeViewCreate;
/// <summary>
/// 创建节点控件
/// </summary>
/// <param name="nodeType">控件类型</param>
/// <param name="position">创建坐标</param>
/// <param name="methodDetailsInfo">节点方法信息基础节点传null</param>
public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position)
{
Task.Run(async () =>
{
if (EnumHelper.TryConvertEnum<NodeControlType>(methodDetailsInfo.NodeType, out var nodeType))
{
await flowEnvironment.CreateNodeAsync(nodeType, position, methodDetailsInfo);
}
});
}
/// <summary>
/// 从工作台事件转发器监听节点创建事件
/// </summary>
/// <param name="eventArgs"></param>
private void FeefService_OnNodeCreate(NodeCreateEventArgs eventArgs)
{
var nodeModel = eventArgs.NodeModel;
if (NodeControls.ContainsKey(nodeModel.Guid))
{
SereinEnv.WriteLine(InfoType.WARN, $"OnNodeCreate 事件意外触发节点Guid重复 - {nodeModel.Guid}");
return;
}
if (!NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM))
{
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。");
return;
}
if (nodeMVVM.ControlType == null
|| nodeMVVM.ViewModelType == null)
{
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点UI类型尚未注册请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。");
return;
}
var isSuccessful = TryCreateNodeView(nodeMVVM.ControlType, // 控件UI类型
nodeMVVM.ViewModelType, // 控件VIewModel类型
nodeModel, // 控件数据实体
out var nodeControl); // 成功创建后传出的节点控件实体
if (!isSuccessful || nodeControl is null)
{
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点创建失败。");
return;
}
var e = new NodeViewCreateEventArgs(nodeControl, eventArgs.Position);
if (OnNodeViewCreate?.Invoke(e) == true)
{
// 成功创建
NodeControls.TryAdd(nodeModel.Guid, nodeControl); // 缓存起来,通知其它地方拿取这个控件
}
}
/// <summary>
/// 创建节点控件
/// </summary>
/// <param name="viewType">节点控件视图控件类型</param>
/// <param name="viewModelType">节点控件ViewModel类型</param>
/// <param name="nodeModel">节点Model实例</param>
/// <param name="nodeView">返回的节点对象</param>
/// <returns>是否创建成功</returns>
/// <exception cref="Exception">无法创建节点控件</exception>
private bool TryCreateNodeView(Type viewType, Type viewModelType, NodeModelBase nodeModel, out INodeControl? nodeView)
{
if (string.IsNullOrEmpty(nodeModel.Guid))
{
nodeModel.Guid = Guid.NewGuid().ToString();
}
var t_ViewModel = Activator.CreateInstance(viewModelType);
if (t_ViewModel is not NodeViewModelBase viewModelBase)
{
nodeView = null;
return false;
}
viewModelBase.NodeModelBase = nodeModel; // 设置节点对象
var controlObj = Activator.CreateInstance(viewType);
if (controlObj is not INodeControl nodeControl)
{
nodeView = null;
return false;
}
else
{
nodeControl.SetNodeModel(nodeModel);
nodeView = nodeControl;
return true;
}
// 在其它地方验证过了,所以注释
//if ((viewType is null)
// || viewModelType is null
// || nodeModel is null)
//{
// nodeView = null;
// return false;
//}
//if (typeof(INodeControl).IsSubclassOf(viewType)
// || typeof(NodeViewModelBase).IsSubclassOf(viewModelType))
//{
// nodeView = null;
// return false;
//}
}
/// <summary>
/// 尝试在连接控制点之间创建连接线
/// </summary>
public void TryCreateConnectionOnJunction(NodeJunctionView startJunction)
{
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)
{
myData.StartPoint = point;
}
else
{
return;
}
myData.Reset();
myData.IsCreateing = true; // 表示开始连接
myData.StartJunction = startJunction;
myData.CurrentJunction = startJunction;
var junctionOfConnectionType = startJunction.JunctionType.ToConnectyionType();
ConnectionLineShape bezierLine; // 类别
Brush brushColor; // 临时线的颜色
if (junctionOfConnectionType == JunctionOfConnectionType.Invoke)
{
brushColor = ConnectionInvokeType.IsSucceed.ToLineColor();
}
else if (junctionOfConnectionType == JunctionOfConnectionType.Arg)
{
brushColor = ConnectionArgSourceType.GetOtherNodeData.ToLineColor();
}
else
{
return;
}
bezierLine = new ConnectionLineShape(myData.StartPoint,
myData.StartPoint,
brushColor,
isTop: true); // 绘制临时的线
//Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
myData.TempLine = new MyLine(MainCanvas, bezierLine);
}
}
}
}

View File

@@ -0,0 +1,90 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Services
{
#region
public delegate void PreviewlMethodInfoHandler(PreviewlMethodInfoEventArgs eventArgs);
#endregion
#region
public class PreviewlMethodInfoEventArgs(MethodDetailsInfo mdInfo) : EventArgs
{
/// <summary>
/// 方法信息
/// </summary>
public MethodDetailsInfo MethodDetailsInfo { get; } = mdInfo;
}
#endregion
/// <summary>
/// 工作台事件管理
/// </summary>
internal interface IWorkbenchEventService
{
/// <summary>
/// 预览了某个方法信息(待创建)
/// </summary>
event PreviewlMethodInfoHandler OnPreviewlMethodInfo;
/// <summary>
/// 预览依赖方法信息
/// </summary>
void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo);
}
/// <summary>
/// 工作台事件的实现类
/// </summary>
internal class WorkbenchEventService : IWorkbenchEventService
{
private readonly IFlowEnvironment flowEnvironment;
/// <summary>
/// 管理工作台的事件
/// </summary>
/// <param name="flowEnvironment"></param>
public WorkbenchEventService(IFlowEnvironment flowEnvironment)
{
this.flowEnvironment = flowEnvironment;
}
private void SubscribeEvents()
{
}
/// <summary>
/// 预览了某个方法信息(待创建)
/// </summary>
public event PreviewlMethodInfoHandler? OnPreviewlMethodInfo;
/// <summary>
/// 预览依赖方法信息
/// </summary>
public void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo)
{
OnPreviewlMethodInfo?.Invoke(new PreviewlMethodInfoEventArgs(mdInfo));
}
/// <summary>
/// 需要放置节点控件
/// </summary>
public void PlateNodeControl()
{
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Serein.Workbench.Avalonia.ViewModels;
public partial class MainViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}

View File

@@ -0,0 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Serein.Workbench.Avalonia.ViewModels;
public class ViewModelBase : ObservableObject
{
}

View File

@@ -0,0 +1,34 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="640"
x:Class="Serein.Workbench.Avalonia.Views.MainView"
x:DataType="vm:MainViewModel">
<Design.DataContext>
<vm:MainViewModel />
</Design.DataContext>
<UserControl.Styles>
</UserControl.Styles>
<Grid RowDefinitions="auto,0,*" ColumnDefinitions="*">
<cv:MainMenuBarView Grid.Row="0" Grid.Column="0" Background="#E2E9EE"/>
<!--这里留空,以后放置功能区-->
<Grid Grid.Row="2" Grid.Column="0"
RowDefinitions="*" ColumnDefinitions="auto,*">
<!--依赖信息-->
<Grid RowDefinitions="*,auto" HorizontalAlignment="Left">
<!--已加载的依赖-->
<cv:FlowLibrarysView Grid.Row="0"/>
<!--<cv:FlowLibraryMethodInfoView Grid.Row="1" HorizontalAlignment="Left"/>-->
<!--当前预览的节点方法信息-->
</Grid>
<!--画布-->
<cv:NodeContainerView Grid.Column="1"/>
<!--其他视图-->
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,23 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Newtonsoft.Json;
using Serein.Library;
using Serein.NodeFlow.Env;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Views;
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,14 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Serein.Workbench.Avalonia.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:Serein.Workbench.Avalonia.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Serein Flow Edit">
<views:MainView />
</Window>

View File

@@ -0,0 +1,36 @@
using Avalonia.Controls;
using Avalonia.Input;
using System.Diagnostics;
using System;
using Avalonia.Markup.Xaml;
using Newtonsoft.Json;
using Serein.NodeFlow.Env;
using System.Threading.Tasks;
using Serein.Library;
using Serein.Workbench.Avalonia.Services;
namespace Serein.Workbench.Avalonia.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IKeyEventService keyEventService = App.GetService<IKeyEventService>();
this.Loaded += MainWindow_Loaded;
this.KeyDown += (o, e) =>
{
keyEventService.SetKeyState(e.Key, true);
};
this.KeyUp += (o, e) =>
{
keyEventService.SetKeyState(e.Key, false);
};
}
private void MainWindow_Loaded(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e)
{
}
}

View File

@@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.BaseNode", "Serein.B
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Script", "Serein.Script\Serein.Script.csproj", "{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Script", "Serein.Script\Serein.Script.csproj", "{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Workbench.Avalonia", "Serein.Workbench.Avalonia\Serein.Workbench.Avalonia.csproj", "{3E11F86C-5914-4998-81DC-6688E5E1A59B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Workbench.Avalonia.Desktop", "Serein.Workbench.Avalonia.Desktop\Serein.Workbench.Avalonia.Desktop.csproj", "{D46C9E9E-9994-45E6-8084-A8A9497B1DC2}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -74,6 +78,14 @@ Global
{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Debug|Any CPU.Build.0 = Debug|Any CPU {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.ActiveCfg = Release|Any CPU {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.Build.0 = Release|Any CPU {D14BC18C-3D69-49FA-BEEA-A9AA570C7469}.Release|Any CPU.Build.0 = Release|Any CPU
{3E11F86C-5914-4998-81DC-6688E5E1A59B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E11F86C-5914-4998-81DC-6688E5E1A59B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E11F86C-5914-4998-81DC-6688E5E1A59B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E11F86C-5914-4998-81DC-6688E5E1A59B}.Release|Any CPU.Build.0 = Release|Any CPU
{D46C9E9E-9994-45E6-8084-A8A9497B1DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D46C9E9E-9994-45E6-8084-A8A9497B1DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D46C9E9E-9994-45E6-8084-A8A9497B1DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D46C9E9E-9994-45E6-8084-A8A9497B1DC2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE