mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
添加Avalonia项目的demo
This commit is contained in:
23
Serein.Workbench.Avalonia.Desktop/Program.cs
Normal file
23
Serein.Workbench.Avalonia.Desktop/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Desktop;
|
||||
|
||||
class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
|
||||
One for Windows with net8.0-windows TFM, one for MacOS with net8.0-macos and one with net8.0 TFM for Linux.-->
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Serein.Workbench.Avalonia\Serein.Workbench.Avalonia.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
18
Serein.Workbench.Avalonia.Desktop/app.manifest
Normal file
18
Serein.Workbench.Avalonia.Desktop/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embeded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
17
Serein.Workbench.Avalonia/Api/IFlowEEForwardingService.cs
Normal file
17
Serein.Workbench.Avalonia/Api/IFlowEEForwardingService.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程事件管理,转发流程运行环境中触发的事件到工作台各个订阅者
|
||||
/// </summary>
|
||||
internal interface IFlowEEForwardingService : IFlowEnvironmentEvent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
23
Serein.Workbench.Avalonia/Api/INodeControl.cs
Normal file
23
Serein.Workbench.Avalonia/Api/INodeControl.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Serein.Library;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Api
|
||||
{
|
||||
internal interface INodeControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 对应的节点实体
|
||||
/// </summary>
|
||||
NodeModelBase NodeModelBase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化使用的方法,设置节点实体
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
void SetNodeModel(NodeModelBase nodeModel);
|
||||
}
|
||||
}
|
||||
52
Serein.Workbench.Avalonia/Api/INodeJunction.cs
Normal file
52
Serein.Workbench.Avalonia/Api/INodeJunction.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Custom.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Api
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 约束一个节点应该有哪些控制点
|
||||
/// </summary>
|
||||
public interface INodeJunction
|
||||
{
|
||||
/// <summary>
|
||||
/// 方法执行入口控制点
|
||||
/// </summary>
|
||||
NodeJunctionView ExecuteJunction { get; }
|
||||
/// <summary>
|
||||
/// 执行完成后下一个要执行的方法控制点
|
||||
/// </summary>
|
||||
NodeJunctionView NextStepJunction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数节点控制点
|
||||
/// </summary>
|
||||
NodeJunctionView[] ArgDataJunction { get; }
|
||||
/// <summary>
|
||||
/// 返回值控制点
|
||||
/// </summary>
|
||||
NodeJunctionView ReturnDataJunction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取目标参数控制点,用于防止wpf释放资源导致找不到目标节点,返回-1,-1的坐标
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
NodeJunctionView GetJunctionOfArgData(int index)
|
||||
{
|
||||
var arr = ArgDataJunction;
|
||||
if (index >= arr.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return arr[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Serein.Workbench.Avalonia/App.axaml
Normal file
21
Serein.Workbench.Avalonia/App.axaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Serein.Workbench.Avalonia.App"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
|
||||
RequestedThemeVariant="Default">
|
||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
|
||||
</Application.Styles>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--自定义控件:依赖信息-->
|
||||
<ResourceInclude Source="/Custom/Views/FlowLibraryInfoView.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
139
Serein.Workbench.Avalonia/App.axaml.cs
Normal file
139
Serein.Workbench.Avalonia/App.axaml.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow;
|
||||
using Serein.NodeFlow.Env;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Custom.Node.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Custom.Node.Views;
|
||||
using Serein.Workbench.Avalonia.Custom.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Views;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册ViewModel
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public static void AddViewModelServices(this IServiceCollection collection)
|
||||
{
|
||||
collection.AddTransient<MainViewModel>(); // 主窗体
|
||||
collection.AddTransient<MainMenuBarViewModel>(); // 主窗体菜单
|
||||
collection.AddTransient<FlowLibrarysViewModel>(); // 依赖集合
|
||||
collection.AddTransient<FlowLibraryMethodInfoViewModel>(); // 预览的方法信息
|
||||
//collection.AddTransient<ParameterDetailsViewModel>(); // 节点参数信息
|
||||
collection.AddTransient<NodeContainerViewModel>(); // 节点容器(画布)
|
||||
|
||||
|
||||
collection.AddTransient<ActionNodeViewModel>(); // 节点容器(画布)
|
||||
|
||||
|
||||
//collection.AddTransient<FlowLibraryInfoViewModel>(); // 依赖信息
|
||||
}
|
||||
|
||||
public static void AddWorkbenchServices(this IServiceCollection collection)
|
||||
{
|
||||
collection.AddSingleton<IFlowEEForwardingService, FlowEEForwardingService>(); // 流程事件管理
|
||||
collection.AddSingleton<IWorkbenchEventService, WorkbenchEventService>(); // 流程事件管理
|
||||
collection.AddSingleton<INodeOperationService, NodeOperationService>(); // 节点操作管理
|
||||
collection.AddSingleton<IKeyEventService, KeyEventService>(); // 按键事件管理
|
||||
//collection.AddSingleton<FlowNodeControlService>(); // 流程节点控件管理
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册流程接口相关实例
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public static void AddFlowServices(this IServiceCollection collection)
|
||||
{
|
||||
|
||||
#region 创建实例
|
||||
Func<SynchronizationContext> getSyncContext = null;
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var uiContext = SynchronizationContext.Current; // 在UI线程上获取UI线程上下文信息
|
||||
if(uiContext is not null)
|
||||
{
|
||||
getSyncContext = () => uiContext;
|
||||
}
|
||||
});
|
||||
|
||||
UIContextOperation? uIContextOperation = null;
|
||||
uIContextOperation = new UIContextOperation(getSyncContext); // 封装一个调用UI线程的工具类
|
||||
FlowEnvironmentDecorator flowEnvironmentDecorator = new FlowEnvironmentDecorator(uIContextOperation);
|
||||
collection.AddSingleton<UIContextOperation>(uIContextOperation); // 注册UI线程操作上下文
|
||||
collection.AddSingleton<IFlowEnvironment>(flowEnvironmentDecorator); // 注册运行环境
|
||||
collection.AddSingleton<IFlowEnvironmentEvent>(flowEnvironmentDecorator); // 注册运行环境事件
|
||||
//var strte = tcs.Task.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
//if (strte) // 等待实例生成完成
|
||||
//{
|
||||
//}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private static IServiceProvider? ServiceProvider;
|
||||
public static T GetService<T>() where T : class
|
||||
{
|
||||
return ServiceProvider?.GetService<T>() ?? throw new NullReferenceException();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
|
||||
public override async void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// 如果使用 CommunityToolkit,则需要用下面一行移除 Avalonia 数据验证。
|
||||
// 如果没有这一行,数据验证将会在 Avalonia 和 CommunityToolkit 中重复。
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
// 注册应用程序运行所需的所有服务
|
||||
var collection = new ServiceCollection();
|
||||
collection.AddWorkbenchServices();
|
||||
collection.AddFlowServices();
|
||||
collection.AddViewModelServices();
|
||||
var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口
|
||||
App.ServiceProvider = services;
|
||||
|
||||
|
||||
var vm = App.ServiceProvider?.GetRequiredService<MainViewModel>();
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||
{
|
||||
singleViewPlatform.MainView = new MainView
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Serein.Workbench.Avalonia/Assets/avalonia-logo.ico
Normal file
BIN
Serein.Workbench.Avalonia/Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
38
Serein.Workbench.Avalonia/Commands/CommandBase.cs
Normal file
38
Serein.Workbench.Avalonia/Commands/CommandBase.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Commands
|
||||
{
|
||||
internal abstract class CommandBase
|
||||
{
|
||||
// CanExecuteChanged 事件
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以执行命令,子类可以重写这个方法来提供具体的可执行条件
|
||||
/// </summary>
|
||||
/// <param name="parameter"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool CanExecute(object parameter)
|
||||
{
|
||||
return true; // 默认实现返回 true,表示命令可以执行
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行命令,子类可以重写这个方法来实现具体的命令逻辑
|
||||
/// </summary>
|
||||
/// <param name="parameter"></param>
|
||||
public abstract void Execute(object parameter);
|
||||
|
||||
/// <summary>
|
||||
/// 用于触发 CanExecuteChanged 事件
|
||||
/// </summary>
|
||||
protected void OnCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Serein.Workbench.Avalonia/Commands/MyCommand.cs
Normal file
48
Serein.Workbench.Avalonia/Commands/MyCommand.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程控制命令
|
||||
/// </summary>
|
||||
internal class MyCommand : CommandBase
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Func<bool> _canExecute;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数接收执行动作和是否可执行的条件
|
||||
/// </summary>
|
||||
/// <param name="execute"></param>
|
||||
/// <param name="canExecute"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public MyCommand(Action execute, Func<bool> canExecute = null)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 CanExecute 方法,基于 _canExecute 委托的结果来判断命令是否可执行
|
||||
/// </summary>
|
||||
/// <param name="parameter"></param>
|
||||
/// <returns></returns>
|
||||
public override bool CanExecute(object parameter)
|
||||
{
|
||||
return _canExecute?.Invoke() ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写 Execute 方法,执行具体的命令逻辑
|
||||
/// </summary>
|
||||
/// <param name="parameter"></param>
|
||||
public override void Execute(object parameter)
|
||||
{
|
||||
_execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Serein.Workbench.Avalonia/Controls/DragControls.cs
Normal file
127
Serein.Workbench.Avalonia/Controls/DragControls.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现拖动的控件
|
||||
/// </summary>
|
||||
public partial class DragControls : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 记录上一次鼠标位置
|
||||
/// </summary>
|
||||
private Point lastMousePosition;
|
||||
|
||||
/// <summary>
|
||||
/// 用于平滑更新坐标的计时器
|
||||
/// </summary>
|
||||
private DispatcherTimer _timer;
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否先启动了拖动
|
||||
/// </summary>
|
||||
private bool isDragging = false;
|
||||
|
||||
/// <summary>
|
||||
/// 需要更新的坐标点
|
||||
/// </summary>
|
||||
private PixelPoint _targetPosition;
|
||||
|
||||
public DragControls()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 添加当前控件的事件监听
|
||||
PointerPressed += OnPointerPressed;
|
||||
PointerMoved += OnPointerMoved;
|
||||
PointerReleased += OnPointerReleased;
|
||||
|
||||
// 初始化计时器
|
||||
_timer = new DispatcherTimer
|
||||
{
|
||||
|
||||
|
||||
Interval = TimeSpan.FromMilliseconds(10)
|
||||
};
|
||||
_timer.Tick += OnTimerTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计时器事件
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null && window.Position != _targetPosition)
|
||||
{
|
||||
|
||||
|
||||
// 更新坐标
|
||||
window.Position = _targetPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
// 启动拖动
|
||||
isDragging = true;
|
||||
// 记录当前坐标
|
||||
lastMousePosition = e.GetPosition(this);
|
||||
e.Handled = true;
|
||||
// 启动计时器
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
if (!isDragging) return;
|
||||
// 停止拖动
|
||||
isDragging = false;
|
||||
e.Handled = true;
|
||||
// 停止计时器
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void OnPointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
|
||||
// 如果没有启动拖动,则不执行
|
||||
if (!isDragging) return;
|
||||
|
||||
var currentMousePosition = e.GetPosition(this);
|
||||
var offset = currentMousePosition - lastMousePosition;
|
||||
var window = this.FindAncestorOfType<Window>();
|
||||
if (window != null)
|
||||
{
|
||||
// 记录当前坐标
|
||||
_targetPosition = new PixelPoint(window.Position.X + (int)offset.X,
|
||||
window.Position.Y + (int)offset.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
245
Serein.Workbench.Avalonia/Custom/Junction/JunctionControlBase.cs
Normal file
245
Serein.Workbench.Avalonia/Custom/Junction/JunctionControlBase.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Serein.Workbench.Avalonia.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.VisualTree;
|
||||
using Serein.Library;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Junction
|
||||
{
|
||||
public abstract class JunctionControlBase : Control
|
||||
{
|
||||
protected JunctionControlBase()
|
||||
{
|
||||
this.Width = 25;
|
||||
this.Height = 20;
|
||||
this.PointerPressed += JunctionControlBase_PointerPressed;
|
||||
this.PointerMoved += JunctionControlBase_PointerMoved;
|
||||
this.PointerExited += JunctionControlBase_PointerExited;
|
||||
}
|
||||
|
||||
/*
|
||||
#region 控件属性,所在的节点
|
||||
public static readonly DependencyProperty NodeProperty =
|
||||
DependencyProperty.Register(nameof(MyNode), typeof(NodeModelBase), typeof(JunctionControlBase), new PropertyMetadata(default(NodeModelBase)));
|
||||
|
||||
//public NodeModelBase NodeModel;
|
||||
|
||||
/// <summary>
|
||||
/// 所在的节点
|
||||
/// </summary>
|
||||
public NodeModelBase MyNode
|
||||
{
|
||||
get { return (NodeModelBase)GetValue(NodeProperty); }
|
||||
set { SetValue(NodeProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 控件属性,连接器类型
|
||||
public static readonly DependencyProperty JunctionTypeProperty =
|
||||
DependencyProperty.Register(nameof(JunctionType), typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
|
||||
|
||||
/// <summary>
|
||||
/// 控制点类型
|
||||
/// </summary>
|
||||
public JunctionType JunctionType
|
||||
{
|
||||
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
|
||||
set { SetValue(JunctionTypeProperty, value.ToString()); }
|
||||
}
|
||||
#endregion
|
||||
protected override Geometry DefiningGeometry => StreamGeometry;
|
||||
*/
|
||||
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
|
||||
|
||||
/// <summary>
|
||||
/// 重绘方法
|
||||
/// </summary>
|
||||
/// <param name="drawingContext"></param>
|
||||
public abstract void OnRender(DrawingContext drawingContext);
|
||||
/// <summary>
|
||||
/// 中心点
|
||||
/// </summary>
|
||||
public abstract Point MyCenterPoint { get; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 禁止连接
|
||||
/// </summary>
|
||||
private bool IsConnectionDisable;
|
||||
|
||||
/// <summary>
|
||||
/// 处理鼠标悬停状态
|
||||
/// </summary>
|
||||
private bool _isMouseOver;
|
||||
public bool IsMouseOver
|
||||
{
|
||||
get => _isMouseOver;
|
||||
set
|
||||
{
|
||||
if (_isMouseOver != value)
|
||||
{
|
||||
//GlobalJunctionData.MyGlobalConnectingData.CurrentJunction = this;
|
||||
_isMouseOver = value;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件重绘事件
|
||||
/// </summary>
|
||||
/// <param name="drawingContext"></param>
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
OnRender(drawingContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取背景颜色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected Brush GetBackgrounp()
|
||||
{
|
||||
return (Brush)Brushes.Transparent;
|
||||
//var myData = GlobalJunctionData.MyGlobalConnectingData;
|
||||
//if (!myData.IsCreateing)
|
||||
//{
|
||||
// return Brushes.Transparent;
|
||||
//}
|
||||
//if (IsMouseOver)
|
||||
//{
|
||||
// if (myData.IsCanConnected)
|
||||
// {
|
||||
// if (myData.Type == JunctionOfConnectionType.Invoke)
|
||||
// {
|
||||
// return myData.ConnectionInvokeType.ToLineColor();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return myData.ConnectionArgSourceType.ToLineColor();
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return Brushes.Red;
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// return Brushes.Transparent;
|
||||
//}
|
||||
}
|
||||
|
||||
private object lockObj = new object();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 控件获得鼠标焦点事件
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void JunctionControlBase_PointerMoved(object? sender, global::Avalonia.Input.PointerEventArgs e)
|
||||
{
|
||||
//if (!GlobalJunctionData.MyGlobalConnectingData.IsCreateing) return;
|
||||
|
||||
//if (IsMouseOver) return;
|
||||
IsMouseOver = true;
|
||||
|
||||
this.InvalidateVisual();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件失去鼠标焦点事件
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void JunctionControlBase_PointerExited(object? sender, global::Avalonia.Input.PointerEventArgs e)
|
||||
{
|
||||
IsMouseOver = false;
|
||||
e.Handled = true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在碰撞点上按下鼠标控件开始进行移动
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void JunctionControlBase_PointerPressed(object? sender, global::Avalonia.Input.PointerPressedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
//if (e.LeftButton == MouseButtonState.Pressed)
|
||||
//{
|
||||
// var canvas = MainWindow.GetParentOfType<Canvas>(this);
|
||||
// if (canvas != null)
|
||||
// {
|
||||
// var myData = GlobalJunctionData.MyGlobalConnectingData;
|
||||
// myData.Reset();
|
||||
// myData.IsCreateing = true; // 表示开始连接
|
||||
// myData.StartJunction = this;
|
||||
// myData.CurrentJunction = this;
|
||||
// myData.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
|
||||
|
||||
// var junctionOfConnectionType = this.JunctionType.ToConnectyionType();
|
||||
// ConnectionLineShape bezierLine; // 类别
|
||||
// Brush brushColor; // 临时线的颜色
|
||||
// if (junctionOfConnectionType == JunctionOfConnectionType.Invoke)
|
||||
// {
|
||||
// brushColor = ConnectionInvokeType.IsSucceed.ToLineColor();
|
||||
// }
|
||||
// else if (junctionOfConnectionType == JunctionOfConnectionType.Arg)
|
||||
// {
|
||||
// brushColor = ConnectionArgSourceType.GetOtherNodeData.ToLineColor();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// bezierLine = new ConnectionLineShape(LineType.Bezier,
|
||||
// myData.StartPoint,
|
||||
// myData.StartPoint,
|
||||
// brushColor,
|
||||
// isTop: true); // 绘制临时的线
|
||||
|
||||
// Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
|
||||
// myData.MyLine = new MyLine(canvas, bezierLine);
|
||||
// }
|
||||
//}
|
||||
//e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取起始控制点
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Point GetStartPoint()
|
||||
{
|
||||
if (this.GetTransformedBounds() is TransformedBounds transformed)
|
||||
{
|
||||
var size = transformed.Bounds.Size;
|
||||
return new Point(size.Width / 2, size.Height / 2); // 起始节点选择右侧边缘中心
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Point(0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Node.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点ViewModel基类
|
||||
/// </summary>
|
||||
internal abstract class NodeViewModelBase : ViewModelBase
|
||||
{
|
||||
internal abstract NodeModelBase NodeModelBase { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="100"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Node.Views.ActionNodeView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.Node.ViewModels"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
xmlns:dtp="using:Serein.Workbench.Avalonia.DataTemplates"
|
||||
|
||||
Background="#C6EEF7"
|
||||
x:DataType="vm:ActionNodeViewModel">
|
||||
<Design.DataContext>
|
||||
<vm:ActionNodeViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Border>
|
||||
<Grid RowDefinitions="25,*,*,*,*">
|
||||
|
||||
<!--调用控制点,方法名称,下一个方法调用控制点-->
|
||||
<Grid x:Name="HeaderGrid" Grid.Row="0" ColumnDefinitions="auto,*,auto" VerticalAlignment="Center">
|
||||
<cv:NodeJunctionView Grid.Column="0" JunctionType="Execute" MyNode="{Binding NodeMoel}" Width="30" Height="15" Margin="4,0,2,0" />
|
||||
<StackPanel Grid.Column="1" Grid.RowSpan="2" >
|
||||
<TextBlock Text="{Binding NodeMoel.DisplayName}" FontSize="17" HorizontalAlignment="Center">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel >
|
||||
<TextBlock Text="{Binding NodeMoel.MethodDetails}" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<cv:NodeJunctionView Grid.Column="2" JunctionType="NextStep" MyNode="{Binding NodeMoel}" Width="30" Height="15" Margin="2,0,8,0"/>
|
||||
</Grid>
|
||||
|
||||
<!--入参信息-->
|
||||
<StackPanel Grid.Row="1" Background="#E3FDFD">
|
||||
<ItemsControl ItemsSource="{Binding NodeMoel.MethodDetails.ParameterDetailss}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<dtp:NodeMethodParameterInfoDataTemplate>
|
||||
</dtp:NodeMethodParameterInfoDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Custom.Node.ViewModels;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Node.Views;
|
||||
|
||||
public partial class ActionNodeView : UserControl, INodeControl
|
||||
{
|
||||
private ActionNodeViewModel _vm;
|
||||
public ActionNodeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = App.GetService<ActionNodeViewModel>();
|
||||
DataContext = _vm;
|
||||
}
|
||||
|
||||
NodeModelBase INodeControl.NodeModelBase => _vm.NodeModelBase ?? throw new System.NotImplementedException(); // ¶¯×÷½Úµã
|
||||
|
||||
void INodeControl.SetNodeModel(NodeModelBase nodeModel) // ¶¯×÷½Úµã
|
||||
{
|
||||
_vm.NodeModelBase = nodeModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Collections;
|
||||
using System.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.ViewModels
|
||||
{
|
||||
|
||||
internal partial class FlowLibraryInfoViewModel:ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 依赖名称
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public string _libraryName;
|
||||
|
||||
private ObservableCollection<MethodDetailsInfo> activateMethods;
|
||||
private ObservableCollection<MethodDetailsInfo> flipflopMethods;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 动作节点
|
||||
/// </summary>
|
||||
public ObservableCollection<MethodDetailsInfo> ActivateMethods { get => activateMethods; set => SetProperty(ref activateMethods,value); }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器节点
|
||||
/// </summary>
|
||||
public ObservableCollection<MethodDetailsInfo> FlipflopMethods { get => flipflopMethods; set => SetProperty(ref activateMethods, value); }
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 加载项目信息
|
||||
///// </summary>
|
||||
///// <param name="libraryMds"></param>
|
||||
//public void LoadLibraryInfo(LibraryMds libraryMds)
|
||||
//{
|
||||
// this.AssemblyName = libraryMds.AssemblyName;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.ViewModels
|
||||
{
|
||||
|
||||
internal partial class FlowLibraryMethodInfoViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前预览的方法信息
|
||||
/// </summary>
|
||||
|
||||
[ObservableProperty]
|
||||
private MethodDetailsInfo methodDetailsInfo;
|
||||
|
||||
|
||||
private IWorkbenchEventService workbenchEventService;
|
||||
|
||||
public FlowLibraryMethodInfoViewModel()
|
||||
{
|
||||
workbenchEventService = App.GetService<IWorkbenchEventService>();
|
||||
workbenchEventService.OnPreviewlMethodInfo += WorkbenchEventService_OnPreviewlMethodInfo;
|
||||
methodDetailsInfo = new MethodDetailsInfo
|
||||
{
|
||||
AssemblyName = "wait selection...",
|
||||
MethodAnotherName = "wait selection...",
|
||||
MethodName = "wait selection...",
|
||||
NodeType = "wait selection...",
|
||||
ReturnTypeFullName = "wait selection...",
|
||||
IsParamsArgIndex = -1,
|
||||
ParameterDetailsInfos = []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private void WorkbenchEventService_OnPreviewlMethodInfo(PreviewlMethodInfoEventArgs eventArgs)
|
||||
{
|
||||
var mdInfo = eventArgs.MethodDetailsInfo;
|
||||
MethodDetailsInfo = mdInfo;
|
||||
Debug.WriteLine($"预览了 {mdInfo.AssemblyName } - {mdInfo.MethodAnotherName} 方法");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Newtonsoft.Json;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.ViewModels
|
||||
{
|
||||
internal partial class FlowLibrarysViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行环境
|
||||
/// </summary>
|
||||
private IFlowEnvironment flowEnvironment { get; }
|
||||
/// <summary>
|
||||
/// 流程运行环境事件服务
|
||||
/// </summary>
|
||||
private IFlowEEForwardingService feefService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境加载的依赖
|
||||
/// </summary>
|
||||
public ObservableCollection<LibraryMds> LibraryList { get; } = new ObservableCollection<LibraryMds>();
|
||||
|
||||
|
||||
|
||||
public FlowLibrarysViewModel()
|
||||
{
|
||||
flowEnvironment = App.GetService<IFlowEnvironment>();
|
||||
feefService = App.GetService<IFlowEEForwardingService>();
|
||||
feefService.OnDllLoad += FeefService_OnDllLoad; ;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载了依赖信息
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
private void FeefService_OnDllLoad(LoadDllEventArgs e)
|
||||
{
|
||||
Debug.WriteLine(e.NodeLibraryInfo.AssemblyName + " count :" + e.MethodDetailss.Count);
|
||||
var libraryMds = new LibraryMds { AssemblyName = e.NodeLibraryInfo.AssemblyName, Mds = e.MethodDetailss.ToArray() };
|
||||
LibraryList.Add(libraryMds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.ViewModels
|
||||
{
|
||||
internal partial class MainMenuBarViewModel : ViewModelBase
|
||||
{
|
||||
private IFlowEnvironment flowEnvironment { get; }
|
||||
|
||||
|
||||
public MainMenuBarViewModel()
|
||||
{
|
||||
flowEnvironment = App.GetService<IFlowEnvironment>();
|
||||
|
||||
var uiContextOperation = App.GetService<UIContextOperation>();
|
||||
}
|
||||
|
||||
public void SaveProjectCommand()
|
||||
{
|
||||
flowEnvironment.SaveProject();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Avalonia.Controls;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow.Env;
|
||||
using Serein.Workbench.Avalonia.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.ViewModels
|
||||
{
|
||||
internal class NodeContainerViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 正在创建方法调用关系的连接线
|
||||
/// </summary>
|
||||
public bool IsConnectionInvokeNode { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 正在创建参数传递关系的连接线
|
||||
/// </summary>
|
||||
public bool IsConnectionArgSourceNode { get; set; } = false;
|
||||
public NodeContainerViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
217
Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs
Normal file
217
Serein.Workbench.Avalonia/Custom/Views/ConnectionLineShape.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Skia;
|
||||
using Avalonia.Styling;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Workbench.Avalonia.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views
|
||||
{
|
||||
public class ConnectionLineShape : Control
|
||||
{
|
||||
private readonly double strokeThickness;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 确定起始坐标和目标坐标、外光样式的曲线
|
||||
/// </summary>
|
||||
/// <param name="start">起始坐标</param>
|
||||
/// <param name="end">结束坐标</param>
|
||||
/// <param name="brush">颜色</param>
|
||||
/// <param name="isDotted">是否为虚线</param>
|
||||
public ConnectionLineShape(Point start,
|
||||
Point end,
|
||||
Brush brush,
|
||||
bool isDotted = false,
|
||||
bool isTop = false)
|
||||
{
|
||||
this.brush = brush;
|
||||
startPoint = start;
|
||||
endPoint = end;
|
||||
this.strokeThickness = 4;
|
||||
InitElementPoint(isDotted, isTop);
|
||||
InvalidateVisual(); // 触发重绘
|
||||
}
|
||||
|
||||
|
||||
public void InitElementPoint(bool isDotted, bool isTop = false)
|
||||
{
|
||||
//hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
|
||||
//hitVisiblePen.Freeze(); // Freeze以提高性能
|
||||
visualPen = new Pen(brush, 3.0); // 默认可视化Pen
|
||||
opacity = 1.0d;
|
||||
var dashStyle = new DashStyle();
|
||||
|
||||
if (isDotted)
|
||||
{
|
||||
opacity = 0.42d;
|
||||
visualPen.DashStyle = new DashStyle(); // DashStyles.Dash; // 选择虚线样式
|
||||
}
|
||||
//visualPen.Freeze(); // Freeze以提高性能
|
||||
|
||||
linkSize = 4; // 整线条粗细
|
||||
int zIndex = 999999;
|
||||
|
||||
this.ZIndex = zIndex;
|
||||
//Panel.SetZIndex(this, zIndex); // 置底
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新线条落点位置
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
public void UpdatePoints(Point start, Point end)
|
||||
{
|
||||
startPoint = start;
|
||||
endPoint = end;
|
||||
InvalidateVisual(); // 触发重绘
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新线条落点位置
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
public void UpdateEndPoints(Point point)
|
||||
{
|
||||
endPoint = point;
|
||||
InvalidateVisual(); // 触发重绘
|
||||
}
|
||||
/// <summary>
|
||||
/// 更新线条起点位置
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
public void UpdateStartPoints(Point point)
|
||||
{
|
||||
startPoint = point;
|
||||
InvalidateVisual(); // 触发重绘
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件重绘事件
|
||||
/// </summary>
|
||||
/// <param name="drawingContext"></param>
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
// 刷新线条显示位置
|
||||
DrawBezierCurve(drawingContext, startPoint, endPoint);
|
||||
|
||||
}
|
||||
#region 重绘
|
||||
|
||||
private StreamGeometry streamGeometry = new StreamGeometry();
|
||||
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
|
||||
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
|
||||
//private Pen hitVisiblePen; // 初始化碰撞检测线
|
||||
private Pen visualPen; // 默认可视化Pen
|
||||
private Point startPoint; // 连接线的起始节点
|
||||
private Point endPoint; // 连接线的终点
|
||||
private Brush brush; // 线条颜色
|
||||
private double opacity; // 透明度
|
||||
|
||||
double linkSize; // 根据缩放比例调整线条粗细
|
||||
|
||||
//public void UpdateLineColor()
|
||||
//{
|
||||
// visualPen = new Pen(brush, 3.0); // 默认可视化Pen
|
||||
// InvalidateVisual(); // 触发重绘
|
||||
//}
|
||||
|
||||
|
||||
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
|
||||
private Vector axis = new Vector(1, 0);
|
||||
private Vector startToEnd;
|
||||
private int i = 0;
|
||||
private void DrawBezierCurve(DrawingContext drawingContext,
|
||||
Point start,
|
||||
Point end)
|
||||
{
|
||||
// 控制点的计算逻辑
|
||||
double power = 140; // 控制贝塞尔曲线的“拉伸”强度
|
||||
drawingContext.PushOpacity(opacity);
|
||||
|
||||
// 计算轴向向量与起点到终点的向量
|
||||
//var axis = new Vector(1, 0);
|
||||
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
|
||||
|
||||
|
||||
|
||||
//var dp = axis.DotProduct(startToEnd);
|
||||
//dp = dp < 50 ? 50 : dp;
|
||||
//var pow = Math.Max(0, dp) ;
|
||||
//var k = 1 - Math.Pow(pow, 10.0);
|
||||
//
|
||||
//Debug.WriteLine("pow : " + pow);
|
||||
//Debug.WriteLine("k : " + k);
|
||||
//Debug.WriteLine("");
|
||||
|
||||
// 计算拉伸程度k,拉伸与水平夹角正相关
|
||||
var dp = axis.DotProduct(startToEnd) ;
|
||||
var pow = Math.Pow(dp, 10.0);
|
||||
pow = pow > 0 ? 0 : pow;
|
||||
var k = 1 - pow;
|
||||
// 如果起点x大于终点x,增加额外的偏移量,避免重叠
|
||||
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
|
||||
// 控制点的实际计算
|
||||
c0 = new Point(+(power + bias) * k + start.X, start.Y);
|
||||
c1 = new Point(-(power + bias) * k + end.X, end.Y);
|
||||
|
||||
// 准备StreamGeometry以用于绘制曲线
|
||||
// why can't clearValue()?
|
||||
//streamGeometry.ClearValue(ThemeProperty);
|
||||
//var streamGeometry = new StreamGeometry();
|
||||
//if( i++ > 100 && streamGeometry is AvaloniaObject avaloniaObject)
|
||||
//{
|
||||
// var platformImplInfo = streamGeometry.GetType().GetProperty("PlatformImpl", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
// var platformImpl = platformImplInfo?.GetValue(streamGeometry);
|
||||
|
||||
// var pathCache = platformImpl?.GetType().GetField("_bounds", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
// if(pathCache is IDisposable disposable)
|
||||
// {
|
||||
// disposable.Dispose();
|
||||
// }
|
||||
// //pathCache?.Invoke(platformImpl, []);
|
||||
// Debug.WriteLine("invoke => InvalidateCaches");
|
||||
// i = 0;
|
||||
// //public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged
|
||||
// //private readonly ValueStore _values;
|
||||
//}
|
||||
|
||||
// this is override "Render()" method
|
||||
// display a bezier-line on canvas and follow the mouse in real time
|
||||
// I don't want to re-instantiate StreamGeometry()
|
||||
// because I want to reduce the number of GC
|
||||
// but , it seems impossible to do so in avalonia
|
||||
streamGeometry = new StreamGeometry();
|
||||
// in wpf , this method allows display content to be cleared
|
||||
// streamGeometry.Clear(); // this is wpf method
|
||||
// in avalonia, why does this method need value of "AvaloniaProperty" data type ?
|
||||
// but I don't know use what "AvaloniaProperty" to clear the displayed content
|
||||
// if I don't clear the cache or re-instantiate it
|
||||
// the canvas will display repeated lines , because exits cache inside streamGeometry
|
||||
// streamGeometry.ClearValue("AvaloniaProperty");
|
||||
using (var context = streamGeometry.Open())
|
||||
{
|
||||
context.BeginFigure(start, true); // start point of the bezier-line
|
||||
context.CubicBezierTo(c0, c1, end, true); // drawing bezier-line
|
||||
}
|
||||
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
xmlns:cv="using:Serein.Workbench.Avalonia.Custom.Views"
|
||||
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
|
||||
xmlns:dtp="using:Serein.Workbench.Avalonia.DataTemplates"
|
||||
>
|
||||
|
||||
<!--预览-->
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Width="400" Spacing="10">
|
||||
<StackPanel Background="{DynamicResource SystemRegionBrush}">
|
||||
<cv:FlowLibraryInfoView />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<ControlTheme x:Key="{x:Type cv:FlowLibraryInfoView}" TargetType="cv:FlowLibraryInfoView">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<StackPanel>
|
||||
<!--类库名称-->
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" Margin="2">
|
||||
<TextBlock Text="library : " FontSize="18"></TextBlock>
|
||||
<TextBlock Text="{TemplateBinding LibraryName}" FontSize="18"></TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!--Action Method Info-->
|
||||
<!--动作节点方法信息-->
|
||||
<ListBox x:Name="PART_ActionMethodInfos" ItemsSource="{TemplateBinding ActionMethods}" Background="#F1FBFB">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<!--use custom DataTemplate create items -->
|
||||
<!--使用自定义模板创建子项控件-->
|
||||
<dtp:LibraryMethodInfoDataTemplate>
|
||||
</dtp:LibraryMethodInfoDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!--Flipflop Method Info-->
|
||||
<!--触发器节点方法信息-->
|
||||
<ListBox x:Name="PART_FlipflopMethodInfos" ItemsSource="{TemplateBinding FlipflopMethods}" Background="#FBF8F1">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<dtp:LibraryMethodInfoDataTemplate>
|
||||
</dtp:LibraryMethodInfoDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.VisualTree;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
public partial class FlowLibraryInfoView : TemplatedControl
|
||||
{
|
||||
|
||||
private IWorkbenchEventService workbenchEventService;
|
||||
|
||||
public FlowLibraryInfoView()
|
||||
{
|
||||
workbenchEventService = App.GetService<IWorkbenchEventService>();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
// 如果改变的属性是 MdsProperty ,则加载方法信息
|
||||
if (change.Property == MdsProperty)
|
||||
{
|
||||
if(change.NewValue is MethodDetailsInfo[] value)
|
||||
{
|
||||
onNext(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取到控件信息
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
#region 动作节点方法信息
|
||||
if (e.NameScope.Find("PART_ActionMethodInfos") is ListBox p_am)
|
||||
{
|
||||
p_am.AddHandler(InputElement.PointerExitedEvent, ListBox_PointerExited);
|
||||
p_am.AddHandler(SelectingItemsControl.SelectionChangedEvent, ListBox_SelectionChanged);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region 触发器节点方法信息
|
||||
if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm)
|
||||
{
|
||||
p_fm.SelectionChanged += ListBox_SelectionChanged;
|
||||
p_fm.PointerExited += ListBox_PointerExited;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
private void ListBox_SelectionChanged(object? o, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (o is ListBox listBox && listBox.SelectedIndex > 0 && listBox.SelectedItem is MethodDetailsInfo mdInfo)
|
||||
{
|
||||
workbenchEventService.PreviewLibraryMethodInfo(mdInfo); // 通知其它地方预览了某个方法信息
|
||||
}
|
||||
}
|
||||
private void ListBox_PointerExited(object? o, PointerEventArgs e)
|
||||
{
|
||||
if (o is ListBox listBox && listBox.SelectedIndex > -1)
|
||||
{
|
||||
listBox.SelectedIndex = -1; // 如果鼠标离开了,取消已选状态
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将信息加载出来
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
private void onNext(MethodDetailsInfo[] value)
|
||||
{
|
||||
if(value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var fmd = value.Where(item => nameof(NodeType.Flipflop).Equals(item.NodeType));
|
||||
FlipflopMethods = new ObservableCollection<MethodDetailsInfo>(fmd);
|
||||
var amd = value.Where(item => nameof(NodeType.Action).Equals(item.NodeType));
|
||||
ActionMethods = new ObservableCollection<MethodDetailsInfo>(amd);
|
||||
}
|
||||
|
||||
|
||||
#region Template Public Property / 控件公开属性
|
||||
public static readonly DirectProperty<FlowLibraryInfoView, string> LibraryNameProperty =
|
||||
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, string>(nameof(LibraryName), o => o.LibraryName, (o, v) => o.LibraryName = v);
|
||||
public static readonly DirectProperty<FlowLibraryInfoView, MethodDetailsInfo[]> MdsProperty =
|
||||
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, MethodDetailsInfo[]>(nameof(Mds), o => o.Mds, (o, v) => o.Mds = v);
|
||||
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> ActionMethodsProperty =
|
||||
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(ActionMethods), o => o.ActionMethods, (o, v) => o.ActionMethods = v);
|
||||
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> FlipflopMethodsProperty =
|
||||
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(FlipflopMethods), o => o.FlipflopMethods, (o, v) => o.FlipflopMethods = v);
|
||||
|
||||
private string libraryName = string.Empty;
|
||||
private ObservableCollection<MethodDetailsInfo> actionMethods;
|
||||
private ObservableCollection<MethodDetailsInfo> flipflopMethods;
|
||||
private MethodDetailsInfo[] mds = [];
|
||||
|
||||
|
||||
public string LibraryName
|
||||
{
|
||||
get { return libraryName; }
|
||||
set { SetAndRaise(LibraryNameProperty, ref libraryName, value); }
|
||||
}
|
||||
|
||||
/*
|
||||
public static readonly AttachedProperty<string> LibraryName2Property = AvaloniaProperty.RegisterAttached<FlowLibraryInfoView, Control, string>("LibraryName2");
|
||||
|
||||
public static string GetLibraryName2(Control element)
|
||||
{
|
||||
return element.GetValue(LibraryName2Property);
|
||||
}
|
||||
|
||||
public static void SetLibraryName2(Control element, string value)
|
||||
{
|
||||
element.SetValue(LibraryName2Property, value);
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Method Info
|
||||
/// 方法信息
|
||||
/// </summary>
|
||||
public MethodDetailsInfo[] Mds
|
||||
{
|
||||
get { return mds; }
|
||||
set
|
||||
{
|
||||
SetAndRaise(MdsProperty, ref mds, value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 动作节点的方法
|
||||
/// </summary>
|
||||
public ObservableCollection<MethodDetailsInfo> ActionMethods
|
||||
{
|
||||
get { return actionMethods; }
|
||||
set
|
||||
{
|
||||
SetAndRaise(ActionMethodsProperty, ref actionMethods, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发器节点的方法
|
||||
/// </summary>
|
||||
public ObservableCollection<MethodDetailsInfo> FlipflopMethods
|
||||
{
|
||||
get { return flipflopMethods; }
|
||||
set
|
||||
{
|
||||
|
||||
SetAndRaise(FlipflopMethodsProperty, ref flipflopMethods, value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public class ItemsChangeObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
|
||||
{
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
RegisterPropertyChanged(e.NewItems);
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
UnRegisterPropertyChanged(e.OldItems);
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Replace)
|
||||
{
|
||||
UnRegisterPropertyChanged(e.OldItems);
|
||||
RegisterPropertyChanged(e.NewItems);
|
||||
}
|
||||
|
||||
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
UnRegisterPropertyChanged(this);
|
||||
base.ClearItems();
|
||||
}
|
||||
private void RegisterPropertyChanged(IList items)
|
||||
{
|
||||
foreach (INotifyPropertyChanged item in items)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void UnRegisterPropertyChanged(IList items)
|
||||
{
|
||||
foreach (INotifyPropertyChanged item in items)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
//launch an event Reset with name of property changed
|
||||
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibraryMethodInfoView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
x:DataType="vm:FlowLibraryMethodInfoViewModel">
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:FlowLibraryMethodInfoViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="StackPanel">
|
||||
<Setter Property="Margin" Value="2,1,2,1" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="节点类型 - "/>
|
||||
<TextBlock Text="{Binding MethodDetailsInfo.NodeType}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="方法描述 - "/>
|
||||
<TextBlock Text="{Binding MethodDetailsInfo.MethodAnotherName}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="方法名称 - "/>
|
||||
<TextBlock Text="{Binding MethodDetailsInfo.MethodName}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text=" 返 回 值 - "/>
|
||||
<TextBlock Text="{Binding MethodDetailsInfo.ReturnTypeFullName}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,50 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Custom.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Custom.Views;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
public partial class FlowLibraryMethodInfoView : UserControl
|
||||
{
|
||||
private FlowLibraryMethodInfoViewModel _vm;
|
||||
public FlowLibraryMethodInfoView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = App.GetService<FlowLibraryMethodInfoViewModel>();
|
||||
DataContext = _vm;
|
||||
//this.PointerPressed += FlowLibraryMethodInfoView_PointerPressed;
|
||||
}
|
||||
|
||||
//private async void FlowLibraryMethodInfoView_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
//{
|
||||
// if (_vm.MethodDetailsInfo is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// DataObject dragData = new DataObject();
|
||||
// dragData.Set(DataFormats.Text, $"{_vm.MethodDetailsInfo.MethodAnotherName}");
|
||||
// var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
|
||||
// Debug.WriteLine("DoDrag :" + result);
|
||||
// switch (result)
|
||||
// {
|
||||
// case DragDropEffects.Copy:
|
||||
// Debug.WriteLine("文本来自 Copy");
|
||||
// break;
|
||||
// case DragDropEffects.Link:
|
||||
// Debug.WriteLine("文本来自 Link");
|
||||
// break;
|
||||
// case DragDropEffects.None:
|
||||
// Debug.WriteLine("拖拽操作被取消");
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibrarysView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
x:DataType="vm:FlowLibrarysViewModel"
|
||||
>
|
||||
<Design.DataContext>
|
||||
<vm:FlowLibrarysViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Resources>
|
||||
<cv:FlowLibraryInfoView x:Key="FlowLibraryInfoView">
|
||||
</cv:FlowLibraryInfoView>
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- , DataType={x:Type vm:FlowLibrarysViewModel} -->
|
||||
<!-- x:DataType="baselibrary:LibraryMds" -->
|
||||
<!-- LibraryInfo="{Binding}"-->
|
||||
<ScrollViewer HorizontalAlignment="Left" VerticalAlignment="Top">
|
||||
<!--Displays dependecy information loaded from runtime environment-->
|
||||
<!--显示从运行环境加载的所有依赖信息-->
|
||||
<ItemsControl ItemsSource="{Binding LibraryList}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="baselibrary:LibraryMds" >
|
||||
<cv:FlowLibraryInfoView LibraryName="{Binding AssemblyName}" Mds="{Binding Mds}"/>
|
||||
<!--<StackPanel Background="{DynamicResource SystemRegionBrush}" Margin="2,2,2,8">
|
||||
|
||||
</StackPanel>-->
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Custom.ViewModels;
|
||||
using System;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
public partial class FlowLibrarysView : UserControl
|
||||
{
|
||||
public FlowLibrarysView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = App.GetService<FlowLibrarysViewModel>();
|
||||
}
|
||||
}
|
||||
43
Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml
Normal file
43
Serein.Workbench.Avalonia/Custom/Views/MainMenuBarView.axaml
Normal file
@@ -0,0 +1,43 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Views.MainMenuBarView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
|
||||
x:DataType="vm:MainMenuBarViewModel">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="MenuItem">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:MainMenuBarViewModel />
|
||||
</Design.DataContext>
|
||||
<StackPanel HorizontalAlignment="Center" >
|
||||
<StackPanel.Resources>
|
||||
<SolidColorBrush x:Key="MenuFlyoutBackground">#FFFFFF</SolidColorBrush>
|
||||
</StackPanel.Resources>
|
||||
<Menu Background="Transparent">
|
||||
<MenuItem Header="项目">
|
||||
<MenuItem Header="保存项目" Command="{Binding SaveProjectCommand}"/>
|
||||
<MenuItem Header="打开本地项目"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="调试">
|
||||
<MenuItem Header="运行(从起始节点)"/>
|
||||
<MenuItem Header="运行(从当前节点)"/>
|
||||
<MenuItem Header="结束流程"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="视图">
|
||||
<MenuItem Header="输出窗口"/>
|
||||
<MenuItem Header="重置画布"/>
|
||||
<MenuItem Header="定位节点"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="远程">
|
||||
<MenuItem Header="启动远程服务" />
|
||||
<MenuItem Header="连接远程环境" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,16 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Serein.Workbench.Avalonia.Custom.ViewModels;
|
||||
using System;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
public partial class MainMenuBarView : UserControl
|
||||
{
|
||||
public MainMenuBarView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = App.GetService<MainMenuBarViewModel>();
|
||||
}
|
||||
}
|
||||
142
Serein.Workbench.Avalonia/Custom/Views/MatrixHelper.cs
Normal file
142
Serein.Workbench.Avalonia/Custom/Views/MatrixHelper.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using static System.Math;
|
||||
|
||||
namespace Avalonia.Controls.PanAndZoom;
|
||||
|
||||
/// <summary>
|
||||
/// Avalonia Matrix helper methods.
|
||||
/// </summary>
|
||||
public static class MatrixHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a translation matrix using the specified offsets.
|
||||
/// </summary>
|
||||
/// <param name="offsetX">X-coordinate offset.</param>
|
||||
/// <param name="offsetY">Y-coordinate offset.</param>
|
||||
/// <returns>The created translation matrix.</returns>
|
||||
public static Matrix Translate(double offsetX, double offsetY)
|
||||
{
|
||||
return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepends a translation around the center of provided matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to prepend translation.</param>
|
||||
/// <param name="offsetX">X-coordinate offset.</param>
|
||||
/// <param name="offsetY">Y-coordinate offset.</param>
|
||||
/// <returns>The created translation matrix.</returns>
|
||||
public static Matrix TranslatePrepend(Matrix matrix, double offsetX, double offsetY)
|
||||
{
|
||||
return Translate(offsetX, offsetY) * matrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix that scales along the x-axis and y-axis.
|
||||
/// </summary>
|
||||
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
|
||||
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
|
||||
/// <returns>The created scaling matrix.</returns>
|
||||
public static Matrix Scale(double scaleX, double scaleY)
|
||||
{
|
||||
return new Matrix(scaleX, 0, 0, scaleY, 0.0, 0.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix that is scaling from a specified center.
|
||||
/// </summary>
|
||||
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
|
||||
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
|
||||
/// <param name="centerX">The center X-coordinate of the scaling.</param>
|
||||
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
|
||||
/// <returns>The created scaling matrix.</returns>
|
||||
public static Matrix ScaleAt(double scaleX, double scaleY, double centerX, double centerY)
|
||||
{
|
||||
return new Matrix(scaleX, 0, 0, scaleY, centerX - (scaleX * centerX), centerY - (scaleY * centerY));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepends a scale around the center of provided matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to prepend scale.</param>
|
||||
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
|
||||
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
|
||||
/// <param name="centerX">The center X-coordinate of the scaling.</param>
|
||||
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
|
||||
/// <returns>The created scaling matrix.</returns>
|
||||
public static Matrix ScaleAtPrepend(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY)
|
||||
{
|
||||
return ScaleAt(scaleX, scaleY, centerX, centerY) * matrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation and scale matrix using the specified offsets and scales along the x-axis and y-axis.
|
||||
/// </summary>
|
||||
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
|
||||
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
|
||||
/// <param name="offsetX">X-coordinate offset.</param>
|
||||
/// <param name="offsetY">Y-coordinate offset.</param>
|
||||
/// <returns>The created translation and scale matrix.</returns>
|
||||
public static Matrix ScaleAndTranslate(double scaleX, double scaleY, double offsetX, double offsetY)
|
||||
{
|
||||
return new Matrix(scaleX, 0.0, 0.0, scaleY, offsetX, offsetY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix.
|
||||
/// </summary>
|
||||
/// <param name="angleX">Angle of skew along the X-axis in radians.</param>
|
||||
/// <param name="angleY">Angle of skew along the Y-axis in radians.</param>
|
||||
/// <returns>When the method completes, contains the created skew matrix.</returns>
|
||||
public static Matrix Skew(float angleX, float angleY)
|
||||
{
|
||||
return new Matrix(1.0, Tan(angleX), Tan(angleY), 1.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix that rotates.
|
||||
/// </summary>
|
||||
/// <param name="radians">Angle of rotation in radians. Angles are measured clockwise when looking along the rotation axis.</param>
|
||||
/// <returns>The created rotation matrix.</returns>
|
||||
public static Matrix Rotation(double radians)
|
||||
{
|
||||
double cos = Cos(radians);
|
||||
double sin = Sin(radians);
|
||||
return new Matrix(cos, sin, -sin, cos, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix that rotates about a specified center.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
/// <param name="centerX">The center X-coordinate of the rotation.</param>
|
||||
/// <param name="centerY">The center Y-coordinate of the rotation.</param>
|
||||
/// <returns>The created rotation matrix.</returns>
|
||||
public static Matrix Rotation(double angle, double centerX, double centerY)
|
||||
{
|
||||
return Translate(-centerX, -centerY) * Rotation(angle) * Translate(centerX, centerY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix that rotates about a specified center.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
/// <param name="center">The center of the rotation.</param>
|
||||
/// <returns>The created rotation matrix.</returns>
|
||||
public static Matrix Rotation(double angle, Vector center)
|
||||
{
|
||||
return Translate(-center.X, -center.Y) * Rotation(angle) * Translate(center.X, center.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a point by this matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to use as a transformation matrix.</param>
|
||||
/// <param name="point">>The original point to apply the transformation.</param>
|
||||
/// <returns>The result of the transformation for the input point.</returns>
|
||||
public static Point TransformPoint(Matrix matrix, Point point)
|
||||
{
|
||||
return new Point(
|
||||
(point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.M31,
|
||||
(point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.M32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Views.NodeContainerView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
x:DataType="vm:NodeContainerViewModel"
|
||||
Background="#F8FBF1"
|
||||
DragDrop.AllowDrop="True">
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:NodeContainerViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<DockPanel>
|
||||
<!--放置节点-->
|
||||
<Canvas x:Name="PART_NodeContainer">
|
||||
</Canvas>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,427 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.PanAndZoom;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Custom.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Extension;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using Point = Avalonia.Point;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
public partial class NodeContainerView : UserControl
|
||||
{
|
||||
private readonly NodeContainerViewModel _vm;
|
||||
private readonly INodeOperationService nodeOperationService;
|
||||
private readonly IKeyEventService keyEventService;
|
||||
|
||||
#region 与画布相关的字段
|
||||
/// <summary>
|
||||
/// 是否正在预览节点控件
|
||||
/// </summary>
|
||||
private bool IsPreviewNodeControl;
|
||||
/// <summary>
|
||||
/// 标记是否正在尝试选取控件
|
||||
/// </summary>
|
||||
private bool IsSelectControl;
|
||||
/// <summary>
|
||||
/// 标记是否正在拖动控件
|
||||
/// </summary>
|
||||
private bool IsControlDragging;
|
||||
/// <summary>
|
||||
/// 标记是否正在拖动画布
|
||||
/// </summary>
|
||||
private bool IsCanvasDragging;
|
||||
/// <summary>
|
||||
/// 标记是否正在选取节点
|
||||
/// </summary>
|
||||
private bool IsSelectDragging;
|
||||
/// <summary>
|
||||
/// 当前选取的控件
|
||||
/// </summary>
|
||||
private readonly List<Control> selectNodeControls = [];
|
||||
|
||||
/// <summary>
|
||||
/// 记录开始拖动节点控件时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startControlDragPoint;
|
||||
/// <summary>
|
||||
/// 记录移动画布开始时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startCanvasDragPoint;
|
||||
/// <summary>
|
||||
/// 记录开始选取节点控件时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startSelectControolPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 组合变换容器
|
||||
/// </summary>
|
||||
private readonly TransformGroup canvasTransformGroup;
|
||||
/// <summary>
|
||||
/// 缩放画布
|
||||
/// </summary>
|
||||
private readonly ScaleTransform scaleTransform;
|
||||
/// <summary>
|
||||
/// 平移画布
|
||||
/// </summary>
|
||||
private readonly TranslateTransform translateTransform;
|
||||
#endregion
|
||||
|
||||
|
||||
public NodeContainerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm= App.GetService<NodeContainerViewModel>();
|
||||
DataContext = _vm;
|
||||
|
||||
#region 获取UI相关的服务
|
||||
keyEventService = App.GetService<IKeyEventService>();
|
||||
nodeOperationService = App.GetService<INodeOperationService>();
|
||||
nodeOperationService.MainCanvas = PART_NodeContainer;
|
||||
nodeOperationService.OnNodeViewCreate += NodeOperationService_OnNodeViewCreate; // 处理事件
|
||||
keyEventService.KeyUp += (k) =>
|
||||
{
|
||||
if (k == Key.Escape)
|
||||
{
|
||||
IsCanvasDragging = false;
|
||||
IsControlDragging = false;
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region 设置UI事件
|
||||
AddHandler(DragDrop.DropEvent, Drop); // 创建节点相关
|
||||
|
||||
|
||||
PointerPressed += NodeContainerView_PointerPressed;
|
||||
PointerReleased += NodeContainerView_PointerReleased;
|
||||
PointerMoved += NodeContainerView_PointerMoved;
|
||||
PointerWheelChanged += NodeContainerView_PointerWheelChanged;
|
||||
#endregion
|
||||
|
||||
#region 初始化画布动画容器
|
||||
canvasTransformGroup = new TransformGroup();
|
||||
scaleTransform = new ScaleTransform();
|
||||
translateTransform = new TranslateTransform();
|
||||
canvasTransformGroup.Children.Add(scaleTransform);
|
||||
canvasTransformGroup.Children.Add(translateTransform);
|
||||
PART_NodeContainer.RenderTransform = canvasTransformGroup;
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region 工具方法
|
||||
|
||||
public Point GetPositionOfCanvas(PointerEventArgs e)
|
||||
{
|
||||
return e.GetPosition(PART_NodeContainer);
|
||||
}
|
||||
public Point GetPositionOfCanvas(DragEventArgs e)
|
||||
{
|
||||
return e.GetPosition(PART_NodeContainer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 画布的移动、缩放、框选,以及拖拽事件
|
||||
|
||||
#region 响应拖拽事件
|
||||
private void Drop(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.Contains(DataFormats.Text))
|
||||
{
|
||||
var json = e.Data.GetText();
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var mdInfo = json.ToJsonObject<MethodDetailsInfo>();
|
||||
if (mdInfo is not null)
|
||||
{
|
||||
var canvasDropPosition = GetPositionOfCanvas(e); // 更新画布落点
|
||||
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
|
||||
nodeOperationService.CreateNodeView(mdInfo, position); // 提交创建节点的请求
|
||||
}
|
||||
|
||||
}
|
||||
else // if (e.Data.Contains(DataFormats.FileNames))
|
||||
{
|
||||
var files = e.Data.GetFiles();
|
||||
var str = files?.Select(f => f.Path);
|
||||
if (str is not null)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 拖动画布
|
||||
private void NodeContainerView_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (IsPreviewNodeControl)
|
||||
{
|
||||
IsCanvasDragging = false;
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
if (!IsCanvasDragging)
|
||||
{
|
||||
IsCanvasDragging = true;
|
||||
startCanvasDragPoint = e.GetPosition(this);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
private void NodeContainerView_PointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
IsCanvasDragging = false; // 不再拖动
|
||||
}
|
||||
|
||||
private void NodeContainerView_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
|
||||
var myData = nodeOperationService.ConnectingData;
|
||||
if (myData.IsCreateing)
|
||||
{
|
||||
var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsLeftButtonPressed);
|
||||
//Debug.WriteLine("canvas ispass = " + isPass);
|
||||
if (isPass)
|
||||
{
|
||||
if (myData.Type == JunctionOfConnectionType.Invoke)
|
||||
{
|
||||
_vm.IsConnectionInvokeNode = true; // 正在连接节点的调用关系
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_vm.IsConnectionArgSourceNode = true; // 正在连接节点的调用关系
|
||||
}
|
||||
var currentPoint = e.GetPosition(PART_NodeContainer);
|
||||
myData.UpdatePoint(new Point(currentPoint.X - 5, currentPoint.Y - 5));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (IsCanvasDragging)
|
||||
{
|
||||
// 拖动画布
|
||||
Point currentMousePosition = e.GetPosition(this);
|
||||
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
|
||||
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
|
||||
translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
startCanvasDragPoint = currentMousePosition;
|
||||
}
|
||||
}
|
||||
|
||||
// 缩放
|
||||
private void NodeContainerView_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
var delta = e.Delta.Y;
|
||||
if (delta < 0 && scaleTransform.ScaleX < 0.02) return;
|
||||
if (delta > 0 && scaleTransform.ScaleY > 4.0) return;
|
||||
|
||||
// 缩放因子,根据滚轮方向调整
|
||||
double zoomFactor = delta > 0 ? 1.23 : 0.78;
|
||||
|
||||
// 当前缩放比例
|
||||
double oldScale = scaleTransform.ScaleX;
|
||||
double newScale = oldScale * zoomFactor;
|
||||
|
||||
// 记录缩放前的鼠标位置
|
||||
var mousePosition = GetPositionOfCanvas(e);
|
||||
|
||||
// 更新缩放比例
|
||||
scaleTransform.ScaleX = newScale;
|
||||
scaleTransform.ScaleY = newScale;
|
||||
|
||||
// 记录缩放后的鼠标位置
|
||||
var newMousePosition = GetPositionOfCanvas(e);
|
||||
|
||||
// 更新 TranslateTransform,确保以鼠标位置为中心进行缩放
|
||||
var s_position = newMousePosition - mousePosition; // 计算偏移量
|
||||
translateTransform.X += s_position.X * newScale; // 根据缩放比例进行偏移
|
||||
translateTransform.Y += s_position.Y * newScale; // 根据缩放比例进行偏移
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 节点事件处理相关方法
|
||||
/// <summary>
|
||||
/// 拖拽创建控件
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <returns></returns>
|
||||
private bool NodeOperationService_OnNodeViewCreate(NodeViewCreateEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.NodeControl is not Control control)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var position = eventArgs.Position;// 坐标
|
||||
SetNodeEvent(control); // 设置该控件与画布交互的相关事件
|
||||
|
||||
DragControl(control, position.X, position.Y);
|
||||
PART_NodeContainer.Children.Add(control);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置节点与画布容器相关的操作事件
|
||||
/// </summary>
|
||||
/// <param name="nodeControl"></param>
|
||||
private void SetNodeEvent(Control nodeControl)
|
||||
{
|
||||
nodeControl.PointerMoved += NodeControl_PointerMoved; ;
|
||||
nodeControl.PointerExited += NodeControl_PointerExited;
|
||||
nodeControl.PointerPressed += Block_MouseLeftButtonDown;
|
||||
nodeControl.PointerMoved += Block_MouseMove;
|
||||
nodeControl.PointerReleased += (s, e) => IsControlDragging = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 控件交互的相关方法
|
||||
|
||||
/// <summary>
|
||||
/// 移动控件
|
||||
/// </summary>
|
||||
/// <param name="nodeControl"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
private void DragControl(Control nodeControl, double x, double y)
|
||||
{
|
||||
Canvas.SetLeft(nodeControl, x);
|
||||
Canvas.SetTop(nodeControl, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件的鼠标左键按下事件,启动拖动操作。
|
||||
/// </summary>
|
||||
private void Block_MouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsRightButtonPressed);
|
||||
if (!isPass)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender is Control nodeControl)
|
||||
{
|
||||
IsControlDragging = true;
|
||||
startControlDragPoint = GetPositionOfCanvas(e); // 记录鼠标按下时的位置
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
|
||||
/// </summary>
|
||||
private void Block_MouseMove(object? sender, PointerEventArgs e)
|
||||
{
|
||||
|
||||
if (sender is not Control nodeControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsCanvasDragging)
|
||||
return;
|
||||
if (IsSelectControl)
|
||||
return;
|
||||
|
||||
if (IsControlDragging) // 如果正在拖动控件
|
||||
{
|
||||
Point currentPosition = GetPositionOfCanvas(e); // 获取当前鼠标位置
|
||||
|
||||
// 单个移动
|
||||
if (selectNodeControls.Count == 0 || !selectNodeControls.Contains(nodeControl))
|
||||
{
|
||||
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
|
||||
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
|
||||
double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距
|
||||
double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距
|
||||
DragControl(nodeControl, newLeft, newTop);
|
||||
}
|
||||
// 批量移动
|
||||
else
|
||||
{
|
||||
// 进行批量移动
|
||||
// 获取旧位置
|
||||
var oldLeft = Canvas.GetLeft(nodeControl);
|
||||
var oldTop = Canvas.GetTop(nodeControl);
|
||||
|
||||
// 计算被选择控件的偏移量
|
||||
var deltaX = /*(int)*/(currentPosition.X - startControlDragPoint.X);
|
||||
var deltaY = /*(int)*/(currentPosition.Y - startControlDragPoint.Y);
|
||||
|
||||
// 移动被选择的控件
|
||||
var newLeft = oldLeft + deltaX;
|
||||
var newTop = oldTop + deltaY;
|
||||
|
||||
//this.EnvDecorator.MoveNode(nodeControlMain.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点
|
||||
DragControl(nodeControl, newLeft, newTop);
|
||||
// 计算控件实际移动的距离
|
||||
var actualDeltaX = newLeft - oldLeft;
|
||||
var actualDeltaY = newTop - oldTop;
|
||||
|
||||
// 移动其它选中的控件
|
||||
foreach (var selectItemNode in selectNodeControls)
|
||||
{
|
||||
if (selectItemNode != nodeControl) // 跳过已经移动的控件
|
||||
{
|
||||
var otherNewLeft = Canvas.GetLeft(selectItemNode) + actualDeltaX;
|
||||
var otherNewTop = Canvas.GetTop(selectItemNode) + actualDeltaY;
|
||||
DragControl(selectItemNode, otherNewLeft, otherNewTop);
|
||||
//this.EnvDecorator.MoveNode(nodeControl.ViewModel.NodeModel.Guid, otherNewLeft, otherNewTop); // 移动节点
|
||||
}
|
||||
}
|
||||
|
||||
// 更新节点之间线的连接位置
|
||||
//foreach (var nodeControl in selectNodeControls)
|
||||
//{
|
||||
// //nodeControl.UpdateLocationConnections();
|
||||
//}
|
||||
}
|
||||
startControlDragPoint = currentPosition; // 更新起始点位置
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void NodeControl_PointerExited(object? sender, PointerEventArgs e)
|
||||
{
|
||||
IsPreviewNodeControl = false;
|
||||
}
|
||||
|
||||
private void NodeControl_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
IsPreviewNodeControl = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Serein.Workbench.Avalonia.Custom.Views">
|
||||
|
||||
<!--
|
||||
Additional resources
|
||||
Using Control Themes:
|
||||
https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes
|
||||
Using Theme Variants:
|
||||
https://docs.avaloniaui.net/docs/guides/styles-and-resources/how-to-use-theme-variants
|
||||
-->
|
||||
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Width="400" Spacing="10">
|
||||
<StackPanel Background="{DynamicResource SystemRegionBrush}">
|
||||
<controls:NodeJunctionView />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<ControlTheme x:Key="{x:Type controls:NodeJunctionView}" TargetType="controls:NodeJunctionView">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
215
Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs
Normal file
215
Serein.Workbench.Avalonia/Custom/Views/NodeJunctionView.axaml.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Views;
|
||||
using System.Drawing;
|
||||
using System;
|
||||
using Color = Avalonia.Media.Color;
|
||||
using Point = Avalonia.Point;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Threading;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Extension;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Custom.Views;
|
||||
|
||||
/// <summary>
|
||||
/// 连接控制点
|
||||
/// </summary>
|
||||
public class NodeJunctionView : TemplatedControl
|
||||
{
|
||||
private readonly INodeOperationService nodeOperationService;
|
||||
|
||||
/// <summary>
|
||||
/// Render方法中控制自绘内容
|
||||
/// </summary>
|
||||
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
|
||||
|
||||
/// <summary>
|
||||
/// 正在查看
|
||||
/// </summary>
|
||||
private bool IsPreviewing;
|
||||
|
||||
public NodeJunctionView()
|
||||
{
|
||||
nodeOperationService = App.GetService<INodeOperationService>();
|
||||
this.PointerMoved += NodeJunctionView_PointerMoved;
|
||||
this.PointerExited += NodeJunctionView_PointerExited;
|
||||
|
||||
this.PointerPressed += NodeJunctionView_PointerPressed;
|
||||
this.PointerReleased += NodeJunctionView_PointerReleased;
|
||||
}
|
||||
|
||||
private void NodeJunctionView_PointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
nodeOperationService.ConnectingData.IsCreateing = false;
|
||||
}
|
||||
|
||||
private void NodeJunctionView_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
nodeOperationService.TryCreateConnectionOnJunction(this); // 尝试开始创建
|
||||
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取到控件信息
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
//if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm)
|
||||
//{
|
||||
// //p_fm.SelectionChanged += ListBox_SelectionChanged;
|
||||
// //p_fm.PointerExited += ListBox_PointerExited;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
public static readonly DirectProperty<NodeJunctionView, JunctionType> JunctionTypeProperty =
|
||||
AvaloniaProperty.RegisterDirect<NodeJunctionView, JunctionType>(nameof(JunctionType), o => o.JunctionType, (o, v) => o.JunctionType = v);
|
||||
private JunctionType junctionType;
|
||||
public JunctionType JunctionType
|
||||
{
|
||||
get { return junctionType; }
|
||||
set { SetAndRaise(JunctionTypeProperty, ref junctionType, value); }
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<NodeJunctionView, NodeModelBase?> MyNodeProperty =
|
||||
AvaloniaProperty.RegisterDirect<NodeJunctionView, NodeModelBase?>(nameof(MyNode), o => o.MyNode, (o, v) => o.MyNode = v);
|
||||
private NodeModelBase? myNode;
|
||||
public NodeModelBase? MyNode
|
||||
{
|
||||
get { return myNode; }
|
||||
set { SetAndRaise(MyNodeProperty, ref myNode, value); }
|
||||
}
|
||||
|
||||
#region 重绘UI视觉
|
||||
|
||||
/// <summary>
|
||||
/// 控件重绘事件
|
||||
/// </summary>
|
||||
/// <param name="drawingContext"></param>
|
||||
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
double width = 44;
|
||||
double height = 26;
|
||||
var background = GetBackgrounp();
|
||||
var pen = new Pen(Brushes.Black, 1);
|
||||
|
||||
// 输入连接器的背景
|
||||
var connectorRect = new Rect(0, 0, width, height);
|
||||
drawingContext.DrawRectangle(Brushes.Transparent, new Pen(), connectorRect);
|
||||
|
||||
|
||||
double circleCenterX = width / 2 ; // 中心 X 坐标
|
||||
double circleCenterY = height / 2 ; // 中心 Y 坐标
|
||||
//_myCenterPoint = new Point(circleCenterX - Width / 2, circleCenterY); // 中心坐标
|
||||
|
||||
// 定义圆形的大小和位置
|
||||
var diameterCircle = width - 20;
|
||||
Rect rect = new(4, 2, diameterCircle / 2, diameterCircle / 2);
|
||||
var ellipse = new EllipseGeometry(rect);
|
||||
drawingContext.DrawGeometry(background, pen, ellipse);
|
||||
|
||||
// 定义三角形的间距
|
||||
double triangleCenterX = width / 2 - 2; // 三角形中心 X 坐标
|
||||
double triangleCenterY = height / 2 -5; // 三角形中心 Y 坐标
|
||||
|
||||
// 绘制三角形
|
||||
var pathGeometry = new StreamGeometry();
|
||||
using (var context = pathGeometry.Open())
|
||||
{
|
||||
int t = 6;
|
||||
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - t), true);
|
||||
context.LineTo(new Point(triangleCenterX + 8, triangleCenterY), true);
|
||||
context.LineTo(new Point(triangleCenterX, triangleCenterY + t), true);
|
||||
context.LineTo(new Point(triangleCenterX, triangleCenterY - t), true);
|
||||
}
|
||||
drawingContext.DrawGeometry(background, new Pen(Brushes.Black, 1), pathGeometry);
|
||||
|
||||
}
|
||||
|
||||
#region 处理鼠标事件
|
||||
|
||||
private void NodeJunctionView_PointerExited(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (IsPreviewing)
|
||||
{
|
||||
IsPreviewing = false;
|
||||
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
|
||||
private void NodeJunctionView_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (!IsPreviewing)
|
||||
{
|
||||
IsPreviewing = true;
|
||||
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取背景颜色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected IBrush GetBackgrounp()
|
||||
{
|
||||
var myData = nodeOperationService.ConnectingData;
|
||||
if (!myData.IsCreateing)
|
||||
{
|
||||
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
|
||||
return new SolidColorBrush(Color.Parse("#76ABEE"));
|
||||
}
|
||||
|
||||
if (myData.IsCanConnected)
|
||||
{
|
||||
if (myData.Type == JunctionOfConnectionType.Invoke)
|
||||
{
|
||||
return myData.ConnectionInvokeType.ToLineColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
return myData.ConnectionArgSourceType.ToLineColor();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Brushes.Red;
|
||||
}
|
||||
|
||||
if (IsPreviewing)
|
||||
{
|
||||
//return new SolidColorBrush(Color.Parse("#04FC10"));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
|
||||
return new SolidColorBrush(Color.Parse("#76ABEE"));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="390" d:DesignHeight="40"
|
||||
x:Class="Serein.Workbench.Avalonia.Custom.Views.ParameterDetailsInfoView"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
|
||||
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
|
||||
x:DataType="vm:ParameterDetailsViewModel"
|
||||
VerticalAlignment="Center">
|
||||
<Design.DataContext>
|
||||
<vm:ParameterDetailsViewModel/>
|
||||
</Design.DataContext>
|
||||
<StackPanel >
|
||||
<StackPanel.Resources>
|
||||
<converter:IsVisibleOfParameterConverter x:Key="visibleConverter"/>
|
||||
|
||||
|
||||
</StackPanel.Resources>
|
||||
|
||||
|
||||
|
||||
<Grid ColumnDefinitions="20,40,90,auto" Margin="6,0,10,0">
|
||||
|
||||
<!--<ToolTip.Tip>
|
||||
<StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>-->
|
||||
<!--<ToolTip Background="LightYellow" Foreground="#071042" Content="" />-->
|
||||
|
||||
<cv:NodeJunctionView Grid.Column="0" JunctionType="ArgData" MyNode="{Binding ParameterDetails.NodeModel}" Width="30" Height="15" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding ParameterDetails.IsExplicitData, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" >
|
||||
|
||||
</CheckBox>
|
||||
<!--<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Index, StringFormat='arg{0} '}" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />-->
|
||||
<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Name}" FontSize="14"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
ToolTip.Placement="Bottom" ToolTip.VerticalOffset="6">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding ParameterDetails}" FontSize="14" TextTrimming="None" TextWrapping="WrapWithOverflow"/>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="3" IsVisible="{Binding IsVisibleA}" FontSize="14" Text=" [ 自动取参 ]" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="3" IsVisible="{Binding IsVisibleB}" FontSize="14" Text="{Binding ParameterDetails.DataValue, Mode=TwoWay}" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="3" IsVisible="{Binding IsVisibleC}"
|
||||
ItemsSource="{Binding ParameterDetails.Items}"
|
||||
SelectedValue="{Binding ParameterDetails.DataValue,Mode=OneTime}"
|
||||
MinWidth="100" MaxWidth="300">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="14"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serein.Library;
|
||||
using System.Diagnostics;
|
||||
using MsBox.Avalonia.Enums;
|
||||
using MsBox.Avalonia;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia;
|
||||
using Serein.Library.Utils;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.DataTemplates
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 方法信息模板
|
||||
/// </summary>
|
||||
internal class LibraryMethodInfoDataTemplate : IDataTemplate
|
||||
{
|
||||
public Control Build(object param)
|
||||
{
|
||||
if (param is MethodDetailsInfo mdInfo)
|
||||
{
|
||||
var textBlock = new TextBlock() { Text = mdInfo.MethodAnotherName };
|
||||
textBlock.Margin = new Thickness(2d, -6d, 2d, -6d);
|
||||
textBlock.FontSize = 12;
|
||||
textBlock.PointerPressed += TextBlock_PointerPressed;
|
||||
//var stackPanel = new StackPanel();
|
||||
//stackPanel.Children.Add(textBlock);
|
||||
//ToolTip toolTip = new ToolTip();
|
||||
//toolTip.FontSize = 12;
|
||||
//toolTip.Content = mdInfo.MethodAnotherName;
|
||||
//textBlock.Tag = mdInfo;
|
||||
return textBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
var textBlock = new TextBlock() { Text = $"Binding 类型不为预期的[MethodDetailsInfo],而是[{param?.GetType()}]" };
|
||||
textBlock.Margin = new Thickness(2d, -6d, 2d, -6d);
|
||||
textBlock.FontSize = 12;
|
||||
textBlock.PointerPressed += TextBlock_PointerPressed;
|
||||
return textBlock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is MethodDetailsInfo;
|
||||
}
|
||||
|
||||
|
||||
private static void TextBlock_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBlock textBlock || textBlock.Tag is not MethodDetailsInfo mdInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var dragData = new DataObject(); // 设置需要传递的数据
|
||||
dragData.Set(DataFormats.Text, mdInfo.ToJsonText());
|
||||
_ = DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
|
||||
//var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
|
||||
//Debug.WriteLine("DoDrag :" + result);
|
||||
//switch (result)
|
||||
//{
|
||||
// case DragDropEffects.Copy:
|
||||
// Debug.WriteLine("文本来自 Copy");
|
||||
// break;
|
||||
// case DragDropEffects.Link:
|
||||
// Debug.WriteLine("文本来自 Link");
|
||||
// break;
|
||||
// case DragDropEffects.None:
|
||||
// Debug.WriteLine("拖拽操作被取消");
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Serein.Workbench.Avalonia/Extension/LineExtension.cs
Normal file
55
Serein.Workbench.Avalonia/Extension/LineExtension.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Avalonia.Media;
|
||||
using Serein.Library;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Extension
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 线条颜色
|
||||
/// </summary>
|
||||
public static class LineExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据连接类型指定颜色
|
||||
/// </summary>
|
||||
/// <param name="currentConnectionType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static SolidColorBrush ToLineColor(this ConnectionInvokeType currentConnectionType)
|
||||
{
|
||||
return currentConnectionType switch
|
||||
{
|
||||
ConnectionInvokeType.IsSucceed => new SolidColorBrush(Color.Parse("#04FC10")), // 04FC10 & 027E08
|
||||
ConnectionInvokeType.IsFail => new SolidColorBrush(Color.Parse("#F18905")),
|
||||
ConnectionInvokeType.IsError => new SolidColorBrush(Color.Parse("#FE1343")),
|
||||
ConnectionInvokeType.Upstream => new SolidColorBrush(Color.Parse("#4A82E4")),
|
||||
ConnectionInvokeType.None => new SolidColorBrush(Color.Parse("#56CEF6")),
|
||||
_ => throw new Exception(),
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据连接类型指定颜色
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static SolidColorBrush ToLineColor(this ConnectionArgSourceType connection)
|
||||
{
|
||||
return connection switch
|
||||
{
|
||||
|
||||
ConnectionArgSourceType.GetPreviousNodeData => new SolidColorBrush(Color.Parse("#56CEF6")), // 04FC10 & 027E08
|
||||
ConnectionArgSourceType.GetOtherNodeData => new SolidColorBrush(Color.Parse("#56CEF6")),
|
||||
ConnectionArgSourceType.GetOtherNodeDataOfInvoke => new SolidColorBrush(Color.Parse("#56CEF6")),
|
||||
_ => throw new Exception(),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
42
Serein.Workbench.Avalonia/Extension/PointExtension.cs
Normal file
42
Serein.Workbench.Avalonia/Extension/PointExtension.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Extension
|
||||
{
|
||||
public static class PointExtension
|
||||
{
|
||||
public static Point Add(this Point a, Point b)
|
||||
{
|
||||
return new Point(a.X + b.X, a.Y + b.Y);
|
||||
}
|
||||
|
||||
public static Point Sub(this Point a, Point b)
|
||||
{
|
||||
return new Point(a.X - b.X, a.Y - b.Y);
|
||||
}
|
||||
|
||||
public static Vector ToVector(this Point me)
|
||||
{
|
||||
return new Vector(me.X, me.Y);
|
||||
}
|
||||
}
|
||||
public static class VectorExtension
|
||||
{
|
||||
public static double DotProduct(this Vector a, Vector b)
|
||||
{
|
||||
return a.X * b.X + a.Y * b.Y;
|
||||
}
|
||||
|
||||
public static Vector NormalizeTo(this Vector v)
|
||||
{
|
||||
var temp = v;
|
||||
temp.Normalize();
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Serein.Workbench.Avalonia/Extension/PointerExtension.cs
Normal file
32
Serein.Workbench.Avalonia/Extension/PointerExtension.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Extension
|
||||
{
|
||||
internal static class PointerExtension
|
||||
{
|
||||
public static bool JudgePointer(this
|
||||
PointerEventArgs eventArgs,
|
||||
object? sender,
|
||||
PointerType pointerType,
|
||||
Func<PointerPointProperties,bool> judgePointerFunc)
|
||||
{
|
||||
if(sender is not Visual visual)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (eventArgs.Pointer.Type == pointerType) // 是否是否是指定的设备类型
|
||||
{
|
||||
var point = eventArgs.GetCurrentPoint(visual); // 获取到点击点
|
||||
return judgePointerFunc.Invoke(point.Properties); // 判断是否属于某种类型的点击
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Serein.Workbench.Avalonia/Model/ConnectingData.cs
Normal file
125
Serein.Workbench.Avalonia/Model/ConnectingData.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Avalonia;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Custom.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Model
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 节点之间连接线的相关控制方法
|
||||
/// </summary>
|
||||
public class ConnectingData
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否正在创建连线
|
||||
/// </summary>
|
||||
public bool IsCreateing { get; set; }
|
||||
/// <summary>
|
||||
/// 起始控制点
|
||||
/// </summary>
|
||||
public NodeJunctionView? StartJunction { get; set; }
|
||||
/// <summary>
|
||||
/// 当前的控制点
|
||||
/// </summary>
|
||||
public NodeJunctionView? CurrentJunction { get; set; }
|
||||
/// <summary>
|
||||
/// 开始坐标
|
||||
/// </summary>
|
||||
public Point StartPoint { get; set; }
|
||||
/// <summary>
|
||||
/// 线条样式
|
||||
/// </summary>
|
||||
public MyLine? TempLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 线条类别(方法调用)
|
||||
/// </summary>
|
||||
public ConnectionInvokeType ConnectionInvokeType { get; set; } = ConnectionInvokeType.IsSucceed;
|
||||
/// <summary>
|
||||
/// 线条类别(参数传递)
|
||||
/// </summary>
|
||||
public ConnectionArgSourceType ConnectionArgSourceType { get; set; } = ConnectionArgSourceType.GetOtherNodeData;
|
||||
|
||||
/// <summary>
|
||||
/// 判断当前连接类型
|
||||
/// </summary>
|
||||
public JunctionOfConnectionType? Type => StartJunction?.JunctionType.ToConnectyionType();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许连接
|
||||
/// </summary>
|
||||
|
||||
public bool IsCanConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
if (StartJunction is null
|
||||
|| CurrentJunction is null
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(StartJunction?.MyNode is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!StartJunction.MyNode.Equals(CurrentJunction.MyNode)
|
||||
&& StartJunction.JunctionType.IsCanConnection(CurrentJunction.JunctionType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新临时的连接线
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
public void UpdatePoint(Point point)
|
||||
{
|
||||
if (StartJunction is null
|
||||
|| CurrentJunction is null
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (StartJunction.JunctionType == Library.JunctionType.Execute
|
||||
|| StartJunction.JunctionType == Library.JunctionType.ArgData)
|
||||
{
|
||||
TempLine?.Line.UpdateStartPoints(point);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempLine?.Line.UpdateEndPoints(point);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
IsCreateing = false;
|
||||
StartJunction = null;
|
||||
CurrentJunction = null;
|
||||
TempLine?.Remove();
|
||||
ConnectionInvokeType = ConnectionInvokeType.IsSucceed;
|
||||
ConnectionArgSourceType = ConnectionArgSourceType.GetOtherNodeData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
14
Serein.Workbench.Avalonia/Model/FlowLibraryInfo.cs
Normal file
14
Serein.Workbench.Avalonia/Model/FlowLibraryInfo.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Model
|
||||
{
|
||||
|
||||
internal class FlowLibraryInfo
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
40
Serein.Workbench.Avalonia/Model/MyLine.cs
Normal file
40
Serein.Workbench.Avalonia/Model/MyLine.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Avalonia.Controls;
|
||||
using Serein.Workbench.Avalonia.Custom.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Model
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 绘制的线
|
||||
/// </summary>
|
||||
public class MyLine
|
||||
{
|
||||
/// <summary>
|
||||
/// 将线条绘制出来
|
||||
/// </summary>
|
||||
/// <param name="canvas">放置画布</param>
|
||||
/// <param name="line">线的实体</param>
|
||||
public MyLine(Canvas canvas, ConnectionLineShape line)
|
||||
{
|
||||
Canvas = canvas;
|
||||
Line = line;
|
||||
canvas?.Children.Add(line);
|
||||
}
|
||||
|
||||
public Canvas Canvas { get; }
|
||||
public ConnectionLineShape Line { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 移除线
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
Canvas?.Children.Remove(Line);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj
Normal file
60
Serein.Workbench.Avalonia/Serein.Workbench.Avalonia.csproj
Normal file
@@ -0,0 +1,60 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Remove="Commands\**" />
|
||||
<Compile Remove="Commands\**" />
|
||||
<EmbeddedResource Remove="Commands\**" />
|
||||
<None Remove="Commands\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Custom\ViewModels\FlowLibraryInfoViewModel.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Skia" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.3" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\Library\Serein.Library.csproj" />
|
||||
<ProjectReference Include="..\..\..\DynamicControl\SereinFlow\NodeFlow\Serein.NodeFlow.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Custom\Views\FlowLibraryMethodInfoView.axaml.cs">
|
||||
<DependentUpon>FlowLibraryMethodInfoView1. axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Custom\Views\MainMenuBarView.axaml.cs">
|
||||
<DependentUpon>MainMenuBarView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Custom\Views\FlowLibraryInfoView.axaml.cs">
|
||||
<DependentUpon>FlowLibraryInfoView. axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Custom\Views\NodeContainerView.axaml.cs">
|
||||
<DependentUpon>NodeContainerView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
345
Serein.Workbench.Avalonia/Services/FlowEEForwardingService.cs
Normal file
345
Serein.Workbench.Avalonia/Services/FlowEEForwardingService.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Services
|
||||
{
|
||||
|
||||
|
||||
|
||||
internal class FlowEEForwardingService : IFlowEEForwardingService
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程运行环境
|
||||
/// </summary>
|
||||
private readonly IFlowEnvironment flowEnvironment;
|
||||
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 转发流程运行环境各个事件的实现类
|
||||
/// </summary>
|
||||
/// <param name="flowEnvironment"></param>
|
||||
/// <param name="flowNodeControlService"></param>
|
||||
public FlowEEForwardingService(IFlowEnvironment flowEnvironment,
|
||||
IFlowEnvironmentEvent flowEnvironmentEvent)
|
||||
{
|
||||
this.flowEnvironment = flowEnvironment;
|
||||
this.flowEnvironmentEvent = flowEnvironmentEvent;
|
||||
InitFlowEnvironmentEvent();
|
||||
}
|
||||
|
||||
#region 工作台事件转发
|
||||
/// <summary>
|
||||
/// 加载了依赖文件事件
|
||||
/// </summary>
|
||||
public event LoadDllHandler? OnDllLoad;
|
||||
/// <summary>
|
||||
/// 项目加载完成事件
|
||||
/// </summary>
|
||||
public event ProjectLoadedHandler? OnProjectLoaded;
|
||||
/// <summary>
|
||||
/// 项目保存中事件
|
||||
/// </summary>
|
||||
public event ProjectSavingHandler? OnProjectSaving;
|
||||
/// <summary>
|
||||
/// 节点连接改变事件
|
||||
/// </summary>
|
||||
public event NodeConnectChangeHandler? OnNodeConnectChange;
|
||||
/// <summary>
|
||||
/// 节点创建事件
|
||||
/// </summary>
|
||||
public event NodeCreateHandler? OnNodeCreate;
|
||||
/// <summary>
|
||||
/// 节点移除事件
|
||||
/// </summary>
|
||||
public event NodeRemoveHandler? OnNodeRemove;
|
||||
/// <summary>
|
||||
/// 节点放置容器事件
|
||||
/// </summary>
|
||||
public event NodePlaceHandler? OnNodePlace;
|
||||
/// <summary>
|
||||
/// 节点取出事件
|
||||
/// </summary>
|
||||
public event NodeTakeOutHandler? OnNodeTakeOut;
|
||||
/// <summary>
|
||||
/// 流程起始节点改变事件
|
||||
/// </summary>
|
||||
public event StartNodeChangeHandler? OnStartNodeChange;
|
||||
/// <summary>
|
||||
/// 流程运行完毕事件
|
||||
/// </summary>
|
||||
public event FlowRunCompleteHandler? OnFlowRunComplete;
|
||||
/// <summary>
|
||||
/// 被监视的对象数据改变事件
|
||||
/// </summary>
|
||||
public event MonitorObjectChangeHandler? OnMonitorObjectChange;
|
||||
/// <summary>
|
||||
/// 节点中断状态改变事件
|
||||
/// </summary>
|
||||
public event NodeInterruptStateChangeHandler? OnNodeInterruptStateChange;
|
||||
/// <summary>
|
||||
/// 表达式中断触发事件
|
||||
/// </summary>
|
||||
public event ExpInterruptTriggerHandler? OnInterruptTrigger;
|
||||
/// <summary>
|
||||
/// 容器对象改变事件
|
||||
/// </summary>
|
||||
public event IOCMembersChangedHandler? OnIOCMembersChanged;
|
||||
/// <summary>
|
||||
/// 节点定位事件
|
||||
/// </summary>
|
||||
public event NodeLocatedHandler? OnNodeLocated;
|
||||
/// <summary>
|
||||
/// 节点移动事件
|
||||
/// </summary>
|
||||
public event NodeMovedHandler? OnNodeMoved;
|
||||
/// <summary>
|
||||
/// 运行环境输出事件
|
||||
/// </summary>
|
||||
public event EnvOutHandler? OnEnvOut;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 流程运行环境事件
|
||||
|
||||
private void InitFlowEnvironmentEvent()
|
||||
{
|
||||
flowEnvironmentEvent.OnDllLoad += FlowEnvironment_DllLoadEvent;
|
||||
flowEnvironmentEvent.OnProjectSaving += EnvDecorator_OnProjectSaving;
|
||||
flowEnvironmentEvent.OnProjectLoaded += FlowEnvironment_OnProjectLoaded;
|
||||
flowEnvironmentEvent.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent;
|
||||
flowEnvironmentEvent.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt;
|
||||
flowEnvironmentEvent.OnNodeCreate += FlowEnvironment_NodeCreateEvent;
|
||||
flowEnvironmentEvent.OnNodeRemove += FlowEnvironment_NodeRemoveEvent;
|
||||
flowEnvironmentEvent.OnNodePlace += EnvDecorator_OnNodePlaceEvent;
|
||||
flowEnvironmentEvent.OnNodeTakeOut += EnvDecorator_OnNodeTakeOutEvent;
|
||||
flowEnvironmentEvent.OnFlowRunComplete += FlowEnvironment_OnFlowRunCompleteEvent;
|
||||
|
||||
flowEnvironmentEvent.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChangeEvent;
|
||||
flowEnvironmentEvent.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChangeEvent;
|
||||
flowEnvironmentEvent.OnInterruptTrigger += FlowEnvironment_OnInterruptTriggerEvent;
|
||||
|
||||
flowEnvironmentEvent.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChangedEvent;
|
||||
|
||||
flowEnvironmentEvent.OnNodeLocated += FlowEnvironment_OnNodeLocateEvent;
|
||||
flowEnvironmentEvent.OnNodeMoved += FlowEnvironment_OnNodeMovedEvent;
|
||||
|
||||
flowEnvironmentEvent.OnEnvOut += FlowEnvironment_OnEnvOutEvent;
|
||||
}
|
||||
|
||||
private void ResetFlowEnvironmentEvent()
|
||||
{
|
||||
flowEnvironmentEvent.OnDllLoad -= FlowEnvironment_DllLoadEvent;
|
||||
flowEnvironmentEvent.OnProjectSaving -= EnvDecorator_OnProjectSaving;
|
||||
flowEnvironmentEvent.OnProjectLoaded -= FlowEnvironment_OnProjectLoaded;
|
||||
flowEnvironmentEvent.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent;
|
||||
flowEnvironmentEvent.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt;
|
||||
flowEnvironmentEvent.OnNodeCreate -= FlowEnvironment_NodeCreateEvent;
|
||||
flowEnvironmentEvent.OnNodeRemove -= FlowEnvironment_NodeRemoveEvent;
|
||||
flowEnvironmentEvent.OnNodePlace -= EnvDecorator_OnNodePlaceEvent;
|
||||
flowEnvironmentEvent.OnNodeTakeOut -= EnvDecorator_OnNodeTakeOutEvent;
|
||||
flowEnvironmentEvent.OnFlowRunComplete -= FlowEnvironment_OnFlowRunCompleteEvent;
|
||||
|
||||
|
||||
flowEnvironmentEvent.OnMonitorObjectChange -= FlowEnvironment_OnMonitorObjectChangeEvent;
|
||||
flowEnvironmentEvent.OnNodeInterruptStateChange -= FlowEnvironment_OnNodeInterruptStateChangeEvent;
|
||||
flowEnvironmentEvent.OnInterruptTrigger -= FlowEnvironment_OnInterruptTriggerEvent;
|
||||
|
||||
flowEnvironmentEvent.OnIOCMembersChanged -= FlowEnvironment_OnIOCMembersChangedEvent;
|
||||
flowEnvironmentEvent.OnNodeLocated -= FlowEnvironment_OnNodeLocateEvent;
|
||||
flowEnvironmentEvent.OnNodeMoved -= FlowEnvironment_OnNodeMovedEvent;
|
||||
|
||||
flowEnvironmentEvent.OnEnvOut -= FlowEnvironment_OnEnvOutEvent;
|
||||
|
||||
}
|
||||
|
||||
#region 运行环境事件
|
||||
|
||||
/// <summary>
|
||||
/// 环境内容输出
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="value"></param>
|
||||
private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value)
|
||||
{
|
||||
//LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需要保存项目
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void EnvDecorator_OnProjectSaving(ProjectSavingEventArgs eventArgs)
|
||||
{
|
||||
OnProjectSaving?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载完成
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
||||
{
|
||||
OnProjectLoaded?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行完成
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_OnFlowRunCompleteEvent(FlowEventArgs eventArgs)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, "-------运行完成---------\r\n");
|
||||
OnFlowRunComplete?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载了DLL文件,dll内容
|
||||
/// </summary>
|
||||
private void FlowEnvironment_DllLoadEvent(LoadDllEventArgs eventArgs)
|
||||
{
|
||||
OnDllLoad?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点连接关系变更
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
|
||||
{
|
||||
OnNodeConnectChange?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点移除事件
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_NodeRemoveEvent(NodeRemoveEventArgs eventArgs)
|
||||
{
|
||||
OnNodeRemove?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加节点事件
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">添加节点事件参数</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs)
|
||||
{
|
||||
OnNodeCreate?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 放置一个节点
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void EnvDecorator_OnNodePlaceEvent(NodePlaceEventArgs eventArgs)
|
||||
{
|
||||
OnNodePlace?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取出一个节点
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void EnvDecorator_OnNodeTakeOutEvent(NodeTakeOutEventArgs eventArgs)
|
||||
{
|
||||
OnNodeTakeOut?.Invoke(eventArgs);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置了流程起始控件
|
||||
/// </summary>
|
||||
/// <param name="oldNodeGuid"></param>
|
||||
/// <param name="newNodeGuid"></param>
|
||||
private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs)
|
||||
{
|
||||
|
||||
OnStartNodeChange?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 被监视的对象发生改变
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_OnMonitorObjectChangeEvent(MonitorObjectEventArgs eventArgs)
|
||||
{
|
||||
OnMonitorObjectChange?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点中断状态改变。
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_OnNodeInterruptStateChangeEvent(NodeInterruptStateChangeEventArgs eventArgs)
|
||||
{
|
||||
OnNodeInterruptStateChange?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点触发了中断
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_OnInterruptTriggerEvent(InterruptTriggerEventArgs eventArgs)
|
||||
{
|
||||
OnInterruptTrigger?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IOC变更
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_OnIOCMembersChangedEvent(IOCMembersChangedEventArgs eventArgs)
|
||||
{
|
||||
OnIOCMembersChanged?.Invoke(eventArgs);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点需要定位
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
private void FlowEnvironment_OnNodeLocateEvent(NodeLocatedEventArgs eventArgs)
|
||||
{
|
||||
OnNodeLocated?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 节点移动
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_OnNodeMovedEvent(NodeMovedEventArgs eventArgs)
|
||||
{
|
||||
OnNodeMoved?.Invoke(eventArgs);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
80
Serein.Workbench.Avalonia/Services/KeyEventService.cs
Normal file
80
Serein.Workbench.Avalonia/Services/KeyEventService.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Services
|
||||
{
|
||||
delegate void KeyDownEventHandler(Key key);
|
||||
delegate void KeyUpEventHandler(Key key);
|
||||
|
||||
/// <summary>
|
||||
/// 全局事件服务
|
||||
/// </summary>
|
||||
internal interface IKeyEventService
|
||||
{
|
||||
event KeyDownEventHandler KeyDown;
|
||||
event KeyUpEventHandler KeyUp;
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个按键状态
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
bool GetKeyState(Key key);
|
||||
/// <summary>
|
||||
/// 设置某个按键的状态
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="state"></param>
|
||||
void SetKeyState(Key key, bool statestate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 管理按键状态
|
||||
/// </summary>
|
||||
internal class KeyEventService : IKeyEventService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 按键按下
|
||||
/// </summary>
|
||||
public event KeyDownEventHandler KeyDown;
|
||||
/// <summary>
|
||||
/// 按键松开
|
||||
/// </summary>
|
||||
public event KeyUpEventHandler KeyUp;
|
||||
|
||||
public KeyEventService()
|
||||
{
|
||||
var arr = Enum.GetValues<Key>();
|
||||
KeysState = new bool[arr.Length];
|
||||
|
||||
// 绑定快捷键
|
||||
//HotKeyManager.SetHotKey(saveMenuItem, new KeyGesture(Key.S, KeyModifiers.Control));
|
||||
}
|
||||
|
||||
private readonly bool[] KeysState;
|
||||
public bool GetKeyState(Key key)
|
||||
{
|
||||
return KeysState[(int)key];
|
||||
}
|
||||
public void SetKeyState(Key key, bool state)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
KeyDown?.Invoke(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyUp?.Invoke(key);
|
||||
}
|
||||
//Debug.WriteLine($"按键事件:{key} - {state}");
|
||||
KeysState[(int)key] = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
329
Serein.Workbench.Avalonia/Services/NodeOperationService.cs
Normal file
329
Serein.Workbench.Avalonia/Services/NodeOperationService.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
using Newtonsoft.Json;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow;
|
||||
using Serein.Workbench.Avalonia.Api;
|
||||
using Serein.Workbench.Avalonia.Custom.Node.ViewModels;
|
||||
using Serein.Workbench.Avalonia.Custom.Node.Views;
|
||||
using Serein.Workbench.Avalonia.Custom.Views;
|
||||
using Serein.Workbench.Avalonia.Extension;
|
||||
using Serein.Workbench.Avalonia.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Api
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 提供节点操作的接口
|
||||
/// </summary>
|
||||
internal interface INodeOperationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接数据
|
||||
/// </summary>
|
||||
ConnectingData ConnectingData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 主画布
|
||||
/// </summary>
|
||||
Canvas MainCanvas { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点创建事件
|
||||
/// </summary>
|
||||
|
||||
event NodeViewCreateHandle OnNodeViewCreate;
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点控件
|
||||
/// </summary>
|
||||
/// <param name="nodeType">控件类型</param>
|
||||
/// <param name="position">创建坐标</param>
|
||||
/// <param name="methodDetailsInfo">节点方法信息</param>
|
||||
public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从连接控制点创建连接
|
||||
/// </summary>
|
||||
/// <param name="startJunction"></param>
|
||||
void TryCreateConnectionOnJunction(NodeJunctionView startJunction);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#region 事件与事件参数
|
||||
/// <summary>
|
||||
/// 创建节点控件事件
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
|
||||
internal delegate bool NodeViewCreateHandle(NodeViewCreateEventArgs eventArgs);
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点控件事件参数
|
||||
/// </summary>
|
||||
|
||||
|
||||
|
||||
internal class NodeViewCreateEventArgs : EventArgs
|
||||
{
|
||||
internal NodeViewCreateEventArgs(INodeControl nodeControl, PositionOfUI position)
|
||||
{
|
||||
this.NodeControl = nodeControl;
|
||||
this.Position = position;
|
||||
}
|
||||
public INodeControl NodeControl { get; private set; }
|
||||
public PositionOfUI Position { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点操作相关服务
|
||||
/// </summary>
|
||||
internal class NodeOperationService : INodeOperationService
|
||||
{
|
||||
|
||||
public NodeOperationService(IFlowEnvironment flowEnvironment,
|
||||
IFlowEEForwardingService feefService)
|
||||
{
|
||||
this.flowEnvironment = flowEnvironment;
|
||||
this.feefService = feefService;
|
||||
|
||||
NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeView), typeof(ActionNodeViewModel)); // 注册动作节点
|
||||
ConnectingData = new ConnectingData();
|
||||
feefService.OnNodeCreate += FeefService_OnNodeCreate; // 订阅运行环境创建节点事件
|
||||
|
||||
|
||||
// 手动加载项目
|
||||
_ = Task.Run(async delegate
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
var flowEnvironment = App.GetService<IFlowEnvironment>();
|
||||
var filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\project.dnf";
|
||||
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
|
||||
var projectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
|
||||
var projectDfilePath = System.IO.Path.GetDirectoryName(filePath)!;
|
||||
flowEnvironment.LoadProject(new FlowEnvInfo { Project = projectData }, projectDfilePath);
|
||||
}, CancellationToken.None);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ConnectingData ConnectingData { get; private set; }
|
||||
public Canvas MainCanvas { get; set; }
|
||||
|
||||
|
||||
|
||||
#region 私有变量
|
||||
|
||||
/// <summary>
|
||||
/// 存储所有与节点有关的控件
|
||||
/// </summary>
|
||||
private Dictionary<string, INodeControl> NodeControls { get; } = [];
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 流程运行环境
|
||||
/// </summary>
|
||||
private readonly IFlowEnvironment flowEnvironment;
|
||||
|
||||
/// <summary>
|
||||
/// 流程运行环境事件转发
|
||||
/// </summary>
|
||||
private readonly IFlowEEForwardingService feefService;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 创建了节点控件
|
||||
/// </summary>
|
||||
public event NodeViewCreateHandle OnNodeViewCreate;
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点控件
|
||||
/// </summary>
|
||||
/// <param name="nodeType">控件类型</param>
|
||||
/// <param name="position">创建坐标</param>
|
||||
/// <param name="methodDetailsInfo">节点方法信息(基础节点传null)</param>
|
||||
public void CreateNodeView(MethodDetailsInfo methodDetailsInfo, PositionOfUI position)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (EnumHelper.TryConvertEnum<NodeControlType>(methodDetailsInfo.NodeType, out var nodeType))
|
||||
{
|
||||
await flowEnvironment.CreateNodeAsync(nodeType, position, methodDetailsInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从工作台事件转发器监听节点创建事件
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FeefService_OnNodeCreate(NodeCreateEventArgs eventArgs)
|
||||
{
|
||||
var nodeModel = eventArgs.NodeModel;
|
||||
if (NodeControls.ContainsKey(nodeModel.Guid))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"OnNodeCreate 事件意外触发,节点Guid重复 - {nodeModel.Guid}");
|
||||
return;
|
||||
}
|
||||
if (!NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。");
|
||||
return;
|
||||
}
|
||||
if (nodeMVVM.ControlType == null
|
||||
|| nodeMVVM.ViewModelType == null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,UI类型尚未注册(请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。");
|
||||
return;
|
||||
}
|
||||
|
||||
var isSuccessful = TryCreateNodeView(nodeMVVM.ControlType, // 控件UI类型
|
||||
nodeMVVM.ViewModelType, // 控件VIewModel类型
|
||||
nodeModel, // 控件数据实体
|
||||
out var nodeControl); // 成功创建后传出的节点控件实体
|
||||
if (!isSuccessful || nodeControl is null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点创建失败。");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var e = new NodeViewCreateEventArgs(nodeControl, eventArgs.Position);
|
||||
if (OnNodeViewCreate?.Invoke(e) == true)
|
||||
{
|
||||
// 成功创建
|
||||
NodeControls.TryAdd(nodeModel.Guid, nodeControl); // 缓存起来,通知其它地方拿取这个控件
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点控件
|
||||
/// </summary>
|
||||
/// <param name="viewType">节点控件视图控件类型</param>
|
||||
/// <param name="viewModelType">节点控件ViewModel类型</param>
|
||||
/// <param name="nodeModel">节点Model实例</param>
|
||||
/// <param name="nodeView">返回的节点对象</param>
|
||||
/// <returns>是否创建成功</returns>
|
||||
/// <exception cref="Exception">无法创建节点控件</exception>
|
||||
private bool TryCreateNodeView(Type viewType, Type viewModelType, NodeModelBase nodeModel, out INodeControl? nodeView)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nodeModel.Guid))
|
||||
{
|
||||
nodeModel.Guid = Guid.NewGuid().ToString();
|
||||
}
|
||||
var t_ViewModel = Activator.CreateInstance(viewModelType);
|
||||
if (t_ViewModel is not NodeViewModelBase viewModelBase)
|
||||
{
|
||||
nodeView = null;
|
||||
return false;
|
||||
}
|
||||
viewModelBase.NodeModelBase = nodeModel; // 设置节点对象
|
||||
var controlObj = Activator.CreateInstance(viewType);
|
||||
if (controlObj is not INodeControl nodeControl)
|
||||
{
|
||||
nodeView = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeControl.SetNodeModel(nodeModel);
|
||||
nodeView = nodeControl;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 在其它地方验证过了,所以注释
|
||||
//if ((viewType is null)
|
||||
// || viewModelType is null
|
||||
// || nodeModel is null)
|
||||
//{
|
||||
// nodeView = null;
|
||||
// return false;
|
||||
//}
|
||||
//if (typeof(INodeControl).IsSubclassOf(viewType)
|
||||
// || typeof(NodeViewModelBase).IsSubclassOf(viewModelType))
|
||||
//{
|
||||
// nodeView = null;
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试在连接控制点之间创建连接线
|
||||
/// </summary>
|
||||
public void TryCreateConnectionOnJunction(NodeJunctionView startJunction)
|
||||
{
|
||||
if (MainCanvas is not null)
|
||||
{
|
||||
var myData = ConnectingData;
|
||||
var junctionSize = startJunction.GetTransformedBounds()!.Value.Bounds.Size;
|
||||
var junctionPoint = new Point(junctionSize.Width / 2, junctionSize.Height / 2);
|
||||
if (startJunction.TranslatePoint(junctionPoint, MainCanvas) is Point point)
|
||||
{
|
||||
myData.StartPoint = point;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
myData.Reset();
|
||||
myData.IsCreateing = true; // 表示开始连接
|
||||
myData.StartJunction = startJunction;
|
||||
myData.CurrentJunction = startJunction;
|
||||
|
||||
var junctionOfConnectionType = startJunction.JunctionType.ToConnectyionType();
|
||||
ConnectionLineShape bezierLine; // 类别
|
||||
Brush brushColor; // 临时线的颜色
|
||||
if (junctionOfConnectionType == JunctionOfConnectionType.Invoke)
|
||||
{
|
||||
brushColor = ConnectionInvokeType.IsSucceed.ToLineColor();
|
||||
}
|
||||
else if (junctionOfConnectionType == JunctionOfConnectionType.Arg)
|
||||
{
|
||||
brushColor = ConnectionArgSourceType.GetOtherNodeData.ToLineColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
bezierLine = new ConnectionLineShape(myData.StartPoint,
|
||||
myData.StartPoint,
|
||||
brushColor,
|
||||
isTop: true); // 绘制临时的线
|
||||
|
||||
//Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
|
||||
myData.TempLine = new MyLine(MainCanvas, bezierLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
90
Serein.Workbench.Avalonia/Services/WorkbenchEventService.cs
Normal file
90
Serein.Workbench.Avalonia/Services/WorkbenchEventService.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Services
|
||||
{
|
||||
|
||||
#region 工作台事件
|
||||
|
||||
public delegate void PreviewlMethodInfoHandler(PreviewlMethodInfoEventArgs eventArgs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 工作台事件参数
|
||||
public class PreviewlMethodInfoEventArgs(MethodDetailsInfo mdInfo) : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 方法信息
|
||||
/// </summary>
|
||||
public MethodDetailsInfo MethodDetailsInfo { get; } = mdInfo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 工作台事件管理
|
||||
/// </summary>
|
||||
internal interface IWorkbenchEventService
|
||||
{
|
||||
/// <summary>
|
||||
/// 预览了某个方法信息(待创建)
|
||||
/// </summary>
|
||||
event PreviewlMethodInfoHandler OnPreviewlMethodInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 预览依赖方法信息
|
||||
/// </summary>
|
||||
void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工作台事件的实现类
|
||||
/// </summary>
|
||||
internal class WorkbenchEventService : IWorkbenchEventService
|
||||
{
|
||||
|
||||
private readonly IFlowEnvironment flowEnvironment;
|
||||
/// <summary>
|
||||
/// 管理工作台的事件
|
||||
/// </summary>
|
||||
/// <param name="flowEnvironment"></param>
|
||||
public WorkbenchEventService(IFlowEnvironment flowEnvironment)
|
||||
{
|
||||
this.flowEnvironment = flowEnvironment;
|
||||
|
||||
}
|
||||
|
||||
private void SubscribeEvents()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预览了某个方法信息(待创建)
|
||||
/// </summary>
|
||||
public event PreviewlMethodInfoHandler? OnPreviewlMethodInfo;
|
||||
/// <summary>
|
||||
/// 预览依赖方法信息
|
||||
/// </summary>
|
||||
public void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo)
|
||||
{
|
||||
OnPreviewlMethodInfo?.Invoke(new PreviewlMethodInfoEventArgs(mdInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 需要放置节点控件
|
||||
/// </summary>
|
||||
public void PlateNodeControl()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
7
Serein.Workbench.Avalonia/ViewModels/MainViewModel.cs
Normal file
7
Serein.Workbench.Avalonia/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Serein.Workbench.Avalonia.ViewModels;
|
||||
|
||||
public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
public string Greeting => "Welcome to Avalonia!";
|
||||
|
||||
}
|
||||
7
Serein.Workbench.Avalonia/ViewModels/ViewModelBase.cs
Normal file
7
Serein.Workbench.Avalonia/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.ViewModels;
|
||||
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
}
|
||||
34
Serein.Workbench.Avalonia/Views/MainView.axaml
Normal file
34
Serein.Workbench.Avalonia/Views/MainView.axaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.ViewModels"
|
||||
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="640"
|
||||
x:Class="Serein.Workbench.Avalonia.Views.MainView"
|
||||
x:DataType="vm:MainViewModel">
|
||||
<Design.DataContext>
|
||||
<vm:MainViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Styles>
|
||||
</UserControl.Styles>
|
||||
<Grid RowDefinitions="auto,0,*" ColumnDefinitions="*">
|
||||
<cv:MainMenuBarView Grid.Row="0" Grid.Column="0" Background="#E2E9EE"/>
|
||||
<!--这里留空,以后放置功能区-->
|
||||
<Grid Grid.Row="2" Grid.Column="0"
|
||||
RowDefinitions="*" ColumnDefinitions="auto,*">
|
||||
<!--依赖信息-->
|
||||
<Grid RowDefinitions="*,auto" HorizontalAlignment="Left">
|
||||
<!--已加载的依赖-->
|
||||
<cv:FlowLibrarysView Grid.Row="0"/>
|
||||
<!--<cv:FlowLibraryMethodInfoView Grid.Row="1" HorizontalAlignment="Left"/>-->
|
||||
<!--当前预览的节点方法信息-->
|
||||
</Grid>
|
||||
|
||||
<!--画布-->
|
||||
<cv:NodeContainerView Grid.Column="1"/>
|
||||
<!--其他视图-->
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
23
Serein.Workbench.Avalonia/Views/MainView.axaml.cs
Normal file
23
Serein.Workbench.Avalonia/Views/MainView.axaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Newtonsoft.Json;
|
||||
using Serein.Library;
|
||||
using Serein.NodeFlow.Env;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Views;
|
||||
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
Serein.Workbench.Avalonia/Views/MainWindow.axaml
Normal file
14
Serein.Workbench.Avalonia/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:Serein.Workbench.Avalonia.ViewModels"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="clr-namespace:Serein.Workbench.Avalonia.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Serein.Workbench.Avalonia.Views.MainWindow"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
Title="Serein Flow Edit">
|
||||
<views:MainView />
|
||||
|
||||
|
||||
</Window>
|
||||
36
Serein.Workbench.Avalonia/Views/MainWindow.axaml.cs
Normal file
36
Serein.Workbench.Avalonia/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Newtonsoft.Json;
|
||||
using Serein.NodeFlow.Env;
|
||||
using System.Threading.Tasks;
|
||||
using Serein.Library;
|
||||
using Serein.Workbench.Avalonia.Services;
|
||||
|
||||
namespace Serein.Workbench.Avalonia.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
IKeyEventService keyEventService = App.GetService<IKeyEventService>();
|
||||
this.Loaded += MainWindow_Loaded;
|
||||
this.KeyDown += (o, e) =>
|
||||
{
|
||||
keyEventService.SetKeyState(e.Key, true);
|
||||
};
|
||||
this.KeyUp += (o, e) =>
|
||||
{
|
||||
keyEventService.SetKeyState(e.Key, false);
|
||||
};
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user