From c07ea09c9d6a1526db4041772961edde4de261ae Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 10 Sep 2020 19:56:39 +0200 Subject: [PATCH] Core - Streamlined public parts of profile creation UI - Started adjusting the VMs for the layer refactor --- .../Abstract/DisplayConditionPart.cs | 2 +- .../Conditions/DisplayConditionGroup.cs | 4 +- .../Conditions/DisplayConditionList.cs | 4 +- .../DisplayConditionListPredicate.cs | 55 +++++- .../Conditions/DisplayConditionPredicate.cs | 36 ++-- src/Artemis.Core/Models/Profile/Folder.cs | 36 ++-- src/Artemis.Core/Models/Profile/Layer.cs | 56 ++++-- src/Artemis.Core/Models/Profile/Profile.cs | 8 +- .../Models/Profile/ProfileElement.cs | 5 +- .../Models/Profile/RenderProfileElement.cs | 30 +++- .../LayerEffects/Internal/BaseLayerEffect.cs | 2 + .../LayerEffects/LayerEffectDescriptor.cs | 18 +- .../Placeholder/PlaceholderLayerEffect.cs | 58 ++++++ .../PlaceholderLayerEffectDescriptor.cs | 11 ++ src/Artemis.Core/Stores/LayerBrushStore.cs | 3 +- .../Interfaces/IProfileEditorService.cs | 9 +- .../Services/ProfileEditorService.cs | 11 ++ .../Ninject/Factories/IVMFactory.cs | 31 ++-- src/Artemis.UI/Ninject/UiModule.cs | 2 +- .../Abstract/DisplayConditionViewModel.cs | 29 +-- .../DisplayConditionGroupView.xaml | 2 +- .../DisplayConditionGroupViewModel.cs | 40 ++--- .../DisplayConditionListPredicateViewModel.cs | 25 ++- .../DisplayConditionListView.xaml | 2 +- .../DisplayConditionListViewModel.cs | 31 ++-- .../DisplayConditionPredicateViewModel.cs | 27 ++- .../DisplayConditionsView.xaml | 2 +- .../DisplayConditionsViewModel.cs | 24 +-- ...del.cs => IProfileEditorPanelViewModel.cs} | 2 +- .../Abstract/LayerPropertyBaseViewModel.cs | 36 ---- .../LayerEffects/EffectsViewModel.cs | 28 +-- .../LayerPropertiesViewModel.cs | 3 +- .../LayerPropertyGroupViewModel.cs | 166 +++--------------- .../LayerProperties/LayerPropertyViewModel.cs | 136 ++------------ .../Rails/LayerPropertyTimelineViewModel.cs | 26 +++ .../Timeline/Rails/TimelineGroupViewModel.cs | 17 ++ ...w.xaml => LayerPropertyGroupTreeView.xaml} | 52 +++--- ....cs => LayerPropertyGroupTreeViewModel.cs} | 57 ++++-- ...tyView.xaml => LayerPropertyTreeView.xaml} | 14 +- .../Tree/LayerPropertyTreeViewModel.cs | 93 ++++++++++ .../Tree/TreePropertyViewModel.cs | 89 ---------- .../LayerProperties/Tree/TreeView.xaml | 10 +- .../LayerProperties/Tree/TreeViewModel.cs | 4 +- .../ProfileEditor/ProfileEditorViewModel.cs | 4 +- .../ProfileTree/ProfileTreeView.xaml | 6 +- .../ProfileTree/ProfileTreeViewModel.cs | 38 ++-- .../ProfileTree/TreeItem/FolderViewModel.cs | 30 +--- .../ProfileTree/TreeItem/LayerViewModel.cs | 10 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 99 ++++------- .../Visualization/ProfileDeviceView.xaml | 60 ------- .../Visualization/ProfileDeviceViewModel.cs | 96 ---------- .../Visualization/ProfileLedView.xaml | 93 ---------- .../Visualization/ProfileLedViewModel.cs | 148 ---------------- .../Visualization/ProfileViewModel.cs | 2 +- .../LayerBrushSettingsWindowView.xaml | 2 +- .../LayerBrushSettingsWindowViewModel.cs | 2 +- .../LayerEffectSettingsWindowView.xaml | 2 +- .../LayerEffectSettingsWindowViewModel.cs | 2 +- 58 files changed, 694 insertions(+), 1196 deletions(-) create mode 100644 src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs create mode 100644 src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs rename src/Artemis.UI/Screens/ProfileEditor/{ProfileEditorPanelViewModel.cs => IProfileEditorPanelViewModel.cs} (55%) delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyGroupView.xaml => LayerPropertyGroupTreeView.xaml} (71%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyGroupViewModel.cs => LayerPropertyGroupTreeViewModel.cs} (69%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/{TreePropertyView.xaml => LayerPropertyTreeView.xaml} (78%) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyTreeViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml delete mode 100644 src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerBrushSettingsWindowView.xaml (97%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerBrushSettingsWindowViewModel.cs (92%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerEffectSettingsWindowView.xaml (97%) rename src/Artemis.UI/Screens/ProfileEditor/{ => Windows}/LayerEffectSettingsWindowViewModel.cs (92%) diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs index 72ca6a7c4..f751046d0 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DisplayConditionPart.cs @@ -61,7 +61,7 @@ namespace Artemis.Core /// public abstract bool EvaluateObject(object target); - internal abstract void ApplyToEntity(); + internal abstract void Save(); internal abstract DisplayConditionPartEntity GetEntity(); #region IDisposable diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs index cc4cc7e49..055a40b0b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionGroup.cs @@ -105,14 +105,14 @@ namespace Artemis.Core }; } - internal override void ApplyToEntity() + internal override void Save() { Entity.BooleanOperator = (int) BooleanOperator; Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); foreach (var child in Children) - child.ApplyToEntity(); + child.Save(); } internal override DisplayConditionPartEntity GetEntity() diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs index 155ae3489..5ecbc607c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionList.cs @@ -116,7 +116,7 @@ namespace Artemis.Core CompiledListAccessor = lambda.Compile(); } - internal override void ApplyToEntity() + internal override void Save() { // Target list if (ListDataModel != null) @@ -132,7 +132,7 @@ namespace Artemis.Core Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); foreach (var child in Children) - child.ApplyToEntity(); + child.Save(); } internal override DisplayConditionPartEntity GetEntity() diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs index 41c5890df..ef93a992b 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionListPredicate.cs @@ -190,22 +190,31 @@ namespace Artemis.Core return result; } - internal override void ApplyToEntity() + internal override void Save() { Entity.PredicateType = (int) PredicateType; - Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid; - Entity.ListPropertyPath = ListPropertyPath; + if (ListDataModel != null) + { + Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid; + Entity.ListPropertyPath = ListPropertyPath; + } Entity.LeftPropertyPath = LeftPropertyPath; Entity.RightPropertyPath = RightPropertyPath; Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; - Entity.OperatorType = Operator?.GetType().Name; + if (Operator != null) + { + Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; + Entity.OperatorType = Operator.GetType().Name; + } } - internal void Initialize() + private void Initialize() { + ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; + // Left side if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) UpdateLeftSide(Entity.LeftPropertyPath); @@ -222,7 +231,7 @@ namespace Artemis.Core if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPropertyPath != null) { if (ListContainsInnerPath(Entity.RightPropertyPath)) - UpdateLeftSide(Entity.LeftPropertyPath); + UpdateRightSideDynamic(Entity.RightPropertyPath); } // Right side static else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) @@ -384,5 +393,37 @@ namespace Artemis.Core Expression.Property ); } + + #region Event handlers + + private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) + { + var conditionOperator = e.Registration.ConditionOperator; + if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) + UpdateOperator(conditionOperator); + } + + private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) + { + if (e.Registration.ConditionOperator != Operator) + return; + Operator = null; + CompiledListPredicate = null; + } + + #endregion + + #region IDisposable + + /// + protected override void Dispose(bool disposing) + { + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; + + base.Dispose(disposing); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs index 53d70de8a..a86f57dc1 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs @@ -232,7 +232,7 @@ namespace Artemis.Core return $"[Static] {LeftPropertyPath} {Operator.Description} {RightStaticValue}"; } - internal override void ApplyToEntity() + internal override void Save() { Entity.PredicateType = (int) PredicateType; Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid; @@ -250,6 +250,8 @@ namespace Artemis.Core { DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; + ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; // Left side if (Entity.LeftDataModelGuid != null) @@ -415,19 +417,6 @@ namespace Artemis.Core ); } - private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter) - { - var listType = dataModel.GetListTypeInPath(path); - if (listType == null) - throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list"); - - path = dataModel.GetListInnerPath(path); - return path.Split('.').Aggregate( - Expression.Convert(listParameter, listType), // Cast to the appropriate type - Expression.Property - ); - } - #region Event handlers private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) @@ -454,6 +443,23 @@ namespace Artemis.Core } } + private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) + { + var conditionOperator = e.Registration.ConditionOperator; + if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) + UpdateOperator(conditionOperator); + } + + private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) + { + if (e.Registration.ConditionOperator != Operator) + return; + + Operator = null; + CompiledStaticPredicate = null; + CompiledDynamicPredicate = null; + } + #endregion /// @@ -461,6 +467,8 @@ namespace Artemis.Core { DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; base.Dispose(disposing); } diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 1436f7bcf..4491b8143 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -8,24 +8,35 @@ using SkiaSharp; namespace Artemis.Core { + /// + /// Represents a folder in a + /// public sealed class Folder : RenderProfileElement { private SKBitmap _folderBitmap; - public Folder(Profile profile, ProfileElement parent, string name) + /// + /// Creates a new instance of the class and adds itself to the child collection of the provided + /// + /// + /// The parent of the folder + /// The name of the folder + public Folder(ProfileElement parent, string name) { FolderEntity = new FolderEntity(); EntityId = Guid.NewGuid(); - Profile = profile; - Parent = parent; + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Profile = Parent.Profile; Name = name; Enabled = true; DisplayContinuously = true; _layerEffects = new List(); _expandedPropertyGroups = new List(); + ApplyRenderElementDefaults(); + Parent.AddChild(this); } internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) @@ -182,22 +193,7 @@ namespace Artemis.Core canvas.Restore(); } - - /// - /// Adds a new folder to the bottom of this folder - /// - /// - /// - public Folder AddFolder(string name) - { - if (_disposed) - throw new ObjectDisposedException("Folder"); - - var folder = new Folder(Profile, this, name) {Order = Children.LastOrDefault()?.Order ?? 1}; - AddChild(folder); - return folder; - } - + /// public override void AddChild(ProfileElement child, int? order = null) { @@ -296,7 +292,7 @@ namespace Artemis.Core // Conditions RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.ApplyToEntity(); + DisplayConditionGroup?.Save(); SaveRenderElement(); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index b1d411e9c..fc8dbb7c6 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -11,8 +11,7 @@ using SkiaSharp; namespace Artemis.Core { /// - /// Represents a layer on a profile. To create new layers use the by injecting - /// into your code + /// Represents a layer in a /// public sealed class Layer : RenderProfileElement { @@ -23,13 +22,19 @@ namespace Artemis.Core private List _leds; private LayerTransformProperties _transform; - internal Layer(Profile profile, ProfileElement parent, string name) + /// + /// Creates a new instance of the class and adds itself to the child collection of the provided + /// + /// + /// The parent of the layer + /// The name of the layer + public Layer(ProfileElement parent, string name) { LayerEntity = new LayerEntity(); EntityId = Guid.NewGuid(); - Profile = profile; - Parent = parent; + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Profile = Parent.Profile; Name = name; Enabled = true; DisplayContinuously = true; @@ -40,10 +45,10 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - InitializeDefaultGroups(); - - parent.AddChild(this); + Initialize(); ApplyRenderElementDefaults(); + + Parent.AddChild(this); } internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) @@ -58,8 +63,7 @@ namespace Artemis.Core _leds = new List(); _expandedPropertyGroups = new List(); - InitializeDefaultGroups(); - + Initialize(); Load(); } @@ -114,6 +118,8 @@ namespace Artemis.Core return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } + #region IDisposable + /// protected override void Dispose(bool disposing) { @@ -133,8 +139,13 @@ namespace Artemis.Core _transform?.Dispose(); } - private void InitializeDefaultGroups() + #endregion + + private void Initialize() { + LayerBrushStore.LayerBrushAdded += LayerBrushStoreOnLayerBrushAdded; + LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved; + // Layers have two hardcoded property groups, instantiate them General.Initialize(this, "General.", Constants.CorePluginInfo); Transform.Initialize(this, "Transform.", Constants.CorePluginInfo); @@ -191,7 +202,7 @@ namespace Artemis.Core // Conditions RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity; - DisplayConditionGroup?.ApplyToEntity(); + DisplayConditionGroup?.Save(); SaveRenderElement(); } @@ -711,6 +722,27 @@ namespace Artemis.Core #endregion + #region Event handlers + + private void LayerBrushStoreOnLayerBrushRemoved(object sender, LayerBrushStoreEvent e) + { + if (LayerBrush.Descriptor == e.Registration.LayerBrushDescriptor) + DeactivateLayerBrush(); + } + + private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e) + { + if (LayerBrush != null) + return; + + var current = General.BrushReference.CurrentValue; + if (e.Registration.Plugin.PluginInfo.Guid == current.BrushPluginGuid && + e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType) + ActivateLayerBrush(); + } + + #endregion + #region Events public event EventHandler RenderPropertiesUpdated; diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 10806d755..583c10119 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -16,17 +16,19 @@ namespace Artemis.Core ProfileEntity = new ProfileEntity(); EntityId = Guid.NewGuid(); + Profile = this; Module = module; Name = name; UndoStack = new Stack(); RedoStack = new Stack(); - AddChild(new Folder(this, this, "Root folder")); + var _ = new Folder(this, "Root folder"); Save(); } internal Profile(ProfileModule module, ProfileEntity profileEntity) { + Profile = this; ProfileEntity = profileEntity; EntityId = profileEntity.Id; @@ -103,7 +105,9 @@ namespace Artemis.Core // Populate the profile starting at the root, the rest is populated recursively var rootFolder = ProfileEntity.Folders.FirstOrDefault(f => f.ParentId == EntityId); if (rootFolder == null) - AddChild(new Folder(this, this, "Root folder")); + { + var _ = new Folder(this, "Root folder"); + } else AddChild(new Folder(this, this, rootFolder)); } diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 0a2ee39aa..c91bcd2fe 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -110,9 +110,12 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException(GetType().Name); - + lock (ChildrenList) { + if (ChildrenList.Contains(child)) + return; + // Add to the end of the list if (order == null) { diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 81fd839a1..20c938211 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; 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; @@ -49,7 +50,7 @@ namespace Artemis.Core { Id = layerEffect.EntityId, PluginGuid = layerEffect.PluginInfo.Guid, - EffectType = layerEffect.GetType().Name, + EffectType = layerEffect.GetEffectTypeName(), Name = layerEffect.Name, Enabled = layerEffect.Enabled, HasBeenRenamed = layerEffect.HasBeenRenamed, @@ -268,11 +269,29 @@ namespace Artemis.Core { foreach (var layerEffectEntity in RenderElementEntity.LayerEffects) { - if (_layerEffects.Any(e => e.EntityId == layerEffectEntity.Id)) + // If there is a non-placeholder existing effect, skip this entity + var existing = _layerEffects.FirstOrDefault(e => e.EntityId == layerEffectEntity.Id); + if (existing != null && !existing.Descriptor.IsPlaceHolder) continue; var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor; - descriptor?.CreateInstance(this, layerEffectEntity); + if (descriptor != null) + { + // If a descriptor is found but there is an existing placeholder, remove the placeholder + if (existing != null) + { + _layerEffects.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(); + descriptor.CreateInstance(this, layerEffectEntity); + } } } @@ -297,9 +316,10 @@ namespace Artemis.Core throw new NotImplementedException(); } - private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e) + private void LayerEffectStoreOnLayerEffectAdded(object sender, LayerEffectStoreEvent e) { - ActivateEffects(); + if (RenderElementEntity.LayerEffects.Any(ef => ef.PluginGuid == e.Registration.Plugin.PluginInfo.Guid)) + ActivateEffects(); } #endregion diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index f928cbab8..0607a7f24 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -148,5 +148,7 @@ namespace Artemis.Core.LayerEffects // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // but LayerEffect outside the core internal abstract void Initialize(); + + internal virtual string GetEffectTypeName() => GetType().Name; } } \ 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 47d95d396..808869b6f 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Artemis.Core.LayerBrushes; using Artemis.Storage.Entities.Profile; using Ninject; @@ -51,6 +50,11 @@ namespace Artemis.Core.LayerEffects /// internal IKernel Kernel { get; set; } + /// + /// Gets a boolean indicating if this descriptor is a placeholder for a missing plugin + /// + public bool IsPlaceHolder { get; internal set; } + /// /// Creates an instance of the described effect and applies it to the render element /// @@ -60,6 +64,12 @@ namespace Artemis.Core.LayerEffects if (renderElement.LayerEffects.Any(e => e.EntityId == entity.Id)) return; + if (IsPlaceHolder) + { + CreatePlaceHolderInstance(renderElement, entity); + return; + } + var effect = (BaseLayerEffect) Kernel.Get(LayerEffectType); effect.ProfileElement = renderElement; effect.EntityId = entity.Id; @@ -73,5 +83,11 @@ namespace Artemis.Core.LayerEffects renderElement.ActivateLayerEffect(effect); } + + private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity) + { + var effect = new PlaceholderLayerEffect(entity) {ProfileElement = renderElement, Descriptor = this}; + renderElement.ActivateLayerEffect(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 new file mode 100644 index 000000000..067417d0d --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -0,0 +1,58 @@ +using Artemis.Storage.Entities.Profile; +using SkiaSharp; + +namespace Artemis.Core.LayerEffects +{ + /// + /// Represents a layer effect that could not be loaded due to a missing plugin + /// + public class PlaceholderLayerEffect : BaseLayerEffect + { + internal PlaceholderLayerEffect(LayerEffectEntity originalEntity) + { + OriginalEntity = originalEntity; + + EntityId = OriginalEntity.Id; + Order = OriginalEntity.Order; + Name = OriginalEntity.Name; + Enabled = OriginalEntity.Enabled; + HasBeenRenamed = OriginalEntity.HasBeenRenamed; + } + + internal LayerEffectEntity OriginalEntity { get; } + + /// + public override void EnableLayerEffect() + { + } + + /// + public override void DisableLayerEffect() + { + } + + /// + public override void Update(double deltaTime) + { + } + + /// + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + } + + /// + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) + { + } + + internal override string GetEffectTypeName() + { + return OriginalEntity.EffectType; + } + + internal override void Initialize() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs new file mode 100644 index 000000000..b34583a0f --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffectDescriptor.cs @@ -0,0 +1,11 @@ +namespace Artemis.Core.LayerEffects.Placeholder +{ + internal static class PlaceholderLayerEffectDescriptor + { + public static LayerEffectDescriptor Create() + { + var descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, null) {IsPlaceHolder = true}; + return descriptor; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Stores/LayerBrushStore.cs b/src/Artemis.Core/Stores/LayerBrushStore.cs index 75af208b0..9dd77dc9b 100644 --- a/src/Artemis.Core/Stores/LayerBrushStore.cs +++ b/src/Artemis.Core/Stores/LayerBrushStore.cs @@ -51,7 +51,8 @@ namespace Artemis.Core { lock (Registrations) { - return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && d.LayerBrushDescriptor.LayerBrushType.Name == typeName); + return Registrations.FirstOrDefault(d => d.Plugin.PluginInfo.Guid == pluginGuid && + d.LayerBrushDescriptor.LayerBrushType.Name == typeName); } } diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs index 92df0c90c..b4185660e 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IProfileEditorService.cs @@ -10,7 +10,7 @@ namespace Artemis.UI.Shared.Services { Profile SelectedProfile { get; } RenderProfileElement SelectedProfileElement { get; } - BaseLayerProperty SelectedDataBinding { get; } + ILayerProperty SelectedDataBinding { get; } TimeSpan CurrentTime { get; set; } int PixelsPerSecond { get; set; } IReadOnlyList RegisteredPropertyEditors { get; } @@ -19,7 +19,7 @@ namespace Artemis.UI.Shared.Services void UpdateSelectedProfile(); void ChangeSelectedProfileElement(RenderProfileElement profileElement); void UpdateSelectedProfileElement(); - void ChangeSelectedDataBinding(BaseLayerProperty layerProperty); + void ChangeSelectedDataBinding(ILayerProperty layerProperty); void UpdateProfilePreview(); bool UndoUpdateProfile(); bool RedoUpdateProfile(); @@ -88,5 +88,10 @@ namespace Artemis.UI.Shared.Services /// A keyframe to exclude during keyframe snapping /// TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, bool snapToKeyframes, BaseLayerPropertyKeyframe excludedKeyframe = null); + + /// + /// If a matching registration is found, creates a new supporting + /// + PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index ac437396b..bb651f9bd 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -5,6 +5,7 @@ using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Ninject; +using Ninject.Parameters; using Serilog; using Stylet; @@ -269,6 +270,16 @@ namespace Artemis.UI.Shared.Services return time; } + public PropertyInputViewModel CreatePropertyInputViewModel(LayerProperty layerProperty) + { + var registration = RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); + if (registration == null) + return null; + + var parameter = new ConstructorArgument("layerProperty", layerProperty); + return (PropertyInputViewModel) Kernel.Get(registration.ViewModelType, parameter); + } + public ProfileModule GetCurrentModule() { return SelectedProfile?.Module; diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 68df7dd44..04c3cecef 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,13 +1,10 @@ -using System.Reflection; -using Artemis.Core; +using Artemis.Core; using Artemis.Core.Modules; using Artemis.UI.Screens.Modules; using Artemis.UI.Screens.Modules.Tabs; using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor.DisplayConditions; -using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract; using Artemis.UI.Screens.ProfileEditor.LayerProperties; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; @@ -19,7 +16,6 @@ using Artemis.UI.Screens.Settings.Debug; using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Stylet; -using Module = Artemis.Core.Modules.Module; namespace Artemis.UI.Ninject.Factories { @@ -46,15 +42,10 @@ namespace Artemis.UI.Ninject.Factories DeviceDebugViewModel Create(ArtemisDevice device); } - public interface IFolderVmFactory : IVmFactory + public interface IProfileTreeVmFactory : IVmFactory { - FolderViewModel Create(ProfileElement folder); - FolderViewModel Create(TreeItemViewModel parent, ProfileElement folder); - } - - public interface ILayerVmFactory : IVmFactory - { - LayerViewModel Create(TreeItemViewModel parent, ProfileElement folder); + FolderViewModel FolderViewModel(ProfileElement folder); + LayerViewModel LayerViewModel(ProfileElement layer); } public interface IProfileLayerVmFactory : IVmFactory @@ -72,10 +63,10 @@ namespace Artemis.UI.Ninject.Factories public interface IDisplayConditionsVmFactory : IVmFactory { - DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, bool isListGroup); - DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList, DisplayConditionViewModel parent); - DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate, DisplayConditionViewModel parent); - DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate, DisplayConditionViewModel parent); + DisplayConditionGroupViewModel DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, bool isListGroup); + DisplayConditionListViewModel DisplayConditionListViewModel(DisplayConditionList displayConditionList); + DisplayConditionPredicateViewModel DisplayConditionPredicateViewModel(DisplayConditionPredicate displayConditionPredicate); + DisplayConditionListPredicateViewModel DisplayConditionListPredicateViewModel(DisplayConditionListPredicate displayConditionListPredicate); } public interface IDataBindingsVmFactory : IVmFactory @@ -87,11 +78,13 @@ namespace Artemis.UI.Ninject.Factories public interface ILayerPropertyVmFactory : IVmFactory { + LayerPropertyViewModel LayerPropertyViewModel(ILayerProperty layerProperty); + LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup); + LayerPropertyTreeViewModel LayerPropertyGroupViewModel(LayerProperty layerProperty); + LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel); LayerPropertyGroupViewModel LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription); TreeViewModel TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); EffectsViewModel EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel); TimelineViewModel TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups); - TreePropertyGroupViewModel TreePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel); - TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel); } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 2903c5a48..3ae26f21d 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Ninject { x.FromThisAssembly() .SelectAllClasses() - .InheritedFrom() + .InheritedFrom() .BindAllBaseClasses(); }); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs index 743fec551..65565fdb2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/Abstract/DisplayConditionViewModel.cs @@ -1,44 +1,23 @@ -using System; -using Artemis.Core; +using Artemis.Core; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract { - public abstract class DisplayConditionViewModel : PropertyChangedBase, IDisposable + public abstract class DisplayConditionViewModel : Conductor.Collection.AllActive { - protected DisplayConditionViewModel(DisplayConditionPart model, DisplayConditionViewModel parent) + protected DisplayConditionViewModel(DisplayConditionPart model) { Model = model; - Parent = parent; - Children = new BindableCollection(); } public DisplayConditionPart Model { get; } - public DisplayConditionViewModel Parent { get; set; } - public BindableCollection Children { get; } - - public void Dispose() - { - foreach (var child in Children) - child.Dispose(); - - Dispose(true); - GC.SuppressFinalize(this); - } public abstract void Update(); public virtual void Delete() { Model.Parent.RemoveChild(Model); - Parent.Update(); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } + ((DisplayConditionViewModel) Parent).Update(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml index b0ec8a679..f1a770d08 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml @@ -124,7 +124,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs index 1f1deae4d..a5caf78dd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using System.Windows; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.DisplayConditions.Abstract; @@ -11,21 +10,24 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionGroupViewModel : DisplayConditionViewModel, IViewAware + public class DisplayConditionGroupViewModel : DisplayConditionViewModel { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; private bool _isInitialized; private bool _isRootGroup; - public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, DisplayConditionViewModel parent, bool isListGroup, - IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) : base(displayConditionGroup, parent) + public DisplayConditionGroupViewModel(DisplayConditionGroup displayConditionGroup, + bool isListGroup, + IProfileEditorService profileEditorService, + IDisplayConditionsVmFactory displayConditionsVmFactory) + : base(displayConditionGroup) { IsListGroup = isListGroup; _profileEditorService = profileEditorService; _displayConditionsVmFactory = displayConditionsVmFactory; - Children.CollectionChanged += (sender, args) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); + Items.CollectionChanged += (sender, args) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); Execute.PostToUIThread(async () => { @@ -50,16 +52,9 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _isInitialized, value); } - public bool DisplayBooleanOperator => Children.Count > 1; + public bool DisplayBooleanOperator => Items.Count > 1; public string SelectedBooleanOperator => DisplayConditionGroup.BooleanOperator.Humanize(); - public void AttachView(UIElement view) - { - View = view; - } - - public UIElement View { get; set; } - public void SelectBooleanOperator(string type) { var enumValue = Enum.Parse(type); @@ -105,39 +100,36 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); // Remove VMs of effects no longer applied on the layer - var toRemove = Children.Where(c => !DisplayConditionGroup.Children.Contains(c.Model)).ToList(); + var toRemove = Items.Where(c => !DisplayConditionGroup.Children.Contains(c.Model)).ToList(); // Using RemoveRange breaks our lovely animations foreach (var displayConditionViewModel in toRemove) - { - Children.Remove(displayConditionViewModel); - displayConditionViewModel.Dispose(); - } + CloseItem(displayConditionViewModel); foreach (var childModel in Model.Children) { - if (Children.Any(c => c.Model == childModel)) + if (Items.Any(c => c.Model == childModel)) continue; switch (childModel) { case DisplayConditionGroup displayConditionGroup: - Children.Add(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, IsListGroup)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, IsListGroup)); break; case DisplayConditionList displayConditionListPredicate: - Children.Add(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionListViewModel(displayConditionListPredicate)); break; case DisplayConditionPredicate displayConditionPredicate: if (!IsListGroup) - Children.Add(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionPredicateViewModel(displayConditionPredicate)); break; case DisplayConditionListPredicate displayConditionListPredicate: if (IsListGroup) - Children.Add(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate, this)); + ActivateItem(_displayConditionsVmFactory.DisplayConditionListPredicateViewModel(displayConditionListPredicate)); break; } } - foreach (var childViewModel in Children) + foreach (var childViewModel in Items) childViewModel.Update(); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs index 08e7165ec..7d07d3c11 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListPredicateViewModel.cs @@ -17,9 +17,9 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle + public class DisplayConditionListPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle, IDisposable { - private readonly IDataModelService _dataModelService; + private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; @@ -39,16 +39,15 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public DisplayConditionListPredicateViewModel( DisplayConditionListPredicate displayConditionListPredicate, - DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, - IDataModelService dataModelService, + IConditionOperatorService conditionOperatorService, ISettingsService settingsService, - IEventAggregator eventAggregator) : base(displayConditionListPredicate, parent) + IEventAggregator eventAggregator) : base(displayConditionListPredicate) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; - _dataModelService = dataModelService; + _conditionOperatorService = conditionOperatorService; _eventAggregator = eventAggregator; _updateTimer = new Timer(500); _supportedInputTypes = new List(); @@ -206,7 +205,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionListPredicate.Operator == null) DisplayConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionListPredicate.Operator; @@ -277,12 +276,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions _eventAggregator.Subscribe(this); } - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (LeftSideDataModelOpen) @@ -353,5 +346,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedOperator = displayConditionOperator; ApplyOperator(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml index 3ed7ca5d1..634955989 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListView.xaml @@ -105,7 +105,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs index a2ab2fb6f..73393f6cb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionListViewModel.cs @@ -14,7 +14,7 @@ using Humanizer; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionListViewModel : DisplayConditionViewModel + public class DisplayConditionListViewModel : DisplayConditionViewModel, IDisposable { private readonly IDataModelUIService _dataModelUIService; private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; @@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, IDisplayConditionsVmFactory displayConditionsVmFactory, - ISettingsService settingsService) : base(displayConditionList, parent) + ISettingsService settingsService) : base(displayConditionList) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; @@ -152,36 +152,27 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions TargetDataModel.ApplyTypeFilter(true, typeof(IList)); // Remove VMs of effects no longer applied on the layer - var toRemove = Children.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList(); + var toRemove = Items.Where(c => !DisplayConditionList.Children.Contains(c.Model)).ToList(); // Using RemoveRange breaks our lovely animations foreach (var displayConditionViewModel in toRemove) - { - Children.Remove(displayConditionViewModel); - displayConditionViewModel.Dispose(); - } + CloseItem(displayConditionViewModel); foreach (var childModel in Model.Children) { - if (Children.Any(c => c.Model == childModel)) + if (Items.Any(c => c.Model == childModel)) continue; if (!(childModel is DisplayConditionGroup displayConditionGroup)) continue; - var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, this, true); + var viewModel = _displayConditionsVmFactory.DisplayConditionGroupViewModel(displayConditionGroup, true); viewModel.IsRootGroup = true; - Children.Add(viewModel); + ActivateItem(viewModel); } - foreach (var childViewModel in Children) + foreach (var childViewModel in Items) childViewModel.Update(); } - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (TargetDataModelOpen) @@ -205,5 +196,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedListProperty = dataModelListViewModel; ApplyList(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index a7750ee91..f8b7833bd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -16,9 +16,9 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle + public class DisplayConditionPredicateViewModel : DisplayConditionViewModel, IHandle, IHandle, IDisposable { - private readonly IDataModelService _dataModelService; + private readonly IConditionOperatorService _conditionOperatorService; private readonly IDataModelUIService _dataModelUIService; private readonly IEventAggregator _eventAggregator; private readonly IProfileEditorService _profileEditorService; @@ -38,16 +38,15 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions public DisplayConditionPredicateViewModel( DisplayConditionPredicate displayConditionPredicate, - DisplayConditionViewModel parent, IProfileEditorService profileEditorService, IDataModelUIService dataModelUIService, - IDataModelService dataModelService, + IConditionOperatorService conditionOperatorService, ISettingsService settingsService, - IEventAggregator eventAggregator) : base(displayConditionPredicate, parent) + IEventAggregator eventAggregator) : base(displayConditionPredicate) { _profileEditorService = profileEditorService; _dataModelUIService = dataModelUIService; - _dataModelService = dataModelService; + _conditionOperatorService = conditionOperatorService; _eventAggregator = eventAggregator; _updateTimer = new Timer(500); _supportedInputTypes = new List(); @@ -208,7 +207,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_dataModelService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); if (DisplayConditionPredicate.Operator == null) DisplayConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); SelectedOperator = DisplayConditionPredicate.Operator; @@ -278,13 +277,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions ); _eventAggregator.Subscribe(this); } - - protected override void Dispose(bool disposing) - { - _updateTimer.Stop(); - _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; - } - + private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) { if (LeftSideDataModelOpen) @@ -335,5 +328,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions SelectedOperator = displayConditionOperator; ApplyOperator(); } + + public void Dispose() + { + _updateTimer.Dispose(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 43ca3526b..68c8ef8f9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -43,7 +43,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 2931a2907..e2d3a7a04 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -3,17 +3,17 @@ using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions { - public class DisplayConditionsViewModel : ProfileEditorPanelViewModel + public class DisplayConditionsViewModel : Conductor, IProfileEditorPanelViewModel { private readonly IDisplayConditionsVmFactory _displayConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; private bool _alwaysFinishTimeline; private bool _displayContinuously; private RenderProfileElement _renderProfileElement; - private DisplayConditionGroupViewModel _rootGroup; private int _transitionerIndex; public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IDisplayConditionsVmFactory displayConditionsVmFactory) @@ -28,11 +28,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions set => SetAndNotify(ref _transitionerIndex, value); } - public DisplayConditionGroupViewModel RootGroup - { - get => _rootGroup; - set => SetAndNotify(ref _rootGroup, value); - } public RenderProfileElement RenderProfileElement { @@ -70,9 +65,6 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions protected override void OnDeactivate() { _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; - - RootGroup?.Dispose(); - RootGroup = null; } private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e) @@ -87,8 +79,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (e.RenderProfileElement == null) { - RootGroup?.Dispose(); - RootGroup = null; + ActiveItem = null; return; } @@ -96,14 +87,13 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions if (e.RenderProfileElement.DisplayConditionGroup == null) e.RenderProfileElement.DisplayConditionGroup = new DisplayConditionGroup(null); - RootGroup?.Dispose(); - RootGroup = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, null, false); - RootGroup.IsRootGroup = true; - RootGroup.Update(); + ActiveItem = _displayConditionsVmFactory.DisplayConditionGroupViewModel(e.RenderProfileElement.DisplayConditionGroup, false); + ActiveItem.IsRootGroup = true; + ActiveItem.Update(); // Only show the intro to conditions once, and only if the layer has no conditions if (TransitionerIndex != 1) - TransitionerIndex = RootGroup.Children.Any() ? 1 : 0; + TransitionerIndex = ActiveItem.Items.Any() ? 1 : 0; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs similarity index 55% rename from src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs rename to src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs index e2829cefe..cf5601e85 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorPanelViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/IProfileEditorPanelViewModel.cs @@ -2,7 +2,7 @@ namespace Artemis.UI.Screens.ProfileEditor { - public class ProfileEditorPanelViewModel : Screen + public interface IProfileEditorPanelViewModel : IScreen { } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs deleted file mode 100644 index 96278e0c3..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract -{ - public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable - { - private BindableCollection _children; - private bool _isExpanded; - - protected LayerPropertyBaseViewModel() - { - Children = new BindableCollection(); - } - - public abstract bool IsVisible { get; } - - public virtual bool IsExpanded - { - get => _isExpanded; - set => SetAndNotify(ref _isExpanded, value); - } - - public BindableCollection Children - { - get => _children; - set => SetAndNotify(ref _children, value); - } - - public abstract void Dispose(); - - public abstract List GetKeyframes(bool expandedOnly); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs index 3423e21d3..199318a63 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs @@ -9,32 +9,22 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects { - public class EffectsViewModel : PropertyChangedBase + public class EffectsViewModel : Conductor.Collection.AllActive { private readonly IPluginService _pluginService; private readonly IProfileEditorService _profileEditorService; - private readonly IRenderElementService _renderElementService; - private BindableCollection _layerEffectDescriptors; private LayerEffectDescriptor _selectedLayerEffectDescriptor; - public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginService pluginService, IRenderElementService renderElementService, IProfileEditorService profileEditorService) + public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginService pluginService, IProfileEditorService profileEditorService) { _pluginService = pluginService; - _renderElementService = renderElementService; _profileEditorService = profileEditorService; LayerPropertiesViewModel = layerPropertiesViewModel; - LayerEffectDescriptors = new BindableCollection(); PropertyChanged += HandleSelectedLayerEffectChanged; } public LayerPropertiesViewModel LayerPropertiesViewModel { get; } - public bool HasLayerEffectDescriptors => LayerEffectDescriptors.Any(); - - public BindableCollection LayerEffectDescriptors - { - get => _layerEffectDescriptors; - set => SetAndNotify(ref _layerEffectDescriptors, value); - } + public bool HasLayerEffectDescriptors => Items.Any(); public LayerEffectDescriptor SelectedLayerEffectDescriptor { @@ -46,15 +36,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects { var layerBrushProviders = _pluginService.GetPluginsOfType(); var descriptors = layerBrushProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); - LayerEffectDescriptors.AddRange(descriptors.Except(LayerEffectDescriptors)); - LayerEffectDescriptors.RemoveRange(LayerEffectDescriptors.Except(descriptors)); + Items.AddRange(descriptors.Except(Items)); + Items.RemoveRange(Items.Except(descriptors)); // Sort by display name var index = 0; - foreach (var layerEffectDescriptor in LayerEffectDescriptors.OrderBy(d => d.DisplayName).ToList()) + foreach (var layerEffectDescriptor in Items.OrderBy(d => d.DisplayName).ToList()) { - if (LayerEffectDescriptors.IndexOf(layerEffectDescriptor) != index) - LayerEffectDescriptors.Move(LayerEffectDescriptors.IndexOf(layerEffectDescriptor), index); + if (Items.IndexOf(layerEffectDescriptor) != index) + ((BindableCollection) Items).Move(Items.IndexOf(layerEffectDescriptor), index); index++; } @@ -78,7 +68,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects Execute.PostToUIThread(async () => { await Task.Delay(500); - _renderElementService.AddLayerEffect(renderElement, SelectedLayerEffectDescriptor); + renderElement.AddLayerEffect(SelectedLayerEffectDescriptor); _profileEditorService.UpdateSelectedProfileElement(); }); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 4aebe4602..68c462ff1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -16,11 +16,10 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using GongSolutions.Wpf.DragDrop; using Stylet; -using static Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerPropertyGroupViewModel.ViewModelType; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertiesViewModel : ProfileEditorPanelViewModel, IDropTarget + public class LayerPropertiesViewModel : Conductor.Collection.AllActive, IProfileEditorPanelViewModel, IDropTarget { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly IDataBindingsVmFactory _dataBindingsVmFactory; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 15cbe89f3..3f87b3479 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -1,58 +1,31 @@ using System; -using System.Collections.Generic; -using System.Linq; using Artemis.Core; using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; -using Artemis.UI.Shared.Services; -using Humanizer; -using Ninject; -using Ninject.Parameters; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel + public class LayerPropertyGroupViewModel : PropertyChangedBase, IDisposable { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; - private ViewModelType _groupType; - private TimelinePropertyGroupViewModel _timelinePropertyGroupViewModel; - private TreePropertyGroupViewModel _treePropertyGroupViewModel; - public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription, - IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory) + public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory) { _layerPropertyVmFactory = layerPropertyVmFactory; - ProfileEditorService = profileEditorService; LayerPropertyGroup = layerPropertyGroup; - PropertyGroupDescription = propertyGroupDescription; - - TreePropertyGroupViewModel = _layerPropertyVmFactory.TreePropertyGroupViewModel(this); - TimelinePropertyGroupViewModel = _layerPropertyVmFactory.TimelinePropertyGroupViewModel(this); - - // Generate a fallback name if the description does not contain one - if (PropertyGroupDescription.Name == null) - { - var propertyInfo = LayerPropertyGroup.Parent?.GetType().GetProperties().FirstOrDefault(p => ReferenceEquals(p.GetValue(LayerPropertyGroup.Parent), LayerPropertyGroup)); - if (propertyInfo != null) - PropertyGroupDescription.Name = propertyInfo.Name.Humanize(); - else - PropertyGroupDescription.Name = "Unknown group"; - } - - LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; + LayerPropertyGroupTreeViewModel = layerPropertyVmFactory.LayerPropertyGroupTreeViewModel(this); PopulateChildren(); - DetermineType(); } - public override bool IsVisible => !LayerPropertyGroup.IsHidden; - public IProfileEditorService ProfileEditorService { get; } public LayerPropertyGroup LayerPropertyGroup { get; } - public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; } + public LayerPropertyGroupTreeViewModel LayerPropertyGroupTreeViewModel { get; } + public BindableCollection Children { get; set; } - public override bool IsExpanded + public bool IsVisible => !LayerPropertyGroup.IsHidden; + + public bool IsExpanded { get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup); set @@ -62,81 +35,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties } } - public ViewModelType GroupType - { - get => _groupType; - set => SetAndNotify(ref _groupType, value); - } - - public TreePropertyGroupViewModel TreePropertyGroupViewModel - { - get => _treePropertyGroupViewModel; - set => SetAndNotify(ref _treePropertyGroupViewModel, value); - } - - public TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel - { - get => _timelinePropertyGroupViewModel; - set => SetAndNotify(ref _timelinePropertyGroupViewModel, value); - } - - public override List GetKeyframes(bool expandedOnly) - { - var result = new List(); - if (expandedOnly && !IsExpanded || LayerPropertyGroup.IsHidden) - return result; - - foreach (var layerPropertyBaseViewModel in Children) - result.AddRange(layerPropertyBaseViewModel.GetKeyframes(expandedOnly)); - - return result; - } - - public override void Dispose() - { - foreach (var layerPropertyBaseViewModel in Children) - layerPropertyBaseViewModel.Dispose(); - - LayerPropertyGroup.VisibilityChanged -= LayerPropertyGroupOnVisibilityChanged; - TimelinePropertyGroupViewModel.Dispose(); - } - - public List GetAllChildren() - { - var result = new List(); - foreach (var layerPropertyBaseViewModel in Children) - { - result.Add(layerPropertyBaseViewModel); - if (layerPropertyBaseViewModel is LayerPropertyGroupViewModel layerPropertyGroupViewModel) - result.AddRange(layerPropertyGroupViewModel.GetAllChildren()); - } - - return result; - } - - public void UpdateOrder(int order) - { - LayerPropertyGroup.LayerEffect.Order = order; - NotifyOfPropertyChange(nameof(IsExpanded)); - } - - private void DetermineType() - { - if (LayerPropertyGroup is LayerGeneralProperties) - GroupType = ViewModelType.General; - else if (LayerPropertyGroup is LayerTransformProperties) - GroupType = ViewModelType.Transform; - else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null) - GroupType = ViewModelType.LayerBrushRoot; - else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null) - GroupType = ViewModelType.LayerEffectRoot; - else - GroupType = ViewModelType.None; - } - private void PopulateChildren() { // Get all properties and property groups and create VMs for them + // The group has methods for getting this without reflection but then we lose the order of the properties as they are defined on the group foreach (var propertyInfo in LayerPropertyGroup.GetType().GetProperties()) { var propertyAttribute = (PropertyDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); @@ -144,50 +46,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties var value = propertyInfo.GetValue(LayerPropertyGroup); // Create VMs for properties on the group - if (propertyAttribute != null && value is BaseLayerProperty baseLayerProperty) + if (propertyAttribute != null && value is ILayerProperty layerProperty) { - var viewModel = CreateLayerPropertyViewModel(baseLayerProperty, propertyAttribute); - if (viewModel != null) - Children.Add(viewModel); + var layerPropertyViewModel = _layerPropertyVmFactory.LayerPropertyViewModel(layerProperty); + // After creation ensure a supported input VM was found, if not, discard the VM + if (!layerPropertyViewModel.LayerPropertyTreeViewModel.HasPropertyInputViewModel) + layerPropertyViewModel.Dispose(); + else + Children.Add(layerPropertyViewModel); } // Create VMs for child groups on this group, resulting in a nested structure else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup) - Children.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute)); + Children.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerPropertyGroup)); } } - private LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription) + public void Dispose() { - // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line - var genericType = baseLayerProperty.GetType().Name == typeof(LayerProperty<>).Name - ? baseLayerProperty.GetType().GetGenericArguments()[0] - : baseLayerProperty.GetType().BaseType.GetGenericArguments()[0]; - - // Only create entries for types supported by a tree input VM - if (!genericType.IsEnum && ProfileEditorService.RegisteredPropertyEditors.All(r => r.SupportedType != genericType)) - return null; - var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType); - var parameters = new IParameter[] + foreach (var child in Children) { - new ConstructorArgument("layerProperty", baseLayerProperty), - new ConstructorArgument("propertyDescription", propertyDescription) - }; - - return (LayerPropertyBaseViewModel) ProfileEditorService.Kernel.Get(genericViewModel, parameters); - } - - private void LayerPropertyGroupOnVisibilityChanged(object sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(IsVisible)); - } - - public enum ViewModelType - { - General, - Transform, - LayerBrushRoot, - LayerEffectRoot, - None + if (child is IDisposable disposableChild) + disposableChild.Dispose(); + } } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 542c0730b..f436b7f0d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,141 +1,37 @@ using System; -using System.Collections.Generic; -using System.Linq; using Artemis.Core; -using Artemis.UI.Exceptions; -using Artemis.UI.PropertyInput; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; -using Humanizer; using Ninject; using Ninject.Parameters; +using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : LayerPropertyViewModel + public class LayerPropertyViewModel : PropertyChangedBase, IDisposable { - private TimelinePropertyViewModel _timelinePropertyViewModel; - private TreePropertyViewModel _treePropertyViewModel; - - public LayerPropertyViewModel(IProfileEditorService profileEditorService, LayerProperty layerProperty) : base(profileEditorService, layerProperty) + public LayerPropertyViewModel(ILayerProperty layerProperty, IKernel kernel) { LayerProperty = layerProperty; - TreePropertyViewModel = CreateTreePropertyViewModel(); - TimelinePropertyViewModel = new TimelinePropertyViewModel(this, profileEditorService); + var parameter = new ConstructorArgument("layerProperty", LayerProperty); + var treeViewModelType = typeof(LayerPropertyTreeViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); + var timelineViewModelType = typeof(LayerPropertyTimelineViewModel<>).MakeGenericType(layerProperty.GetType().GetGenericArguments()); - TreePropertyBaseViewModel = TreePropertyViewModel; - TimelinePropertyBaseViewModel = TimelinePropertyViewModel; - - // Generate a fallback name if the description does not contain one - if (LayerProperty.PropertyDescription.Name == null) - { - var propertyInfo = LayerProperty.Parent?.GetType().GetProperties().FirstOrDefault(p => ReferenceEquals(p.GetValue(LayerProperty.Parent), LayerProperty)); - if (propertyInfo != null) - LayerProperty.PropertyDescription.Name = propertyInfo.Name.Humanize(); - else - LayerProperty.PropertyDescription.Name = $"Unknown {typeof(T).Name} property"; - } - - LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged; + LayerPropertyTreeViewModel = (ILayerPropertyTreeViewModel) kernel.Get(treeViewModelType, parameter); + LayerPropertyTimelineViewModel = (ILayerPropertyTimelineViewModel) kernel.Get(timelineViewModelType, parameter); } - public override bool IsVisible => !LayerProperty.IsHidden; + public ILayerProperty LayerProperty { get; } + public ILayerPropertyTreeViewModel LayerPropertyTreeViewModel { get; } + public ILayerPropertyTimelineViewModel LayerPropertyTimelineViewModel { get; } - public LayerProperty LayerProperty { get; } + public bool IsVisible { get; set; } + public bool IsExpanded { get; set; } - public TreePropertyViewModel TreePropertyViewModel + public void Dispose() { - get => _treePropertyViewModel; - set => SetAndNotify(ref _treePropertyViewModel, value); - } - - public TimelinePropertyViewModel TimelinePropertyViewModel - { - get => _timelinePropertyViewModel; - set => SetAndNotify(ref _timelinePropertyViewModel, value); - } - - public override List GetKeyframes(bool expandedOnly) - { - if (LayerProperty.KeyframesEnabled && !LayerProperty.IsHidden) - return LayerProperty.BaseKeyframes.ToList(); - return new List(); - } - - public override void Dispose() - { - TreePropertyViewModel.Dispose(); - TimelinePropertyViewModel.Dispose(); - - LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged; - } - - public void SetCurrentValue(T value, bool saveChanges) - { - LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime); - if (saveChanges) - ProfileEditorService.UpdateSelectedProfileElement(); - else - ProfileEditorService.UpdateProfilePreview(); - } - - private TreePropertyViewModel CreateTreePropertyViewModel() - { - // Make sure there is a supported property editor VM, unless the type is an enum, then we'll use the EnumPropertyInputViewModel - Type vmType = null; - if (typeof(T).IsEnum) - vmType = typeof(EnumPropertyInputViewModel<>).MakeGenericType(typeof(T)); - else - { - var registration = ProfileEditorService.RegisteredPropertyEditors.FirstOrDefault(r => r.SupportedType == typeof(T)); - if (registration != null) - vmType = registration.ViewModelType; - } - - if (vmType == null) - throw new ArtemisUIException($"Cannot create a tree property view model for type {typeof(T)}, found no matching property editor"); - - var parameters = new IParameter[] - { - new ConstructorArgument("layerProperty", LayerProperty) - }; - return new TreePropertyViewModel(this, (PropertyInputViewModel) ProfileEditorService.Kernel.Get(vmType, parameters), ProfileEditorService); - } - - private void LayerPropertyOnVisibilityChanged(object sender, EventArgs e) - { - NotifyOfPropertyChange(nameof(IsVisible)); - } - } - - public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel - { - private TimelinePropertyViewModel _timelinePropertyBaseViewModel; - private TreePropertyViewModel _treePropertyBaseViewModel; - - protected LayerPropertyViewModel(IProfileEditorService profileEditorService, BaseLayerProperty baseLayerProperty) - { - ProfileEditorService = profileEditorService; - BaseLayerProperty = baseLayerProperty; - } - - public IProfileEditorService ProfileEditorService { get; } - public BaseLayerProperty BaseLayerProperty { get; } - - public TreePropertyViewModel TreePropertyBaseViewModel - { - get => _treePropertyBaseViewModel; - set => SetAndNotify(ref _treePropertyBaseViewModel, value); - } - - public TimelinePropertyViewModel TimelinePropertyBaseViewModel - { - get => _timelinePropertyBaseViewModel; - set => SetAndNotify(ref _timelinePropertyBaseViewModel, value); + LayerPropertyTreeViewModel?.Dispose(); + LayerPropertyTimelineViewModel?.Dispose(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs new file mode 100644 index 000000000..370dc0904 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/LayerPropertyTimelineViewModel.cs @@ -0,0 +1,26 @@ +using System; +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties +{ + public class LayerPropertyTimelineViewModel : Screen, ILayerPropertyTimelineViewModel + { + public LayerProperty LayerProperty { get; } + public LayerPropertyViewModel LayerPropertyViewModel { get; } + + public LayerPropertyTimelineViewModel(LayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel) + { + LayerProperty = layerProperty; + LayerPropertyViewModel = layerPropertyViewModel; + } + + public void Dispose() + { + } + } + + public interface ILayerPropertyTimelineViewModel : IScreen, IDisposable + { + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs new file mode 100644 index 000000000..fb39a056f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Rails/TimelineGroupViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Artemis.Core; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Rails +{ + public class TimelineGroupViewModel + { + public LayerPropertyGroup LayerPropertyGroup { get; } + + public TimelineGroupViewModel(LayerPropertyGroup layerPropertyGroup) + { + LayerPropertyGroup = layerPropertyGroup; + } + } +} diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml similarity index 71% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml index 406333f4d..a33638fbd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/LayerPropertyGroupTreeView.xaml @@ -9,7 +9,7 @@ xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:dd="urn:gong-wpf-dragdrop" mc:Ignorable="d" - d:DataContext="{d:DesignInstance local:TreePropertyGroupViewModel}" + d:DataContext="{d:DesignInstance local:LayerPropertyGroupTreeViewModel}" d:DesignHeight="450" d:DesignWidth="800"> @@ -18,8 +18,8 @@ @@ -27,7 +27,7 @@ - General + General @@ -57,14 +57,14 @@ - Transform + Transform @@ -79,23 +79,23 @@ Brush -  @@ -125,7 +125,7 @@ - - + + - - + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs index 15ba072bb..5c84fa57f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs @@ -5,11 +5,13 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree { - public class TreeViewModel : PropertyChangedBase + public class TreeViewModel : Screen { public TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups) { LayerPropertiesViewModel = layerPropertiesViewModel; + + // Not using the Items collection because the list should persist even after this VM gets closed LayerPropertyGroups = layerPropertyGroups; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 52d80ed29..4e459d2b8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -17,7 +17,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor { - public class ProfileEditorViewModel : Conductor.Collection.AllActive + public class ProfileEditorViewModel : Conductor.Collection.AllActive { private readonly IModuleService _moduleService; private readonly IProfileEditorService _profileEditorService; @@ -36,7 +36,7 @@ namespace Artemis.UI.Screens.ProfileEditor private PluginSetting _sidePanelsWidth; public ProfileEditorViewModel(ProfileModule module, - ICollection viewModels, + ICollection viewModels, IProfileEditorService profileEditorService, IProfileService profileService, IDialogService dialogService, diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml index c4cbdf640..32eb00d36 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -27,7 +27,7 @@ - + - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 1593bfecc..6eee624df 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -11,24 +11,17 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { - public class ProfileTreeViewModel : ProfileEditorPanelViewModel, IDropTarget + public class ProfileTreeViewModel : Conductor, IProfileEditorPanelViewModel, IDropTarget { - private readonly IFolderVmFactory _folderVmFactory; + private readonly IProfileTreeVmFactory _profileTreeVmFactory; private readonly IProfileEditorService _profileEditorService; - private FolderViewModel _rootFolder; private TreeItemViewModel _selectedTreeItem; private bool _updatingTree; - public ProfileTreeViewModel(IProfileEditorService profileEditorService, IFolderVmFactory folderVmFactory) + public ProfileTreeViewModel(IProfileEditorService profileEditorService, IProfileTreeVmFactory profileTreeVmFactory) { _profileEditorService = profileEditorService; - _folderVmFactory = folderVmFactory; - } - - public FolderViewModel RootFolder - { - get => _rootFolder; - set => SetAndNotify(ref _rootFolder, value); + _profileTreeVmFactory = profileTreeVmFactory; } public TreeItemViewModel SelectedTreeItem @@ -73,7 +66,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree switch (dragDropType) { case DragDropType.Add: - source.Parent.RemoveExistingElement(source); + ((TreeItemViewModel) source.Parent).RemoveExistingElement(source); target.AddExistingElement(source); break; case DragDropType.InsertBefore: @@ -92,27 +85,25 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree // ReSharper disable once UnusedMember.Global - Called from view public void AddFolder() { - RootFolder?.AddFolder(); + ActiveItem?.AddFolder(); } // ReSharper disable once UnusedMember.Global - Called from view public void AddLayer() { - RootFolder?.AddLayer(); + ActiveItem?.AddLayer(); } protected override void OnInitialActivate() { Subscribe(); CreateRootFolderViewModel(); + base.OnInitialActivate(); } protected override void OnClose() { Unsubscribe(); - - RootFolder?.Dispose(); - RootFolder = null; base.OnClose(); } @@ -122,12 +113,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree var firstChild = _profileEditorService.SelectedProfile?.Children?.FirstOrDefault(); if (!(firstChild is Folder folder)) { - RootFolder = null; + ActivateItem(null); return; } - RootFolder?.Dispose(); - RootFolder = _folderVmFactory.Create(folder); + ActivateItem(_profileTreeVmFactory.FolderViewModel(folder)); _updatingTree = false; // Auto-select the first layer @@ -150,7 +140,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { if (parent == source) return DragDropType.None; - parent = parent.Parent; + parent = (TreeItemViewModel) parent.Parent; } switch (dropInfo.InsertPosition) @@ -186,20 +176,20 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree if (e.RenderProfileElement == SelectedTreeItem?.ProfileElement) return; - if (RootFolder == null) + if (ActiveItem == null) { CreateRootFolderViewModel(); return; } _updatingTree = true; - RootFolder.UpdateProfileElements(); + ActiveItem.UpdateProfileElements(); _updatingTree = false; if (e.RenderProfileElement == null) SelectedTreeItem = null; else { - var match = RootFolder.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement); + var match = ActiveItem.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement); if (match != null) SelectedTreeItem = match; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs index 16d594f11..cfc085612 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs @@ -1,5 +1,4 @@ using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -11,36 +10,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public FolderViewModel(ProfileElement folder, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(null, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) - { - } - - public FolderViewModel(TreeItemViewModel parent, - ProfileElement folder, - IProfileEditorService profileEditorService, - IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(parent, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) : + base(folder, profileEditorService, dialogService, profileTreeVmFactory) { } public override bool SupportsChildren => true; - - protected override void Dispose(bool disposing) - { - if (disposing) - { - foreach (var treeItemViewModel in Children) - treeItemViewModel.Dispose(); - Children.Clear(); - } - - base.Dispose(disposing); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index c539a5a55..ce0e43ebc 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -1,5 +1,4 @@ using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -7,14 +6,11 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { public class LayerViewModel : TreeItemViewModel { - public LayerViewModel(TreeItemViewModel parent, - ProfileElement folder, + public LayerViewModel(ProfileElement layer, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) : - base(parent, folder, profileEditorService, dialogService, renderElementService, folderVmFactory, layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) : + base(layer, profileEditorService, dialogService, profileTreeVmFactory) { } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index a8d7f006b..cc923620d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Dialogs; @@ -12,66 +11,45 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { - public abstract class TreeItemViewModel : PropertyChangedBase, IDisposable + public abstract class TreeItemViewModel : Conductor.Collection.AllActive, IDisposable { private readonly IDialogService _dialogService; - private readonly IFolderVmFactory _folderVmFactory; - private readonly ILayerVmFactory _layerVmFactory; private readonly IProfileEditorService _profileEditorService; - private readonly IRenderElementService _renderElementService; - private TreeItemViewModel _parent; + private readonly IProfileTreeVmFactory _profileTreeVmFactory; private ProfileElement _profileElement; - protected TreeItemViewModel(TreeItemViewModel parent, - ProfileElement profileElement, + protected TreeItemViewModel(ProfileElement profileElement, IProfileEditorService profileEditorService, IDialogService dialogService, - IRenderElementService renderElementService, - IFolderVmFactory folderVmFactory, - ILayerVmFactory layerVmFactory) + IProfileTreeVmFactory profileTreeVmFactory) { _profileEditorService = profileEditorService; _dialogService = dialogService; - _renderElementService = renderElementService; - _folderVmFactory = folderVmFactory; - _layerVmFactory = layerVmFactory; + _profileTreeVmFactory = profileTreeVmFactory; - Parent = parent; ProfileElement = profileElement; - Children = new BindableCollection(); - Subscribe(); UpdateProfileElements(); } - public TreeItemViewModel Parent - { - get => _parent; - set => SetAndNotify(ref _parent, value); - } - public ProfileElement ProfileElement { get => _profileElement; set => SetAndNotify(ref _profileElement, value); } - public BindableCollection Children { get; } - public abstract bool SupportsChildren { get; } public void Dispose() { Unsubscribe(); - Dispose(true); - GC.SuppressFinalize(this); } public List GetAllChildren() { var children = new List(); - foreach (var childFolder in Children) + foreach (var childFolder in Items) { // Add all children in this element children.Add(childFolder); @@ -84,34 +62,38 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public void SetElementInFront(TreeItemViewModel source) { + var sourceParent = (TreeItemViewModel) source.Parent; + var parent = (TreeItemViewModel) Parent; if (source.Parent != Parent) { - source.Parent.RemoveExistingElement(source); - Parent.AddExistingElement(source); + sourceParent.RemoveExistingElement(source); + parent.AddExistingElement(source); } - Parent.Unsubscribe(); - Parent.ProfileElement.RemoveChild(source.ProfileElement); - Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order); - Parent.Subscribe(); + parent.Unsubscribe(); + parent.ProfileElement.RemoveChild(source.ProfileElement); + parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order); + parent.Subscribe(); - Parent.UpdateProfileElements(); + parent.UpdateProfileElements(); } public void SetElementBehind(TreeItemViewModel source) { + var sourceParent = (TreeItemViewModel) source.Parent; + var parent = (TreeItemViewModel) Parent; if (source.Parent != Parent) { - source.Parent.RemoveExistingElement(source); - Parent.AddExistingElement(source); + sourceParent.RemoveExistingElement(source); + parent.AddExistingElement(source); } - Parent.Unsubscribe(); - Parent.ProfileElement.RemoveChild(source.ProfileElement); - Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1); - Parent.Subscribe(); + parent.Unsubscribe(); + parent.ProfileElement.RemoveChild(source.ProfileElement); + parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1); + parent.Subscribe(); - Parent.UpdateProfileElements(); + parent.UpdateProfileElements(); } public void RemoveExistingElement(TreeItemViewModel treeItem) @@ -138,7 +120,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name); - ProfileElement.AddChild(new Folder(ProfileElement.Profile, ProfileElement, "New folder")); + var _ = new Folder(ProfileElement, "New folder"); _profileEditorService.UpdateSelectedProfile(); } @@ -147,7 +129,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); - _renderElementService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer"); + var _ = new Layer(ProfileElement, "New layer"); _profileEditorService.UpdateSelectedProfile(); } @@ -180,7 +162,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem return; // Farewell, cruel world - var parent = Parent; + var parent = (TreeItemViewModel) Parent; ProfileElement.Parent?.RemoveChild(ProfileElement); parent.RemoveExistingElement(this); @@ -190,17 +172,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public void UpdateProfileElements() { // Remove VMs that are no longer a child - var toRemove = Children.Where(c => c.ProfileElement.Parent != ProfileElement).ToList(); + var toRemove = Items.Where(c => c.ProfileElement.Parent != ProfileElement).ToList(); foreach (var treeItemViewModel in toRemove) - Children.Remove(treeItemViewModel); + DeactivateItem(treeItemViewModel); // Order the children - var vmsList = Children.OrderBy(v => v.ProfileElement.Order).ToList(); + var vmsList = Items.OrderBy(v => v.ProfileElement.Order).ToList(); for (var index = 0; index < vmsList.Count; index++) { var profileElementViewModel = vmsList[index]; - if (Children.IndexOf(profileElementViewModel) != index) - Children.Move(Children.IndexOf(profileElementViewModel), index); + if (Items.IndexOf(profileElementViewModel) != index) + ((BindableCollection) Items).Move(Items.IndexOf(profileElementViewModel), index); } // Ensure every child element has an up-to-date VM @@ -212,13 +194,13 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { if (profileElement is Folder folder) { - if (Children.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null) - newChildren.Add(_folderVmFactory.Create((FolderViewModel) this, folder)); + if (Items.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder) == null) + newChildren.Add(_profileTreeVmFactory.FolderViewModel(folder)); } else if (profileElement is Layer layer) { - if (Children.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null) - newChildren.Add(_layerVmFactory.Create((FolderViewModel) this, layer)); + if (Items.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer) == null) + newChildren.Add(_profileTreeVmFactory.LayerViewModel(layer)); } } @@ -229,7 +211,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem foreach (var treeItemViewModel in newChildren) { treeItemViewModel.UpdateProfileElements(); - Children.Add(treeItemViewModel); + ActivateItem(treeItemViewModel); } } @@ -238,13 +220,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem _profileEditorService.UpdateSelectedProfile(); } - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - private void Subscribe() { ProfileElement.ChildAdded += ProfileElementOnChildAdded; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml deleted file mode 100644 index 82a471bc7..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceView.xaml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs deleted file mode 100644 index 828600820..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileDeviceViewModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using Artemis.Core; -using Stylet; - -namespace Artemis.UI.Screens.ProfileEditor.Visualization -{ - public class ProfileDeviceViewModel : CanvasViewModel - { - private bool _addedLeds; - private ArtemisDevice _device; - private ObservableCollection _leds; - - public ProfileDeviceViewModel(ArtemisDevice device) - { - Device = device; - Leds = new ObservableCollection(); - - Task.Run(AddLedsAsync); - } - - public ObservableCollection Leds - { - get => _leds; - set => SetAndNotify(ref _leds, value); - } - - public ArtemisDevice Device - { - get => _device; - set => SetAndNotify(ref _device, value); - } - - public bool AddedLeds - { - get => _addedLeds; - private set => SetAndNotify(ref _addedLeds, value); - } - - public new double X - { - get => Device.X; - set => Device.X = value; - } - - public new double Y - { - get => Device.Y; - set => Device.Y = value; - } - - public int ZIndex - { - get => Device.ZIndex; - set => Device.ZIndex = value; - } - - - public Rect DeviceRectangle => Device.RgbDevice == null - ? new Rect() - : new Rect(X, Y, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height); - - /// - /// Update the color of all LEDs if finished adding - /// - public void Update() - { - if (!AddedLeds) - return; - - foreach (var ledViewModel in Leds) - ledViewModel.Update(); - } - - /// - /// Adds LEDs in batches of 5 to avoid UI freezes - /// - /// - private async Task AddLedsAsync() - { - var index = 0; - foreach (var led in Device.Leds.ToList()) - { - Execute.OnUIThreadSync(() => Leds.Add(new ProfileLedViewModel(led))); - if (index % 5 == 0) - await Task.Delay(1); - - index++; - } - - AddedLeds = true; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml deleted file mode 100644 index 083bfc23c..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedView.xaml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs deleted file mode 100644 index 26abba437..000000000 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLedViewModel.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Media; -using Artemis.Core; -using Artemis.UI.Extensions; -using RGB.NET.Core; -using Stylet; -using Color = System.Windows.Media.Color; - -namespace Artemis.UI.Screens.ProfileEditor.Visualization -{ - public class ProfileLedViewModel : PropertyChangedBase - { - private Color _displayColor; - private Geometry _displayGeometry; - private bool _isDimmed; - private bool _isSelected; - private Geometry _strokeGeometry; - - public ProfileLedViewModel(ArtemisLed led) - { - Led = led; - - // Don't want ActualLocation here since rotation is done in XAML - X = led.RgbLed.Location.X * led.RgbLed.Device.Scale.Horizontal; - Y = led.RgbLed.Location.Y * led.RgbLed.Device.Scale.Vertical; - Width = led.RgbLed.ActualSize.Width; - Height = led.RgbLed.ActualSize.Height; - - Execute.PostToUIThread(CreateLedGeometry); - } - - public ArtemisLed Led { get; } - - public double X { get; } - public double Y { get; } - public double Width { get; } - public double Height { get; } - - public bool IsSelected - { - get => _isSelected; - set => SetAndNotify(ref _isSelected, value); - } - - public bool IsDimmed - { - get => _isDimmed; - set => SetAndNotify(ref _isDimmed, value); - } - - public Geometry DisplayGeometry - { - get => _displayGeometry; - private set => SetAndNotify(ref _displayGeometry, value); - } - - public Geometry StrokeGeometry - { - get => _strokeGeometry; - private set => SetAndNotify(ref _strokeGeometry, value); - } - - public Color DisplayColor - { - get => _displayColor; - private set => SetAndNotify(ref _displayColor, value); - } - - public void Update() - { - var newColor = Led.RgbLed.Color.ToMediaColor(); - Execute.PostToUIThread(() => - { - if (!DisplayColor.Equals(newColor)) - DisplayColor = newColor; - }); - } - - private void CreateLedGeometry() - { - switch (Led.RgbLed.Shape) - { - case Shape.Custom: - if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) - CreateCustomGeometry(2.0); - else - CreateCustomGeometry(1.0); - break; - case Shape.Rectangle: - if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad) - CreateKeyCapGeometry(); - else - CreateRectangleGeometry(); - break; - case Shape.Circle: - CreateCircleGeometry(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - // Stroke geometry is the display geometry excluding the inner geometry - StrokeGeometry = DisplayGeometry.GetWidenedPathGeometry(new Pen(null, 1.0), 0.1, ToleranceType.Absolute); - DisplayGeometry.Freeze(); - StrokeGeometry.Freeze(); - } - - private void CreateRectangleGeometry() - { - DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1)); - } - - private void CreateCircleGeometry() - { - DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Width - 1, Height - 1)); - } - - private void CreateKeyCapGeometry() - { - DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Width - 2, Height - 2), 1.6, 1.6); - } - - private void CreateCustomGeometry(double deflateAmount) - { - try - { - DisplayGeometry = Geometry.Combine( - Geometry.Empty, - Geometry.Parse(Led.RgbLed.ShapeData), - GeometryCombineMode.Union, - new TransformGroup - { - Children = new TransformCollection - { - new ScaleTransform(Width - deflateAmount, Height - deflateAmount), - new TranslateTransform(deflateAmount / 2, deflateAmount / 2) - } - } - ); - } - catch (Exception) - { - CreateRectangleGeometry(); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index c64e1ffcc..0c5b0f566 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -15,7 +15,7 @@ using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Visualization { - public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle, IHandle + public class ProfileViewModel : Screen, IProfileEditorPanelViewModel, IHandle, IHandle { private readonly IProfileEditorService _profileEditorService; private readonly IProfileLayerVmFactory _profileLayerVmFactory; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml similarity index 97% rename from src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml index 6acacbc9a..42507087e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerBrushSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerBrushSettingsWindowView.xaml @@ -1,4 +1,4 @@ - { diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml similarity index 97% rename from src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml index f7f6d2977..1cc495742 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerEffectSettingsWindowView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Windows/LayerEffectSettingsWindowView.xaml @@ -1,4 +1,4 @@ - {