Browse Source

Constants WIP

CPKreuz 1 year ago
parent
commit
271c2d579d
23 changed files with 356 additions and 0 deletions
  1. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateConstant_ChangeInfo.cs
  2. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/DeleteConstant_ChangeInfo.cs
  3. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/UpdateConstantValue_ChangeInfo.cs
  4. 10 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/GraphConstant.cs
  5. 10 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/IReadOnlyGraphConstant.cs
  6. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs
  7. 23 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  8. 41 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ConstantNode.cs
  9. 39 0
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateConstantNode_Change.cs
  10. 35 0
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateGraphConstant_Change.cs
  11. 35 0
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateGraphConstantValue_Change.cs
  12. 19 0
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  13. 1 0
      src/PixiEditor/Models/Handlers/INodeGraphHandler.cs
  14. 4 0
      src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs
  15. 1 0
      src/PixiEditor/Styles/PixiEditor.Controls.axaml
  16. 30 0
      src/PixiEditor/Styles/Templates/NodeConstantManager.axaml
  17. 2 0
      src/PixiEditor/Styles/Templates/NodeGraphView.axaml
  18. 12 0
      src/PixiEditor/ViewModels/Document/INodeGraphConstantHandler.cs
  19. 22 0
      src/PixiEditor/ViewModels/Document/NodeGraphConstantViewModel.cs
  20. 16 0
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  21. 9 0
      src/PixiEditor/ViewModels/Document/Nodes/ConstantNodeViewModel.cs
  22. 18 0
      src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs
  23. 19 0
      src/PixiEditor/Views/Nodes/NodeConstantManager.cs

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/CreateConstant_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record CreateConstant_ChangeInfo(Guid Id, Type Type) : IChangeInfo;

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/DeleteConstant_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record DeleteConstant_ChangeInfo(Guid Id) : IChangeInfo;

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/NodeGraph/UpdateConstantValue_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+public record UpdateConstantValue_ChangeInfo(Guid Id, object Value) : IChangeInfo;

+ 10 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/GraphConstant.cs

@@ -0,0 +1,10 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+internal class GraphConstant(Guid id, Type type) : IReadOnlyGraphConstant
+{
+    public Guid Id { get; set; } = id;
+    
+    public object Value { get; set; }
+
+    public Type Type { get; set; } = type;
+}

+ 10 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/IReadOnlyGraphConstant.cs

@@ -0,0 +1,10 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public interface IReadOnlyGraphConstant
+{
+    public Guid Id { get; }
+    
+    public object? Value { get; }
+    
+    public Type Type { get; }
+}

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNodeGraph.cs

