diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index fab80bf52..4eadc28c6 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -94,7 +94,7 @@ namespace Artemis.Core
///
/// Gets the general properties of the layer
///
- [PropertyGroupDescription(Name = "General", Description = "A collection of general properties")]
+ [PropertyGroupDescription(Identifier = "General", Name = "General", Description = "A collection of general properties")]
public LayerGeneralProperties General
{
get => _general;
@@ -104,7 +104,7 @@ namespace Artemis.Core
///
/// Gets the transform properties of the layer
///
- [PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties")]
+ [PropertyGroupDescription(Identifier = "Transform", Name = "Transform", Description = "A collection of transformation properties")]
public LayerTransformProperties Transform
{
get => _transform;
@@ -208,19 +208,20 @@ namespace Artemis.Core
LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved;
// Layers have two hardcoded property groups, instantiate them
- Attribute generalAttribute = Attribute.GetCustomAttribute(
+ PropertyGroupDescriptionAttribute generalAttribute = (PropertyGroupDescriptionAttribute) Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(General))!,
typeof(PropertyGroupDescriptionAttribute)
)!;
- Attribute transformAttribute = Attribute.GetCustomAttribute(
+ PropertyGroupDescriptionAttribute transformAttribute = (PropertyGroupDescriptionAttribute) Attribute.GetCustomAttribute(
GetType().GetProperty(nameof(Transform))!,
typeof(PropertyGroupDescriptionAttribute)
)!;
- General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute;
- General.Initialize(this, "General.", Constants.CorePluginFeature);
- Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute;
- Transform.Initialize(this, "Transform.", Constants.CorePluginFeature);
+ LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier};
+ LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier};
+
+ General.Initialize(this, null, generalAttribute, LayerEntity.GeneralPropertyGroup);
+ Transform.Initialize(this, null, transformAttribute, LayerEntity.TransformPropertyGroup);
General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet;
ApplyShapeType();
@@ -241,8 +242,7 @@ namespace Artemis.Core
return;
LayerBrushReference? current = General.BrushReference.CurrentValue;
- if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId &&
- e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
+ if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId && e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
ActivateLayerBrush();
}
@@ -282,7 +282,13 @@ namespace Artemis.Core
General.ApplyToEntity();
Transform.ApplyToEntity();
- LayerBrush?.BaseProperties?.ApplyToEntity();
+
+ // Don't override the old value of LayerBrush if the current value is null, this avoid losing settings of an unavailable brush
+ if (LayerBrush != null)
+ {
+ LayerBrush.Save();
+ LayerEntity.LayerBrush = LayerBrush.LayerBrushEntity;
+ }
// LEDs
LayerEntity.Leds.Clear();
@@ -712,53 +718,32 @@ namespace Artemis.Core
#region Brush management
///
- /// Changes the current layer brush to the brush described in the provided
+ /// Changes the current layer brush to the provided layer brush and activates it
///
- public void ChangeLayerBrush(LayerBrushDescriptor descriptor)
+ public void ChangeLayerBrush(BaseLayerBrush? layerBrush)
{
- if (descriptor == null)
- throw new ArgumentNullException(nameof(descriptor));
+ General.BrushReference.SetCurrentValue(layerBrush != null ? new LayerBrushReference(layerBrush.Descriptor) : null, null);
+ LayerBrush = layerBrush;
if (LayerBrush != null)
- {
- BaseLayerBrush brush = LayerBrush;
- LayerBrush = null;
- brush.Dispose();
- }
-
- // Ensure the brush reference matches the brush
- LayerBrushReference? current = General.BrushReference.BaseValue;
- if (!descriptor.MatchesLayerBrushReference(current))
- General.BrushReference.SetCurrentValue(new LayerBrushReference(descriptor), null);
-
- ActivateLayerBrush();
- }
-
- ///
- /// Removes the current layer brush from the layer
- ///
- public void RemoveLayerBrush()
- {
- if (LayerBrush == null)
- return;
-
- BaseLayerBrush brush = LayerBrush;
- DeactivateLayerBrush();
- LayerEntity.PropertyEntities.RemoveAll(p => p.FeatureId == brush.ProviderId && p.Path.StartsWith("LayerBrush."));
+ ActivateLayerBrush();
+ else
+ OnLayerBrushUpdated();
}
internal void ActivateLayerBrush()
{
try
{
- LayerBrushReference? current = General.BrushReference.CurrentValue;
- if (current == null)
+ if (LayerBrush == null)
+ {
+ // If the brush is null, try to instantiate it
+ LayerBrushReference? brushReference = General.BrushReference.CurrentValue;
+ if (brushReference?.LayerBrushProviderId != null && brushReference.BrushType != null)
+ ChangeLayerBrush(LayerBrushStore.Get(brushReference.LayerBrushProviderId, brushReference.BrushType)?.LayerBrushDescriptor.CreateInstance(this, LayerEntity.LayerBrush));
+ // If that's not possible there's nothing to do
return;
-
- LayerBrushDescriptor? descriptor = current.LayerBrushProviderId != null && current.BrushType != null
- ? LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor
- : null;
- descriptor?.CreateInstance(this);
+ }
General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
@@ -778,9 +763,9 @@ namespace Artemis.Core
if (LayerBrush == null)
return;
- BaseLayerBrush brush = LayerBrush;
+ BaseLayerBrush? brush = LayerBrush;
LayerBrush = null;
- brush.Dispose();
+ brush?.Dispose();
OnLayerBrushUpdated();
}
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs
index e4b63a567..b3b58b7bb 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs
@@ -7,6 +7,11 @@ namespace Artemis.Core
///
public class PropertyDescriptionAttribute : Attribute
{
+ ///
+ /// The identifier of this property used for storage, if not set one will be generated property name in code
+ ///
+ public string? Identifier { get; set; }
+
///
/// The user-friendly name for this property, shown in the UI
///
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs
index 98cdb74db..4049688b6 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs
@@ -8,12 +8,17 @@ namespace Artemis.Core
public class PropertyGroupDescriptionAttribute : Attribute
{
///
- /// The user-friendly name for this property, shown in the UI.
+ /// The identifier of this property group used for storage, if not set one will be generated based on the group name in code
///
- public string? Name { get; set; }
+ public string? Identifier { get; set; }
///
- /// The user-friendly description for this property, shown in the UI.
+ /// The user-friendly name for this property group, shown in the UI.
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// The user-friendly description for this property group, shown in the UI.
///
public string? Description { get; set; }
}
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
index 52ae56bf1..9a4fd8333 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
@@ -16,6 +16,11 @@ namespace Artemis.Core
/// Gets the description attribute applied to this property
///
PropertyDescriptionAttribute PropertyDescription { get; }
+
+ ///
+ /// Gets the profile element (such as layer or folder) this property is applied to
+ ///
+ RenderProfileElement ProfileElement { get; }
///
/// The parent group of this layer property, set after construction
@@ -43,7 +48,7 @@ namespace Artemis.Core
public bool DataBindingsSupported { get; }
///
- /// Gets the unique path of the property on the layer
+ /// Gets the unique path of the property on the render element
///
string Path { get; }
@@ -56,7 +61,7 @@ namespace Artemis.Core
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
///
bool IsLoadedFromStorage { get; }
-
+
///
/// Initializes the layer property
///
@@ -64,7 +69,7 @@ namespace Artemis.Core
///
///
///
- void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path);
+ void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description);
///
/// Attempts to load and add the provided keyframe entity to the layer property
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 4ebd5bb61..9cfb5769d 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Text;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
@@ -27,10 +28,10 @@ namespace Artemis.Core
// These are set right after construction to keep the constructor (and inherited constructs) clean
ProfileElement = null!;
LayerPropertyGroup = null!;
- Path = null!;
Entity = null!;
PropertyDescription = null!;
DataBinding = null!;
+ Path = "";
CurrentValue = default!;
DefaultValue = default!;
@@ -117,13 +118,11 @@ namespace Artemis.Core
}
}
- ///
- /// Gets the profile element (such as layer or folder) this property is applied to
- ///
- public RenderProfileElement ProfileElement { get; internal set; }
+ ///
+ public RenderProfileElement ProfileElement { get; private set; }
///
- public LayerPropertyGroup LayerPropertyGroup { get; internal set; }
+ public LayerPropertyGroup LayerPropertyGroup { get; private set; }
#endregion
@@ -457,16 +456,18 @@ namespace Artemis.Core
internal PropertyEntity Entity { get; set; }
///
- public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description, string path)
+ public void Initialize(RenderProfileElement profileElement, LayerPropertyGroup group, PropertyEntity entity, bool fromStorage, PropertyDescriptionAttribute description)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
+ if (description.Identifier == null)
+ throw new ArtemisCoreException("Can't initialize a property group without an identifier");
+
_isInitialized = true;
ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement));
LayerPropertyGroup = group ?? throw new ArgumentNullException(nameof(group));
- Path = path;
Entity = entity ?? throw new ArgumentNullException(nameof(entity));
PropertyDescription = description ?? throw new ArgumentNullException(nameof(description));
IsLoadedFromStorage = fromStorage;
@@ -475,6 +476,9 @@ namespace Artemis.Core
if (PropertyDescription.DisableKeyframes)
KeyframesSupported = false;
+ // Create the path to this property by walking up the tree
+ Path = LayerPropertyGroup.Path + "." + description.Identifier;
+
OnInitialize();
}
diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
index 130f06e87..3cdbeec17 100644
--- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
+++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
@@ -31,9 +31,7 @@ namespace Artemis.Core
{
// These are set right after construction to keep the constructor (and inherited constructs) clean
GroupDescription = null!;
- Feature = null!;
- ProfileElement = null!;
- Path = null!;
+ Path = "";
_layerProperties = new List();
_layerPropertyGroups = new List();
@@ -42,47 +40,32 @@ namespace Artemis.Core
LayerPropertyGroups = new ReadOnlyCollection(_layerPropertyGroups);
}
- ///
- /// Gets the description of this group
- ///
- public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; }
-
- ///
- /// Gets the plugin feature this group is associated with
- ///
- public PluginFeature Feature { get; set; }
-
///
/// Gets the profile element (such as layer or folder) this group is associated with
///
- public RenderProfileElement ProfileElement { get; internal set; }
+ public RenderProfileElement ProfileElement { get; private set; }
+
+ ///
+ /// Gets the description of this group
+ ///
+ public PropertyGroupDescriptionAttribute GroupDescription { get; private set; }
///
/// The parent group of this group
///
- [LayerPropertyIgnore]
+ [LayerPropertyIgnore] // Ignore the parent when selecting child groups
public LayerPropertyGroup? Parent { get; internal set; }
///
- /// The path of this property group
+ /// Gets the unique path of the property on the render element
///
- public string Path { get; internal set; }
+ public string Path { get; private set; }
///
/// Gets whether this property groups properties are all initialized
///
public bool PropertiesInitialized { get; private set; }
- ///
- /// The layer brush this property group belongs to
- ///
- public BaseLayerBrush? LayerBrush { get; internal set; }
-
- ///
- /// The layer effect this property group belongs to
- ///
- public BaseLayerEffect? LayerEffect { get; internal set; }
-
///
/// Gets or sets whether the property is hidden in the UI
///
@@ -96,6 +79,11 @@ namespace Artemis.Core
}
}
+ ///
+ /// Gets the entity this property group uses for persistent storage
+ ///
+ public PropertyGroupEntity? PropertyGroupEntity { get; internal set; }
+
///
/// A list of all layer properties in this group
///
@@ -194,17 +182,20 @@ namespace Artemis.Core
}
}
- internal void Initialize(RenderProfileElement profileElement, string path, PluginFeature feature)
+ internal void Initialize(RenderProfileElement profileElement, LayerPropertyGroup? parent, PropertyGroupDescriptionAttribute groupDescription, PropertyGroupEntity? propertyGroupEntity)
{
- if (path == null) throw new ArgumentNullException(nameof(path));
+ if (groupDescription.Identifier == null)
+ throw new ArtemisCoreException("Can't initialize a property group without an identifier");
// Doubt this will happen but let's make sure
if (PropertiesInitialized)
throw new ArtemisCoreException("Layer property group already initialized, wut");
- Feature = feature ?? throw new ArgumentNullException(nameof(feature));
- ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement));
- Path = path.TrimEnd('.');
+ ProfileElement = profileElement;
+ Parent = parent;
+ GroupDescription = groupDescription;
+ PropertyGroupEntity = propertyGroupEntity ?? new PropertyGroupEntity {Identifier = groupDescription.Identifier};
+ Path = parent != null ? parent.Path + "." + groupDescription.Identifier : groupDescription.Identifier;
// Get all properties implementing ILayerProperty or LayerPropertyGroup
foreach (PropertyInfo propertyInfo in GetType().GetProperties())
@@ -271,61 +262,68 @@ namespace Artemis.Core
private void InitializeProperty(PropertyInfo propertyInfo, PropertyDescriptionAttribute propertyDescription)
{
- string path = $"{Path}.{propertyInfo.Name}";
-
- if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
- throw new ArtemisPluginException($"Layer property with PropertyDescription attribute must be of type LayerProperty at {path}");
-
- if (!(Activator.CreateInstance(propertyInfo.PropertyType, true) is ILayerProperty instance))
- throw new ArtemisPluginException($"Failed to create instance of layer property at {path}");
-
- // Ensure the description has a name, if not this is a good point to set it based on the property info
+ // Ensure the description has an identifier and name, if not this is a good point to set it based on the property info
+ if (string.IsNullOrWhiteSpace(propertyDescription.Identifier))
+ propertyDescription.Identifier = propertyInfo.Name;
if (string.IsNullOrWhiteSpace(propertyDescription.Name))
propertyDescription.Name = propertyInfo.Name.Humanize();
- PropertyEntity entity = GetPropertyEntity(ProfileElement, path, out bool fromStorage);
- instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription, path);
+ if (!typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
+ throw new ArtemisPluginException($"Property with PropertyDescription attribute must be of type ILayerProperty: {propertyDescription.Identifier}");
+ if (Activator.CreateInstance(propertyInfo.PropertyType, true) is not ILayerProperty instance)
+ throw new ArtemisPluginException($"Failed to create instance of layer property: {propertyDescription.Identifier}");
+
+ PropertyEntity entity = GetPropertyEntity(propertyDescription.Identifier, out bool fromStorage);
+ instance.Initialize(ProfileElement, this, entity, fromStorage, propertyDescription);
propertyInfo.SetValue(this, instance);
+
_layerProperties.Add(instance);
}
private void InitializeChildGroup(PropertyInfo propertyInfo, PropertyGroupDescriptionAttribute propertyGroupDescription)
{
- string path = Path + ".";
-
- if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
- throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup");
-
- if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
- throw new ArtemisPluginException($"Failed to create instance of layer property group at {path + propertyInfo.Name}");
-
- // Ensure the description has a name, if not this is a good point to set it based on the property info
+ // Ensure the description has an identifier and name name, if not this is a good point to set it based on the property info
+ if (string.IsNullOrWhiteSpace(propertyGroupDescription.Identifier))
+ propertyGroupDescription.Identifier = propertyInfo.Name;
if (string.IsNullOrWhiteSpace(propertyGroupDescription.Name))
propertyGroupDescription.Name = propertyInfo.Name.Humanize();
- instance.Parent = this;
- instance.GroupDescription = propertyGroupDescription;
- instance.LayerBrush = LayerBrush;
- instance.LayerEffect = LayerEffect;
- instance.Initialize(ProfileElement, $"{path}{propertyInfo.Name}.", Feature);
+ if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
+ throw new ArtemisPluginException($"Property with PropertyGroupDescription attribute must be of type LayerPropertyGroup: {propertyGroupDescription.Identifier}");
+ if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
+ throw new ArtemisPluginException($"Failed to create instance of layer property group: {propertyGroupDescription.Identifier}");
+
+ PropertyGroupEntity entity = GetPropertyGroupEntity(propertyGroupDescription.Identifier);
+ instance.Initialize(ProfileElement, this, propertyGroupDescription, entity);
propertyInfo.SetValue(this, instance);
_layerPropertyGroups.Add(instance);
}
- private PropertyEntity GetPropertyEntity(RenderProfileElement profileElement, string path, out bool fromStorage)
+ private PropertyEntity GetPropertyEntity(string identifier, out bool fromStorage)
{
- PropertyEntity? entity = profileElement.RenderElementEntity.PropertyEntities.FirstOrDefault(p => p.FeatureId == Feature.Id && p.Path == path);
+ if (PropertyGroupEntity == null)
+ throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyEntity)} without {nameof(PropertyGroupEntity)} being setup");
+
+ PropertyEntity? entity = PropertyGroupEntity.Properties.FirstOrDefault(p => p.Identifier == identifier);
fromStorage = entity != null;
if (entity == null)
{
- entity = new PropertyEntity {FeatureId = Feature.Id, Path = path};
- profileElement.RenderElementEntity.PropertyEntities.Add(entity);
+ entity = new PropertyEntity {Identifier = identifier};
+ PropertyGroupEntity.Properties.Add(entity);
}
return entity;
}
+ private PropertyGroupEntity GetPropertyGroupEntity(string identifier)
+ {
+ if (PropertyGroupEntity == null)
+ throw new ArtemisCoreException($"Can't execute {nameof(GetPropertyGroupEntity)} without {nameof(PropertyGroupEntity)} being setup");
+
+ return PropertyGroupEntity.PropertyGroups.FirstOrDefault(g => g.Identifier == identifier) ?? new PropertyGroupEntity() {Identifier = identifier};
+ }
+
///
public void Dispose()
{
diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
index 44066b0db..be2ec4a2b 100644
--- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
@@ -97,20 +97,10 @@ namespace Artemis.Core
internal void SaveRenderElement()
{
RenderElementEntity.LayerEffects.Clear();
- foreach (BaseLayerEffect layerEffect in LayerEffects)
+ foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
- LayerEffectEntity layerEffectEntity = new()
- {
- Id = layerEffect.EntityId,
- ProviderId = layerEffect.Descriptor?.PlaceholderFor ?? layerEffect.ProviderId,
- EffectType = layerEffect.GetEffectTypeName(),
- Name = layerEffect.Name,
- Suspended = layerEffect.Suspended,
- HasBeenRenamed = layerEffect.HasBeenRenamed,
- Order = layerEffect.Order
- };
- RenderElementEntity.LayerEffects.Add(layerEffectEntity);
- layerEffect.BaseProperties?.ApplyToEntity();
+ baseLayerEffect.Save();
+ RenderElementEntity.LayerEffects.Add(baseLayerEffect.LayerEffectEntity);
}
// Condition
@@ -310,7 +300,7 @@ namespace Artemis.Core
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects)
{
// If there is a non-placeholder existing effect, skip this entity
- BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id);
+ BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.LayerEffectEntity.Id == layerEffectEntity.Id);
if (existing != null && existing.Descriptor.PlaceholderFor == null)
continue;
@@ -351,7 +341,7 @@ namespace Artemis.Core
List pluginEffects = LayerEffectsList.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList();
foreach (BaseLayerEffect pluginEffect in pluginEffects)
{
- LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.EntityId);
+ LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.LayerEffectEntity.Id);
LayerEffectsList.Remove(pluginEffect);
pluginEffect.Dispose();
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
index 40a37b09d..69027def6 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Artemis.Storage.Entities.Profile;
using SkiaSharp;
namespace Artemis.Core.LayerBrushes
@@ -24,6 +25,7 @@ namespace Artemis.Core.LayerBrushes
// Both are set right after construction to keep the constructor of inherited classes clean
_layer = null!;
_descriptor = null!;
+ LayerBrushEntity = null!;
}
///
@@ -35,6 +37,11 @@ namespace Artemis.Core.LayerBrushes
internal set => SetAndNotify(ref _layer, value);
}
+ ///
+ /// Gets the brush entity this brush uses for persistent storage
+ ///
+ public LayerBrushEntity LayerBrushEntity { get; internal set; }
+
///
/// Gets the descriptor of this brush
///
@@ -185,6 +192,13 @@ namespace Artemis.Core.LayerBrushes
public override string BrokenDisplayName => Descriptor.DisplayName;
#endregion
+
+ internal void Save()
+ {
+ // No need to update the type or provider ID, they're set once by the LayerBrushDescriptors CreateInstance and can't change
+ BaseProperties?.ApplyToEntity();
+ LayerBrushEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
+ }
}
///
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
index 4225b0455..fca826e2d 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
@@ -1,4 +1,5 @@
using System;
+using Artemis.Storage.Entities.Profile;
namespace Artemis.Core.LayerBrushes
{
@@ -32,12 +33,11 @@ namespace Artemis.Core.LayerBrushes
internal set => _properties = value;
}
- internal void InitializeProperties()
+ internal void InitializeProperties(PropertyGroupEntity? propertyGroupEntity)
{
Properties = Activator.CreateInstance();
- Properties.GroupDescription = new PropertyGroupDescriptionAttribute {Name = Descriptor.DisplayName, Description = Descriptor.Description};
- Properties.LayerBrush = this;
- Properties.Initialize(Layer, "LayerBrush.", Descriptor.Provider);
+ PropertyGroupDescriptionAttribute groupDescription = new() {Identifier = "Brush", Name = Descriptor.DisplayName, Description = Descriptor.Description};
+ Properties.Initialize(Layer, null, groupDescription, propertyGroupEntity);
PropertiesInitialized = true;
EnableLayerBrush();
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
index 9e69380d0..749d64bfa 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs
@@ -33,7 +33,7 @@ namespace Artemis.Core.LayerBrushes
internal override void Initialize()
{
- TryOrBreak(InitializeProperties, "Failed to initialize");
+ TryOrBreak(() => InitializeProperties(Layer.LayerEntity.LayerBrush?.PropertyGroup), "Failed to initialize");
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs
index 6fb91ca71..8aa7026e0 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs
@@ -1,4 +1,5 @@
using System;
+using Artemis.Storage.Entities.Profile;
using Ninject;
namespace Artemis.Core.LayerBrushes
@@ -57,23 +58,20 @@ namespace Artemis.Core.LayerBrushes
///
/// Creates an instance of the described brush and applies it to the layer
///
- internal void CreateInstance(Layer layer)
+ public BaseLayerBrush CreateInstance(Layer layer, LayerBrushEntity? entity)
{
- if (layer == null) throw new ArgumentNullException(nameof(layer));
- if (layer.LayerBrush != null)
- throw new ArtemisCoreException("Layer already has an instantiated layer brush");
+ if (layer == null)
+ throw new ArgumentNullException(nameof(layer));
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Kernel!.Get(LayerBrushType);
brush.Layer = layer;
brush.Descriptor = this;
+ brush.LayerBrushEntity = entity ?? new LayerBrushEntity { ProviderId = Provider.Id, BrushType = LayerBrushType.FullName };
+
brush.Initialize();
brush.Update(0);
- layer.LayerBrush = brush;
- layer.OnLayerBrushUpdated();
-
- if (layer.ShouldBeEnabled)
- brush.InternalEnable();
+ return brush;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
index 1f1948808..479738e15 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs
@@ -77,7 +77,7 @@ namespace Artemis.Core.LayerBrushes
internal override void Initialize()
{
- TryOrBreak(InitializeProperties, "Failed to initialize");
+ TryOrBreak(() => InitializeProperties(Layer.LayerEntity.LayerBrush?.PropertyGroup), "Failed to initialize");
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
index ce32edfd0..24c46589a 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs
@@ -1,4 +1,5 @@
using System;
+using Artemis.Storage.Entities.Profile;
using SkiaSharp;
namespace Artemis.Core.LayerEffects
@@ -24,16 +25,13 @@ namespace Artemis.Core.LayerEffects
_profileElement = null!;
_descriptor = null!;
_name = null!;
+ LayerEffectEntity = null!;
}
///
- /// Gets the unique ID of this effect
+ /// Gets the
///
- public Guid EntityId
- {
- get => _entityId;
- internal set => SetAndNotify(ref _entityId, value);
- }
+ public LayerEffectEntity LayerEffectEntity { get; internal set; }
///
/// Gets the profile element (such as layer or folder) this effect is applied to
@@ -109,8 +107,6 @@ namespace Artemis.Core.LayerEffects
///
public virtual LayerPropertyGroup? BaseProperties => null;
- internal string PropertyRootPath => $"LayerEffect.{EntityId}.{GetType().Name}.";
-
///
/// Gets a boolean indicating whether the layer effect is enabled or not
///
@@ -227,5 +223,17 @@ namespace Artemis.Core.LayerEffects
public override string BrokenDisplayName => Name;
#endregion
+
+ public void Save()
+ {
+ // No need to update the ID, type and provider ID. They're set once by the LayerBrushDescriptors CreateInstance and can't change
+ LayerEffectEntity.Name = Name;
+ LayerEffectEntity.Suspended = Suspended;
+ LayerEffectEntity.HasBeenRenamed = HasBeenRenamed;
+ LayerEffectEntity.Order = Order;
+
+ BaseProperties?.ApplyToEntity();
+ LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs
index 3a1b03e62..8916758e7 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs
@@ -36,8 +36,7 @@ namespace Artemis.Core.LayerEffects
internal void InitializeProperties()
{
Properties = Activator.CreateInstance();
- Properties.LayerEffect = this;
- Properties.Initialize(ProfileElement, PropertyRootPath, Descriptor.Provider);
+ Properties.Initialize(ProfileElement, null, new PropertyGroupDescriptionAttribute(){Identifier = "LayerEffect"}, LayerEffectEntity.PropertyGroup);
PropertiesInitialized = true;
EnableLayerEffect();
diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs
index f724f367a..34f501845 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs
@@ -54,11 +54,28 @@ namespace Artemis.Core.LayerEffects
///
/// Creates an instance of the described effect and applies it to the render element
///
- internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity entity)
+ internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity? entity)
{
- // Skip effects already on the element
- if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id))
- return;
+ if (LayerEffectType == null)
+ throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType");
+
+ if (entity == null)
+ {
+ entity = new LayerEffectEntity
+ {
+ Id = Guid.NewGuid(),
+ Suspended = false,
+ Order = renderElement.LayerEffects.Count + 1,
+ ProviderId = Provider.Id,
+ EffectType = LayerEffectType.FullName
+ };
+ }
+ else
+ {
+ // Skip effects already on the element
+ if (renderElement.LayerEffects.Any(e => e.LayerEffectEntity.Id == entity.Id))
+ return;
+ }
if (PlaceholderFor != null)
{
@@ -66,12 +83,9 @@ namespace Artemis.Core.LayerEffects
return;
}
- if (LayerEffectType == null)
- throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType");
-
BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType);
effect.ProfileElement = renderElement;
- effect.EntityId = entity.Id;
+ effect.LayerEffectEntity = entity;
effect.Order = entity.Order;
effect.Name = entity.Name;
effect.Suspended = entity.Suspended;
diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs
index 07f57491b..a1ec92371 100644
--- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs
+++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs
@@ -13,7 +13,7 @@ namespace Artemis.Core.LayerEffects.Placeholder
OriginalEntity = originalEntity;
PlaceholderFor = placeholderFor;
- EntityId = OriginalEntity.Id;
+ LayerEffectEntity = originalEntity;
Order = OriginalEntity.Order;
Name = OriginalEntity.Name;
Suspended = OriginalEntity.Suspended;
diff --git a/src/Artemis.Storage/Entities/Profile/LayerBrushEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerBrushEntity.cs
new file mode 100644
index 000000000..1e99d1318
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/LayerBrushEntity.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Artemis.Storage.Entities.Profile;
+
+public class LayerBrushEntity
+{
+ public string ProviderId { get; set; }
+ public string BrushType { get; set; }
+
+ public PropertyGroupEntity PropertyGroup { get; set; }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs
index a575e24a9..236d1d2fb 100644
--- a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile
{
@@ -11,5 +12,7 @@ namespace Artemis.Storage.Entities.Profile
public bool Suspended { get; set; }
public bool HasBeenRenamed { get; set; }
public int Order { get; set; }
+
+ public PropertyGroupEntity PropertyGroup { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs
index 4be0f1d02..1c8ff29ea 100644
--- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs
@@ -24,9 +24,12 @@ namespace Artemis.Storage.Entities.Profile
public List Leds { get; set; }
public List AdaptionHints { get; set; }
+ public PropertyGroupEntity GeneralPropertyGroup { get; set; }
+ public PropertyGroupEntity TransformPropertyGroup { get; set; }
+ public LayerBrushEntity LayerBrush { get; set; }
+
[BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; }
-
public Guid ProfileId { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs
index ec5fc0344..21036efcc 100644
--- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs
@@ -1,22 +1,14 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.DataBindings;
-namespace Artemis.Storage.Entities.Profile
+namespace Artemis.Storage.Entities.Profile;
+
+public class PropertyEntity
{
- public class PropertyEntity
- {
- public PropertyEntity()
- {
- KeyframeEntities = new List();
- }
+ public string Identifier { get; set; }
+ public string Value { get; set; }
+ public bool KeyframesEnabled { get; set; }
- public string FeatureId { get; set; }
- public string Path { get; set; }
- public DataBindingEntity DataBinding { get; set; }
-
- public string Value { get; set; }
- public bool KeyframesEnabled { get; set; }
-
- public List KeyframeEntities { get; set; }
- }
+ public DataBindingEntity DataBinding { get; set; }
+ public List KeyframeEntities { get; set; } = new();
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/PropertyGroupEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyGroupEntity.cs
new file mode 100644
index 000000000..3f4825782
--- /dev/null
+++ b/src/Artemis.Storage/Entities/Profile/PropertyGroupEntity.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace Artemis.Storage.Entities.Profile;
+
+public class PropertyGroupEntity
+{
+ public string Identifier { get; set; }
+ public List Properties { get; set; } = new();
+ public List PropertyGroups { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
index bdea29e96..2b4be9532 100644
--- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
+++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
@@ -49,13 +49,13 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{
if (LayerProperty.ProfileElement is Layer layer)
{
- layer.ChangeLayerBrush(SelectedDescriptor);
- if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
- Execute.PostToUIThread(async () =>
- {
- await Task.Delay(400);
- await _dialogService.ShowDialogAt("LayerProperties", new Dictionary {{"layerBrush", layer.LayerBrush}});
- });
+ // layer.ChangeLayerBrush(SelectedDescriptor);
+ // if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
+ // Execute.PostToUIThread(async () =>
+ // {
+ // await Task.Delay(400);
+ // await _dialogService.ShowDialogAt("LayerProperties", new Dictionary {{"layerBrush", layer.LayerBrush}});
+ // });
}
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index 4c506471b..41c7a1a23 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -332,19 +332,19 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
if (SelectedProfileElement == null)
return;
- // Remove VMs of effects no longer applied on the layer
- List toRemove = Items
- .Where(l => l.LayerPropertyGroup.LayerEffect != null && !SelectedProfileElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect))
- .ToList();
- Items.RemoveRange(toRemove);
-
- foreach (BaseLayerEffect layerEffect in SelectedProfileElement.LayerEffects)
- {
- if (Items.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect) || layerEffect.BaseProperties == null)
- continue;
-
- Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties));
- }
+ // // Remove VMs of effects no longer applied on the layer
+ // List toRemove = Items
+ // .Where(l => l.LayerPropertyGroup.LayerEffect != null && !SelectedProfileElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect))
+ // .ToList();
+ // Items.RemoveRange(toRemove);
+ //
+ // foreach (BaseLayerEffect layerEffect in SelectedProfileElement.LayerEffects)
+ // {
+ // if (Items.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect) || layerEffect.BaseProperties == null)
+ // continue;
+ //
+ // Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties));
+ // }
SortProperties();
}
@@ -355,11 +355,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
List nonEffectProperties = Items
.Where(l => l.TreeGroupViewModel.GroupType != LayerEffectRoot)
.ToList();
- // Order the effects
- List effectProperties = Items
- .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
- .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order)
- .ToList();
+ // // Order the effects
+ // List effectProperties = Items
+ // .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
+ // .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order)
+ // .ToList();
// Put the non-effect properties in front
for (int index = 0; index < nonEffectProperties.Count; index++)
@@ -369,13 +369,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index);
}
- // Put the effect properties after, sorted by their order
- for (int index = 0; index < effectProperties.Count; index++)
- {
- LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index];
- if (Items.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count)
- ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count);
- }
+ // // Put the effect properties after, sorted by their order
+ // for (int index = 0; index < effectProperties.Count; index++)
+ // {
+ // LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index];
+ // if (Items.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count)
+ // ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count);
+ // }
}
public async void ToggleEffectsViewModel()
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs
index 84e28c8a3..ce26802e1 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs
@@ -87,8 +87,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
public void UpdateOrder(int order)
{
- if (LayerPropertyGroup.LayerEffect != null)
- LayerPropertyGroup.LayerEffect.Order = order;
+ // if (LayerPropertyGroup.LayerEffect != null)
+ // LayerPropertyGroup.LayerEffect.Order = order;
NotifyOfPropertyChange(nameof(IsExpanded));
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs
index c05e3f26b..caaebf9bb 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Models/KeyframeClipboardModel.cs
@@ -59,7 +59,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models
public KeyframeClipboardModel(ILayerPropertyKeyframe layerPropertyKeyframe)
{
- FeatureId = layerPropertyKeyframe.UntypedLayerProperty.LayerPropertyGroup.Feature.Id;
+ // FeatureId = layerPropertyKeyframe.UntypedLayerProperty.LayerPropertyGroup.Feature.Id;
Path = layerPropertyKeyframe.UntypedLayerProperty.Path;
KeyframeEntity = layerPropertyKeyframe.GetKeyframeEntity();
}
@@ -70,15 +70,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models
public ILayerPropertyKeyframe Paste(List properties, TimeSpan offset)
{
- ILayerProperty property = properties.FirstOrDefault(p => p.LayerPropertyGroup.Feature.Id == FeatureId && p.Path == Path);
- if (property != null)
- {
- KeyframeEntity.Position += offset;
- ILayerPropertyKeyframe keyframe = property.AddKeyframeEntity(KeyframeEntity);
- KeyframeEntity.Position -= offset;
-
- return keyframe;
- }
+ // ILayerProperty property = properties.FirstOrDefault(p => p.LayerPropertyGroup.Feature.Id == FeatureId && p.Path == Path);
+ // if (property != null)
+ // {
+ // KeyframeEntity.Position += offset;
+ // ILayerPropertyKeyframe keyframe = property.AddKeyframeEntity(KeyframeEntity);
+ // KeyframeEntity.Position -= offset;
+ //
+ // return keyframe;
+ // }
return null;
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs
index 01b7417db..cf003c3aa 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs
@@ -56,94 +56,94 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
? LayerPropertyGroupViewModel.Items
: null;
- public void OpenBrushSettings()
- {
- BaseLayerBrush layerBrush = LayerPropertyGroup.LayerBrush;
- LayerBrushConfigurationDialog configurationViewModel = (LayerBrushConfigurationDialog) layerBrush.ConfigurationDialog;
- if (configurationViewModel == null)
- return;
+ // public void OpenBrushSettings()
+ // {
+ // BaseLayerBrush layerBrush = LayerPropertyGroup.LayerBrush;
+ // LayerBrushConfigurationDialog configurationViewModel = (LayerBrushConfigurationDialog) layerBrush.ConfigurationDialog;
+ // if (configurationViewModel == null)
+ // return;
+ //
+ // try
+ // {
+ // // Limit to one constructor, there's no need to have more and it complicates things anyway
+ // ConstructorInfo[] constructors = configurationViewModel.Type.GetConstructors();
+ // if (constructors.Length != 1)
+ // throw new ArtemisUIException("Brush configuration dialogs must have exactly one constructor");
+ //
+ // // Find the BaseLayerBrush parameter, it is required by the base constructor so its there for sure
+ // ParameterInfo brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType));
+ // ConstructorArgument argument = new(brushParameter.Name, layerBrush);
+ // BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
+ //
+ // _layerBrushSettingsWindowVm = new LayerBrushSettingsWindowViewModel(viewModel, configurationViewModel);
+ // _windowManager.ShowDialog(_layerBrushSettingsWindowVm);
+ //
+ // // Save changes after the dialog closes
+ // _profileEditorService.SaveSelectedProfileConfiguration();
+ // }
+ // catch (Exception e)
+ // {
+ // _dialogService.ShowExceptionDialog("An exception occured while trying to show the brush's settings window", e);
+ // }
+ // }
- try
- {
- // Limit to one constructor, there's no need to have more and it complicates things anyway
- ConstructorInfo[] constructors = configurationViewModel.Type.GetConstructors();
- if (constructors.Length != 1)
- throw new ArtemisUIException("Brush configuration dialogs must have exactly one constructor");
+ // public void OpenEffectSettings()
+ // {
+ // BaseLayerEffect layerEffect = LayerPropertyGroup.LayerEffect;
+ // LayerEffectConfigurationDialog configurationViewModel = (LayerEffectConfigurationDialog) layerEffect.ConfigurationDialog;
+ // if (configurationViewModel == null)
+ // return;
+ //
+ // try
+ // {
+ // // Limit to one constructor, there's no need to have more and it complicates things anyway
+ // ConstructorInfo[] constructors = configurationViewModel.Type.GetConstructors();
+ // if (constructors.Length != 1)
+ // throw new ArtemisUIException("Effect configuration dialogs must have exactly one constructor");
+ //
+ // ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
+ // ConstructorArgument argument = new(effectParameter.Name, layerEffect);
+ // EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
+ //
+ // _layerEffectSettingsWindowVm = new LayerEffectSettingsWindowViewModel(viewModel, configurationViewModel);
+ // _windowManager.ShowDialog(_layerEffectSettingsWindowVm);
+ //
+ // // Save changes after the dialog closes
+ // _profileEditorService.SaveSelectedProfileConfiguration();
+ // }
+ // catch (Exception e)
+ // {
+ // _dialogService.ShowExceptionDialog("An exception occured while trying to show the effect's settings window", e);
+ // throw;
+ // }
+ // }
- // Find the BaseLayerBrush parameter, it is required by the base constructor so its there for sure
- ParameterInfo brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType));
- ConstructorArgument argument = new(brushParameter.Name, layerBrush);
- BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
-
- _layerBrushSettingsWindowVm = new LayerBrushSettingsWindowViewModel(viewModel, configurationViewModel);
- _windowManager.ShowDialog(_layerBrushSettingsWindowVm);
-
- // Save changes after the dialog closes
- _profileEditorService.SaveSelectedProfileConfiguration();
- }
- catch (Exception e)
- {
- _dialogService.ShowExceptionDialog("An exception occured while trying to show the brush's settings window", e);
- }
- }
-
- public void OpenEffectSettings()
- {
- BaseLayerEffect layerEffect = LayerPropertyGroup.LayerEffect;
- LayerEffectConfigurationDialog configurationViewModel = (LayerEffectConfigurationDialog) layerEffect.ConfigurationDialog;
- if (configurationViewModel == null)
- return;
-
- try
- {
- // Limit to one constructor, there's no need to have more and it complicates things anyway
- ConstructorInfo[] constructors = configurationViewModel.Type.GetConstructors();
- if (constructors.Length != 1)
- throw new ArtemisUIException("Effect configuration dialogs must have exactly one constructor");
-
- ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
- ConstructorArgument argument = new(effectParameter.Name, layerEffect);
- EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel.Get(configurationViewModel.Type, argument);
-
- _layerEffectSettingsWindowVm = new LayerEffectSettingsWindowViewModel(viewModel, configurationViewModel);
- _windowManager.ShowDialog(_layerEffectSettingsWindowVm);
-
- // Save changes after the dialog closes
- _profileEditorService.SaveSelectedProfileConfiguration();
- }
- catch (Exception e)
- {
- _dialogService.ShowExceptionDialog("An exception occured while trying to show the effect's settings window", e);
- throw;
- }
- }
-
- public async void RenameEffect()
- {
- object result = await _dialogService.ShowDialogAt(
- "PropertyTreeDialogHost",
- new Dictionary
- {
- {"subject", "effect"},
- {"currentName", LayerPropertyGroup.LayerEffect.Name}
- }
- );
- if (result is string newName)
- {
- LayerPropertyGroup.LayerEffect.Name = newName;
- LayerPropertyGroup.LayerEffect.HasBeenRenamed = true;
- _profileEditorService.SaveSelectedProfileConfiguration();
- }
- }
-
- public void DeleteEffect()
- {
- if (LayerPropertyGroup.LayerEffect == null)
- return;
-
- LayerPropertyGroup.ProfileElement.RemoveLayerEffect(LayerPropertyGroup.LayerEffect);
- _profileEditorService.SaveSelectedProfileConfiguration();
- }
+ // public async void RenameEffect()
+ // {
+ // object result = await _dialogService.ShowDialogAt(
+ // "PropertyTreeDialogHost",
+ // new Dictionary
+ // {
+ // {"subject", "effect"},
+ // {"currentName", LayerPropertyGroup.LayerEffect.Name}
+ // }
+ // );
+ // if (result is string newName)
+ // {
+ // LayerPropertyGroup.LayerEffect.Name = newName;
+ // LayerPropertyGroup.LayerEffect.HasBeenRenamed = true;
+ // _profileEditorService.SaveSelectedProfileConfiguration();
+ // }
+ // }
+ //
+ // public void DeleteEffect()
+ // {
+ // if (LayerPropertyGroup.LayerEffect == null)
+ // return;
+ //
+ // LayerPropertyGroup.ProfileElement.RemoveLayerEffect(LayerPropertyGroup.LayerEffect);
+ // _profileEditorService.SaveSelectedProfileConfiguration();
+ // }
public void SuspendedToggled()
{
@@ -175,10 +175,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree
GroupType = LayerPropertyGroupType.General;
else if (LayerPropertyGroup is LayerTransformProperties)
GroupType = LayerPropertyGroupType.Transform;
- else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null)
- GroupType = LayerPropertyGroupType.LayerBrushRoot;
- else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null)
- GroupType = LayerPropertyGroupType.LayerEffectRoot;
+ // else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null)
+ // GroupType = LayerPropertyGroupType.LayerBrushRoot;
+ // else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null)
+ // GroupType = LayerPropertyGroupType.LayerEffectRoot;
else
GroupType = LayerPropertyGroupType.None;
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
index ae3ed6dde..fb6a06c69 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs
@@ -151,8 +151,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem
ProfileElement.AddChild(layer, 0);
// Could be null if the default brush got disabled
LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush();
- if (brush != null)
- layer.ChangeLayerBrush(brush);
+ // if (brush != null)
+ // layer.ChangeLayerBrush(brush);
layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
_profileEditorService.SaveSelectedProfileConfiguration();
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs
index 9e56b678c..45181bd84 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs
@@ -93,8 +93,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
folder.AddChild(newLayer);
LayerBrushDescriptor brush = _layerBrushService.GetDefaultLayerBrush();
- if (brush != null)
- newLayer.ChangeLayerBrush(brush);
+ // if (brush != null)
+ // newLayer.ChangeLayerBrush(brush);
newLayer.AddLeds(selectedLeds);
ProfileEditorService.ChangeSelectedProfileElement(newLayer);
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs
index 2032431a6..a93d99142 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs
@@ -1,4 +1,5 @@
-using Artemis.Core;
+using System;
+using Artemis.Core;
using Artemis.Core.LayerBrushes;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
@@ -6,11 +7,14 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
///
/// Represents a profile editor command that can be used to change the brush of a layer.
///
-public class ChangeLayerBrush : IProfileEditorCommand
+public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
{
private readonly Layer _layer;
private readonly LayerBrushDescriptor _layerBrushDescriptor;
- private readonly LayerBrushDescriptor? _previousDescriptor;
+ private readonly BaseLayerBrush? _previousBrush;
+
+ private BaseLayerBrush? _newBrush;
+ private bool _executed;
///
/// Creates a new instance of the class.
@@ -19,7 +23,7 @@ public class ChangeLayerBrush : IProfileEditorCommand
{
_layer = layer;
_layerBrushDescriptor = layerBrushDescriptor;
- _previousDescriptor = layer.LayerBrush?.Descriptor;
+ _previousBrush = _layer.LayerBrush;
}
#region Implementation of IProfileEditorCommand
@@ -30,14 +34,32 @@ public class ChangeLayerBrush : IProfileEditorCommand
///
public void Execute()
{
- _layer.ChangeLayerBrush(_layerBrushDescriptor);
+ // Create the new brush
+ _newBrush ??= _layerBrushDescriptor.CreateInstance(_layer, null);
+ // Change the brush to the new brush
+ _layer.ChangeLayerBrush(_newBrush);
+
+ _executed = true;
}
///
public void Undo()
{
- if (_previousDescriptor != null)
- _layer.ChangeLayerBrush(_previousDescriptor);
+ _layer.ChangeLayerBrush(_previousBrush);
+ _executed = false;
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ ///
+ public void Dispose()
+ {
+ if (_executed)
+ _previousBrush?.Dispose();
+ else
+ _newBrush?.Dispose();
}
#endregion
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
index b759fe8cc..aaf57c0be 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs
@@ -3,22 +3,86 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared.Services.Interfaces;
-namespace Artemis.UI.Shared.Services.ProfileEditor
+namespace Artemis.UI.Shared.Services.ProfileEditor;
+
+///
+/// Provides access the the profile editor back-end logic.
+///
+public interface IProfileEditorService : IArtemisSharedUIService
{
- public interface IProfileEditorService : IArtemisSharedUIService
- {
- IObservable ProfileConfiguration { get; }
- IObservable ProfileElement { get; }
- IObservable History { get; }
- IObservable Time { get; }
- IObservable PixelsPerSecond { get; }
+ ///
+ /// Gets an observable of the currently selected profile configuration.
+ ///
+ IObservable ProfileConfiguration { get; }
- void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
- void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
- void ChangeTime(TimeSpan time);
+ ///
+ /// Gets an observable of the currently selected profile element.
+ ///
+ IObservable ProfileElement { get; }
- void ExecuteCommand(IProfileEditorCommand command);
- void SaveProfile();
- Task SaveProfileAsync();
- }
+ ///
+ /// Gets an observable of the current editor history.
+ ///
+ IObservable History { get; }
+
+ ///
+ /// Gets an observable of the profile preview playback time.
+ ///
+ IObservable Time { get; }
+
+ ///
+ /// Gets an observable of the profile preview playing state.
+ ///
+ IObservable Playing { get; }
+
+ ///
+ /// Gets an observable of the zoom level.
+ ///
+ IObservable PixelsPerSecond { get; }
+
+
+ ///
+ /// Changes the selected profile by its .
+ ///
+ /// The profile configuration of the profile to select.
+ void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
+
+ ///
+ /// Changes the selected profile element.
+ ///
+ /// The profile element to select.
+ void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
+
+ ///
+ /// Changes the current profile preview playback time.
+ ///
+ /// The new time.
+ void ChangeTime(TimeSpan time);
+
+ ///
+ /// Executes the provided command and adds it to the history.
+ ///
+ /// The command to execute.
+ void ExecuteCommand(IProfileEditorCommand command);
+
+ ///
+ /// Saves the current profile.
+ ///
+ void SaveProfile();
+
+ ///
+ /// Asynchronously saves the current profile.
+ ///
+ /// A task representing the save action.
+ Task SaveProfileAsync();
+
+ ///
+ /// Resumes profile preview playback.
+ ///
+ void Play();
+
+ ///
+ /// Pauses profile preview playback.
+ ///
+ void Pause();
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index 0441f068f..ec9d5f05e 100644
--- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services.Interfaces;
+using Serilog;
namespace Artemis.UI.Shared.Services.ProfileEditor;
@@ -15,19 +16,27 @@ internal class ProfileEditorService : IProfileEditorService
private readonly Dictionary _profileEditorHistories = new();
private readonly BehaviorSubject _profileElementSubject = new(null);
private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero);
+ private readonly BehaviorSubject _playingSubject = new(false);
+ private readonly BehaviorSubject _suspendedEditingSubject = new(false);
private readonly BehaviorSubject _pixelsPerSecondSubject = new(300);
+ private readonly ILogger _logger;
private readonly IProfileService _profileService;
+ private readonly IModuleService _moduleService;
private readonly IWindowService _windowService;
- public ProfileEditorService(IProfileService profileService, IWindowService windowService)
+ public ProfileEditorService(ILogger logger, IProfileService profileService, IModuleService moduleService, IWindowService windowService)
{
+ _logger = logger;
_profileService = profileService;
+ _moduleService = moduleService;
_windowService = windowService;
ProfileConfiguration = _profileConfigurationSubject.AsObservable();
ProfileElement = _profileElementSubject.AsObservable();
History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory));
Time = _timeSubject.AsObservable();
+ Playing = _playingSubject.AsObservable();
+ SuspendedEditing = _suspendedEditingSubject.AsObservable();
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
}
@@ -47,10 +56,46 @@ internal class ProfileEditorService : IProfileEditorService
public IObservable ProfileElement { get; }
public IObservable History { get; }
public IObservable Time { get; }
+ public IObservable Playing { get; }
+ public IObservable SuspendedEditing { get; }
public IObservable PixelsPerSecond { get; }
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
{
+ if (ReferenceEquals(_profileConfigurationSubject.Value, profileConfiguration))
+ return;
+
+ _logger.Verbose("ChangeCurrentProfileConfiguration {profile}", profileConfiguration);
+
+ // Stop playing and save the current profile
+ Pause();
+ if (_profileConfigurationSubject.Value?.Profile != null)
+ _profileConfigurationSubject.Value.Profile.LastSelectedProfileElement = _profileElementSubject.Value;
+ SaveProfile();
+
+ // No need to deactivate the profile, if needed it will be deactivated next update
+ if (_profileConfigurationSubject.Value != null)
+ _profileConfigurationSubject.Value.IsBeingEdited = false;
+
+ // Deselect whatever profile element was active
+ ChangeCurrentProfileElement(null);
+
+ // The new profile may need activation
+ if (profileConfiguration != null)
+ {
+ profileConfiguration.IsBeingEdited = true;
+ _moduleService.SetActivationOverride(profileConfiguration.Module);
+ _profileService.ActivateProfile(profileConfiguration);
+ _profileService.RenderForEditor = true;
+
+ if (profileConfiguration.Profile?.LastSelectedProfileElement is RenderProfileElement renderProfileElement)
+ ChangeCurrentProfileElement(renderProfileElement);
+ }
+ else
+ {
+ _moduleService.SetActivationOverride(null);
+ _profileService.RenderForEditor = false;
+ }
_profileConfigurationSubject.OnNext(profileConfiguration);
}
@@ -61,6 +106,7 @@ internal class ProfileEditorService : IProfileEditorService
public void ChangeTime(TimeSpan time)
{
+ Tick(time);
_timeSubject.OnNext(time);
}
@@ -101,4 +147,48 @@ internal class ProfileEditorService : IProfileEditorService
{
await Task.Run(SaveProfile);
}
+
+ ///
+ public void Play()
+ {
+ if (!_playingSubject.Value)
+ _playingSubject.OnNext(true);
+ }
+
+ ///
+ public void Pause()
+ {
+ if (_playingSubject.Value)
+ _playingSubject.OnNext(false);
+ }
+
+ private void Tick(TimeSpan time)
+ {
+ if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value)
+ return;
+
+ TickProfileElement(_profileConfigurationSubject.Value.Profile.GetRootFolder(), time);
+ }
+
+ private void TickProfileElement(ProfileElement profileElement, TimeSpan time)
+ {
+ if (profileElement is not RenderProfileElement renderElement)
+ return;
+
+ if (renderElement.Suspended)
+ {
+ renderElement.Disable();
+ }
+ else
+ {
+ renderElement.Enable();
+ renderElement.Timeline.Override(
+ time,
+ (renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
+ );
+
+ foreach (ProfileElement child in renderElement.Children)
+ TickProfileElement(child, time);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml
index b359ef18f..302197215 100644
--- a/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml
+++ b/src/Avalonia/Artemis.UI.Shared/Styles/Artemis.axaml
@@ -24,8 +24,8 @@
-
-
+
+
diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/Condensed.axaml
similarity index 61%
rename from src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml
rename to src/Avalonia/Artemis.UI.Shared/Styles/Condensed.axaml
index 5ce52297f..a2aad151b 100644
--- a/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml
+++ b/src/Avalonia/Artemis.UI.Shared/Styles/Condensed.axaml
@@ -1,6 +1,7 @@
+ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls">
@@ -24,6 +25,9 @@
Bluheheheheh
Bluhgfdgdsheheh
+
+
+
@@ -31,19 +35,34 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
index e9c0b05c0..f6b0cd9c0 100644
--- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj
+++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/Avalonia/Artemis.UI/Assets/Images/Logo/application.ico b/src/Avalonia/Artemis.UI/Assets/Images/Logo/application.ico
new file mode 100644
index 000000000..bdb89dbd0
Binary files /dev/null and b/src/Avalonia/Artemis.UI/Assets/Images/Logo/application.ico differ
diff --git a/src/Avalonia/Artemis.UI/Converters/SKColorToColor2Converter.cs b/src/Avalonia/Artemis.UI/Converters/SKColorToColor2Converter.cs
new file mode 100644
index 000000000..bd27466a6
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Converters/SKColorToColor2Converter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Globalization;
+using Avalonia.Data.Converters;
+using FluentAvalonia.UI.Media;
+using SkiaSharp;
+
+namespace Artemis.UI.Converters;
+
+///
+/// Converts into .
+///
+public class SKColorToColor2Converter : IValueConverter
+{
+ ///
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is SKColor skColor)
+ return new Color2(skColor.Red, skColor.Green, skColor.Blue, skColor.Alpha);
+ return new Color2();
+ }
+
+ ///
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is Color2 color2)
+ return new SKColor(color2.R, color2.G, color2.B, color2.A);
+ return SKColor.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Converters/SKColorToStringConverter.cs b/src/Avalonia/Artemis.UI/Converters/SKColorToStringConverter.cs
new file mode 100644
index 000000000..e7707d8f1
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Converters/SKColorToStringConverter.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Globalization;
+using Avalonia.Data.Converters;
+using SkiaSharp;
+
+namespace Artemis.UI.Converters;
+
+///
+///
+/// Converts into .
+///
+public class SKColorToStringConverter : IValueConverter
+{
+ ///
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value?.ToString();
+ }
+
+ ///
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (string.IsNullOrWhiteSpace(value as string))
+ return SKColor.Empty;
+
+ return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml
index 84c152c51..8452fec0f 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml
@@ -2,7 +2,8 @@
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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView">
- TODO
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs
index 60a4fbc8a..7d98828d3 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs
@@ -1,13 +1,31 @@
-using Artemis.Core;
+using System;
+using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput;
+using ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput;
public class BoolPropertyInputViewModel : PropertyInputViewModel
{
+ private BooleanOptions _selectedBooleanOption;
+
public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
+ this.WhenAnyValue(vm => vm.InputValue).Subscribe(v => SelectedBooleanOption = v ? BooleanOptions.True : BooleanOptions.False);
+ this.WhenAnyValue(vm => vm.SelectedBooleanOption).Subscribe(v => InputValue = v == BooleanOptions.True);
}
+
+ public BooleanOptions SelectedBooleanOption
+ {
+ get => _selectedBooleanOption;
+ set => this.RaiseAndSetIfChanged(ref _selectedBooleanOption, value);
+ }
+}
+
+public enum BooleanOptions
+{
+ True,
+ False
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
index adab728e6..ec3832869 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml
@@ -23,7 +23,7 @@
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
index 796b1e439..41059cafa 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs
@@ -66,6 +66,16 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel _windowService.CreateContentDialog().WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush)).ShowAsync());
}
+ #region Overrides of PropertyInputViewModel
+
+ ///
+ protected override void OnInputValueChanged()
+ {
+ this.RaisePropertyChanged(nameof(SelectedDescriptor));
+ }
+
+ #endregion
+
private void UpdateDescriptorsIfChanged(PluginFeatureEventArgs e)
{
if (e.PluginFeature is not LayerBrushProvider)
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml
index c8d1d0490..f04e9e77c 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml
@@ -2,7 +2,8 @@
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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView">
- TODO
-
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml
index 7ac5dc871..233631978 100644
--- a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml
+++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml
@@ -2,7 +2,31 @@
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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:converters="clr-namespace:Artemis.UI.Converters"
+ xmlns:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView">
- TODO
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/MainWindow.axaml b/src/Avalonia/Artemis.UI/MainWindow.axaml
index fb57c2f6f..590aef1a0 100644
--- a/src/Avalonia/Artemis.UI/MainWindow.axaml
+++ b/src/Avalonia/Artemis.UI/MainWindow.axaml
@@ -5,7 +5,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.MainWindow"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0">
diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
index ffbda5e3a..38b9dd0fc 100644
--- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -1,5 +1,7 @@
using System.Collections.ObjectModel;
using Artemis.Core;
+using Artemis.Core.LayerBrushes;
+using Artemis.Core.LayerEffects;
using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.ProfileEditor;
@@ -67,6 +69,8 @@ namespace Artemis.UI.Ninject.Factories
{
ProfileElementPropertyViewModel ProfileElementPropertyViewModel(ILayerProperty layerProperty);
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
+ ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
+ ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
TreeGroupViewModel TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel);
// TimelineGroupViewModel TimelineGroupViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
diff --git a/src/Avalonia/Artemis.UI/Screens/Debugger/DebugView.axaml b/src/Avalonia/Artemis.UI/Screens/Debugger/DebugView.axaml
index 6c7eff869..325cd5362 100644
--- a/src/Avalonia/Artemis.UI/Screens/Debugger/DebugView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/Debugger/DebugView.axaml
@@ -7,7 +7,7 @@
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Debugger.DebugView"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Debugger"
Width="1200"
Height="800">
diff --git a/src/Avalonia/Artemis.UI/Screens/Device/DevicePropertiesView.axaml b/src/Avalonia/Artemis.UI/Screens/Device/DevicePropertiesView.axaml
index 96cdfc9b7..7a27ae925 100644
--- a/src/Avalonia/Artemis.UI/Screens/Device/DevicePropertiesView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/Device/DevicePropertiesView.axaml
@@ -6,7 +6,7 @@
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.DevicePropertiesView"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Device Properties"
Width="1250"
Height="900">
diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml
index 988cb4734..7bfea49dc 100644
--- a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml
@@ -5,7 +5,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsWindowView"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Title="{Binding DisplayName}"
Width="800"
Height="800"
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml
new file mode 100644
index 000000000..e180d3ab9
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Don't repeat the timeline
+
+
+ This setting only applies to the editor and does not affect the repeat mode during normal profile playback
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml.cs
new file mode 100644
index 000000000..ed5b99c6b
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.Playback
+{
+ public partial class PlaybackView : ReactiveUserControl
+ {
+ public PlaybackView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs
new file mode 100644
index 000000000..70c099028
--- /dev/null
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Services.ProfileEditor;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.ProfileEditor.Playback;
+
+public class PlaybackViewModel : ActivatableViewModelBase
+{
+ private readonly ICoreService _coreService;
+ private readonly IProfileEditorService _profileEditorService;
+ private readonly ISettingsService _settingsService;
+ private RenderProfileElement? _profileElement;
+ private ObservableAsPropertyHelper? _currentTime;
+ private ObservableAsPropertyHelper? _formattedCurrentTime;
+ private ObservableAsPropertyHelper? _playing;
+ private bool _repeating;
+ private bool _repeatTimeline;
+ private bool _repeatSegment;
+
+ public PlaybackViewModel(ICoreService coreService, IProfileEditorService profileEditorService, ISettingsService settingsService)
+ {
+ _coreService = coreService;
+ _profileEditorService = profileEditorService;
+ _settingsService = settingsService;
+
+ this.WhenActivated(d =>
+ {
+ _profileEditorService.ProfileElement.Subscribe(e => _profileElement = e).DisposeWith(d);
+ _currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d);
+ _formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d);
+ _playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d);
+
+ Observable.FromEventPattern(x => coreService.FrameRendering += x, x => coreService.FrameRendering -= x)
+ .Subscribe(e => CoreServiceOnFrameRendering(e.EventArgs))
+ .DisposeWith(d);
+ });
+ }
+
+ public TimeSpan CurrentTime => _currentTime?.Value ?? TimeSpan.Zero;
+ public string? FormattedCurrentTime => _formattedCurrentTime?.Value;
+ public bool Playing => _playing?.Value ?? false;
+
+ public bool Repeating
+ {
+ get => _repeating;
+ set => this.RaiseAndSetIfChanged(ref _repeating, value);
+ }
+
+ public bool RepeatTimeline
+ {
+ get => _repeatTimeline;
+ set => this.RaiseAndSetIfChanged(ref _repeatTimeline, value);
+ }
+
+ public bool RepeatSegment
+ {
+ get => _repeatSegment;
+ set => this.RaiseAndSetIfChanged(ref _repeatSegment, value);
+ }
+
+ public void PlayFromStart()
+ {
+ GoToStart();
+ if (!Playing)
+ _profileEditorService.Play();
+ }
+
+ public void TogglePlay()
+ {
+ if (!Playing)
+ _profileEditorService.Play();
+ else
+ _profileEditorService.Pause();
+ }
+
+ public void GoToStart()
+ {
+ _profileEditorService.ChangeTime(TimeSpan.Zero);
+ }
+
+ public void GoToEnd()
+ {
+ if (_profileElement == null)
+ return;
+
+ _profileEditorService.ChangeTime(_profileElement.Timeline.EndSegmentEndPosition);
+ }
+
+ public void GoToPreviousFrame()
+ {
+ if (_profileElement == null)
+ return;
+
+ double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
+ double newTime = Math.Max(0, Math.Round((CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
+ _profileEditorService.ChangeTime(TimeSpan.FromMilliseconds(newTime));
+ }
+
+ public void GoToNextFrame()
+ {
+ if (_profileElement == null)
+ return;
+
+ double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
+ double newTime = Math.Round((CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
+ newTime = Math.Min(newTime, _profileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds);
+ _profileEditorService.ChangeTime(TimeSpan.FromMilliseconds(newTime));
+ }
+
+ public void CycleRepeating()
+ {
+ if (!Repeating)
+ {
+ RepeatTimeline = true;
+ RepeatSegment = false;
+ Repeating = true;
+ }
+ else if (RepeatTimeline)
+ {
+ RepeatTimeline = false;
+ RepeatSegment = true;
+ }
+ else if (RepeatSegment)
+ {
+ RepeatTimeline = true;
+ RepeatSegment = false;
+ Repeating = false;
+ }
+ }
+
+ private TimeSpan GetCurrentSegmentStart()
+ {
+ if (_profileElement == null)
+ return TimeSpan.Zero;
+
+ if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
+ return TimeSpan.Zero;
+ if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
+ return _profileElement.Timeline.MainSegmentStartPosition;
+ if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
+ return _profileElement.Timeline.EndSegmentStartPosition;
+
+ return TimeSpan.Zero;
+ }
+
+ private TimeSpan GetCurrentSegmentEnd()
+ {
+ if (_profileElement == null)
+ return TimeSpan.Zero;
+
+ if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
+ return _profileElement.Timeline.StartSegmentEndPosition;
+ if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
+ return _profileElement.Timeline.MainSegmentEndPosition;
+ if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
+ return _profileElement.Timeline.EndSegmentEndPosition;
+
+ return TimeSpan.Zero;
+ }
+
+ private void CoreServiceOnFrameRendering(FrameRenderingEventArgs e)
+ {
+ if (!Playing)
+ return;
+
+ TimeSpan newTime = CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
+ if (_profileElement != null)
+ {
+ if (Repeating && RepeatTimeline)
+ {
+ if (newTime > _profileElement.Timeline.Length)
+ newTime = TimeSpan.Zero;
+ }
+ else if (Repeating && RepeatSegment)
+ {
+ if (newTime > GetCurrentSegmentEnd())
+ newTime = GetCurrentSegmentStart();
+ }
+ else if (newTime > _profileElement.Timeline.Length)
+ {
+ newTime = _profileElement.Timeline.Length;
+ _profileEditorService.Pause();
+ }
+ }
+
+ _profileEditorService.ChangeTime(newTime);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml
index 031ee5c09..de14a7b71 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml
@@ -3,18 +3,45 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties"
+ xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView">
-
-
-
-
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs
index ae979f444..69bd34970 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml.cs
@@ -1,18 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
-namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties
-{
- public partial class ProfileElementPropertiesView : ReactiveUserControl
- {
- public ProfileElementPropertiesView()
- {
- InitializeComponent();
- }
+namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
+public class ProfileElementPropertiesView : ReactiveUserControl
+{
+ public ProfileElementPropertiesView()
+ {
+ InitializeComponent();
}
-}
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs
index 93e518660..5e396dd9b 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs
@@ -6,9 +6,10 @@ using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
+using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.UI.Ninject.Factories;
-using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
+using Artemis.UI.Screens.ProfileEditor.Playback;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI;
@@ -17,20 +18,18 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
{
+ private readonly Dictionary _cachedViewModels;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
- private readonly IProfileEditorService _profileEditorService;
- private ProfileElementPropertyGroupViewModel? _brushPropertyGroup;
private ObservableAsPropertyHelper? _profileElement;
- private readonly Dictionary> _profileElementGroups;
private ObservableCollection _propertyGroupViewModels;
///
- public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory)
+ public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel)
{
- _profileEditorService = profileEditorService;
_layerPropertyVmFactory = layerPropertyVmFactory;
- PropertyGroupViewModels = new ObservableCollection();
- _profileElementGroups = new Dictionary>();
+ _propertyGroupViewModels = new ObservableCollection();
+ _cachedViewModels = new Dictionary();
+ PlaybackViewModel = playbackViewModel;
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
this.WhenAnyValue(vm => vm.ProfileElement)
@@ -47,10 +46,11 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
.Subscribe(_ => UpdateGroups());
// React to service profile element changes as long as the VM is active
- this.WhenActivated(d => _profileElement = _profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d));
+ this.WhenActivated(d => _profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d));
this.WhenAnyValue(vm => vm.ProfileElement).Subscribe(_ => UpdateGroups());
}
+ public PlaybackViewModel PlaybackViewModel { get; }
public RenderProfileElement? ProfileElement => _profileElement?.Value;
public Layer? Layer => _profileElement?.Value as Layer;
@@ -68,57 +68,51 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
return;
}
- if (!_profileElementGroups.TryGetValue(ProfileElement, out List? viewModels))
- {
- viewModels = new List();
- _profileElementGroups[ProfileElement] = viewModels;
- }
-
- List groups = new();
-
+ ObservableCollection viewModels = new();
if (Layer != null)
{
- // Add default layer groups
- groups.Add(Layer.General);
- groups.Add(Layer.Transform);
- // Add brush group
+ // Add base VMs
+ viewModels.Add(GetOrCreateViewModel(Layer.General, null, null));
+ viewModels.Add(GetOrCreateViewModel(Layer.Transform, null, null));
+
+ // Add brush VM if the brush has properties
if (Layer.LayerBrush?.BaseProperties != null)
- groups.Add(Layer.LayerBrush.BaseProperties);
+ viewModels.Add(GetOrCreateViewModel(Layer.LayerBrush.BaseProperties, Layer.LayerBrush, null));
}
- // Add effect groups
- foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects)
- {
+ // Add effect VMs
+ foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects.OrderBy(e => e.Order))
if (layerEffect.BaseProperties != null)
- groups.Add(layerEffect.BaseProperties);
- }
+ viewModels.Add(GetOrCreateViewModel(layerEffect.BaseProperties, null, layerEffect));
- // Remove redundant VMs
- viewModels.RemoveAll(vm => !groups.Contains(vm.LayerPropertyGroup));
-
- // Create VMs for missing groups
- foreach (LayerPropertyGroup group in groups)
+ // Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
+ for (int index = 0; index < viewModels.Count; index++)
{
- if (viewModels.All(vm => vm.LayerPropertyGroup != group))
- viewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(group));
+ ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel = viewModels[index];
+ if (index > PropertyGroupViewModels.Count - 1)
+ PropertyGroupViewModels.Add(profileElementPropertyGroupViewModel);
+ else if (!ReferenceEquals(PropertyGroupViewModels[index], profileElementPropertyGroupViewModel))
+ PropertyGroupViewModels[index] = profileElementPropertyGroupViewModel;
}
- // Get all non-effect properties
- List nonEffectProperties = viewModels
- .Where(l => l.TreeGroupViewModel.GroupType != LayerPropertyGroupType.LayerEffectRoot)
- .ToList();
- // Order the effects
- List effectProperties = viewModels
- .Where(l => l.TreeGroupViewModel.GroupType == LayerPropertyGroupType.LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
- .OrderBy(l => l.LayerPropertyGroup.LayerEffect?.Order)
- .ToList();
+ while (PropertyGroupViewModels.Count > viewModels.Count)
+ PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
+ }
- ObservableCollection propertyGroupViewModels = new();
- foreach (ProfileElementPropertyGroupViewModel viewModel in nonEffectProperties)
- propertyGroupViewModels.Add(viewModel);
- foreach (ProfileElementPropertyGroupViewModel viewModel in effectProperties)
- propertyGroupViewModels.Add(viewModel);
+ private ProfileElementPropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
+ {
+ if (_cachedViewModels.TryGetValue(layerPropertyGroup, out ProfileElementPropertyGroupViewModel? cachedVm))
+ return cachedVm;
- PropertyGroupViewModels = propertyGroupViewModels;
+ ProfileElementPropertyGroupViewModel createdVm;
+ if (layerBrush != null)
+ createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerBrush);
+ else if (layerEffect != null)
+ createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerEffect);
+ else
+ createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup);
+
+ _cachedViewModels[layerPropertyGroup] = createdVm;
+ return createdVm;
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs
index 081c3209c..a3b6426a3 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs
@@ -3,6 +3,8 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Artemis.Core;
+using Artemis.Core.LayerBrushes;
+using Artemis.Core.LayerEffects;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
using Artemis.UI.Shared;
@@ -33,8 +35,23 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
PopulateChildren();
}
+ public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerBrush layerBrush)
+ : this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
+ {
+ LayerBrush = layerBrush;
+ }
+
+ public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerEffect layerEffect)
+ : this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
+ {
+ LayerEffect = layerEffect;
+ }
+
public ObservableCollection Children { get; }
public LayerPropertyGroup LayerPropertyGroup { get; }
+ public BaseLayerBrush? LayerBrush { get; }
+ public BaseLayerEffect? LayerEffect { get; }
+
public TreeGroupViewModel TreeGroupViewModel { get; }
public bool IsVisible
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs
index 16fcc9eea..15e9f84a3 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/ITreePropertyViewModel.cs
@@ -1,9 +1,11 @@
-using ReactiveUI;
+using Artemis.Core;
+using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
public interface ITreePropertyViewModel : IReactiveObject
{
+ ILayerProperty BaseLayerProperty { get; }
bool HasDataBinding { get; }
double GetDepth();
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml
index 0ccb5b649..5f693289b 100644
--- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml
@@ -70,24 +70,24 @@
Brush -
+ IsVisible="{Binding LayerBrush.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
Extra options available!
@@ -108,31 +108,31 @@
-
+
Effect
-
+ IsVisible="{Binding !LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
+ IsVisible="{Binding LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
@@ -160,7 +160,7 @@
Height="24"
VerticalAlignment="Center"
Command="{Binding OpenEffectSettings}"
- IsVisible="{Binding LayerPropertyGroup.LayerEffect.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
+ IsVisible="{Binding LayerEffect.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
index 8a35ed42a..e88ca311d 100644
--- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs
@@ -126,7 +126,7 @@ namespace Artemis.UI.Screens.Root
{
_trayIcon = new TrayIcon
{
- Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/bow.ico"))),
+ Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/application.ico"))),
Command = ReactiveCommand.Create(OpenMainWindow)
};
_trayIcon.Menu = (NativeMenu?) Application.Current!.FindResource("TrayIconMenu");
diff --git a/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml
index 37211a912..accd393f5 100644
--- a/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/Root/SplashView.axaml
@@ -5,7 +5,7 @@
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Root.SplashView"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0"
Height="450"
Width="400"
diff --git a/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml b/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml
index eca9aa119..b79452edb 100644
--- a/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml
@@ -12,7 +12,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850"
x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView"
Title="{Binding DisplayName}"
- Icon="/Assets/Images/Logo/bow.ico"
+ Icon="/Assets/Images/Logo/application.ico"
Width="800"
MinWidth="420"
Height="850">