diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
index 0cf5fc9fb..e2392c79e 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
@@ -242,7 +242,11 @@ namespace Artemis.Core
EasingTime = Entity.EasingTime;
EasingFunction = (Easings.Functions) Entity.EasingFunction;
+ }
+ ///
+ public void LoadNodeScript()
+ {
Script.Dispose();
Script = Entity.NodeScript != null
? new NodeScript(GetScriptName(), "The value to put into the data binding", Entity.NodeScript, LayerProperty.ProfileElement.Profile)
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs
index cab335a43..e7d5ca1d1 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingRegistration.cs
@@ -67,7 +67,7 @@ namespace Artemis.Core
DataBinding = new DataBinding(LayerProperty, dataBinding);
return DataBinding;
}
-
+
///
public void ClearDataBinding()
{
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
index 1f54ca146..3c7c995bd 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs
@@ -19,5 +19,10 @@ namespace Artemis.Core
/// Applies the data binding to the layer property
///
void Apply();
+
+ ///
+ /// If the data binding is enabled, loads the node script for that data binding
+ ///
+ void LoadNodeScript();
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index c0c3a2528..a9090e12d 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -206,12 +206,10 @@ namespace Artemis.Core
}
}
+ List renderElements = GetAllRenderElements();
+
if (ProfileEntity.LastSelectedProfileElement != Guid.Empty)
- {
- LastSelectedProfileElement = GetAllFolders().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
- if (LastSelectedProfileElement == null)
- LastSelectedProfileElement = GetAllLayers().FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
- }
+ LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
else
LastSelectedProfileElement = null;
@@ -219,6 +217,10 @@ namespace Artemis.Core
scriptConfiguration.Script?.Dispose();
ScriptConfigurations.Clear();
ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)));
+
+ // Load node scripts last since they may rely on the profile structure being in place
+ foreach (RenderProfileElement renderProfileElement in renderElements)
+ renderProfileElement.LoadNodeScript();
}
internal override void Save()
@@ -253,10 +255,7 @@ namespace Artemis.Core
///
public override IEnumerable GetBrokenHierarchy()
{
- foreach (IBreakableModel breakableModel in GetAllFolders().SelectMany(folders => folders.GetBrokenHierarchy()))
- yield return breakableModel;
- foreach (IBreakableModel breakableModel in GetAllLayers().SelectMany(layer => layer.GetBrokenHierarchy()))
- yield return breakableModel;
+ return GetAllRenderElements().SelectMany(folders => folders.GetBrokenHierarchy());
}
#endregion
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 81537b0ba..a36dea794 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -99,6 +99,13 @@ namespace Artemis.Core
///
public bool Disposed { get; protected set; }
+ #region Overrides of BreakableModel
+
+ ///
+ public override string BrokenDisplayName => Name ?? GetType().Name;
+
+ #endregion
+
///
/// Updates the element
///
@@ -121,13 +128,6 @@ namespace Artemis.Core
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
}
- #region Overrides of BreakableModel
-
- ///
- public override string BrokenDisplayName => Name ?? GetType().Name;
-
- #endregion
-
#region Hierarchy
///
@@ -147,7 +147,9 @@ namespace Artemis.Core
// Add to the end of the list
if (order == null)
+ {
ChildrenList.Add(child);
+ }
// Insert at the given index
else
{
@@ -191,6 +193,27 @@ namespace Artemis.Core
ChildrenList[index].Order = index;
}
+ ///
+ /// Returns a flattened list of all child render elements
+ ///
+ ///
+ public List GetAllRenderElements()
+ {
+ if (Disposed)
+ throw new ObjectDisposedException(GetType().Name);
+
+ List elements = new();
+ foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast())
+ {
+ // Add all folders in this element
+ elements.Add(childElement);
+ // Add all folders in folders inside this element
+ elements.AddRange(childElement.GetAllRenderElements());
+ }
+
+ return elements;
+ }
+
///
/// Returns a flattened list of all child folders
///
@@ -240,27 +263,6 @@ namespace Artemis.Core
internal abstract void Load();
internal abstract void Save();
- #endregion
-
- #region IDisposable
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Disposes the profile element
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- }
- }
-
#endregion
#region Events
@@ -292,5 +294,26 @@ namespace Artemis.Core
}
#endregion
+
+ #region IDisposable
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes the profile element
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ }
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
index 7003dd985..f6a87f5b0 100644
--- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
@@ -70,10 +70,6 @@ namespace Artemis.Core
internal void LoadRenderElement()
{
- DisplayCondition = RenderElementEntity.NodeScript != null
- ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile)
- : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
-
Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline)
: new Timeline();
@@ -111,6 +107,19 @@ namespace Artemis.Core
Timeline?.Save();
}
+ internal void LoadNodeScript()
+ {
+ DisplayCondition = RenderElementEntity.NodeScript != null
+ ? new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile)
+ : new NodeScript($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
+
+ foreach (ILayerProperty layerProperty in GetAllLayerProperties())
+ {
+ foreach (IDataBindingRegistration dataBindingRegistration in layerProperty.GetAllDataBindingRegistrations())
+ dataBindingRegistration.GetDataBinding()?.LoadNodeScript();
+ }
+ }
+
internal void OnLayerEffectsUpdated()
{
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index 3f532faf3..b6221c19f 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -438,10 +438,8 @@ namespace Artemis.Core.Services
profile.Save();
if (includeChildren)
{
- foreach (Folder folder in profile.GetAllFolders())
- folder.Save();
- foreach (Layer layer in profile.GetAllLayers())
- layer.Save();
+ foreach (RenderProfileElement child in profile.GetAllRenderElements())
+ child.Save();
}
// If there are no changes, don't bother saving
@@ -599,11 +597,9 @@ namespace Artemis.Core.Services
profile.Save();
- foreach (Folder folder in profile.GetAllFolders())
- folder.Save();
- foreach (Layer layer in profile.GetAllLayers())
- layer.Save();
-
+ foreach (RenderProfileElement renderProfileElement in profile.GetAllRenderElements())
+ renderProfileElement.Save();
+
_logger.Debug("Adapt profile - Saving " + profile);
profile.RedoStack.Clear();
profile.UndoStack.Push(memento);
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs
index 6f4fea3d0..b0549cdd7 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs
@@ -313,15 +313,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null)
return;
- foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders()
+ foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllRenderElements()
.SelectMany(f => f.GetAllLayerProperties(), (f, p) => p)
.SelectMany(p => p.GetAllDataBindingRegistrations()))
dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta);
- foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllLayers()
- .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p)
- .SelectMany(p => p.GetAllDataBindingRegistrations()))
- dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta);
-
+
// TODO: Only update when there are data bindings
if (!_profileEditorService.Playing)
_profileEditorService.UpdateProfilePreview();
diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs
new file mode 100644
index 000000000..edf84d7e1
--- /dev/null
+++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/LayerPropertyNodeCustomViewModel.cs
@@ -0,0 +1,80 @@
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.Core;
+using Stylet;
+
+namespace Artemis.VisualScripting.Nodes.CustomViewModels
+{
+ public class LayerPropertyNodeCustomViewModel : CustomNodeViewModel
+ {
+ private readonly LayerPropertyNode _node;
+
+ private RenderProfileElement _selectedProfileElement;
+ private ILayerProperty _selectedLayerProperty;
+
+ public LayerPropertyNodeCustomViewModel(LayerPropertyNode node) : base(node)
+ {
+ _node = node;
+ }
+
+ public BindableCollection ProfileElements { get; } = new();
+
+ public RenderProfileElement SelectedProfileElement
+ {
+ get => _selectedProfileElement;
+ set
+ {
+ if (!SetAndNotify(ref _selectedProfileElement, value)) return;
+ _node.ChangeProfileElement(_selectedProfileElement);
+ GetLayerProperties();
+ }
+ }
+
+ public BindableCollection LayerProperties { get; } = new();
+
+ public ILayerProperty SelectedLayerProperty
+ {
+ get => _selectedLayerProperty;
+ set
+ {
+ if (!SetAndNotify(ref _selectedLayerProperty, value)) return;
+ _node.ChangeLayerProperty(_selectedLayerProperty);
+ }
+ }
+
+ private void GetProfileElements()
+ {
+ ProfileElements.Clear();
+ if (_node.Script.Context is not Profile profile)
+ return;
+
+ List elements = new(profile.GetAllRenderElements());
+
+ ProfileElements.AddRange(elements.OrderBy(e => e.Order));
+ _selectedProfileElement = _node.ProfileElement;
+ NotifyOfPropertyChange(nameof(SelectedProfileElement));
+ }
+
+ private void GetLayerProperties()
+ {
+ LayerProperties.Clear();
+ if (_node.ProfileElement == null)
+ return;
+
+ LayerProperties.AddRange(_node.ProfileElement.GetAllLayerProperties().Where(l => l.GetAllDataBindingRegistrations().Any()));
+ _selectedLayerProperty = _node.LayerProperty;
+ NotifyOfPropertyChange(nameof(SelectedLayerProperty));
+ }
+
+ #region Overrides of CustomNodeViewModel
+
+ ///
+ protected override void OnDisplay()
+ {
+ GetProfileElements();
+ GetLayerProperties();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml
new file mode 100644
index 000000000..42be19cb8
--- /dev/null
+++ b/src/Artemis.VisualScripting/Nodes/CustomViews/LayerPropertyNodeCustomView.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs
new file mode 100644
index 000000000..64f75d022
--- /dev/null
+++ b/src/Artemis.VisualScripting/Nodes/LayerPropertyNode.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.Core;
+using Artemis.VisualScripting.Nodes.CustomViewModels;
+
+namespace Artemis.VisualScripting.Nodes
+{
+ [Node("Layer/Folder Property", "Outputs the property of a selected layer or folder")]
+ public class LayerPropertyNode : Node
+ {
+ private readonly object _layerPropertyLock = new();
+
+ 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 list = LayerProperty.GetAllDataBindingRegistrations();
+ int index = 0;
+ foreach (IPin pin in Pins)
+ {
+ OutputPin outputPin = (OutputPin) pin;
+ IDataBindingRegistration dataBindingRegistration = list[index];
+ index++;
+
+ // TODO: Is this really non-nullable?
+ outputPin.Value = dataBindingRegistration.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)
+ return;
+ if (Storage is not LayerPropertyNodeEntity entity)
+ return;
+
+ RenderProfileElement element = profile.GetAllRenderElements().FirstOrDefault(l => l.EntityId == entity.ElementId);
+
+ ProfileElement = element;
+ LayerProperty = element?.GetAllLayerProperties().FirstOrDefault(p => p.Path == entity.PropertyPath);
+ CreatePins();
+ }
+ }
+
+ public void ChangeProfileElement(RenderProfileElement profileElement)
+ {
+ lock (_layerPropertyLock)
+ {
+ ProfileElement = profileElement;
+ LayerProperty = null;
+
+ 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 (IDataBindingRegistration dataBindingRegistration in LayerProperty.GetAllDataBindingRegistrations())
+ CreateOutputPin(dataBindingRegistration.ValueType, dataBindingRegistration.DisplayName);
+ }
+
+ private void ProfileOnChildRemoved(object? sender, EventArgs e)
+ {
+ if (Script.Context is not Profile profile)
+ return;
+
+ if (!profile.GetAllRenderElements().Contains(ProfileElement))
+ ChangeProfileElement(null);
+ }
+ }
+
+ public class LayerPropertyNodeEntity
+ {
+ public Guid ElementId { get; set; }
+ public string PropertyPath { get; set; }
+ }
+}
\ No newline at end of file