@@ -6,6 +6,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 public interface IReadOnlyNodeGraph
 {
     public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
+    public IReadOnlyCollection<IReadOnlyGraphConstant> Constants { get; }
     public IReadOnlyNode OutputNode { get; }
     public void AddNode(IReadOnlyNode node);
     public void RemoveNode(IReadOnlyNode node);

+ 23 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -10,7 +10,10 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 public class NodeGraph : IReadOnlyNodeGraph, IDisposable
 {
     private readonly List<Node> _nodes = new();
+    private readonly List<GraphConstant> _constants = new();
+    
     public IReadOnlyCollection<Node> Nodes => _nodes;
+    public IReadOnlyCollection<IReadOnlyGraphConstant> Constants => _constants;
     public OutputNode? OutputNode => Nodes.OfType<OutputNode>().FirstOrDefault();
 
     IReadOnlyCollection<IReadOnlyNode> IReadOnlyNodeGraph.AllNodes => Nodes;
@@ -36,6 +39,26 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
         _nodes.Remove(node);
     }
 
+    internal void AddConstant(GraphConstant constant)
+    {
+        if (_constants.Contains(constant))
+            return;
+        
+        _constants.Add(constant);
+    }
+
+    internal void RemoveConstant(Guid id) => RemoveConstant(_constants.FirstOrDefault(x => x.Id == id));
+
+    internal void RemoveConstant(GraphConstant? constant) => _constants.Remove(constant);
+
+    internal void UpdateConstantValue(Guid id, object value, out object oldValue)
+    {
+        var constant = _constants.First(x => x.Id == id);
+
+        oldValue = constant.Value;
+        constant.Value = value;
+    }
+
     public Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode outputNode)
     {
         var finalQueue = new HashSet<IReadOnlyNode>();

+ 41 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ConstantNode.cs

@@ -0,0 +1,41 @@
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core;
+using PixiEditor.DrawingApi.Core.Shaders.Generation.Expressions;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[NodeInfo("PixiEditor.Constant")]
+public class ConstantNode : Node
+{
+    private IReadOnlyGraphConstant _constant;
+
+    public Guid ConstantId => _constant.Id;
+
+    internal ConstantNode(IReadOnlyGraphConstant constant)
+    {
+        _constant = constant;
+
+        Value = CreateFuncOutput("Value", "VALUE", _ => GetValue());
+    }
+
+    public FuncOutputProperty<Float1> Value { get; set; }
+
+    protected override Texture? OnExecute(RenderingContext context) => null;
+
+    public override Node CreateCopy() => new ConstantNode(_constant);
+
+    private Float1 GetValue()
+    {
+        if (_constant.Value == null)
+        {
+            return 0;
+        }
+
+        return (double)_constant.Value;
+    }
+    
+    public static object GetDefault(Type type)
+    {
+        return type.IsValueType ? Activator.CreateInstance(type) : null;
+    }
+}

+ 39 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateConstantNode_Change.cs

@@ -0,0 +1,39 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class CreateConstantNode_Change : Change
+{
+    public Guid NodeId { get; }
+    
+    public Guid ConstantId { get; }
+ 
+    [GenerateMakeChangeAction]
+    public CreateConstantNode_Change(Guid nodeId, Guid constantId)
+    {
+        NodeId = nodeId;
+        ConstantId = constantId;
+    }
+
+    public override bool InitializeAndValidate(Document target) => true;
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        var constant = target.NodeGraph.Constants.First(x => x.Id == ConstantId);
+        var node = new ConstantNode(constant) { Id = NodeId };
+
+        target.NodeGraph.AddNode(node);
+        
+        ignoreInUndo = false;
+        return CreateNode_ChangeInfo.CreateFromNode(node);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        Node node = target.FindNodeOrThrow<Node>(NodeId);
+        target.NodeGraph.RemoveNode(node);
+
+        return new DeleteNode_ChangeInfo(NodeId);
+    }
+}

+ 35 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateGraphConstant_Change.cs

@@ -0,0 +1,35 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class CreateGraphConstant_Change : Change
+{
+    private Guid Id { get; }
+    
+    private Type Type { get; }
+
+    [GenerateMakeChangeAction]
+    public CreateGraphConstant_Change(Guid id, Type type)
+    {
+        Id = id;
+        Type = type;
+    }
+
+    public override bool InitializeAndValidate(Document target) => true;
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.NodeGraph.AddConstant(new GraphConstant(Id, Type));
+
+        ignoreInUndo = false;
+        return new CreateConstant_ChangeInfo(Id, Type);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.NodeGraph.RemoveConstant(Id);
+        
+        return new DeleteConstant_ChangeInfo(Id);
+    }
+}

+ 35 - 0
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateGraphConstantValue_Change.cs

@@ -0,0 +1,35 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
+
+namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
+
+internal class UpdateGraphConstantValue_Change : Change
+{
+    private object oldValue;
+    
+    public Guid Id { get; }
+    
+    public object Value { get; }
+    
+    [GenerateMakeChangeAction]
+    public UpdateGraphConstantValue_Change(Guid id, object value)
+    {
+        Id = id;
+        Value = value;
+    }
+
+    public override bool InitializeAndValidate(Document target) => true;
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        if (firstApply)
+            target.NodeGraph.UpdateConstantValue(Id, Value, out oldValue);
+
+        ignoreInUndo = false;
+        return new UpdateConstantValue_ChangeInfo(Id, Value);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        return new UpdateConstantValue_ChangeInfo(Id, oldValue);
+    }
+}

+ 19 - 0
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -199,6 +199,12 @@ internal class DocumentUpdater
             case SetPlayingState_PassthroughAction info:
                 ProcessPlayAnimation(info);
                 break;
+            case CreateConstant_ChangeInfo info:
+                ProcessCreateConstant(info);
+                break;
+            case UpdateConstantValue_ChangeInfo info:
+                ProcessUpdateConstantValue(info);
+                break;
         }
     }
 
@@ -674,4 +680,17 @@ internal class DocumentUpdater
     {
         doc.AnimationHandler.SetOnionFrames(info.OnionFrames, info.Opacity);
     }
