diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs
index ec2cb019b..5e8f003f1 100644
--- a/src/Artemis.Core/VisualScripting/Node.cs
+++ b/src/Artemis.Core/VisualScripting/Node.cs
@@ -4,353 +4,364 @@ using System.Collections.ObjectModel;
using Ninject;
using Ninject.Parameters;
-namespace Artemis.Core
+namespace Artemis.Core;
+
+///
+/// Represents a kind of node inside a
+///
+public abstract class Node : CorePropertyChanged, INode
{
- ///
- /// Represents a kind of node inside a
- ///
- public abstract class Node : CorePropertyChanged, INode
+ ///
+ public event EventHandler? Resetting;
+
+ #region Properties & Fields
+
+ private string _name;
+
+ ///
+ public string Name
{
- ///
- public event EventHandler? Resetting;
+ get => _name;
+ protected set => SetAndNotify(ref _name, value);
+ }
- #region Properties & Fields
+ private string _description;
- private string _name;
+ ///
+ public string Description
+ {
+ get => _description;
+ protected set => SetAndNotify(ref _description, value);
+ }
- ///
- public string Name
+ private double _x;
+
+ ///
+ public double X
+ {
+ get => _x;
+ set => SetAndNotify(ref _x, value);
+ }
+
+ private double _y;
+
+ ///
+ public double Y
+ {
+ get => _y;
+ set => SetAndNotify(ref _y, value);
+ }
+
+ ///
+ public virtual bool IsExitNode => false;
+
+ ///
+ public virtual bool IsDefaultNode => false;
+
+ private readonly List _pins = new();
+
+ ///
+ public IReadOnlyCollection Pins => new ReadOnlyCollection(_pins);
+
+ private readonly List _pinCollections = new();
+
+ ///
+ public IReadOnlyCollection PinCollections => new ReadOnlyCollection(_pinCollections);
+
+ #endregion
+
+ #region Construtors
+
+ ///
+ /// Creates a new instance of the class with an empty name and description
+ ///
+ protected Node()
+ {
+ _name = string.Empty;
+ _description = string.Empty;
+ }
+
+ ///
+ /// Creates a new instance of the class with the provided name and description
+ ///
+ protected Node(string name, string description)
+ {
+ _name = name;
+ _description = description;
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates a new input pin and adds it to the collection
+ ///
+ /// The name of the pin
+ /// The type of value the pin will hold
+ /// The newly created pin
+ protected InputPin CreateInputPin(string name = "")
+ {
+ InputPin pin = new(this, name);
+ _pins.Add(pin);
+ OnPropertyChanged(nameof(Pins));
+ return pin;
+ }
+
+ ///
+ /// Creates a new input pin and adds it to the collection
+ ///
+ /// The type of value the pin will hold
+ /// The name of the pin
+ /// The newly created pin
+ protected InputPin CreateInputPin(Type type, string name = "")
+ {
+ InputPin pin = new(this, type, name);
+ _pins.Add(pin);
+ OnPropertyChanged(nameof(Pins));
+ return pin;
+ }
+
+ ///
+ /// Creates a new output pin and adds it to the collection
+ ///
+ /// The name of the pin
+ /// The type of value the pin will hold
+ /// The newly created pin
+ protected OutputPin CreateOutputPin(string name = "")
+ {
+ OutputPin pin = new(this, name);
+ _pins.Add(pin);
+ OnPropertyChanged(nameof(Pins));
+ return pin;
+ }
+
+ ///
+ /// Creates a new output pin and adds it to the collection
+ ///
+ /// The type of value the pin will hold
+ /// The name of the pin
+ /// The newly created pin
+ protected OutputPin CreateOutputPin(Type type, string name = "")
+ {
+ OutputPin pin = new(this, type, name);
+ _pins.Add(pin);
+ OnPropertyChanged(nameof(Pins));
+ return pin;
+ }
+
+ ///
+ /// Removes the provided from the node and it's collection
+ ///
+ /// The pin to remove
+ /// if the pin was removed; otherwise .
+ protected bool RemovePin(Pin pin)
+ {
+ bool isRemoved = _pins.Remove(pin);
+ if (isRemoved)
{
- get => _name;
- protected set => SetAndNotify(ref _name, value);
- }
-
- private string _description;
-
- ///
- public string Description
- {
- get => _description;
- protected set => SetAndNotify(ref _description, value);
- }
-
- private double _x;
-
- ///
- public double X
- {
- get => _x;
- set => SetAndNotify(ref _x, value);
- }
-
- private double _y;
-
- ///
- public double Y
- {
- get => _y;
- set => SetAndNotify(ref _y, value);
- }
-
- ///
- public virtual bool IsExitNode => false;
-
- ///
- public virtual bool IsDefaultNode => false;
-
- private readonly List _pins = new();
-
- ///
- public IReadOnlyCollection Pins => new ReadOnlyCollection(_pins);
-
- private readonly List _pinCollections = new();
-
- ///
- public IReadOnlyCollection PinCollections => new ReadOnlyCollection(_pinCollections);
-
- #endregion
-
- #region Construtors
-
- ///
- /// Creates a new instance of the class with an empty name and description
- ///
- protected Node()
- {
- _name = string.Empty;
- _description = string.Empty;
- }
-
- ///
- /// Creates a new instance of the class with the provided name and description
- ///
- protected Node(string name, string description)
- {
- _name = name;
- _description = description;
- }
-
- #endregion
-
- #region Methods
-
- ///
- /// Creates a new input pin and adds it to the collection
- ///
- /// The name of the pin
- /// The type of value the pin will hold
- /// The newly created pin
- protected InputPin CreateInputPin(string name = "")
- {
- InputPin pin = new(this, name);
- _pins.Add(pin);
+ pin.DisconnectAll();
OnPropertyChanged(nameof(Pins));
- return pin;
}
- ///
- /// Creates a new input pin and adds it to the collection
- ///
- /// The type of value the pin will hold
- /// The name of the pin
- /// The newly created pin
- protected InputPin CreateInputPin(Type type, string name = "")
- {
- InputPin pin = new(this, type, name);
- _pins.Add(pin);
- OnPropertyChanged(nameof(Pins));
- return pin;
- }
+ return isRemoved;
+ }
- ///
- /// Creates a new output pin and adds it to the collection
- ///
- /// The name of the pin
- /// The type of value the pin will hold
- /// The newly created pin
- protected OutputPin CreateOutputPin(string name = "")
- {
- OutputPin pin = new(this, name);
- _pins.Add(pin);
- OnPropertyChanged(nameof(Pins));
- return pin;
- }
+ ///
+ /// Creates a new input pin collection and adds it to the collection
+ ///
+ /// The type of value the pins of this collection will hold
+ /// The name of the pin collection
+ /// The amount of pins to initially add to the collection
+ /// The resulting input pin collection
+ protected InputPinCollection CreateInputPinCollection(string name = "", int initialCount = 1)
+ {
+ InputPinCollection pin = new(this, name, initialCount);
+ _pinCollections.Add(pin);
+ OnPropertyChanged(nameof(PinCollections));
+ return pin;
+ }
- ///
- /// Creates a new output pin and adds it to the collection
- ///
- /// The type of value the pin will hold
- /// The name of the pin
- /// The newly created pin
- protected OutputPin CreateOutputPin(Type type, string name = "")
- {
- OutputPin pin = new(this, type, name);
- _pins.Add(pin);
- OnPropertyChanged(nameof(Pins));
- return pin;
- }
+ ///
+ /// Creates a new input pin collection and adds it to the collection
+ ///
+ /// The type of value the pins of this collection will hold
+ /// The name of the pin collection
+ /// The amount of pins to initially add to the collection
+ /// The resulting input pin collection
+ protected InputPinCollection CreateInputPinCollection(Type type, string name = "", int initialCount = 1)
+ {
+ InputPinCollection pin = new(this, type, name, initialCount);
+ _pinCollections.Add(pin);
+ OnPropertyChanged(nameof(PinCollections));
+ return pin;
+ }
- ///
- /// Removes the provided from the node and it's collection
- ///
- /// The pin to remove
- /// if the pin was removed; otherwise .
- protected bool RemovePin(Pin pin)
+ ///
+ /// Creates a new output pin collection and adds it to the collection
+ ///
+ /// The type of value the pins of this collection will hold
+ /// The name of the pin collection
+ /// The amount of pins to initially add to the collection
+ /// The resulting output pin collection
+ protected OutputPinCollection CreateOutputPinCollection(string name = "", int initialCount = 1)
+ {
+ OutputPinCollection pin = new(this, name, initialCount);
+ _pinCollections.Add(pin);
+ OnPropertyChanged(nameof(PinCollections));
+ return pin;
+ }
+
+ ///
+ /// Removes the provided from the node and it's
+ /// collection
+ ///
+ /// The pin collection to remove
+ /// if the pin collection was removed; otherwise .
+ protected bool RemovePinCollection(PinCollection pinCollection)
+ {
+ bool isRemoved = _pinCollections.Remove(pinCollection);
+ if (isRemoved)
{
- bool isRemoved = _pins.Remove(pin);
- if (isRemoved)
- {
+ foreach (IPin pin in pinCollection)
pin.DisconnectAll();
- OnPropertyChanged(nameof(Pins));
- }
-
- return isRemoved;
- }
-
- ///
- /// Creates a new input pin collection and adds it to the collection
- ///
- /// The type of value the pins of this collection will hold
- /// The name of the pin collection
- /// The amount of pins to initially add to the collection
- /// The resulting input pin collection
- protected InputPinCollection CreateInputPinCollection(string name = "", int initialCount = 1)
- {
- InputPinCollection pin = new(this, name, initialCount);
- _pinCollections.Add(pin);
OnPropertyChanged(nameof(PinCollections));
- return pin;
}
- ///
- /// Creates a new input pin collection and adds it to the collection
- ///
- /// The type of value the pins of this collection will hold
- /// The name of the pin collection
- /// The amount of pins to initially add to the collection
- /// The resulting input pin collection
- protected InputPinCollection CreateInputPinCollection(Type type, string name = "", int initialCount = 1)
- {
- InputPinCollection pin = new(this, type, name, initialCount);
- _pinCollections.Add(pin);
- OnPropertyChanged(nameof(PinCollections));
- return pin;
- }
+ return isRemoved;
+ }
- ///
- /// Creates a new output pin collection and adds it to the collection
- ///
- /// The type of value the pins of this collection will hold
- /// The name of the pin collection
- /// The amount of pins to initially add to the collection
- /// The resulting output pin collection
- protected OutputPinCollection CreateOutputPinCollection(string name = "", int initialCount = 1)
- {
- OutputPinCollection pin = new(this, name, initialCount);
- _pinCollections.Add(pin);
- OnPropertyChanged(nameof(PinCollections));
- return pin;
- }
+ ///
+ public virtual void Initialize(INodeScript script)
+ {
+ }
- ///
- /// Removes the provided from the node and it's
- /// collection
- ///
- /// The pin collection to remove
- /// if the pin collection was removed; otherwise .
- protected bool RemovePinCollection(PinCollection pinCollection)
- {
- bool isRemoved = _pinCollections.Remove(pinCollection);
- if (isRemoved)
- {
- foreach (IPin pin in pinCollection)
- pin.DisconnectAll();
- OnPropertyChanged(nameof(PinCollections));
- }
+ ///
+ public abstract void Evaluate();
- return isRemoved;
- }
+ ///
+ public virtual void Reset()
+ {
+ foreach (IPin pin in _pins)
+ pin.Reset();
+ foreach (IPinCollection pinCollection in _pinCollections)
+ pinCollection.Reset();
- ///
- public virtual void Initialize(INodeScript script)
- {
- }
-
- ///
- public abstract void Evaluate();
-
- ///
- public virtual void Reset()
- {
- foreach (IPin pin in _pins)
- pin.Reset();
- foreach (IPinCollection pinCollection in _pinCollections)
- pinCollection.Reset();
-
- Resetting?.Invoke(this, EventArgs.Empty);
- }
-
- ///
- /// Called whenever the node must show it's custom view model, if , no custom view model is used
- ///
- /// The custom view model, if , no custom view model is used
- public virtual ICustomNodeViewModel? GetCustomViewModel()
- {
- return null;
- }
-
- ///
- /// Serializes the object into a string
- ///
- /// The serialized object
- internal virtual string SerializeStorage()
- {
- return string.Empty;
- }
-
- ///
- /// Deserializes the object and sets it
- ///
- /// The serialized object
- internal virtual void DeserializeStorage(string serialized)
- {
- }
-
- #endregion
+ Resetting?.Invoke(this, EventArgs.Empty);
}
///
- /// Represents a kind of node inside a containing storage value of type
- /// .
+ /// Called whenever the node must show it's custom view model, if , no custom view model is used
///
- /// The type of value the node stores
- public abstract class Node : Node
+ ///
+ /// The custom view model, if , no custom view model is used
+ public virtual ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript)
{
- private TStorage? _storage;
+ return null;
+ }
- ///
- protected Node()
- {
- }
+ ///
+ /// Serializes the object into a string
+ ///
+ /// The serialized object
+ internal virtual string SerializeStorage()
+ {
+ return string.Empty;
+ }
- ///
- protected Node(string name, string description) : base(name, description)
- {
- }
+ ///
+ /// Deserializes the object and sets it
+ ///
+ /// The serialized object
+ internal virtual void DeserializeStorage(string serialized)
+ {
+ }
- ///
- /// Gets or sets the storage object of this node, this is saved across sessions
- ///
- public TStorage? Storage
- {
- get => _storage;
- set => SetAndNotify(ref _storage, value);
- }
+ #endregion
+}
- internal override string SerializeStorage()
- {
- return CoreJson.SerializeObject(Storage, true);
- }
+///
+/// Represents a kind of node inside a containing storage value of type
+/// .
+///
+/// The type of value the node stores
+public abstract class Node : Node
+{
+ private TStorage? _storage;
- internal override void DeserializeStorage(string serialized)
+ ///
+ protected Node()
+ {
+ }
+
+ ///
+ protected Node(string name, string description) : base(name, description)
+ {
+ }
+
+ ///
+ /// Gets or sets the storage object of this node, this is saved across sessions
+ ///
+ public TStorage? Storage
+ {
+ get => _storage;
+ set
{
- Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage);
+ if (SetAndNotify(ref _storage, value))
+ StorageModified?.Invoke(this, EventArgs.Empty);
}
}
///
- /// Represents a kind of node inside a containing storage value of type
- /// and a view model of type .
+ /// Occurs whenever the storage of this node was modified.
///
- /// The type of value the node stores
- /// The type of view model the node uses
- public abstract class Node : Node where TViewModel : ICustomNodeViewModel
+ public event EventHandler? StorageModified;
+
+ internal override string SerializeStorage()
{
- ///
- protected Node()
- {
- }
+ return CoreJson.SerializeObject(Storage, true);
+ }
- ///
- protected Node(string name, string description) : base(name, description)
- {
- }
+ internal override void DeserializeStorage(string serialized)
+ {
+ Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage);
+ }
+}
- [Inject]
- internal IKernel Kernel { get; set; } = null!;
+///
+/// Represents a kind of node inside a containing storage value of type
+/// and a view model of type .
+///
+/// The type of value the node stores
+/// The type of view model the node uses
+public abstract class Node : Node where TViewModel : ICustomNodeViewModel
+{
+ ///
+ protected Node()
+ {
+ }
- ///
- /// Called when a view model is required
- ///
- public virtual TViewModel GetViewModel()
- {
- return Kernel.Get(new ConstructorArgument("node", this));
- }
+ ///
+ protected Node(string name, string description) : base(name, description)
+ {
+ }
- ///
- public override ICustomNodeViewModel GetCustomViewModel()
- {
- return GetViewModel();
- }
+ [Inject]
+ internal IKernel Kernel { get; set; } = null!;
+
+ ///
+ /// Called when a view model is required
+ ///
+ ///
+ public virtual TViewModel GetViewModel(NodeScript nodeScript)
+ {
+ return Kernel.Get(new ConstructorArgument("node", this), new ConstructorArgument("script", nodeScript));
+ }
+
+ ///
+ ///
+ public override ICustomNodeViewModel? GetCustomViewModel(NodeScript nodeScript)
+ {
+ return GetViewModel(nodeScript);
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/UpdateStorage.cs b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/UpdateStorage.cs
new file mode 100644
index 000000000..8444d5c02
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Services/NodeEditor/Commands/UpdateStorage.cs
@@ -0,0 +1,72 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
+
+///
+/// Represents a node editor command that can be used to update the storage value of a node.
+///
+/// The type of value the node stores
+public class UpdateStorage : INodeEditorCommand, IDisposable
+{
+ private readonly Node _node;
+ private readonly TStorage? _originalValue;
+ private readonly TStorage? _value;
+ private readonly string _valueDescription;
+ private bool _updatedValue;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The node to update.
+ /// The new value of the node.
+ /// The description of the value that was updated.
+ public UpdateStorage(Node node, TStorage? value, string valueDescription = "value")
+ {
+ _node = node;
+ _value = value;
+ _valueDescription = valueDescription;
+
+ _originalValue = _node.Storage;
+ }
+
+ #region Implementation of INodeEditorCommand
+
+ ///
+ public string DisplayName => $"Update node {_valueDescription}";
+
+ ///
+ public void Execute()
+ {
+ _updatedValue = true;
+ _node.Storage = _value;
+ }
+
+ ///
+ public void Undo()
+ {
+ _updatedValue = false;
+ _node.Storage = _originalValue;
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ ///
+ public void Dispose()
+ {
+ if (_updatedValue)
+ {
+ if (_originalValue is IDisposable disposable)
+ disposable.Dispose();
+ }
+ else
+ {
+ if (_value is IDisposable disposable)
+ disposable.Dispose();
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs b/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs
index dcff99245..211ab8517 100644
--- a/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs
+++ b/src/Avalonia/Artemis.UI.Shared/VisualScripting/CustomNodeViewModel.cs
@@ -4,46 +4,54 @@ using System.Reactive.Disposables;
using Artemis.Core;
using ReactiveUI;
-namespace Artemis.UI.Shared.VisualScripting
+namespace Artemis.UI.Shared.VisualScripting;
+
+///
+/// Represents a custom view model for a node
+///
+public abstract class CustomNodeViewModel : ActivatableViewModelBase, ICustomNodeViewModel
{
- public abstract class CustomNodeViewModel : ActivatableViewModelBase, ICustomNodeViewModel
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The node the view model is for.
+ /// The script the node is contained in.
+ protected CustomNodeViewModel(INode node, INodeScript script)
{
- protected CustomNodeViewModel(INode node)
+ Node = node;
+ Script = script;
+
+ this.WhenActivated(d =>
{
- Node = node;
-
- this.WhenActivated(d =>
- {
- Node.PropertyChanged += NodeOnPropertyChanged;
- Disposable.Create(() => Node.PropertyChanged -= NodeOnPropertyChanged).DisposeWith(d);
- });
- }
-
- public INode Node { get; }
-
- #region Events
-
- ///
- public event EventHandler NodeModified;
-
- ///
- /// Invokes the event
- ///
- protected virtual void OnNodeModified()
- {
- NodeModified?.Invoke(this, EventArgs.Empty);
- }
-
- #endregion
-
- #region Event handlers
-
- private void NodeOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if (e.PropertyName == "Storage")
- OnNodeModified();
- }
-
- #endregion
+ Node.PropertyChanged += NodeOnPropertyChanged;
+ Disposable.Create(() => Node.PropertyChanged -= NodeOnPropertyChanged).DisposeWith(d);
+ });
}
+
+ ///
+ /// Gets the node the view model is for.
+ ///
+ public INode Node { get; }
+
+ ///
+ /// Gets script the node is contained in.
+ ///
+ public INodeScript Script { get; }
+
+ ///
+ /// Invokes the event
+ ///
+ protected virtual void OnNodeModified()
+ {
+ NodeModified?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void NodeOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Storage")
+ OnNodeModified();
+ }
+
+ ///
+ public event EventHandler? NodeModified;
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
index 99b3892ea..9dbe70cd0 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
@@ -8,6 +8,7 @@ using Artemis.Core.Events;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia;
@@ -21,15 +22,17 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptViewModel : ActivatableViewModelBase
{
private readonly INodeEditorService _nodeEditorService;
+ private readonly INotificationService _notificationService;
private readonly SourceList _nodeViewModels;
private readonly INodeVmFactory _nodeVmFactory;
private DragCableViewModel? _dragViewModel;
private List? _initialNodeSelection;
- public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
+ public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, INotificationService notificationService)
{
_nodeVmFactory = nodeVmFactory;
_nodeEditorService = nodeEditorService;
+ _notificationService = notificationService;
_nodeViewModels = new SourceList();
NodeScript = nodeScript;
@@ -44,6 +47,13 @@ public class NodeScriptViewModel : ActivatableViewModelBase
Observable.FromEventPattern>(x => NodeScript.NodeRemoved += x, x => NodeScript.NodeRemoved -= x)
.Subscribe(e => HandleNodeRemoved(e.EventArgs))
.DisposeWith(d);
+
+ nodeEditorService.GetHistory(NodeScript).Undo
+ .Subscribe(c => _notificationService.CreateNotification().WithMessage(c != null ? $"Undid {c.DisplayName}" : "Nothing to undo").Show())
+ .DisposeWith(d);
+ nodeEditorService.GetHistory(NodeScript).Redo
+ .Subscribe(c => _notificationService.CreateNotification().WithMessage(c != null ? $"Redid {c.DisplayName}" : "Nothing to redo").Show())
+ .DisposeWith(d);
});
// Create VMs for all nodes
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
index 8da647b8c..e26d17a2b 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
@@ -113,7 +113,7 @@ public class NodeViewModel : ActivatableViewModelBase
})).DisposeWith(d);
if (Node is Node coreNode)
- CustomNodeViewModel = coreNode.GetCustomViewModel();
+ CustomNodeViewModel = coreNode.GetCustomViewModel(nodeScriptViewModel.NodeScript);
});
}
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs
index a71435a36..8e0fc74e3 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Color/CustomViewModels/StaticSKColorValueNodeCustomViewModel.cs
@@ -1,10 +1,11 @@
-using Artemis.UI.Shared.VisualScripting;
+using Artemis.Core;
+using Artemis.UI.Shared.VisualScripting;
namespace Artemis.VisualScripting.Nodes.Color.CustomViewModels;
public class StaticSKColorValueNodeCustomViewModel : CustomNodeViewModel
{
- public StaticSKColorValueNodeCustomViewModel(StaticSKColorValueNode node) : base(node)
+ public StaticSKColorValueNodeCustomViewModel(StaticSKColorValueNode node, INodeScript script) : base(node, script)
{
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs
index 54d8bbc7e..f5874a6c3 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/EnumEqualsNodeCustomViewModel.cs
@@ -10,7 +10,7 @@ public class EnumEqualsNodeCustomViewModel : CustomNodeViewModel
{
private readonly EnumEqualsNode _node;
- public EnumEqualsNodeCustomViewModel(EnumEqualsNode node) : base(node)
+ public EnumEqualsNodeCustomViewModel(EnumEqualsNode node, INodeScript script) : base(node, script)
{
_node = node;
}
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs
index f11a6ab08..d5ebe03ef 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs
@@ -11,7 +11,7 @@ public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel
private RenderProfileElement _selectedProfileElement;
- public LayerPropertyNodeCustomViewModel(LayerPropertyNode node) : base(node)
+ public LayerPropertyNodeCustomViewModel(LayerPropertyNode node, INodeScript script) : base(node, script)
{
_node = node;
}
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
index 271e3d4dc..256681acb 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
@@ -5,14 +5,14 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels;
public class StaticNumericValueNodeCustomViewModel : CustomNodeViewModel
{
- public StaticNumericValueNodeCustomViewModel(INode node) : base(node)
+ public StaticNumericValueNodeCustomViewModel(INode node, INodeScript script) : base(node, script)
{
}
}
public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel
{
- public StaticStringValueNodeCustomViewModel(INode node) : base(node)
+ public StaticStringValueNodeCustomViewModel(INode node, INodeScript script) : base(node, script)
{
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs
index 8ebb713a4..e0a1b8a34 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelEventNodeCustomViewModel.cs
@@ -1,9 +1,11 @@
using System.Collections.ObjectModel;
-using System.ComponentModel;
using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
+using Artemis.Storage.Entities.Profile;
+using Artemis.UI.Shared.Services.NodeEditor;
+using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.VisualScripting;
using ReactiveUI;
@@ -12,29 +14,40 @@ namespace Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
public class DataModelEventNodeCustomViewModel : CustomNodeViewModel
{
private readonly DataModelEventNode _node;
+ private readonly INodeEditorService _nodeEditorService;
+ private bool _updating;
+ private DataModelPath? _dataModelPath;
private ObservableCollection? _modules;
- public DataModelEventNodeCustomViewModel(DataModelEventNode node, ISettingsService settingsService) : base(node)
+ public DataModelEventNodeCustomViewModel(DataModelEventNode node, INodeScript script, ISettingsService settingsService, INodeEditorService nodeEditorService) : base(node, script)
{
_node = node;
+ _nodeEditorService = nodeEditorService;
ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
+ Modules = new ObservableCollection();
this.WhenActivated(d =>
{
- if (Modules != null)
- return;
+ // Set up extra modules
+ if (_node.Script?.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
+ Modules = new ObservableCollection {scriptProfile.Configuration.Module};
+ else if (_node.Script?.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
+ Modules = new ObservableCollection {profileConfiguration.Module};
- Modules = new ObservableCollection();
- if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
- Modules.Add(scriptProfile.Configuration.Module);
- else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
- Modules.Add(profileConfiguration.Module);
+ // Subscribe to node changes
+ _node.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d);
+ UpdateDataModelPath(_node.Storage);
- _node.PropertyChanged += NodeOnPropertyChanged;
- Disposable.Create(() => _node.PropertyChanged -= NodeOnPropertyChanged).DisposeWith(d);
+ Disposable.Create(() =>
+ {
+ _dataModelPath?.Dispose();
+ _dataModelPath = null;
+ }).DisposeWith(d);
});
+
+ this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath);
}
public PluginSetting ShowFullPaths { get; }
@@ -47,25 +60,48 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel
set => RaiseAndSetIfChanged(ref _modules, value);
}
- public DataModelPath DataModelPath
+ public DataModelPath? DataModelPath
{
- get => _node.DataModelPath;
- set
+ get => _dataModelPath;
+ set => RaiseAndSetIfChanged(ref _dataModelPath, value);
+ }
+
+ private void UpdateDataModelPath(DataModelPathEntity? entity)
+ {
+ try
{
- if (ReferenceEquals(_node.DataModelPath, value))
+ if (_updating)
return;
- _node.DataModelPath?.Dispose();
- _node.DataModelPath = value;
- _node.DataModelPath.Save();
+ _updating = true;
- _node.Storage = _node.DataModelPath.Entity;
+ DataModelPath? old = DataModelPath;
+ DataModelPath = entity != null ? new DataModelPath(entity) : null;
+ old?.Dispose();
+ }
+ finally
+ {
+ _updating = false;
}
}
- private void NodeOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ private void ApplyDataModelPath(DataModelPath? path)
{
- if (e.PropertyName == nameof(DataModelNode.DataModelPath))
- this.RaisePropertyChanged(nameof(DataModelPath));
+ try
+ {
+ if (_updating)
+ return;
+ if (path?.Path == _node.Storage?.Path)
+ return;
+
+ _updating = true;
+
+ path?.Save();
+ _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, path?.Entity, "event"));
+ }
+ finally
+ {
+ _updating = false;
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs
index ead1825d2..089121775 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViewModels/DataModelNodeCustomViewModel.cs
@@ -4,6 +4,9 @@ using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
+using Artemis.Storage.Entities.Profile;
+using Artemis.UI.Shared.Services.NodeEditor;
+using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.VisualScripting;
using ReactiveUI;
@@ -12,29 +15,39 @@ namespace Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
public class DataModelNodeCustomViewModel : CustomNodeViewModel
{
private readonly DataModelNode _node;
+ private readonly INodeEditorService _nodeEditorService;
private ObservableCollection? _modules;
+ private DataModelPath? _dataModelPath;
+ private bool _updating;
- public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService) : base(node)
+ public DataModelNodeCustomViewModel(DataModelNode node, INodeScript script, ISettingsService settingsService, INodeEditorService nodeEditorService) : base(node, script)
{
_node = node;
+ _nodeEditorService = nodeEditorService;
ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
this.WhenActivated(d =>
{
- if (Modules != null)
- return;
-
- Modules = new ObservableCollection();
+ // Set up extra modules
if (_node.Script?.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
- Modules.Add(scriptProfile.Configuration.Module);
+ Modules = new ObservableCollection {scriptProfile.Configuration.Module};
else if (_node.Script?.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
- Modules.Add(profileConfiguration.Module);
+ Modules = new ObservableCollection {profileConfiguration.Module};
- _node.PropertyChanged += NodeOnPropertyChanged;
- Disposable.Create(() => _node.PropertyChanged -= NodeOnPropertyChanged).DisposeWith(d);
+ // Subscribe to node changes
+ _node.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d);
+ UpdateDataModelPath(_node.Storage);
+
+ Disposable.Create(() =>
+ {
+ _dataModelPath?.Dispose();
+ _dataModelPath = null;
+ }).DisposeWith(d);
});
+
+ this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath);
}
public PluginSetting ShowFullPaths { get; }
@@ -48,24 +61,46 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel
public DataModelPath? DataModelPath
{
- get => _node.DataModelPath;
- set
+ get => _dataModelPath;
+ set => RaiseAndSetIfChanged(ref _dataModelPath, value);
+ }
+
+ private void UpdateDataModelPath(DataModelPathEntity? entity)
+ {
+ try
{
- if (ReferenceEquals(_node.DataModelPath, value))
+ if (_updating)
return;
- _node.DataModelPath?.Dispose();
- _node.DataModelPath = value;
- _node.DataModelPath?.Save();
+ _updating = true;
- _node.Storage = _node.DataModelPath?.Entity;
- _node.UpdateOutputPin();
+ DataModelPath? old = DataModelPath;
+ DataModelPath = entity != null ? new DataModelPath(entity) : null;
+ old?.Dispose();
+ }
+ finally
+ {
+ _updating = false;
}
}
- private void NodeOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ private void ApplyDataModelPath(DataModelPath? path)
{
- if (e.PropertyName == nameof(DataModelNode.DataModelPath))
- this.RaisePropertyChanged(nameof(DataModelPath));
+ try
+ {
+ if (_updating)
+ return;
+ if (path?.Path == _node.Storage?.Path)
+ return;
+
+ _updating = true;
+
+ path?.Save();
+ _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, path?.Entity, "path"));
+ }
+ finally
+ {
+ _updating = false;
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.axaml.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.axaml.cs
index 18a8d72de..c6cdd09f3 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.axaml.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelEventNodeCustomView.axaml.cs
@@ -1,10 +1,12 @@
+using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.DataModel.CustomViews
{
- public partial class DataModelEventNodeCustomView : UserControl
+ public partial class DataModelEventNodeCustomView : ReactiveUserControl
{
public DataModelEventNodeCustomView()
{
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.axaml.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.axaml.cs
index 6fe945575..4138d3c5e 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.axaml.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/CustomViews/DataModelNodeCustomView.axaml.cs
@@ -1,10 +1,12 @@
+using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.DataModel.CustomViews
{
- public partial class DataModelNodeCustomView : UserControl
+ public partial class DataModelNodeCustomView : ReactiveUserControl
{
public DataModelNodeCustomView()
{
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs
index 3638a10cd..3279e01ab 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs
@@ -1,4 +1,5 @@
-using Artemis.Core;
+using System.ComponentModel;
+using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
@@ -24,6 +25,9 @@ public class DataModelEventNode : Node UpdateDataModelPath();
}
public INodeScript? Script { get; set; }
@@ -31,12 +35,6 @@ public class DataModelEventNode : Node _dataModelPath;
- set => SetAndNotify(ref _dataModelPath, value);
- }
-
public override void Initialize(INodeScript script)
{
Script = script;
@@ -44,13 +42,13 @@ public class DataModelEventNode : Node _lastTrigger)
{
@@ -111,6 +109,14 @@ public class DataModelEventNode : Node
public void Dispose()
{
+ _dataModelPath?.Dispose();
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs
index 5aa8d9b0e..c2204ed9d 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs
@@ -1,4 +1,5 @@
-using Artemis.Core;
+using System.ComponentModel;
+using Artemis.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.DataModel.CustomViewModels;
using Avalonia.Threading;
@@ -13,17 +14,12 @@ public class DataModelNode : Node UpdateDataModelPath();
}
public INodeScript? Script { get; private set; }
public OutputPin Output { get; }
-
- public DataModelPath? DataModelPath
- {
- get => _dataModelPath;
- set => SetAndNotify(ref _dataModelPath, value);
- }
-
+
public override void Initialize(INodeScript script)
{
Script = script;
@@ -31,18 +27,26 @@ public class DataModelNode : Node
public void Dispose()
{
- DataModelPath?.Dispose();
+ _dataModelPath?.Dispose();
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs
index ae4a1de1f..996063499 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViewModels/EasingTypeNodeCustomViewModel.cs
@@ -9,7 +9,7 @@ public class EasingTypeNodeCustomViewModel : CustomNodeViewModel
private readonly EasingTypeNode _node;
private NodeEasingViewModel _selectedEasingViewModel;
- public EasingTypeNodeCustomViewModel(EasingTypeNode node) : base(node)
+ public EasingTypeNodeCustomViewModel(EasingTypeNode node, INodeScript script) : base(node, script)
{
_node = node;
EasingViewModels = new ObservableCollection(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(e => new NodeEasingViewModel(e)));
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs
index 4bbe43396..ab443be24 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Maths/CustomViewModels/MathExpressionNodeCustomViewModel.cs
@@ -5,7 +5,7 @@ namespace Artemis.VisualScripting.Nodes.Maths.CustomViewModels;
public class MathExpressionNodeCustomViewModel : CustomNodeViewModel
{
- public MathExpressionNodeCustomViewModel(INode node) : base(node)
+ public MathExpressionNodeCustomViewModel(INode node, INodeScript script) : base(node, script)
{
}
}
\ No newline at end of file