From 2bf36fbf2092451d46e523c8642d83f7db29921b Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 23 Apr 2022 17:07:04 +0200 Subject: [PATCH] Core - Refactored effects Profile editor - Added effect creation --- src/.idea/.idea.Artemis/.idea/avalonia.xml | 7 + src/.idea/.idea.Artemis/.idea/misc.xml | 8 + src/Artemis.Core/Extensions/TypeExtensions.cs | 2 +- src/Artemis.Core/Models/Profile/Folder.cs | 43 +- src/Artemis.Core/Models/Profile/Layer.cs | 36 +- .../Profile/LayerEffectPropertyGroup.cs | 25 + .../Profile/LayerProperties/LayerProperty.cs | 2 +- .../Models/Profile/LayerPropertyGroup.cs | 11 +- .../Models/Profile/RenderProfileElement.cs | 762 +++++++++--------- .../Models/Surface/ArtemisDevice.cs | 2 +- .../Internal/PropertiesLayerBrush.cs | 4 +- .../Plugins/LayerBrushes/LayerBrush.cs | 2 +- .../LayerBrushes/LayerBrushDescriptor.cs | 2 - .../Plugins/LayerBrushes/PerLedLayerBrush.cs | 2 +- .../LayerEffects/Internal/BaseLayerEffect.cs | 32 +- .../Plugins/LayerEffects/LayerEffect.cs | 27 +- .../LayerEffects/LayerEffectDescriptor.cs | 193 +++-- .../Placeholder/PlaceholderLayerEffect.cs | 3 +- .../PlaceholderLayerEffectDescriptor.cs | 6 +- src/Artemis.Core/Plugins/Modules/Module.cs | 6 +- .../Plugins/Settings/PluginSettings.cs | 2 +- .../Services/Input/InputService.cs | 2 +- .../EndPoints/DataModelJsonPluginEndPoint.cs | 2 +- .../WebServer/Interfaces/IWebServerService.cs | 2 +- .../Services/WebServer/WebServerService.cs | 6 +- .../Internal/DataBindingExitNode.cs | 4 +- .../Profile/Abstract/RenderElementEntity.cs | 3 - .../Entities/Profile/FolderEntity.cs | 1 - .../Entities/Profile/LayerEffectEntity.cs | 2 - .../Entities/Profile/LayerEntity.cs | 1 - .../M0002ProfileEntitiesEnabledMigration.cs | 10 +- src/Artemis.UI.Linux/packages.lock.json | 8 +- src/Artemis.UI.MacOS/packages.lock.json | 8 +- .../Artemis.UI.Shared.csproj | 8 +- .../Controls/ArtemisIcon.axaml | 1 - .../Controls/ArtemisIcon.axaml.cs | 8 +- .../Display/DefaultDataModelDisplayView.xaml | 47 -- .../ProfileEditor/Commands/AddLayerEffect.cs | 48 ++ src/Artemis.UI.Shared/packages.lock.json | 6 +- src/Artemis.UI.Windows/packages.lock.json | 8 +- src/Artemis.UI/Artemis.UI.csproj | 2 +- src/Artemis.UI/ArtemisBootstrapper.cs | 3 +- .../ColorGradientPropertyInputViewModel.cs | 10 +- .../FloatPropertyInputViewModel.cs | 7 +- .../FloatRangePropertyInputViewModel.cs | 11 +- .../IntPropertyInputViewModel.cs | 7 +- .../IntRangePropertyInputViewModel.cs | 11 +- .../Screens/Device/DeviceSettingsViewModel.cs | 2 +- .../Properties/Dialogs/AddEffectView.axaml | 64 ++ .../Properties/Dialogs/AddEffectView.axaml.cs | 28 + .../Properties/Dialogs/AddEffectViewModel.cs | 62 ++ .../Panels/Properties/PropertiesView.axaml | 33 +- .../Panels/Properties/PropertiesViewModel.cs | 25 +- .../Properties/PropertyGroupViewModel.cs | 30 +- .../Properties/Tree/TreeGroupView.axaml | 46 +- .../Settings/Tabs/GeneralTabView.axaml | 4 +- .../Settings/Tabs/GeneralTabViewModel.cs | 36 +- .../Settings/Tabs/RenderSettingViewModel.cs | 13 + src/Artemis.UI/packages.lock.json | 8 +- .../packages.lock.json | 6 +- src/Artemis.sln | 8 + 61 files changed, 1005 insertions(+), 763 deletions(-) create mode 100644 src/.idea/.idea.Artemis/.idea/misc.xml create mode 100644 src/Artemis.Core/Models/Profile/LayerEffectPropertyGroup.cs delete mode 100644 src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml create mode 100644 src/Artemis.UI.Shared/Services/ProfileEditor/Commands/AddLayerEffect.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectViewModel.cs create mode 100644 src/Artemis.UI/Screens/Settings/Tabs/RenderSettingViewModel.cs diff --git a/src/.idea/.idea.Artemis/.idea/avalonia.xml b/src/.idea/.idea.Artemis/.idea/avalonia.xml index cdb3f426a..663884d07 100644 --- a/src/.idea/.idea.Artemis/.idea/avalonia.xml +++ b/src/.idea/.idea.Artemis/.idea/avalonia.xml @@ -14,6 +14,13 @@ + + + + + + + diff --git a/src/.idea/.idea.Artemis/.idea/misc.xml b/src/.idea/.idea.Artemis/.idea/misc.xml new file mode 100644 index 000000000..283b9b4d4 --- /dev/null +++ b/src/.idea/.idea.Artemis/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index e8c25e05a..748e4dc51 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -230,7 +230,7 @@ namespace Artemis.Core } if (genericType.IsInterface) - foreach (var i in typeToCheck.GetInterfaces()) + foreach (Type i in typeToCheck.GetInterfaces()) if (i.IsOfGenericType(genericType, out concreteGenericType)) return true; diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 8ffbaec64..afc95727b 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -227,19 +227,29 @@ namespace Artemis.Core /// public override void Enable() { - if (Enabled) - return; - + // No checks here, effects will do their own checks to ensure they never enable twice + // Also not enabling children, they'll enable themselves during their own Update foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.InternalEnable(); + Enabled = true; + } + + /// + public override void Disable() + { + // No checks here, effects will do their own checks to ensure they never disable twice + foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + baseLayerEffect.InternalDisable(); + + // Disabling children since their Update won't get called with their parent disabled foreach (ProfileElement profileElement in Children) { - if (profileElement is RenderProfileElement renderProfileElement && renderProfileElement.ShouldBeEnabled) - renderProfileElement.Enable(); + if (profileElement is RenderProfileElement renderProfileElement) + renderProfileElement.Disable(); } - Enabled = true; + Enabled = false; } /// @@ -250,24 +260,6 @@ namespace Artemis.Core baseLayerEffect.InternalUpdate(Timeline); ; } - /// - public override void Disable() - { - if (!Enabled) - return; - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects) - baseLayerEffect.InternalDisable(); - - foreach (ProfileElement profileElement in Children) - { - if (profileElement is RenderProfileElement renderProfileElement) - renderProfileElement.Disable(); - } - - Enabled = false; - } - /// /// Occurs when a property affecting the rendering properties of this folder has been updated /// @@ -308,7 +300,6 @@ namespace Artemis.Core internal override void Load() { - ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups); Reset(); // Load child folders @@ -340,8 +331,6 @@ namespace Artemis.Core FolderEntity.Suspended = Suspended; FolderEntity.ProfileId = Profile.EntityId; - FolderEntity.ExpandedPropertyGroups.Clear(); - FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups); SaveRenderElement(); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 79099830f..4153c6cd8 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -296,7 +296,6 @@ namespace Artemis.Core Suspended = LayerEntity.Suspended; Order = LayerEntity.Order; - ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups); LoadRenderElement(); Adapter.Load(); } @@ -313,8 +312,6 @@ namespace Artemis.Core LayerEntity.Suspended = Suspended; LayerEntity.Name = Name; LayerEntity.ProfileId = Profile.EntityId; - LayerEntity.ExpandedPropertyGroups.Clear(); - LayerEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups); General.ApplyToEntity(); Transform.ApplyToEntity(); @@ -382,7 +379,7 @@ namespace Artemis.Core UpdateDisplayCondition(); UpdateTimeline(deltaTime); - + if (ShouldBeEnabled) Enable(); else if (Timeline.IsFinished && !_renderCopies.Any()) @@ -505,9 +502,7 @@ namespace Artemis.Core /// public override void Enable() { - if (Enabled) - return; - + // No checks here, the brush and effects will do their own checks to ensure they never enable twice bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush"); if (!tryOrBreak) return; @@ -519,10 +514,21 @@ namespace Artemis.Core }, "Failed to enable one or more effects"); if (!tryOrBreak) return; - + Enabled = true; } + /// + public override void Disable() + { + // No checks here, the brush and effects will do their own checks to ensure they never disable twice + LayerBrush?.InternalDisable(); + foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + baseLayerEffect.InternalDisable(); + + Enabled = false; + } + /// public override void OverrideTimelineAndApply(TimeSpan position) { @@ -536,19 +542,6 @@ namespace Artemis.Core baseLayerEffect.InternalUpdate(Timeline); } - /// - public override void Disable() - { - if (!Enabled) - return; - - LayerBrush?.InternalDisable(); - foreach (BaseLayerEffect baseLayerEffect in LayerEffects) - baseLayerEffect.InternalDisable(); - - Enabled = false; - } - /// public override void Reset() { @@ -822,6 +815,7 @@ namespace Artemis.Core General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; Transform.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; + LayerBrush?.Update(0); OnLayerBrushUpdated(); ClearBrokenState("Failed to initialize layer brush"); diff --git a/src/Artemis.Core/Models/Profile/LayerEffectPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerEffectPropertyGroup.cs new file mode 100644 index 000000000..5e1b6abb7 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerEffectPropertyGroup.cs @@ -0,0 +1,25 @@ +namespace Artemis.Core +{ + /// + /// Represents a property group on a layer + /// + /// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will + /// initialize these for you. + /// + /// + public abstract class LayerEffectPropertyGroup : LayerPropertyGroup + { + /// + /// Whether or not this layer effect is enabled + /// + [PropertyDescription(Name = "Enabled", Description = "Whether or not this layer effect is enabled")] + public BoolLayerProperty IsEnabled { get; set; } = null!; + + internal void InitializeIsEnabled() + { + IsEnabled.DefaultValue = true; + if (!IsEnabled.IsLoadedFromStorage) + IsEnabled.SetCurrentValue(true); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index f671d6a07..fa1e96f0e 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -203,7 +203,7 @@ namespace Artemis.Core /// or existing keyframe. /// /// The keyframe if one was created or updated. - public LayerPropertyKeyframe? SetCurrentValue(T value, TimeSpan? time) + public LayerPropertyKeyframe? SetCurrentValue(T value, TimeSpan? time = null) { if (_disposed) throw new ObjectDisposedException("LayerProperty"); diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 3cdbeec17..6a80f1ac8 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -249,6 +249,15 @@ namespace Artemis.Core layerPropertyGroup.Update(timeline); } + internal void MoveLayerProperty(ILayerProperty layerProperty, int index) + { + if (!_layerProperties.Contains(layerProperty)) + return; + + _layerProperties.Remove(layerProperty); + _layerProperties.Insert(index, layerProperty); + } + internal virtual void OnVisibilityChanged() { VisibilityChanged?.Invoke(this, EventArgs.Empty); @@ -292,7 +301,7 @@ namespace Artemis.Core 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); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 9064c1978..97fc1489e 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -4,393 +4,393 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects.Placeholder; -using Artemis.Core.Properties; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; using SkiaSharp; -namespace Artemis.Core +namespace Artemis.Core; + +/// +/// Represents an element of a that has advanced rendering capabilities +/// +public abstract class RenderProfileElement : ProfileElement { - /// - /// Represents an element of a that has advanced rendering capabilities - /// - public abstract class RenderProfileElement : ProfileElement + private SKRectI _bounds; + private SKPath? _path; + + internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile) { - private SKRectI _bounds; - private SKPath? _path; - - internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile) - { - Timeline = new Timeline(); - ExpandedPropertyGroups = new List(); - LayerEffectsList = new List(); - LayerEffects = new ReadOnlyCollection(LayerEffectsList); - Parent = parent ?? throw new ArgumentNullException(nameof(parent)); - _displayCondition = new AlwaysOnCondition(this); - - LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; - LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; - } - - /// - /// Gets a boolean indicating whether this render element and its layers/brushes are enabled - /// - public bool Enabled { get; protected set; } - - /// - /// Gets a boolean indicating whether this render element and its layers/brushes should be enabled - /// - public abstract bool ShouldBeEnabled { get; } - - /// - /// Creates a list of all layer properties present on this render element - /// - /// A list of all layer properties present on this render element - public abstract List GetAllLayerProperties(); - - /// - /// Occurs when a layer effect has been added or removed to this render element - /// - public event EventHandler? LayerEffectsUpdated; - - /// - protected override void Dispose(bool disposing) - { - LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded; - LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved; - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects) - baseLayerEffect.Dispose(); - - if (DisplayCondition is IDisposable disposable) - disposable.Dispose(); - - base.Dispose(disposing); - } - - internal void LoadRenderElement() - { - Timeline = RenderElementEntity.Timeline != null - ? new Timeline(RenderElementEntity.Timeline) - : new Timeline(); - - DisplayCondition = RenderElementEntity.DisplayCondition switch - { - AlwaysOnConditionEntity entity => new AlwaysOnCondition(entity, this), - PlayOnceConditionEntity entity => new PlayOnceCondition(entity, this), - StaticConditionEntity entity => new StaticCondition(entity, this), - EventConditionEntity entity => new EventCondition(entity, this), - _ => DisplayCondition - }; - - ActivateEffects(); - } - - internal void SaveRenderElement() - { - RenderElementEntity.LayerEffects.Clear(); - foreach (BaseLayerEffect baseLayerEffect in LayerEffects) - { - baseLayerEffect.Save(); - RenderElementEntity.LayerEffects.Add(baseLayerEffect.LayerEffectEntity); - } - - // Condition - DisplayCondition?.Save(); - RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; - - // Timeline - RenderElementEntity.Timeline = Timeline?.Entity; - Timeline?.Save(); - } - - internal void LoadNodeScript() - { - if (DisplayCondition is INodeScriptCondition scriptCondition) - scriptCondition.LoadNodeScript(); - - foreach (ILayerProperty layerProperty in GetAllLayerProperties()) - layerProperty.BaseDataBinding.LoadNodeScript(); - } - - internal void OnLayerEffectsUpdated() - { - LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); - } - - #region Timeline - - /// - /// Gets the timeline associated with this render element - /// - public Timeline Timeline { get; private set; } - - /// - /// Updates the according to the provided and current display condition - /// - protected void UpdateTimeline(double deltaTime) - { - DisplayCondition.UpdateTimeline(deltaTime); - } - - #endregion - - #region Properties - - internal abstract RenderElementEntity RenderElementEntity { get; } - - /// - /// Gets the parent of this element - /// - public new ProfileElement Parent - { - get => base.Parent!; - internal set - { - base.Parent = value; - OnPropertyChanged(nameof(Parent)); - } - } - - /// - /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is - /// clipped. - /// - public SKPath? Path - { - get => _path; - protected set - { - SetAndNotify(ref _path, value); - // I can't really be sure about the performance impact of calling Bounds often but - // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive - Bounds = SKRectI.Round(value?.Bounds ?? SKRect.Empty); - } - } - - /// - /// The bounds of this entity - /// - public SKRectI Bounds - { - get => _bounds; - private set => SetAndNotify(ref _bounds, value); - } - - - #region Property group expansion - - internal List ExpandedPropertyGroups; - - /// - /// Determines whether the provided property group is expanded - /// - /// The property group to check - /// A boolean indicating whether the provided property group is expanded - public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup) - { - return ExpandedPropertyGroups.Contains(layerPropertyGroup.Path); - } - - /// - /// Expands or collapses the provided property group - /// - /// The group to expand or collapse - /// Whether to expand or collapse the property group - public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded) - { - if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup)) - ExpandedPropertyGroups.Remove(layerPropertyGroup.Path); - else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup)) - ExpandedPropertyGroups.Add(layerPropertyGroup.Path); - } - - #endregion - - #endregion - - #region State - - /// - /// Enables the render element and its brushes and effects - /// - public abstract void Disable(); - - /// - /// Disables the render element and its brushes and effects - /// - public abstract void Enable(); - - #endregion - - #region Effect management - - internal readonly List LayerEffectsList; - - /// - /// Gets a read-only collection of the layer effects on this entity - /// - public ReadOnlyCollection LayerEffects { get; } - - /// - /// Adds a the layer effect described inthe provided - /// - public void AddLayerEffect(LayerEffectDescriptor descriptor) - { - if (descriptor == null) - throw new ArgumentNullException(nameof(descriptor)); - - LayerEffectEntity entity = new() - { - Id = Guid.NewGuid(), - Suspended = false, - Order = LayerEffects.Count + 1 - }; - descriptor.CreateInstance(this, entity); - - OrderEffects(); - OnLayerEffectsUpdated(); - } - - /// - /// Removes the provided layer - /// - /// - public void RemoveLayerEffect([NotNull] BaseLayerEffect effect) - { - if (effect == null) throw new ArgumentNullException(nameof(effect)); - - // Remove the effect from the layer and dispose it - LayerEffectsList.Remove(effect); - effect.Dispose(); - - // Update the order on the remaining effects - OrderEffects(); - OnLayerEffectsUpdated(); - } - - private void OrderEffects() - { - int index = 0; - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) - { - baseLayerEffect.Order = Order = index + 1; - index++; - } - - LayerEffectsList.Sort((a, b) => a.Order.CompareTo(b.Order)); - } - - internal void ActivateEffects() - { - foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects) - { - // If there is a non-placeholder existing effect, skip this entity - BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.LayerEffectEntity.Id == layerEffectEntity.Id); - if (existing != null && existing.Descriptor.PlaceholderFor == null) - continue; - - LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor; - if (descriptor != null) - { - // If a descriptor is found but there is an existing placeholder, remove the placeholder - if (existing != null) - { - LayerEffectsList.Remove(existing); - existing.Dispose(); - } - - // Create an instance with the descriptor - descriptor.CreateInstance(this, layerEffectEntity); - } - else if (existing == null) - { - // If no descriptor was found and there was no existing placeholder, create a placeholder - descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId); - descriptor.CreateInstance(this, layerEffectEntity); - } - } - - OrderEffects(); - } - - - internal void ActivateLayerEffect(BaseLayerEffect layerEffect) - { - LayerEffectsList.Add(layerEffect); - OnLayerEffectsUpdated(); - } - - private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e) - { - // If effects provided by the plugin are on the element, replace them with placeholders - 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.LayerEffectEntity.Id); - LayerEffectsList.Remove(pluginEffect); - pluginEffect.Dispose(); - - LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId); - descriptor.CreateInstance(this, entity); - } - } - - private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) - { - if (RenderElementEntity.LayerEffects.Any(ef => ef.ProviderId == e.Registration.PluginFeature.Id)) - ActivateEffects(); - } - - #endregion - - #region Conditions - - /// - /// Gets whether the display conditions applied to this layer where met or not during last update - /// Always true if the layer has no display conditions - /// - public bool DisplayConditionMet - { - get => _displayConditionMet; - protected set => SetAndNotify(ref _displayConditionMet, value); - } - - private bool _displayConditionMet; - - /// - /// Gets or sets the display condition used to determine whether this element is active or not - /// - public ICondition DisplayCondition - { - get => _displayCondition; - set => SetAndNotify(ref _displayCondition, value); - } - - private ICondition _displayCondition; - - /// - /// Evaluates the display conditions on this element and applies any required changes to the - /// - public void UpdateDisplayCondition() - { - if (Suspended) - { - DisplayConditionMet = false; - return; - } - - DisplayCondition.Update(); - DisplayConditionMet = DisplayCondition.IsMet; - } - - #endregion - - /// - /// Overrides the main timeline to the specified time and clears any extra time lines - /// - /// The position to set the timeline to - public abstract void OverrideTimelineAndApply(TimeSpan position); + _layerEffects = new List(); + _displayCondition = new AlwaysOnCondition(this); + Timeline = new Timeline(); + LayerEffects = new ReadOnlyCollection(_layerEffects); + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + + LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; + LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; } + + /// + /// Gets a boolean indicating whether this render element and its layers/brushes are enabled + /// + public bool Enabled { get; protected set; } + + /// + /// Gets a boolean indicating whether this render element and its layers/brushes should be enabled + /// + public abstract bool ShouldBeEnabled { get; } + + /// + /// Creates a list of all layer properties present on this render element + /// + /// A list of all layer properties present on this render element + public abstract List GetAllLayerProperties(); + + /// + /// Occurs when a layer effect has been added or removed to this render element + /// + public event EventHandler? LayerEffectsUpdated; + + /// + /// Overrides the main timeline to the specified time and clears any extra time lines + /// + /// The position to set the timeline to + public abstract void OverrideTimelineAndApply(TimeSpan position); + + /// + protected override void Dispose(bool disposing) + { + LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded; + LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved; + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + baseLayerEffect.Dispose(); + + if (DisplayCondition is IDisposable disposable) + disposable.Dispose(); + + base.Dispose(disposing); + } + + internal void LoadRenderElement() + { + Timeline = RenderElementEntity.Timeline != null + ? new Timeline(RenderElementEntity.Timeline) + : new Timeline(); + + DisplayCondition = RenderElementEntity.DisplayCondition switch + { + AlwaysOnConditionEntity entity => new AlwaysOnCondition(entity, this), + PlayOnceConditionEntity entity => new PlayOnceCondition(entity, this), + StaticConditionEntity entity => new StaticCondition(entity, this), + EventConditionEntity entity => new EventCondition(entity, this), + _ => DisplayCondition + }; + + LoadLayerEffects(); + } + + internal void SaveRenderElement() + { + RenderElementEntity.LayerEffects.Clear(); + foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + { + baseLayerEffect.Save(); + RenderElementEntity.LayerEffects.Add(baseLayerEffect.LayerEffectEntity); + } + + // Condition + DisplayCondition?.Save(); + RenderElementEntity.DisplayCondition = DisplayCondition?.Entity; + + // Timeline + RenderElementEntity.Timeline = Timeline?.Entity; + Timeline?.Save(); + } + + internal void LoadNodeScript() + { + if (DisplayCondition is INodeScriptCondition scriptCondition) + scriptCondition.LoadNodeScript(); + + foreach (ILayerProperty layerProperty in GetAllLayerProperties()) + layerProperty.BaseDataBinding.LoadNodeScript(); + } + + private void OnLayerEffectsUpdated() + { + LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); + } + + #region Timeline + + /// + /// Gets the timeline associated with this render element + /// + public Timeline Timeline { get; private set; } + + /// + /// Updates the according to the provided and current display + /// condition + /// + protected void UpdateTimeline(double deltaTime) + { + DisplayCondition.UpdateTimeline(deltaTime); + } + + #endregion + + #region Properties + + internal abstract RenderElementEntity RenderElementEntity { get; } + + /// + /// Gets the parent of this element + /// + public new ProfileElement Parent + { + get => base.Parent!; + internal set + { + base.Parent = value; + OnPropertyChanged(nameof(Parent)); + } + } + + /// + /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is + /// clipped. + /// + public SKPath? Path + { + get => _path; + protected set + { + SetAndNotify(ref _path, value); + // I can't really be sure about the performance impact of calling Bounds often but + // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive + Bounds = SKRectI.Round(value?.Bounds ?? SKRect.Empty); + } + } + + /// + /// The bounds of this entity + /// + public SKRectI Bounds + { + get => _bounds; + private set => SetAndNotify(ref _bounds, value); + } + + #endregion + + #region State + + /// + /// Enables the render element and its brushes and effects + /// + public abstract void Disable(); + + /// + /// Disables the render element and its brushes and effects + /// + public abstract void Enable(); + + #endregion + + #region Effect management + + private readonly List _layerEffects; + + /// + /// Gets a read-only collection of the layer effects on this entity + /// + public ReadOnlyCollection LayerEffects { get; } + + /// + /// Adds a the provided layer effect to the render profile element + /// + /// The effect to add. + public void AddLayerEffect(BaseLayerEffect layerEffect) + { + if (layerEffect == null) + throw new ArgumentNullException(nameof(layerEffect)); + + // Ensure something needs to be done + if (_layerEffects.Contains(layerEffect)) + return; + + // Make sure the layer effect is tied to this element + layerEffect.ProfileElement = this; + _layerEffects.Add(layerEffect); + + // Update the order on the effects + OrderEffects(); + OnLayerEffectsUpdated(); + } + + /// + /// Removes the provided layer effect. + /// + /// The effect to remove. + public void RemoveLayerEffect(BaseLayerEffect layerEffect) + { + if (layerEffect == null) + throw new ArgumentNullException(nameof(layerEffect)); + + // Ensure something needs to be done + if (!_layerEffects.Contains(layerEffect)) + return; + + // Remove the effect from the layer + _layerEffects.Remove(layerEffect); + + // Update the order on the remaining effects + OrderEffects(); + OnLayerEffectsUpdated(); + } + + private void LoadLayerEffects() + { + foreach (BaseLayerEffect baseLayerEffect in _layerEffects) + baseLayerEffect.Dispose(); + _layerEffects.Clear(); + + foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects.OrderBy(e => e.Order)) + LoadLayerEffect(layerEffectEntity); + } + + private void LoadLayerEffect(LayerEffectEntity layerEffectEntity) + { + LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor; + BaseLayerEffect layerEffect; + // Create an instance with the descriptor + if (descriptor != null) + { + layerEffect = descriptor.CreateInstance(this, layerEffectEntity); + } + // If no descriptor was found and there was no existing placeholder, create a placeholder + else + { + descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId); + layerEffect = descriptor.CreateInstance(this, layerEffectEntity); + } + + _layerEffects.Add(layerEffect); + } + + private void ReplaceLayerEffectWithPlaceholder(BaseLayerEffect layerEffect) + { + int index = _layerEffects.IndexOf(layerEffect); + if (index == -1) + return; + + LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffect.ProviderId); + BaseLayerEffect placeholder = descriptor.CreateInstance(this, layerEffect.LayerEffectEntity); + _layerEffects[index] = placeholder; + layerEffect.Dispose(); + OnLayerEffectsUpdated(); + } + + private void ReplacePlaceholderWithLayerEffect(PlaceholderLayerEffect placeholder) + { + int index = _layerEffects.IndexOf(placeholder); + if (index == -1) + return; + + LayerEffectDescriptor? descriptor = LayerEffectStore.Get(placeholder.OriginalEntity.ProviderId, placeholder.PlaceholderFor)?.LayerEffectDescriptor; + if (descriptor == null) + throw new ArtemisCoreException("Can't replace a placeholder effect because the real effect isn't available."); + + BaseLayerEffect layerEffect = descriptor.CreateInstance(this, placeholder.OriginalEntity); + _layerEffects[index] = layerEffect; + placeholder.Dispose(); + OnLayerEffectsUpdated(); + } + + private void OrderEffects() + { + int index = 0; + foreach (BaseLayerEffect baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) + { + baseLayerEffect.Order = Order = index + 1; + index++; + } + + _layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order)); + } + + private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e) + { + // Find effects that just got disabled and replace them with placeholders + List affectedLayerEffects = _layerEffects.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList(); + + if (!affectedLayerEffects.Any()) + return; + + foreach (BaseLayerEffect baseLayerEffect in affectedLayerEffects) + ReplaceLayerEffectWithPlaceholder(baseLayerEffect); + OnLayerEffectsUpdated(); + } + + private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) + { + // Find placeholders that just got enabled and replace them with real effects + List affectedPlaceholders = LayerEffects + .Where(l => l is PlaceholderLayerEffect ph && ph.OriginalEntity.ProviderId == e.Registration.PluginFeature.Id) + .Cast() + .ToList(); + + if (!affectedPlaceholders.Any()) + return; + + foreach (PlaceholderLayerEffect placeholderLayerEffect in affectedPlaceholders) + ReplacePlaceholderWithLayerEffect(placeholderLayerEffect); + OnLayerEffectsUpdated(); + } + + #endregion + + #region Conditions + + /// + /// Gets whether the display conditions applied to this layer where met or not during last update + /// Always true if the layer has no display conditions + /// + public bool DisplayConditionMet + { + get => _displayConditionMet; + private set => SetAndNotify(ref _displayConditionMet, value); + } + + private bool _displayConditionMet; + + /// + /// Gets or sets the display condition used to determine whether this element is active or not + /// + public ICondition DisplayCondition + { + get => _displayCondition; + set => SetAndNotify(ref _displayCondition, value); + } + + private ICondition _displayCondition; + + /// + /// Evaluates the display conditions on this element and applies any required changes to the + /// + public void UpdateDisplayCondition() + { + if (Suspended) + { + DisplayConditionMet = false; + return; + } + + DisplayCondition.Update(); + DisplayConditionMet = DisplayCondition.IsMet; + } + + #endregion } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 8c6dc49db..aa8991159 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -451,7 +451,7 @@ namespace Artemis.Core }); DeviceEntity.InputMappings.Clear(); - foreach (var (original, mapped) in InputMappings) + foreach ((ArtemisLed? original, ArtemisLed? mapped) in InputMappings) DeviceEntity.InputMappings.Add(new InputMappingEntity {OriginalLedId = (int) original.RgbLed.Id, MappedLedId = (int) mapped.RgbLed.Id}); DeviceEntity.Categories.Clear(); diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs index fca826e2d..48dc97915 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs @@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes /// /// For internal use only, please use or or instead /// - public abstract class PropertiesLayerBrush : BaseLayerBrush where T : LayerPropertyGroup + public abstract class PropertiesLayerBrush : BaseLayerBrush where T : LayerPropertyGroup, new() { private T _properties = null!; @@ -35,7 +35,7 @@ namespace Artemis.Core.LayerBrushes internal void InitializeProperties(PropertyGroupEntity? propertyGroupEntity) { - Properties = Activator.CreateInstance(); + Properties = new T(); PropertyGroupDescriptionAttribute groupDescription = new() {Identifier = "Brush", Name = Descriptor.DisplayName, Description = Descriptor.Description}; Properties.Initialize(Layer, null, groupDescription, propertyGroupEntity); PropertiesInitialized = true; diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs index 749d64bfa..9cfc502b3 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs @@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes /// Represents a brush that renders on a layer /// /// The type of brush properties - public abstract class LayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup + public abstract class LayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup, new() { /// /// Creates a new instance of the class diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index 8aa7026e0..b2fad30cc 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -69,8 +69,6 @@ namespace Artemis.Core.LayerBrushes brush.LayerBrushEntity = entity ?? new LayerBrushEntity { ProviderId = Provider.Id, BrushType = LayerBrushType.FullName }; brush.Initialize(); - brush.Update(0); - return brush; } } diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs index 479738e15..a60db4405 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs @@ -6,7 +6,7 @@ namespace Artemis.Core.LayerBrushes /// Represents a brush that renders on a per-layer basis /// /// The type of brush properties - public abstract class PerLedLayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup + public abstract class PerLedLayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup, new() { /// /// Creates a new instance of the class diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index 24c46589a..495f24dba 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -7,12 +7,11 @@ namespace Artemis.Core.LayerEffects /// /// For internal use only, please use instead /// - public abstract class BaseLayerEffect : BreakableModel, IDisposable + public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel { private ILayerEffectConfigurationDialog? _configurationDialog; private LayerEffectDescriptor _descriptor; private bool _suspended; - private Guid _entityId; private bool _hasBeenRenamed; private string _name; private int _order; @@ -51,15 +50,6 @@ namespace Artemis.Core.LayerEffects set => SetAndNotify(ref _name, value); } - /// - /// Gets or sets the suspended state, if suspended the effect is skipped in render and update - /// - public bool Suspended - { - get => _suspended; - set => SetAndNotify(ref _suspended, value); - } - /// /// Gets or sets whether the effect has been renamed by the user, if true consider refraining from changing the name /// programatically @@ -105,13 +95,18 @@ namespace Artemis.Core.LayerEffects /// /// Gets a reference to the layer property group without knowing it's type /// - public virtual LayerPropertyGroup? BaseProperties => null; + public virtual LayerEffectPropertyGroup? BaseProperties => null; /// /// Gets a boolean indicating whether the layer effect is enabled or not /// public bool Enabled { get; private set; } + /// + /// Gets a boolean indicating whether the layer effect is suspended or not + /// + public bool Suspended => BaseProperties is not {PropertiesInitialized: true} || !BaseProperties.IsEnabled; + #region IDisposable /// @@ -224,11 +219,20 @@ namespace Artemis.Core.LayerEffects #endregion + /// + public void Load() + { + Name = LayerEffectEntity.Name; + HasBeenRenamed = LayerEffectEntity.HasBeenRenamed; + Order = LayerEffectEntity.Order; + } + + /// 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.ProviderId = Descriptor.Provider.Id; + LayerEffectEntity.EffectType = GetType().FullName; LayerEffectEntity.Name = Name; - LayerEffectEntity.Suspended = Suspended; LayerEffectEntity.HasBeenRenamed = HasBeenRenamed; LayerEffectEntity.Order = Order; diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs index 8916758e7..87259d44b 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffect.cs @@ -6,7 +6,7 @@ namespace Artemis.Core.LayerEffects /// Represents an effect that applies preprocessing and/or postprocessing to a layer /// /// - public abstract class LayerEffect : BaseLayerEffect where T : LayerPropertyGroup + public abstract class LayerEffect : BaseLayerEffect where T : LayerEffectPropertyGroup, new() { private T _properties = null!; @@ -16,7 +16,7 @@ namespace Artemis.Core.LayerEffects public bool PropertiesInitialized { get; internal set; } /// - public override LayerPropertyGroup BaseProperties => Properties; + public override LayerEffectPropertyGroup BaseProperties => Properties; /// /// Gets the properties of this effect. @@ -32,19 +32,22 @@ namespace Artemis.Core.LayerEffects } internal set => _properties = value; } - - internal void InitializeProperties() - { - Properties = Activator.CreateInstance(); - Properties.Initialize(ProfileElement, null, new PropertyGroupDescriptionAttribute(){Identifier = "LayerEffect"}, LayerEffectEntity.PropertyGroup); - PropertiesInitialized = true; - - EnableLayerEffect(); - } - + internal override void Initialize() { InitializeProperties(); } + + private void InitializeProperties() + { + Properties = new T(); + Properties.Initialize(ProfileElement, null, new PropertyGroupDescriptionAttribute {Identifier = "LayerEffect"}, LayerEffectEntity.PropertyGroup); + + // Initialize will call PopulateDefaults but that is for plugin developers so can't rely on that to default IsEnabled to true + Properties.InitializeIsEnabled(); + PropertiesInitialized = true; + + EnableLayerEffect(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 34f501845..59fa31b5c 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,116 +1,111 @@ using System; -using System.Linq; using Artemis.Core.LayerEffects.Placeholder; using Artemis.Storage.Entities.Profile; using Ninject; -namespace Artemis.Core.LayerEffects +namespace Artemis.Core.LayerEffects; + +/// +/// A class that describes a layer effect +/// +public class LayerEffectDescriptor { - /// - /// A class that describes a layer effect - /// - public class LayerEffectDescriptor + internal LayerEffectDescriptor(string displayName, string description, string icon, Type layerEffectType, LayerEffectProvider provider) { - internal LayerEffectDescriptor(string displayName, string description, string icon, Type? layerEffectType, LayerEffectProvider provider) + DisplayName = displayName; + Description = description; + Icon = icon; + LayerEffectType = layerEffectType ?? throw new ArgumentNullException(nameof(layerEffectType)); + Provider = provider ?? throw new ArgumentNullException(nameof(provider)); + } + + internal LayerEffectDescriptor(string placeholderFor, LayerEffectProvider provider) + { + PlaceholderFor = placeholderFor ?? throw new ArgumentNullException(nameof(placeholderFor)); + Provider = provider ?? throw new ArgumentNullException(nameof(provider)); + DisplayName = "Missing effect"; + Description = "This effect could not be loaded"; + Icon = "FileQuestion"; + } + + /// + /// The name that is displayed in the UI + /// + public string DisplayName { get; } + + /// + /// The description that is displayed in the UI + /// + public string Description { get; } + + /// + /// The Material icon to display in the UI, a full reference can be found + /// here + /// + public string Icon { get; } + + /// + /// The type of the layer effect + /// + public Type? LayerEffectType { get; } + + /// + /// The plugin that provided this + /// + public LayerEffectProvider Provider { get; } + + /// + /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder + /// + public string? PlaceholderFor { get; } + + /// + /// Creates an instance of the described effect and applies it to the render element + /// + public BaseLayerEffect CreateInstance(RenderProfileElement renderElement, LayerEffectEntity? entity) + { + if (PlaceholderFor != null) { - DisplayName = displayName; - Description = description; - Icon = icon; - LayerEffectType = layerEffectType; - Provider = provider; - } - - /// - /// The name that is displayed in the UI - /// - public string DisplayName { get; } - - /// - /// The description that is displayed in the UI - /// - public string Description { get; } - - /// - /// The Material icon to display in the UI, a full reference can be found - /// here - /// - public string Icon { get; } - - /// - /// The type of the layer effect - /// - public Type? LayerEffectType { get; } - - /// - /// The plugin that provided this - /// - public LayerEffectProvider Provider { get; } - - /// - /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder - /// - public string? PlaceholderFor { get; internal set; } - - /// - /// Creates an instance of the described effect and applies it to the render element - /// - internal void CreateInstance(RenderProfileElement renderElement, LayerEffectEntity? entity) - { - 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; - } + throw new ArtemisCoreException("Cannot create a placeholder for a layer effect that wasn't loaded from an entity"); - if (PlaceholderFor != null) - { - CreatePlaceHolderInstance(renderElement, entity); - return; - } - - BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType); - effect.ProfileElement = renderElement; - effect.LayerEffectEntity = entity; - effect.Order = entity.Order; - effect.Name = entity.Name; - effect.Suspended = entity.Suspended; - effect.Descriptor = this; - - effect.Initialize(); - effect.Update(0); - - renderElement.ActivateLayerEffect(effect); + return CreatePlaceHolderInstance(renderElement, entity); } - private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) + 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.Descriptor = this; + if (entity != null) { - if (PlaceholderFor == null) - throw new ArtemisCoreException("Cannot create a placeholder instance using a layer effect descriptor that is not a placeholder for anything"); - PlaceholderLayerEffect effect = new(entity, PlaceholderFor) - { - ProfileElement = renderElement, - Descriptor = this - }; + effect.LayerEffectEntity = entity; + effect.Load(); effect.Initialize(); - renderElement.ActivateLayerEffect(effect); - - if (renderElement.ShouldBeEnabled) - effect.InternalEnable(); } + else + { + effect.LayerEffectEntity = new LayerEffectEntity(); + effect.Initialize(); + effect.Save(); + } + + return effect; + } + + private BaseLayerEffect CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) + { + if (PlaceholderFor == null) + throw new ArtemisCoreException("Cannot create a placeholder instance using a layer effect descriptor that is not a placeholder for anything"); + + PlaceholderLayerEffect effect = new(entity, PlaceholderFor) + { + ProfileElement = renderElement, + Descriptor = this + }; + effect.Initialize(); + + return effect; } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs index a1ec92371..c9b187088 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -16,7 +16,6 @@ namespace Artemis.Core.LayerEffects.Placeholder LayerEffectEntity = originalEntity; Order = OriginalEntity.Order; Name = OriginalEntity.Name; - Suspended = OriginalEntity.Suspended; HasBeenRenamed = OriginalEntity.HasBeenRenamed; } @@ -58,7 +57,7 @@ namespace Artemis.Core.LayerEffects.Placeholder /// /// This is in place so that the UI has something to show /// - internal class PlaceholderProperties : LayerPropertyGroup + internal class PlaceholderProperties : LayerEffectPropertyGroup { protected override void PopulateDefaults() { diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs index 374340bb1..a5496d986 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs @@ -4,11 +4,7 @@ { public static LayerEffectDescriptor Create(string missingProviderId) { - LayerEffectDescriptor descriptor = new("Missing effect", "This effect could not be loaded", "FileQuestion", null, Constants.EffectPlaceholderPlugin) - { - PlaceholderFor = missingProviderId - }; - + LayerEffectDescriptor descriptor = new(missingProviderId, Constants.EffectPlaceholderPlugin); return descriptor; } } diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index d6dbfff46..5b62963a2 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -12,7 +12,7 @@ namespace Artemis.Core.Modules /// /// Allows you to add new data to the Artemis data model /// - public abstract class Module : Module where T : DataModel + public abstract class Module : Module where T : DataModel, new() { /// /// The data model driving this module @@ -79,7 +79,7 @@ namespace Artemis.Core.Modules internal override void InternalEnable() { - DataModel = Activator.CreateInstance(); + DataModel = new T(); DataModel.Module = this; DataModel.DataModelDescription = GetDataModelDescription(); base.InternalEnable(); @@ -310,7 +310,7 @@ namespace Artemis.Core.Modules /// internal override void InternalEnable() { - foreach ((DefaultCategoryName categoryName, var path) in _pendingDefaultProfilePaths) + foreach ((DefaultCategoryName categoryName, string? path) in _pendingDefaultProfilePaths) AddDefaultProfile(categoryName, path); _pendingDefaultProfilePaths.Clear(); diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs index 203b6185b..dbfdb860f 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs @@ -71,7 +71,7 @@ namespace Artemis.Core /// public void SaveAllSettings() { - foreach (var (_, pluginSetting) in _settingEntities) + foreach ((string _, IPluginSetting? pluginSetting) in _settingEntities) pluginSetting.Save(); } diff --git a/src/Artemis.Core/Services/Input/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs index f47ac5d78..17082f7df 100644 --- a/src/Artemis.Core/Services/Input/InputService.cs +++ b/src/Artemis.Core/Services/Input/InputService.cs @@ -301,7 +301,7 @@ namespace Artemis.Core.Services public void ReleaseAll() { - foreach (var (device, keys) in _pressedKeys.ToList()) + foreach ((ArtemisDevice? device, HashSet? keys) in _pressedKeys.ToList()) { foreach (KeyboardKey keyboardKey in keys) { diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs index 70a019331..5b65b0cf2 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -12,7 +12,7 @@ namespace Artemis.Core.Services /// or . /// Note: Both will be deserialized and serialized respectively using JSON. /// - public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel + public class DataModelJsonPluginEndPoint : PluginEndPoint where T : DataModel, new() { private readonly Module _module; diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 7c8e2124f..6a8fe71ef 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -51,7 +51,7 @@ namespace Artemis.Core.Services /// The module whose datamodel to apply the received JSON to /// The name of the end point, must be unique /// The resulting end point - DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel; + DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new(); /// /// Adds a new endpoint for the given plugin feature receiving an a . diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 0d7654812..2b450ec78 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -50,7 +50,7 @@ namespace Artemis.Core.Services .WithModule(PluginsModule); // Add registered modules - foreach (var webModule in _modules) + foreach (WebModuleRegistration? webModule in _modules) server = server.WithModule(webModule.CreateInstance()); server = server @@ -132,7 +132,7 @@ namespace Artemis.Core.Services return endPoint; } - public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel + public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new() { if (module == null) throw new ArgumentNullException(nameof(module)); if (endPointName == null) throw new ArgumentNullException(nameof(endPointName)); @@ -141,7 +141,7 @@ namespace Artemis.Core.Services return endPoint; } - private void HandleDataModelRequest(Module module, T value) where T : DataModel + private void HandleDataModelRequest(Module module, T value) where T : DataModel, new() { } diff --git a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs index 224e61e0f..2ca393ca6 100644 --- a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs +++ b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs @@ -24,7 +24,7 @@ namespace Artemis.Core.Internal public override void Evaluate() { - foreach (var (property, inputPin) in _propertyPins) + foreach ((IDataBindingProperty? property, InputPin? inputPin) in _propertyPins) { if (inputPin.ConnectedTo.Any()) _propertyValues[property] = inputPin.Value!; @@ -35,7 +35,7 @@ namespace Artemis.Core.Internal public void ApplyToDataBinding() { - foreach (var (property, pendingValue) in _propertyValues) + foreach ((IDataBindingProperty? property, object? pendingValue) in _propertyValues) property.SetValue(pendingValue); } diff --git a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs index 26f1d9c53..6eb99c5d8 100644 --- a/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Abstract/RenderElementEntity.cs @@ -12,11 +12,8 @@ namespace Artemis.Storage.Entities.Profile.Abstract public List LayerEffects { get; set; } public List PropertyEntities { get; set; } - public List ExpandedPropertyGroups { get; set; } public IConditionEntity DisplayCondition { get; set; } public TimelineEntity Timeline { get; set; } - - public NodeScriptEntity NodeScript { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs index 0f6cdde1b..a9d25396b 100644 --- a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs @@ -11,7 +11,6 @@ namespace Artemis.Storage.Entities.Profile { PropertyEntities = new List(); LayerEffects = new List(); - ExpandedPropertyGroups = new List(); } public int Order { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs index 236d1d2fb..b3b0c5187 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEffectEntity.cs @@ -5,11 +5,9 @@ namespace Artemis.Storage.Entities.Profile { public class LayerEffectEntity { - public Guid Id { get; set; } public string ProviderId { get; set; } public string EffectType { get; set; } public string Name { get; set; } - public bool Suspended { get; set; } public bool HasBeenRenamed { get; set; } public int Order { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs index 1c8ff29ea..ffe08feb0 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs @@ -14,7 +14,6 @@ namespace Artemis.Storage.Entities.Profile AdaptionHints = new List(); PropertyEntities = new List(); LayerEffects = new List(); - ExpandedPropertyGroups = new List(); } public int Order { get; set; } diff --git a/src/Artemis.Storage/Migrations/M0002ProfileEntitiesEnabledMigration.cs b/src/Artemis.Storage/Migrations/M0002ProfileEntitiesEnabledMigration.cs index d019c1667..699b25c40 100644 --- a/src/Artemis.Storage/Migrations/M0002ProfileEntitiesEnabledMigration.cs +++ b/src/Artemis.Storage/Migrations/M0002ProfileEntitiesEnabledMigration.cs @@ -17,15 +17,17 @@ namespace Artemis.Storage.Migrations foreach (FolderEntity profileEntityFolder in profileEntity.Folders) { profileEntityFolder.Suspended = false; - foreach (LayerEffectEntity layerEffectEntity in profileEntityFolder.LayerEffects) - layerEffectEntity.Suspended = false; + // Commented out during Avalonia port when Suspended was moved into the LayerEffect's LayerProperties + // foreach (LayerEffectEntity layerEffectEntity in profileEntityFolder.LayerEffects) + // layerEffectEntity.Suspended = false; } foreach (LayerEntity profileEntityLayer in profileEntity.Layers) { profileEntityLayer.Suspended = false; - foreach (LayerEffectEntity layerEffectEntity in profileEntityLayer.LayerEffects) - layerEffectEntity.Suspended = false; + // Commented out during Avalonia port when Suspended was moved into the LayerEffect's LayerProperties + // foreach (LayerEffectEntity layerEffectEntity in profileEntityLayer.LayerEffects) + // layerEffectEntity.Suspended = false; } repository.Upsert(profileEntity); diff --git a/src/Artemis.UI.Linux/packages.lock.json b/src/Artemis.UI.Linux/packages.lock.json index 078afba5d..e49cf2d22 100644 --- a/src/Artemis.UI.Linux/packages.lock.json +++ b/src/Artemis.UI.Linux/packages.lock.json @@ -222,8 +222,8 @@ }, "FluentAvaloniaUI": { "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", + "resolved": "1.3.4", + "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==", "dependencies": { "Avalonia": "0.10.13", "Avalonia.Desktop": "0.10.13", @@ -1780,7 +1780,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Flurl.Http": "3.2.0", "Live.Avalonia": "1.3.1", "Material.Icons.Avalonia": "1.0.2", @@ -1801,7 +1801,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease7", "ReactiveUI": "17.1.50", diff --git a/src/Artemis.UI.MacOS/packages.lock.json b/src/Artemis.UI.MacOS/packages.lock.json index 078afba5d..e49cf2d22 100644 --- a/src/Artemis.UI.MacOS/packages.lock.json +++ b/src/Artemis.UI.MacOS/packages.lock.json @@ -222,8 +222,8 @@ }, "FluentAvaloniaUI": { "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", + "resolved": "1.3.4", + "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==", "dependencies": { "Avalonia": "0.10.13", "Avalonia.Desktop": "0.10.13", @@ -1780,7 +1780,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Flurl.Http": "3.2.0", "Live.Avalonia": "1.3.1", "Material.Icons.Avalonia": "1.0.2", @@ -1801,7 +1801,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease7", "ReactiveUI": "17.1.50", diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index a55751eb4..221832a3e 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -23,19 +23,13 @@ - + - - - $(DefaultXamlRuntime) - MSBuild:Compile - - diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml index 56dcf901e..293135dfd 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml @@ -4,5 +4,4 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Shared.ArtemisIcon"> - Welcome to Avalonia! diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs index 7805c02a8..75bea347f 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Shared { SvgSource source = new(); source.Load(iconString); - Content = new SvgImage {Source = source}; + Content = new Image {Source = new SvgImage {Source = source}}; } // An URI pointing to a different kind of image else @@ -79,10 +79,8 @@ namespace Artemis.UI.Shared private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e) { - if (Content is SvgImage svgImage) - svgImage.Source?.Dispose(); - else if (Content is Image image) - ((Bitmap) image.Source).Dispose(); + if (Content is Image image && image.Source is IDisposable disposable) + disposable.Dispose(); } private void InitializeComponent() diff --git a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml deleted file mode 100644 index b722b7d59..000000000 --- a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayView.xaml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/AddLayerEffect.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/AddLayerEffect.cs new file mode 100644 index 000000000..feedd6543 --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/AddLayerEffect.cs @@ -0,0 +1,48 @@ +using System; +using Artemis.Core; +using Artemis.Core.LayerEffects; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to add a layer effect to a profile element. +/// +public class AddLayerEffect : IProfileEditorCommand, IDisposable +{ + private readonly RenderProfileElement _renderProfileElement; + private readonly BaseLayerEffect _layerEffect; + private bool _executed; + + /// + /// Creates a new instance of the class. + /// + public AddLayerEffect(RenderProfileElement renderProfileElement, BaseLayerEffect layerEffect) + { + _renderProfileElement = renderProfileElement; + _layerEffect = layerEffect; + } + + /// + public string DisplayName => "Add layer effect"; + + /// + public void Execute() + { + _renderProfileElement.AddLayerEffect(_layerEffect); + _executed = true; + } + + /// + public void Undo() + { + _renderProfileElement.RemoveLayerEffect(_layerEffect); + _executed = false; + } + + /// + public void Dispose() + { + if (!_executed) + _layerEffect.Dispose(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/packages.lock.json b/src/Artemis.UI.Shared/packages.lock.json index aaa59d395..0dd5d6365 100644 --- a/src/Artemis.UI.Shared/packages.lock.json +++ b/src/Artemis.UI.Shared/packages.lock.json @@ -62,9 +62,9 @@ }, "FluentAvaloniaUI": { "type": "Direct", - "requested": "[1.3.0, )", - "resolved": "1.3.0", - "contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", + "requested": "[1.3.4, )", + "resolved": "1.3.4", + "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==", "dependencies": { "Avalonia": "0.10.13", "Avalonia.Desktop": "0.10.13", diff --git a/src/Artemis.UI.Windows/packages.lock.json b/src/Artemis.UI.Windows/packages.lock.json index 51d6f98cb..6a5f3484a 100644 --- a/src/Artemis.UI.Windows/packages.lock.json +++ b/src/Artemis.UI.Windows/packages.lock.json @@ -238,8 +238,8 @@ }, "FluentAvaloniaUI": { "type": "Transitive", - "resolved": "1.3.0", - "contentHash": "xzcsuOswakMpz/EdA59NEOgaCtZ/9zsd5QWTB0YYQqSv1GF95Uk2aMVtO5gtfNrCT4lZvGNWVf3HGjYz9cHovQ==", + "resolved": "1.3.4", + "contentHash": "INZBxCGyt4Dzm0IaNXoXuU08VUQ62FbeMHPH7MO5W1WeVdU5N8+1YTYfqYAA66twa5wba8MYqezXWhoU8Hq0DQ==", "dependencies": { "Avalonia": "0.10.13", "Avalonia.Desktop": "0.10.13", @@ -1796,7 +1796,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Flurl.Http": "3.2.0", "Live.Avalonia": "1.3.1", "Material.Icons.Avalonia": "1.0.2", @@ -1817,7 +1817,7 @@ "Avalonia.Svg.Skia": "0.10.12", "Avalonia.Xaml.Behaviors": "0.10.13.2", "DynamicData": "7.5.4", - "FluentAvaloniaUI": "1.3.0", + "FluentAvaloniaUI": "1.3.4", "Material.Icons.Avalonia": "1.0.2", "RGB.NET.Core": "1.0.0-prerelease7", "ReactiveUI": "17.1.50", diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index c750a6d1a..af2c19e4d 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Artemis.UI/ArtemisBootstrapper.cs b/src/Artemis.UI/ArtemisBootstrapper.cs index 0764be57d..5eac6489d 100644 --- a/src/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Artemis.UI/ArtemisBootstrapper.cs @@ -42,8 +42,6 @@ public static class ArtemisBootstrapper _kernel.Load(modules); _kernel.UseNinjectDependencyResolver(); - DataModelPicker.DataModelUIService = _kernel.Get(); - return _kernel; } @@ -62,6 +60,7 @@ public static class ArtemisBootstrapper _application.DataContext = rootViewModel; RxApp.DefaultExceptionHandler = Observer.Create(DisplayUnhandledException); + DataModelPicker.DataModelUIService = _kernel.Get(); } private static void DisplayUnhandledException(Exception exception) diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs index 0fe7783a7..fbde8d7a9 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Specialized; -using System.Linq; -using Artemis.Core; +using Artemis.Core; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.PropertyInput; -using Avalonia.Media; using ReactiveUI; namespace Artemis.UI.DefaultTypes.PropertyInput; @@ -60,14 +56,10 @@ public class ColorGradientPropertyInputViewModel : PropertyInputViewModel(LayerProperty, ColorGradient, _originalGradient, Time)); - } _originalGradient = null; } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs index fae919a27..a5c38997e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.PropertyInput; using ReactiveUI.Validation.Extensions; @@ -11,10 +12,10 @@ public class FloatPropertyInputViewModel : PropertyInputViewModel : base(layerProperty, profileEditorService, propertyInputService) { if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) - this.ValidationRule(vm => vm.InputValue, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.InputValue, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue), $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) - this.ValidationRule(vm => vm.InputValue, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.InputValue, i => i <= Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue), $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs index 16df98f6e..2bfbbb04e 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.PropertyInput; using ReactiveUI; @@ -16,17 +17,17 @@ public class FloatRangePropertyInputViewModel : PropertyInputViewModel vm.Start, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.Start, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue), $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); - this.ValidationRule(vm => vm.End, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.End, i => i >= Convert.ToSingle(LayerProperty.PropertyDescription.MinInputValue), $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); } if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) { - this.ValidationRule(vm => vm.Start, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.Start, i => i < Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue), $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); - this.ValidationRule(vm => vm.End, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.End, i => i < Convert.ToSingle(LayerProperty.PropertyDescription.MaxInputValue), $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); } } diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs index 92f02cfa6..95e16ed4f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.PropertyInput; using ReactiveUI.Validation.Extensions; @@ -11,10 +12,10 @@ public class IntPropertyInputViewModel : PropertyInputViewModel : base(layerProperty, profileEditorService, propertyInputService) { if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) - this.ValidationRule(vm => vm.InputValue, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.InputValue, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue), $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) - this.ValidationRule(vm => vm.InputValue, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.InputValue, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue), $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); } } \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs index 3e548ceaf..9fb62c16b 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.PropertyInput; using ReactiveUI; @@ -16,17 +17,17 @@ public class IntRangePropertyInputViewModel : PropertyInputViewModel if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) { - this.ValidationRule(vm => vm.Start, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.Start, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue), $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); - this.ValidationRule(vm => vm.End, i => i >= (int)LayerProperty.PropertyDescription.MinInputValue, + this.ValidationRule(vm => vm.End, i => i >= Convert.ToInt32(LayerProperty.PropertyDescription.MinInputValue), $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); } if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) { - this.ValidationRule(vm => vm.Start, i => i < (int)LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.Start, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue), $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); - this.ValidationRule(vm => vm.End, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, + this.ValidationRule(vm => vm.End, i => i < Convert.ToInt32(LayerProperty.PropertyDescription.MaxInputValue), $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); } } diff --git a/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs b/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs index 34d4c882c..dd861e0f1 100644 --- a/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs @@ -77,7 +77,7 @@ namespace Artemis.UI.Screens.Device await _windowService.CreateContentDialog() .WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input") - .WithViewModel(out var viewModel, ("device", Device)) + .WithViewModel(out DeviceDetectInputViewModel? viewModel, ("device", Device)) .WithCloseButtonText("Cancel") .ShowAsync(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml new file mode 100644 index 000000000..3fec4881e --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + None of the effects match your search + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml.cs new file mode 100644 index 000000000..77b18f314 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectView.axaml.cs @@ -0,0 +1,28 @@ +using Artemis.Core.LayerEffects; +using Avalonia; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs; + +public class AddEffectView : ReactiveUserControl +{ + public AddEffectView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + if (sender is not IDataContextProvider {DataContext: LayerEffectDescriptor descriptor} || ViewModel == null) + return; + + ViewModel?.AddLayerEffect(descriptor); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectViewModel.cs new file mode 100644 index 000000000..3193351f0 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Dialogs/AddEffectViewModel.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.ObjectModel; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.Core.LayerEffects; +using Artemis.Core.Services; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using DynamicData; +using ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.Properties.Dialogs; + +public class AddEffectViewModel : ContentDialogViewModelBase +{ + private readonly RenderProfileElement _renderProfileElement; + private readonly IProfileEditorService _profileEditorService; + private string? _searchText; + + public AddEffectViewModel(RenderProfileElement renderProfileElement, IProfileEditorService profileEditorService, ILayerEffectService layerEffectService) + { + _renderProfileElement = renderProfileElement; + _profileEditorService = profileEditorService; + + SourceList layerEffectSourceList = new(); + layerEffectSourceList.AddRange(layerEffectService.GetLayerEffects()); + IObservable> layerEffectFilter = this.WhenAnyValue(vm => vm.SearchText).Select(CreatePredicate); + + layerEffectSourceList.Connect() + .Filter(layerEffectFilter) + .Bind(out ReadOnlyObservableCollection layerEffectDescriptors) + .Subscribe(); + LayerEffectDescriptors = layerEffectDescriptors; + } + + public ReadOnlyObservableCollection LayerEffectDescriptors { get; } + + public string? SearchText + { + get => _searchText; + set => this.RaiseAndSetIfChanged(ref _searchText, value); + } + + public void AddLayerEffect(LayerEffectDescriptor descriptor) + { + BaseLayerEffect layerEffect = descriptor.CreateInstance(_renderProfileElement, null); + _profileEditorService.ExecuteCommand(new AddLayerEffect(_renderProfileElement, layerEffect)); + ContentDialog?.Hide(); + } + + private Func CreatePredicate(string? search) + { + if (string.IsNullOrWhiteSpace(search)) + return _ => true; + + search = search.Trim(); + return data => data.DisplayName.Contains(search, StringComparison.InvariantCultureIgnoreCase) || + data.Description.Contains(search, StringComparison.InvariantCultureIgnoreCase); + } +} + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml index b6ead192e..7d755037a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesView.axaml @@ -13,7 +13,7 @@ - + @@ -29,13 +29,22 @@ Name="TreeScrollViewer" Offset="{CompiledBinding #TimelineScrollViewer.Offset, Mode=OneWay}" Background="{DynamicResource CardStrokeColorDefaultSolidBrush}"> - - - - - - - + + + + + + + + + + @@ -45,12 +54,12 @@ Background="Transparent" Margin="0 0 -5 0" /> - + Content="{CompiledBinding DataBindingViewModel}" /> - - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs index bfd8b37d5..25ef9fc7d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertiesViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; @@ -12,8 +13,12 @@ using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Playback; using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding; +using Artemis.UI.Screens.ProfileEditor.Properties.Dialogs; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; +using Artemis.UI.Screens.Sidebar; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.ProfileEditor; using ReactiveUI; @@ -23,6 +28,8 @@ public class PropertiesViewModel : ActivatableViewModelBase { private readonly Dictionary _cachedPropertyViewModels; private readonly IDataBindingVmFactory _dataBindingVmFactory; + private readonly IWindowService _windowService; + private readonly ILayerEffectService _layerEffectService; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; @@ -38,18 +45,22 @@ public class PropertiesViewModel : ActivatableViewModelBase ISettingsService settingsService, ILayerPropertyVmFactory layerPropertyVmFactory, IDataBindingVmFactory dataBindingVmFactory, + IWindowService windowService, + ILayerEffectService layerEffectService, PlaybackViewModel playbackViewModel) { _profileEditorService = profileEditorService; _settingsService = settingsService; _layerPropertyVmFactory = layerPropertyVmFactory; _dataBindingVmFactory = dataBindingVmFactory; + _windowService = windowService; + _layerEffectService = layerEffectService; _cachedPropertyViewModels = new Dictionary(); PropertyGroupViewModels = new ObservableCollection(); PlaybackViewModel = playbackViewModel; TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels); - + AddEffect = ReactiveCommand.CreateFromTask(ExecuteAddEffect); // React to service profile element changes as long as the VM is active this.WhenActivated(d => { @@ -86,6 +97,7 @@ public class PropertiesViewModel : ActivatableViewModelBase public ObservableCollection PropertyGroupViewModels { get; } public PlaybackViewModel PlaybackViewModel { get; } public TimelineViewModel TimelineViewModel { get; } + public ReactiveCommand AddEffect { get; } public DataBindingViewModel? DataBindingViewModel { @@ -102,6 +114,17 @@ public class PropertiesViewModel : ActivatableViewModelBase public IObservable Playing => _profileEditorService.Playing; public PluginSetting PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0); + private async Task ExecuteAddEffect() + { + if (ProfileElement == null) + return; + + await _windowService.CreateContentDialog() + .WithTitle("Add layer effect") + .WithViewModel(out AddEffectViewModel _, ("renderProfileElement", ProfileElement)) + .WithCloseButtonText("Cancel") + .ShowAsync(); + } private void UpdatePropertyGroups() { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs index edccd7921..83da67450 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/PropertyGroupViewModel.cs @@ -38,18 +38,36 @@ public class PropertyGroupViewModel : ViewModelBase, IDisposable PopulateChildren(); } - public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, - BaseLayerBrush layerBrush) - : this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService) + public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerBrush layerBrush) { + _layerPropertyVmFactory = layerPropertyVmFactory; + _propertyInputService = propertyInputService; LayerBrush = layerBrush; + Children = new ObservableCollection(); + LayerPropertyGroup = layerPropertyGroup; + TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); + TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); + + LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; + _isVisible = !LayerPropertyGroup.IsHidden; + + PopulateChildren(); } - public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, - BaseLayerEffect layerEffect) - : this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService) + public PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerEffect layerEffect) { + _layerPropertyVmFactory = layerPropertyVmFactory; + _propertyInputService = propertyInputService; LayerEffect = layerEffect; + Children = new ObservableCollection(); + LayerPropertyGroup = layerPropertyGroup; + TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); + TimelineGroupViewModel = layerPropertyVmFactory.TimelineGroupViewModel(this); + + LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; + _isVisible = !LayerPropertyGroup.IsHidden; + + PopulateChildren(); } public ObservableCollection Children { get; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml index 9fdd258eb..0d84c6054 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml @@ -53,7 +53,7 @@ - + General @@ -61,22 +61,23 @@ - + Transform + Margin="0 0 5 0" /> - Brush -  + Margin="0 5 5 0"> + Brush - - Effect @@ -133,18 +134,23 @@ Margin="0 5" IsVisible="{Binding LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> - - - - + + +