+    
+    
+    private void ProcessCreateConstant(CreateConstant_ChangeInfo info)
+    {
+        doc.NodeGraphHandler.Constants.Add(new NodeGraphConstantViewModel(info.Id, info.Type));
+    }
+
+    private void ProcessUpdateConstantValue(UpdateConstantValue_ChangeInfo info)
+    {
+        var constant = doc.NodeGraphHandler.Constants.FirstOrDefault(x => x.Id == info.Id);
+        
+        constant.SetValueInternal(info.Value);
+    }
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/INodeGraphHandler.cs

@@ -7,6 +7,7 @@ namespace PixiEditor.Models.Handlers;
 internal interface INodeGraphHandler
 {
    public ObservableCollection<INodeHandler> AllNodes { get; }
+   public ObservableCollection<NodeGraphConstantViewModel> Constants { get; }
    public ObservableCollection<NodeConnectionViewModel> Connections { get; }
    public ObservableCollection<NodeFrameViewModelBase> Frames { get; }
    public INodeHandler OutputNode { get; }

+ 4 - 0
src/PixiEditor/Models/Rendering/AffectedAreasGatherer.cs

@@ -156,6 +156,10 @@ internal class AffectedAreasGatherer
                 case OnionFrames_ChangeInfo:
                     AddWholeCanvasToMainImage();
                     break;
+                case UpdateConstantValue_ChangeInfo:
+                    AddWholeCanvasToMainImage();
+                    AddWholeCanvasToEveryImagePreview();
+                    break;
             }
         }
     }

+ 1 - 0
src/PixiEditor/Styles/PixiEditor.Controls.axaml

@@ -15,6 +15,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/TimelineGroupHeader.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeFrameView.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeGraphView.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeConstantManager.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeView.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/NodeSocket.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor/Styles/Templates/ConnectionView.axaml"/>

+ 30 - 0
src/PixiEditor/Styles/Templates/NodeConstantManager.axaml

@@ -0,0 +1,30 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
+                    xmlns:nodes="clr-namespace:PixiEditor.Views.Nodes"
+                    xmlns:document="clr-namespace:PixiEditor.ViewModels.Document"
+                    xmlns:input="clr-namespace:PixiEditor.Views.Input">
+    <ControlTheme TargetType="nodes:NodeConstantManager" x:Key="{x:Type nodes:NodeConstantManager}">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Grid RowDefinitions="50,*">
+                    <Button Command="{xaml:Command PixiEditor.NodeGraph.CreateConstant}">Add</Button>
+                    <ItemsControl Grid.Row="1" ItemsSource="{Binding Constants, RelativeSource={RelativeSource TemplatedParent}}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate DataType="document:INodeGraphConstantHandler">
+                                <StackPanel Orientation="Horizontal">
+                                    <TextBlock Text="{Binding NameBindable}"/>
+                                    <input:NumberInput Value="{Binding ValueBindable, Mode=TwoWay}" />
+                                    <Button
+                                        Content="Node"
+                                        Command="{xaml:Command PixiEditor.NodeGraph.CreateConstantNode, UseProvided=True}"
+                                        CommandParameter="{Binding Id}"/>
+                                </StackPanel>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </Grid>
+            </ControlTemplate>
+        </Setter>
+    </ControlTheme>
+</ResourceDictionary>

+ 2 - 0
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -16,6 +16,8 @@
                                 />
                         </Flyout>
                         </Grid.ContextFlyout>
+                    <nodes:NodeConstantManager ZIndex="2" Width="400" Height="800" HorizontalAlignment="Left"
+                                               Constants="{Binding NodeGraph.Constants, RelativeSource={RelativeSource TemplatedParent}}"/>
                         <ItemsControl ZIndex="1" ClipToBounds="False"
                                       ItemsSource="{Binding NodeGraph.AllNodes, RelativeSource={RelativeSource TemplatedParent}}">
                             <ItemsControl.ItemsPanel>

+ 12 - 0
src/PixiEditor/ViewModels/Document/INodeGraphConstantHandler.cs

@@ -0,0 +1,12 @@
+namespace PixiEditor.ViewModels.Document;
+
+public interface INodeGraphConstantHandler
+{
+    public Guid Id { get; }
+    
+    public string NameBindable { get; }
+    
+    public object ValueBindable { get; set; }
+    
+    public Type Type { get; }
+}

