diff --git a/Serein.Workbench.Avalonia.Desktop/Program.cs b/Serein.Workbench.Avalonia.Desktop/Program.cs new file mode 100644 index 0000000..d114cba --- /dev/null +++ b/Serein.Workbench.Avalonia.Desktop/Program.cs @@ -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() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); + +} diff --git a/Serein.Workbench.Avalonia.Desktop/Serein.Workbench.Avalonia.Desktop.csproj b/Serein.Workbench.Avalonia.Desktop/Serein.Workbench.Avalonia.Desktop.csproj new file mode 100644 index 0000000..d38d11d --- /dev/null +++ b/Serein.Workbench.Avalonia.Desktop/Serein.Workbench.Avalonia.Desktop.csproj @@ -0,0 +1,19 @@ + + + WinExe + + net8.0 + enable + true + app.manifest + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia.Desktop/app.manifest b/Serein.Workbench.Avalonia.Desktop/app.manifest new file mode 100644 index 0000000..e0ce8d0 --- /dev/null +++ b/Serein.Workbench.Avalonia.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Api/IFlowEEForwardingService.cs b/Serein.Workbench.Avalonia/Api/IFlowEEForwardingService.cs new file mode 100644 index 0000000..e47db79 --- /dev/null +++ b/Serein.Workbench.Avalonia/Api/IFlowEEForwardingService.cs @@ -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 +{ + /// + /// 流程事件管理,转发流程运行环境中触发的事件到工作台各个订阅者 + /// + internal interface IFlowEEForwardingService : IFlowEnvironmentEvent + { + + } +} diff --git a/Serein.Workbench.Avalonia/Api/INodeControl.cs b/Serein.Workbench.Avalonia/Api/INodeControl.cs new file mode 100644 index 0000000..ed1158f --- /dev/null +++ b/Serein.Workbench.Avalonia/Api/INodeControl.cs @@ -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 + { + /// + /// 对应的节点实体 + /// + NodeModelBase NodeModelBase { get; } + + /// + /// 初始化使用的方法,设置节点实体 + /// + /// + void SetNodeModel(NodeModelBase nodeModel); + } +} diff --git a/Serein.Workbench.Avalonia/Api/INodeJunction.cs b/Serein.Workbench.Avalonia/Api/INodeJunction.cs new file mode 100644 index 0000000..9423def --- /dev/null +++ b/Serein.Workbench.Avalonia/Api/INodeJunction.cs @@ -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 +{ + + + + /// + /// 约束一个节点应该有哪些控制点 + /// + public interface INodeJunction + { + /// + /// 方法执行入口控制点 + /// + NodeJunctionView ExecuteJunction { get; } + /// + /// 执行完成后下一个要执行的方法控制点 + /// + NodeJunctionView NextStepJunction { get; } + + /// + /// 参数节点控制点 + /// + NodeJunctionView[] ArgDataJunction { get; } + /// + /// 返回值控制点 + /// + NodeJunctionView ReturnDataJunction { get; } + + /// + /// 获取目标参数控制点,用于防止wpf释放资源导致找不到目标节点,返回-1,-1的坐标 + /// + /// + /// + NodeJunctionView GetJunctionOfArgData(int index) + { + var arr = ArgDataJunction; + if (index >= arr.Length) + { + return null; + } + return arr[index]; + } + } +} diff --git a/Serein.Workbench.Avalonia/App.axaml b/Serein.Workbench.Avalonia/App.axaml new file mode 100644 index 0000000..266d3be --- /dev/null +++ b/Serein.Workbench.Avalonia/App.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/App.axaml.cs b/Serein.Workbench.Avalonia/App.axaml.cs new file mode 100644 index 0000000..c823dd3 --- /dev/null +++ b/Serein.Workbench.Avalonia/App.axaml.cs @@ -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 +{ + /// + /// 注册ViewModel + /// + /// + public static void AddViewModelServices(this IServiceCollection collection) + { + collection.AddTransient(); // 主窗体 + collection.AddTransient(); // 主窗体菜单 + collection.AddTransient(); // 依赖集合 + collection.AddTransient(); // 预览的方法信息 + //collection.AddTransient(); // 节点参数信息 + collection.AddTransient(); // 节点容器(画布) + + + collection.AddTransient(); // 节点容器(画布) + + + //collection.AddTransient(); // 依赖信息 + } + + public static void AddWorkbenchServices(this IServiceCollection collection) + { + collection.AddSingleton(); // 流程事件管理 + collection.AddSingleton(); // 流程事件管理 + collection.AddSingleton(); // 节点操作管理 + collection.AddSingleton(); // 按键事件管理 + //collection.AddSingleton(); // 流程节点控件管理 + } + + + /// + /// 注册流程接口相关实例 + /// + /// + public static void AddFlowServices(this IServiceCollection collection) + { + + #region 创建实例 + Func 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); // 注册UI线程操作上下文 + collection.AddSingleton(flowEnvironmentDecorator); // 注册运行环境 + collection.AddSingleton(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() where T : class + { + return ServiceProvider?.GetService() ?? 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(); + 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(); + } + +} diff --git a/Serein.Workbench.Avalonia/Assets/avalonia-logo.ico b/Serein.Workbench.Avalonia/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/Serein.Workbench.Avalonia/Assets/avalonia-logo.ico differ diff --git a/Serein.Workbench.Avalonia/Commands/CommandBase.cs b/Serein.Workbench.Avalonia/Commands/CommandBase.cs new file mode 100644 index 0000000..b34fe48 --- /dev/null +++ b/Serein.Workbench.Avalonia/Commands/CommandBase.cs @@ -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; + + /// + /// 是否可以执行命令,子类可以重写这个方法来提供具体的可执行条件 + /// + /// + /// + public virtual bool CanExecute(object parameter) + { + return true; // 默认实现返回 true,表示命令可以执行 + } + + /// + /// 执行命令,子类可以重写这个方法来实现具体的命令逻辑 + /// + /// + public abstract void Execute(object parameter); + + /// + /// 用于触发 CanExecuteChanged 事件 + /// + protected void OnCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Serein.Workbench.Avalonia/Commands/MyCommand.cs b/Serein.Workbench.Avalonia/Commands/MyCommand.cs new file mode 100644 index 0000000..6ec85f9 --- /dev/null +++ b/Serein.Workbench.Avalonia/Commands/MyCommand.cs @@ -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 +{ + /// + /// 流程控制命令 + /// + internal class MyCommand : CommandBase + { + private readonly Action _execute; + private readonly Func _canExecute; + + /// + /// 构造函数接收执行动作和是否可执行的条件 + /// + /// + /// + /// + public MyCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + /// + /// 重写 CanExecute 方法,基于 _canExecute 委托的结果来判断命令是否可执行 + /// + /// + /// + public override bool CanExecute(object parameter) + { + return _canExecute?.Invoke() ?? true; + } + + /// + /// 重写 Execute 方法,执行具体的命令逻辑 + /// + /// + public override void Execute(object parameter) + { + _execute(); + } + } +} diff --git a/Serein.Workbench.Avalonia/Controls/DragControls.cs b/Serein.Workbench.Avalonia/Controls/DragControls.cs new file mode 100644 index 0000000..0221a3b --- /dev/null +++ b/Serein.Workbench.Avalonia/Controls/DragControls.cs @@ -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 +{ + /// + /// 实现拖动的控件 + /// + public partial class DragControls : UserControl + { + /// + /// 记录上一次鼠标位置 + /// + private Point lastMousePosition; + + /// + /// 用于平滑更新坐标的计时器 + /// + private DispatcherTimer _timer; + + /// + /// 标记是否先启动了拖动 + /// + private bool isDragging = false; + + /// + /// 需要更新的坐标点 + /// + private PixelPoint _targetPosition; + + public DragControls() + { + InitializeComponent(); + + // 添加当前控件的事件监听 + PointerPressed += OnPointerPressed; + PointerMoved += OnPointerMoved; + PointerReleased += OnPointerReleased; + + // 初始化计时器 + _timer = new DispatcherTimer + { + + + Interval = TimeSpan.FromMilliseconds(10) + }; + _timer.Tick += OnTimerTick; + } + + /// + /// 计时器事件 + /// + /// + /// + private void OnTimerTick(object sender, EventArgs e) + { + + + var window = this.FindAncestorOfType(); + 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(); + if (window != null) + { + // 记录当前坐标 + _targetPosition = new PixelPoint(window.Position.X + (int)offset.X, + window.Position.Y + (int)offset.Y); + } + } + } +} diff --git a/Serein.Workbench.Avalonia/Converters/BoolToBrushConverter .cs b/Serein.Workbench.Avalonia/Converters/BoolToBrushConverter .cs new file mode 100644 index 0000000..0011f90 --- /dev/null +++ b/Serein.Workbench.Avalonia/Converters/BoolToBrushConverter .cs @@ -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; + } + } +} diff --git a/Serein.Workbench.Avalonia/Converters/IsVisibleOfParameterConverter.cs b/Serein.Workbench.Avalonia/Converters/IsVisibleOfParameterConverter.cs new file mode 100644 index 0000000..f6e7309 --- /dev/null +++ b/Serein.Workbench.Avalonia/Converters/IsVisibleOfParameterConverter.cs @@ -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(); + } + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Junction/JunctionControlBase.cs b/Serein.Workbench.Avalonia/Custom/Junction/JunctionControlBase.cs new file mode 100644 index 0000000..8f61ad5 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Junction/JunctionControlBase.cs @@ -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; + + /// + /// 所在的节点 + /// + 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))); + + /// + /// 控制点类型 + /// + public JunctionType JunctionType + { + get { return EnumHelper.ConvertEnum(GetValue(JunctionTypeProperty).ToString()); } + set { SetValue(JunctionTypeProperty, value.ToString()); } + } + #endregion + protected override Geometry DefiningGeometry => StreamGeometry; +*/ + protected readonly StreamGeometry StreamGeometry = new StreamGeometry(); + + /// + /// 重绘方法 + /// + /// + public abstract void OnRender(DrawingContext drawingContext); + /// + /// 中心点 + /// + public abstract Point MyCenterPoint { get; } + + + + /// + /// 禁止连接 + /// + private bool IsConnectionDisable; + + /// + /// 处理鼠标悬停状态 + /// + private bool _isMouseOver; + public bool IsMouseOver + { + get => _isMouseOver; + set + { + if (_isMouseOver != value) + { + //GlobalJunctionData.MyGlobalConnectingData.CurrentJunction = this; + _isMouseOver = value; + InvalidateVisual(); + } + + } + } + + /// + /// 控件重绘事件 + /// + /// + public override void Render(DrawingContext drawingContext) + { + OnRender(drawingContext); + } + + /// + /// 获取背景颜色 + /// + /// + 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(); + + + /// + /// 控件获得鼠标焦点事件 + /// + /// + /// + private void JunctionControlBase_PointerMoved(object? sender, global::Avalonia.Input.PointerEventArgs e) + { + //if (!GlobalJunctionData.MyGlobalConnectingData.IsCreateing) return; + + //if (IsMouseOver) return; + IsMouseOver = true; + + this.InvalidateVisual(); + } + + /// + /// 控件失去鼠标焦点事件 + /// + /// + /// + private void JunctionControlBase_PointerExited(object? sender, global::Avalonia.Input.PointerEventArgs e) + { + IsMouseOver = false; + e.Handled = true; + + } + + /// + /// 在碰撞点上按下鼠标控件开始进行移动 + /// + /// + /// + private void JunctionControlBase_PointerPressed(object? sender, global::Avalonia.Input.PointerPressedEventArgs e) + { + throw new NotImplementedException(); + //if (e.LeftButton == MouseButtonState.Pressed) + //{ + // var canvas = MainWindow.GetParentOfType(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; + } + + /// + /// 获取起始控制点 + /// + /// + 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); + } + + } + + + + + + } + + +} diff --git a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs new file mode 100644 index 0000000..0429f57 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/ActionNodeViewModel.cs @@ -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() + { + } + + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs new file mode 100644 index 0000000..27b878f --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Node/ViewModels/NodeViewModelBase.cs @@ -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 +{ + /// + /// 节点ViewModel基类 + /// + internal abstract class NodeViewModelBase : ViewModelBase + { + internal abstract NodeModelBase NodeModelBase { get; set; } + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml new file mode 100644 index 0000000..e24738a --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs new file mode 100644 index 0000000..099852e --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Node/Views/ActionNodeView.axaml.cs @@ -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(); + DataContext = _vm; + } + + NodeModelBase INodeControl.NodeModelBase => _vm.NodeModelBase ?? throw new System.NotImplementedException(); // ڵ + + void INodeControl.SetNodeModel(NodeModelBase nodeModel) // ڵ + { + _vm.NodeModelBase = nodeModel; + } +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryInfoViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryInfoViewModel.cs new file mode 100644 index 0000000..dcf2d80 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryInfoViewModel.cs @@ -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 + { + /// + /// 依赖名称 + /// + [ObservableProperty] + public string _libraryName; + + private ObservableCollection activateMethods; + private ObservableCollection flipflopMethods; + + + /// + /// 动作节点 + /// + public ObservableCollection ActivateMethods { get => activateMethods; set => SetProperty(ref activateMethods,value); } + + /// + /// 触发器节点 + /// + public ObservableCollection FlipflopMethods { get => flipflopMethods; set => SetProperty(ref activateMethods, value); } + + + ///// + ///// 加载项目信息 + ///// + ///// + //public void LoadLibraryInfo(LibraryMds libraryMds) + //{ + // this.AssemblyName = libraryMds.AssemblyName; + //} + + + + + + + } +} diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryMethodInfoViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryMethodInfoViewModel.cs new file mode 100644 index 0000000..4c78b17 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibraryMethodInfoViewModel.cs @@ -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 + { + /// + /// 当前预览的方法信息 + /// + + [ObservableProperty] + private MethodDetailsInfo methodDetailsInfo; + + + private IWorkbenchEventService workbenchEventService; + + public FlowLibraryMethodInfoViewModel() + { + workbenchEventService = App.GetService(); + 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} 方法"); + } + + + + + + + + + + + + } + + + + + +} diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibrarysViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibrarysViewModel.cs new file mode 100644 index 0000000..0c23963 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/FlowLibrarysViewModel.cs @@ -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 + { + /// + /// 运行环境 + /// + private IFlowEnvironment flowEnvironment { get; } + /// + /// 流程运行环境事件服务 + /// + private IFlowEEForwardingService feefService { get; } + + /// + /// 运行环境加载的依赖 + /// + public ObservableCollection LibraryList { get; } = new ObservableCollection(); + + + + public FlowLibrarysViewModel() + { + flowEnvironment = App.GetService(); + feefService = App.GetService(); + feefService.OnDllLoad += FeefService_OnDllLoad; ; + + } + + + /// + /// 加载了依赖信息 + /// + /// + 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); + } + + + + } +} diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/MainMenuBarViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/MainMenuBarViewModel.cs new file mode 100644 index 0000000..7d9aace --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/MainMenuBarViewModel.cs @@ -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(); + + var uiContextOperation = App.GetService(); + } + + public void SaveProjectCommand() + { + flowEnvironment.SaveProject(); + } + + + } + + + +} diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/NodeContainerViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/NodeContainerViewModel.cs new file mode 100644 index 0000000..d540ae7 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/NodeContainerViewModel.cs @@ -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 + { + /// + /// 正在创建方法调用关系的连接线 + /// + public bool IsConnectionInvokeNode { get; set; } = false; + /// + /// 正在创建参数传递关系的连接线 + /// + public bool IsConnectionArgSourceNode { get; set; } = false; + public NodeContainerViewModel() + { + + } + + } +} diff --git a/Serein.Workbench.Avalonia/Custom/ViewModels/ParameterDetailsViewModel.cs b/Serein.Workbench.Avalonia/Custom/ViewModels/ParameterDetailsViewModel.cs new file mode 100644 index 0000000..779caab --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/ViewModels/ParameterDetailsViewModel.cs @@ -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; + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs b/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs new file mode 100644 index 0000000..60f822f --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs @@ -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; + + + /// + /// 确定起始坐标和目标坐标、外光样式的曲线 + /// + /// 起始坐标 + /// 结束坐标 + /// 颜色 + /// 是否为虚线 + 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); // 置底 + } + + /// + /// 更新线条落点位置 + /// + /// + /// + public void UpdatePoints(Point start, Point end) + { + startPoint = start; + endPoint = end; + InvalidateVisual(); // 触发重绘 + } + + /// + /// 更新线条落点位置 + /// + /// + public void UpdateEndPoints(Point point) + { + endPoint = point; + InvalidateVisual(); // 触发重绘 + } + /// + /// 更新线条起点位置 + /// + /// + public void UpdateStartPoints(Point point) + { + startPoint = point; + InvalidateVisual(); // 触发重绘 + } + + /// + /// 控件重绘事件 + /// + /// + 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 + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml new file mode 100644 index 0000000..01456e6 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml.cs new file mode 100644 index 0000000..30f2dde --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryInfoView.axaml.cs @@ -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(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + // ı MdsProperty طϢ + if (change.Property == MdsProperty) + { + if(change.NewValue is MethodDetailsInfo[] value) + { + onNext(value); + } + } + } + + /// + /// ȡؼϢ + /// + /// + 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); // ֪ͨطԤijϢ + } + } + private void ListBox_PointerExited(object? o, PointerEventArgs e) + { + if (o is ListBox listBox && listBox.SelectedIndex > -1) + { + listBox.SelectedIndex = -1; // 뿪ˣȡѡ״̬ + } + } + + + + /// + /// Ϣس + /// + /// + private void onNext(MethodDetailsInfo[] value) + { + if(value is null) + { + return; + } + var fmd = value.Where(item => nameof(NodeType.Flipflop).Equals(item.NodeType)); + FlipflopMethods = new ObservableCollection(fmd); + var amd = value.Where(item => nameof(NodeType.Action).Equals(item.NodeType)); + ActionMethods = new ObservableCollection(amd); + } + + + #region Template Public Property / ؼ + public static readonly DirectProperty LibraryNameProperty = + AvaloniaProperty.RegisterDirect(nameof(LibraryName), o => o.LibraryName, (o, v) => o.LibraryName = v); + public static readonly DirectProperty MdsProperty = + AvaloniaProperty.RegisterDirect(nameof(Mds), o => o.Mds, (o, v) => o.Mds = v); + public static readonly DirectProperty> ActionMethodsProperty = + AvaloniaProperty.RegisterDirect>(nameof(ActionMethods), o => o.ActionMethods, (o, v) => o.ActionMethods = v); + public static readonly DirectProperty> FlipflopMethodsProperty = + AvaloniaProperty.RegisterDirect>(nameof(FlipflopMethods), o => o.FlipflopMethods, (o, v) => o.FlipflopMethods = v); + + private string libraryName = string.Empty; + private ObservableCollection actionMethods; + private ObservableCollection flipflopMethods; + private MethodDetailsInfo[] mds = []; + + + public string LibraryName + { + get { return libraryName; } + set { SetAndRaise(LibraryNameProperty, ref libraryName, value); } + } + +/* + public static readonly AttachedProperty LibraryName2Property = AvaloniaProperty.RegisterAttached("LibraryName2"); + + public static string GetLibraryName2(Control element) + { + return element.GetValue(LibraryName2Property); + } + + public static void SetLibraryName2(Control element, string value) + { + element.SetValue(LibraryName2Property, value); + } + */ + + /// + /// Method Info + /// Ϣ + /// + public MethodDetailsInfo[] Mds + { + get { return mds; } + set + { + SetAndRaise(MdsProperty, ref mds, value); + } + } + /// + /// ڵķ + /// + public ObservableCollection ActionMethods + { + get { return actionMethods; } + set + { + SetAndRaise(ActionMethodsProperty, ref actionMethods, value); + } + } + + /// + /// ڵķ + /// + public ObservableCollection FlipflopMethods + { + get { return flipflopMethods; } + set + { + + SetAndRaise(FlipflopMethodsProperty, ref flipflopMethods, value); + } + } + + #endregion +} + + +public class ItemsChangeObservableCollection : ObservableCollection 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)); + } +} diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml new file mode 100644 index 0000000..88b0d89 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml.cs new file mode 100644 index 0000000..4c174ef --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibraryMethodInfoView.axaml.cs @@ -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(); + 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; + // } + //} + + + +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml b/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml new file mode 100644 index 0000000..2e53a5a --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml.cs new file mode 100644 index 0000000..3deecba --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/FlowLibrarysView.axaml.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml b/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml new file mode 100644 index 0000000..00690f1 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml @@ -0,0 +1,43 @@ + + + + + + + + + + + #FFFFFF + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml.cs new file mode 100644 index 0000000..02d74b1 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Views/MatrixHelper.cs b/Serein.Workbench.Avalonia/Custom/Views/MatrixHelper.cs new file mode 100644 index 0000000..06e5d39 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/MatrixHelper.cs @@ -0,0 +1,142 @@ +using static System.Math; + +namespace Avalonia.Controls.PanAndZoom; + +/// +/// Avalonia Matrix helper methods. +/// +public static class MatrixHelper +{ + /// + /// Creates a translation matrix using the specified offsets. + /// + /// X-coordinate offset. + /// Y-coordinate offset. + /// The created translation matrix. + public static Matrix Translate(double offsetX, double offsetY) + { + return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY); + } + + /// + /// Prepends a translation around the center of provided matrix. + /// + /// The matrix to prepend translation. + /// X-coordinate offset. + /// Y-coordinate offset. + /// The created translation matrix. + public static Matrix TranslatePrepend(Matrix matrix, double offsetX, double offsetY) + { + return Translate(offsetX, offsetY) * matrix; + } + + /// + /// Creates a matrix that scales along the x-axis and y-axis. + /// + /// Scaling factor that is applied along the x-axis. + /// Scaling factor that is applied along the y-axis. + /// The created scaling matrix. + public static Matrix Scale(double scaleX, double scaleY) + { + return new Matrix(scaleX, 0, 0, scaleY, 0.0, 0.0); + } + + /// + /// Creates a matrix that is scaling from a specified center. + /// + /// Scaling factor that is applied along the x-axis. + /// Scaling factor that is applied along the y-axis. + /// The center X-coordinate of the scaling. + /// The center Y-coordinate of the scaling. + /// The created scaling matrix. + 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)); + } + + /// + /// Prepends a scale around the center of provided matrix. + /// + /// The matrix to prepend scale. + /// Scaling factor that is applied along the x-axis. + /// Scaling factor that is applied along the y-axis. + /// The center X-coordinate of the scaling. + /// The center Y-coordinate of the scaling. + /// The created scaling matrix. + public static Matrix ScaleAtPrepend(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY) + { + return ScaleAt(scaleX, scaleY, centerX, centerY) * matrix; + } + + /// + /// Creates a translation and scale matrix using the specified offsets and scales along the x-axis and y-axis. + /// + /// Scaling factor that is applied along the x-axis. + /// Scaling factor that is applied along the y-axis. + /// X-coordinate offset. + /// Y-coordinate offset. + /// The created translation and scale matrix. + public static Matrix ScaleAndTranslate(double scaleX, double scaleY, double offsetX, double offsetY) + { + return new Matrix(scaleX, 0.0, 0.0, scaleY, offsetX, offsetY); + } + + /// + /// Creates a skew matrix. + /// + /// Angle of skew along the X-axis in radians. + /// Angle of skew along the Y-axis in radians. + /// When the method completes, contains the created skew matrix. + public static Matrix Skew(float angleX, float angleY) + { + return new Matrix(1.0, Tan(angleX), Tan(angleY), 1.0, 0.0, 0.0); + } + + /// + /// Creates a matrix that rotates. + /// + /// Angle of rotation in radians. Angles are measured clockwise when looking along the rotation axis. + /// The created rotation matrix. + public static Matrix Rotation(double radians) + { + double cos = Cos(radians); + double sin = Sin(radians); + return new Matrix(cos, sin, -sin, cos, 0, 0); + } + + /// + /// Creates a matrix that rotates about a specified center. + /// + /// Angle of rotation in radians. + /// The center X-coordinate of the rotation. + /// The center Y-coordinate of the rotation. + /// The created rotation matrix. + public static Matrix Rotation(double angle, double centerX, double centerY) + { + return Translate(-centerX, -centerY) * Rotation(angle) * Translate(centerX, centerY); + } + + /// + /// Creates a matrix that rotates about a specified center. + /// + /// Angle of rotation in radians. + /// The center of the rotation. + /// The created rotation matrix. + public static Matrix Rotation(double angle, Vector center) + { + return Translate(-center.X, -center.Y) * Rotation(angle) * Translate(center.X, center.Y); + } + + /// + /// Transforms a point by this matrix. + /// + /// The matrix to use as a transformation matrix. + /// >The original point to apply the transformation. + /// The result of the transformation for the input point. + 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); + } +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml new file mode 100644 index 0000000..dcfd2b1 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs new file mode 100644 index 0000000..198a3f6 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeContainerView.axaml.cs @@ -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 뻭صֶ + /// + /// ǷԤڵؼ + /// + private bool IsPreviewNodeControl; + /// + /// Ƿڳѡȡؼ + /// + private bool IsSelectControl; + /// + /// Ƿ϶ؼ + /// + private bool IsControlDragging; + /// + /// Ƿ϶ + /// + private bool IsCanvasDragging; + /// + /// Ƿѡȡڵ + /// + private bool IsSelectDragging; + /// + /// ǰѡȡĿؼ + /// + private readonly List selectNodeControls = []; + + /// + /// ¼ʼ϶ڵؼʱλ + /// + private Point startControlDragPoint; + /// + /// ¼ƶʼʱλ + /// + private Point startCanvasDragPoint; + /// + /// ¼ʼѡȡڵؼʱλ + /// + private Point startSelectControolPoint; + + /// + /// ϱ任 + /// + private readonly TransformGroup canvasTransformGroup; + /// + /// Ż + /// + private readonly ScaleTransform scaleTransform; + /// + /// ƽƻ + /// + private readonly TranslateTransform translateTransform; + #endregion + + + public NodeContainerView() + { + InitializeComponent(); + _vm= App.GetService(); + DataContext = _vm; + + #region ȡUIصķ + keyEventService = App.GetService(); + nodeOperationService = App.GetService(); + 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(); + 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 ڵ¼ط + /// + /// קؼ + /// + /// + /// + 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; + } + + /// + /// ýڵ뻭صIJ¼ + /// + /// + 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 ؼط + + /// + /// ƶؼ + /// + /// + /// + /// + private void DragControl(Control nodeControl, double x, double y) + { + Canvas.SetLeft(nodeControl, x); + Canvas.SetTop(nodeControl, y); + } + + /// + /// ؼ¼϶ + /// + 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; // ֹ¼Ӱؼ + } + + } + + /// + /// ؼƶ¼϶¿ؼλáƶƶ߼ + /// + 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 + + + + + +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml new file mode 100644 index 0000000..913219b --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs new file mode 100644 index 0000000..dbf6ee2 --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs @@ -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; + +/// +/// ӿƵ +/// +public class NodeJunctionView : TemplatedControl +{ + private readonly INodeOperationService nodeOperationService; + + /// + /// RenderпԻ + /// + protected readonly StreamGeometry StreamGeometry = new StreamGeometry(); + + /// + /// ڲ鿴 + /// + private bool IsPreviewing; + + public NodeJunctionView() + { + nodeOperationService = App.GetService(); + this.PointerMoved += NodeJunctionView_PointerMoved; + this.PointerExited += NodeJunctionView_PointerExited; + + this.PointerPressed += NodeJunctionView_PointerPressed; + this.PointerReleased += NodeJunctionView_PointerReleased; + } + + private void NodeJunctionView_PointerReleased(object? sender, PointerReleasedEventArgs e) + { + nodeOperationService.ConnectingData.IsCreateing = false; + } + + private void NodeJunctionView_PointerPressed(object? sender, PointerPressedEventArgs e) + { + nodeOperationService.TryCreateConnectionOnJunction(this); // Կʼ + Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); + } + + + + /// + /// ȡؼϢ + /// + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + //if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm) + //{ + // //p_fm.SelectionChanged += ListBox_SelectionChanged; + // //p_fm.PointerExited += ListBox_PointerExited; + //} + } + + + public static readonly DirectProperty JunctionTypeProperty = + AvaloniaProperty.RegisterDirect(nameof(JunctionType), o => o.JunctionType, (o, v) => o.JunctionType = v); + private JunctionType junctionType; + public JunctionType JunctionType + { + get { return junctionType; } + set { SetAndRaise(JunctionTypeProperty, ref junctionType, value); } + } + + public static readonly DirectProperty MyNodeProperty = + AvaloniaProperty.RegisterDirect(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Ӿ + + /// + /// ؼػ¼ + /// + /// + + 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 + + + /// + /// ȡɫ + /// + /// + 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 + + + + +} + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml new file mode 100644 index 0000000..785860c --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml.cs b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml.cs new file mode 100644 index 0000000..63eab2b --- /dev/null +++ b/Serein.Workbench.Avalonia/Custom/Views/ParameterDetailsInfoView.axaml.cs @@ -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; + } + + +} \ No newline at end of file diff --git a/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs b/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs new file mode 100644 index 0000000..0e0b883 --- /dev/null +++ b/Serein.Workbench.Avalonia/DataTemplates/LibraryMethodInfoDataTemplate.cs @@ -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 +{ + + /// + /// 方法信息模板 + /// + 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; + //} + } + + + + } + +} diff --git a/Serein.Workbench.Avalonia/DataTemplates/NodeMethodParameterInfoDataTemplate.cs b/Serein.Workbench.Avalonia/DataTemplates/NodeMethodParameterInfoDataTemplate.cs new file mode 100644 index 0000000..ae6a25d --- /dev/null +++ b/Serein.Workbench.Avalonia/DataTemplates/NodeMethodParameterInfoDataTemplate.cs @@ -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; + } + } +} diff --git a/Serein.Workbench.Avalonia/Extension/LineExtension.cs b/Serein.Workbench.Avalonia/Extension/LineExtension.cs new file mode 100644 index 0000000..8164c82 --- /dev/null +++ b/Serein.Workbench.Avalonia/Extension/LineExtension.cs @@ -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 +{ + + /// + /// 线条颜色 + /// + public static class LineExtension + { + /// + /// 根据连接类型指定颜色 + /// + /// + /// + /// + 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(), + }; + } + /// + /// 根据连接类型指定颜色 + /// + /// + /// + /// + 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(), + }; + } + + } + +} diff --git a/Serein.Workbench.Avalonia/Extension/PointExtension.cs b/Serein.Workbench.Avalonia/Extension/PointExtension.cs new file mode 100644 index 0000000..8578180 --- /dev/null +++ b/Serein.Workbench.Avalonia/Extension/PointExtension.cs @@ -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; + } + } +} diff --git a/Serein.Workbench.Avalonia/Extension/PointerExtension.cs b/Serein.Workbench.Avalonia/Extension/PointerExtension.cs new file mode 100644 index 0000000..6fa6f07 --- /dev/null +++ b/Serein.Workbench.Avalonia/Extension/PointerExtension.cs @@ -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 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; + } + } +} diff --git a/Serein.Workbench.Avalonia/Model/ConnectingData.cs b/Serein.Workbench.Avalonia/Model/ConnectingData.cs new file mode 100644 index 0000000..26e945f --- /dev/null +++ b/Serein.Workbench.Avalonia/Model/ConnectingData.cs @@ -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 +{ + + /// + /// 节点之间连接线的相关控制方法 + /// + public class ConnectingData + { + /// + /// 是否正在创建连线 + /// + public bool IsCreateing { get; set; } + /// + /// 起始控制点 + /// + public NodeJunctionView? StartJunction { get; set; } + /// + /// 当前的控制点 + /// + public NodeJunctionView? CurrentJunction { get; set; } + /// + /// 开始坐标 + /// + public Point StartPoint { get; set; } + /// + /// 线条样式 + /// + public MyLine? TempLine { get; set; } + + /// + /// 线条类别(方法调用) + /// + public ConnectionInvokeType ConnectionInvokeType { get; set; } = ConnectionInvokeType.IsSucceed; + /// + /// 线条类别(参数传递) + /// + public ConnectionArgSourceType ConnectionArgSourceType { get; set; } = ConnectionArgSourceType.GetOtherNodeData; + + /// + /// 判断当前连接类型 + /// + public JunctionOfConnectionType? Type => StartJunction?.JunctionType.ToConnectyionType(); + + + /// + /// 是否允许连接 + /// + + 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; + } + } + } + + /// + /// 更新临时的连接线 + /// + /// + 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); + + } + } + + /// + /// 重置 + /// + public void Reset() + { + IsCreateing = false; + StartJunction = null; + CurrentJunction = null; + TempLine?.Remove(); + ConnectionInvokeType = ConnectionInvokeType.IsSucceed; + ConnectionArgSourceType = ConnectionArgSourceType.GetOtherNodeData; + } + + + + } +} diff --git a/Serein.Workbench.Avalonia/Model/FlowLibraryInfo.cs b/Serein.Workbench.Avalonia/Model/FlowLibraryInfo.cs new file mode 100644 index 0000000..fc83bb4 --- /dev/null +++ b/Serein.Workbench.Avalonia/Model/FlowLibraryInfo.cs @@ -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 + { + + } +} diff --git a/Serein.Workbench.Avalonia/Model/MyLine.cs b/Serein.Workbench.Avalonia/Model/MyLine.cs new file mode 100644 index 0000000..d543755 --- /dev/null +++ b/Serein.Workbench.Avalonia/Model/MyLine.cs @@ -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 +{ + + /// + /// 绘制的线 + /// + public class MyLine + { + /// + /// 将线条绘制出来 + /// + /// 放置画布 + /// 线的实体 + public MyLine(Canvas canvas, ConnectionLineShape line) + { + Canvas = canvas; + Line = line; + canvas?.Children.Add(line); + } + + public Canvas Canvas { get; } + public ConnectionLineShape Line { get; } + + /// + /// 移除线 + /// + public void Remove() + { + Canvas?.Children.Remove(Line); + } + } +} diff --git a/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj b/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj new file mode 100644 index 0000000..ef5b5df --- /dev/null +++ b/Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj @@ -0,0 +1,60 @@ + + + net8.0 + enable + latest + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FlowLibraryMethodInfoView1. axaml + + + MainMenuBarView.axaml + + + FlowLibraryInfoView. axaml + + + NodeContainerView.axaml + + + diff --git a/Serein.Workbench.Avalonia/Services/FlowEEForwardingService.cs b/Serein.Workbench.Avalonia/Services/FlowEEForwardingService.cs new file mode 100644 index 0000000..ceb9116 --- /dev/null +++ b/Serein.Workbench.Avalonia/Services/FlowEEForwardingService.cs @@ -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 + { + /// + /// 流程运行环境 + /// + private readonly IFlowEnvironment flowEnvironment; + private readonly IFlowEnvironmentEvent flowEnvironmentEvent; + + /// + /// 转发流程运行环境各个事件的实现类 + /// + /// + /// + public FlowEEForwardingService(IFlowEnvironment flowEnvironment, + IFlowEnvironmentEvent flowEnvironmentEvent) + { + this.flowEnvironment = flowEnvironment; + this.flowEnvironmentEvent = flowEnvironmentEvent; + InitFlowEnvironmentEvent(); + } + + #region 工作台事件转发 + /// + /// 加载了依赖文件事件 + /// + public event LoadDllHandler? OnDllLoad; + /// + /// 项目加载完成事件 + /// + public event ProjectLoadedHandler? OnProjectLoaded; + /// + /// 项目保存中事件 + /// + public event ProjectSavingHandler? OnProjectSaving; + /// + /// 节点连接改变事件 + /// + public event NodeConnectChangeHandler? OnNodeConnectChange; + /// + /// 节点创建事件 + /// + public event NodeCreateHandler? OnNodeCreate; + /// + /// 节点移除事件 + /// + public event NodeRemoveHandler? OnNodeRemove; + /// + /// 节点放置容器事件 + /// + public event NodePlaceHandler? OnNodePlace; + /// + /// 节点取出事件 + /// + public event NodeTakeOutHandler? OnNodeTakeOut; + /// + /// 流程起始节点改变事件 + /// + public event StartNodeChangeHandler? OnStartNodeChange; + /// + /// 流程运行完毕事件 + /// + public event FlowRunCompleteHandler? OnFlowRunComplete; + /// + /// 被监视的对象数据改变事件 + /// + public event MonitorObjectChangeHandler? OnMonitorObjectChange; + /// + /// 节点中断状态改变事件 + /// + public event NodeInterruptStateChangeHandler? OnNodeInterruptStateChange; + /// + /// 表达式中断触发事件 + /// + public event ExpInterruptTriggerHandler? OnInterruptTrigger; + /// + /// 容器对象改变事件 + /// + public event IOCMembersChangedHandler? OnIOCMembersChanged; + /// + /// 节点定位事件 + /// + public event NodeLocatedHandler? OnNodeLocated; + /// + /// 节点移动事件 + /// + public event NodeMovedHandler? OnNodeMoved; + /// + /// 运行环境输出事件 + /// + 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 运行环境事件 + + /// + /// 环境内容输出 + /// + /// + /// + private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value) + { + //LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}"); + } + + /// + /// 需要保存项目 + /// + /// + /// + private void EnvDecorator_OnProjectSaving(ProjectSavingEventArgs eventArgs) + { + OnProjectSaving?.Invoke(eventArgs); + } + + /// + /// 加载完成 + /// + /// + private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) + { + OnProjectLoaded?.Invoke(eventArgs); + } + + /// + /// 运行完成 + /// + /// + /// + private void FlowEnvironment_OnFlowRunCompleteEvent(FlowEventArgs eventArgs) + { + SereinEnv.WriteLine(InfoType.INFO, "-------运行完成---------\r\n"); + OnFlowRunComplete?.Invoke(eventArgs); + } + + /// + /// 加载了DLL文件,dll内容 + /// + private void FlowEnvironment_DllLoadEvent(LoadDllEventArgs eventArgs) + { + OnDllLoad?.Invoke(eventArgs); + } + + /// + /// 节点连接关系变更 + /// + /// + private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) + { + OnNodeConnectChange?.Invoke(eventArgs); + } + + /// + /// 节点移除事件 + /// + /// + private void FlowEnvironment_NodeRemoveEvent(NodeRemoveEventArgs eventArgs) + { + OnNodeRemove?.Invoke(eventArgs); + } + + /// + /// 添加节点事件 + /// + /// 添加节点事件参数 + /// + private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs) + { + OnNodeCreate?.Invoke(eventArgs); + } + + /// + /// 放置一个节点 + /// + /// + /// + private void EnvDecorator_OnNodePlaceEvent(NodePlaceEventArgs eventArgs) + { + OnNodePlace?.Invoke(eventArgs); + } + + /// + /// 取出一个节点 + /// + /// + private void EnvDecorator_OnNodeTakeOutEvent(NodeTakeOutEventArgs eventArgs) + { + OnNodeTakeOut?.Invoke(eventArgs); + + } + + /// + /// 设置了流程起始控件 + /// + /// + /// + private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs) + { + + OnStartNodeChange?.Invoke(eventArgs); + } + + /// + /// 被监视的对象发生改变 + /// + /// + private void FlowEnvironment_OnMonitorObjectChangeEvent(MonitorObjectEventArgs eventArgs) + { + OnMonitorObjectChange?.Invoke(eventArgs); + } + + /// + /// 节点中断状态改变。 + /// + /// + private void FlowEnvironment_OnNodeInterruptStateChangeEvent(NodeInterruptStateChangeEventArgs eventArgs) + { + OnNodeInterruptStateChange?.Invoke(eventArgs); + } + + /// + /// 节点触发了中断 + /// + /// + /// + private void FlowEnvironment_OnInterruptTriggerEvent(InterruptTriggerEventArgs eventArgs) + { + OnInterruptTrigger?.Invoke(eventArgs); + } + + /// + /// IOC变更 + /// + /// + /// + private void FlowEnvironment_OnIOCMembersChangedEvent(IOCMembersChangedEventArgs eventArgs) + { + OnIOCMembersChanged?.Invoke(eventArgs); + + } + + /// + /// 节点需要定位 + /// + /// + /// + private void FlowEnvironment_OnNodeLocateEvent(NodeLocatedEventArgs eventArgs) + { + OnNodeLocated?.Invoke(eventArgs); + } + + + /// + /// 节点移动 + /// + /// + private void FlowEnvironment_OnNodeMovedEvent(NodeMovedEventArgs eventArgs) + { + OnNodeMoved?.Invoke(eventArgs); + } + + + #endregion + + + #endregion + + + } +} diff --git a/Serein.Workbench.Avalonia/Services/KeyEventService.cs b/Serein.Workbench.Avalonia/Services/KeyEventService.cs new file mode 100644 index 0000000..3c6ec44 --- /dev/null +++ b/Serein.Workbench.Avalonia/Services/KeyEventService.cs @@ -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); + + /// + /// 全局事件服务 + /// + internal interface IKeyEventService + { + event KeyDownEventHandler KeyDown; + event KeyUpEventHandler KeyUp; + + /// + /// 获取某个按键状态 + /// + /// + /// + bool GetKeyState(Key key); + /// + /// 设置某个按键的状态 + /// + /// + /// + void SetKeyState(Key key, bool statestate); + } + + /// + /// 管理按键状态 + /// + internal class KeyEventService : IKeyEventService + { + + /// + /// 按键按下 + /// + public event KeyDownEventHandler KeyDown; + /// + /// 按键松开 + /// + public event KeyUpEventHandler KeyUp; + + public KeyEventService() + { + var arr = Enum.GetValues(); + 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; + } + } +} diff --git a/Serein.Workbench.Avalonia/Services/NodeOperationService.cs b/Serein.Workbench.Avalonia/Services/NodeOperationService.cs new file mode 100644 index 0000000..7608f82 --- /dev/null +++ b/Serein.Workbench.Avalonia/Services/NodeOperationService.cs @@ -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 +{ + + /// + /// 提供节点操作的接口 + /// + internal interface INodeOperationService + { + /// + /// 连接数据 + /// + ConnectingData ConnectingData { get; } + + /// + /// 主画布 + /// + Canvas MainCanvas { get; set; } + + /// + /// 节点创建事件 + /// + + event NodeViewCreateHandle OnNodeViewCreate; + + /// + /// 创建节点控件 + /// + /// 控件类型 + /// 创建坐标 + /// 节点方法信息 + public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position); + + /// + /// 尝试从连接控制点创建连接 + /// + /// + void TryCreateConnectionOnJunction(NodeJunctionView startJunction); + + } + + + + + #region 事件与事件参数 + /// + /// 创建节点控件事件 + /// + /// + + internal delegate bool NodeViewCreateHandle(NodeViewCreateEventArgs eventArgs); + + /// + /// 创建节点控件事件参数 + /// + + + + 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 +{ + /// + /// 节点操作相关服务 + /// + 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(); + 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(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 私有变量 + + /// + /// 存储所有与节点有关的控件 + /// + private Dictionary NodeControls { get; } = []; + + + + /// + /// 流程运行环境 + /// + private readonly IFlowEnvironment flowEnvironment; + + /// + /// 流程运行环境事件转发 + /// + private readonly IFlowEEForwardingService feefService; + #endregion + + /// + /// 创建了节点控件 + /// + public event NodeViewCreateHandle OnNodeViewCreate; + + /// + /// 创建节点控件 + /// + /// 控件类型 + /// 创建坐标 + /// 节点方法信息(基础节点传null) + public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position) + { + Task.Run(async () => + { + if (EnumHelper.TryConvertEnum(methodDetailsInfo.NodeType, out var nodeType)) + { + await flowEnvironment.CreateNodeAsync(nodeType, position, methodDetailsInfo); + } + }); + } + + + /// + /// 从工作台事件转发器监听节点创建事件 + /// + /// + 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); // 缓存起来,通知其它地方拿取这个控件 + } + + } + + /// + /// 创建节点控件 + /// + /// 节点控件视图控件类型 + /// 节点控件ViewModel类型 + /// 节点Model实例 + /// 返回的节点对象 + /// 是否创建成功 + /// 无法创建节点控件 + 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; + //} + } + + /// + /// 尝试在连接控制点之间创建连接线 + /// + 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); + } + } + } + +} diff --git a/Serein.Workbench.Avalonia/Services/WorkbenchEventService.cs b/Serein.Workbench.Avalonia/Services/WorkbenchEventService.cs new file mode 100644 index 0000000..6ce9684 --- /dev/null +++ b/Serein.Workbench.Avalonia/Services/WorkbenchEventService.cs @@ -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 + { + /// + /// 方法信息 + /// + public MethodDetailsInfo MethodDetailsInfo { get; } = mdInfo; + } + #endregion + + + /// + /// 工作台事件管理 + /// + internal interface IWorkbenchEventService + { + /// + /// 预览了某个方法信息(待创建) + /// + event PreviewlMethodInfoHandler OnPreviewlMethodInfo; + + /// + /// 预览依赖方法信息 + /// + void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo); + } + + /// + /// 工作台事件的实现类 + /// + internal class WorkbenchEventService : IWorkbenchEventService + { + + private readonly IFlowEnvironment flowEnvironment; + /// + /// 管理工作台的事件 + /// + /// + public WorkbenchEventService(IFlowEnvironment flowEnvironment) + { + this.flowEnvironment = flowEnvironment; + + } + + private void SubscribeEvents() + { + + } + + /// + /// 预览了某个方法信息(待创建) + /// + public event PreviewlMethodInfoHandler? OnPreviewlMethodInfo; + /// + /// 预览依赖方法信息 + /// + public void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo) + { + OnPreviewlMethodInfo?.Invoke(new PreviewlMethodInfoEventArgs(mdInfo)); + } + + /// + /// 需要放置节点控件 + /// + public void PlateNodeControl() + { + + } + } + +} + diff --git a/Serein.Workbench.Avalonia/ViewModels/MainViewModel.cs b/Serein.Workbench.Avalonia/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..746e9a1 --- /dev/null +++ b/Serein.Workbench.Avalonia/ViewModels/MainViewModel.cs @@ -0,0 +1,7 @@ +namespace Serein.Workbench.Avalonia.ViewModels; + +public partial class MainViewModel : ViewModelBase +{ + public string Greeting => "Welcome to Avalonia!"; + +} diff --git a/Serein.Workbench.Avalonia/ViewModels/ViewModelBase.cs b/Serein.Workbench.Avalonia/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..ba8ce4d --- /dev/null +++ b/Serein.Workbench.Avalonia/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Serein.Workbench.Avalonia.ViewModels; + +public class ViewModelBase : ObservableObject +{ +} diff --git a/Serein.Workbench.Avalonia/Views/MainView.axaml b/Serein.Workbench.Avalonia/Views/MainView.axaml new file mode 100644 index 0000000..a26f1c7 --- /dev/null +++ b/Serein.Workbench.Avalonia/Views/MainView.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Serein.Workbench.Avalonia/Views/MainView.axaml.cs b/Serein.Workbench.Avalonia/Views/MainView.axaml.cs new file mode 100644 index 0000000..795be73 --- /dev/null +++ b/Serein.Workbench.Avalonia/Views/MainView.axaml.cs @@ -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(); + } + + +} diff --git a/Serein.Workbench.Avalonia/Views/MainWindow.axaml b/Serein.Workbench.Avalonia/Views/MainWindow.axaml new file mode 100644 index 0000000..eec4356 --- /dev/null +++ b/Serein.Workbench.Avalonia/Views/MainWindow.axaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Serein.Workbench.Avalonia/Views/MainWindow.axaml.cs b/Serein.Workbench.Avalonia/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..e46204f --- /dev/null +++ b/Serein.Workbench.Avalonia/Views/MainWindow.axaml.cs @@ -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(); + 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) + { + + + } +} diff --git a/SereinFlow.sln b/SereinFlow.sln index 446fed2..549ddc0 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.BaseNode", "Serein.B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Script", "Serein.Script\Serein.Script.csproj", "{D14BC18C-3D69-49FA-BEEA-A9AA570C7469}" 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 GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.Release|Any CPU.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE