diff --git a/AXNode.png b/AXNode.png new file mode 100644 index 0000000..ffffddd Binary files /dev/null and b/AXNode.png differ diff --git a/AXNode/AXNode.csproj b/AXNode/AXNode.csproj new file mode 100644 index 0000000..09248ae --- /dev/null +++ b/AXNode/AXNode.csproj @@ -0,0 +1,50 @@ + + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AXNode/App.axaml b/AXNode/App.axaml new file mode 100644 index 0000000..7e799ea --- /dev/null +++ b/AXNode/App.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/App.axaml.cs b/AXNode/App.axaml.cs new file mode 100644 index 0000000..1153e82 --- /dev/null +++ b/AXNode/App.axaml.cs @@ -0,0 +1,41 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using AXNode.AppTool; +using XLib.Animate; + +namespace AXNode; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + + // AnimationEngine.Instance.Start(); + Init(); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + + private void Init() + { + // 初始化应用程序代理 + AppDelegate.Init(); + + // 初始化系统数据 + SystemDataDelegate.Instance.Init(); + // 启动系统服务 + SystemServiceDelegate.Instance.Start(); + // 初始化系统工具 + SystemToolDelegate.Instance.Init(); + } +} \ No newline at end of file diff --git a/AXNode/AppTool/AppDelegate.cs b/AXNode/AppTool/AppDelegate.cs new file mode 100644 index 0000000..91cf154 --- /dev/null +++ b/AXNode/AppTool/AppDelegate.cs @@ -0,0 +1,32 @@ +using System; +using System.Windows; +using Avalonia; +using Avalonia.Threading; + +namespace AXNode.AppTool +{ + /// + /// 应用程序代理 + /// + public class AppDelegate + { + public static App Main { get; set; } + + public static string AppTitle => "XNode 1.0.3 Alpha"; + + public static void Init() + { + if (Application.Current is App app) Main = app; + } + + public static void Invoke(Action action) + { + Dispatcher.UIThread.Invoke(action); + } + + public static void BeginInvoke(Action action) + { + Dispatcher.UIThread.Invoke(action); + } + } +} \ No newline at end of file diff --git a/AXNode/AppTool/ClassExtension.cs b/AXNode/AppTool/ClassExtension.cs new file mode 100644 index 0000000..1bb3bc2 --- /dev/null +++ b/AXNode/AppTool/ClassExtension.cs @@ -0,0 +1,22 @@ +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Define; + +namespace AXNode.AppTool +{ + public static class ClassExtension + { + /// + /// 获取引脚路径 + /// + public static PinPath GetPinPath(this PinBase pin) + { + return new PinPath + { + NodeVersion = pin.OwnerGroup.OwnerNode.Version, + NodeID = pin.OwnerGroup.OwnerNode.ID, + GroupIndex = pin.OwnerGroup.Index, + PinIndex = pin.OwnerGroup.GetPinIndex(pin) + }; + } + } +} \ No newline at end of file diff --git a/AXNode/AppTool/ProjectDelegate.cs b/AXNode/AppTool/ProjectDelegate.cs new file mode 100644 index 0000000..d8212d9 --- /dev/null +++ b/AXNode/AppTool/ProjectDelegate.cs @@ -0,0 +1,6 @@ +namespace AXNode.AppTool +{ + class ProjectDelegate + { + } +} \ No newline at end of file diff --git a/AXNode/AppTool/SystemDelegate.cs b/AXNode/AppTool/SystemDelegate.cs new file mode 100644 index 0000000..8546b82 --- /dev/null +++ b/AXNode/AppTool/SystemDelegate.cs @@ -0,0 +1,64 @@ +using XLib.Animate; +using XLib.Base.AppFrame; +using AXNode.SubSystem.CacheSystem; +using AXNode.SubSystem.ControlSystem; +using AXNode.SubSystem.EventSystem; +using AXNode.SubSystem.NodeLibSystem; +using AXNode.SubSystem.OptionSystem; +using AXNode.SubSystem.ResourceSystem; +using AXNode.SubSystem.TimerSystem; + +namespace AXNode.AppTool +{ + /// + /// 系统数据代理 + /// + public class SystemDataDelegate : ManagerDelegate + { + private SystemDataDelegate() + { + // 选项、缓存 + ManagerList.Add(OptionManager.Instance); + ManagerList.Add(CacheManager.Instance); + // 资源、节点库 + ManagerList.Add(ResourceManager.Instance); + ManagerList.Add(NodeLibManager.Instance); + } + + public static SystemDataDelegate Instance { get; } = new SystemDataDelegate(); + } + + /// + /// 系统服务代理 + /// + public class SystemServiceDelegate : ServiceDelegate + { + private SystemServiceDelegate() + { + // 时间引擎 + ServiceList.Add(TimeEngine.Instance); + // 应用定时器 + ServiceList.Add(AppTimer.Instance); + // 控制引擎 + ServiceList.Add(ControlEngine.Instance); + // 动画引擎 + ServiceList.Add(AnimationEngine.Instance); + } + + public static SystemServiceDelegate Instance { get; } = new SystemServiceDelegate(); + } + + /// + /// 系统工具代理 + /// + public class SystemToolDelegate : ManagerDelegate + { + private SystemToolDelegate() + { + // 事件管理器 + ManagerList.Add(EM.Instance); + } + + public static SystemToolDelegate Instance { get; } = new SystemToolDelegate(); + } +} \ No newline at end of file diff --git a/AXNode/Assets/Cursor/CanNotMove.cur b/AXNode/Assets/Cursor/CanNotMove.cur new file mode 100644 index 0000000..61f4add Binary files /dev/null and b/AXNode/Assets/Cursor/CanNotMove.cur differ diff --git a/AXNode/Assets/Cursor/Cross.cur b/AXNode/Assets/Cursor/Cross.cur new file mode 100644 index 0000000..b7f63d6 Binary files /dev/null and b/AXNode/Assets/Cursor/Cross.cur differ diff --git a/AXNode/Assets/Cursor/Disable.cur b/AXNode/Assets/Cursor/Disable.cur new file mode 100644 index 0000000..b753684 Binary files /dev/null and b/AXNode/Assets/Cursor/Disable.cur differ diff --git a/AXNode/Assets/Cursor/DragObject.cur b/AXNode/Assets/Cursor/DragObject.cur new file mode 100644 index 0000000..b000e06 Binary files /dev/null and b/AXNode/Assets/Cursor/DragObject.cur differ diff --git a/AXNode/Assets/Cursor/Draw.cur b/AXNode/Assets/Cursor/Draw.cur new file mode 100644 index 0000000..97befdd Binary files /dev/null and b/AXNode/Assets/Cursor/Draw.cur differ diff --git a/AXNode/Assets/Cursor/Insert.cur b/AXNode/Assets/Cursor/Insert.cur new file mode 100644 index 0000000..ab10f76 Binary files /dev/null and b/AXNode/Assets/Cursor/Insert.cur differ diff --git a/AXNode/Assets/Cursor/Move.cur b/AXNode/Assets/Cursor/Move.cur new file mode 100644 index 0000000..649496b Binary files /dev/null and b/AXNode/Assets/Cursor/Move.cur differ diff --git a/AXNode/Assets/Cursor/MoveBottom.cur b/AXNode/Assets/Cursor/MoveBottom.cur new file mode 100644 index 0000000..54329b2 Binary files /dev/null and b/AXNode/Assets/Cursor/MoveBottom.cur differ diff --git a/AXNode/Assets/Cursor/MoveSelected.cur b/AXNode/Assets/Cursor/MoveSelected.cur new file mode 100644 index 0000000..2d1875d Binary files /dev/null and b/AXNode/Assets/Cursor/MoveSelected.cur differ diff --git a/AXNode/Assets/Cursor/MoveTop.cur b/AXNode/Assets/Cursor/MoveTop.cur new file mode 100644 index 0000000..279c4a2 Binary files /dev/null and b/AXNode/Assets/Cursor/MoveTop.cur differ diff --git a/AXNode/Assets/Cursor/MoveX.cur b/AXNode/Assets/Cursor/MoveX.cur new file mode 100644 index 0000000..16b03fc Binary files /dev/null and b/AXNode/Assets/Cursor/MoveX.cur differ diff --git a/AXNode/Assets/Cursor/MoveY.cur b/AXNode/Assets/Cursor/MoveY.cur new file mode 100644 index 0000000..51e228f Binary files /dev/null and b/AXNode/Assets/Cursor/MoveY.cur differ diff --git a/AXNode/Assets/Cursor/OnOff.cur b/AXNode/Assets/Cursor/OnOff.cur new file mode 100644 index 0000000..a9d5795 Binary files /dev/null and b/AXNode/Assets/Cursor/OnOff.cur differ diff --git a/AXNode/Assets/Cursor/ResizeDownUp.cur b/AXNode/Assets/Cursor/ResizeDownUp.cur new file mode 100644 index 0000000..203232d Binary files /dev/null and b/AXNode/Assets/Cursor/ResizeDownUp.cur differ diff --git a/AXNode/Assets/Cursor/ResizeUpDown.cur b/AXNode/Assets/Cursor/ResizeUpDown.cur new file mode 100644 index 0000000..12f1068 Binary files /dev/null and b/AXNode/Assets/Cursor/ResizeUpDown.cur differ diff --git a/AXNode/Assets/Cursor/Select.cur b/AXNode/Assets/Cursor/Select.cur new file mode 100644 index 0000000..83be813 Binary files /dev/null and b/AXNode/Assets/Cursor/Select.cur differ diff --git a/AXNode/Assets/Font/simsun.ttc b/AXNode/Assets/Font/simsun.ttc new file mode 100644 index 0000000..40e9693 Binary files /dev/null and b/AXNode/Assets/Font/simsun.ttc differ diff --git a/AXNode/Assets/Icon15/Add.png b/AXNode/Assets/Icon15/Add.png new file mode 100644 index 0000000..49dcd3c Binary files /dev/null and b/AXNode/Assets/Icon15/Add.png differ diff --git a/AXNode/Assets/Icon15/Browse.png b/AXNode/Assets/Icon15/Browse.png new file mode 100644 index 0000000..d76605c Binary files /dev/null and b/AXNode/Assets/Icon15/Browse.png differ diff --git a/AXNode/Assets/Icon15/Clear.png b/AXNode/Assets/Icon15/Clear.png new file mode 100644 index 0000000..a533135 Binary files /dev/null and b/AXNode/Assets/Icon15/Clear.png differ diff --git a/AXNode/Assets/Icon15/ControlBoard.png b/AXNode/Assets/Icon15/ControlBoard.png new file mode 100644 index 0000000..678ff49 Binary files /dev/null and b/AXNode/Assets/Icon15/ControlBoard.png differ diff --git a/AXNode/Assets/Icon15/Copy.png b/AXNode/Assets/Icon15/Copy.png new file mode 100644 index 0000000..9cde699 Binary files /dev/null and b/AXNode/Assets/Icon15/Copy.png differ diff --git a/AXNode/Assets/Icon15/Delete.png b/AXNode/Assets/Icon15/Delete.png new file mode 100644 index 0000000..167f376 Binary files /dev/null and b/AXNode/Assets/Icon15/Delete.png differ diff --git a/AXNode/Assets/Icon15/EmptyFolder.png b/AXNode/Assets/Icon15/EmptyFolder.png new file mode 100644 index 0000000..6bf4013 Binary files /dev/null and b/AXNode/Assets/Icon15/EmptyFolder.png differ diff --git a/AXNode/Assets/Icon15/File.png b/AXNode/Assets/Icon15/File.png new file mode 100644 index 0000000..dbfce55 Binary files /dev/null and b/AXNode/Assets/Icon15/File.png differ diff --git a/AXNode/Assets/Icon15/Folder.png b/AXNode/Assets/Icon15/Folder.png new file mode 100644 index 0000000..f9f65b8 Binary files /dev/null and b/AXNode/Assets/Icon15/Folder.png differ diff --git a/AXNode/Assets/Icon15/Lib.png b/AXNode/Assets/Icon15/Lib.png new file mode 100644 index 0000000..8bdc82e Binary files /dev/null and b/AXNode/Assets/Icon15/Lib.png differ diff --git a/AXNode/Assets/Icon15/Node.png b/AXNode/Assets/Icon15/Node.png new file mode 100644 index 0000000..18ff10b Binary files /dev/null and b/AXNode/Assets/Icon15/Node.png differ diff --git a/AXNode/Assets/Icon15/NodePreset.png b/AXNode/Assets/Icon15/NodePreset.png new file mode 100644 index 0000000..f75d206 Binary files /dev/null and b/AXNode/Assets/Icon15/NodePreset.png differ diff --git a/AXNode/Assets/Icon15/Remove.png b/AXNode/Assets/Icon15/Remove.png new file mode 100644 index 0000000..635853f Binary files /dev/null and b/AXNode/Assets/Icon15/Remove.png differ diff --git a/AXNode/Assets/Icon15/Rename.png b/AXNode/Assets/Icon15/Rename.png new file mode 100644 index 0000000..f6dc127 Binary files /dev/null and b/AXNode/Assets/Icon15/Rename.png differ diff --git a/AXNode/Assets/Icon16/AlignCenter.png b/AXNode/Assets/Icon16/AlignCenter.png new file mode 100644 index 0000000..5bc8b4b Binary files /dev/null and b/AXNode/Assets/Icon16/AlignCenter.png differ diff --git a/AXNode/Assets/Icon16/AlignLeft.png b/AXNode/Assets/Icon16/AlignLeft.png new file mode 100644 index 0000000..d691806 Binary files /dev/null and b/AXNode/Assets/Icon16/AlignLeft.png differ diff --git a/AXNode/Assets/Icon16/AlignRight.png b/AXNode/Assets/Icon16/AlignRight.png new file mode 100644 index 0000000..b58516c Binary files /dev/null and b/AXNode/Assets/Icon16/AlignRight.png differ diff --git a/AXNode/Assets/Icon16/ClearConsole.png b/AXNode/Assets/Icon16/ClearConsole.png new file mode 100644 index 0000000..64eca20 Binary files /dev/null and b/AXNode/Assets/Icon16/ClearConsole.png differ diff --git a/AXNode/Assets/Icon16/CloseFile.png b/AXNode/Assets/Icon16/CloseFile.png new file mode 100644 index 0000000..a4be1fa Binary files /dev/null and b/AXNode/Assets/Icon16/CloseFile.png differ diff --git a/AXNode/Assets/Icon16/Console.png b/AXNode/Assets/Icon16/Console.png new file mode 100644 index 0000000..462686f Binary files /dev/null and b/AXNode/Assets/Icon16/Console.png differ diff --git a/AXNode/Assets/Icon16/ExpandAll.png b/AXNode/Assets/Icon16/ExpandAll.png new file mode 100644 index 0000000..464d67b Binary files /dev/null and b/AXNode/Assets/Icon16/ExpandAll.png differ diff --git a/AXNode/Assets/Icon16/FurlAll.png b/AXNode/Assets/Icon16/FurlAll.png new file mode 100644 index 0000000..b4e2e9c Binary files /dev/null and b/AXNode/Assets/Icon16/FurlAll.png differ diff --git a/AXNode/Assets/Icon16/NewFile.png b/AXNode/Assets/Icon16/NewFile.png new file mode 100644 index 0000000..79d52bf Binary files /dev/null and b/AXNode/Assets/Icon16/NewFile.png differ diff --git a/AXNode/Assets/Icon16/Node/CPU.png b/AXNode/Assets/Icon16/Node/CPU.png new file mode 100644 index 0000000..263aa5c Binary files /dev/null and b/AXNode/Assets/Icon16/Node/CPU.png differ diff --git a/AXNode/Assets/Icon16/Node/Calculator.png b/AXNode/Assets/Icon16/Node/Calculator.png new file mode 100644 index 0000000..44b3b7b Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Calculator.png differ diff --git a/AXNode/Assets/Icon16/Node/Chip.png b/AXNode/Assets/Icon16/Node/Chip.png new file mode 100644 index 0000000..3b02f40 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Chip.png differ diff --git a/AXNode/Assets/Icon16/Node/Delay.png b/AXNode/Assets/Icon16/Node/Delay.png new file mode 100644 index 0000000..2972b1f Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Delay.png differ diff --git a/AXNode/Assets/Icon16/Node/Fan.png b/AXNode/Assets/Icon16/Node/Fan.png new file mode 100644 index 0000000..aefad14 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Fan.png differ diff --git a/AXNode/Assets/Icon16/Node/Flow.png b/AXNode/Assets/Icon16/Node/Flow.png new file mode 100644 index 0000000..44382f0 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Flow.png differ diff --git a/AXNode/Assets/Icon16/Node/FrameDriver.png b/AXNode/Assets/Icon16/Node/FrameDriver.png new file mode 100644 index 0000000..9363e36 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/FrameDriver.png differ diff --git a/AXNode/Assets/Icon16/Node/Function.png b/AXNode/Assets/Icon16/Node/Function.png new file mode 100644 index 0000000..baea615 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Function.png differ diff --git a/AXNode/Assets/Icon16/Node/Key.png b/AXNode/Assets/Icon16/Node/Key.png new file mode 100644 index 0000000..3ca162d Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Key.png differ diff --git a/AXNode/Assets/Icon16/Node/Loop.png b/AXNode/Assets/Icon16/Node/Loop.png new file mode 100644 index 0000000..32f0369 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Loop.png differ diff --git a/AXNode/Assets/Icon16/Node/Message.png b/AXNode/Assets/Icon16/Node/Message.png new file mode 100644 index 0000000..1be99ac Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Message.png differ diff --git a/AXNode/Assets/Icon16/Node/Midi.png b/AXNode/Assets/Icon16/Node/Midi.png new file mode 100644 index 0000000..902bf01 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Midi.png differ diff --git a/AXNode/Assets/Icon16/Node/Net.png b/AXNode/Assets/Icon16/Node/Net.png new file mode 100644 index 0000000..56b0448 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Net.png differ diff --git a/AXNode/Assets/Icon16/Node/Node.png b/AXNode/Assets/Icon16/Node/Node.png new file mode 100644 index 0000000..8147874 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Node.png differ diff --git a/AXNode/Assets/Icon16/Node/Pause.png b/AXNode/Assets/Icon16/Node/Pause.png new file mode 100644 index 0000000..09c26cc Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Pause.png differ diff --git a/AXNode/Assets/Icon16/Node/Spectrum.png b/AXNode/Assets/Icon16/Node/Spectrum.png new file mode 100644 index 0000000..4e8a274 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Spectrum.png differ diff --git a/AXNode/Assets/Icon16/Node/Timer.png b/AXNode/Assets/Icon16/Node/Timer.png new file mode 100644 index 0000000..c0e5ccc Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Timer.png differ diff --git a/AXNode/Assets/Icon16/Node/Variate.png b/AXNode/Assets/Icon16/Node/Variate.png new file mode 100644 index 0000000..b088cf0 Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Variate.png differ diff --git a/AXNode/Assets/Icon16/Node/Volume.png b/AXNode/Assets/Icon16/Node/Volume.png new file mode 100644 index 0000000..775c9af Binary files /dev/null and b/AXNode/Assets/Icon16/Node/Volume.png differ diff --git a/AXNode/Assets/Icon16/OpenFile.png b/AXNode/Assets/Icon16/OpenFile.png new file mode 100644 index 0000000..6221129 Binary files /dev/null and b/AXNode/Assets/Icon16/OpenFile.png differ diff --git a/AXNode/Assets/Icon16/Redo.png b/AXNode/Assets/Icon16/Redo.png new file mode 100644 index 0000000..2c68f91 Binary files /dev/null and b/AXNode/Assets/Icon16/Redo.png differ diff --git a/AXNode/Assets/Icon16/Rename.png b/AXNode/Assets/Icon16/Rename.png new file mode 100644 index 0000000..ecdb93c Binary files /dev/null and b/AXNode/Assets/Icon16/Rename.png differ diff --git a/AXNode/Assets/Icon16/Revert.png b/AXNode/Assets/Icon16/Revert.png new file mode 100644 index 0000000..8c0e823 Binary files /dev/null and b/AXNode/Assets/Icon16/Revert.png differ diff --git a/AXNode/Assets/Icon16/Save.png b/AXNode/Assets/Icon16/Save.png new file mode 100644 index 0000000..be4c200 Binary files /dev/null and b/AXNode/Assets/Icon16/Save.png differ diff --git a/AXNode/Assets/Icon16/SaveAll.png b/AXNode/Assets/Icon16/SaveAll.png new file mode 100644 index 0000000..6d8f19e Binary files /dev/null and b/AXNode/Assets/Icon16/SaveAll.png differ diff --git a/AXNode/Assets/Icon16/SaveAs.png b/AXNode/Assets/Icon16/SaveAs.png new file mode 100644 index 0000000..758d08a Binary files /dev/null and b/AXNode/Assets/Icon16/SaveAs.png differ diff --git a/AXNode/Assets/Icon16/Undo.png b/AXNode/Assets/Icon16/Undo.png new file mode 100644 index 0000000..f924deb Binary files /dev/null and b/AXNode/Assets/Icon16/Undo.png differ diff --git a/AXNode/Assets/PinIcons.axaml b/AXNode/Assets/PinIcons.axaml new file mode 100644 index 0000000..68d7bb2 --- /dev/null +++ b/AXNode/Assets/PinIcons.axaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/AXNode/CoreEditer.axaml b/AXNode/CoreEditer.axaml new file mode 100644 index 0000000..459832e --- /dev/null +++ b/AXNode/CoreEditer.axaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/CoreEditer.axaml.cs b/AXNode/CoreEditer.axaml.cs new file mode 100644 index 0000000..22284de --- /dev/null +++ b/AXNode/CoreEditer.axaml.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.ProjectSystem; +using XLib.Base; +using XLib.Base.VirtualDisk; + +namespace AXNode +{ + public partial class CoreEditer : UserControl + { + #region 属性 + + /// 节点列表 + public List NodeList => Panel_NodeEditer.NodeList; + + #endregion + + #region 构造方法 + + public CoreEditer() + { + InitializeComponent(); + Loaded += CoreEditer_Loaded; + } + + #endregion + + #region 公开方法 + + /// + /// 重置编辑器 + /// + public void ResetEditer() => Panel_NodeEditer.Reset(); + + /// + /// 加载节点 + /// + public void LoadNode(NodeBase node) => Panel_NodeEditer.LoadNode(node); + + /// + /// 查找引脚 + /// + public PinBase? FindPin(PinPath path) => Panel_NodeEditer.FindPin(path); + + #endregion + + #region 控件事件 + + private void CoreEditer_Loaded(object sender, RoutedEventArgs e) + { + Init(); + ProjectManager.Instance.NewProject(); + ProjectManager.Instance.Saved = true; + } + + #endregion + + #region 私有方法 + + /// + /// 初始化核心编辑器 + /// + private void Init() + { + // 初始化编辑器面板 + Panel_NodeEditer.Init(); + // 初始化节点库面板 + Panel_NodeLib.Init(); + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/MainWindow.axaml b/AXNode/MainWindow.axaml new file mode 100644 index 0000000..789df00 --- /dev/null +++ b/AXNode/MainWindow.axaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/MainWindow.axaml.cs b/AXNode/MainWindow.axaml.cs new file mode 100644 index 0000000..865e7d1 --- /dev/null +++ b/AXNode/MainWindow.axaml.cs @@ -0,0 +1,259 @@ +using System; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using Avalonia.Styling; +using AXNode.AppTool; +using AXNode.Sample.Layer; +using AXNode.SubSystem.CacheSystem; +using AXNode.SubSystem.EventSystem; +using AXNode.SubSystem.ProjectSystem; +using AXNode.SubSystem.ResourceSystem; +using AXNode.SubSystem.WindowSystem; +using XLib.Avalonia.WindowDefine; +using XLib.AvaloniaControl; +using XLib.Base; + +namespace AXNode; + +public partial class MainWindow : Window +{ + #region 属性 + + /// 核心编辑器实例 + public CoreEditer Editer + { + get + { + if (_coreEditer == null) throw new Exception("核心编辑器为空"); + return _coreEditer; + } + } + + #endregion + + #region 构造方法 + + public MainWindow() => InitializeComponent(); + + #endregion + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + Closing += OnClosing; + WindowLoaded(); + AddHandler(KeyDownEvent, (sender, args) => { Keyboard.RegisterModifiers(args.KeyModifiers); }, + RoutingStrategies.Tunnel); + AddHandler(PointerMovedEvent, (sender, args) => + { + Mouse.RegisterPointerEventArgs(args); + Mouse.RegisterDirectlyOver(args); + }, RoutingStrategies.Tunnel); + } + + private async void OnClosing(object? sender, WindowClosingEventArgs e) + { + e.Cancel = await ReadyClose() == true; + } + + + #region XMainWindow 方法 + + protected void WindowLoaded() + { + // 恢复窗口状态并监听窗口状态 + RecoverWindowState(); + ListenWindowState(); + + // 设置主窗口实例 + WM.Main = this; + + // 初始化工具栏 + // InitToolBar(); + + // 加载核心编辑器 + LoadCoreEditer(); + + // 监听系统事件 + EM.Instance.Add(EventType.Project_Changed, UpdateTitle); + } + + protected async Task ReadyClose() + { + // 项目未保存 + if (!ProjectManager.Instance.Saved) + { + bool? result = await WM.ShowAsk("当前项目未保存,是否保存?"); + // 保存 + if (result == true) + { + bool saved = ProjectManager.Instance.SaveProject(); + // 确定保存,但未执行,则取消操作 + if (!saved) return false; + } + // 取消操作 + else if (result == null) return false; + } + + // 关闭项目 + ProjectManager.Instance.CloseProject(); + + return true; + } + + #endregion + + #region 窗口事件 + + private void XMainWindow_PreviewKeyDown(object? sender, KeyEventArgs e) + { + EM.Instance.Invoke(EventType.KeyDown, e.Key.ToString()); + } + + private void XMainWindow_PreviewKeyUp(object? sender, KeyEventArgs e) + { + EM.Instance.Invoke(EventType.KeyUp, e.Key.ToString()); + } + + #endregion + + #region 私有方法 + + /// + /// 恢复窗口状态 + /// + private void RecoverWindowState() + { + WindowState = CacheManager.Instance.Cache.MainWindow.State; + var screenFromVisual = this.Screens.ScreenFromVisual(this); + // 居中窗口 + Bounds = new Rect((screenFromVisual.WorkingArea.Width - Width) / 2, + (screenFromVisual.WorkingArea.Height - Height) / 2, CacheManager.Instance.Cache.MainWindow.Width, + CacheManager.Instance.Cache.MainWindow.Height); + } + + /// + /// 监听窗口状态 + /// + private void ListenWindowState() + { + // StateChanged += (s, e) => + // { + // if (WindowState is WindowState.Normal or WindowState.Maximized) + // { + // CacheManager.Instance.Cache.MainWindow.State = WindowState; + // CacheManager.Instance.UpdateCache(); + // } + // }; + SizeChanged += (s, e) => + { + if (WindowState == WindowState.Maximized) return; + CacheManager.Instance.Cache.MainWindow.Width = (int)Width; + CacheManager.Instance.Cache.MainWindow.Height = (int)Height; + CacheManager.Instance.UpdateCache(); + }; + } + + + /// + /// 工具栏.单击工具 + /// + private void ToolBar_ToolClick(string name) + { + switch (name) + { + // 新建项目 + case "NewProject": + ProjectManager.Instance.NewProject(); + UpdateTitle(); + break; + // 打开项目 + case "OpenProject": + ProjectManager.Instance.OpenProject(); + UpdateTitle(); + break; + // 保存项目 + case "SaveProject": + ProjectManager.Instance.SaveProject(); + UpdateTitle(); + break; + // 另存为项目 + case "SaveAs": + ProjectManager.Instance.SaveAsProject(); + UpdateTitle(); + break; + // 撤销 + case "Undo": + break; + // 重做 + case "Redo": + break; + // 控制台 + case "Console": + if (_consoleOpened) + { + OSTool.CloseConsole(); + _consoleOpened = false; + } + else + { + OSTool.OpenConsole(); + _consoleOpened = true; + } + + break; + // 清空控制台 + case "ClearConsole": + Console.Clear(); + break; + } + } + + /// + /// 加载核心编辑器 + /// + private void LoadCoreEditer() + { + _coreEditer = new CoreEditer { Margin = new Thickness(0, 2, 0, 0) }; + MainGrid.Children.Add(_coreEditer); + } + + /// + /// 更新标题 + /// + private void UpdateTitle() + { + if (ProjectManager.Instance.CurrentProject != null) + { + Title = ProjectManager.Instance.CurrentProject.ProjectName; + if (!ProjectManager.Instance.Saved) Title += "*"; + Title += " - " + AppDelegate.AppTitle; + } + else Title = AppDelegate.AppTitle; + } + + #endregion + + #region 字段 + + /// 工具栏 + // private ToolBar? _toolBar = null; + + /// 核心编辑器 + private CoreEditer? _coreEditer = null; + + /// 控制台已打开 + private bool _consoleOpened = false; + + #endregion + + + private void MenuItem_OnClick(object? sender, RoutedEventArgs e) + { + ToolBar_ToolClick((sender as MenuItem).Tag.ToString()); + } +} \ No newline at end of file diff --git a/AXNode/Program.cs b/AXNode/Program.cs new file mode 100644 index 0000000..fa46a1b --- /dev/null +++ b/AXNode/Program.cs @@ -0,0 +1,21 @@ +using Avalonia; +using System; + +namespace AXNode; + +class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(); +} \ No newline at end of file diff --git a/AXNode/Sample/Layer/LoadingLayer.cs b/AXNode/Sample/Layer/LoadingLayer.cs new file mode 100644 index 0000000..7a1ee64 --- /dev/null +++ b/AXNode/Sample/Layer/LoadingLayer.cs @@ -0,0 +1,104 @@ +using Avalonia; +using Avalonia.Media; +using Avalonia.Threading; +using XLib.Animate; +using XLib.Avalonia.Drawing; +using XLib.Math.Easing; + +namespace AXNode.Sample.Layer +{ + /// + /// 加载图层。播放加载动画,测试动画用 + /// + public class LoadingLayer : SingleBoard, IMotion + { + public void Start() + { + // 创建并添加动画队列 + _group.Add(this.CreateAnimation("offset", 0, 200, 4300, loop: true)); + _group.Add(CreatQueue("x1", 0, 0, 200, 400, 800)); + _group.Add(CreatQueue("x2", 200, 0 - 8, 200 - 8, 400 - 8, 600)); + _group.Add(CreatQueue("x3", 400, 0 - 16, 200 - 16, 400 - 16, 400)); + _group.Add(CreatQueue("x4", 600, 0 - 24, 200 - 24, 400 - 24, 200)); + _group.Add(CreatQueue("x5", 800, 0 - 32, 200 - 32, 400 - 32, 0)); + // 添加动画 + AnimationEngine.Instance.AddAnimation(_group); + } + + private AnimationQueue CreatQueue(string property, double leftDelay, double x1, double x2, double x3, + double rightDelay) + { + AnimationQueue queue = new AnimationQueue { Loop = true }; + queue.AnimationList.Add(new AnimationDelay { Duration = leftDelay }); + queue.AnimationList.Add(this.CreateAnimation(property, x1, x2, 1000, EasingType.QuinticEase, + EasingMode.EaseOut)); + queue.AnimationList.Add(new AnimationDelay { Duration = 500 }); + queue.AnimationList.Add(this.CreateAnimation(property, x2, x3, 1000, EasingType.QuinticEase, + EasingMode.EaseIn)); + queue.AnimationList.Add(new AnimationDelay { Duration = 1000 }); + queue.AnimationList.Add(new AnimationDelay { Duration = rightDelay }); + queue.Init(); + return queue; + } + + private void Group_Finished(IAnimation sender) + { + Dispatcher.UIThread.Invoke(Clear); + } + + public void Stop() + { + _group.Stop(); + } + + // public override void Init() => _brush.Freeze(); + public override void Render(DrawingContext context) + { + DrawVertex(context, new Point(_x1 + _offset, 0), 4, _brush, null); + DrawVertex(context, new Point(_x2 + _offset, 0), 4, _brush, null); + DrawVertex(context, new Point(_x3 + _offset, 0), 4, _brush, null); + DrawVertex(context, new Point(_x4 + _offset, 0), 4, _brush, null); + DrawVertex(context, new Point(_x5 + _offset, 0), 4, _brush, null); + } + + public double GetMotionProperty(string propertyName) => 0; + + public void SetMotionProperty(string propertyName, double value) + { + switch (propertyName) + { + case "offset": + _offset = value; + break; + case "x1": + _x1 = value; + break; + case "x2": + _x2 = value; + break; + case "x3": + _x3 = value; + break; + case "x4": + _x4 = value; + break; + case "x5": + _x5 = value; + break; + } + + Dispatcher.UIThread.Invoke(Update); + } + + private double _offset = 0; + private double _x1 = 0; + private double _x2 = 0; + private double _x3 = 0; + private double _x4 = 0; + private double _x5 = 0; + + private AnimationGroup _group = new AnimationGroup(); + + private readonly IBrush _brush = new SolidColorBrush(Colors.Black); + } +} \ No newline at end of file diff --git a/AXNode/Sample/Layer/TargetPointLayer.cs b/AXNode/Sample/Layer/TargetPointLayer.cs new file mode 100644 index 0000000..47137cb --- /dev/null +++ b/AXNode/Sample/Layer/TargetPointLayer.cs @@ -0,0 +1,59 @@ +using System; +using Avalonia; +using Avalonia.Media; +using Avalonia.Threading; +using XLib.Animate; +using XLib.Avalonia.Drawing; +using XLib.Math.Easing; + +namespace XLib.Sample.Layer +{ + /// + /// 目标点图层。测试动画用 + /// + public class TargetPointLayer : SingleBoard, IMotion + { + public Point TargetPoint { get; set; } = new Point(); + + public override void Render(DrawingContext context) + { + Opacity = _opacity; + context.DrawEllipse(null, _pen, TargetPoint, _size / 2, _size / 2); + } + + public double GetMotionProperty(string propertyName) => 0; + + public void SetMotionProperty(string propertyName, double value) + { + switch (propertyName) + { + case "Size": + _size = value; + Console.WriteLine(_size); + break; + case "Alpha": + _opacity = value; + break; + } + + Dispatcher.UIThread.Invoke(Update); + } + + public void SetTargetPoint(Point point) + { + TargetPoint = point; + // 创建动画组 + AnimationGroup group = new AnimationGroup(); + group.Add( + this.CreateAnimation("Size", 0, 400, 2800, EasingType.QuinticEase, EasingMode.EaseOut, loop: true)); + group.Add(this.CreateAnimation("Alpha", 1, 0, 2800, loop: true)); + // 添加动画组 + AnimationEngine.Instance.AddAnimation(group); + } + + private double _size = 0; + private double _opacity = 1; + + private readonly Pen _pen = new Pen(Brushes.Black, 1); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/ArchiveManager.cs b/AXNode/SubSystem/ArchiveSystem/ArchiveManager.cs new file mode 100644 index 0000000..dcca4c3 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/ArchiveManager.cs @@ -0,0 +1,148 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; +using System.Text; +using XLib.Base.ArchiveFrame; +using AXNode.SubSystem.ArchiveSystem.Define.Data_1_0; +using AXNode.SubSystem.ArchiveSystem.Loader; +using AXNode.SubSystem.WindowSystem; + +namespace AXNode.SubSystem.ArchiveSystem +{ + /// + /// 存档管理器 + /// + public class ArchiveManager + { + #region 单例 + + private ArchiveManager() + { + } + + public static ArchiveManager Instance { get; } = new ArchiveManager(); + + #endregion + + #region 属性 + + /// 当前版本 + public string CurrentVersion { get; private set; } = "1.0"; + + #endregion + + #region 公开方法 + + /// + /// 生成存档 + /// + public ArchiveFile GenerateArchive() + { + return new ArchiveFile + { + Version = CurrentVersion, + Data = Extracter.Extract(), + }; + } + + /// + /// 读取存档文件 + /// + public ArchiveFile? ReadArchiveFile(string filePath) + { + string jsonData = File.ReadAllText(filePath, Encoding.UTF8); + return JsonConvert.DeserializeObject(jsonData); + } + + /// + /// 加载存档 + /// + public bool LoadArchive(ArchiveFile file, string path, JObject originData) + { + // 检查存档版本 + if (!CheckVersion(file)) + { + WM.ShowError($"读取存档“{path}”失败:无效的版本"); + return false; + } + + // 比较版本,过新则不加载 + int result = CompareVersion(file.Version); + if (result < 0) + { + WM.ShowTip("存档版本过新,请升级软件后重试"); + return false; + } + + // 导入存档数据 + if (!ImportArchiveData(file, originData, path)) + { + WM.ShowError($"存档“{path}”加载失败"); + return false; + } + + return true; + } + + #endregion + + #region 私有方法 + + /// + /// 检查版本 + /// + private bool CheckVersion(ArchiveFile file) + { + if (string.IsNullOrEmpty(file.Version) || file.Version == "???") return false; + try + { + ArchiveVersion version = new ArchiveVersion(file.Version); + } + catch (Exception) + { + return false; + } + + return true; + } + + /// + /// 比较版本 + /// + private int CompareVersion(string version) + { + ArchiveVersion file = new ArchiveVersion(version); + ArchiveVersion current = new ArchiveVersion(CurrentVersion); + return file.CompareTo(current); + } + + /// + /// 导入存档数据 + /// + private bool ImportArchiveData(ArchiveFile file, JObject originData, string archiveFilePath) + { + try + { + switch (file.Version) + { + case "1.0": + Data_1_0? data_1_0 = originData.ToObject(); + if (data_1_0 == null) return false; + if (!Loader_1_0.Import(data_1_0, archiveFilePath)) return false; + break; + default: + return false; + } + + return true; + } + catch (Exception) + { + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/ConnectLineData.cs b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/ConnectLineData.cs new file mode 100644 index 0000000..9769eb9 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/ConnectLineData.cs @@ -0,0 +1,14 @@ +namespace AXNode.SubSystem.ArchiveSystem.Define.Data_1_0 +{ + /// + /// 连接线数据 + /// + public class ConnectLineData + { + public string Start { get; set; } = ""; + + public string End { get; set; } = ""; + + public override string ToString() => $"{Start}-{End}"; + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/Data_1_0.cs b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/Data_1_0.cs new file mode 100644 index 0000000..5260b61 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/Data_1_0.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace AXNode.SubSystem.ArchiveSystem.Define.Data_1_0 +{ + /// + /// 存档数据 + /// + public class Data_1_0 + { + /// 节点列表 + public List NodeList { get; set; } = new List(); + + /// 连接线列表 + public List ConnectLineList { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeBaseData.cs b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeBaseData.cs new file mode 100644 index 0000000..6f93089 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeBaseData.cs @@ -0,0 +1,36 @@ +namespace AXNode.SubSystem.ArchiveSystem.Define.Data_1_0 +{ + public class NodeBaseData + { + public NodeBaseData() + { + } + + public NodeBaseData(string data) + { + string[] array = data.Split('/'); + NodeLibName = array[0]; + TypeString = array[1]; + Version = array[2]; + ID = int.Parse(array[3]); + Point = array[4]; + } + + /// 节点库名称 + public string NodeLibName { get; set; } = "Inner"; + + /// 类型字符串 + public string TypeString { get; set; } = ""; + + /// 节点版本 + public string Version { get; set; } = "1.0"; + + /// 编号 + public int ID { get; set; } = -1; + + /// 坐标 + public string Point { get; set; } = "0,0"; + + public override string ToString() => $"{NodeLibName}/{TypeString}/{Version}/{ID}/{Point}"; + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeData.cs b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeData.cs new file mode 100644 index 0000000..21a6404 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Define/Data_1_0/NodeData.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace AXNode.SubSystem.ArchiveSystem.Define.Data_1_0 +{ + /// + /// 节点数据 + /// + public class NodeData + { + /// 基本数据 + public string BaseData { get; set; } = ""; + + /// 参数表 + public Dictionary ParaDict { get; set; } = new Dictionary(); + + /// 属性表 + public Dictionary PropertyDict { get; set; } = new Dictionary(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Extracter.cs b/AXNode/SubSystem/ArchiveSystem/Extracter.cs new file mode 100644 index 0000000..864c69b --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Extracter.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using XLib.Node; +using AXNode.AppTool; +using AXNode.SubSystem.ArchiveSystem.Define.Data_1_0; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.WindowSystem; + +namespace AXNode.SubSystem.ArchiveSystem; + +public class Extracter +{ + /// + /// 提取存档数据 + /// + public static Data_1_0 Extract() + { + Data_1_0 result = new Data_1_0(); + + FillNodeList(result); + FillConnectLineList(result); + + return result; + } + + /// + /// 填充节点列表 + /// + private static void FillNodeList(Data_1_0 data) + { + // 遍历节点 + foreach (var node in WM.Main.Editer.NodeList) + { + // 基本数据 + NodeBaseData baseData = new NodeBaseData + { + NodeLibName = node.NodeLibName, + TypeString = node.GetTypeString(), + Version = node.Version, + ID = node.ID, + Point = node.Point.ToString(), + }; + // 参数与属性 + NodeData nodeData = new NodeData + { + BaseData = baseData.ToString(), + ParaDict = node.GetParaDict(), + PropertyDict = node.GetPropertyDict(), + }; + // 添加数据 + data.NodeList.Add(JsonConvert.SerializeObject(nodeData)); + } + } + + /// + /// 填充连接线列表 + /// + private static void FillConnectLineList(Data_1_0 data) + { + // 连接信息 + Dictionary> connectInfo = new Dictionary>(); + // 遍历节点,填充连接信息 + foreach (var node in WM.Main.Editer.NodeList) + { + // 遍历全部引脚 + foreach (var pin in node.GetAllPin()) + { + // 忽略输入引脚与空输出引脚 + if (pin.Flow == PinFlow.Input || pin.TargetList.Count == 0) continue; + // 添加连接源 + if (!connectInfo.ContainsKey(pin)) connectInfo.Add(pin, new HashSet()); + // 添加连接目标 + foreach (var target in pin.TargetList) connectInfo[pin].Add(target); + } + } + + // 遍历连接信息,填充连接线数据 + foreach (var pair in connectInfo) + { + PinPath startPath = pair.Key.GetPinPath(); + foreach (var targetPin in pair.Value) + { + PinPath endPath = targetPin.GetPinPath(); + ConnectLineData lineData = new ConnectLineData + { + Start = startPath.ToString(), + End = endPath.ToString(), + }; + data.ConnectLineList.Add(lineData.ToString()); + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ArchiveSystem/Loader/Loader_1_0.cs b/AXNode/SubSystem/ArchiveSystem/Loader/Loader_1_0.cs new file mode 100644 index 0000000..d472742 --- /dev/null +++ b/AXNode/SubSystem/ArchiveSystem/Loader/Loader_1_0.cs @@ -0,0 +1,87 @@ +using System; +using Newtonsoft.Json; +using XLib.Node; +using AXNode.SubSystem.ArchiveSystem.Define.Data_1_0; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.NodeLibSystem; +using AXNode.SubSystem.WindowSystem; + +namespace AXNode.SubSystem.ArchiveSystem.Loader +{ + public class Loader_1_0 + { + public static bool Import(Data_1_0 data, string archiveFilePath) + { + try + { + LoadNodeList(data); + LoadConnectLineList(data); + } + catch (Exception ex) + { +#if DEBUG + throw ex; +#endif + WM.ShowError("加载存档失败:" + ex.Message); + return false; + } + + return true; + } + + /// + /// 加载节点列表 + /// + private static void LoadNodeList(Data_1_0 data) + { + foreach (var nodeString in data.NodeList) + { + // 解析节点数据 + NodeData? nodeData = JsonConvert.DeserializeObject(nodeString); + if (nodeData == null) continue; + // 解析节点基本数据 + NodeBaseData? nodeBaseData = new NodeBaseData(nodeData.BaseData); + if (nodeBaseData == null) continue; + // 创建节点实例 + NodeBase? node; + if (nodeBaseData.NodeLibName == "Inner") + node = NodeLibManager.Instance.CreateNode(nodeBaseData.TypeString); + else + node = NodeLibManager.Instance.CreateNode(nodeBaseData.NodeLibName, nodeBaseData.TypeString); + // 创建节点失败,则跳过 + if (node == null) continue; + // 初始化节点 + node.Init(); + // 设置编号与坐标 + node.ID = nodeBaseData.ID; + node.Point = new NodePoint(nodeBaseData.Point); + // 加载参数、属性 + node.LoadParaDict(nodeBaseData.Version, nodeData.ParaDict); + node.LoadPropertyDict(nodeBaseData.Version, nodeData.PropertyDict); + // 加载节点至编辑器 + WM.Main.Editer.LoadNode(node); + } + } + + /// + /// 加载连接线列表 + /// + private static void LoadConnectLineList(Data_1_0 data) + { + foreach (var lineString in data.ConnectLineList) + { + // 解析引脚路径 + PinPath startPath = PinPath.ParsePinPath(lineString.Split('-')[0]); + PinPath endPath = PinPath.ParsePinPath(lineString.Split('-')[1]); + // 查找引脚 + PinBase? startPin = WM.Main.Editer.FindPin(startPath); + if (startPin == null) continue; + PinBase? endPin = WM.Main.Editer.FindPin(endPath); + if (endPin == null) continue; + // 连接引脚 + startPin.AddTarget(endPin); + endPin.AddSource(startPin); + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/CacheSystem/CacheData.cs b/AXNode/SubSystem/CacheSystem/CacheData.cs new file mode 100644 index 0000000..380650c --- /dev/null +++ b/AXNode/SubSystem/CacheSystem/CacheData.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Windows; +using Avalonia.Controls; +using AXNode.SubSystem.CacheSystem; + +namespace AXNode.SubSystem.CacheSystem +{ + public class MainWindowCache + { + public WindowState State { get; set; } = WindowState.Normal; + public int Width { get; set; } = 1280; + public int Height { get; set; } = 720; + } + + /// + /// 节点库缓存 + /// + public class NodeLibCache + { + public List ExpandedFolderList { get; set; } = new List(); + + /// + /// 判断指定的文件夹是否处于展开状态 + /// + public bool IsExpanded(string folderPath) => ExpandedFolderList.Contains(folderPath); + + /// + /// 折叠指定文件夹 + /// + public void Fold(string folderPath) + { + if (ExpandedFolderList.Contains(folderPath)) ExpandedFolderList.Remove(folderPath); + CacheManager.Instance.UpdateCache(); + } + + /// + /// 展开指定文件夹 + /// + public void Expand(string folderPath) + { + if (!ExpandedFolderList.Contains(folderPath)) ExpandedFolderList.Add(folderPath); + CacheManager.Instance.UpdateCache(); + } + } + + public class CacheData + { + public MainWindowCache MainWindow { get; set; } = new MainWindowCache(); + + public NodeLibCache NodeLib { get; set; } = new NodeLibCache(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/CacheSystem/CacheManager.cs b/AXNode/SubSystem/CacheSystem/CacheManager.cs new file mode 100644 index 0000000..0040dc6 --- /dev/null +++ b/AXNode/SubSystem/CacheSystem/CacheManager.cs @@ -0,0 +1,92 @@ +using Newtonsoft.Json; +using System.IO; +using AXNode.SubSystem.CacheSystem; +using XLib.Base; +using AXNode.SubSystem.OptionSystem; + +namespace AXNode.SubSystem.CacheSystem +{ + /// + /// 缓存管理器 + /// + public class CacheManager : IManager + { + #region 单例 + + private CacheManager() + { + } + + public static CacheManager Instance { get; } = new CacheManager(); + + #endregion + + #region 属性 + + public string Name { get; set; } = "缓存管理器"; + + /// 缓存数据 + public CacheData Cache { get; set; } = new CacheData(); + + #endregion + + #region “IManager”方法 + + public void Init() + { + // 不存在缓存,则新建缓存 + if (!File.Exists($"{OptionManager.Instance.CachePath}\\{_cacheFileName}")) SaveCacheFile(); + // 加载缓存文件 + LoadCacheFile(); + } + + public void Reset() + { + } + + public void Clear() + { + } + + #endregion + + #region 公开方法 + + /// + /// 更新缓存 + /// + public void UpdateCache() => SaveCacheFile(); + + #endregion + + #region 私有方法 + + /// + /// 保存缓存文件 + /// + private void SaveCacheFile() + { + string filePath = $"{OptionManager.Instance.CachePath}\\{_cacheFileName}"; + File.WriteAllText(filePath, JsonConvert.SerializeObject(Cache, Formatting.Indented)); + } + + /// + /// 加载缓存文件 + /// + private void LoadCacheFile() + { + string jsonData = File.ReadAllText($"{OptionManager.Instance.CachePath}\\{_cacheFileName}"); + CacheData? cache = JsonConvert.DeserializeObject(jsonData); + if (cache != null) Cache = cache; + } + + #endregion + + #region 字段 + + /// 缓存文件名 + private readonly string _cacheFileName = "Cache.json"; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ControlSystem/ControlEngine.cs b/AXNode/SubSystem/ControlSystem/ControlEngine.cs new file mode 100644 index 0000000..c4b713b --- /dev/null +++ b/AXNode/SubSystem/ControlSystem/ControlEngine.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using XLib.Base.AppFrame; +using XLib.Base.Drive; +using AXNode.SubSystem.TimerSystem; + +namespace AXNode.SubSystem.ControlSystem +{ + public class ControlEngine : ServiceBase, ITimerHandler + { + #region 单例 + + private ControlEngine() + { + } + + public static ControlEngine Instance { get; } = new ControlEngine(); + + #endregion + + #region 生命周期 + + public override void Start() => TimeEngine.Instance.AddTimerHandler(this); + + public override void Stop() => TimeEngine.Instance.RemoveHandler(this); + + #endregion + + #region 接口实现 + + public void Tick() + { + foreach (var frameDriver in _frameDriverList) frameDriver.Update(); + } + + #endregion + + #region 公开方法 + + /// + /// 连接驱动器 + /// + public void Connect(IFrameDriver frameDriver) + { + if (_frameDriverList.Contains(frameDriver)) return; + _frameDriverList = new List(_frameDriverList) { frameDriver }; + } + + /// + /// 断开驱动器 + /// + public void Disconnect(IFrameDriver frameDriver) + { + List newList = new List(_frameDriverList); + newList.Remove(frameDriver); + _frameDriverList = newList; + } + + #endregion + + #region 字段 + + /// 帧驱动器列表 + private List _frameDriverList = new List(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/EventSystem/EM.cs b/AXNode/SubSystem/EventSystem/EM.cs new file mode 100644 index 0000000..87cfbd5 --- /dev/null +++ b/AXNode/SubSystem/EventSystem/EM.cs @@ -0,0 +1,68 @@ +using System.Windows; +using Avalonia.Threading; +using XLib.Base; +using AXNode.SubSystem.EventSystem; + +namespace AXNode.SubSystem.EventSystem +{ + public class EM : EM + { + #region 单例 + + private EM() + { + } + + public static EM Instance { get; } = new EM(); + + #endregion + + #region 引发事件 + + public void Invoke(EventType eventType) + { + InnerInvoke(eventType); + } + + public void Invoke(EventType eventType, T1 value1) + { + InnerInvoke(eventType, value1); + } + + public void Invoke(EventType eventType, T1 value1, T2 value2) + { + InnerInvoke(eventType, value1, value2); + } + + public void Invoke(EventType eventType, T1 value1, T2 value2, T3 value3) + { + InnerInvoke(eventType, value1, value2, value3); + } + + #endregion + + #region 引发事件 + + public void BeginInvoke(EventType eventType) + { + Dispatcher.UIThread.Invoke(() => InnerInvoke(eventType)); + } + + public void BeginInvoke(EventType eventType, T1 value1) + { + Dispatcher.UIThread.Invoke(() => InnerInvoke(eventType, value1)); + } + + public void BeginInvoke(EventType eventType, T1 value1, T2 value2) + { + Dispatcher.UIThread.Invoke(() => InnerInvoke(eventType, value1, value2)); + } + + public void BeginInvoke(EventType eventType, T1 value1, T2 value2, T3 value3) + { + Dispatcher.UIThread.Invoke(() => InnerInvoke(eventType, value1, value2, value3)); + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/EventSystem/EventType.cs b/AXNode/SubSystem/EventSystem/EventType.cs new file mode 100644 index 0000000..15fbfbb --- /dev/null +++ b/AXNode/SubSystem/EventSystem/EventType.cs @@ -0,0 +1,17 @@ +namespace AXNode.SubSystem.EventSystem +{ + public enum EventType + { + /// 按键按下 + KeyDown, + + /// 按键松开 + KeyUp, + + /// 项目已变更 + Project_Changed, + + /// 项目已加载 + Project_Loaded, + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml b/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml new file mode 100644 index 0000000..17cdb41 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml.cs new file mode 100644 index 0000000..cea56f7 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ActionPinGroupView.axaml.cs @@ -0,0 +1,54 @@ +using System; +using System.Windows; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using XLib.Node; +using AXNode.SubSystem.ResourceSystem; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class ActionPinGroupView : PinGroupViewBase + { + public ActionPinGroupView() => InitializeComponent(); + + public ActionPinGroup? Instance { get; set; } = null; + + public override void Init() + { + if (Instance == null) return; + Title_Pin.Text = Instance.ActionName; + Instance.ActionNameChanged = (name) => Title_Pin.Text = name; + Icon_Pin.Content = PinIconManager.Instance.ExecutePin_Null; + + PinArea.PointerEntered += PinArea_MouseEnter; + PinArea.PointerExited += PinArea_MouseLeave; + } + + public override Grid GetPinArea() + { + if (HoveredPin != null) return PinArea; + throw new Exception("无命中引脚"); + } + + private void PinArea_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = Instance.OutputPin; + } + + private void PinArea_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = null; + } + + public override Point GetPinOffset(NodeView card, int pinIndex) => + PinArea.TranslatePoint(new Point(14, 8), card) ?? new Point(0, 0); + + public override void UpdatePinIcon() + { + if (Instance.OutputPin.TargetList.Count == 0) Icon_Pin.Content = PinIconManager.Instance.ExecutePin_Null; + else Icon_Pin.Content = PinIconManager.Instance.ExecutePin; + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml b/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml new file mode 100644 index 0000000..920ce11 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml.cs new file mode 100644 index 0000000..58ec31e --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ControlPinGroupView.axaml.cs @@ -0,0 +1,22 @@ +using System.Windows; +using AXNode.SubSystem.NodeEditSystem.Control; +using XLib.Node; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + /// + /// 控件引脚组视图 + /// + public partial class ControlPinGroupView : PinGroupViewBase + { + public ControlPinGroupView() => InitializeComponent(); + + public ControlPinGroup? Instance { get; set; } = null; + + public override void Init() + { + if (Instance == null) return; + ControlBox.Children.Add((Avalonia.Controls.Control)Instance.ControlInstance); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml b/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml new file mode 100644 index 0000000..f3d019d --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml.cs new file mode 100644 index 0000000..547d4cd --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/DataPinGroupView.axaml.cs @@ -0,0 +1,148 @@ +using System; +using System.Windows; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Threading; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.ResourceSystem; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class DataPinGroupView : PinGroupViewBase + { + #region 构造方法 + + public DataPinGroupView() => InitializeComponent(); + + #endregion + + #region 属性 + + public DataPinGroup? Instance { get; set; } = null; + + #endregion + + #region 基类方法 + + public override void Init() + { + if (Instance == null) return; + + Block_Name.Text = Instance.Name; + // Block_Name.Text = Instance.Name + GetTypeName(); + Block_Name.Foreground = new SolidColorBrush(GetDataPinColor()); + Input_Value.Text = Instance.Value; + Input_Value.IsReadOnly = !Instance.CanInput; + // InputBoxArea.Width = new GridLength(Instance.BoxWidth); + + // 无输入引脚:隐藏引脚图标与区域 + if (Instance.InputPin == null) + { + Icon_LeftPin.IsVisible = false; + LeftPinArea.IsVisible = false; + } + // 设置图标 + else + { + Icon_LeftPin.Content = GetDataPinIcon(); + LeftPinArea.PointerEntered += LeftPinArea_MouseEnter; + LeftPinArea.PointerExited += PinArea_MouseLeave; + } + + // 无输出引脚:隐藏引脚图标与区域 + if (Instance.OutputPin == null) + { + Icon_RightPin.IsVisible = false; + RightPinArea.IsVisible = false; + } + // 设置图标 + else + { + Icon_RightPin.Content = GetDataPinIcon(); + RightPinArea.PointerEntered += RightPinArea_MouseEnter; + RightPinArea.PointerExited += PinArea_MouseLeave; + } + + Instance.ValueChanged += ValueChanged; + Input_Value.TextChanged += Input_Value_TextChanged; + } + + public override Grid GetPinArea() + { + if (Instance.InputPin != null && HoveredPin == Instance.InputPin) return LeftPinArea; + if (Instance.OutputPin != null && HoveredPin == Instance.OutputPin) return RightPinArea; + throw new Exception("无命中引脚"); + } + + public override Point GetPinOffset(NodeView card, int pinIndex) + { + if (pinIndex == 0) return LeftPinArea.TranslatePoint(new Point(3, 8), card) ?? throw new Exception(); + return RightPinArea.TranslatePoint(new Point(14, 8), card) ?? throw new Exception(); + } + + public override void UpdatePinIcon() + { + if (Instance.InputPin != null) + Icon_LeftPin.Content = GetDataPinIcon(Instance.InputPin.SourceList.Count > 0); + if (Instance.OutputPin != null) + Icon_RightPin.Content = GetDataPinIcon(Instance.OutputPin.TargetList.Count > 0); + } + + #endregion + + #region 控件事件 + + private void LeftPinArea_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = Instance.InputPin; + } + + private void RightPinArea_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = Instance.OutputPin; + } + + private void PinArea_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = null; + } + + #endregion + + #region 私有方法 + + private Color GetDataPinColor() + { + return Instance.Type switch + { + "int" => PinColorSet.Int, + "double" => PinColorSet.Double, + "string" => PinColorSet.String, + "bool" => PinColorSet.Bool, + "byte[]" => PinColorSet.ByteArray, + _ => Colors.White, + }; + } + + private Shape GetDataPinIcon(bool solid = false) + { + return PinIconManager.Instance.GetDataPinIcon(Instance.Type, solid); + } + + private void ValueChanged() + { + Dispatcher.UIThread.Invoke(() => Input_Value.Text = Instance.Value); + } + + private void Input_Value_TextChanged(object sender, TextChangedEventArgs e) + { + Instance.Value = Input_Value.Text; + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml b/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml new file mode 100644 index 0000000..49436ec --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml.cs new file mode 100644 index 0000000..8f0082d --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/ExecutePinGroupView.axaml.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using XLib.Node; +using AXNode.SubSystem.ResourceSystem; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class ExecutePinGroupView : PinGroupViewBase + { + public ExecutePinGroupView() => InitializeComponent(); + + public ExecutePinGroup? Instance { get; set; } = null; + + public override void Init() + { + if (Instance == null) return; + Icon_LeftPin.Content = PinIconManager.Instance.ExecutePin_Null; + Block_ExecuteDesc.Text = Instance.ExecuteDesc; + Icon_RightPin.Content = PinIconManager.Instance.ExecutePin_Null; + + LeftPinArea.PointerEntered += LeftPinArea_MouseEnter; + RightPinArea.PointerEntered += RightPinArea_MouseEnter; + LeftPinArea.PointerExited += PinArea_MouseLeave; + RightPinArea.PointerExited += PinArea_MouseLeave; + } + + public override Grid GetPinArea() + { + if (HoveredPin == Instance.InputPin) return LeftPinArea; + if (HoveredPin == Instance.OutputPin) return RightPinArea; + throw new Exception("无命中引脚"); + } + + private void LeftPinArea_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = Instance.InputPin; + } + + private void RightPinArea_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = Instance.OutputPin; + } + + private void PinArea_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + HoveredPin = null; + } + + public override Point GetPinOffset(NodeView card, int pinIndex) + { + if (pinIndex == 0) return LeftPinArea.TranslatePoint(new Point(3, 8), card) ?? throw new Exception(); + return RightPinArea.TranslatePoint(new Point(14, 8), card) ?? throw new Exception(); + } + + public override void UpdatePinIcon() + { + if (Instance.InputPin.SourceList.Count == 0) Icon_LeftPin.Content = PinIconManager.Instance.ExecutePin_Null; + else Icon_LeftPin.Content = PinIconManager.Instance.ExecutePin; + if (Instance.OutputPin.TargetList.Count == 0) + Icon_RightPin.Content = PinIconManager.Instance.ExecutePin_Null; + else Icon_RightPin.Content = PinIconManager.Instance.ExecutePin; + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml b/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml new file mode 100644 index 0000000..e051725 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml.cs new file mode 100644 index 0000000..6def469 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/HoverToolBar.axaml.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows; +using Avalonia.Controls; +using Avalonia.Interactivity; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class HoverToolBar : UserControl + { + public HoverToolBar() => InitializeComponent(); + + public event Action ToolClick; + + public void Init() + { + foreach (var item in Stack_ToolBar.Children) + if (item is Button button) + button.Click += Tool_Click; + } + + private void Tool_Click(object sender, RoutedEventArgs e) => ToolClick?.Invoke(((Button)sender).Name); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/BreakPin.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/BreakPin.png new file mode 100644 index 0000000..2d1e72f Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/BreakPin.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Delete.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Delete.png new file mode 100644 index 0000000..f648450 Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Delete.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Black.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Black.png new file mode 100644 index 0000000..6c555aa Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Black.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Green.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Green.png new file mode 100644 index 0000000..3804816 Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Green.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Red.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Red.png new file mode 100644 index 0000000..719b3b8 Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Light_Red.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Start.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Start.png new file mode 100644 index 0000000..2b0a7ff Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Start.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/Image/Stop.png b/AXNode/SubSystem/NodeEditSystem/Control/Image/Stop.png new file mode 100644 index 0000000..d7e93b2 Binary files /dev/null and b/AXNode/SubSystem/NodeEditSystem/Control/Image/Stop.png differ diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml b/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml new file mode 100644 index 0000000..87288d4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml.cs new file mode 100644 index 0000000..f050d84 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodeMap.axaml.cs @@ -0,0 +1,15 @@ +using Avalonia.Controls; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + /// + /// NodeMap.xaml 的交互逻辑 + /// + public partial class NodeMap : UserControl + { + public NodeMap() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml b/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml new file mode 100644 index 0000000..760a419 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml.cs new file mode 100644 index 0000000..19ca3ed --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.axaml.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using AXNode.SubSystem.NodeEditSystem.Control.PropertyBar; +using XLib.Node; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class NodePropertyPanel : UserControl + { + public NodePropertyPanel() + { + InitializeComponent(); + } + + /// 节点实例 + public NodeBase? Instance { get; set; } + + /// + /// 加载属性条 + /// + public void LoadPropertyBar() + { + if (Instance == null) return; + + foreach (var property in Instance.PropertyList) + { + var bar = CreatePropertyBar(property); + if (bar == null) continue; + + if (MainStackPanel.Children.Count > 0) bar.Margin = new Thickness(0, 10, 0, 0); + MainStackPanel.Children.Add(bar); + } + } + + /// + /// 清空属性条 + /// + public void ClearPropertyBar() => MainStackPanel.Children.Clear(); + + #region 控件事件 + + private void MainGrid_MouseWheel(object? sender, PointerWheelEventArgs e) + { + MainScrollBar.Value -= e.Delta.Y / 120 * 50; + } + + private void MainStackPanel_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (MainScrollBar != null) + { + MainScrollBar.ViewportSize = MainGrid.Height; + MainScrollBar.Maximum = MainStackPanel.Height - MainGrid.Height; + } + } + + private void MainScrollBar_ValueChanged(object? sender, + RangeBaseValueChangedEventArgs rangeBaseValueChangedEventArgs) + { + MainStackPanel.Margin = new Thickness(0, -MainScrollBar.Value, 0, 0); + } + + #endregion + + #region 私有方法 + + /// + /// 创建属性条 + /// + private PropertyBarBase? CreatePropertyBar(NodeProperty property) + { + PropertyBarBase? bar = null; + + switch (property.Type) + { + case "int": + bar = new TextInput(); + bar.Title = property.Name; + bar.LoadProperty(property.Value); + ((TextInput)bar).TextChanged += text => property.Value = text; + break; + case "label": + bar = new TextInput(); + bar.Title = property.Name; + bar.LoadProperty(property.Value); + break; + case "List": + ListSelecter selecter = new ListSelecter(); + bar = selecter; + List list = ((ListProperty)property).GetValueList(); + selecter.Title = property.Name; + selecter.LoadProperty(list, property.Value); + selecter.SelectionChanged += index => ((ListProperty)property).Index = index; + break; + case "CustomList": + ListEditer editer = new ListEditer(); + editer.Instance = (CustomListProperty)property; + editer.Init(); + bar = editer; + + break; + } + + return bar; + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml b/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml new file mode 100644 index 0000000..599624d --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml.cs new file mode 100644 index 0000000..b2eefc4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/NodeView.axaml.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Threading; +using AXNode.SubSystem.ResourceSystem; +using AXNode.SubSystem.TimerSystem; +using XLib.Avalonia.UI; +using XLib.Node; +using XLib.Avalonia.UI; +using XLib.AvaloniaControl; +using AXNode.AppTool; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.ResourceSystem; +using AXNode.SubSystem.TimerSystem; +using AXNode.SubSystem.WindowSystem; + +// using ProgressBar = XLib.AvaloniaControl.ProgressBar; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + public partial class NodeView : MoveableControl + { + #region 构造方法 + + public NodeView() => InitializeComponent(); + + #endregion + + #region 属性 + + /// 节点颜色 + public Color NodeColor + { + get => _nodeColor; + set + { + _nodeColor = value; + // Color_Start.Color = Color.FromArgb(255, _nodeColor.R, _nodeColor.G, _nodeColor.B); + // Color_End.Color = Color.FromArgb(0, _nodeColor.R, _nodeColor.G, _nodeColor.B); + NodeFillColor.Background = + new SolidColorBrush(Color.FromArgb(48, _nodeColor.R, _nodeColor.G, _nodeColor.B)); + } + } + + /// 节点实例 + public NodeBase NodeInstance { get; set; } + + /// + /// 悬停引脚 + /// + public PinBase? HoveredPin + { + get + { + foreach (var item in _pinGroupViewList) + if (item.HoveredPin != null) + return item.HoveredPin; + return null; + } + } + + #endregion + + #region 事件 + + /// 节点背景.鼠标进入 + public Action? NodeBackMouseEnter { get; set; } = null; + + /// 节点背景.鼠标离开 + public Action? NodeBackMouseLeave { get; set; } = null; + + /// 引脚组列表变更 + public Action? PinGroupListChanged { get; set; } = null; + + /// 节点变更 + public Action? NodeChanged { get; set; } = null; + + #endregion + + #region 公开方法 + + /// + /// 初始化 + /// + public void Init() + { + // 设置节点图标与标题 + NodeIcon.Source = ImageResManager.Instance.GetNodeIcon(NodeInstance.Icon); + Block_Title.Text = NodeInstance.Title; + // 加载引脚组 + foreach (var pinGroup in NodeInstance.PinGroupList) + { + PinGroupViewBase? pinView = CreatePinGroupView(pinGroup); + if (pinView == null) continue; + Stack_PinGroupList.Children.Add(pinView); + _pinGroupViewList.Add(pinView); + pinView.Init(); + } + + if (_pinGroupViewList.Count > 0) + _pinGroupViewList[0].Margin = new Thickness(0); + // 设置节点委托 + NodeInstance.OpenProgressBar = OpenProgressBar; + NodeInstance.CloseProgressBar = CloseProgress; + // 监听节点 + NodeInstance.StateChanged += Node_StateChanged; + NodeInstance.ExecuteError += Node_ExecuteError; + NodeInstance.ParaChanged += Node_ParaChanged; + NodeInstance.PropertyChanged += Node_PropertyChanged; + NodeInstance.PinGroupListChanged += Node_PinGroupListChanged; + // 设置定时器处理器委托 + _timerHandler.OnTick = TimerHandler_Tick; + } + + /// + /// 清理 + /// + public void Clear() + { + NodeInstance.StateChanged -= Node_StateChanged; + NodeInstance.ExecuteError -= Node_ExecuteError; + NodeInstance.ParaChanged -= Node_ParaChanged; + NodeInstance.PropertyChanged -= Node_PropertyChanged; + NodeInstance.PinGroupListChanged -= Node_PinGroupListChanged; + } + + /// + /// 获取悬停引脚的偏移 + /// + public Point GetHoveredPinOffset() + { + foreach (var groupView in _pinGroupViewList) + { + if (groupView.HoveredPin != null) + return groupView.GetHoveredPinOffset(); + } + + return new Point(); + } + + /// + /// 获取引脚相对于节点的坐标 + /// + public Point GetPinOffset(PinPath path) => _pinGroupViewList[path.GroupIndex].GetPinOffset(this, path.PinIndex); + + /// + /// 更新全部引脚图标 + /// + public void UpdateAllPinIcon() + { + foreach (var groupCard in _pinGroupViewList) groupCard.UpdatePinIcon(); + } + + /// + /// 获取可命中矩形区域 + /// + public Rect GetHittableRect() => + new Rect(Canvas.GetLeft(this) + 11, Canvas.GetTop(this), Bounds.Width - 22, Bounds.Height); + + #endregion + + #region MoveableControl 方法 + + protected override void OnOffsetChanged() => NodeInstance.Point = new NodePoint((int)Point.X, (int)Point.Y); + + #endregion + + #region 控件事件 + + private void NodeBack_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + NodeBackMouseEnter?.Invoke(this); + } + + private void NodeBack_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + NodeBackMouseLeave?.Invoke(this); + } + + #endregion + + #region 节点委托 + + private void OpenProgressBar(IProgressGetter progressGetter) + { + // 添加进度条控件 + Dispatcher.UIThread.Invoke(() => + { + // _progressBar = new ProgressBar + // { + // Height = 1, + // VerticalAlignment = VerticalAlignment.Bottom, + // IsHitTestVisible = false + // }; + NodeBack.Children.Add(_progressBar); + Grid.SetRow(_progressBar, 1); + }); + // 设置进度获取器 + _progressGetter = progressGetter; + // 启动实时刷新 + AppTimer.Instance.AddTimerHandler(_timerHandler); + } + + private void CloseProgress() + { + // 停止刷新 + AppTimer.Instance.RemoveTimerHandler(_timerHandler); + // 移除进度条控件 + Dispatcher.UIThread.Invoke(() => NodeBack.Children.Remove(_progressBar)); + _progressBar = null; + } + + #endregion + + #region 节点事件 + + private void Node_StateChanged() + { + AppDelegate.Invoke(() => + { + if (NodeInstance.State == NodeState.Enable) + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Green"); + else + { + if (NodeInstance.RunError) + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Red"); + else + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", + "Light_Black"); + } + }); + } + + private void Node_ExecuteError(Exception ex) + { + Dispatcher.UIThread.Invoke(() => WM.ShowError("节点执行异常:" + ex.Message)); + } + + private void Node_ParaChanged() => NodeChanged?.Invoke(); + + private void Node_PropertyChanged() => NodeChanged?.Invoke(); + + private void Node_PinGroupListChanged() + { + // 清空当前引脚组 + Stack_PinGroupList.Children.Clear(); + _pinGroupViewList.Clear(); + // 加载引脚组 + foreach (var pinGroup in NodeInstance.PinGroupList) + { + PinGroupViewBase? pinView = CreatePinGroupView(pinGroup); + if (pinView == null) continue; + Stack_PinGroupList.Children.Add(pinView); + _pinGroupViewList.Add(pinView); + pinView.Init(); + } + + if (_pinGroupViewList.Count > 0) + _pinGroupViewList[0].Margin = new Thickness(0); + + UpdateLayout(); + // 通知引脚组列表变更 + PinGroupListChanged?.Invoke(); + // 通知节点变更 + NodeChanged?.Invoke(); + } + + /// + /// 定时器.走动 + /// + private void TimerHandler_Tick() + { + if (_progressBar == null || _progressGetter == null) return; + // _progressBar.Progress = _progressGetter.GetProgress(); + } + + /// + /// 查找引脚组 + /// + private PinGroupBase? FindPinGroup(string title) + { + foreach (var pinGroup in NodeInstance.PinGroupList) + if (pinGroup.GetTitle() == title) + return pinGroup; + return null; + } + + #endregion + + #region 私有方法 + + /// + /// 创建引脚组视图 + /// + private PinGroupViewBase? CreatePinGroupView(PinGroupBase pinGroup) + { + switch (pinGroup.GroupType) + { + case PinGroupType.Execute: + return new ExecutePinGroupView + { + Instance = (ExecutePinGroup)pinGroup, + Margin = new Thickness(0, _pinItemInterval, 0, 0), + }; + case PinGroupType.Data: + return new DataPinGroupView + { + Instance = (DataPinGroup)pinGroup, + Margin = new Thickness(0, _pinItemInterval, 0, 0), + }; + case PinGroupType.Action: + return new ActionPinGroupView + { + Instance = (ActionPinGroup)pinGroup, + Margin = new Thickness(0, _pinItemInterval, 0, 0), + }; + case PinGroupType.Control: + return new ControlPinGroupView + { + Margin = new Thickness(0, _pinItemInterval, 0, 0), + Instance = (ControlPinGroup)pinGroup, + }; + } + + return null; + } + + #endregion + + #region 字段 + + private readonly int _pinItemInterval = 5; + + /// 引脚组列表 + private readonly List _pinGroupViewList = new List(); + + /// 引脚信息列表 + private readonly List _connectInfoList = new List(); + + private Color _nodeColor = Colors.White; + + /// 进度条控件 + private ProgressBar? _progressBar = null; + + /// 进度获取器 + private IProgressGetter? _progressGetter = null; + + /// 定时器处理器 + private readonly TimerHandler _timerHandler = new TimerHandler(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs b/AXNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs new file mode 100644 index 0000000..d276f17 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs @@ -0,0 +1,62 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using XLib.AvaloniaControl; +using XLib.Node; + +namespace AXNode.SubSystem.NodeEditSystem.Control +{ + /// + /// 引脚组视图基类 + /// + public class PinGroupViewBase : UserControl + { + /// 悬停引脚 + public PinBase? HoveredPin { get; set; } = null; + + /// + /// 初始化 + /// + public virtual void Init() + { + } + + /// + /// 获取引脚区域控件 + /// + public virtual Grid GetPinArea() => throw new NotImplementedException(); + + /// + /// 获取悬停引脚相对于鼠标的偏移量 + /// + public Point GetHoveredPinOffset() + { + Grid pinArea = GetPinArea(); + Point offset = Mouse.GetPosition(pinArea); + if (HoveredPin.Flow == PinFlow.Input) + { + offset = new(3 - offset.X, offset.Y); + offset = new(offset.X, 8 - offset.Y); + } + else + { + offset = new(14 - offset.X, offset.Y); + offset = new(offset.X, 8 - offset.Y); + } + + return offset; + } + + /// + /// 获取相对于节点的坐标 + /// + public virtual Point GetPinOffset(NodeView card, int pinIndex) => new Point(); + + /// + /// 更新引脚图标 + /// + public virtual void UpdatePinIcon() + { + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml new file mode 100644 index 0000000..9f06b24 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml.cs new file mode 100644 index 0000000..508e01d --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.axaml.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; +using Avalonia.Controls; +using Avalonia.Interactivity; + +namespace AXNode.SubSystem.NodeEditSystem.Control.PropertyBar +{ + public partial class CustomListItem : UserControl + { + public CustomListItem() + { + InitializeComponent(); + } + + public string ItemValue + { + get => Input_Value.Text; + set => Input_Value.Text = value; + } + + public Action? Remove_Click { get; set; } = null; + + public Action? Value_Changed { get; set; } = null; + + private void Input_Value_TextChanged(object sender, TextChangedEventArgs e) => Value_Changed?.Invoke(this); + + private void Tool_Remove_Click(object sender, RoutedEventArgs e) => Remove_Click?.Invoke(this); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml new file mode 100644 index 0000000..23adbda --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml @@ -0,0 +1,24 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml.cs new file mode 100644 index 0000000..ced08c4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.axaml.cs @@ -0,0 +1,96 @@ +using System.Windows; +using Avalonia; +using Avalonia.Interactivity; +using XLib.Base; +using XLib.Node; + +namespace AXNode.SubSystem.NodeEditSystem.Control.PropertyBar +{ + public partial class ListEditer : PropertyBarBase + { + public ListEditer() => InitializeComponent(); + + public CustomListProperty Instance { get; set; } + + public void Init() + { + Stack_CaseList.Children.Clear(); + foreach (var item in Instance.ItemList) + { + CustomListItem listItem = new CustomListItem + { + ItemValue = item, + Margin = new Thickness(0, 10, 0, 0), + Remove_Click = OnRemoveClick, + Value_Changed = OnValueChanged + }; + Stack_CaseList.Children.Add(listItem); + } + + if (Stack_CaseList.Children.Count > 0) + ((CustomListItem)Stack_CaseList.Children[0]).Margin = new Thickness(); + else Grid_Tool.Margin = new Thickness(); + } + + private void Tool_Add_Click(object? sender, RoutedEventArgs routedEventArgs) + { + (int, int) mousePoint = OSTool.GetMousePoint(); + + // 生成项 + string value = GenerateCaseValue(); + CustomListItem listItem = new CustomListItem + { + ItemValue = value, + Margin = new Thickness(0, 10, 0, 0), + Remove_Click = OnRemoveClick, + Value_Changed = OnValueChanged + }; + // 添加项 + Stack_CaseList.Children.Add(listItem); + ((CustomListItem)Stack_CaseList.Children[0]).Margin = new Thickness(); + Instance.ItemList.Add(value); + Instance.ItemAdded?.Invoke(value); + + Grid_Tool.Margin = new Thickness(0, 10, 0, 0); + + mousePoint.Item2 += 35; + OSTool.SetMousePoint(mousePoint.Item1, mousePoint.Item2); + } + + private void OnRemoveClick(CustomListItem listItem) + { + // 移除项 + Stack_CaseList.Children.Remove(listItem); + if (Stack_CaseList.Children.Count > 0) + ((CustomListItem)Stack_CaseList.Children[0]).Margin = new Thickness(); + else Grid_Tool.Margin = new Thickness(); + + int index = Instance.ItemList.IndexOf(listItem.ItemValue); + Instance.ItemList.Remove(listItem.ItemValue); + Instance.ItemRemoved?.Invoke(index); + } + + private void OnValueChanged(CustomListItem listItem) + { + int index = Stack_CaseList.Children.IndexOf(listItem); + Instance.ItemList[index] = listItem.ItemValue; + Instance.ItemChanged?.Invoke(index, listItem.ItemValue); + } + + private string GenerateCaseValue() + { + int nameID = 1; + while (true) + { + string name = $"Case_{nameID:00}"; + if (Instance.ItemList.Contains(name)) + { + nameID++; + continue; + } + + return name; + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml new file mode 100644 index 0000000..cfcb849 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml.cs new file mode 100644 index 0000000..7cf23d9 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.axaml.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Input; + +namespace AXNode.SubSystem.NodeEditSystem.Control.PropertyBar +{ + public partial class ListSelecter : PropertyBarBase + { + public ListSelecter() => InitializeComponent(); + + public Action? SelectionChanged { get; set; } = null; + + public void LoadProperty(List list, string value) + { + Block_Title.Text = Title; + foreach (var item in list) + { + ComboBoxItem boxItem = new ComboBoxItem + { + Content = item, + }; + ToolTip.SetTip(boxItem, item); + Box_ItemList.Items.Add(boxItem); + } + + Box_ItemList.SelectedIndex = list.IndexOf(value); + } + + private void MainGrid_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + Block_Title.Foreground = _hovered; + } + + private void MainGrid_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + Block_Title.Foreground = _default; + } + + private void Box_ItemList_SelectionChanged(object? sender, SelectionChangedEventArgs selectionChangedEventArgs) + { + SelectionChanged?.Invoke(Box_ItemList.SelectedIndex - 1); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs new file mode 100644 index 0000000..6a1dc75 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Media; + +namespace AXNode.SubSystem.NodeEditSystem.Control.PropertyBar +{ + public class PropertyBarBase : UserControl + { + public string Title { get; set; } = ""; + + public virtual void LoadProperty(string value) + { + } + + protected SolidColorBrush _default = new SolidColorBrush(Color.FromRgb(140, 140, 140)); + protected SolidColorBrush _hovered = new SolidColorBrush(Color.FromRgb(255, 255, 255)); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml new file mode 100644 index 0000000..ae63dcb --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml.cs new file mode 100644 index 0000000..9d66588 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.axaml.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Input; + +namespace AXNode.SubSystem.NodeEditSystem.Control.PropertyBar +{ + public partial class TextInput : PropertyBarBase + { + public TextInput() + { + InitializeComponent(); + // Input_Value.TextChanged += OnTextChanged; + Input_Value.KeyDown += Input_Value_KeyDown; + } + + public string Text { get; set; } = ""; + + public event Action TextChanged; + + /// + /// 加载属性 + /// + public override void LoadProperty(string text) + { + Block_Title.Text = Title; + Input_Value.Text = text; + } + + private void MainGrid_MouseEnter(object? sender, PointerEventArgs pointerEventArgs) + { + Block_Title.Foreground = _hovered; + } + + private void MainGrid_MouseLeave(object? sender, PointerEventArgs pointerEventArgs) + { + Block_Title.Foreground = _default; + } + + private void OnTextChanged(object sender, TextChangedEventArgs e) + { + TextChanged?.Invoke(Input_Value.Text); + Text = Input_Value.Text; + } + + private void Input_Value_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + e.Handled = true; + TextChanged?.Invoke(Input_Value.Text); + Text = Input_Value.Text; + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/ConnectLine.cs b/AXNode/SubSystem/NodeEditSystem/Define/ConnectLine.cs new file mode 100644 index 0000000..27e34b4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/ConnectLine.cs @@ -0,0 +1,15 @@ +using System.Windows; +using Avalonia; + +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 连接线 + /// + public class ConnectLine + { + public Point Start { get; set; } + + public Point End { get; set; } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs b/AXNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs new file mode 100644 index 0000000..b7b3f23 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs @@ -0,0 +1,32 @@ +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 鼠标命中区域 + /// + public enum MouseHitedArea + { + /// 空白处 + Space, + + /// 节点 + Node, + + /// 引脚 + Pin, + + /// 连接线 + ConnectLine, + } + + /// + /// 选择方式 + /// + public enum SelectType + { + /// 框选 + Box, + + /// 交叉 + Cross + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/PinColorSet.cs b/AXNode/SubSystem/NodeEditSystem/Define/PinColorSet.cs new file mode 100644 index 0000000..761a1b7 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/PinColorSet.cs @@ -0,0 +1,23 @@ +using Avalonia.Media; +using XLib.Avalonia.Ex; + +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 引脚颜色集 + /// + public class PinColorSet + { + public static Color Execute => "C47EFF".ToColor(); + + public static Color Bool => "A7C4B5".ToColor(); + + public static Color Int => "B3D465".ToColor(); + + public static Color Double => "E06C9F".ToColor(); + + public static Color String => "F3B562".ToColor(); + + public static Color ByteArray => "6CB891".ToColor(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/PinConnectInfo.cs b/AXNode/SubSystem/NodeEditSystem/Define/PinConnectInfo.cs new file mode 100644 index 0000000..5934ed4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/PinConnectInfo.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 引脚连接信息 + /// + public class PinConnectInfo + { + public string Title { get; set; } = ""; + + public PinFlow Flow { get; set; } = PinFlow.Input; + + public List PathList { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/PinPath.cs b/AXNode/SubSystem/NodeEditSystem/Define/PinPath.cs new file mode 100644 index 0000000..191a87a --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/PinPath.cs @@ -0,0 +1,33 @@ +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 引脚路径:节点编号.引脚组索引.引脚索引 + /// + public class PinPath + { + /// + /// 解析引脚路径 + /// + public static PinPath ParsePinPath(string path) + { + string[] nodeArray = path.Split(','); + return new PinPath + { + NodeVersion = nodeArray[0], + NodeID = int.Parse(nodeArray[1]), + GroupIndex = int.Parse(nodeArray[2]), + PinIndex = int.Parse(nodeArray[3]) + }; + } + + public string NodeVersion { get; set; } = "1.0"; + + public int NodeID { get; set; } = -1; + + public int GroupIndex { get; set; } = -1; + + public int PinIndex { get; set; } = -1; + + public override string ToString() => $"{NodeVersion},{NodeID},{GroupIndex},{PinIndex}"; + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Define/TargetBox.cs b/AXNode/SubSystem/NodeEditSystem/Define/TargetBox.cs new file mode 100644 index 0000000..d8abc54 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Define/TargetBox.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Windows; +using Avalonia; + +namespace AXNode.SubSystem.NodeEditSystem.Define +{ + /// + /// 目标框 + /// + public class TargetBox + { + public Point ScreenPoint { get; set; } + + public double Height { get; set; } + + public double Width { get; set; } + + /// 外框偏移。正数向外,负数向内 + public double BoxOffset { get; set; } = 0; + + /// + /// 获取绘制目标框的坐标列表。共绘制八条线,每条线两个坐标 + /// + public List GetPointList(int lineLength) + { + List result = new List(); + + double left = ScreenPoint.X - BoxOffset; + double right = ScreenPoint.X + Width + BoxOffset; + double top = ScreenPoint.Y - BoxOffset; + double bottom = ScreenPoint.Y + Height + BoxOffset; + + double hx1 = left + lineLength; + double hx2 = right - lineLength; + double hy1 = top + 0.5; + double hy2 = bottom - 0.5; + + result.Add(new Point(left, hy1)); + result.Add(new Point(hx1, hy1)); + result.Add(new Point(hx2, hy1)); + result.Add(new Point(right, hy1)); + + result.Add(new Point(left, hy2)); + result.Add(new Point(hx1, hy2)); + result.Add(new Point(hx2, hy2)); + result.Add(new Point(right, hy2)); + + double vx1 = left + 0.5; + double vx2 = right - 0.5; + double vy1 = top + lineLength; + double vy2 = bottom - lineLength; + + result.Add(new Point(vx1, top)); + result.Add(new Point(vx1, vy1)); + result.Add(new Point(vx1, vy2)); + result.Add(new Point(vx1, bottom)); + + result.Add(new Point(vx2, top)); + result.Add(new Point(vx2, vy1)); + result.Add(new Point(vx2, vy2)); + result.Add(new Point(vx2, bottom)); + + return result; + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs new file mode 100644 index 0000000..6d91bee --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using XLib.Base.UIComponent; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Control; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Component +{ + /// + /// 卡片组件:管理编辑器中的节点控件 + /// + public class CardComponent : Component + { + #region 属性 + + /// 全部卡片 + public List AllCard => _cardList; + + /// 选中卡片列表 + public List SelectedCardList => _selectedCardSet.ToList(); + + #endregion + + #region 生命周期 + + protected override void Reset() + { + _host.LayerBox_Node.Children.Clear(); + _cardList.Clear(); + _selectedCardSet.Clear(); + } + + #endregion + + #region 公开方法 + + /// + /// 生成节点卡片 + /// + public NodeView GenerateNodeCard(NodeBase node) + { + // 创建节点卡片 + NodeView card = new NodeView + { + NodeColor = Color.FromRgb(node.Color.r, node.Color.g, node.Color.b), + NodeInstance = node, + Point = new Point(node.Point.X, node.Point.Y), + }; + // 设置卡片坐标 + Point center = GetComponent().WorldCenter; + Canvas.SetLeft(card, center.X + node.Point.X - 12); + Canvas.SetTop(card, center.Y + node.Point.Y - 1); + // 初始化节点卡片 + card.Init(); + // 添加视图 + _host.LayerBox_Node.Children.Add(card); + _cardList.Add(card); + // 调用已加载 + node.Loaded(); + // 返回节点视图 + return card; + } + + /// + /// 添加选择 + /// + public void AddSelect(NodeView view) => _selectedCardSet.Add(view); + + /// + /// 添加选择 + /// + public void RemoveSelect(NodeView view) => _selectedCardSet.Remove(view); + + /// + /// 清空选择 + /// + public void ClearSelect() => _selectedCardSet.Clear(); + + /// + /// 更新节点卡片 + /// + public void UpdateNodeCard() + { + Point center = GetComponent().WorldCenter; + foreach (var card in _cardList) + { + Canvas.SetLeft(card, center.X + card.Point.X - 12); + Canvas.SetTop(card, center.Y + card.Point.Y - 1); + } + } + + /// + /// 将卡片置顶 + /// + public void SetTop(NodeView view) + { + _host.LayerBox_Node.Children.Remove(view); + _host.LayerBox_Node.Children.Add(view); + _cardList.Remove(view); + _cardList.Add(view); + } + + /// + /// 获取节点卡片 + /// + public NodeView GetNodeCard(int nodeID) + { + foreach (var card in _cardList) + if (card.NodeInstance.ID == nodeID) + return card; + throw new Exception("获取节点卡片失败"); + } + + /// + /// 删除节点卡片 + /// + public void DeleteNodeCard(NodeView card) + { + card.Clear(); + _host.LayerBox_Node.Children.Remove(card); + _cardList.Remove(card); + } + + #endregion + + #region 字段 + + /// 卡片列表 + private readonly List _cardList = new List(); + + /// 选中卡片集 + private readonly HashSet _selectedCardSet = new HashSet(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs new file mode 100644 index 0000000..47224c8 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs @@ -0,0 +1,403 @@ +using System; +using System.Windows; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using AXNode.AppTool; +using XLib.Animate; +using XLib.Base.UIComponent; +using XLib.Math.Easing; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Control; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.NodeEditSystem.Panel.Layer; +using XLib.Avalonia.Drawing; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.NodeEditSystem.Panel.Layer; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Component +{ + public class DrawingComponent : Component + { + #region 属性 + + /// 世界中心 + public Point WorldCenter => _gridLayer.GridCenter; + + /// 悬停框 + public TargetBox? HoverBox + { + get => _hoverBoxLayer?.Box; + set + { + // 图层为空,则忽略 + if (_hoverBoxLayer == null) return; + + // 设置悬停框 + _hoverBoxLayer.Box = value; + // 播放悬停框动画 + if (value != null) + _hoverBoxLayer.Motion("BoxMargin", 10, 0, 200, EasingType.QuinticEase, EasingMode.EaseOut); + } + } + + /// 悬停的连接线 + public VisualConnectLine? HoveredConnectLine => _hoveredLine as VisualConnectLine; + + #endregion + + #region 生命周期 + + protected override void Init() + { + EnableLayer(); + _host.OperateArea.SizeChanged += OperateArea_SizeChanged; + } + + protected override void Reset() => ResetLayer(); + + #endregion + + #region 公开方法 + + /// + /// 屏幕坐标转世界坐标 + /// + public Point ScreenToWorld(Point screenPoint) + { + // 转世界坐标 + Point worldPoint = new Point(screenPoint.X - _gridLayer.GridCenter.X, + screenPoint.Y - _gridLayer.GridCenter.Y); + // 对齐至网格 + double x = Math.Round(worldPoint.X / _gridLayer.CellWidth) * _gridLayer.CellWidth; + double y = Math.Round(worldPoint.Y / _gridLayer.CellHeight) * _gridLayer.CellHeight; + // 返回坐标 + return new Point(x, y); + } + + /// + /// 更新悬停框 + /// + public void UpdateHoverBox() => _hoverBoxLayer.Update(); + + /// + /// 清空选框 + /// + public void ClearSelectBox() => _selectBoxLayer.Clear(); + + /// + /// 更新选框 + /// + public void UpdateSelectBox(Point start, Point end) + { + _selectBoxLayer.Start = start; + _selectBoxLayer.End = end; + _selectBoxLayer.Update(); + } + + /// + /// 获取选框区域 + /// + public Rect GetSelectBoxRect() => new Rect(_selectBoxLayer.Start, _selectBoxLayer.End); + + /// + /// 获取选择方式 + /// + public SelectType GetSelectType() => + _selectBoxLayer.End.X < _selectBoxLayer.Start.X ? SelectType.Cross : SelectType.Box; + + /// + /// 更新选中框 + /// + public void UpdateSelectedBox() + { + _selectedBoxLayer.BoxList.Clear(); + foreach (var card in GetComponent().SelectedCardList) + { + TargetBox box = new TargetBox + { + ScreenPoint = new Point(Canvas.GetLeft(card) + 9, Canvas.GetTop(card) - 2), + Width = card.Bounds.Width - 18, + Height = card.Bounds.Height + 4 + }; + _selectedBoxLayer.BoxList.Add(box); + } + + _selectedBoxLayer.Update(); + } + + /// + /// 开始绘制临时连接线 + /// + public void BeginDrawTempConnectLine(Point start) + { + _tempLineLayer.Line = new ConnectLine + { + Start = start, + End = start, + }; + _tempLineLayer.Update(); + } + + /// + /// 更新临时连接线起点 + /// + public void UpdateTempLineStart(Point start) + { + if (_tempLineLayer.Line != null) + _tempLineLayer.Line.Start = new Point(start.X, start.Y); + _tempLineLayer.Update(); + } + + /// + /// 设置临时连接线终点 + /// + public void UpdateTempLineEnd(Point end) + { + if (_tempLineLayer.Line != null) + { + _tempLineLayer.Line.End = new Point(end.X, end.Y); + } + + _tempLineLayer.Update(); + } + + /// + /// 清除临时连接线 + /// + public void ClearTempLine() + { + _tempLineLayer.Line = null; + _tempLineLayer.Clear(); + } + + /// + /// 添加连接线 + /// + public void AddConnectLine(PinBase start, PinBase end) + { + VisualConnectLine connectLine = new VisualConnectLine + { + StartPin = start, + EndPin = end, + Start = GetPinPoint(start), + End = GetPinPoint(end), + IsData = start is DataPin, + }; + if (connectLine.IsData) connectLine.Color = GetPinColor((DataPin)start); + _connectLineLayer.AddConnectLine(connectLine); + connectLine.Update(); + } + + /// + /// 移除连接线 + /// + public void RemoveConnectLine(PinBase start, PinBase end) => _connectLineLayer.RemoveConnectLine(start, end); + + /// + /// 更新连接线 + /// + public void UpdateConnectLine() + { + // 遍历连接线元素 + foreach (var element in _connectLineLayer.ConnectLineList) + { + // 更新连接线的坐标 + element.Start = GetPinPoint(element.StartPin); + element.End = GetPinPoint(element.EndPin); + // 重绘连接线 + element.Update(); + } + } + + /// + /// 更新悬停连接线 + /// + public void UpdateHoveredConnectLine(Point point) + { + _hoveredLine = _connectLineLayer.GetHitedVisualElement(point); + if (_hoveredLine is VisualConnectLine line) + { + _lineBackLayer.Start = GetPinPoint(line.StartPin); + _lineBackLayer.End = GetPinPoint(line.EndPin); + _lineBackLayer.Update(); + } + else _lineBackLayer.Clear(); + } + + /// + /// 拖动视口 + /// + public void DragViewport(Point offset) + { + // 移动网格 + _gridLayer.MoveLayer(offset); + // 更新节点视图 + GetComponent().UpdateNodeCard(); + // 更新选中框与连接线 + UpdateSelectedBox(); + UpdateConnectLine(); + } + + /// + /// 结束拖动 + /// + public void EndDrag() => _gridLayer.ApplyOffset(); + + #endregion + + #region 控件事件 + + private void OperateArea_SizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateLayerSize(); + UpdateGrid(); + GetComponent().UpdateNodeCard(); + UpdateSelectedBox(); + UpdateConnectLine(); + } + + #endregion + + #region 私有方法 + + /// + /// 启用图层 + /// + private void EnableLayer() + { + // 创建图层 + _gridLayer = new GridLayer(); + _lineBackLayer = new ConnectLineBackLayer(); + _connectLineLayer = new ConnectLineLayer(); + _hoverBoxLayer = new HoverBoxLayer(); + _selectedBoxLayer = new SelectedBoxLayer(); + _selectBoxLayer = new SelectBoxLayer(); + _tempLineLayer = new TempConnectLineLayer(); + // 添加图层 + Host.Layer_Base.Children.Add(_gridLayer); + Host.Layer_Base.Children.Add(_lineBackLayer); + Host.Layer_Base.Children.Add(_connectLineLayer); + Host.Layer_Box.Children.Add(_hoverBoxLayer); + Host.Layer_Box.Children.Add(_selectedBoxLayer); + Host.Layer_Box.Children.Add(_selectBoxLayer); + Host.Layer_Temp.Children.Add(_tempLineLayer); + // 更新图层尺寸 + UpdateLayerSize(); + // 更新网格 + UpdateGrid(); + } + + /// + /// 重置图层 + /// + private void ResetLayer() + { + // 重置网格 + _gridLayer.Reset(); + _gridLayer.Update(); + // 清空连接线 + _lineBackLayer.Clear(); + _connectLineLayer.ClearConnectLine(); + // 清空悬停框、选框 + _hoverBoxLayer.Box = null; + _hoverBoxLayer.Clear(); + _selectBoxLayer.Clear(); + // 清空选中框 + _selectedBoxLayer.Clear(); + _selectedBoxLayer.BoxList.Clear(); + } + + /// + /// 更新图层尺寸 + /// + private void UpdateLayerSize() + { + double width = Host.OperateArea.Bounds.Width; + double height = Host.OperateArea.Bounds.Height; + + _gridLayer.Width = width; + _gridLayer.Height = height; + _lineBackLayer.Width = width; + _lineBackLayer.Height = height; + _connectLineLayer.Width = width; + _connectLineLayer.Height = height; + _hoverBoxLayer.Width = width; + _hoverBoxLayer.Height = height; + _selectBoxLayer.Width = width; + _selectBoxLayer.Height = height; + _selectedBoxLayer.Width = width; + _selectedBoxLayer.Height = height; + _tempLineLayer.Width = width; + _tempLineLayer.Height = height; + } + + /// + /// 更新网格 + /// + private void UpdateGrid() => _gridLayer.Update(); + + /// + /// 获取引脚相对于图层的坐标 + /// + private Point GetPinPoint(PinBase pin) + { + // 获取引脚路径 + PinPath path = pin.GetPinPath(); + // 获取节点卡片 + NodeView card = GetComponent().GetNodeCard(path.NodeID); + // 获取引脚坐标(相对于节点) + Point pinOffset = card.GetPinOffset(path); + // 获取节点坐标 + Point nodePoint = new Point(Canvas.GetLeft(card), Canvas.GetTop(card)); + // 返回偏移 + return new Point(nodePoint.X + pinOffset.X, nodePoint.Y + pinOffset.Y); + } + + /// + /// 获取引脚颜色 + /// + private Color GetPinColor(DataPin pin) + { + return ((DataPinGroup)pin.OwnerGroup).Type switch + { + "bool" => PinColorSet.Bool, + "int" => PinColorSet.Int, + "double" => PinColorSet.Double, + "string" => PinColorSet.String, + "byte[]" => PinColorSet.ByteArray, + _ => Colors.White, + }; + } + + #endregion + + #region 字段 + + /// 网格图层 + private GridLayer? _gridLayer; + + /// 连接线背景图层 + private ConnectLineBackLayer? _lineBackLayer; + + /// 连接线图层 + private ConnectLineLayer? _connectLineLayer; + + /// 悬停框图层 + private HoverBoxLayer? _hoverBoxLayer; + + /// 选框图层 + private SelectBoxLayer? _selectBoxLayer; + + /// 选中框图层 + private SelectedBoxLayer? _selectedBoxLayer; + + /// 临时连接线图层 + private TempConnectLineLayer? _tempLineLayer; + + /// 悬停连接线 + private VisualElement? _hoveredLine = null; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs new file mode 100644 index 0000000..ca28115 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs @@ -0,0 +1,15 @@ +using XLib.Base.UIComponent; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Component +{ + public class EditerComponent : Component + { + #region 生命周期 + + #endregion + + #region 公开方法 + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs new file mode 100644 index 0000000..0d264cc --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs @@ -0,0 +1,800 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Layout; +using XLib.Base; +using XLib.Base.UIComponent; +using XLib.Base.VirtualDisk; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Control; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.ProjectSystem; +using AXNode.SubSystem.ResourceSystem; +using AXNode.SubSystem.WindowSystem; +using XLib.AvaloniaControl.Tool; +using AXNode.SubSystem.NodeEditSystem.Control; +using XLib.AvaloniaControl; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Component +{ + /// + /// 交互组件:处理键盘、鼠标事件 + /// + public class InteractionComponent : Component + { + #region 属性 + + public NodeView? HoveredCard => _hoveredNodeView; + + #endregion + + #region 生命周期 + + protected override void Init() + { + _tool = new SelectTool(this); + _tool.Init(); + _hoverToolBar = new HoverToolBar + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + }; + _host.LayerBox_ToolBar.Children.Add(_hoverToolBar); + _hoverToolBar.UpdateLayout(); + _hoverToolBar.IsVisible = false; + _hoverToolBar.Init(); + _hoverToolBar.ToolClick += HoverToolBar_ToolClick; + } + + protected override void Enable() + { + _host.OperateArea.PointerMoved += OperateArea_MouseMove; + _host.OperateArea.PointerPressed += OperateArea_MouseDown; + _host.OperateArea.PointerReleased += OperateArea_MouseUp; + } + + protected override void Reset() + { + ResetComponent(); + } + + protected override void Disable() + { + ResetComponent(); + _host.OperateArea.PointerMoved -= OperateArea_MouseMove; + _host.OperateArea.PointerPressed -= OperateArea_MouseDown; + _host.OperateArea.PointerReleased -= OperateArea_MouseUp; + _hoverToolBar.IsVisible = false; + } + + protected override void Remove() + { + ResetComponent(); + _host.OperateArea.PointerMoved -= OperateArea_MouseMove; + _host.OperateArea.PointerPressed -= OperateArea_MouseDown; + _host.OperateArea.PointerReleased -= OperateArea_MouseUp; + _hoverToolBar.ToolClick -= HoverToolBar_ToolClick; + _host.LayerBox_ToolBar.Children.Remove(_hoverToolBar); + _hoverToolBar = null; + } + + #endregion + + #region 公开方法 + + public async void HandleKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Delete) + { + List cardList = GetComponent().SelectedCardList; + if (cardList.Count == 0) return; + + bool? delete = await WM.ShowAsk("确定删除选中节点吗?", "确定", false, TipLevel.Warning); + if (delete != true) return; + + DeleteNode(cardList); + } + else if (e.Key == Key.Space) + { + List cardList = GetComponent().SelectedCardList; + if (cardList.Count == 0) return; + + foreach (NodeView card in cardList) + { + if (card.NodeInstance.State == NodeState.Disable) card.NodeInstance.Start(); + else card.NodeInstance.Stop(); + } + } + } + + /// + /// 处理放下 + /// + public void HandleDrop(List itemList) + { + // 获取屏幕坐标 + var screenPoint = Mouse.GetPosition(_host.OperateArea, true); + // 获取节点组件 + var component = GetComponent(); + // 放下节点 + bool added = false; + foreach (var item in itemList) + { + if (item is File file && file.Instance is NodeType nodeType) + { + // 放下并创建节点卡片 + NodeView? nodeView = component.DropNode(file.ID, nodeType, screenPoint); + if (nodeView == null) continue; + nodeView.NodeBackMouseEnter = NodeBack_MouseEnter; + nodeView.NodeBackMouseLeave = NodeBack_MouseLeave; + nodeView.PinGroupListChanged = PinGroupListChanged; + nodeView.NodeChanged = NodeChanged; + added = true; + } + } + + if (added) ProjectManager.Instance.Saved = false; + } + + /// + /// 监听节点卡片 + /// + public void ListenNodeCard(NodeView nodeView) + { + nodeView.NodeBackMouseEnter = NodeBack_MouseEnter; + nodeView.NodeBackMouseLeave = NodeBack_MouseLeave; + nodeView.PinGroupListChanged = PinGroupListChanged; + nodeView.NodeChanged = NodeChanged; + } + + /// + /// 更新全部引脚图标 + /// + public void UpdateAllPinIcon() + { + foreach (var card in GetComponent().AllCard) card.UpdateAllPinIcon(); + } + + #endregion + + #region 工具方法 + + /// + /// 捕获操作图层 + /// + public void CaptureOperationLayer() => _host.OperateArea.CaptureMouse(); + + /// + /// 释放操作图层 + /// + public void ReleaseOperationLayer() => _host.OperateArea.ReleaseMouseCapture(); + + /// + /// 获取鼠标命中区域 + /// + public MouseHitedArea GetHitedArea() + { + // 如果悬停节点不为空 + if (_hoveredNodeView != null) + return _hoveredNodeView.HoveredPin == null ? MouseHitedArea.Node : MouseHitedArea.Pin; + // 如果悬停于连接线 + else if (GetComponent().HoveredConnectLine != null) return MouseHitedArea.ConnectLine; + // 返回空白区域 + return MouseHitedArea.Space; + } + + /// + /// 清除悬停框 + /// + public void ClearHoverBox() + { + GetComponent().HoverBox = null; + GetComponent().UpdateHoverBox(); + } + + /// + /// 处理鼠标移动 + /// + public void HandleMouseMove() + { + // 重置光标 + _tool.Cursor = CursorManager.Instance.Select; + // 悬停在引脚上:切换光标 + if (_hoveredNodeView != null && _hoveredNodeView.HoveredPin != null) + _tool.Cursor = CursorManager.Instance.Cross; + // 设置光标 + _host.OperateArea.Cursor = _tool.Cursor; + + // 更新悬停连接线 + GetComponent().UpdateHoveredConnectLine(Mouse.GetPosition(_host.OperateArea)); + } + + /// + /// 移除节点焦点 + /// + public void RemoveNodeFocus() => _host.OperateArea.Focus(); + + /// + /// 启动并执行节点 + /// + public void StartAndExecute() + { + _hoveredNodeView.NodeInstance.Start(); + _hoveredNodeView.NodeInstance.Execute(); + } + + #region 选择 + + public bool CurrentNodeSelected() => + GetComponent().SelectedCardList.Contains(_hoveredNodeView); + + public void SetTop() => GetComponent().SetTop(_hoveredNodeView); + + public void AddSelect() + { + GetComponent().AddSelect(_hoveredNodeView); + GetComponent().UpdateSelectedBox(); + UpdateHoverToolBar(); + UpdatePropertyPanel(); + } + + public void RemoveSelect() + { + GetComponent().RemoveSelect(_hoveredNodeView); + GetComponent().UpdateSelectedBox(); + UpdateHoverToolBar(); + UpdatePropertyPanel(); + } + + public void ClearSelect() + { + GetComponent().ClearSelect(); + GetComponent().UpdateSelectedBox(); + UpdateHoverToolBar(); + UpdatePropertyPanel(); + } + + #endregion + + #region 选框 + + public void BeginDrawSelectBox() => _mouseDown = Mouse.GetPosition(_host.OperateArea); + + public void CancelDrawSelectBox() => GetComponent().ClearSelectBox(); + + public void DrawSelectBox() => + GetComponent().UpdateSelectBox(_mouseDown, Mouse.GetPosition(_host.OperateArea)); + + public void EndDrawSelectBox() + { + // 清除选框 + GetComponent().ClearSelectBox(); + // 获取选框区域与选择方式 + Rect rect = GetComponent().GetSelectBoxRect(); + SelectType type = GetComponent().GetSelectType(); + // 选择节点视图 + foreach (var card in GetComponent().AllCard) + { + Rect nodeRect = card.GetHittableRect(); + switch (type) + { + case SelectType.Box: + if (rect.Contains(nodeRect)) GetComponent().AddSelect(card); + break; + case SelectType.Cross: + if (rect.Intersects(nodeRect)) GetComponent().AddSelect(card); + break; + } + } + + // 更新选中框 + GetComponent().UpdateSelectedBox(); + // 更新工具栏 + UpdateHoverToolBar(); + // 更新属性面板 + UpdatePropertyPanel(); + } + + #endregion + + #region 拖动节点 + + public void BeginDragNode() + { + _host.OperateArea.Cursor = CursorManager.Instance.SelectAndMove; + _mouseDown = Mouse.GetPosition(_host.OperateArea); + } + + public void CancelDragNode() + { + _host.OperateArea.Cursor = _tool.Cursor; + } + + public void DragNode() + { + // 更新当前坐标 + _mousePoint = Mouse.GetPosition(_host.OperateArea); + // 计算偏移 + Point offset = new Point(_mousePoint.X - _mouseDown.X, _mousePoint.Y - _mouseDown.Y); + // offset = new (0,0) ; + + // 对齐网格 + offset = new(Math.Round(offset.X / 10) * 10, offset.Y); + offset = new(offset.X, Math.Round(offset.Y / 10) * 10); + + // 获取世界中心 + Point center = GetComponent().WorldCenter; + // 设置节点偏移并更新坐标 + foreach (var card in GetComponent().SelectedCardList) + { + card.SetOffset(new Point(offset.X, offset.Y)); + Canvas.SetLeft(card, center.X + card.Point.X - 12); + Canvas.SetTop(card, center.Y + card.Point.Y - 1); + } + + // 更新选中框、连接线 + GetComponent().UpdateSelectedBox(); + GetComponent().UpdateConnectLine(); + // 更新悬浮工具栏 + UpdateHoverToolBar(); + + ProjectManager.Instance.Saved = false; + } + + public void EndDragNode() + { + _host.OperateArea.Cursor = _tool.Cursor; + foreach (var card in GetComponent().SelectedCardList) card.ApplyOffset(); + } + + #endregion + + #region 连接线 + + public void BeginDrawConnectLine() + { + // 设置起始引脚 + _startPin = _hoveredNodeView.HoveredPin; + // 设置鼠标坐标 + _mouseDown = Mouse.GetPosition(_host.OperateArea); + // 获取引脚与鼠标的偏移量 + Point offset = _hoveredNodeView.GetHoveredPinOffset(); + // 计算引脚连接点坐标 + Point pinPoint = new Point(_mouseDown.X + offset.X, _mouseDown.Y + offset.Y); + // 开始绘制连接线 + GetComponent().BeginDrawTempConnectLine(pinPoint); + } + + public void CancelDrawConnectLine() + { + GetComponent().ClearTempLine(); + } + + public void DrawConnectLine() + { + // 更新鼠标坐标 + _mousePoint = Mouse.GetPosition(_host.OperateArea); + if (_hoveredNodeView != null && _hoveredNodeView.HoveredPin != null) + { + // 获取引脚与鼠标的偏移量 + Point offset = _hoveredNodeView.GetHoveredPinOffset(); + // 计算引脚连接点坐标 + _mousePoint = new Point(_mousePoint.X + offset.X, _mousePoint.Y + offset.Y); + } + + // 根据起始引脚类型更新连接线的起点或终点 + if (_startPin.Flow == PinFlow.Input) + GetComponent().UpdateTempLineStart(_mousePoint); + else + GetComponent().UpdateTempLineEnd(_mousePoint); + } + + public void EndDrawConnectLine() + { + // 清除临时连接线 + GetComponent().ClearTempLine(); + // 悬停引脚不为空 + if (_hoveredNodeView != null && _hoveredNodeView.HoveredPin != null) + { + PinBase endPin = _hoveredNodeView.HoveredPin; + // 无法连接 + if (!CanConnect(_startPin, endPin)) return; + // 连接引脚:将结束引脚写入起始引脚 + if (_startPin.Flow == PinFlow.Input) + { + // 如果是数据引脚,先移除原有的连接线 + if (_startPin is DataPin && _startPin.SourceList.Count > 0) + GetComponent().RemoveConnectLine(_startPin.SourceList[0], _startPin); + // 连接源与目标。数据引脚会自动断开原有连接 + _startPin.AddSource(endPin); + endPin.AddTarget(_startPin); + // 添加连接线 + GetComponent().AddConnectLine(endPin, _startPin); + } + else + { + // 如果是数据引脚,先移除原有的连接线 + if (endPin is DataPin && endPin.SourceList.Count > 0) + GetComponent().RemoveConnectLine(endPin.SourceList[0], endPin); + // 连接源与目标。数据引脚会自动断开原有连接 + _startPin.AddTarget(endPin); + endPin.AddSource(_startPin); + // 添加连接线 + GetComponent().AddConnectLine(_startPin, endPin); + } + + // 更新引脚图标 + UpdateAllPinIcon(); + + ProjectManager.Instance.Saved = false; + } + + _startPin = null; + } + + #endregion + + #region 断开引脚 + + public void BeginBreakPin() => _rightHitedPin = _hoveredNodeView.HoveredPin; + + public void CancelBreakPin() => _rightHitedPin = null; + + public void EndBreakPin() + { + // 命中输入节点,则与源断开 + if (_rightHitedPin.Flow == PinFlow.Input) + { + List sourceList = new List(_rightHitedPin.SourceList); + foreach (var source in sourceList) BreakPin(source, _rightHitedPin); + } + // 否则与目标断开 + else + { + List targetList = new List(_rightHitedPin.TargetList); + foreach (var target in targetList) BreakPin(_rightHitedPin, target); + } + + // 更新引脚图标 + UpdateAllPinIcon(); + + _rightHitedPin = null; + + ProjectManager.Instance.Saved = false; + } + + #endregion + + #region 移除连接线 + + public void RemoveConnectLine() + { + // 获取连接线 + Layer.VisualConnectLine? connectLine = GetComponent().HoveredConnectLine; + if (connectLine == null) return; + // 断开引脚 + BreakPin(connectLine.StartPin, connectLine.EndPin); + // 更新引脚图标 + UpdateAllPinIcon(); + } + + #endregion + + #region 拖动视口 + + public void BeginDragViewport() + { + _host.OperateArea.Cursor = CursorManager.Instance.Move; + _mouseDown = Mouse.GetPosition(_host.OperateArea); + } + + public void CancelDragViewport() + { + _host.OperateArea.Cursor = _tool.Cursor; + } + + public void DragViewport() + { + _mousePoint = Mouse.GetPosition(_host.OperateArea); + GetComponent() + .DragViewport(new Point(_mousePoint.X - _mouseDown.X, _mousePoint.Y - _mouseDown.Y)); + UpdateHoverToolBar(); + } + + public void EndDragViewport() + { + _host.OperateArea.Cursor = _tool.Cursor; + GetComponent().EndDrag(); + } + + #endregion + + #endregion + + #region 节点事件 + + private void NodeBack_MouseEnter(NodeView nodeView) => SwitchHoverTarget(nodeView); + + private void NodeBack_MouseLeave(NodeView nodeView) => SwitchHoverTarget(null); + + private void PinGroupListChanged() + { + // 更新选中框 + GetComponent().UpdateSelectedBox(); + // 更新连接线 + GetComponent().UpdateConnectLine(); + // 更新引脚图标 + UpdateAllPinIcon(); + } + + private void NodeChanged() + { + ProjectManager.Instance.Saved = false; + } + + #endregion + + #region 控件事件 + + private void OperateArea_MouseMove(object? sender, PointerEventArgs pointerEventArgs) => _tool?.OnMouseMove(); + + private void OperateArea_MouseDown(object? sender, PointerPressedEventArgs e) + { + var point = e.GetCurrentPoint(sender as Avalonia.Controls.Control); + if (e.ClickCount == 1) _tool?.OnMouseDown(ToMouseBtn(point)); + else if (point.Properties.IsLeftButtonPressed) _tool?.OnDoubleClick(); + } + + private void OperateArea_MouseUp(object? sender, PointerReleasedEventArgs e) + { + var point = e.GetCurrentPoint(sender as Avalonia.Controls.Control); + + _tool?.OnMouseUp(ToMouseBtn(point)); + } + + private MouseButton ToMouseBtn(PointerPoint point) + { + return point.Properties.PointerUpdateKind switch + { + PointerUpdateKind.LeftButtonPressed => MouseButton.Left, + PointerUpdateKind.LeftButtonReleased => MouseButton.Left, + PointerUpdateKind.MiddleButtonPressed => MouseButton.Middle, + PointerUpdateKind.MiddleButtonReleased => MouseButton.Middle, + PointerUpdateKind.RightButtonPressed => MouseButton.Right, + PointerUpdateKind.RightButtonReleased => MouseButton.Right, + _ => MouseButton.None + }; + } + + private void HoverToolBar_ToolClick(string name) + { + switch (name) + { + case "Tool_Start": + foreach (var card in GetComponent().SelectedCardList) + card.NodeInstance.Start(); + break; + case "Tool_Stop": + foreach (var card in GetComponent().SelectedCardList) + card.NodeInstance.Stop(); + break; + case "Tool_Delete": + DeleteNode(GetComponent().SelectedCardList); + break; + } + } + + #endregion + + #region 私有方法 + + /// + /// 切换悬停目标 + /// + private void SwitchHoverTarget(NodeView? target) + { + // 与当前目标一致,忽略 + if (_hoveredNodeView == target) return; + // 更新目标 + _hoveredNodeView = target; + // 当前目标为空,清空悬停框 + if (_hoveredNodeView == null) + { + GetComponent().HoverBox = null; + GetComponent().UpdateHoverBox(); + return; + } + + // 当前目标已选中,不绘制悬停框 + if (GetComponent().SelectedCardList.Contains(_hoveredNodeView)) return; + + // 设置悬停框 + GetComponent().HoverBox = new TargetBox + { + ScreenPoint = new Point(Canvas.GetLeft(_hoveredNodeView) + 9, Canvas.GetTop(_hoveredNodeView) - 2), + Width = _hoveredNodeView.Bounds.Width - 18, + Height = _hoveredNodeView.Bounds.Height + 4 + }; + } + + /// + /// 更新悬停工具栏 + /// + private void UpdateHoverToolBar() + { + // 选中数量 + int selectedCount = GetComponent().SelectedCardList.Count; + // 显隐工具栏 + _hoverToolBar.IsVisible = selectedCount == 0 ? false : true; + + // 无选中 + if (selectedCount == 0) + { + } + // 选中一个 + else if (selectedCount == 1) + { + Rect cardRect = GetComponent().SelectedCardList[0].GetHittableRect(); + double left = cardRect.Right - _hoverToolBar.Bounds.Width; + double top = cardRect.Top - 10 - _hoverToolBar.Bounds.Height; + Canvas.SetLeft(_hoverToolBar, left + 1); + Canvas.SetTop(_hoverToolBar, top + 1); + } + // 选中多个 + else + { + List selectedList = GetComponent().SelectedCardList; + Rect rect = selectedList[0].GetHittableRect(); + for (int index = 1; index < selectedList.Count; index++) + { + rect = rect.Union(selectedList[index].GetHittableRect()); + } + + double left = Math.Round((rect.Right - rect.Left - _hoverToolBar.Bounds.Width) / 2) + rect.Left; + double top = rect.Top - 10 - _hoverToolBar.Bounds.Height; + Canvas.SetLeft(_hoverToolBar, left + 1); + Canvas.SetTop(_hoverToolBar, top + 1); + } + } + + /// + /// 更新属性面板 + /// + private void UpdatePropertyPanel() + { + // 选中数量 + int selectedCount = GetComponent().SelectedCardList.Count; + // 显隐属性面板 + _host.PropertyArea.IsVisible = selectedCount == 0 ? false : true; + + if (selectedCount == 0) + { + _host.PropertyPanel.Instance = null; + _host.PropertyPanel.ClearPropertyBar(); + } + else + { + // 获取第一个选中的节点 + NodeView firstCard = GetComponent().SelectedCardList[0]; + if (firstCard.NodeInstance.PropertyList.Count == 0) + { + _host.PropertyArea.IsVisible = false; + return; + } + + // 加载属性条 + _host.PropertyPanel.Instance = firstCard.NodeInstance; + _host.PropertyPanel.LoadPropertyBar(); + } + } + + /// + /// 判断两个引脚能否连接 + /// + private bool CanConnect(PinBase start, PinBase end) + { + // 不能连接自己 + if (end == start) return false; + // 不能处于同一节点下 + if (end.OwnerGroup.OwnerNode == start.OwnerGroup.OwnerNode) return false; + // 流向不能一致 + if (end.Flow == start.Flow) return false; + // 类型必须一致 + if (end.GetType() != start.GetType()) return false; + // 已连接 + if (start.TargetList.Contains(end)) return false; + + return true; + } + + /// + /// 断开引脚 + /// + private void BreakPin(PinBase source, PinBase target) + { + source.TargetList.Remove(target); + target.SourceList.Remove(source); + GetComponent().RemoveConnectLine(source, target); + } + + /// + /// 删除节点 + /// + private void DeleteNode(List cardList) + { + // 删除节点实例与卡片 + foreach (var card in cardList) + { + GetComponent().DeleteNode(card.NodeInstance); + GetComponent().DeleteNodeCard(card); + } + + // 更新引脚图标 + UpdateAllPinIcon(); + // 清空选择 + GetComponent().ClearSelect(); + // 更新选中框 + GetComponent().UpdateSelectedBox(); + // 更新悬浮工具栏 + UpdateHoverToolBar(); + // 更新属性面板 + UpdatePropertyPanel(); + // 更新鼠标悬停 + HandleMouseMove(); + + ProjectManager.Instance.Saved = false; + } + + /// + /// 重置组件 + /// + private void ResetComponent() + { + ReleaseOperationLayer(); + Host.Cursor = null; + _tool.Reset(); + _hoveredNodeView = null; + + _mouseDown = new Point(); + _mousePoint = new Point(); + + _startPin = null; + _rightHitedPin = null; + + _hoverToolBar.IsVisible = false; + _host.PropertyPanel.Instance = null; + _host.PropertyPanel.ClearPropertyBar(); + _host.PropertyArea.IsVisible = false; + } + + #endregion + + #region 字段 + + private SelectTool _tool; + + /// 当前悬停的节点视图 + private NodeView? _hoveredNodeView = null; + + /// 当前鼠标坐标 + private Point _mousePoint = new Point(); + + /// 鼠标按下坐标 + private Point _mouseDown = new Point(); + + /// 起始引脚 + private PinBase? _startPin; + + /// 右键命中引脚 + private PinBase? _rightHitedPin = null; + + /// 悬浮工具栏 + private HoverToolBar _hoverToolBar; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs new file mode 100644 index 0000000..4f6dc34 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs @@ -0,0 +1,130 @@ +using System.Collections.Generic; +using System.Windows; +using Avalonia; +using AXNode.SubSystem.NodeEditSystem.Control; +using XLib.Base.ID; +using XLib.Base.UIComponent; +using XLib.Node; +using AXNode.SubSystem.NodeEditSystem.Control; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Component +{ + /// + /// 节点组件:管理编辑器中的节点实例 + /// + public class NodeComponent : Component + { + #region 属性 + + /// 节点列表 + public List NodeList => _nodeList; + + #endregion + + #region 生命周期 + + protected override void Reset() + { + _nodeIDBox.Reset(); + _nodeDict.Clear(); + _nodeList.Clear(); + } + + #endregion + + #region 公开方法 + + /// + /// 放下节点 + /// + public NodeView? DropNode(int fileID, NodeType nodeType, Point screenPoint) + { + // 获取世界坐标 + var worldPoint = GetComponent().ScreenToWorld(screenPoint); + // 创建节点实例 + NodeBase nodeInstance = nodeType.NewInstance(); + nodeInstance.PinBreaked += Node_PinBreaked; + nodeInstance.TypeID = fileID; + // 设置节点编号、坐标 + nodeInstance.ID = _nodeIDBox.TakeID(); + nodeInstance.Point = new NodePoint((int)worldPoint.X, (int)worldPoint.Y); + // 添加节点引用 + _nodeDict.Add(nodeInstance.ID, nodeInstance); + _nodeList.Add(nodeInstance); + // 生成节点卡片 + NodeView card = GetComponent().GenerateNodeCard(nodeInstance); + // 启动节点 + nodeInstance.Start(); + return card; + } + + /// + /// 加载节点 + /// + public void LoadNode(NodeBase node) + { + node.PinBreaked += Node_PinBreaked; + _nodeIDBox.UseID(node.ID); + _nodeDict.Add(node.ID, node); + _nodeList.Add(node); + NodeView card = GetComponent().GenerateNodeCard(node); + card.UpdateLayout(); + GetComponent().ListenNodeCard(card); + } + + /// + /// 删除节点 + /// + public void DeleteNode(NodeBase node) + { + node.Clear(); + node.BreakAllPin(); + node.PinBreaked -= Node_PinBreaked; + _nodeIDBox.RecycleID(node.ID); + _nodeDict.Remove(node.ID); + _nodeList.Remove(node); + } + + /// + /// 生成连接线 + /// + public void GenerateConnectLine() + { + // 遍历节点 + foreach (var node in _nodeList) + { + // 遍历全部引脚 + foreach (var pin in node.GetAllPin()) + { + // 忽略输入引脚与空输出引脚 + if (pin.Flow == PinFlow.Input || pin.TargetList.Count == 0) continue; + // 添加连接线 + foreach (var target in pin.TargetList) + GetComponent().AddConnectLine(pin, target); + } + } + } + + #endregion + + #region 节点事件 + + private void Node_PinBreaked(PinBase start, PinBase end) => + GetComponent().RemoveConnectLine(start, end); + + #endregion + + #region 字段 + + /// 节点编号箱 + private readonly IDBox _nodeIDBox = new IDBox(); + + /// 节点字典 + private readonly Dictionary _nodeDict = new Dictionary(); + + /// 节点列表 + private List _nodeList = new List(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml b/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml new file mode 100644 index 0000000..db99960 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml.cs b/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml.cs new file mode 100644 index 0000000..e95d3a4 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/EditPanel.axaml.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Input; +using XLib.Base; +using XLib.Base.UIComponent; +using XLib.Base.VirtualDisk; +using XLib.Node; +using AXNode.SubSystem.EventSystem; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.NodeEditSystem.Panel.Component; +using AXNode.SubSystem.NodeEditSystem.Define; +using AXNode.SubSystem.NodeEditSystem.Panel.Component; +using XLib.AvaloniaControl; + +namespace AXNode.SubSystem.NodeEditSystem.Panel; + +public partial class EditPanel : UserControl, IDropable +{ + #region 属性 + + /// 节点列表 + public List NodeList => _nodeComponent.NodeList; + + #endregion + + #region 构造方法 + + public EditPanel() + { + InitializeComponent(); + // Mouse.InitDropable(this); + + DragDrop.SetAllowDrop(this, true); + this.AddHandler(DragDrop.DropEvent, ((sender, args) => + { + Mouse.RegisterDragEventArgs(args); + if (args.Data.Get("object") is ITreeItem treeItem) + { + if (this is IDropable dropable) + { + try + { + dropable.OnDrop(new List { treeItem }); + } + catch (Exception ex) + { + Console.WriteLine("拖动失败:" + ex.Message + ex.StackTrace); + } + } + } + })); + } + + #endregion + + #region 生命周期 + + public void Init() + { + // 添加核心组件 + _editerComponent = _componentBox.AddComponent(this, "编辑器组件"); + // 添加功能组件 + _drawingComponent = _componentBox.AddComponent(this, "绘图组件"); + _nodeComponent = _componentBox.AddComponent(this, "节点组件"); + _cardComponent = _componentBox.AddComponent(this, "卡片组件"); + _interactionComponent = _componentBox.AddComponent(this, "交互组件"); + // 注册核心组件 + _componentBox.RegisterCoreComponent(_editerComponent); + // 注册功能组件 + _editerComponent.AddComponent(_drawingComponent); + _editerComponent.AddComponent(_nodeComponent); + _editerComponent.AddComponent(_cardComponent); + _editerComponent.AddComponent(_interactionComponent); + // 初始化组件 + _componentBox.Init(); + // 启用编辑 + _editerComponent.ReqEnable(); + // 监听系统事件 + EM.Instance.Add(EventType.Project_Loaded, Project_Loaded); + } + + #endregion + + #region IDropable 方法 + + public void OnDrag(List fileList) + { + } + + public void OnDrop(List fileList) + { + _interactionComponent.HandleDrop(fileList); + } + + public bool CanDrop(List fileList) => fileList[0] is File file && file.Extension == "nt"; + + #endregion + + #region 公开方法 + + /// + /// 重置 + /// + public void Reset() => _componentBox.Reset(); + + /// + /// 加载节点 + /// + public void LoadNode(NodeBase node) => _nodeComponent.LoadNode(node); + + /// + /// 查找引脚 + /// + public PinBase? FindPin(PinPath path) + { + foreach (var node in _nodeComponent.NodeList) + if (node.ID == path.NodeID) + return node.FindPin(path.NodeVersion, path.GroupIndex, path.PinIndex); + return null; + } + + #endregion + + #region 系统事件 + + private void Project_Loaded() + { + // 更新引脚图标 + _interactionComponent.UpdateAllPinIcon(); + // 生成连接线 + _nodeComponent.GenerateConnectLine(); + } + + #endregion + + #region 字段 + + /// 组件箱 + private readonly ComponentBox _componentBox = new ComponentBox(); + + /// 编辑器组件 + private EditerComponent _editerComponent; + + /// 绘图组件 + private DrawingComponent _drawingComponent; + + /// 节点组件 + private NodeComponent _nodeComponent; + + /// 卡片组件 + private CardComponent _cardComponent; + + /// 交互组件 + private InteractionComponent _interactionComponent; + + #endregion +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineBackLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineBackLayer.cs new file mode 100644 index 0000000..d3b4bf7 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineBackLayer.cs @@ -0,0 +1,59 @@ +using System.Windows; +using Avalonia; +using Avalonia.Media; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 连接线背景图层 + /// + public class ConnectLineBackLayer : SingleBoard + { + public Point Start { get; set; } + public Point End { get; set; } + + + public override void Render(DrawingContext context) + { + // 计算连接线区域 + _left = Start.X; + _right = End.X; + _top = Start.Y + 0.5; + _bottom = End.Y + 0.5; + + // 计算贝塞尔曲线的控制线长度 + double controlLineLength = (_right - _left) / 2; + if (controlLineLength < _minLength) controlLineLength = _minLength; + + // 创建形状 + PathGeometry geometry = new PathGeometry(); + PathFigure figure = new PathFigure(); + figure.IsClosed = false; + geometry.Figures.Add(figure); + + // 计算贝塞尔曲线的控制点与终点 + Point p1 = new Point(_left + controlLineLength, _top); + Point p2 = new Point(_right - controlLineLength, _bottom); + Point endPoint = new Point(_right, _bottom); + + // 设置起点并添加贝塞尔曲线 + figure.StartPoint = new Point(_left, _top); + var bs = new BezierSegment(); + (bs.Point1, bs.Point2, bs.Point3, bs.IsStroked) = (p1, p2, endPoint, true); + figure.Segments.Add(bs); + + context.DrawGeometry(null, _pen, geometry); + } + + private double _left = 0; + private double _right = 0; + private double _top = 0; + private double _bottom = 0; + + /// 控制线最短长度 + private const int _minLength = 40; + + private readonly Pen _pen = new Pen(new SolidColorBrush(Color.FromArgb(64, 255, 255, 255)), 5); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs new file mode 100644 index 0000000..45057c1 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using XLib.Node; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 连接线图层 + /// + public class ConnectLineLayer : DrawingBoard + { + public List ConnectLineList => _connectLineList; + + public void AddConnectLine(VisualConnectLine connectLine) + { + _connectLineList.Add(connectLine); + // 添加可视元素 + AddVisualElement(connectLine); + // 绘制可视元素 + connectLine.Update(); + } + + public void RemoveConnectLine(PinBase start, PinBase end) + { + int lineIndex = -1; + int index = 0; + // 查找连接线 + foreach (var line in _connectLineList) + { + if (line.StartPin == start && line.EndPin == end) + { + lineIndex = index; + break; + } + + index++; + } + + // 移除连接线 + if (lineIndex != -1) + { + RemoveVisualElement(_connectLineList[lineIndex]); + _connectLineList.RemoveAt(lineIndex); + } + } + + public void ClearConnectLine() + { + _connectLineList.Clear(); + ClearVisualElement(); + } + + private readonly List _connectLineList = new List(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs new file mode 100644 index 0000000..32a0205 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs @@ -0,0 +1,213 @@ +using System.Runtime.CompilerServices; +using System.Windows; +using Avalonia; +using Avalonia.Media; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 网格图层 + /// + public class GridLayer : SingleBoard + { + #region 属性 + + public Point GridCenter { get; set; } + + public int GridLineCount { get; private set; } + + public int GridListCount { get; private set; } + + public int CellWidth => _gridWidth / _subdivideWidth; + + public int CellHeight => _gridHeight / _subdivideHeight; + + #endregion + + #region 公开方法 + + public void MoveLayer(Point offset) + { + _moveOffset = offset; + Update(); + } + + public void Reset() + { + _centerOffset = new Point(); + Update(); + } + + public override void Render(DrawingContext context) + { + DrawGrid(context); + } + + /// + /// 结束拖动时调用 + /// + public void ApplyOffset() + { + _centerOffset = new Point(_centerOffset.X + _moveOffset.X, _centerOffset.Y + _moveOffset.Y); + _moveOffset = new Point(); + } + + /// + /// 获取世界坐标 + /// + public Point GetWorldPoint(Point screenPoint) + { + return new Point(screenPoint.X - GridCenter.X, screenPoint.Y - GridCenter.Y); + } + + /// + /// 获取屏幕坐标 + /// + public Point GetScreenPoint(Point worldPoint) + { + return new Point(GridCenter.X + worldPoint.X, GridCenter.Y + worldPoint.Y); + } + + #endregion + + #region 私有方法 + + /// + /// 绘制网格 + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DrawGrid(DrawingContext context) + { + int line; + int list; + int subIndex; + + // 更新中心点 + Point realOffset = new Point(_centerOffset.X + _moveOffset.X, _centerOffset.Y + _moveOffset.Y); + GridCenter = new Point((int)Bounds.Width / 2 + realOffset.X, (int)Bounds.Height / 2 + realOffset.Y); + + // 更新绘图起始点 + UpdateDrawStart(); + // 更新网格线数量 + UpdateGridLineCount(); + + // 绘制细分线 + _currentPen = _microLine; + for (line = 0; line < GridLineCount; line++) + for (subIndex = 1; subIndex < _subdivideHeight; subIndex++) + DrawHorizontalLine(context, line * _gridHeight + _gridHeight / _subdivideHeight * subIndex); + for (list = 0; list < GridListCount; list++) + for (subIndex = 1; subIndex < _subdivideWidth; subIndex++) + DrawVerticalLine(context, list * _gridWidth + _gridWidth / _subdivideWidth * subIndex); + + // 绘制网格线 + _currentPen = _normalLine; + for (line = 0; line < GridLineCount; line++) + { + if (_drawStart.Y + line * _gridHeight == GridCenter.Y) continue; + DrawHorizontalLine(context, line * _gridHeight); + } + + for (list = 0; list < GridListCount; list++) + { + if (_drawStart.X + list * _gridWidth == GridCenter.X) continue; + DrawVerticalLine(context, list * _gridWidth); + } + + // 绘制中心线 + _currentPen = _centerLine; + DrawHorizontalLine(context, (int)GridCenter.Y - (int)_drawStart.Y); + _currentPen = _centerList; + DrawVerticalLine(context, (int)GridCenter.X - (int)_drawStart.X); + } + + /// + /// 更新绘图起始点,即左上角 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateDrawStart() + { + // 中心点横坐标 < 0 + if (GridCenter.X < 0) + _drawStart = new Point(GridCenter.X + (int)(0 - GridCenter.X) / _gridWidth * _gridWidth, + _drawStart.Y); + else + _drawStart = new Point(GridCenter.X - ((int)GridCenter.X / _gridWidth + 1) * _gridWidth, + _drawStart.Y); + + // 中心点纵坐标 < 0 + if (GridCenter.Y < 0) + _drawStart = new Point(_drawStart.X, + GridCenter.Y + (int)(0 - GridCenter.Y) / _gridHeight * _gridHeight); + else + _drawStart = new Point(_drawStart.X, + GridCenter.Y - ((int)GridCenter.Y / _gridHeight + 1) * _gridHeight); + } + + /// + /// 更新网格线数量 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateGridLineCount() + { + GridLineCount = (int)(Bounds.Height / _gridHeight) + 2; + GridListCount = (int)(Bounds.Width / _gridWidth) + 2; + } + + /// + /// 绘制水平线 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DrawHorizontalLine(DrawingContext context, int y) + { + int realy = (int)_drawStart.Y + y; + if (realy < 0 || realy > Bounds.Height) return; + context.DrawLine(_currentPen, new Point(0, realy), new Point(Bounds.Width, realy)); + } + + /// + /// 绘制垂直线 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DrawVerticalLine(DrawingContext context, int x) + { + int realx = (int)_drawStart.X + x; + if (realx < 0 || realx > Bounds.Width) return; + context.DrawLine(_currentPen, new Point(realx, 0), new Point(realx, Bounds.Height)); + } + + #endregion + + #region 画笔、画刷 + + private readonly Pen _normalLine = new(new SolidColorBrush(Color.FromArgb(255, 30, 30, 30)), 2); + private readonly Pen _microLine = new(new SolidColorBrush(Color.FromArgb(255, 20, 20, 20)), 2); + private readonly Pen _centerLine = new(new SolidColorBrush(Color.FromArgb(255, 60, 60, 60)), 2); + private readonly Pen _centerList = new(new SolidColorBrush(Color.FromArgb(255, 60, 60, 60)), 2); + private Pen? _currentPen; + + #endregion + + #region 字段 + + /// 格子宽度 + private readonly int _gridWidth = 120; + + /// 宽度细分量 + private readonly int _subdivideWidth = 4; + + /// 格子高度 + private readonly int _gridHeight = 120; + + /// 高度细分量 + private readonly int _subdivideHeight = 4; + + private Point _drawStart = new Point(); + private Point _centerOffset = new Point(); + private Point _moveOffset = new Point(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs new file mode 100644 index 0000000..964c83a --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Windows; +using Avalonia; +using Avalonia.Media; +using Avalonia.Threading; +using AXNode.SubSystem.NodeEditSystem.Define; +using XLib.Animate; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 悬停框图层 + /// + public class HoverBoxLayer : SingleBoard, IMotion + { + /// 悬停框 + public TargetBox? Box { get; set; } = null; + + public override void Render(DrawingContext context) + { + if (Box == null) return; + + List pointList = Box.GetPointList(15); + for (int index = 0; index < pointList.Count / 2; index++) + context.DrawLine(_pen, pointList[index * 2], pointList[index * 2 + 1]); + } + + public double GetMotionProperty(string propertyName) => 0; + + public void SetMotionProperty(string propertyName, double value) + { + if (Box == null) return; + switch (propertyName) + { + case "BoxMargin": + Box.BoxOffset = value; + break; + } + + Dispatcher.UIThread.Invoke(Update); + } + + private readonly Pen _pen = new Pen(Brushes.White, 1); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectBoxLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectBoxLayer.cs new file mode 100644 index 0000000..c6bd057 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectBoxLayer.cs @@ -0,0 +1,32 @@ +using System.Windows; +using Avalonia; +using Avalonia.Media; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 选框图层 + /// + public class SelectBoxLayer : SingleBoard + { + public Point Start { get; set; } = new Point(); + + public Point End { get; set; } = new Point(); + + public override void Render(DrawingContext context) + { + base.Render(context); + var start = new Point(Start.X + 0.5, Start.Y + 0.5); + var end = new Point(End.X + 0.5, End.Y + 0.5); + if (End.X >= Start.X) + context.DrawRectangle(_blue, _border, new Rect(start, end)); + else + context.DrawRectangle(_orange, _border, new Rect(start, end)); + } + + private readonly Pen _border = new Pen(new SolidColorBrush(Color.FromRgb(255, 255, 255)), 1); + private readonly Brush _blue = new SolidColorBrush(Color.FromArgb(32, 0, 128, 235)); + private readonly Brush _orange = new SolidColorBrush(Color.FromArgb(32, 255, 120, 0)); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectedBoxLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectedBoxLayer.cs new file mode 100644 index 0000000..7410990 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/SelectedBoxLayer.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Windows; +using Avalonia; +using Avalonia.Media; +using AXNode.SubSystem.NodeEditSystem.Define; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 选中框图层 + /// + public class SelectedBoxLayer : SingleBoard + { + public List BoxList { get; set; } = new List(); + + public override void Render(DrawingContext context) + { + foreach (var box in BoxList) + { + List pointList = box.GetPointList(15); + for (int index = 0; index < pointList.Count / 2; index++) + context.DrawLine(_pen, pointList[index * 2], pointList[index * 2 + 1]); + } + } + + private readonly Pen _pen = new Pen(new SolidColorBrush(Color.FromRgb(237, 100, 21)), 1); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/TempConnectLineLayer.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/TempConnectLineLayer.cs new file mode 100644 index 0000000..e567161 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/TempConnectLineLayer.cs @@ -0,0 +1,64 @@ +using System.Windows; +using Avalonia; +using Avalonia.Media; +using AXNode.SubSystem.NodeEditSystem.Define; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + /// + /// 临时连接线图层 + /// + public class TempConnectLineLayer : SingleBoard + { + public ConnectLine? Line { get; set; } = null; + + public override void Render(DrawingContext context) + { + if (Line == null) return; + + // 计算连接线区域 + _left = Line.Start.X; + _right = Line.End.X; + _top = Line.Start.Y + 0.5; + _bottom = Line.End.Y + 0.5; + // 计算贝塞尔曲线的控制线长度 + double controlLineLength = (_right - _left) / 2; + if (controlLineLength < _minLength) controlLineLength = _minLength; + + // 创建形状 + PathGeometry geometry = new PathGeometry(); + PathFigure figure = new PathFigure(); + figure.IsClosed = false; + geometry.Figures.Add(figure); + + // 计算贝塞尔曲线的控制点与终点 + Point p1 = new Point(_left + controlLineLength, _top); + Point p2 = new Point(_right - controlLineLength, _bottom); + Point endPoint = new Point(_right, _bottom); + + // 设置起点并添加贝塞尔曲线 + figure.StartPoint = new Point(_left, _top); + var bs = new BezierSegment(); + (bs.Point1, bs.Point2, bs.Point3, bs.IsStroked) = (p1, p2, endPoint, true); + figure.Segments.Add(bs); + + // 绘制形状 + context.DrawGeometry(null, _pen, geometry); + } + + #region 字段 + + private double _left = 0; + private double _right = 0; + private double _top = 0; + private double _bottom = 0; + + /// 控制线最短长度 + private readonly int _minLength = 40; + + private readonly Pen _pen = new Pen(new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)), 1); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/Layer/VisualConnectLine.cs b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/VisualConnectLine.cs new file mode 100644 index 0000000..57d4f72 --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/Layer/VisualConnectLine.cs @@ -0,0 +1,79 @@ +using System.Windows; +using Avalonia; +using Avalonia.Media; +using XLib.Node; +using XLib.Avalonia.Drawing; + +namespace AXNode.SubSystem.NodeEditSystem.Panel.Layer +{ + public class VisualConnectLine : VisualElement + { + // public VisualConnectLine() => _penExecute.Freeze(); + + public override bool HitTest(Point point) + { + // todo + return false; + } + + public PinBase StartPin { get; set; } + + public PinBase EndPin { get; set; } + + public Point Start { get; set; } + + public Point End { get; set; } + + public Color Color { get; set; } = Colors.White; + + public bool IsData { get; set; } = false; + + public override void Render(DrawingContext context) + { + // 计算连接线区域 + _left = Start.X; + _right = End.X; + _top = Start.Y + 0.5; + _bottom = End.Y + 0.5; + + // 计算贝塞尔曲线的控制线长度 + double controlLineLength = (_right - _left) / 2; + if (controlLineLength < _minLength) controlLineLength = _minLength; + + // 创建形状 + PathGeometry geometry = new PathGeometry(); + PathFigure figure = new PathFigure(); + figure.IsClosed = false; + geometry.Figures.Add(figure); + + // 计算贝塞尔曲线的控制点与终点 + Point p1 = new Point(_left + controlLineLength, _top); + Point p2 = new Point(_right - controlLineLength, _bottom); + Point endPoint = new Point(_right, _bottom); + + // 设置起点并添加贝塞尔曲线 + figure.StartPoint = new Point(_left, _top); + var bs = new BezierSegment(); + (bs.Point1, bs.Point2, bs.Point3, bs.IsStroked) = (p1, p2, endPoint, true); + figure.Segments.Add(bs); + + // Pen pen = new Pen(new SolidColorBrush(Color), 1); + // 绘制形状 + if (!IsData) context.DrawGeometry(null, _penExecute, geometry); + else context.DrawGeometry(null, new Pen(new SolidColorBrush(Color), 1), geometry); + } + + public override string ToString() => $"{Start}-{End}"; + + private double _left = 0; + private double _right = 0; + private double _top = 0; + private double _bottom = 0; + + /// 控制线最短长度 + private readonly int _minLength = 40; + + /// 执行引脚线 + private readonly Pen _penExecute = new Pen(new SolidColorBrush(Color.FromArgb(255, 196, 126, 255)), 1); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs b/AXNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs new file mode 100644 index 0000000..09d13ec --- /dev/null +++ b/AXNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs @@ -0,0 +1,253 @@ +using System; +using System.Windows.Input; +using Avalonia.Input; +using AXNode; +using AXNode.SubSystem.NodeEditSystem.Panel.Component; +using XLib.Avalonia; +using XLib.Avalonia.Behavior; +using AXNode.SubSystem.NodeEditSystem.Panel.Component; +using XLib.AvaloniaControl; + +namespace AXNode.SubSystem.NodeEditSystem.Panel +{ + public class SelectTool : ToolBase + { + public SelectTool(InteractionComponent host) : base(host) + { + } + + /// 光标 + public Cursor Cursor { get; set; } + + public override void Init() + { + 移动(); + + 命中空白(); + 命中节点(); + 命中引脚(); + 双击节点(); + + 中键按下(); + + 右键引脚(); + 右键连接线(); + } + + public override void OnLeftButtonDown() + { + switch (_host.GetHitedArea()) + { + case Define.MouseHitedArea.Space: + Invoke(Behaviors.HitedSpace); + break; + case Define.MouseHitedArea.Node: + Invoke("命中节点"); + break; + case Define.MouseHitedArea.Pin: + Invoke("命中引脚"); + break; + } + } + + public override void OnDoubleClick() + { + if (_host.GetHitedArea() == Define.MouseHitedArea.Space) + Invoke("双击空白处"); + else if (_host.GetHitedArea() == Define.MouseHitedArea.Node) + Invoke("双击节点"); + } + + public override void OnRightButtonDown() + { + var hitedArea = _host.GetHitedArea(); + if (hitedArea == Define.MouseHitedArea.Pin) + Invoke("右键引脚"); + else if (hitedArea == Define.MouseHitedArea.ConnectLine) + Invoke("右键连接线"); + } + + private void 移动() + { + NewTree(Behaviors.Move, (_) => + { + _host.HandleMouseMove(); + ResetTree(); + }); + Finish(); + } + + private void 命中空白() + { + NewTree(Behaviors.HitedSpace, (_) => + { + _host.RemoveNodeFocus(); + if (Keyboard.Modifiers != KeyModifiers.Control) _host.ClearSelect(); + _host.BeginDrawSelectBox(); + _host.CaptureOperationLayer(); + }); + NewNode(Behaviors.LeftUp, (_) => + { + _host.CancelDrawSelectBox(); + _host.ReleaseOperationLayer(); + ResetTree(); + }); + BackToRoot(); + NewNode(Behaviors.Move, (_) => { _host.DrawSelectBox(); }); + NewNode(Behaviors.LeftUp, (_) => + { + _host.EndDrawSelectBox(); + _host.HandleMouseMove(); + _host.ReleaseOperationLayer(); + ResetTree(); + }); + Finish(); + } + + private void 命中节点() + { + NewTree("命中节点", (_) => + { + // 清除悬停框 + _host.ClearHoverBox(); + // 按下了“Ctrl”键,加选或减选 + if (Keyboard.Modifiers == KeyModifiers.Control) + { + if (_host.CurrentNodeSelected()) _host.RemoveSelect(); + else _host.AddSelect(); + } + // 当前节点未选中 + else if (!_host.CurrentNodeSelected()) + { + _host.ClearSelect(); + _host.SetTop(); + _host.AddSelect(); + } + + // 开始拖动节点 + _host.BeginDragNode(); + _host.CaptureOperationLayer(); + }); + NewNode(Behaviors.LeftUp, (_) => + { + _host.CancelDragNode(); + _host.HandleMouseMove(); + _host.ReleaseOperationLayer(); + ResetTree(); + }); + BackToRoot(); + NewNode(Behaviors.Move, (_) => _host.DragNode()); + NewNode(Behaviors.LeftUp, (_) => + { + _host.EndDragNode(); + _host.HandleMouseMove(); + _host.ReleaseOperationLayer(); + ResetTree(); + }); + Finish(); + } + + private void 命中引脚() + { + NewTree("命中引脚", (_) => + { + _host.ClearHoverBox(); + // 开始绘制连接线 + _host.BeginDrawConnectLine(); + }); + // NewNode(Behaviors.LeftUp, (_) => + // { + // // 取消绘制连接线 + // _host.CancelDrawConnectLine(); + // _host.HandleMouseMove(); + // ResetTree(); + // }); + BackToRoot(); + NewNode(Behaviors.Move, (_) => + { + // 绘制连接线 + _host.DrawConnectLine(); + }); + NewNode(Behaviors.LeftUp, (_) => + { + // 结束绘制连接线 + _host.EndDrawConnectLine(); + _host.HandleMouseMove(); + ResetTree(); + }); + Finish(); + } + + private void 双击节点() + { + NewTree("双击节点", (_) => + { + _host.StartAndExecute(); + ResetTree(); + }); + Finish(); + } + + private void 中键按下() + { + NewTree(Behaviors.MiddleDown, (_) => + { + _host.ClearHoverBox(); + _host.BeginDragViewport(); + _host.CaptureOperationLayer(); + }); + NewNode(Behaviors.MiddleUp, (_) => + { + _host.ReleaseOperationLayer(); + _host.CancelDragViewport(); + _host.HandleMouseMove(); + ResetTree(); + }); + BackToRoot(); + NewNode(Behaviors.Move, (_) => { _host.DragViewport(); }); + NewNode(Behaviors.MiddleUp, (_) => + { + _host.ReleaseOperationLayer(); + _host.EndDragViewport(); + _host.HandleMouseMove(); + ResetTree(); + }); + Finish(); + } + + private void 右键引脚() + { + NewTree("右键引脚", (_) => + { + _host.ClearHoverBox(); + _host.BeginBreakPin(); + }); + NewNode(Behaviors.RightUp, (_) => + { + _host.EndBreakPin(); + _host.HandleMouseMove(); + ResetTree(); + }); + BackToRoot(); + NewNode(Behaviors.Move, (_) => { _host.CancelBreakPin(); }); + NewNode(Behaviors.RightUp, (_) => + { + _host.HandleMouseMove(); + ResetTree(); + }); + Finish(); + } + + private void 右键连接线() + { + NewTree("右键连接线", null); + NewNode(Behaviors.RightUp, (_) => + { + _host.RemoveConnectLine(); + _host.HandleMouseMove(); + ResetTree(); + }); + Finish(); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Double.cs b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Double.cs new file mode 100644 index 0000000..84cc463 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Double.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Data +{ + public class Data_Double : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Data, "Variate", "小数"); + + PinGroupList.Add(new DataPinGroup(this, "double", "数据", "0.0") + { + Readable = true, + Writeable = false + }); + + InitPinGroup(); + } + + public override string GetTypeString() => nameof(Data_Double); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Data", GetData(0) }, + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + SetData(0, paraDict["Data"]); + } + + protected override NodeBase CloneNode() => new Data_Double(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Int.cs b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Int.cs new file mode 100644 index 0000000..a8603c5 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_Int.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Data +{ + public class Data_Int : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Data, "Variate", "整数"); + + PinGroupList.Add(new DataPinGroup(this, "int", "数据", "0") + { + Readable = true, + Writeable = false + }); + + InitPinGroup(); + } + + public override string GetTypeString() => nameof(Data_Int); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Data", GetData(0) }, + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + SetData(0, paraDict["Data"]); + } + + protected override NodeBase CloneNode() => new Data_Int(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_String.cs b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_String.cs new file mode 100644 index 0000000..5006b0c --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Data/Data_String.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Data +{ + public class Data_String : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Data, "Variate", "字符串"); + + PinGroupList.Add(new DataPinGroup(this, "string", "数据", "") + { + BoxWidth = 240, + Readable = true, + Writeable = false + }); + + InitPinGroup(); + } + + public override string GetTypeString() => nameof(Data_String); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Data", GetData(0) }, + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + SetData(0, paraDict["Data"]); + } + + protected override NodeBase CloneNode() => new Data_String(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs b/AXNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs new file mode 100644 index 0000000..a9a4b77 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using AXNode.SubSystem.ControlSystem; +using XLib.Base.Drive; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Drivers +{ + /// + /// 帧驱动器 + /// + public class FrameDriver : NodeBase, IFrameDriver + { + #region IFrameDriver 属性 + + public string Name { get; set; } = "帧驱动器"; + + #endregion + + #region 生命周期 + + public override void Init() + { + SetViewProperty(NodeColorSet.Driver, "FrameDriver", "帧驱动器"); + + DataPinGroup fps = new DataPinGroup(this, "double", "帧率", "25") { Readable = false, Writeable = false }; + fps.ValueChanged += FpsChanged; + PinGroupList.Add(fps); + PinGroupList.Add(new ActionPinGroup(this, "更新")); + + InitPinGroup(); + } + + public override void Enable() + { + ControlEngine.Instance.Connect(this); + } + + public override void Disable() + { + ControlEngine.Instance.Disconnect(this); + } + + public override void Clear() + { + ControlEngine.Instance.Disconnect(this); + } + + #endregion + + #region 公开方法 + + public override string GetTypeString() => nameof(FrameDriver); + + public override Dictionary GetParaDict() => + new Dictionary { { "Fps", GetData(0) } }; + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(0, paraDict["Fps"]); + } + catch (Exception) + { + } + } + + #endregion + + #region IFrameDriver 方法 + + public void Update() + { + _delay++; + while (_delay >= _frameLength) + { + ((ActionPinGroup)PinGroupList[1]).Invoke(); + _delay -= _frameLength; + } + } + + #endregion + + #region 内部方法 + + protected override NodeBase CloneNode() => new FrameDriver(); + + #endregion + + #region 私有方法 + + private void FpsChanged() + { + try + { + double fps = double.Parse(GetData(0)); + if (fps > 120) fps = 120; + if (fps > 0) _frameLength = 1000 / fps; + } + catch (Exception) + { + } + } + + #endregion + + #region 字段 + + /// 单帧时长:毫秒 + private double _frameLength = 40; + + /// 当前延迟 + private double _delay = 0; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs b/AXNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs new file mode 100644 index 0000000..92a9317 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using AXNode.SubSystem.ControlSystem; +using XLib.Base.Drive; +using XLib.Base.Ex; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Drivers +{ + /// + /// 定时驱动器 + /// + public class TimerDriver : NodeBase, IFrameDriver, IProgressGetter + { + #region 生命周期 + + public override void Init() + { + SetViewProperty(NodeColorSet.Driver, "Timer", "定时驱动器"); + + DataPinGroup time = new DataPinGroup(this, "double", "间隔毫秒", "5000") + { Readable = false, Writeable = false }; + time.ValueChanged += Time_ValueChanged; + PinGroupList.Add(time); + PinGroupList.Add(new ActionPinGroup(this, "更新")); + + InitPinGroup(); + } + + public override void Enable() + { + OpenProgressBar?.Invoke(this); + ControlEngine.Instance.Connect(this); + } + + public override void Disable() + { + CloseProgressBar?.Invoke(); + ControlEngine.Instance.Disconnect(this); + _delay = 0; + } + + public override void Clear() + { + CloseProgressBar?.Invoke(); + ControlEngine.Instance.Disconnect(this); + _delay = 0; + } + + #endregion + + #region NodeBase 方法 + + public override string GetTypeString() => nameof(TimerDriver); + + public override Dictionary GetParaDict() + { + return new Dictionary + { + { "Time", GetData(0) } + }; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(0, paraDict["Time"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new TimerDriver(); + + #endregion + + #region IFrameDriver 方法 + + public void Update() + { + _delay++; + while (_delay >= _frameLength) + { + GetPinGroup(1).Invoke(); + _delay -= _frameLength; + } + } + + #endregion + + #region IProgressGetter 方法 + + public double GetProgress() => _delay / _frameLength; + + #endregion + + #region 私有方法 + + private void Time_ValueChanged() + { + try + { + _frameLength = double.Parse(GetData(0)).Limit(1000 / 120.0, double.MaxValue); + } + catch (Exception) + { + } + } + + #endregion + + #region 字段 + + /// 单帧时长:毫秒 + private double _frameLength = 5000; + + /// 当前延迟 + private double _delay = 0; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs b/AXNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs new file mode 100644 index 0000000..c4fd9ab --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using AXNode.SubSystem.EventSystem; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Events +{ + /// + /// 键盘事件 + /// + public class Event_Keyboard : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Event, "Key", "按键"); + + PinGroupList.Add(new DataPinGroup(this, "string", "当前按键", "") { BoxWidth = 120, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "监听按键", "Space") + { BoxWidth = 120, Readable = false, Writeable = false }); + PinGroupList.Add(new ActionPinGroup(this, "按下")); + PinGroupList.Add(new ActionPinGroup(this, "松开")); + + InitPinGroup(); + } + + public override void Enable() + { + EM.Instance.Add(EventType.KeyDown, OnKeyDown); + EM.Instance.Add(EventType.KeyUp, OnKeyUp); + } + + public override void Disable() + { + EM.Instance.Remove(EventType.KeyDown, OnKeyDown); + EM.Instance.Remove(EventType.KeyUp, OnKeyUp); + } + + public override void Clear() + { + EM.Instance.Remove(EventType.KeyDown, OnKeyDown); + EM.Instance.Remove(EventType.KeyUp, OnKeyUp); + } + + public override string GetTypeString() => nameof(Event_Keyboard); + + public override Dictionary GetParaDict() => + new Dictionary { { "ListenKey", GetData(1) } }; + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["ListenKey"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Event_Keyboard(); + + private void OnKeyDown(string key) + { + SetData(0, key); + if (key == GetData(1)) + GetPinGroup(2).Invoke(); + } + + private void OnKeyUp(string key) + { + SetData(0, key); + if (key == GetData(1)) + GetPinGroup(3).Invoke(); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs new file mode 100644 index 0000000..486af58 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Flows +{ + public class Flow_If : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Flow, "Flow", "判断"); + + PinGroupList.Add(new ExecutePinGroup(this, "根据逻辑值执行不同的逻辑")); + PinGroupList.Add(new DataPinGroup(this, "bool", "逻辑值", "True") { Readable = false }); + PinGroupList.Add(new ActionPinGroup(this, "逻辑真")); + PinGroupList.Add(new ActionPinGroup(this, "逻辑假")); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 获取逻辑值引脚组 + DataPinGroup logicValueGroup = (DataPinGroup)PinGroupList[1]; + // 执行连接至逻辑值的节点 + if (logicValueGroup.InputPin != null && logicValueGroup.InputPin.SourceList.Count > 0) + // 输入引脚.第一个源引脚.所属引脚组.所属节点.执行 + logicValueGroup.InputPin.SourceList[0].OwnerGroup.OwnerNode.Execute(); + // 更新逻辑值 + UpdateData(1); + // 解析值 + bool logicResult = bool.Parse(GetData(1)); + + if (logicResult == true) GetPinGroup(2).Invoke(); + else GetPinGroup(3).Invoke(); + + GetPinGroup().Execute(); + } + + public override string GetTypeString() => nameof(Flow_If); + + public override Dictionary GetParaDict() => + new Dictionary { { "LogicValue", GetData(1) } }; + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["LogicValue"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Flow_If(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_LoopByCount.cs b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_LoopByCount.cs new file mode 100644 index 0000000..b10f276 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_LoopByCount.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Flows +{ + public class Flow_LoopByCount : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Flow, "Loop", "计数循环"); + + PinGroupList.Add(new ExecutePinGroup(this, "循环执行多少次循环体")); + PinGroupList.Add(new DataPinGroup(this, "int", "次数", "10") { Readable = false }); + PinGroupList.Add(new ActionPinGroup(this, "循环体")); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + int count = int.Parse(GetData(1)); + for (int counter = 0; counter < count; counter++) + GetPinGroup(2).Invoke(); + GetPinGroup().Execute(); + } + + public override string GetTypeString() => nameof(Flow_LoopByCount); + + public override Dictionary GetParaDict() => + new Dictionary { { "Times", GetData(1) } }; + + protected override NodeBase CloneNode() => new Flow_LoopByCount(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs new file mode 100644 index 0000000..2079257 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using XLib.Base.Ex; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Flows +{ + public class Flow_Switch : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Flow, "Flow", "选择执行"); + + PinGroupList.Add(new ExecutePinGroup(this, "执行与值相等的引脚")); + PinGroupList.Add(new DataPinGroup(this, "string", "值", "") { BoxWidth = 120, Readable = false }); + PinGroupList.Add(new ActionPinGroup(this, "Case_01")); + PinGroupList.Add(new ActionPinGroup(this, "Case_02")); + PinGroupList.Add(new ActionPinGroup(this, "Default")); + + CustomListProperty caseList = new CustomListProperty + { + Name = "值列表", + ItemList = new List { "Case_01", "Case_02", "Default" }, + ItemAdded = OnItemAdded, + ItemRemoved = OnItemRemoved, + ItemChanged = OnItemChanged + }; + PropertyList.Add(caseList); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新值 + UpdateData(1); + // 获取动作引脚索引 + int actionIndex = GetActionIndex(GetData(1)); + // 执行引脚 + if (actionIndex != -1) GetPinGroup(actionIndex + 2).Invoke(); + + GetPinGroup().Execute(); + } + + public override string GetTypeString() => nameof(Flow_Switch); + + public override Dictionary GetParaDict() => + new Dictionary { { "Value", GetData(1) } }; + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Value"]); + } + catch (Exception) + { + } + } + + public override Dictionary GetPropertyDict() + { + return new Dictionary { { "CustomList", PropertyList[0].ToString() } }; + } + + public override void LoadPropertyDict(string version, Dictionary propertyDict) + { + try + { + PropertyList[0] = new CustomListProperty(propertyDict["CustomList"]) + { + Name = "值列表", + ItemAdded = OnItemAdded, + ItemRemoved = OnItemRemoved, + ItemChanged = OnItemChanged + }; + UpdateAllItem(); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Flow_Switch(); + + #region 属性变更 + + private void OnItemAdded(string item) + { + ActionPinGroup group = new ActionPinGroup(this, item); + PinGroupList.Add(group); + group.Init(); + // 通知引脚组变更 + InvokePinGroupListChanged(); + } + + private void OnItemRemoved(int index) + { + // 引脚组索引 + int groupIndex = index + 2; + if (PinGroupList.IndexOut(groupIndex)) return; + // 断开引脚 + BreakPin(groupIndex); + // 移除引脚组 + PinGroupList.RemoveAt(groupIndex); + // 通知引脚组变更 + InvokePinGroupListChanged(); + } + + private void OnItemChanged(int index, string value) + { + int groupIndex = index + 2; + if (PinGroupList.IndexOut(groupIndex)) return; + ((ActionPinGroup)PinGroupList[groupIndex]).ActionName = value; + // 通知引脚组变更 + InvokePinGroupListChanged(); + } + + #endregion + + #region 私有方法 + + /// + /// 更新全部项 + /// + private void UpdateAllItem() + { + while (PinGroupList.Count > 2) PinGroupList.RemoveLast(); + foreach (var item in ((CustomListProperty)PropertyList[0]).ItemList) + { + ActionPinGroup group = new ActionPinGroup(this, item); + PinGroupList.Add(group); + group.Init(); + } + } + + private int GetActionIndex(string value) + { + int defaultIndex = ((CustomListProperty)PropertyList[0]).ItemList.IndexOf("Default"); + int valueIndex = ((CustomListProperty)PropertyList[0]).ItemList.IndexOf(value); + return valueIndex == -1 ? defaultIndex : valueIndex; + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs new file mode 100644 index 0000000..fed1fb7 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Flows +{ + public class Flow_While : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Flow, "Loop", "条件循环"); + + PinGroupList.Add(new ExecutePinGroup(this, "逻辑值为真,则无限循环执行")); + PinGroupList.Add(new DataPinGroup(this, "bool", "逻辑值", "True") { Readable = false }); + PinGroupList.Add(new ActionPinGroup(this, "循环体")); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 获取逻辑值引脚组 + DataPinGroup logicValueGroup = (DataPinGroup)PinGroupList[1]; + // 获取循环体引脚组 + ActionPinGroup loopBodyGroup = GetPinGroup(2); + + SynchronizationContext? context = SynchronizationContext.Current; + if (context == null) + { + context = new SynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(context); + } + + Task.Run(() => ExecuteWhileSync(logicValueGroup, loopBodyGroup, context)); + } + + public override string GetTypeString() => nameof(Flow_While); + + public override Dictionary GetParaDict() => + new Dictionary { { "LogicValue", GetData(1) } }; + + protected override NodeBase CloneNode() => new Flow_While(); + + private void ExecuteWhileSync(DataPinGroup logicValueGroup, ActionPinGroup loopBodyGroup, + SynchronizationContext context) + { + try + { + while (true) + { + // 执行连接至逻辑值的节点 + if (logicValueGroup.InputPin != null && logicValueGroup.InputPin.SourceList.Count > 0) + // 输入引脚.第一个源引脚.所属引脚组.所属节点.执行 + logicValueGroup.InputPin.SourceList[0].OwnerGroup.OwnerNode.Execute(); + // 更新逻辑值 + UpdateData(1); + // 如果值为假,则退出循环 + if (!bool.Parse(GetData(1))) break; + // 执行循环体 + loopBodyGroup.Invoke(); + } + + context.Post(_ => GetPinGroup().Execute(), null); + } + catch (Exception ex) + { + context.Post(_ => + { + RunError = true; + Stop(); + InvokeExecuteError(ex); + }, null); + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs new file mode 100644 index 0000000..a03b4c7 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using XLib.Node; + + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_BinOP : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "二元运算"); + + PinGroupList.Add(new ExecutePinGroup(this, "执行四则运算")); + PinGroupList.Add(new DataPinGroup(this, "double", "数值一", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "运算符", "+") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "数值二", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "结果", "0") { Writeable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新数值一、数值二 + ((DataPinGroup)PinGroupList[1]).UpdateValue(); + ((DataPinGroup)PinGroupList[3]).UpdateValue(); + // 解析数值 + double num1 = double.Parse(((DataPinGroup)PinGroupList[1]).Value); + double num2 = double.Parse(((DataPinGroup)PinGroupList[3]).Value); + // 执行对比 + double result = 0d; + switch (((DataPinGroup)PinGroupList[2]).Value) + { + case "+": + result = num1 + num2; + break; + case "-": + result = num1 - num2; + break; + case "*": + result = num1 * num2; + break; + case "/": + result = num1 / num2; + break; + } + + // 更新结果 + ((DataPinGroup)PinGroupList[4]).Value = result.ToString(); + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Num1", GetData(1) }, + { "Operator", GetData(2) }, + { "Num2", GetData(3) }, + { "Result", GetData(4) } + }; + return result; + } + + public override string GetTypeString() => nameof(Func_BinOP); + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Num1"]); + SetData(2, paraDict["Operator"]); + SetData(3, paraDict["Num2"]); + SetData(4, paraDict["Result"]); + } + catch (Exception) + { + } + } + + + protected override NodeBase CloneNode() => new Func_Compare(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs new file mode 100644 index 0000000..e48592c --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_Compare : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "关系运算"); + + PinGroupList.Add(new ExecutePinGroup(this, "对比两个值的关系")); + PinGroupList.Add(new DataPinGroup(this, "double", "数值一", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "运算符", "=") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "数值二", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "bool", "对比结果", "True") { Writeable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新数值一、数值二 + ((DataPinGroup)PinGroupList[1]).UpdateValue(); + ((DataPinGroup)PinGroupList[3]).UpdateValue(); + // 解析数值 + double num1 = double.Parse(((DataPinGroup)PinGroupList[1]).Value); + double num2 = double.Parse(((DataPinGroup)PinGroupList[3]).Value); + // 执行对比 + bool result = false; + switch (((DataPinGroup)PinGroupList[2]).Value) + { + case "<": + result = num1 < num2; + break; + case "=": + result = num1 == num2; + break; + case ">": + result = num1 > num2; + break; + case "<=": + result = num1 < num2 || num1 == num2; + break; + case ">=": + result = num1 > num2 || num1 == num2; + break; + case "!=": + result = num1 != num2; + break; + } + + // 更新结果 + ((DataPinGroup)PinGroupList[4]).Value = result == true ? "True" : "False"; + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override string GetTypeString() => nameof(Func_Compare); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Num1", GetData(1) }, + { "Operator", GetData(2) }, + { "Num2", GetData(3) }, + { "Result", GetData(4) } + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Num1"]); + SetData(2, paraDict["Operator"]); + SetData(3, paraDict["Num2"]); + SetData(4, paraDict["Result"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_Compare(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_CreateThread.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_CreateThread.cs new file mode 100644 index 0000000..576e764 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_CreateThread.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_CreateThread : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "多线程执行"); + + PinGroupList.Add(new ExecutePinGroup(this, "创建新线程,并在新线程中执行后续节点")); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + Task.Run(() => GetPinGroup().Execute()); + } + + public override string GetTypeString() => nameof(Func_CreateThread); + + public override Dictionary GetParaDict() => new Dictionary(); + + protected override NodeBase CloneNode() => new Func_CreateThread(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs new file mode 100644 index 0000000..5585ac7 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_Delay : NodeBase + { + #region 生命周期 + + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Delay", "延迟执行"); + + PinGroupList.Add(new ExecutePinGroup(this, "延迟指定毫秒后执行")); + PinGroupList.Add(new DataPinGroup(this, "int", "时长", "5000") { Readable = false, Writeable = false }); + + InitPinGroup(); + } + + #endregion + + #region NodeBase 方法 + + protected override async void ExecuteNode() + { + await Task.Delay(int.Parse(GetData(1))); + GetPinGroup().Execute(); + } + + public override string GetTypeString() => nameof(Func_Delay); + + public override Dictionary GetParaDict() + { + return new Dictionary + { + { "Time", GetData(1) } + }; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Time"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_Delay(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs new file mode 100644 index 0000000..2a2a2a7 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_Log : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "日志"); + + PinGroupList.Add(new ExecutePinGroup(this, "发送消息至控制台")); + PinGroupList.Add(new DataPinGroup(this, "string", "消息", "Message") { BoxWidth = 180, Readable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + ((DataPinGroup)PinGroupList[1]).UpdateValue(); + + Console.WriteLine(((DataPinGroup)PinGroupList[1]).Value); + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override string GetTypeString() => nameof(Func_Log); + + public override Dictionary GetParaDict() => + new Dictionary { { "Msg", GetData(1) } }; + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Msg"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_Log(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs new file mode 100644 index 0000000..463d8e8 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using XLib.Base.Ex; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_NumberToRatio : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "数值转比例"); + + PinGroupList.Add(new ExecutePinGroup(this, "将指定范围内的数值转换为比例值")); + PinGroupList.Add(new DataPinGroup(this, "double", "最小值", "0") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "最大值", "100") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "当前值", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "转换结果", "0") { Readable = true, Writeable = false }); + + NodeProperty precision = new NodeProperty("int", "小数位数", "2"); + precision.ValueChanged += Precision_ValueChanged; + PropertyList.Add(precision); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新当前值 + ((DataPinGroup)PinGroupList[3]).UpdateValue(); + // 解析参数 + double min = double.Parse(((DataPinGroup)PinGroupList[1]).Value); + double max = double.Parse(((DataPinGroup)PinGroupList[2]).Value); + double value = double.Parse(((DataPinGroup)PinGroupList[3]).Value).Limit(min, max); + // 计算结果 + double result = (value - min) / (max - min); + // 设置结果 + ((DataPinGroup)PinGroupList[4]).Value = result.ToString(_format); + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override string GetTypeString() => nameof(Func_NumberToRatio); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Min", GetData(1) }, + { "Max", GetData(2) }, + { "Current", GetData(3) }, + { "Result", GetData(4) } + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Min"]); + SetData(2, paraDict["Max"]); + SetData(3, paraDict["Current"]); + SetData(4, paraDict["Result"]); + } + catch (Exception) + { + } + } + + public override Dictionary GetPropertyDict() + { + return new Dictionary + { + { "Precision", PropertyList[0].Value } + }; + } + + public override void LoadPropertyDict(string version, Dictionary propertyDict) + { + try + { + PropertyList[0].Value = propertyDict["Precision"]; + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_NumberToRatio(); + + private void Precision_ValueChanged(string value) + { + int count = int.Parse(value).Limit(0, 8); + _format = count == 0 ? "0" : "0." + new string('0', count); + } + + private string _format = "0.00"; + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs new file mode 100644 index 0000000..268d7e4 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + /// + /// 比例转整数 + /// + public class Func_RatioToInt : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "比例转整数"); + + PinGroupList.Add(new ExecutePinGroup(this, "将比例值转换为指定范围内的整数")); + PinGroupList.Add(new DataPinGroup(this, "int", "最小值", "0") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "int", "最大值", "100") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "比例", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "int", "转换结果", "0") { Readable = true, Writeable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新比例 + ((DataPinGroup)PinGroupList[3]).UpdateValue(); + // 解析参数 + int min = int.Parse(((DataPinGroup)PinGroupList[1]).Value); + int max = int.Parse(((DataPinGroup)PinGroupList[2]).Value); + double ratio = double.Parse(((DataPinGroup)PinGroupList[3]).Value); + // 计算结果 + int result = (int)(Math.Round((max - min) * ratio) + min); + // 设置结果 + ((DataPinGroup)PinGroupList[4]).Value = result.ToString(); + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override string GetTypeString() => nameof(Func_RatioToInt); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Min", GetData(1) }, + { "Max", GetData(2) }, + { "Current", GetData(3) }, + { "Result", GetData(4) } + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Min"]); + SetData(2, paraDict["Max"]); + SetData(3, paraDict["Current"]); + SetData(4, paraDict["Result"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_RatioToInt(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs new file mode 100644 index 0000000..72c57fa --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + /// + /// 发送网络消息 + /// + public class Func_SendNetMessage : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "发送网络消息"); + + PinGroupList.Add(new ExecutePinGroup(this, "发送字符串至网络设备")); + PinGroupList.Add(new DataPinGroup(this, "string", "地址", "127.0.0.1:2400") + { BoxWidth = 120, Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "协议", "udp") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "bool", "字节模式", "False") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "消息", "Hello World") + { BoxWidth = 200, Readable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 解析地址 + IPEndPoint address = IPEndPoint.Parse(GetData(1)); + // 获取协议 + string protocol = GetData(2).ToUpper(); + // 使用字节模式 + bool byteMode = bool.Parse(GetData(3)); + // 获取消息 + UpdateData(4); + string message = GetData(4); + // 消息转字节 + byte[]? messageByte = MessageToByteArray(message, byteMode); + if (messageByte == null) return; + // 发送消息 + if (protocol == "UDP") + { + UdpClient udpClient = new UdpClient(); + udpClient.Send(messageByte, messageByte.Length, address); + + GetPinGroup().Execute(); + } + else if (protocol == "TCP") + { + SynchronizationContext? context = SynchronizationContext.Current; + if (context == null) + { + context = new SynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(context); + } + + Task.Run(() => SendTcpMessageSync(address, messageByte, context)); + } + } + + public override string GetTypeString() => nameof(Func_SendNetMessage); + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Address", GetData(1) }, + { "Protocol", GetData(2) }, + { "ByteMode", GetData(3) }, + { "Message", GetData(4) } + }; + return result; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + foreach (var para in paraDict) + { + switch (para.Key) + { + case "Address": + SetData(1, para.Value); + break; + case "Protocol": + SetData(2, para.Value); + break; + case "ByteMode": + SetData(3, para.Value); + break; + case "Message": + SetData(4, para.Value); + break; + } + } + } + + protected override NodeBase CloneNode() => new Func_SendNetMessage(); + + /// + /// 将消息转字节数组 + /// + private byte[]? MessageToByteArray(string message, bool byteMode) + { + if (!byteMode) return Encoding.UTF8.GetBytes(message); + try + { + // 将消息按空格分割 + string[] byteArraySource = message.Split(' '); + // 转换为字节数组 + byte[] byteArray = new byte[byteArraySource.Length]; + for (int index = 0; index < byteArraySource.Length; index++) + byteArray[index] = Convert.ToByte(byteArraySource[index], 16); + return byteArray; + } + catch (Exception) + { + } + + return null; + } + + /// + /// 异步发送Tcp消息 + /// + private void SendTcpMessageSync(IPEndPoint address, byte[] messageByte, SynchronizationContext context) + { + try + { + // 发送消息 + TcpClient tcpClient = new TcpClient(); + tcpClient.Connect(address); + NetworkStream stream = tcpClient.GetStream(); + stream.Write(messageByte, 0, messageByte.Length); + stream.Close(); + tcpClient.Close(); + // 发送完成,返回调用线程,执行下一个节点 + context.Post(_ => GetPinGroup().Execute(), null); + } + catch (Exception ex) + { + // 发送异常,在调用线程上执行异常处理 + context.Post(_ => + { + RunError = true; + Stop(); + InvokeExecuteError(ex); + }, null); + } + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs new file mode 100644 index 0000000..8d993b9 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using XLib.Node; + +namespace AXNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_Sleep : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Pause", "暂停执行"); + + PinGroupList.Add(new ExecutePinGroup(this, "暂停指定毫秒后执行")); + PinGroupList.Add(new DataPinGroup(this, "int", "时长", "5000") { Readable = false, Writeable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + Thread.Sleep(int.Parse(GetData(1))); + GetPinGroup().Execute(); + } + + public override string GetTypeString() => nameof(Func_Sleep); + + public override Dictionary GetParaDict() + { + return new Dictionary + { + { "Time", GetData(1) } + }; + } + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Time"]); + } + catch (Exception) + { + } + } + + protected override NodeBase CloneNode() => new Func_Sleep(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/NodeLibManager.cs b/AXNode/SubSystem/NodeLibSystem/NodeLibManager.cs new file mode 100644 index 0000000..e83415e --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/NodeLibManager.cs @@ -0,0 +1,245 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using XLib.Base; +using XLib.Base.Ex; +using XLib.Base.VirtualDisk; +using XLib.Node; +using AXNode.SubSystem.NodeLibSystem.Define.Data; +using AXNode.SubSystem.NodeLibSystem.Define.Drivers; +using AXNode.SubSystem.NodeLibSystem.Define.Events; +using AXNode.SubSystem.NodeLibSystem.Define.Flows; +using AXNode.SubSystem.NodeLibSystem.Define.Functions; +using AXNode.SubSystem.OptionSystem; +using File = XLib.Base.VirtualDisk.File; + +namespace AXNode.SubSystem.NodeLibSystem +{ + public class NodeLibManager : IManager + { + #region 单例 + + private NodeLibManager() + { + } + + public static NodeLibManager Instance { get; } = new NodeLibManager(); + + #endregion + + #region 属性 + + /// 根文件夹 + public Folder Root => _nodeLibRoot.Root; + + /// 节点库字典 + public Dictionary NodeLibDict { get; set; } = new Dictionary(); + + #endregion + + #region IManager 方法 + + public void Init() + { + BuildInnerNodeLib(); + LoadOutsideNodeLib(); + } + + public void Reset() + { + } + + public void Clear() + { + } + + #endregion + + #region 公开方法 + + /// + /// 创建节点 + /// + public NodeBase? CreateNode(string typeString) + { + return typeString switch + { + nameof(Data_Int) => new Data_Int(), + nameof(Data_Double) => new Data_Double(), + nameof(Data_String) => new Data_String(), + + nameof(FrameDriver) => new FrameDriver(), + nameof(TimerDriver) => new TimerDriver(), + + nameof(Event_Keyboard) => new Event_Keyboard(), + + nameof(Flow_If) => new Flow_If(), + nameof(Flow_LoopByCount) => new Flow_LoopByCount(), + nameof(Flow_While) => new Flow_While(), + nameof(Flow_Switch) => new Flow_Switch(), + + nameof(Func_Compare) => new Func_Compare(), + nameof(Func_BinOP) => new Func_BinOP(), + nameof(Func_NumberToRatio) => new Func_NumberToRatio(), + nameof(Func_RatioToInt) => new Func_RatioToInt(), + nameof(Func_SendNetMessage) => new Func_SendNetMessage(), + nameof(Func_Delay) => new Func_Delay(), + nameof(Func_CreateThread) => new Func_CreateThread(), + nameof(Func_Sleep) => new Func_Sleep(), + nameof(Func_Log) => new Func_Log(), + + _ => null, + }; + } + + /// + /// 创建节点 + /// + public NodeBase? CreateNode(string libName, string typeString) => + NodeLibDict.ContainsKey(libName) ? NodeLibDict[libName].CreateNode(typeString) : null; + + #endregion + + #region 私有方法 + + /// + /// 构建内置节点库 + /// + private void BuildInnerNodeLib() + { + // 创建根文件夹 + Folder 内置节点 = _nodeLibRoot.CreateFolder("内置节点".PackToList()); + Root.Childs.Add(内置节点); + + // 创建一级文件夹 + Folder 驱动节点 = _nodeLibRoot.CreateFolder(内置节点, "驱动节点".PackToList()); + Folder 事件节点 = _nodeLibRoot.CreateFolder(内置节点, "事件节点".PackToList()); + Folder 函数节点 = _nodeLibRoot.CreateFolder(内置节点, "函数节点".PackToList()); + Folder 流控制节点 = _nodeLibRoot.CreateFolder(内置节点, "流控制节点".PackToList()); + Folder 数据节点 = _nodeLibRoot.CreateFolder(内置节点, "数据节点".PackToList()); + 内置节点.Childs.Add(驱动节点); + 内置节点.Childs.Add(事件节点); + 内置节点.Childs.Add(函数节点); + 内置节点.Childs.Add(流控制节点); + 内置节点.Childs.Add(数据节点); + // 创建二级文件夹 + Folder 运算函数 = _nodeLibRoot.CreateFolder(函数节点.Path.AppendElement("运算函数")); + Folder 转换器 = _nodeLibRoot.CreateFolder(函数节点.Path.AppendElement("转换器")); + Folder 执行控制 = _nodeLibRoot.CreateFolder(函数节点.Path.AppendElement("执行控制")); + // 创建文件 + + 数据节点.Childs.Add(_nodeLibRoot.CreateFile(数据节点, "整数", "nt", new NodeType())); + 数据节点.Childs.Add(_nodeLibRoot.CreateFile(数据节点, "小数", "nt", new NodeType())); + 数据节点.Childs.Add(_nodeLibRoot.CreateFile(数据节点, "字符串", "nt", new NodeType())); + + 驱动节点.Childs.Add(_nodeLibRoot.CreateFile(驱动节点, "帧驱动器", "nt", new NodeType())); + 驱动节点.Childs.Add(_nodeLibRoot.CreateFile(驱动节点, "定时驱动器", "nt", new NodeType())); + + 事件节点.Childs.Add(_nodeLibRoot.CreateFile(事件节点, "按键", "nt", new NodeType())); + + 运算函数.Childs.Add(_nodeLibRoot.CreateFile(运算函数, "关系运算", "nt", new NodeType())); + 运算函数.Childs.Add(_nodeLibRoot.CreateFile(运算函数, "四则运算", "nt", new NodeType())); + 转换器.Childs.Add(_nodeLibRoot.CreateFile(转换器, "比例转整数", "nt", new NodeType())); + 转换器.Childs.Add(_nodeLibRoot.CreateFile(转换器, "数值转比例", "nt", new NodeType())); + 函数节点.Childs.Add(_nodeLibRoot.CreateFile(函数节点, "发送网络消息", "nt", new NodeType())); + 函数节点.Childs.Add(_nodeLibRoot.CreateFile(函数节点, "日志", "nt", new NodeType())); + + 执行控制.Childs.Add(_nodeLibRoot.CreateFile(执行控制, "多线程执行", "nt", new NodeType())); + 执行控制.Childs.Add(_nodeLibRoot.CreateFile(执行控制, "延迟执行", "nt", new NodeType())); + 执行控制.Childs.Add(_nodeLibRoot.CreateFile(执行控制, "暂停执行", "nt", new NodeType())); + + 流控制节点.Childs.Add(_nodeLibRoot.CreateFile(流控制节点, "判断", "nt", new NodeType())); + 流控制节点.Childs.Add(_nodeLibRoot.CreateFile(流控制节点, "计数循环", "nt", new NodeType())); + 流控制节点.Childs.Add(_nodeLibRoot.CreateFile(流控制节点, "条件循环", "nt", new NodeType())); + 流控制节点.Childs.Add(_nodeLibRoot.CreateFile(流控制节点, "选择执行", "nt", new NodeType())); + } + + /// + /// 加载外部节点库 + /// + private void LoadOutsideNodeLib() + { + // 遍历节点库文件 + foreach (var dllPath in GetAllNodeLibDll()) + { + // 加载动态库 + Assembly dll = Assembly.LoadFrom(dllPath); + // 遍历全部类 + foreach (var type in dll.GetTypes()) + { + if (typeof(INodeLib).IsAssignableFrom(type)) + { + // 获取单例 + PropertyInfo? propertyInfo = type.GetProperty("Instance"); + if (propertyInfo == null) continue; + if (propertyInfo.GetValue(null) is not INodeLib instance) continue; + // 初始化单例 + instance.Init(); + // 保存引用 + NodeLibDict.Add(instance.Name, instance); + } + } + } + + // 遍历节点库 + foreach (var libPair in NodeLibDict) + { + // 创建根文件夹 + Folder root = _nodeLibRoot.CreateFolder(libPair.Value.Title.PackToList()); + // 加载文件夹 + LoadFolder(root, libPair.Value.LibHarddisk.Root); + } + } + + /// + /// 获取全部节点库文件 + /// + private List GetAllNodeLibDll() + { + if (!Directory.Exists(OptionManager.Instance.NodeLibPath)) return new List(); + + DirectoryInfo directoryInfo = new DirectoryInfo(OptionManager.Instance.NodeLibPath); + List result = new List(); + foreach (var fileInfo in directoryInfo.GetFiles()) + { + if (fileInfo.Extension == ".dll") result.Add(fileInfo.FullName); + } + + return result; + } + + /// + /// 加载文件夹至目标文件夹 + /// + private void LoadFolder(Folder target, Folder oldFolder) + { + // 加载文件夹 + foreach (var oldChild in oldFolder.Childs.Where(x => x.IsFolder).Select(x => x as Folder)) + { + // 创建子文件夹 + Folder childFolder = new Folder(oldChild.Name, target); + // 添加子文件夹 + target.Childs.Add(childFolder); + // 递归加载 + LoadFolder(childFolder, oldChild); + } + + // 加载文件 + foreach (var oldFile in oldFolder.Childs.Where(x => !x.IsFolder).Select(x => x as File)) + { + // 创建文件 + _nodeLibRoot.CreateFile(target, oldFile.Name, oldFile.Extension, oldFile.Instance); + } + } + + #endregion + + #region 字段 + + /// 节点库磁盘 + private readonly Harddisk _nodeLibRoot = new Harddisk(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml b/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml new file mode 100644 index 0000000..96cb185 --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml @@ -0,0 +1,66 @@ + + + + + + + + + + White + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml.cs b/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml.cs new file mode 100644 index 0000000..5d0ebad --- /dev/null +++ b/AXNode/SubSystem/NodeLibSystem/NodeLibPanel.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Input; +using AXNode.SubSystem.NodeLibSystem; +using XLib.Base; +using XLib.Base.VirtualDisk; +using XLib.AvaloniaControl; +using AXNode.SubSystem.CacheSystem; +using AXNode.SubSystem.ResourceSystem; + +namespace AXNode.SubSystem.NodeLibSystem +{ + public partial class NodeLibPanel : UserControl + { + public NodeLibPanel() + { + InitializeComponent(); + } + + public void Init() + { + NodePresetTree.ItemsSource = NodeLibManager.Instance.Root.Childs; + } + + private async void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + DataObject obj = new DataObject(); + obj.Set("object", (sender as Control).DataContext); + await DragDrop.DoDragDrop(e, obj, DragDropEffects.Copy); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/OptionSystem/OptionManager.cs b/AXNode/SubSystem/OptionSystem/OptionManager.cs new file mode 100644 index 0000000..82fb539 --- /dev/null +++ b/AXNode/SubSystem/OptionSystem/OptionManager.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using XLib.Base; + +namespace AXNode.SubSystem.OptionSystem +{ + /// + /// 选项管理器 + /// + public class OptionManager : IManager + { + #region 单例 + + private OptionManager() + { + } + + public static OptionManager Instance { get; } = new OptionManager(); + + #endregion + + #region 属性 + + /// 缓存路径 + public string CachePath => _root + "Cache\\"; + + /// 项目路径 + public string ProjectPath => _root + "Project\\"; + + /// 节点库路径 + public string NodeLibPath => _root + "NodeLib\\"; + + #endregion + + #region “IManager”方法 + + public void Init() + { + if (!Directory.Exists(CachePath)) Directory.CreateDirectory(CachePath); + if (!Directory.Exists(ProjectPath)) Directory.CreateDirectory(ProjectPath); + if (!Directory.Exists(NodeLibPath)) Directory.CreateDirectory(NodeLibPath); + } + + public void Reset() + { + } + + public void Clear() + { + } + + #endregion + + #region 字段 + + /// 根路径 + private readonly string _root = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\XNode\\"; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ProjectSystem/FileTool.cs b/AXNode/SubSystem/ProjectSystem/FileTool.cs new file mode 100644 index 0000000..a5eb70d --- /dev/null +++ b/AXNode/SubSystem/ProjectSystem/FileTool.cs @@ -0,0 +1,79 @@ +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Microsoft.Win32; +using XLib.Base; +using AXNode.SubSystem.OptionSystem; +using AXNode.SubSystem.WindowSystem; + +namespace AXNode.SubSystem.ProjectSystem +{ + /// + /// 文件工具 + /// + public class FileTool + { + private FileTool() + { + _projectFilter.TypeList.Add(new TypeInfo("节点项目", "xnode")); + _projectFilter.TypeList.Add(new TypeInfo("节点项目", "json")); + } + + public static FileTool Instance { get; } = new FileTool(); + + /// + /// 打开读取项目对话框 + /// + public string OpenReadProjectDialog() + { + var file = ""; + Task.Run(async () => + { + var topLevel = TopLevel.GetTopLevel(WM.Main); + var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = "Open XNode File", + AllowMultiple = false, + FileTypeFilter = new[] { new FilePickerFileType("XNode文件") { Patterns = new[] { "*.xnode" } } } + }); + + if (files.Count >= 1) + { + file = files[0].TryGetLocalPath(); + } + }).Wait(); + + return file; + } + + /// + /// 打开保存项目对话框 + /// + public string OpenSaveProjectDialog(string fileName) + { + var file = ""; + + Task.Run(async () => + { + var topLevel = TopLevel.GetTopLevel(WM.Main); + + // 启动异步操作以打开对话框。 + var _file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions + { + Title = "Save XNode File", + FileTypeChoices = new[] { new FilePickerFileType("XNode文件") { Patterns = new[] { "*.xnode" } } } + }); + + if (_file is not null) + { + file = _file.Path.AbsolutePath; + } + }); + return file; + } + + private readonly FileFilter _projectFilter = new FileFilter(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ProjectSystem/NodeProject.cs b/AXNode/SubSystem/ProjectSystem/NodeProject.cs new file mode 100644 index 0000000..8331628 --- /dev/null +++ b/AXNode/SubSystem/ProjectSystem/NodeProject.cs @@ -0,0 +1,41 @@ +namespace AXNode.SubSystem.ProjectSystem +{ + /// + /// 节点项目 + /// + public class NodeProject + { + /// 项目路径。仅文件夹 + public string ProjectPath { get; set; } = ""; + + /// 项目名称。不包含扩展名 + public string ProjectName { get; set; } = ""; + + public string ProjectFileName + { + get + { + if (_projectFileName == "") return ProjectName; + return _projectFileName; + } + set => _projectFileName = value; + } + + /// 文件扩展名 + public static string FileExtension { get; private set; } = ".xnode"; + + /// 项目文件路径 + public string ProjectFilePath => $"{ProjectPath}\\{ProjectFileName}{FileExtension}"; + + public NodeProject Clone() + { + return new NodeProject + { + ProjectPath = ProjectPath, + ProjectName = ProjectName, + }; + } + + private string _projectFileName = ""; + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ProjectSystem/ProjectManager.cs b/AXNode/SubSystem/ProjectSystem/ProjectManager.cs new file mode 100644 index 0000000..bdc2d9c --- /dev/null +++ b/AXNode/SubSystem/ProjectSystem/ProjectManager.cs @@ -0,0 +1,265 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; +using System.Text; +using XLib.Base.ArchiveFrame; +using XLib.Base.Ex; +using AXNode.SubSystem.ArchiveSystem; +using AXNode.SubSystem.EventSystem; +using AXNode.SubSystem.WindowSystem; + +namespace AXNode.SubSystem.ProjectSystem +{ + /// + /// 项目管理器 + /// + public class ProjectManager + { + #region 单例 + + private ProjectManager() + { + } + + public static ProjectManager Instance { get; } = new ProjectManager(); + + #endregion + + #region 属性 + + /// 当前项目 + public NodeProject? CurrentProject { get; set; } = null; + + /// 已保存 + public bool Saved + { + get => _saved; + set + { + _saved = value; + EM.Instance.Invoke(EventType.Project_Changed); + } + } + + #endregion + + #region 公开方法 + + /// + /// 新建项目 + /// + public async void NewProject() + { + // 当前项目未保存 + if (!_saved) + { + bool? result = await WM.ShowAsk("当前项目未保存,是否保存?"); + // 取消操作 + if (result == null) return; + // 保存项目 + else if (result == true) SaveProject(); + } + + // 关闭当前项目 + CloseProject(); + // 新建当前项目 + CurrentProject = new NodeProject { ProjectName = GetNewProjectName() }; + } + + /// + /// 打开项目 + /// + public async void OpenProject() + { + // 当前项目未保存 + if (!_saved) + { + bool? result = await WM.ShowAsk("当前项目未保存,是否保存?"); + // 取消操作 + if (result == null) return; + // 保存项目 + else if (result == true) SaveProject(); + } + + // 选择项目文件 + string filePath = FileTool.Instance.OpenReadProjectDialog(); + if (filePath == "") return; + // 防止重复打开 + if (CurrentProject != null && CurrentProject.ProjectFilePath == filePath) + { + WM.ShowTip($"项目“{CurrentProject.ProjectName}”已打开"); + return; + } + + // 读取存档文件 + ArchiveFile? file = ArchiveManager.Instance.ReadArchiveFile(filePath); + if (file == null) + { + WM.ShowError($"项目文件“{filePath}”读取失败:无效的存档文件"); + return; + } + + // 关闭当前项目 + CloseProject(); + // 加载项目 + bool success = ArchiveManager.Instance.LoadArchive(file, filePath, (JObject)file.Data); + if (success) + { + SwitchProject(filePath); + Saved = true; + EM.Instance.Invoke(EventType.Project_Loaded); + } + } + + /// + /// 保存项目 + /// + public bool SaveProject() + { + // 无当前项目 + if (CurrentProject == null) throw new Exception("项目为空"); + // 当前项目无路径,则选择一个路径已创建当前项目 + if (CurrentProject.ProjectPath == "") + { + // 选择项目保存路径 + string projectPath = FileTool.Instance.OpenSaveProjectDialog(CurrentProject.ProjectName); + // 未选择,取消保存 + if (projectPath == "") return false; + // 设置为当前项目 + SwitchProject(projectPath); + // 创建空文本文件 + File.WriteAllText(projectPath, "", Encoding.UTF8); + } + + // 执行保存 + ExecuteSave(); + return true; + } + + /// + /// 另存为项目 + /// + public void SaveAsProject() + { + // 无当前项目 + if (CurrentProject == null) throw new Exception("项目为空"); + // 选择另存路径 + string projectPath = FileTool.Instance.OpenSaveProjectDialog(CurrentProject.ProjectName); + // 未选择,取消另存 + if (projectPath == "") return; + // 设置为当前项目 + SwitchProject(projectPath); + // 创建空文本文件 + File.WriteAllText(projectPath, "", Encoding.UTF8); + // 执行保存 + ExecuteSave(); + } + + /// + /// 关闭项目 + /// + public void CloseProject() + { + // 重置核心编辑器 + WM.Main.Editer.ResetEditer(); + // 置空当前项目 + CurrentProject = null; + _saved = true; + } + + /// + /// 切换项目 + /// + public void SwitchProject(string fullPath) + { + // 解析文件路径与名称 + (string, string) pathInfo = fullPath.ParsePath("\\"); + // 设置当前项目 + CurrentProject = new NodeProject + { + ProjectPath = pathInfo.Item1, + ProjectName = pathInfo.Item2.RemoveExtension() + }; + } + + #endregion + + #region 私有方法 + + /// + /// 获取新建项目名称 + /// + private string GetNewProjectName() + { + _newProjectCounter++; + return $"新建节点项目_{_newProjectCounter:00}"; + } + + /// + /// 执行保存 + /// + private void ExecuteSave() + { + if (!File.Exists(CurrentProject.ProjectFilePath)) return; + if (ProjectReadonly()) return; + + try + { + // 备份项目:防止因保存异常导致项目文件损坏 + string backupPath = BackupProject(); + + // 生成存档数据 + ArchiveFile file = ArchiveManager.Instance.GenerateArchive(); + // 序列化存档数据 + string jsonData = JsonConvert.SerializeObject(file, Formatting.Indented); + // 创建文件并写入数据,文件已存在则覆盖 + File.WriteAllText(CurrentProject.ProjectFilePath, jsonData, Encoding.UTF8); + // 设置为已保存 + Saved = true; + + // 删除备份 + if (backupPath != "" && File.Exists(backupPath)) File.Delete(backupPath); + } + catch (Exception) + { + } + } + + /// + /// 项目为只读 + /// + private bool ProjectReadonly() + { + // 获取文件的属性 + FileAttributes attributes = File.GetAttributes(CurrentProject.ProjectFilePath); + // 返回文件是否为只读 + return (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; + } + + /// + /// 备份项目 + /// + private string BackupProject() + { + if (CurrentProject == null) return ""; + + NodeProject backup = CurrentProject.Clone(); + backup.ProjectFileName += "_Backup"; + File.Copy(CurrentProject.ProjectFilePath, backup.ProjectFilePath, true); + + return backup.ProjectFilePath; + } + + #endregion + + #region 字段 + + /// 新建项目计数器 + private int _newProjectCounter = 0; + + private bool _saved = true; + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ResourceSystem/CursorManager.cs b/AXNode/SubSystem/ResourceSystem/CursorManager.cs new file mode 100644 index 0000000..8011a9a --- /dev/null +++ b/AXNode/SubSystem/ResourceSystem/CursorManager.cs @@ -0,0 +1,107 @@ +using System; +using System.Windows; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +namespace AXNode.SubSystem.ResourceSystem +{ + /// + /// 光标管理器 + /// + public class CursorManager + { + #region 单例 + + private CursorManager() + { + } + + public static CursorManager Instance { get; } = new CursorManager(); + + #endregion + + #region 光标 + + /// 选择 + public Cursor? Select { get; set; } + + /// 选择并移动 + public Cursor? SelectAndMove { get; set; } + + /// 移动 + public Cursor? Move { get; set; } + + /// 水平移动 + public Cursor? MoveX { get; set; } + + /// 垂直移动 + public Cursor? MoveY { get; set; } + + /// 十字 + public Cursor? Cross { get; set; } + + /// 插入 + public Cursor? Insert { get; set; } + + /// 绘制 + public Cursor? Draw { get; set; } + + /// 禁止 + public Cursor? Disable { get; set; } + + /// 移至顶端 + public Cursor? MoveTop { get; set; } + + /// 移至底端 + public Cursor? MoveBottom { get; set; } + + /// 缩放:左上至右下 + public Cursor? ResizeUpDown { get; set; } + + /// 缩放:左下至右上 + public Cursor? ResizeDownUp { get; set; } + + /// 开关 + public Cursor? OnOff { get; set; } + + #endregion + + #region 管理器接口 + + public void Init() + { + Select = LoadCursor("Assets/Cursor/Select.cur"); + SelectAndMove = LoadCursor("Assets/Cursor/MoveSelected.cur"); + Move = LoadCursor("Assets/Cursor/Move.cur"); + MoveX = LoadCursor("Assets/Cursor/MoveX.cur"); + MoveY = LoadCursor("Assets/Cursor/MoveY.cur"); + // Cross = LoadCursor("Assets/Cursor/Cross.cur"); + Cross = Cursor.Parse("cross"); + Insert = LoadCursor("Assets/Cursor/Insert.cur"); + Draw = LoadCursor("Assets/Cursor/Draw.cur"); + Disable = LoadCursor("Assets/Cursor/Disable.cur"); + MoveTop = LoadCursor("Assets/Cursor/MoveTop.cur"); + MoveBottom = LoadCursor("Assets/Cursor/MoveBottom.cur"); + ResizeUpDown = LoadCursor("Assets/Cursor/ResizeUpDown.cur"); + ResizeDownUp = LoadCursor("Assets/Cursor/ResizeDownUp.cur"); + OnOff = LoadCursor("Assets/Cursor/OnOff.cur"); + } + + #endregion + + #region 私有方法 + + private Cursor LoadCursor(string cursorPath) + { + var uri = new Uri($"avares://AXNode/{cursorPath}"); + var bmp = new Bitmap(AssetLoader.Open(uri)); + return new Cursor(bmp, new PixelPoint()); + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ResourceSystem/ImageResManager.cs b/AXNode/SubSystem/ResourceSystem/ImageResManager.cs new file mode 100644 index 0000000..d795a25 --- /dev/null +++ b/AXNode/SubSystem/ResourceSystem/ImageResManager.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Avalonia; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +namespace AXNode.SubSystem.ResourceSystem +{ + public class ImageResManager + { + #region 单例 + + private ImageResManager() + { + } + + public static ImageResManager Instance { get; } = new ImageResManager(); + + #endregion + + /// + /// 获取图片 + /// + public Bitmap GetImage(string path) + { + if (!path.StartsWith("avares:") && !File.Exists(path)) + throw new Exception("图片不存在"); + + // 已加载过此图片,直接返回 + if (_imageResDict.ContainsKey(path)) + return _imageResDict[path]; + + // 创建图片实例 + var uri = new Uri($"{path}"); + var image = new Bitmap(AssetLoader.Open(uri)); + // 保存图片引用 + _imageResDict.Add(path, image); + + // 返回图片实例 + return image; + } + + /// + /// 获取资源图片 + /// + public Bitmap GetAssetsImage(string path) + { + if (path == "") throw new Exception("路径不能为空"); + return GetImage($"avares://AXNode/Assets/{path}"); + } + + /// + /// 获取节点图标 + /// + public Bitmap GetNodeIcon(string iconName) + { + if (iconName == "") throw new Exception("图标名不能为空"); + return GetImage($"avares://AXNode/Assets/Icon16/Node/{iconName}.png"); + } + + /// + /// 获取小图标 + /// + public Bitmap? GetIcon15(string path) + { + if (path == "") throw new Exception("路径不能为空"); + return GetImage($"avares://AXNode/Assets/Icon15/{path}"); + } + + /// + /// 获取图片字体 + /// + public Bitmap GetImageFont(string path) + { + if (path == "") throw new Exception("路径不能为空"); + return GetAssetsImage($"Font/Number/{path}"); + } + + /// + /// 获取子系统图片 + /// + public Bitmap? GetSubSystemImage(string subSystem, string imageName) + { + if (subSystem == "" || imageName == "") return null; + return GetImage($"avares://AXNode/SubSystem/{subSystem}/Image/{imageName}.png"); + } + + /// + /// 获取子系统图片 + /// + public Bitmap? GetSubSystemImage(string subSystem, string subPath, string imageName) + { + if (subSystem == "" || subPath == "" || imageName == "") return null; + return GetImage($"avares://AXNode/SubSystem/{subSystem}/{subPath}/{imageName}.png"); + } + + private readonly Dictionary _imageResDict = new(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ResourceSystem/PinIconManager.cs b/AXNode/SubSystem/ResourceSystem/PinIconManager.cs new file mode 100644 index 0000000..ea77108 --- /dev/null +++ b/AXNode/SubSystem/ResourceSystem/PinIconManager.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using AXNode.SubSystem.NodeEditSystem.Define; + +namespace AXNode.SubSystem.ResourceSystem +{ + /// + /// 引脚图标管理器 + /// + public class PinIconManager + { + #region 单例 + + private PinIconManager() + { + } + + public static PinIconManager Instance { get; } = new PinIconManager(); + + #endregion + + #region 属性 + + public string Name { get; set; } = "引脚图标管理器"; + + private readonly SolidColorBrush ExecuteBrush = new SolidColorBrush(Color.FromRgb(196, 126, 255)); + + public Shape ExecutePinIcon + { + get + { + App.Current.TryGetResource("PinIcons.ExecutePin", null, out var pinIcon); + if (pinIcon is Polygon s) + { + return new Polygon { Points = s.Points, StrokeThickness = 1 }; + } + + throw new ArgumentNullException(); + } + } + + public Shape DataPinIcon + { + get + { + App.Current.TryGetResource("PinIcons.DataPin", null, out var pinIcon); + if (pinIcon is Polygon s) + { + return new Polygon { Points = s.Points, StrokeThickness = 1 }; + } + + throw new ArgumentNullException(); + } + } + + public Shape ExecutePin_Null + { + get + { + var s = ExecutePinIcon; + s.Stroke = ExecuteBrush; + return s; + } + } + + public Shape ExecutePin + { + get + { + var s = ExecutePin_Null; + s.Fill = ExecuteBrush; + return s; + } + } + + #endregion + + #region 生命周期 + + public void Init() + { + // GenerateExecutePinIcon(); + GenerateDataPinIcon(); + } + + #endregion + + #region 公开方法 + + /// + /// 获取数据引脚图标 + /// + public Shape GetDataPinIcon(string dataType, bool solid) + { + if (solid) + { + var s = DataPinIcon; + s.Fill = new SolidColorBrush(_colors[dataType]); + return s; + } + else + { + var s = DataPinIcon; + s.Stroke = new SolidColorBrush(_colors[dataType]); + return s; + } + } + + #endregion + + Dictionary _colors = new(); + + /// + /// 生成数据引脚图标 + /// + private void GenerateDataPinIcon() + { + _colors.Add("bool", PinColorSet.Bool); + _colors.Add("int", PinColorSet.Int); + _colors.Add("double", PinColorSet.Double); + _colors.Add("string", PinColorSet.String); + _colors.Add("byte[]", PinColorSet.ByteArray); + } + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ResourceSystem/PinIconTool.cs b/AXNode/SubSystem/ResourceSystem/PinIconTool.cs new file mode 100644 index 0000000..6740ce5 --- /dev/null +++ b/AXNode/SubSystem/ResourceSystem/PinIconTool.cs @@ -0,0 +1,185 @@ +using XLib.Drawing; + +namespace AXNode.SubSystem.ResourceSystem +{ + /// + /// 引脚类型 + /// + public enum PinType + { + /// 执行引脚 + Execute, + + /// 数据引脚 + Data + } + + /// + /// 引脚样式 + /// + public enum PinStyle + { + /// 空心 + Hollow, + + /// 实心 + Solid, + } + + /// + /// 引脚图标工具:用于生成引脚图标 + /// + public class PinIconTool + { + #region 公开方法 + + /// + /// 创建引脚图标。返回引脚图标的像素点颜色数据 + /// + public static byte[] CreatePinIcon(PinType type, byte r, byte g, byte b, PinStyle style = PinStyle.Hollow) + { + if (type == PinType.Execute) + { + if (style == PinStyle.Hollow) return DrawExecutePinIcon(r, g, b).GetPixelData(); + return DrawSolidExecutePinIcon(r, g, b).GetPixelData(); + } + + if (style == PinStyle.Hollow) return DrawDataPinIcon(r, g, b).GetPixelData(); + return DrawSolidDataPinIcon(r, g, b).GetPixelData(); + } + + #endregion + + #region 私有方法 + + /// + /// 绘制执行引脚图标 + /// + private static Bitmap DrawExecutePinIcon(byte r, byte g, byte b) + { + Pixel pixel = new Pixel(r, g, b); + Pixel alphaPixel = new Pixel(r, g, b, 51); + + Bitmap bitmap = new Bitmap(11, 11); + + // 上边、下边、左边 + bitmap.DrawHorizontalLine(0, 0, 6, pixel); + bitmap.DrawHorizontalLine(0, 10, 6, pixel); + bitmap.DrawVerticalLine(0, 1, 9, pixel); + // 右上斜线、右下斜线 + bitmap.DrawSlash(6, 1, 1, 1, 5, pixel); + bitmap.DrawSlash(9, 6, -1, 1, 4, pixel); + // 黑色区域 + bitmap.DrawRectangle(1, 1, 5, 9, Pixel.Black); + bitmap.DrawVerticalLine(6, 2, 7, Pixel.Black); + bitmap.DrawVerticalLine(7, 3, 5, Pixel.Black); + bitmap.DrawVerticalLine(8, 4, 3, Pixel.Black); + bitmap.DrawPixel(9, 5, Pixel.Black); + // 半透明斜线 + bitmap.DrawSlash(6, 0, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(5, 1, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(8, 6, -1, 1, 4, alphaPixel); + bitmap.DrawSlash(10, 6, -1, 1, 5, alphaPixel); + + return bitmap; + } + + /// + /// 绘制实心执行引脚图标 + /// + private static Bitmap DrawSolidExecutePinIcon(byte r, byte g, byte b) + { + Pixel pixel = new Pixel(r, g, b); + Pixel alphaPixel = new Pixel(r, g, b, 51); + + Bitmap bitmap = new Bitmap(11, 11); + + bitmap.DrawHorizontalLine(0, 0, 6, pixel); + bitmap.DrawHorizontalLine(0, 1, 7, pixel); + bitmap.DrawHorizontalLine(0, 2, 8, pixel); + bitmap.DrawHorizontalLine(0, 3, 9, pixel); + bitmap.DrawHorizontalLine(0, 4, 10, pixel); + bitmap.DrawHorizontalLine(0, 5, 11, pixel); + bitmap.DrawHorizontalLine(0, 6, 10, pixel); + bitmap.DrawHorizontalLine(0, 7, 9, pixel); + bitmap.DrawHorizontalLine(0, 8, 8, pixel); + bitmap.DrawHorizontalLine(0, 9, 7, pixel); + bitmap.DrawHorizontalLine(0, 10, 6, pixel); + + bitmap.DrawSlash(6, 0, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(10, 6, -1, 1, 5, alphaPixel); + + return bitmap; + } + + /// + /// 绘制数据引脚图标 + /// + private static Bitmap DrawDataPinIcon(byte r, byte g, byte b) + { + Pixel pixel = new Pixel(r, g, b); + Pixel alphaPixel = new Pixel(r, g, b, 51); + + Bitmap bitmap = new Bitmap(11, 11); + + bitmap.DrawSlash(5, 0, 1, 1, 6, pixel); + bitmap.DrawSlash(9, 6, -1, 1, 5, pixel); + bitmap.DrawSlash(4, 9, -1, -1, 5, pixel); + bitmap.DrawSlash(1, 4, 1, -1, 4, pixel); + + bitmap.DrawPixel(5, 1, Pixel.Black); + bitmap.DrawHorizontalLine(4, 2, 3, Pixel.Black); + bitmap.DrawHorizontalLine(3, 3, 5, Pixel.Black); + bitmap.DrawHorizontalLine(2, 4, 7, Pixel.Black); + bitmap.DrawHorizontalLine(1, 5, 9, Pixel.Black); + bitmap.DrawHorizontalLine(2, 6, 7, Pixel.Black); + bitmap.DrawHorizontalLine(3, 7, 5, Pixel.Black); + bitmap.DrawHorizontalLine(4, 8, 3, Pixel.Black); + bitmap.DrawPixel(5, 9, Pixel.Black); + + bitmap.DrawSlash(6, 0, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(10, 6, -1, 1, 5, alphaPixel); + bitmap.DrawSlash(4, 10, -1, -1, 5, alphaPixel); + bitmap.DrawSlash(0, 4, 1, -1, 5, alphaPixel); + + bitmap.DrawSlash(5, 1, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(8, 6, -1, 1, 4, alphaPixel); + bitmap.DrawSlash(4, 8, -1, -1, 4, alphaPixel); + bitmap.DrawSlash(2, 4, 1, -1, 3, alphaPixel); + + return bitmap; + } + + /// + /// 绘制实心数据引脚图标 + /// + private static Bitmap DrawSolidDataPinIcon(byte r, byte g, byte b) + { + Pixel pixel = new Pixel(r, g, b); + Pixel alphaPixel = new Pixel(r, g, b, 51); + + Bitmap bitmap = new Bitmap(11, 11); + + bitmap.DrawHorizontalLine(5, 0, 1, pixel); + bitmap.DrawHorizontalLine(4, 1, 3, pixel); + bitmap.DrawHorizontalLine(3, 2, 5, pixel); + bitmap.DrawHorizontalLine(2, 3, 7, pixel); + bitmap.DrawHorizontalLine(1, 4, 9, pixel); + bitmap.DrawHorizontalLine(0, 5, 11, pixel); + bitmap.DrawHorizontalLine(1, 6, 9, pixel); + bitmap.DrawHorizontalLine(2, 7, 7, pixel); + bitmap.DrawHorizontalLine(3, 8, 5, pixel); + bitmap.DrawHorizontalLine(4, 9, 3, pixel); + bitmap.DrawHorizontalLine(5, 10, 1, pixel); + + bitmap.DrawSlash(6, 0, 1, 1, 5, alphaPixel); + bitmap.DrawSlash(10, 6, -1, 1, 5, alphaPixel); + bitmap.DrawSlash(4, 10, -1, -1, 5, alphaPixel); + bitmap.DrawSlash(0, 4, 1, -1, 5, alphaPixel); + + return bitmap; + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/ResourceSystem/ResourceManager.cs b/AXNode/SubSystem/ResourceSystem/ResourceManager.cs new file mode 100644 index 0000000..785593a --- /dev/null +++ b/AXNode/SubSystem/ResourceSystem/ResourceManager.cs @@ -0,0 +1,44 @@ +using XLib.Base; +using AXNode.SubSystem.ResourceSystem; + +namespace AXNode.SubSystem.ResourceSystem +{ + public class ResourceManager : IManager + { + #region 单例 + + private ResourceManager() + { + } + + public static ResourceManager Instance { get; } = new ResourceManager(); + + #endregion + + #region 属性 + + public string Name { get; set; } = "资源管理器"; + + #endregion + + #region 接口实现 + + public void Init() + { + // 光标管理器 + CursorManager.Instance.Init(); + // 引脚图标管理器 + PinIconManager.Instance.Init(); + } + + public void Reset() + { + } + + public void Clear() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/TimerSystem/AppTimer.cs b/AXNode/SubSystem/TimerSystem/AppTimer.cs new file mode 100644 index 0000000..8ce2645 --- /dev/null +++ b/AXNode/SubSystem/TimerSystem/AppTimer.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Avalonia.Threading; +using XLib.Base.AppFrame; + +namespace AXNode.SubSystem.TimerSystem +{ + /// + /// 应用程序定时器 + /// + public class AppTimer : ServiceBase + { + #region 单例 + + private AppTimer() + { + _timer.Interval = TimeSpan.FromMilliseconds(1000.0 / 30.0); + _timer.Tick += Timer_Tick; + } + + public static AppTimer Instance { get; } = new AppTimer(); + + #endregion + + #region 生命周期 + + public override void Start() + { + if (_handlerList.Count > 0) _timer.Start(); + } + + public override void Stop() + { + _timer.Stop(); + } + + #endregion + + #region 公开方法 + + /// + /// 添加定时器处理器 + /// + public void AddTimerHandler(ITimerHandler handler) + { + List newList = new List(_handlerList); + if (!newList.Contains(handler)) newList.Add(handler); + _handlerList = newList; + _timer.Start(); + } + + /// + /// 移除定时器处理器 + /// + public void RemoveTimerHandler(ITimerHandler handler) + { + List newList = new List(_handlerList); + newList.Remove(handler); + _handlerList = newList; + if (_handlerList.Count == 0) _timer.Stop(); + } + + #endregion + + #region 私有方法 + + /// + /// 定时器.走动 + /// + private void Timer_Tick(object? sender, EventArgs e) + { + foreach (var handler in _handlerList) handler.Tick(); + } + + #endregion + + #region 字段 + + /// 定时器 + private readonly DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.Background); + + /// 定时器处理器列表 + private List _handlerList = new List(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/TimerSystem/ITimerHandler.cs b/AXNode/SubSystem/TimerSystem/ITimerHandler.cs new file mode 100644 index 0000000..402c706 --- /dev/null +++ b/AXNode/SubSystem/TimerSystem/ITimerHandler.cs @@ -0,0 +1,19 @@ +using System; + +namespace AXNode.SubSystem.TimerSystem +{ + public interface ITimerHandler + { + void Tick(); + } + + /// + /// 通用定时器处理器 + /// + public class TimerHandler : ITimerHandler + { + public Action? OnTick { get; set; } = null; + + public void Tick() => OnTick?.Invoke(); + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/TimerSystem/TimeEngine.cs b/AXNode/SubSystem/TimerSystem/TimeEngine.cs new file mode 100644 index 0000000..ff0e5a9 --- /dev/null +++ b/AXNode/SubSystem/TimerSystem/TimeEngine.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using XLib.Base; +using XLib.Base.AppFrame; +using XLib.Base.Ex; + +namespace AXNode.SubSystem.TimerSystem +{ + /// + /// 时间引擎 + /// 其中定时器以每秒一千次模拟实时触发 + /// + public class TimeEngine : ServiceBase + { + #region 单例 + + private TimeEngine() => _timer.Tick += Timer_Tick; + public static TimeEngine Instance { get; } = new TimeEngine(); + + #endregion + + #region 属性 + + /// 当前时间。单位:毫秒 + public static double Time => Instance._stopwatch.DoubleMs(); + + /// 秒表实例 + public static Stopwatch Watch => Instance._stopwatch; + + #endregion + + #region 生命周期 + + public override void Start() + { + _timer.Start(); + _stopwatch.Start(); + } + + public override void Stop() + { + _timer.Stop(); + _stopwatch.Stop(); + } + + #endregion + + #region 公开方法 + + public void AddTimerHandler(ITimerHandler handler) + { + if (_handlerList.Contains(handler)) return; + _handlerList = new List(_handlerList) { handler }; + } + + public void RemoveHandler(ITimerHandler handler) + { + List newList = new List(_handlerList); + newList.Remove(handler); + _handlerList = newList; + } + + #endregion + + #region 私有方法 + + private void Timer_Tick() + { + try + { + foreach (var item in _handlerList) item.Tick(); + } + catch (Exception) + { + } + } + + #endregion + + #region 字段 + + /// 秒表:用作应用程序的精确时间参考 + private readonly Stopwatch _stopwatch = new Stopwatch(); + + /// 定时器:定时驱动引擎 + private readonly IHighPrecisionTimer _timer = Environment.OSVersion.Platform == PlatformID.Unix + ? new HighPrecisionTimerL() + : new HighPrecisionTimerW(); + + /// 定时处理器列表 + private List _handlerList = new List(); + + #endregion + } +} \ No newline at end of file diff --git a/AXNode/SubSystem/WindowSystem/AskDialog.axaml b/AXNode/SubSystem/WindowSystem/AskDialog.axaml new file mode 100644 index 0000000..2fb44fe --- /dev/null +++ b/AXNode/SubSystem/WindowSystem/AskDialog.axaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/XNode/SubSystem/NodeEditSystem/Control/HoverToolBar.xaml.cs b/XNode/SubSystem/NodeEditSystem/Control/HoverToolBar.xaml.cs index 343f851..16e0bb4 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/HoverToolBar.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/HoverToolBar.xaml.cs @@ -12,7 +12,8 @@ public partial class HoverToolBar : UserControl public void Init() { foreach (var item in Stack_ToolBar.Children) - if (item is Button button) button.Click += Tool_Click; + if (item is Button button) + button.Click += Tool_Click; } private void Tool_Click(object sender, RoutedEventArgs e) => ToolClick?.Invoke(((Button)sender).Name); diff --git a/XNode/SubSystem/NodeEditSystem/Control/NodeMap.xaml.cs b/XNode/SubSystem/NodeEditSystem/Control/NodeMap.xaml.cs index f54eec9..c4e875b 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/NodeMap.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/NodeMap.xaml.cs @@ -25,4 +25,4 @@ public NodeMap() InitializeComponent(); } } -} +} \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.xaml b/XNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.xaml index ec47895..2734cd7 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/NodePropertyPanel.xaml @@ -11,23 +11,26 @@ - + - + - + - + \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml b/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml index 5b696f0..142c38f 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml @@ -1,51 +1,54 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:XNode.SubSystem.NodeEditSystem.Control" + xmlns:wpfui="clr-namespace:XLib.WPF.UI;assembly=XLib.WPF" + mc:Ignorable="d" d:FontFamily="NSimSun" MinWidth="200" Focusable="True"> - + - + - + - - + + - + - + - + - + - + - + diff --git a/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml.cs b/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml.cs index 65e91fd..bdcaa82 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/NodeView.xaml.cs @@ -32,7 +32,8 @@ public Color NodeColor _nodeColor = value; Color_Start.Color = Color.FromArgb(255, _nodeColor.R, _nodeColor.G, _nodeColor.B); Color_End.Color = Color.FromArgb(0, _nodeColor.R, _nodeColor.G, _nodeColor.B); - NodeFillColor.Background = new SolidColorBrush(Color.FromArgb(48, _nodeColor.R, _nodeColor.G, _nodeColor.B)); + NodeFillColor.Background = + new SolidColorBrush(Color.FromArgb(48, _nodeColor.R, _nodeColor.G, _nodeColor.B)); } } @@ -47,7 +48,8 @@ public PinBase? HoveredPin get { foreach (var item in _pinGroupViewList) - if (item.HoveredPin != null) return item.HoveredPin; + if (item.HoveredPin != null) + return item.HoveredPin; return null; } } @@ -89,6 +91,7 @@ public void Init() _pinGroupViewList.Add(pinView); pinView.Init(); } + if (_pinGroupViewList.Count > 0) _pinGroupViewList[0].Margin = new Thickness(0); // 设置节点委托 @@ -126,6 +129,7 @@ public Point GetHoveredPinOffset() if (groupView.HoveredPin != null) return groupView.GetHoveredPinOffset(); } + return new Point(); } @@ -145,7 +149,8 @@ public void UpdateAllPinIcon() /// /// 获取可命中矩形区域 /// - public Rect GetHittableRect() => new Rect(Canvas.GetLeft(this) + 11, Canvas.GetTop(this), ActualWidth - 22, ActualHeight); + public Rect GetHittableRect() => + new Rect(Canvas.GetLeft(this) + 11, Canvas.GetTop(this), ActualWidth - 22, ActualHeight); #endregion @@ -209,13 +214,17 @@ private void Node_StateChanged() AppDelegate.Invoke(() => { if (NodeInstance.State == NodeState.Enable) - Image_Light.Source = ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Green"); + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Green"); else { if (NodeInstance.RunError) - Image_Light.Source = ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Red"); + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Red"); else - Image_Light.Source = ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", "Light_Black"); + Image_Light.Source = + ImageResManager.Instance.GetSubSystemImage("NodeEditSystem", "Control/Image", + "Light_Black"); } }); } @@ -243,6 +252,7 @@ private void Node_PinGroupListChanged() _pinGroupViewList.Add(pinView); pinView.Init(); } + if (_pinGroupViewList.Count > 0) _pinGroupViewList[0].Margin = new Thickness(0); @@ -268,7 +278,8 @@ private void TimerHandler_Tick() private PinGroupBase? FindPinGroup(string title) { foreach (var pinGroup in NodeInstance.PinGroupList) - if (pinGroup.GetTitle() == title) return pinGroup; + if (pinGroup.GetTitle() == title) + return pinGroup; return null; } @@ -308,6 +319,7 @@ private void TimerHandler_Tick() Instance = (ControlPinGroup)pinGroup, }; } + return null; } @@ -319,6 +331,7 @@ private void TimerHandler_Tick() /// 引脚组列表 private readonly List _pinGroupViewList = new List(); + /// 引脚信息列表 private readonly List _connectInfoList = new List(); @@ -326,8 +339,10 @@ private void TimerHandler_Tick() /// 进度条控件 private ProgressBar? _progressBar = null; + /// 进度获取器 private IProgressGetter? _progressGetter = null; + /// 定时器处理器 private readonly TimerHandler _timerHandler = new TimerHandler(); diff --git a/XNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs b/XNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs index f0f585c..7e907bd 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/PinGroupViewBase.cs @@ -16,7 +16,9 @@ public class PinGroupViewBase : UserControl /// /// 初始化 /// - public virtual void Init() { } + public virtual void Init() + { + } /// /// 获取引脚区域控件 @@ -40,6 +42,7 @@ public Point GetHoveredPinOffset() offset.X = 14 - offset.X; offset.Y = 8 - offset.Y; } + return offset; } @@ -51,6 +54,8 @@ public Point GetHoveredPinOffset() /// /// 更新引脚图标 /// - public virtual void UpdatePinIcon() { } + public virtual void UpdatePinIcon() + { + } } } \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.xaml b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.xaml index f01f630..01037c1 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/CustomListItem.xaml @@ -1,22 +1,23 @@  - - + + - + diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml index 3a85f0c..461b767 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml @@ -1,19 +1,20 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:XNode.SubSystem.NodeEditSystem.Control.PropertyBar" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800"> - + diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml.cs b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml.cs index 37dc6d8..df00e5e 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListEditer.xaml.cs @@ -24,6 +24,7 @@ public void Init() }; Stack_CaseList.Children.Add(listItem); } + if (Stack_CaseList.Children.Count > 0) ((CustomListItem)Stack_CaseList.Children[0]).Margin = new Thickness(); else Grid_Tool.Margin = new Thickness(); @@ -85,6 +86,7 @@ private string GenerateCaseValue() nameID++; continue; } + return name; } } diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml index bf400eb..31b50f3 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml @@ -1,17 +1,19 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:XNode.SubSystem.NodeEditSystem.Control.PropertyBar" + mc:Ignorable="d" + d:DesignWidth="320" Height="25"> - + - - + + \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml.cs b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml.cs index 69399de..5f8aae6 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/ListSelecter.xaml.cs @@ -21,6 +21,7 @@ public void LoadProperty(List list, string value) }; Box_ItemList.Items.Add(boxItem); } + Box_ItemList.SelectedIndex = list.IndexOf(value); } diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs index 13e9129..10f536d 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/PropertyBarBase.cs @@ -7,7 +7,9 @@ public class PropertyBarBase : UserControl { public string Title { get; set; } = ""; - public virtual void LoadProperty(string value) { } + public virtual void LoadProperty(string value) + { + } protected SolidColorBrush _default = new SolidColorBrush(Color.FromRgb(140, 140, 140)); protected SolidColorBrush _hovered = new SolidColorBrush(Color.FromRgb(255, 255, 255)); diff --git a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.xaml b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.xaml index 62735d3..4e799a1 100644 --- a/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.xaml +++ b/XNode/SubSystem/NodeEditSystem/Control/PropertyBar/TextInput.xaml @@ -1,17 +1,18 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:XNode.SubSystem.NodeEditSystem.Control.PropertyBar" + mc:Ignorable="d" + d:DesignWidth="320" Height="25"> - + - - + + \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs b/XNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs index 9681c2e..d07c73f 100644 --- a/XNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs +++ b/XNode/SubSystem/NodeEditSystem/Define/EnumDefine.cs @@ -7,10 +7,13 @@ public enum MouseHitedArea { /// 空白处 Space, + /// 节点 Node, + /// 引脚 Pin, + /// 连接线 ConnectLine, } @@ -22,6 +25,7 @@ public enum SelectType { /// 框选 Box, + /// 交叉 Cross } diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs b/XNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs index e224f87..7d2a08d 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Component/CardComponent.cs @@ -107,7 +107,8 @@ public void SetTop(NodeView view) public NodeView GetNodeCard(int nodeID) { foreach (var card in _cardList) - if (card.NodeInstance.ID == nodeID) return card; + if (card.NodeInstance.ID == nodeID) + return card; throw new Exception("获取节点卡片失败"); } @@ -127,6 +128,7 @@ public void DeleteNodeCard(NodeView card) /// 卡片列表 private readonly List _cardList = new List(); + /// 选中卡片集 private readonly HashSet _selectedCardSet = new HashSet(); diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs b/XNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs index 9a80cc9..ca825d0 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Component/DrawingComponent.cs @@ -62,7 +62,8 @@ protected override void Init() public Point ScreenToWorld(Point screenPoint) { // 转世界坐标 - Point worldPoint = new Point(screenPoint.X - _gridLayer.GridCenter.X, screenPoint.Y - _gridLayer.GridCenter.Y); + Point worldPoint = new Point(screenPoint.X - _gridLayer.GridCenter.X, + screenPoint.Y - _gridLayer.GridCenter.Y); // 对齐至网格 double x = Math.Round(worldPoint.X / _gridLayer.CellWidth) * _gridLayer.CellWidth; double y = Math.Round(worldPoint.Y / _gridLayer.CellHeight) * _gridLayer.CellHeight; @@ -98,7 +99,8 @@ public void UpdateSelectBox(Point start, Point end) /// /// 获取选择方式 /// - public SelectType GetSelectType() => _selectBoxLayer.End.X < _selectBoxLayer.Start.X ? SelectType.Cross : SelectType.Box; + public SelectType GetSelectType() => + _selectBoxLayer.End.X < _selectBoxLayer.Start.X ? SelectType.Cross : SelectType.Box; /// /// 更新选中框 @@ -116,6 +118,7 @@ public void UpdateSelectedBox() }; _selectedBoxLayer.BoxList.Add(box); } + _selectedBoxLayer.Update(); } @@ -366,16 +369,22 @@ private Color GetPinColor(DataPin pin) /// 网格图层 private GridLayer? _gridLayer; + /// 连接线背景图层 private ConnectLineBackLayer? _lineBackLayer; + /// 连接线图层 private ConnectLineLayer? _connectLineLayer; + /// 悬停框图层 private HoverBoxLayer? _hoverBoxLayer; + /// 选框图层 private SelectBoxLayer? _selectBoxLayer; + /// 选中框图层 private SelectedBoxLayer? _selectedBoxLayer; + /// 临时连接线图层 private TempConnectLineLayer? _tempLineLayer; diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs b/XNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs index 799bbb1..8ec19d9 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Component/EditerComponent.cs @@ -6,14 +6,10 @@ public class EditerComponent : Component { #region 生命周期 - - #endregion #region 公开方法 - - #endregion } } \ No newline at end of file diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs b/XNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs index 89dee77..5b72c19 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Component/InteractionComponent.cs @@ -282,6 +282,7 @@ public void EndDrawSelectBox() break; } } + // 更新选中框 GetComponent().UpdateSelectedBox(); // 更新工具栏 @@ -323,6 +324,7 @@ public void DragNode() Canvas.SetLeft(card, center.X + card.Point.X - 12); Canvas.SetTop(card, center.Y + card.Point.Y - 1); } + // 更新选中框、连接线 GetComponent().UpdateSelectedBox(); GetComponent().UpdateConnectLine(); @@ -372,6 +374,7 @@ public void DrawConnectLine() // 计算引脚连接点坐标 _mousePoint = new Point(_mousePoint.X + offset.X, _mousePoint.Y + offset.Y); } + // 根据起始引脚类型更新连接线的起点或终点 if (_startPin.Flow == PinFlow.Input) GetComponent().UpdateTempLineStart(_mousePoint); @@ -412,11 +415,13 @@ public void EndDrawConnectLine() // 添加连接线 GetComponent().AddConnectLine(_startPin, endPin); } + // 更新引脚图标 UpdateAllPinIcon(); ProjectManager.Instance.Saved = false; } + _startPin = null; } @@ -484,7 +489,8 @@ public void CancelDragViewport() public void DragViewport() { _mousePoint = Mouse.GetPosition(_host.OperateArea); - GetComponent().DragViewport(new Point(_mousePoint.X - _mouseDown.X, _mousePoint.Y - _mouseDown.Y)); + GetComponent() + .DragViewport(new Point(_mousePoint.X - _mouseDown.X, _mousePoint.Y - _mouseDown.Y)); UpdateHoverToolBar(); } @@ -572,6 +578,7 @@ private void SwitchHoverTarget(NodeView? target) GetComponent().UpdateHoverBox(); return; } + // 当前目标已选中,不绘制悬停框 if (GetComponent().SelectedCardList.Contains(_hoveredNodeView)) return; @@ -595,7 +602,9 @@ private void UpdateHoverToolBar() _hoverToolBar.Visibility = selectedCount == 0 ? Visibility.Collapsed : Visibility.Visible; // 无选中 - if (selectedCount == 0) { } + if (selectedCount == 0) + { + } // 选中一个 else if (selectedCount == 1) { @@ -614,6 +623,7 @@ private void UpdateHoverToolBar() { rect.Union(selectedList[index].GetHittableRect()); } + double left = Math.Round((rect.Right - rect.Left - _hoverToolBar.ActualWidth) / 2) + rect.Left; double top = rect.Top - 10 - _hoverToolBar.ActualHeight; Canvas.SetLeft(_hoverToolBar, left + 1); @@ -692,6 +702,7 @@ private void DeleteNode(List cardList) GetComponent().DeleteNode(card.NodeInstance); GetComponent().DeleteNodeCard(card); } + // 更新引脚图标 UpdateAllPinIcon(); // 清空选择 @@ -741,11 +752,13 @@ private void ResetComponent() /// 当前鼠标坐标 private Point _mousePoint = new Point(); + /// 鼠标按下坐标 private Point _mouseDown = new Point(); /// 起始引脚 private PinBase? _startPin; + /// 右键命中引脚 private PinBase? _rightHitedPin = null; diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs b/XNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs index 7e760d9..dd6e982 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Component/NodeComponent.cs @@ -106,7 +106,8 @@ public void GenerateConnectLine() #region 节点事件 - private void Node_PinBreaked(PinBase start, PinBase end) => GetComponent().RemoveConnectLine(start, end); + private void Node_PinBreaked(PinBase start, PinBase end) => + GetComponent().RemoveConnectLine(start, end); #endregion @@ -117,6 +118,7 @@ public void GenerateConnectLine() /// 节点字典 private readonly Dictionary _nodeDict = new Dictionary(); + /// 节点列表 private List _nodeList = new List(); diff --git a/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml b/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml index 0fe57b6..bfc3b5e 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml +++ b/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml @@ -1,8 +1,8 @@  - + - + - + - + - + - + - + diff --git a/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml.cs b/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml.cs index 620906f..4d6b632 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/EditPanel.xaml.cs @@ -54,7 +54,9 @@ public void Init() #region IDropable 方法 - public void OnDrag(List fileList) { } + public void OnDrag(List fileList) + { + } public void OnDrop(List fileList) { @@ -83,7 +85,8 @@ public void OnDrop(List fileList) public PinBase? FindPin(PinPath path) { foreach (var node in _nodeComponent.NodeList) - if (node.ID == path.NodeID) return node.FindPin(path.NodeVersion, path.GroupIndex, path.PinIndex); + if (node.ID == path.NodeID) + return node.FindPin(path.NodeVersion, path.GroupIndex, path.PinIndex); return null; } @@ -111,10 +114,13 @@ private void Project_Loaded() /// 绘图组件 private DrawingComponent _drawingComponent; + /// 节点组件 private NodeComponent _nodeComponent; + /// 卡片组件 private CardComponent _cardComponent; + /// 交互组件 private InteractionComponent _interactionComponent; diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs b/XNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs index 8e13c10..96ad0e6 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Layer/ConnectLineLayer.cs @@ -31,8 +31,10 @@ public void RemoveConnectLine(PinBase start, PinBase end) lineIndex = index; break; } + index++; } + // 移除连接线 if (lineIndex != -1) { diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs b/XNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs index 070b0c0..615af78 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Layer/GridLayer.cs @@ -99,11 +99,11 @@ private void DrawGrid() // 绘制细分线 _currentPen = _microLine; for (line = 0; line < GridLineCount; line++) - for (subIndex = 1; subIndex < _subdivideHeight; subIndex++) - DrawHorizontalLine(line * _gridHeight + _gridHeight / _subdivideHeight * subIndex); + for (subIndex = 1; subIndex < _subdivideHeight; subIndex++) + DrawHorizontalLine(line * _gridHeight + _gridHeight / _subdivideHeight * subIndex); for (list = 0; list < GridListCount; list++) - for (subIndex = 1; subIndex < _subdivideWidth; subIndex++) - DrawVerticalLine(list * _gridWidth + _gridWidth / _subdivideWidth * subIndex); + for (subIndex = 1; subIndex < _subdivideWidth; subIndex++) + DrawVerticalLine(list * _gridWidth + _gridWidth / _subdivideWidth * subIndex); // 绘制网格线 _currentPen = _normalLine; @@ -112,6 +112,7 @@ private void DrawGrid() if (_drawStart.Y + line * _gridHeight == GridCenter.Y) continue; DrawHorizontalLine(line * _gridHeight); } + for (list = 0; list < GridListCount; list++) { if (_drawStart.X + list * _gridWidth == GridCenter.X) continue; @@ -192,11 +193,13 @@ private void DrawVerticalLine(int x) /// 格子宽度 private readonly int _gridWidth = 120; + /// 宽度细分量 private readonly int _subdivideWidth = 4; /// 格子高度 private readonly int _gridHeight = 120; + /// 高度细分量 private readonly int _subdivideHeight = 4; diff --git a/XNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs b/XNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs index 6e09b1f..5effbc5 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/Layer/HoverBoxLayer.cs @@ -34,6 +34,7 @@ public void SetMotionProperty(string propertyName, double value) Box.BoxOffset = value; break; } + Dispatcher.Invoke(Update); } diff --git a/XNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs b/XNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs index af79ea8..1b69836 100644 --- a/XNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs +++ b/XNode/SubSystem/NodeEditSystem/Panel/SelectTool.cs @@ -7,7 +7,9 @@ namespace XNode.SubSystem.NodeEditSystem.Panel { public class SelectTool : ToolBase { - public SelectTool(InteractionComponent host) : base(host) { } + public SelectTool(InteractionComponent host) : base(host) + { + } /// 光标 public Cursor Cursor { get; set; } @@ -86,10 +88,7 @@ private void 命中空白() ResetTree(); }); BackToRoot(); - NewNode(Behaviors.Move, (_) => - { - _host.DrawSelectBox(); - }); + NewNode(Behaviors.Move, (_) => { _host.DrawSelectBox(); }); NewNode(Behaviors.LeftUp, (_) => { _host.EndDrawSelectBox(); @@ -119,6 +118,7 @@ private void 命中节点() _host.SetTop(); _host.AddSelect(); } + // 开始拖动节点 _host.BeginDragNode(); _host.CaptureOperationLayer(); @@ -199,10 +199,7 @@ private void 中键按下() ResetTree(); }); BackToRoot(); - NewNode(Behaviors.Move, (_) => - { - _host.DragViewport(); - }); + NewNode(Behaviors.Move, (_) => { _host.DragViewport(); }); NewNode(Behaviors.MiddleUp, (_) => { _host.ReleaseOperationLayer(); @@ -227,10 +224,7 @@ private void 右键引脚() ResetTree(); }); BackToRoot(); - NewNode(Behaviors.Move, (_) => - { - _host.CancelBreakPin(); - }); + NewNode(Behaviors.Move, (_) => { _host.CancelBreakPin(); }); NewNode(Behaviors.RightUp, (_) => { _host.HandleMouseMove(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs b/XNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs index 798f2ac..bd06eba 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Drivers/FrameDriver.cs @@ -28,6 +28,7 @@ public override void Init() InitPinGroup(); } + public override void Enable() { ControlEngine.Instance.Connect(this); @@ -49,7 +50,8 @@ public override void Clear() public override string GetTypeString() => nameof(FrameDriver); - public override Dictionary GetParaDict() => new Dictionary { { "Fps", GetData(0) } }; + public override Dictionary GetParaDict() => + new Dictionary { { "Fps", GetData(0) } }; public override void LoadParaDict(string version, Dictionary paraDict) { @@ -57,7 +59,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(0, paraDict["Fps"]); } - catch (Exception) { } + catch (Exception) + { + } } #endregion @@ -92,7 +96,9 @@ private void FpsChanged() if (fps > 120) fps = 120; if (fps > 0) _frameLength = 1000 / fps; } - catch (Exception) { } + catch (Exception) + { + } } #endregion @@ -101,6 +107,7 @@ private void FpsChanged() /// 单帧时长:毫秒 private double _frameLength = 40; + /// 当前延迟 private double _delay = 0; diff --git a/XNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs b/XNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs index 802d42e..101e363 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Drivers/TimerDriver.cs @@ -16,7 +16,8 @@ public override void Init() { SetViewProperty(NodeColorSet.Driver, "Timer", "定时驱动器"); - DataPinGroup time = new DataPinGroup(this, "double", "间隔毫秒", "5000") { Readable = false, Writeable = false }; + DataPinGroup time = new DataPinGroup(this, "double", "间隔毫秒", "5000") + { Readable = false, Writeable = false }; time.ValueChanged += Time_ValueChanged; PinGroupList.Add(time); PinGroupList.Add(new ActionPinGroup(this, "更新")); @@ -64,7 +65,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(0, paraDict["Time"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new TimerDriver(); @@ -99,7 +102,9 @@ private void Time_ValueChanged() { _frameLength = double.Parse(GetData(0)).Limit(1000 / 120.0, double.MaxValue); } - catch (Exception) { } + catch (Exception) + { + } } #endregion @@ -108,6 +113,7 @@ private void Time_ValueChanged() /// 单帧时长:毫秒 private double _frameLength = 5000; + /// 当前延迟 private double _delay = 0; diff --git a/XNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs b/XNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs index a8bb89e..7b1bb01 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Events/Event_Keyboard.cs @@ -13,7 +13,8 @@ public override void Init() SetViewProperty(NodeColorSet.Event, "Key", "按键"); PinGroupList.Add(new DataPinGroup(this, "string", "当前按键", "") { BoxWidth = 120, Writeable = false }); - PinGroupList.Add(new DataPinGroup(this, "string", "监听按键", "Space") { BoxWidth = 120, Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "监听按键", "Space") + { BoxWidth = 120, Readable = false, Writeable = false }); PinGroupList.Add(new ActionPinGroup(this, "按下")); PinGroupList.Add(new ActionPinGroup(this, "松开")); @@ -49,7 +50,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["ListenKey"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Event_Keyboard(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs index 80519a6..4943494 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_If.cs @@ -46,7 +46,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["LogicValue"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Flow_If(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs index 777c05f..30cd124 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_Switch.cs @@ -51,7 +51,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["Value"]); } - catch (Exception) { } + catch (Exception) + { + } } public override Dictionary GetPropertyDict() @@ -72,7 +74,9 @@ public override void LoadPropertyDict(string version, Dictionary }; UpdateAllItem(); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Flow_Switch(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs index 7518092..3217263 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Flows/Flow_While.cs @@ -28,6 +28,7 @@ protected override void ExecuteNode() context = new SynchronizationContext(); SynchronizationContext.SetSynchronizationContext(context); } + Task.Run(() => ExecuteWhileSync(logicValueGroup, loopBodyGroup, context)); } @@ -38,7 +39,8 @@ public override Dictionary GetParaDict() => protected override NodeBase CloneNode() => new Flow_While(); - private void ExecuteWhileSync(DataPinGroup logicValueGroup, ActionPinGroup loopBodyGroup, SynchronizationContext context) + private void ExecuteWhileSync(DataPinGroup logicValueGroup, ActionPinGroup loopBodyGroup, + SynchronizationContext context) { try { @@ -55,6 +57,7 @@ private void ExecuteWhileSync(DataPinGroup logicValueGroup, ActionPinGroup loopB // 执行循环体 loopBodyGroup.Invoke(); } + context.Post(_ => GetPinGroup().Execute(), null); } catch (Exception ex) diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs new file mode 100644 index 0000000..7e4a2aa --- /dev/null +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_BinOP.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using XLib.Node; + +namespace XNode.SubSystem.NodeLibSystem.Define.Functions +{ + public class Func_BinOP : NodeBase + { + public override void Init() + { + SetViewProperty(NodeColorSet.Function, "Function", "二元运算"); + + PinGroupList.Add(new ExecutePinGroup(this, "执行四则运算")); + PinGroupList.Add(new DataPinGroup(this, "double", "数值一", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "运算符", "+") { Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "数值二", "0") { Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "double", "结果", "0") { Writeable = false }); + + InitPinGroup(); + } + + protected override void ExecuteNode() + { + // 更新数值一、数值二 + ((DataPinGroup)PinGroupList[1]).UpdateValue(); + ((DataPinGroup)PinGroupList[3]).UpdateValue(); + // 解析数值 + double num1 = double.Parse(((DataPinGroup)PinGroupList[1]).Value); + double num2 = double.Parse(((DataPinGroup)PinGroupList[3]).Value); + // 执行对比 + double result = 0d; + switch (((DataPinGroup)PinGroupList[2]).Value) + { + case "+": + result = num1 + num2; + break; + case "-": + result = num1 - num2; + break; + case "*": + result = num1 * num2; + break; + case "/": + result = num1 / num2; + break; + } + + // 更新结果 + ((DataPinGroup)PinGroupList[4]).Value = result.ToString(); + + ((ExecutePinGroup)PinGroupList[0]).Execute(); + } + + public override Dictionary GetParaDict() + { + Dictionary result = new Dictionary + { + { "Num1", GetData(1) }, + { "Operator", GetData(2) }, + { "Num2", GetData(3) }, + { "Result", GetData(4) } + }; + return result; + } + + public override string GetTypeString() => nameof(Func_BinOP); + + public override void LoadParaDict(string version, Dictionary paraDict) + { + try + { + SetData(1, paraDict["Num1"]); + SetData(2, paraDict["Operator"]); + SetData(3, paraDict["Num2"]); + SetData(4, paraDict["Result"]); + } + catch (Exception) + { + } + } + + + protected override NodeBase CloneNode() => new Func_Compare(); + } +} \ No newline at end of file diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs index 4eeeae3..ccd007b 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Compare.cs @@ -48,6 +48,7 @@ protected override void ExecuteNode() result = num1 != num2; break; } + // 更新结果 ((DataPinGroup)PinGroupList[4]).Value = result == true ? "True" : "False"; @@ -77,7 +78,9 @@ public override void LoadParaDict(string version, Dictionary par SetData(3, paraDict["Num2"]); SetData(4, paraDict["Result"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_Compare(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs index 0a4b92c..e2ebd41 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Delay.cs @@ -42,7 +42,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["Time"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_Delay(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs index e1d240e..5c10990 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Log.cs @@ -34,7 +34,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["Msg"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_Log(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs index e8a9a4e..2480587 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_NumberToRatio.cs @@ -61,7 +61,9 @@ public override void LoadParaDict(string version, Dictionary par SetData(3, paraDict["Current"]); SetData(4, paraDict["Result"]); } - catch (Exception) { } + catch (Exception) + { + } } public override Dictionary GetPropertyDict() @@ -78,7 +80,9 @@ public override void LoadPropertyDict(string version, Dictionary { PropertyList[0].Value = propertyDict["Precision"]; } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_NumberToRatio(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs index 5b327c5..dd69a77 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_RatioToInt.cs @@ -59,7 +59,9 @@ public override void LoadParaDict(string version, Dictionary par SetData(3, paraDict["Current"]); SetData(4, paraDict["Result"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_RatioToInt(); diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs index fd974a1..091bdce 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_SendNetMessage.cs @@ -15,10 +15,12 @@ public override void Init() SetViewProperty(NodeColorSet.Function, "Function", "发送网络消息"); PinGroupList.Add(new ExecutePinGroup(this, "发送字符串至网络设备")); - PinGroupList.Add(new DataPinGroup(this, "string", "地址", "127.0.0.1:2400") { BoxWidth = 120, Readable = false, Writeable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "地址", "127.0.0.1:2400") + { BoxWidth = 120, Readable = false, Writeable = false }); PinGroupList.Add(new DataPinGroup(this, "string", "协议", "udp") { Readable = false, Writeable = false }); PinGroupList.Add(new DataPinGroup(this, "bool", "字节模式", "False") { Readable = false, Writeable = false }); - PinGroupList.Add(new DataPinGroup(this, "string", "消息", "Hello World") { BoxWidth = 200, Readable = false }); + PinGroupList.Add(new DataPinGroup(this, "string", "消息", "Hello World") + { BoxWidth = 200, Readable = false }); InitPinGroup(); } @@ -53,6 +55,7 @@ protected override void ExecuteNode() context = new SynchronizationContext(); SynchronizationContext.SetSynchronizationContext(context); } + Task.Run(() => SendTcpMessageSync(address, messageByte, context)); } } @@ -111,7 +114,10 @@ public override void LoadParaDict(string version, Dictionary par byteArray[index] = Convert.ToByte(byteArraySource[index], 16); return byteArray; } - catch (Exception) { } + catch (Exception) + { + } + return null; } diff --git a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs index be51112..5e099de 100644 --- a/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs +++ b/XNode/SubSystem/NodeLibSystem/Define/Functions/Func_Sleep.cs @@ -36,7 +36,9 @@ public override void LoadParaDict(string version, Dictionary par { SetData(1, paraDict["Time"]); } - catch (Exception) { } + catch (Exception) + { + } } protected override NodeBase CloneNode() => new Func_Sleep(); diff --git a/XNode/SubSystem/NodeLibSystem/NodeLibManager.cs b/XNode/SubSystem/NodeLibSystem/NodeLibManager.cs index 217effe..3cca313 100644 --- a/XNode/SubSystem/NodeLibSystem/NodeLibManager.cs +++ b/XNode/SubSystem/NodeLibSystem/NodeLibManager.cs @@ -17,7 +17,10 @@ public class NodeLibManager : IManager { #region 单例 - private NodeLibManager() { } + private NodeLibManager() + { + } + public static NodeLibManager Instance { get; } = new NodeLibManager(); #endregion @@ -40,9 +43,13 @@ public void Init() LoadOutsideNodeLib(); } - public void Reset() { } + public void Reset() + { + } - public void Clear() { } + public void Clear() + { + } #endregion @@ -70,6 +77,7 @@ public void Clear() { } nameof(Flow_Switch) => new Flow_Switch(), nameof(Func_Compare) => new Func_Compare(), + nameof(Func_BinOP) => new Func_BinOP(), nameof(Func_NumberToRatio) => new Func_NumberToRatio(), nameof(Func_RatioToInt) => new Func_RatioToInt(), nameof(Func_SendNetMessage) => new Func_SendNetMessage(), @@ -120,6 +128,7 @@ private void BuildInnerNodeLib() _nodeLibRoot.CreateFile(事件节点, "按键", "nt", new NodeType()); _nodeLibRoot.CreateFile(运算函数, "关系运算", "nt", new NodeType()); + _nodeLibRoot.CreateFile(运算函数, "四则运算", "nt", new NodeType()); _nodeLibRoot.CreateFile(转换器, "比例转整数", "nt", new NodeType()); _nodeLibRoot.CreateFile(转换器, "数值转比例", "nt", new NodeType()); _nodeLibRoot.CreateFile(函数节点, "发送网络消息", "nt", new NodeType()); @@ -161,6 +170,7 @@ private void LoadOutsideNodeLib() } } } + // 遍历节点库 foreach (var libPair in NodeLibDict) { @@ -184,6 +194,7 @@ private List GetAllNodeLibDll() { if (fileInfo.Extension == ".dll") result.Add(fileInfo.FullName); } + return result; } @@ -202,6 +213,7 @@ private void LoadFolder(Folder target, Folder oldFolder) // 递归加载 LoadFolder(childFolder, oldChild); } + // 加载文件 foreach (var oldFile in oldFolder.FileList) { diff --git a/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml b/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml index 98472fc..ae55d96 100644 --- a/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml +++ b/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml @@ -1,25 +1,28 @@  - - - - + \ No newline at end of file diff --git a/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml.cs b/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml.cs index e7690dd..37905b1 100644 --- a/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml.cs +++ b/XNode/SubSystem/NodeLibSystem/NodeLibPanel.xaml.cs @@ -37,13 +37,15 @@ private void LoadNodeLib(Folder folder, TreeItem? parentItem) // 添加文件夹项 TreeItem folderTreeItem = AddTreeItem(parentItem, currentFolder, icon); // 恢复折叠状态 - folderTreeItem.IsExpanded = CacheManager.Instance.Cache.NodeLib.IsExpanded(folderTreeItem.GetFullPath()); + folderTreeItem.IsExpanded = + CacheManager.Instance.Cache.NodeLib.IsExpanded(folderTreeItem.GetFullPath()); folderTreeItem.ItemExpanded = TreeItem_Expanded; folderTreeItem.ItemCollapsed = TreeItem_Collapsed; // 加载文件 if (currentFolder.FolderList.Count > 0 || currentFolder.FileList.Count > 0) LoadNodeLib(currentFolder, folderTreeItem); } + // 加载文件 foreach (var currentFile in folder.FileList) { @@ -54,12 +56,14 @@ private void LoadNodeLib(Folder folder, TreeItem? parentItem) /// /// 树项展开 /// - private void TreeItem_Expanded(TreeItem treeItem) => CacheManager.Instance.Cache.NodeLib.Expand(treeItem.GetFullPath()); + private void TreeItem_Expanded(TreeItem treeItem) => + CacheManager.Instance.Cache.NodeLib.Expand(treeItem.GetFullPath()); /// /// 树项折叠 /// - private void TreeItem_Collapsed(TreeItem treeItem) => CacheManager.Instance.Cache.NodeLib.Fold(treeItem.GetFullPath()); + private void TreeItem_Collapsed(TreeItem treeItem) => + CacheManager.Instance.Cache.NodeLib.Fold(treeItem.GetFullPath()); /// /// 包含子项 diff --git a/XNode/SubSystem/OptionSystem/OptionManager.cs b/XNode/SubSystem/OptionSystem/OptionManager.cs index b709abc..1f2d44d 100644 --- a/XNode/SubSystem/OptionSystem/OptionManager.cs +++ b/XNode/SubSystem/OptionSystem/OptionManager.cs @@ -10,7 +10,10 @@ public class OptionManager : IManager { #region 单例 - private OptionManager() { } + private OptionManager() + { + } + public static OptionManager Instance { get; } = new OptionManager(); #endregion @@ -37,9 +40,13 @@ public void Init() if (!Directory.Exists(NodeLibPath)) Directory.CreateDirectory(NodeLibPath); } - public void Reset() { } + public void Reset() + { + } - public void Clear() { } + public void Clear() + { + } #endregion diff --git a/XNode/SubSystem/ProjectSystem/FileTool.cs b/XNode/SubSystem/ProjectSystem/FileTool.cs index 00178c4..be1fdd7 100644 --- a/XNode/SubSystem/ProjectSystem/FileTool.cs +++ b/XNode/SubSystem/ProjectSystem/FileTool.cs @@ -14,6 +14,7 @@ private FileTool() _projectFilter.TypeList.Add(new TypeInfo("节点项目", "xnode")); _projectFilter.TypeList.Add(new TypeInfo("节点项目", "json")); } + public static FileTool Instance { get; } = new FileTool(); /// diff --git a/XNode/SubSystem/ProjectSystem/ProjectManager.cs b/XNode/SubSystem/ProjectSystem/ProjectManager.cs index de2e991..ea5ad56 100644 --- a/XNode/SubSystem/ProjectSystem/ProjectManager.cs +++ b/XNode/SubSystem/ProjectSystem/ProjectManager.cs @@ -17,7 +17,10 @@ public class ProjectManager { #region 单例 - private ProjectManager() { } + private ProjectManager() + { + } + public static ProjectManager Instance { get; } = new ProjectManager(); #endregion @@ -56,6 +59,7 @@ public void NewProject() // 保存项目 else if (result == true) SaveProject(); } + // 关闭当前项目 CloseProject(); // 新建当前项目 @@ -76,6 +80,7 @@ public void OpenProject() // 保存项目 else if (result == true) SaveProject(); } + // 选择项目文件 string filePath = FileTool.Instance.OpenReadProjectDialog(); if (filePath == "") return; @@ -85,6 +90,7 @@ public void OpenProject() WM.ShowTip($"项目“{CurrentProject.ProjectName}”已打开"); return; } + // 读取存档文件 ArchiveFile? file = ArchiveManager.Instance.ReadArchiveFile(filePath); if (file == null) @@ -92,6 +98,7 @@ public void OpenProject() WM.ShowError($"项目文件“{filePath}”读取失败:无效的存档文件"); return; } + // 关闭当前项目 CloseProject(); // 加载项目 @@ -123,6 +130,7 @@ public bool SaveProject() // 创建空文本文件 File.WriteAllText(projectPath, "", Encoding.UTF8); } + // 执行保存 ExecuteSave(); return true; @@ -212,7 +220,9 @@ private void ExecuteSave() // 删除备份 if (backupPath != "" && File.Exists(backupPath)) File.Delete(backupPath); } - catch (Exception) { } + catch (Exception) + { + } } /// diff --git a/XNode/SubSystem/ResourceSystem/CursorManager.cs b/XNode/SubSystem/ResourceSystem/CursorManager.cs index e63d813..dd07b88 100644 --- a/XNode/SubSystem/ResourceSystem/CursorManager.cs +++ b/XNode/SubSystem/ResourceSystem/CursorManager.cs @@ -11,7 +11,10 @@ public class CursorManager { #region 单例 - private CursorManager() { } + private CursorManager() + { + } + public static CursorManager Instance { get; } = new CursorManager(); #endregion diff --git a/XNode/SubSystem/ResourceSystem/ImageResManager.cs b/XNode/SubSystem/ResourceSystem/ImageResManager.cs index d9c2429..fb04698 100644 --- a/XNode/SubSystem/ResourceSystem/ImageResManager.cs +++ b/XNode/SubSystem/ResourceSystem/ImageResManager.cs @@ -7,7 +7,10 @@ public class ImageResManager { #region 单例 - private ImageResManager() { } + private ImageResManager() + { + } + public static ImageResManager Instance { get; } = new ImageResManager(); #endregion diff --git a/XNode/SubSystem/ResourceSystem/PinIconManager.cs b/XNode/SubSystem/ResourceSystem/PinIconManager.cs index d57a574..8e8ee64 100644 --- a/XNode/SubSystem/ResourceSystem/PinIconManager.cs +++ b/XNode/SubSystem/ResourceSystem/PinIconManager.cs @@ -1,5 +1,6 @@ -using System.Windows.Media; -using System.Windows.Media.Imaging; +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; using XNode.SubSystem.NodeEditSystem.Define; namespace XNode.SubSystem.ResourceSystem @@ -11,7 +12,10 @@ public class PinIconManager { #region 单例 - private PinIconManager() { } + private PinIconManager() + { + } + public static PinIconManager Instance { get; } = new PinIconManager(); #endregion @@ -20,9 +24,56 @@ private PinIconManager() { } public string Name { get; set; } = "引脚图标管理器"; - public ImageSource ExecutePin_Null => _executePin_Null; + private readonly SolidColorBrush ExecuteBrush = new SolidColorBrush(Color.FromRgb(196, 126, 255)); + + public Shape ExecutePinIcon + { + get + { + var pinIcon = Application.Current.MainWindow.FindResource("PinIcons.ExecutePin"); + if (pinIcon is Polygon s) + { + return new Polygon { Points = s.Points, StrokeThickness = 1 }; + } + + throw new ArgumentNullException(); + } + } + + public Shape DataPinIcon + { + get + { + var pinIcon = Application.Current.MainWindow.FindResource("PinIcons.DataPin"); - public ImageSource ExecutePin => _executePin; + if (pinIcon is Polygon s) + { + return new Polygon { Points = s.Points, StrokeThickness = 1 }; + } + + throw new ArgumentNullException(); + } + } + + public Shape ExecutePin_Null + { + get + { + var s = ExecutePinIcon; + s.Stroke = ExecuteBrush; + return s; + } + } + + public Shape ExecutePin + { + get + { + var s = ExecutePin_Null; + s.Fill = ExecuteBrush; + return s; + } + } #endregion @@ -30,7 +81,7 @@ private PinIconManager() { } public void Init() { - GenerateExecutePinIcon(); + // GenerateExecutePinIcon(); GenerateDataPinIcon(); } @@ -41,70 +92,36 @@ public void Init() /// /// 获取数据引脚图标 /// - public BitmapSource GetDataPinIcon(string dataType, bool solid) + public Shape GetDataPinIcon(string dataType, bool solid) { - if (solid) return _dataPinIconDict[dataType]; - return _dataPinIconDict[$"{dataType}_null"]; + if (solid) + { + var s = DataPinIcon; + s.Fill = new SolidColorBrush(_colors[dataType]); + return s; + } + else + { + var s = DataPinIcon; + s.Stroke = new SolidColorBrush(_colors[dataType]); + return s; + } } #endregion - #region 私有方法 - - /// - /// 生成执行引脚图标 - /// - private void GenerateExecutePinIcon() - { - byte[] iconData = PinIconTool.CreatePinIcon(PinType.Execute, 196, 126, 255); - _executePin_Null = CreateSource(11, iconData); - iconData = PinIconTool.CreatePinIcon(PinType.Execute, 196, 126, 255, PinStyle.Solid); - _executePin = CreateSource(11, iconData); - } + Dictionary _colors = new(); /// /// 生成数据引脚图标 /// private void GenerateDataPinIcon() { - GenerateDataPinIcon("bool_null", PinColorSet.Bool); - GenerateDataPinIcon("int_null", PinColorSet.Int); - GenerateDataPinIcon("double_null", PinColorSet.Double); - GenerateDataPinIcon("string_null", PinColorSet.String); - GenerateDataPinIcon("byte[]_null", PinColorSet.ByteArray); - - GenerateDataPinIcon("bool", PinColorSet.Bool, PinStyle.Solid); - GenerateDataPinIcon("int", PinColorSet.Int, PinStyle.Solid); - GenerateDataPinIcon("double", PinColorSet.Double, PinStyle.Solid); - GenerateDataPinIcon("string", PinColorSet.String, PinStyle.Solid); - GenerateDataPinIcon("byte[]", PinColorSet.ByteArray, PinStyle.Solid); + _colors.Add("bool", PinColorSet.Bool); + _colors.Add("int", PinColorSet.Int); + _colors.Add("double", PinColorSet.Double); + _colors.Add("string", PinColorSet.String); + _colors.Add("byte[]", PinColorSet.ByteArray); } - - /// - /// 生成数据引脚图标 - /// - private void GenerateDataPinIcon(string key, Color color, PinStyle style = PinStyle.Hollow) - { - byte[] iconData = PinIconTool.CreatePinIcon(PinType.Data, color.R, color.G, color.B, style); - _dataPinIconDict.Add(key, CreateSource(11, iconData)); - } - - /// - /// 创建图源 - /// - private BitmapSource CreateSource(int size, byte[] iconData) => - BitmapSource.Create(size, size, 96, 96, PixelFormats.Bgra32, null, iconData, size * 4); - - #endregion - - #region 字段 - - private BitmapSource _executePin_Null; - private BitmapSource _executePin; - - /// 数据引脚图标字典 - public Dictionary _dataPinIconDict = new Dictionary(); - - #endregion } } \ No newline at end of file diff --git a/XNode/SubSystem/ResourceSystem/PinIconTool.cs b/XNode/SubSystem/ResourceSystem/PinIconTool.cs index eb58ae4..535e0e9 100644 --- a/XNode/SubSystem/ResourceSystem/PinIconTool.cs +++ b/XNode/SubSystem/ResourceSystem/PinIconTool.cs @@ -9,6 +9,7 @@ public enum PinType { /// 执行引脚 Execute, + /// 数据引脚 Data } @@ -20,6 +21,7 @@ public enum PinStyle { /// 空心 Hollow, + /// 实心 Solid, } @@ -41,6 +43,7 @@ public static byte[] CreatePinIcon(PinType type, byte r, byte g, byte b, PinStyl if (style == PinStyle.Hollow) return DrawExecutePinIcon(r, g, b).GetPixelData(); return DrawSolidExecutePinIcon(r, g, b).GetPixelData(); } + if (style == PinStyle.Hollow) return DrawDataPinIcon(r, g, b).GetPixelData(); return DrawSolidDataPinIcon(r, g, b).GetPixelData(); } diff --git a/XNode/SubSystem/ResourceSystem/ResourceManager.cs b/XNode/SubSystem/ResourceSystem/ResourceManager.cs index c22eb07..76a24d9 100644 --- a/XNode/SubSystem/ResourceSystem/ResourceManager.cs +++ b/XNode/SubSystem/ResourceSystem/ResourceManager.cs @@ -6,7 +6,10 @@ public class ResourceManager : IManager { #region 单例 - private ResourceManager() { } + private ResourceManager() + { + } + public static ResourceManager Instance { get; } = new ResourceManager(); #endregion @@ -27,9 +30,13 @@ public void Init() PinIconManager.Instance.Init(); } - public void Reset() { } + public void Reset() + { + } - public void Clear() { } + public void Clear() + { + } #endregion } diff --git a/XNode/SubSystem/TimerSystem/AppTimer.cs b/XNode/SubSystem/TimerSystem/AppTimer.cs index 8179fd8..5465943 100644 --- a/XNode/SubSystem/TimerSystem/AppTimer.cs +++ b/XNode/SubSystem/TimerSystem/AppTimer.cs @@ -16,6 +16,7 @@ private AppTimer() _timer.Interval = TimeSpan.FromMilliseconds(1000.0 / 30.0); _timer.Tick += Timer_Tick; } + public static AppTimer Instance { get; } = new AppTimer(); #endregion @@ -76,6 +77,7 @@ private void Timer_Tick(object? sender, EventArgs e) /// 定时器 private readonly DispatcherTimer _timer = new DispatcherTimer(DispatcherPriority.Background); + /// 定时器处理器列表 private List _handlerList = new List(); diff --git a/XNode/SubSystem/TimerSystem/TimeEngine.cs b/XNode/SubSystem/TimerSystem/TimeEngine.cs index f9e4a5f..a421b14 100644 --- a/XNode/SubSystem/TimerSystem/TimeEngine.cs +++ b/XNode/SubSystem/TimerSystem/TimeEngine.cs @@ -69,7 +69,9 @@ private void Timer_Tick() { foreach (var item in _handlerList) item.Tick(); } - catch (Exception) { } + catch (Exception) + { + } } #endregion @@ -78,8 +80,9 @@ private void Timer_Tick() /// 秒表:用作应用程序的精确时间参考 private readonly Stopwatch _stopwatch = new Stopwatch(); + /// 定时器:定时驱动引擎 - private readonly HighPrecisionTimer _timer = new HighPrecisionTimer(); + private readonly IHighPrecisionTimer _timer = new HighPrecisionTimerW(); /// 定时处理器列表 private List _handlerList = new List(); diff --git a/XNode/SubSystem/WindowSystem/AskDialog.xaml b/XNode/SubSystem/WindowSystem/AskDialog.xaml index 9bb9535..4e27c7c 100644 --- a/XNode/SubSystem/WindowSystem/AskDialog.xaml +++ b/XNode/SubSystem/WindowSystem/AskDialog.xaml @@ -1,37 +1,41 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + 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:local="clr-namespace:XNode.SubSystem.WindowSystem" + xmlns:xw="clr-namespace:XLib.WPF.WindowDefine;assembly=XLib.WPF" + FontFamily="../../Assets/Font/#新宋体" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" + mc:Ignorable="d" Style="{StaticResource TipDialog}" + Title="提示" Height="151" Width="514"> - + - - + + - - - - + + + + - + - -