+ 22 - 0
src/PixiEditor/ViewModels/Document/NodeGraphConstantViewModel.cs

@@ -0,0 +1,22 @@
+using PixiEditor.ViewModels.SubViewModels;
+
+namespace PixiEditor.ViewModels.Document;
+
+internal class NodeGraphConstantViewModel(Guid id, Type type) : ViewModelBase, INodeGraphConstantHandler
+{
+    private object valueBindable;
+
+    public Guid Id { get; } = id;
+
+    public string NameBindable => Id.ToString()[..5];
+
+    public Type Type { get; } = type;
+
+    public object ValueBindable
+    {
+        get => valueBindable;
+        set => ViewModelMain.Current.NodeGraphManager.UpdateConstantValue((this, value));
+    }
+
+    public void SetValueInternal(object value) => SetProperty(ref valueBindable, value, nameof(ValueBindable));
+}

+ 16 - 0
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -20,6 +20,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 {
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
+    public ObservableCollection<NodeGraphConstantViewModel> Constants { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
     public ObservableCollection<NodeFrameViewModelBase> Frames { get; } = new();
     public StructureTree StructureTree { get; } = new();
@@ -219,6 +220,21 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
         Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value));
     }
 
+    public void CreateConstant(Guid id, Type type)
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new CreateGraphConstant_Action(id, type));
+    }
+
+    public void UpdateConstantValue(INodeGraphConstantHandler constant, object value)
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new UpdateGraphConstantValue_Action(constant.Id, value));
+    }
+    
+    public void CreateConstantNode(Guid nodeId, Guid constantId)
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new CreateConstantNode_Action(nodeId, constantId));
+    }
+
     public void EndChangeNodePosition()
     {
         Internals.ActionAccumulator.AddFinishedActions(new EndNodePosition_Action());

+ 9 - 0
src/PixiEditor/ViewModels/Document/Nodes/ConstantNodeViewModel.cs

@@ -0,0 +1,9 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("CONSTANT", "", "", PickerName = "")]
+internal class ConstantNodeViewModel : NodeViewModel<ConstantNode>
+{
+}

+ 18 - 0
src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -5,6 +5,7 @@ using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Numerics;
 using PixiEditor.ViewModels.Dock;
+using PixiEditor.ViewModels.Document;
 
 namespace PixiEditor.ViewModels.SubViewModels;
 
@@ -46,6 +47,12 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.RemoveNodes(selectedNodes);
     }
 
+    [Command.Basic("PixiEditor.NodeGraph.CreateConstant", "CREATE_NODE_CONSTANT", "CREATE_NODE_CONSTANT", ShortcutContext = typeof(NodeGraphDockViewModel), AnalyticsTrack = true)]
+    public void CreateConstant()
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.CreateConstant(Guid.NewGuid(), typeof(double));
+    }
+
     // TODO: Remove this
     [Command.Debug("PixiEditor.NodeGraph.CreateNodeFrameAroundEverything", "Create node frame", "Create node frame", AnalyticsTrack = true)]
     public void CreateNodeFrameAroundEverything()
@@ -84,4 +91,15 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
     {
         Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.EndChangeNodePosition();
     }
+
+    [Command.Internal("PixiEditor.NodeGraph.CreateConstantNode", AnalyticsTrack = true)]
+    public void CreateConstantNode(Guid id)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.CreateConstantNode(Guid.NewGuid(), id);
+    }
+
+    public void UpdateConstantValue((INodeGraphConstantHandler constant, object value) args)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.UpdateConstantValue(args.constant, args.value);
+    }
 }

+ 19 - 0
src/PixiEditor/Views/Nodes/NodeConstantManager.cs

@@ -0,0 +1,19 @@
+using System.Collections.ObjectModel;
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using PixiEditor.ViewModels.Document;
+
+namespace PixiEditor.Views.Nodes;
+
+internal class NodeConstantManager : TemplatedControl
+{
+    
+    public static readonly StyledProperty<ObservableCollection<NodeGraphConstantViewModel>> ConstantsProperty =
+        AvaloniaProperty.Register<NodeConstantManager, ObservableCollection<NodeGraphConstantViewModel>>(nameof(Constants));
+
+    public ObservableCollection<NodeGraphConstantViewModel> Constants
+    {
+        get => GetValue(ConstantsProperty);
+        set => SetValue(ConstantsProperty, value);
+    }
+}