mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Nodes - Reorganized code
This commit is contained in:
1 parent
69b881b044
commit
7c83d5345f
73 files changed
+1055
-738
No files matched your search
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.External.Commands;
|
||||
|
||||
public class UpdateLayerPropertyNodeSelectedLayerProperty : INodeEditorCommand
|
||||
{
|
||||
private readonly LayerPropertyNode _node;
|
||||
private readonly ILayerProperty? _value;
|
||||
private readonly ILayerProperty? _oldValue;
|
||||
private readonly NodeConnectionStore _connections;
|
||||
|
||||
public UpdateLayerPropertyNodeSelectedLayerProperty(LayerPropertyNode node, ILayerProperty? value)
|
||||
{
|
||||
_node = node;
|
||||
_connections = new NodeConnectionStore(_node);
|
||||
|
||||
_value = value;
|
||||
_oldValue = _node.LayerProperty;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update node layer property";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
// Store connections as they currently are
|
||||
_connections.Store();
|
||||
|
||||
// Update the selected profile element
|
||||
_node.ChangeLayerProperty(_value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
// Restore the previous layer property
|
||||
_node.ChangeLayerProperty(_oldValue);
|
||||
|
||||
// Restore connections
|
||||
_connections.Restore();
|
||||
}
|
||||
}
|
||||
src/Artemis.VisualScripting/Nodes/External/Commands/UpdateLayerPropertyNodeSelectedProfileElement.cs
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.External.Commands;
|
||||
|
||||
public class UpdateLayerPropertyNodeSelectedProfileElement : INodeEditorCommand
|
||||
{
|
||||
private readonly LayerPropertyNode _node;
|
||||
private readonly NodeConnectionStore _connections;
|
||||
|
||||
private readonly RenderProfileElement? _value;
|
||||
private readonly RenderProfileElement? _oldValue;
|
||||
private readonly ILayerProperty? _oldLayerProperty;
|
||||
|
||||
public UpdateLayerPropertyNodeSelectedProfileElement(LayerPropertyNode node, RenderProfileElement? value)
|
||||
{
|
||||
_node = node;
|
||||
_connections = new NodeConnectionStore(_node);
|
||||
|
||||
_value = value;
|
||||
_oldValue = node.ProfileElement;
|
||||
_oldLayerProperty = node.LayerProperty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update node profile element";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
// Store connections as they currently are
|
||||
_connections.Store();
|
||||
|
||||
// Update the selected profile element
|
||||
_node.ChangeProfileElement(_value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
// Can't undo it if that profile element is now gone :\
|
||||
if (_oldValue != null && _oldValue.Disposed)
|
||||
return;
|
||||
|
||||
// Restore the previous profile element
|
||||
_node.ChangeProfileElement(_oldValue);
|
||||
|
||||
// Restore the previous layer property
|
||||
_node.ChangeLayerProperty(_oldLayerProperty);
|
||||
|
||||
// Restore connections
|
||||
_connections.Restore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.VisualScripting.Nodes.External.Screens;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.External;
|
||||
|
||||
[Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")]
|
||||
public class LayerPropertyNode : Node<LayerPropertyNodeEntity, LayerPropertyNodeCustomViewModel>
|
||||
{
|
||||
private readonly object _layerPropertyLock = new();
|
||||
private readonly List<OutputPin> _pinBucket = new();
|
||||
|
||||
public LayerPropertyNode() : base("Layer/Folder Property", "Outputs the property of a selected layer or folder")
|
||||
{
|
||||
}
|
||||
|
||||
public INodeScript? Script { get; private set; }
|
||||
public RenderProfileElement? ProfileElement { get; private set; }
|
||||
public ILayerProperty? LayerProperty { get; private set; }
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
lock (_layerPropertyLock)
|
||||
{
|
||||
// In this case remove the pins so no further evaluations occur
|
||||
if (LayerProperty == null)
|
||||
{
|
||||
CreatePins();
|
||||
return;
|
||||
}
|
||||
|
||||
List<IDataBindingProperty> list = LayerProperty.BaseDataBinding.Properties.ToList();
|
||||
int index = 0;
|
||||
foreach (IPin pin in Pins)
|
||||
{
|
||||
OutputPin outputPin = (OutputPin) pin;
|
||||
IDataBindingProperty dataBindingProperty = list[index];
|
||||
index++;
|
||||
|
||||
// TODO: Is this really non-nullable?
|
||||
outputPin.Value = dataBindingProperty.GetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize(INodeScript script)
|
||||
{
|
||||
Script = script;
|
||||
|
||||
if (script.Context is Profile profile)
|
||||
profile.ChildRemoved += ProfileOnChildRemoved;
|
||||
|
||||
LoadLayerProperty();
|
||||
}
|
||||
|
||||
public void LoadLayerProperty()
|
||||
{
|
||||
lock (_layerPropertyLock)
|
||||
{
|
||||
if (Script?.Context is not Profile profile || Storage == null)
|
||||
return;
|
||||
|
||||
RenderProfileElement? element = profile.GetAllRenderElements().FirstOrDefault(l => l.EntityId == Storage.ElementId);
|
||||
|
||||
ProfileElement = element;
|
||||
LayerProperty = element?.GetAllLayerProperties().FirstOrDefault(p => p.Path == Storage.PropertyPath);
|
||||
CreatePins();
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeProfileElement(RenderProfileElement? profileElement)
|
||||
{
|
||||
lock (_layerPropertyLock)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
LayerProperty = profileElement?.GetAllLayerProperties().FirstOrDefault();
|
||||
|
||||
Storage = new LayerPropertyNodeEntity
|
||||
{
|
||||
ElementId = ProfileElement?.EntityId ?? Guid.Empty,
|
||||
PropertyPath = null
|
||||
};
|
||||
|
||||
CreatePins();
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeLayerProperty(ILayerProperty? layerProperty)
|
||||
{
|
||||
lock (_layerPropertyLock)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
|
||||
Storage = new LayerPropertyNodeEntity
|
||||
{
|
||||
ElementId = ProfileElement?.EntityId ?? Guid.Empty,
|
||||
PropertyPath = LayerProperty?.Path
|
||||
};
|
||||
|
||||
CreatePins();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePins()
|
||||
{
|
||||
while (Pins.Any())
|
||||
RemovePin((Pin) Pins.First());
|
||||
|
||||
if (LayerProperty == null)
|
||||
return;
|
||||
|
||||
foreach (IDataBindingProperty dataBindingRegistration in LayerProperty.BaseDataBinding.Properties)
|
||||
CreateOrAddOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or adds an input pin to the node using a bucket.
|
||||
/// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the
|
||||
/// editor.
|
||||
/// </summary>
|
||||
private void CreateOrAddOutputPin(Type valueType, string displayName)
|
||||
{
|
||||
// Grab the first pin from the bucket that isn't on the node yet
|
||||
OutputPin? pin = _pinBucket.FirstOrDefault(p => !Pins.Contains(p));
|
||||
|
||||
// If there is none, create a new one and add it to the bucket
|
||||
if (pin == null)
|
||||
{
|
||||
pin = CreateOutputPin(valueType, displayName);
|
||||
_pinBucket.Add(pin);
|
||||
}
|
||||
// If there was a pin in the bucket, update it's type and display name and reuse it
|
||||
else
|
||||
{
|
||||
pin.ChangeType(valueType);
|
||||
pin.Name = displayName;
|
||||
AddPin(pin);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfileOnChildRemoved(object? sender, EventArgs e)
|
||||
{
|
||||
if (Script?.Context is not Profile profile)
|
||||
return;
|
||||
|
||||
if (ProfileElement == null || !profile.GetAllRenderElements().Contains(ProfileElement))
|
||||
ChangeProfileElement(null);
|
||||
}
|
||||
}
|
||||
|
||||
public class LayerPropertyNodeEntity
|
||||
{
|
||||
public Guid ElementId { get; set; }
|
||||
public string? PropertyPath { get; set; }
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.External.Screens"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.VisualScripting.Nodes.External.Screens.LayerPropertyNodeCustomView"
|
||||
x:DataType="screens:LayerPropertyNodeCustomViewModel">
|
||||
<Grid>
|
||||
<StackPanel Spacing="5" VerticalAlignment="Top" IsVisible="{CompiledBinding !OutsideProfileContext}">
|
||||
<ComboBox Classes="condensed" Items="{CompiledBinding ProfileElements}" SelectedItem="{CompiledBinding SelectedProfileElement}" />
|
||||
<ComboBox Classes="condensed" Items="{CompiledBinding LayerProperties}" SelectedItem="{CompiledBinding SelectedLayerProperty}" />
|
||||
</StackPanel>
|
||||
<StackPanel IsVisible="{CompiledBinding OutsideProfileContext}" VerticalAlignment="Center">
|
||||
<TextBlock Classes="BodyStrongTextBlockStyle" TextAlignment="Center">
|
||||
Node not available
|
||||
</TextBlock>
|
||||
<TextBlock Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}" TextAlignment="Center">
|
||||
This node cannot be used outside profile scripts.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.External.Screens
|
||||
{
|
||||
public partial class LayerPropertyNodeCustomView : ReactiveUserControl<LayerPropertyNodeCustomViewModel>
|
||||
{
|
||||
public LayerPropertyNodeCustomView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
using Artemis.UI.Shared.VisualScripting;
|
||||
using Artemis.VisualScripting.Nodes.External.Commands;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using DynamicData;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.External.Screens;
|
||||
|
||||
public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel
|
||||
{
|
||||
private readonly LayerPropertyNode _node;
|
||||
private readonly INodeEditorService _nodeEditorService;
|
||||
private bool _outsideProfileContext;
|
||||
|
||||
public LayerPropertyNodeCustomViewModel(LayerPropertyNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
|
||||
{
|
||||
_node = node;
|
||||
_nodeEditorService = nodeEditorService;
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
if (_node.Script?.Context is not Profile profile)
|
||||
{
|
||||
OutsideProfileContext = true;
|
||||
return;
|
||||
}
|
||||
|
||||
OutsideProfileContext = false;
|
||||
Observable.FromEventPattern<ProfileElementEventArgs>(x => profile.DescendentAdded += x, x => profile.DescendentAdded -= x).Subscribe(_ => GetProfileElements()).DisposeWith(d);
|
||||
Observable.FromEventPattern<ProfileElementEventArgs>(x => profile.DescendentRemoved += x, x => profile.DescendentRemoved -= x).Subscribe(_ => GetProfileElements()).DisposeWith(d);
|
||||
GetProfileElements();
|
||||
GetLayerProperties();
|
||||
});
|
||||
|
||||
NodeModified += (_, _) => this.RaisePropertyChanged(nameof(SelectedProfileElement));
|
||||
NodeModified += (_, _) => this.RaisePropertyChanged(nameof(SelectedLayerProperty));
|
||||
this.WhenAnyValue(vm => vm.SelectedProfileElement).Subscribe(_ => GetLayerProperties());
|
||||
}
|
||||
|
||||
public ObservableCollection<RenderProfileElement> ProfileElements { get; } = new();
|
||||
public ObservableCollection<ILayerProperty> LayerProperties { get; } = new();
|
||||
|
||||
public bool OutsideProfileContext
|
||||
{
|
||||
get => _outsideProfileContext;
|
||||
set => this.RaiseAndSetIfChanged(ref _outsideProfileContext, value);
|
||||
}
|
||||
|
||||
public RenderProfileElement? SelectedProfileElement
|
||||
{
|
||||
get => _node.ProfileElement;
|
||||
set
|
||||
{
|
||||
if (value != null && !Equals(_node.ProfileElement, value))
|
||||
_nodeEditorService.ExecuteCommand(Script, new UpdateLayerPropertyNodeSelectedProfileElement(_node, value));
|
||||
}
|
||||
}
|
||||
|
||||
public ILayerProperty? SelectedLayerProperty
|
||||
{
|
||||
get => _node.LayerProperty;
|
||||
set
|
||||
{
|
||||
if (value != null && !Equals(_node.LayerProperty, value))
|
||||
_nodeEditorService.ExecuteCommand(Script, new UpdateLayerPropertyNodeSelectedLayerProperty(_node, value));
|
||||
}
|
||||
}
|
||||
|
||||
private void GetProfileElements()
|
||||
{
|
||||
ProfileElements.Clear();
|
||||
if (_node.Script?.Context is not Profile profile)
|
||||
return;
|
||||
|
||||
List<RenderProfileElement> elements = new(profile.GetAllRenderElements());
|
||||
|
||||
ProfileElements.AddRange(elements.OrderBy(e => e.Order));
|
||||
SelectedProfileElement = _node.ProfileElement;
|
||||
}
|
||||
|
||||
private void GetLayerProperties()
|
||||
{
|
||||
LayerProperties.Clear();
|
||||
if (_node.ProfileElement == null)
|
||||
return;
|
||||
|
||||
LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => !l.IsHidden && l.DataBindingsSupported));
|
||||
SelectedLayerProperty = _node.LayerProperty;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user