diff --git a/Examples/Nodify.Calculator/ApplicationViewModel.cs b/Examples/Nodify.Calculator/ApplicationViewModel.cs index fac7041..8879220 100644 --- a/Examples/Nodify.Calculator/ApplicationViewModel.cs +++ b/Examples/Nodify.Calculator/ApplicationViewModel.cs @@ -103,6 +103,15 @@ namespace Nodify.Calculator GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu); } catch { } + + // When the log panel is closed, clear any lingering execution animation state + LogPanel.PanelClosed += () => + { + foreach (var editor in Editors) + { + editor.Calculator?.ClearExecutionState(); + } + }; } private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator) diff --git a/Examples/Nodify.Calculator/CalculatorViewModel.cs b/Examples/Nodify.Calculator/CalculatorViewModel.cs index 7bb917e..de9418d 100644 --- a/Examples/Nodify.Calculator/CalculatorViewModel.cs +++ b/Examples/Nodify.Calculator/CalculatorViewModel.cs @@ -149,6 +149,14 @@ namespace Nodify.Calculator Operations.Add(knot); } + public void ClearExecutionState() + { + foreach (var op in Operations) + op.ExecutionState = ExecutionState.None; + foreach (var con in Connections) + con.IsActiveInExecution = false; + } + public INodifyCommand StartConnectionCommand { get; } public INodifyCommand CreateConnectionCommand { get; } public INodifyCommand DisconnectConnectorCommand { get; } diff --git a/Examples/Nodify.Calculator/ConnectionViewModel.cs b/Examples/Nodify.Calculator/ConnectionViewModel.cs index 445d7c1..eeaa4ff 100644 --- a/Examples/Nodify.Calculator/ConnectionViewModel.cs +++ b/Examples/Nodify.Calculator/ConnectionViewModel.cs @@ -32,5 +32,12 @@ set { _outputNodeId = value; } } + private bool _isActiveInExecution; + public bool IsActiveInExecution + { + get => _isActiveInExecution; + set => SetProperty(ref _isActiveInExecution, value); + } + } } diff --git a/Examples/Nodify.Calculator/EditorView.xaml b/Examples/Nodify.Calculator/EditorView.xaml index 079e741..1945c2e 100644 --- a/Examples/Nodify.Calculator/EditorView.xaml +++ b/Examples/Nodify.Calculator/EditorView.xaml @@ -99,8 +99,27 @@ Target="{Binding Input.Anchor}" Foreground="{Binding Input.Color}" Stroke="{Binding Input.Color}" - StrokeThickness="2" - /> + StrokeThickness="2"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/Nodify.Calculator/ExecutionState.cs b/Examples/Nodify.Calculator/ExecutionState.cs new file mode 100644 index 0000000..b9c04f7 --- /dev/null +++ b/Examples/Nodify.Calculator/ExecutionState.cs @@ -0,0 +1,10 @@ +namespace Nodify.Calculator +{ + public enum ExecutionState + { + None, + Running, + Completed, + Error + } +} diff --git a/Examples/Nodify.Calculator/Executor.cs b/Examples/Nodify.Calculator/Executor.cs index 21567c4..3ed74d1 100644 --- a/Examples/Nodify.Calculator/Executor.cs +++ b/Examples/Nodify.Calculator/Executor.cs @@ -34,6 +34,9 @@ namespace Nodify.Calculator static Dictionary outputs = new Dictionary(); static Dictionary variables = new Dictionary(); + // Animation delay between node steps (milliseconds) while executing + private const int AnimationDelayMs = 600; + // Auth configuration populated from the Auth node private string _authBaseUrl = string.Empty; private string _authType = string.Empty; @@ -76,6 +79,9 @@ namespace Nodify.Calculator var allnodes = calcModel.Operations; var allConnections = calcModel.Connections; + // Reset any previous execution animation state + ResetExecutionState(calcModel); + // Resolve Auth node configuration before execution ResolveAuthNode(allnodes); @@ -97,6 +103,36 @@ namespace Nodify.Calculator return errorString; } + private static void RunOnUI(Action action) + { + var dispatcher = System.Windows.Application.Current?.Dispatcher; + if (dispatcher == null) { action(); return; } + if (dispatcher.CheckAccess()) action(); + else dispatcher.Invoke(action); + } + + private void ResetExecutionState(CalculatorViewModel calc) + { + RunOnUI(() => + { + foreach (var op in calc.Operations) + op.ExecutionState = ExecutionState.None; + foreach (var con in calc.Connections) + con.IsActiveInExecution = false; + }); + } + + private void SetNodeState(OperationViewModel node, ExecutionState state) + { + RunOnUI(() => node.ExecutionState = state); + } + + private void SetConnectionActive(ConnectionViewModel conn, bool active) + { + if (conn == null) return; + RunOnUI(() => conn.IsActiveInExecution = active); + } + public static string GenerateClassFromJson(string json, string className) { // Parse the JSON to understand its structure @@ -947,12 +983,30 @@ namespace Nodify.Calculator if (isExecute) { + // Mark current node as running and animate + SetNodeState(currentNode, ExecutionState.Running); + System.Threading.Thread.Sleep(AnimationDelayMs / 2); + StartExecution(currentNode, connections); + + // Mark current node as completed + SetNodeState(currentNode, ExecutionState.Completed); + + // Animate the flow connection leading to the next node + SetConnectionActive(connection, true); + System.Threading.Thread.Sleep(AnimationDelayMs); + SetConnectionActive(connection, false); } // Recursively check the next node; stop on the first failure if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute)) { + if (isExecute) + { + // Mark the terminal "end" node as completed too + if (nextNode.Title?.Equals(endNodeTitle, StringComparison.OrdinalIgnoreCase) == true) + SetNodeState(nextNode, ExecutionState.Completed); + } return true; // If a valid path to the "end" is found, exit } } diff --git a/Examples/Nodify.Calculator/LogPanelViewModel.cs b/Examples/Nodify.Calculator/LogPanelViewModel.cs index 55d30c1..fd702f3 100644 --- a/Examples/Nodify.Calculator/LogPanelViewModel.cs +++ b/Examples/Nodify.Calculator/LogPanelViewModel.cs @@ -18,9 +18,18 @@ namespace Nodify.Calculator public bool IsOpen { get => _isOpen; - set => SetProperty(ref _isOpen, value); + set + { + var wasOpen = _isOpen; + if (SetProperty(ref _isOpen, value) && wasOpen && !value) + { + PanelClosed?.Invoke(); + } + } } + public event Action PanelClosed; + private bool _isRunning; public bool IsRunning { diff --git a/Examples/Nodify.Calculator/OperationViewModel.cs b/Examples/Nodify.Calculator/OperationViewModel.cs index dcd638b..0a9f0ac 100644 --- a/Examples/Nodify.Calculator/OperationViewModel.cs +++ b/Examples/Nodify.Calculator/OperationViewModel.cs @@ -72,6 +72,15 @@ namespace Nodify.Calculator public bool IsReadOnly { get; set; } + private ExecutionState _executionState; + [BsonIgnore] + [Newtonsoft.Json.JsonIgnore] + public ExecutionState ExecutionState + { + get => _executionState; + set => SetProperty(ref _executionState, value); + } + [BsonIgnore] private IOperation? _operation; [BsonIgnore]