From 349e0a2c41f6962b45b2a22fe6c7973533dded7f Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 26 Apr 2020 17:30:43 +0200 Subject: [PATCH 01/14] Datamodel - Added basic infrastructure --- src/Artemis.Core/Artemis.Core.csproj | 3 + .../Profile/{ => Colors}/ColorGradient.cs | 26 +------ .../Profile/Colors/ColorGradientStop.cs | 31 +++++++++ .../Profile/Conditions/LayerCondition.cs | 11 +++ .../Attributes/DataModelProperty.cs | 43 ++++++++++++ .../Plugins/Abstract/DataModels/DataModel.cs | 69 +++++++++++++++++++ src/Artemis.Core/Plugins/Abstract/Module.cs | 6 ++ .../Plugins/Abstract/ModuleDataModel.cs | 12 ---- .../Controls/GradientPicker.xaml.cs | 1 + .../ColorGradientToGradientStopsConverter.cs | 1 + .../Ninject/Factories/IVMFactory.cs | 1 + .../GradientEditor/ColorStopViewModel.cs | 1 + .../GradientEditor/GradientEditorViewModel.cs | 1 + .../Services/GradientPickerService.cs | 1 + .../Interfaces/IGradientPickerService.cs | 1 + src/Artemis.UI/Bootstrapper.cs | 3 + .../Ninject/Factories/IVMFactory.cs | 1 + .../ColorGradientPropertyInputViewModel.cs | 1 + .../PropertyTrackKeyframeViewModel.cs | 1 + .../ColorBrush.cs | 1 + .../NoiseBrush.cs | 1 + .../GeneralDataModel.cs | 30 ++++++-- .../GeneralModule.cs | 3 +- 23 files changed, 206 insertions(+), 43 deletions(-) rename src/Artemis.Core/Models/Profile/{ => Colors}/ColorGradient.cs (80%) create mode 100644 src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs create mode 100644 src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs create mode 100644 src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs create mode 100644 src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs delete mode 100644 src/Artemis.Core/Plugins/Abstract/ModuleDataModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 3a632ed07..8869fac59 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -56,4 +56,7 @@ ..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Groups.dll + + + \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs similarity index 80% rename from src/Artemis.Core/Models/Profile/ColorGradient.cs rename to src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index 7414f13ef..f6f2d721a 100644 --- a/src/Artemis.Core/Models/Profile/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -6,7 +6,7 @@ using Artemis.Core.Annotations; using SkiaSharp; using Stylet; -namespace Artemis.Core.Models.Profile +namespace Artemis.Core.Models.Profile.Colors { public class ColorGradient : INotifyPropertyChanged { @@ -88,28 +88,4 @@ namespace Artemis.Core.Models.Profile #endregion } - - public class ColorGradientStop : INotifyPropertyChanged - { - public ColorGradientStop(SKColor color, float position) - { - Color = color; - Position = position; - } - - public SKColor Color { get; set; } - public float Position { get; set; } - - #region PropertyChanged - - public event PropertyChangedEventHandler PropertyChanged; - - [NotifyPropertyChangedInvocator] - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - #endregion - } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs new file mode 100644 index 000000000..7a860da42 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Artemis.Core.Annotations; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile.Colors +{ + public class ColorGradientStop : INotifyPropertyChanged + { + public ColorGradientStop(SKColor color, float position) + { + Color = color; + Position = position; + } + + public SKColor Color { get; set; } + public float Position { get; set; } + + #region PropertyChanged + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs new file mode 100644 index 000000000..fb91ef3b6 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/Conditions/LayerCondition.cs @@ -0,0 +1,11 @@ +using System; +using System.Linq.Expressions; +using Artemis.Core.Plugins.Abstract.DataModels; + +namespace Artemis.Core.Models.Profile.Conditions +{ + public class LayerCondition + { + public Expression> ExpressionTree { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs new file mode 100644 index 000000000..030f951c2 --- /dev/null +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/Attributes/DataModelProperty.cs @@ -0,0 +1,43 @@ +using System; + +namespace Artemis.Core.Plugins.Abstract.DataModels.Attributes +{ + [AttributeUsage(System.AttributeTargets.Property)] + public class DataModelPropertyAttribute : Attribute + { + /// + /// Gets or sets the user-friendly name for this property, shown in the UI. + /// + public string Name { get; set; } + + /// + /// Gets or sets the user-friendly description for this property, shown in the UI. + /// + public string Description { get; set; } + + /// + /// Gets or sets the an optional input prefix to show before input elements in the UI. + /// + public string InputPrefix { get; set; } + + /// + /// Gets or sets an optional input affix to show behind input elements in the UI. + /// + public string InputAffix { get; set; } + + /// + /// Gets or sets an optional maximum input value, only enforced in the UI. + /// + public object MaxInputValue { get; set; } + + /// + /// Gets or sets the input drag step size, used in the UI. + /// + public float InputStepSize { get; set; } + + /// + /// Gets or sets an optional minimum input value, only enforced in the UI. + /// + public object MinInputValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs new file mode 100644 index 000000000..114b1aedb --- /dev/null +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using Artemis.Core.Plugins.Exceptions; +using SkiaSharp; + +namespace Artemis.Core.Plugins.Abstract.DataModels +{ + public abstract class DataModel + { + private static readonly List SupportedTypes = new List + { + typeof(bool), + typeof(byte), + typeof(decimal), + typeof(double), + typeof(float), + typeof(int), + typeof(long), + typeof(string), + typeof(SKColor), + typeof(SKPoint) + }; + + protected DataModel(Module module) + { + Module = module; + Validate(); + } + + public Module Module { get; } + + /// + /// Recursively validates the current datamodel, ensuring all properties annotated with + /// are of supported types. + /// + /// + public bool Validate() + { + return ValidateType(GetType()); + } + + private bool ValidateType(Type type) + { + foreach (var propertyInfo in type.GetProperties()) + { + var dataModelPropertyAttribute = (DataModelPropertyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute)); + if (dataModelPropertyAttribute == null) + continue; + + // If the a nested datamodel, ensure the properties on there are valid + if (propertyInfo.PropertyType == typeof(DataModel)) + ValidateType(propertyInfo.PropertyType); + else if (!SupportedTypes.Contains(propertyInfo.PropertyType)) + { + // Show a useful error for plugin devs + throw new ArtemisPluginException(Module.PluginInfo, + $"Plugin datamodel contains property of unsupported type {propertyInfo.PropertyType.Name}. \r\n\r\n" + + $"Property name: {propertyInfo.Name}\r\n" + + $"Property declared on: {propertyInfo.DeclaringType?.Name ?? "-"} \r\n\r\n" + + $"Supported properties:\r\n{string.Join("\r\n", SupportedTypes.Select(t => $" - {t.Name}"))}"); + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/Module.cs b/src/Artemis.Core/Plugins/Abstract/Module.cs index 2c69f9d4e..1e0b3951a 100644 --- a/src/Artemis.Core/Plugins/Abstract/Module.cs +++ b/src/Artemis.Core/Plugins/Abstract/Module.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Artemis.Core.Models.Surface; +using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.ViewModels; using Artemis.Core.Plugins.Models; using SkiaSharp; @@ -27,6 +28,11 @@ namespace Artemis.Core.Plugins.Abstract /// public string DisplayIcon { get; set; } + /// + /// The optional datamodel driving this module + /// + public DataModel DataModel { get; set; } + /// /// Whether or not this module expands upon the main data model. If set to true any data in main data model can be /// accessed by profiles in this module diff --git a/src/Artemis.Core/Plugins/Abstract/ModuleDataModel.cs b/src/Artemis.Core/Plugins/Abstract/ModuleDataModel.cs deleted file mode 100644 index aef8fe778..000000000 --- a/src/Artemis.Core/Plugins/Abstract/ModuleDataModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Artemis.Core.Plugins.Abstract -{ - public abstract class ModuleDataModel - { - protected ModuleDataModel(Module module) - { - Module = module; - } - - public Module Module { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs index 3a3a59c97..a001c5c9d 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Shared.Annotations; using Artemis.UI.Shared.Services.Interfaces; diff --git a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs index a5c82a4cb..67618268a 100644 --- a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Windows.Data; using System.Windows.Media; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using SkiaSharp; using Stylet; diff --git a/src/Artemis.UI.Shared/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI.Shared/Ninject/Factories/IVMFactory.cs index 026791701..44ba4b651 100644 --- a/src/Artemis.UI.Shared/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI.Shared/Ninject/Factories/IVMFactory.cs @@ -1,4 +1,5 @@ using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Shared.Screens.GradientEditor; namespace Artemis.UI.Shared.Ninject.Factories diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs index 67227cfd1..f43242776 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Shared.Utilities; using Stylet; diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs index f80008f53..9f25b57c9 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Shared.Services.Dialog; using Artemis.UI.Shared.Utilities; using Stylet; diff --git a/src/Artemis.UI.Shared/Services/GradientPickerService.cs b/src/Artemis.UI.Shared/Services/GradientPickerService.cs index 012aedad5..f246c5dd4 100644 --- a/src/Artemis.UI.Shared/Services/GradientPickerService.cs +++ b/src/Artemis.UI.Shared/Services/GradientPickerService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Shared.Screens.GradientEditor; using Artemis.UI.Shared.Services.Interfaces; diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs index d01d54495..6ce388682 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs @@ -1,4 +1,5 @@ using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; namespace Artemis.UI.Shared.Services.Interfaces { diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 77b25d542..c24342daa 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; +using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Ninject; using Artemis.Core.Services.Interfaces; using Artemis.UI.Ninject; @@ -33,6 +34,8 @@ namespace Artemis.UI protected override void Launch() { + var test = new LayerCondition(); + StartupArguments = Args.ToList(); var logger = Kernel.Get(); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 34986e68d..d803e5918 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,5 +1,6 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Abstract; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Screens.Module; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs index c03a8ac86..beeb353c7 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs index 30876cdbe..79d4e6483 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Abstract; using Artemis.Core.Utilities; using Artemis.UI.Services.Interfaces; using Stylet; diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index dcfe1ee33..ac9956297 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; using SkiaSharp; diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index 8bd35f190..81627c599 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Services.Interfaces; diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs index c80f4d24d..3ecf70361 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs @@ -1,15 +1,35 @@ -using Artemis.Core.Attributes; -using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.Abstract.DataModels; +using Artemis.Core.Plugins.Abstract.DataModels.Attributes; +using SkiaSharp; namespace Artemis.Plugins.Modules.General { - public class GeneralDataModel : ModuleDataModel + public class GeneralDataModel : DataModel { public GeneralDataModel(Module module) : base(module) { } - [DataModelProperty(DisplayName = "Unique boolean")] - public bool PropertyUniqueToThisDm { get; set; } + [DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")] + public string TestString { get; set; } + + [DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")] + public bool TestBoolean { get; set; } + + [DataModelProperty(Name = "Player info", Description = "[TEST] Contains information about the player")] + public PlayerInfo PlayerInfo { get; set; } + } + + public class PlayerInfo + { + [DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")] + public string TestString { get; set; } + + [DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")] + public bool TestBoolean { get; set; } + + [DataModelProperty()] + public SKRect SkRect { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 5f4c10688..4c9871d64 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -17,10 +17,11 @@ namespace Artemis.Plugins.Modules.General DisplayName = "General"; DisplayIcon = "AllInclusive"; ExpandsMainDataModel = true; + DataModel = new GeneralDataModel(this); var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); } - + public override void EnablePlugin() { } From 302ba10caa454c5bd207f213c14a9db89420de7a Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 28 Apr 2020 19:26:47 +0200 Subject: [PATCH 02/14] Conditions - WIP commit --- src/Artemis.Core/Artemis.Core.csproj | 3 - .../LayerProperties/Abstract/LayerProperty.cs | 80 +++++++++++++++++++ .../PropertyDescriptionAttribute.cs | 10 +++ .../LayerProperties/BaseLayerProperty.cs | 3 +- .../LayerProperties/FloatLayerProperty.cs | 13 +++ .../LayerProperties/IntLayerProperty.cs | 14 ++++ .../LayerProperties/LayerPropertyKeyFrame.cs | 33 ++++++++ .../LayerProperties/SKColorLayerProperty.cs | 29 +++++++ .../LayerProperties/SKPointLayerProperty.cs | 15 ++++ .../LayerProperties/SKSizeLayerProperty.cs | 15 ++++ 10 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 8869fac59..3a632ed07 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -56,7 +56,4 @@ ..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Groups.dll - - - \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs new file mode 100644 index 000000000..201aaefb4 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Utilities; + +namespace Artemis.Core.Models.Profile.LayerProperties.Abstract +{ + public abstract class LayerProperty + { + private List> _keyframes; + + protected LayerProperty() + { + _keyframes = new List>(); + } + + public T BaseValue { get; set; } + public T CurrentValue { get; set; } + public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); + + /// + /// The total progress on the timeline + /// + public TimeSpan TimelineProgress { get; private set; } + + /// + /// The current keyframe in the timeline + /// + public LayerPropertyKeyFrame CurrentKeyframe { get; protected set; } + + /// + /// The next keyframe in the timeline + /// + public LayerPropertyKeyFrame NextKeyframe { get; protected set; } + + public void Update(double deltaTime) + { + float keyframeProgress; + float keyframeProgressEased; + + TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); + // The current keyframe is the last keyframe before the current time + CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); + // The next keyframe is the first keyframe that's after the current time + NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); + + if (CurrentKeyframe == null) + { + keyframeProgress = 0; + keyframeProgressEased = 0; + } + else if (NextKeyframe == null) + { + keyframeProgress = 1; + keyframeProgressEased = 1; + } + else + { + var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; + keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); + } + + UpdateCurrentValue(keyframeProgress, keyframeProgressEased); + } + + protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased); + + public void OverrideProgress(TimeSpan progress) + { + TimelineProgress = TimeSpan.Zero; + Update(progress.TotalSeconds); + } + + internal void SortKeyframes() + { + _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs new file mode 100644 index 000000000..d6eb245d4 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Artemis.Core.Models.Profile.LayerProperties.Attributes +{ + public class PropertyDescriptionAttribute : Attribute + { + } +} diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 184415ff9..78edeed6c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.KeyframeEngines; +using Artemis.Core.Models.Profile.LayerProperties.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Utilities; using Artemis.Storage.Entities.Profile; @@ -28,7 +29,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties CanUseKeyframes = true; InputStepSize = 1; - // This can only be null if accessed internally + // This can only be null if accessed internally, all public ways of creating enforce a plugin info if (PluginInfo == null) PluginInfo = Constants.CorePluginInfo; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs new file mode 100644 index 000000000..5fa765240 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs @@ -0,0 +1,13 @@ +using Artemis.Core.Models.Profile.LayerProperties.Abstract; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class FloatLayerProperty : LayerProperty + { + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + var diff = NextKeyframe.Value - CurrentKeyframe.Value; + CurrentValue = CurrentKeyframe.Value + diff * keyframeProgressEased; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs new file mode 100644 index 000000000..7f315a1e3 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs @@ -0,0 +1,14 @@ +using System; +using Artemis.Core.Models.Profile.LayerProperties.Abstract; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class IntLayerProperty : LayerProperty + { + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + var diff = NextKeyframe.Value - CurrentKeyframe.Value; + CurrentValue = (int) Math.Round(CurrentKeyframe.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs new file mode 100644 index 000000000..cb94d88e6 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -0,0 +1,33 @@ +using System; +using Artemis.Core.Utilities; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class LayerPropertyKeyFrame + { + private TimeSpan _position; + + public LayerPropertyKeyFrame(LayerProperty layerProperty, T value, TimeSpan position, Easings.Functions easingFunction) + { + _position = position; + Value = value; + LayerProperty = layerProperty; + EasingFunction = easingFunction; + } + + public LayerProperty LayerProperty { get; set; } + public T Value { get; set; } + + public TimeSpan Position + { + get => _position; + set + { + _position = value; + LayerProperty.SortKeyframes(); + } + } + + public Easings.Functions EasingFunction { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs new file mode 100644 index 000000000..78635385a --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs @@ -0,0 +1,29 @@ +using System; +using Artemis.Core.Models.Profile.LayerProperties.Abstract; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class SKColorLayerProperty : LayerProperty + { + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red; + var greenDiff = NextKeyframe.Value.Green - CurrentKeyframe.Value.Green; + var blueDiff = NextKeyframe.Value.Blue - CurrentKeyframe.Value.Blue; + var alphaDiff = NextKeyframe.Value.Alpha - CurrentKeyframe.Value.Alpha; + + CurrentValue = new SKColor( + ClampToByte(CurrentKeyframe.Value.Red + redDiff * keyframeProgressEased), + ClampToByte(CurrentKeyframe.Value.Green + greenDiff * keyframeProgressEased), + ClampToByte(CurrentKeyframe.Value.Blue + blueDiff * keyframeProgressEased), + ClampToByte(CurrentKeyframe.Value.Alpha + alphaDiff * keyframeProgressEased) + ); + } + + private static byte ClampToByte(float value) + { + return (byte) Math.Max(0, Math.Min(255, value)); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs new file mode 100644 index 000000000..a11719034 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs @@ -0,0 +1,15 @@ +using Artemis.Core.Models.Profile.LayerProperties.Abstract; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class SKPointLayerProperty : LayerProperty + { + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + var xDiff = NextKeyframe.Value.X - CurrentKeyframe.Value.X; + var yDiff = NextKeyframe.Value.Y - CurrentKeyframe.Value.Y; + CurrentValue = new SKPoint(CurrentKeyframe.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe.Value.Y + yDiff * keyframeProgressEased); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs new file mode 100644 index 000000000..46fceec37 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs @@ -0,0 +1,15 @@ +using Artemis.Core.Models.Profile.LayerProperties.Abstract; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public class SKSizeLayerProperty : LayerProperty + { + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + var widthDiff = NextKeyframe.Value.Width - CurrentKeyframe.Value.Width; + var heightDiff = NextKeyframe.Value.Height - CurrentKeyframe.Value.Height; + CurrentValue = new SKSize(CurrentKeyframe.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe.Value.Height + heightDiff * keyframeProgressEased); + } + } +} \ No newline at end of file From d9bba8cb548fc2b4582c4ac0c493e08cb9427e88 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 28 Apr 2020 19:40:03 +0200 Subject: [PATCH 03/14] Conditions - Refactor layer properties WIP (needed for consistency..) --- src/Artemis.Core/Artemis.Core.csproj | 1 + .../Events/LayerPropertyEventArgs.cs | 4 +- .../Profile/KeyframeEngines/KeyframeEngine.cs | 4 +- src/Artemis.Core/Models/Profile/Layer.cs | 46 ++- .../Models/Profile/LayerGeneralProperties.cs | 35 ++ .../LayerProperties/Abstract/LayerProperty.cs | 80 ---- .../PropertyDescriptionAttribute.cs | 38 +- .../PropertyGroupDescriptionAttribute.cs | 22 + .../Profile/LayerProperties/BaseKeyframe.cs | 33 -- .../LayerProperties/BaseLayerProperty.cs | 375 ------------------ .../LayerProperties/GenericLayerProperty.cs | 223 +++++++++++ .../Profile/LayerProperties/Keyframe.cs | 18 - .../Profile/LayerProperties/LayerProperty.cs | 76 ---- .../LayerPropertyCollection.cs | 225 ----------- .../LayerProperties/LayerPropertyKeyFrame.cs | 7 +- .../Types/ColorGradientLayerProperty.cs | 19 + .../Types/EnumLayerProperty.cs | 18 + .../{ => Types}/FloatLayerProperty.cs | 11 +- .../{ => Types}/IntLayerProperty.cs | 10 +- .../Types/LayerBrushReferenceLayerProperty.cs | 20 + .../{ => Types}/SKColorLayerProperty.cs | 10 +- .../{ => Types}/SKPointLayerProperty.cs | 12 +- .../{ => Types}/SKSizeLayerProperty.cs | 12 +- .../Models/Profile/LayerPropertyGroup.cs | 62 +++ .../Profile/LayerTransformProperties.cs | 34 ++ .../Plugins/LayerBrush/ILayerBrush.cs | 39 ++ .../Plugins/LayerBrush/LayerBrush.cs | 116 ++---- .../Plugins/LayerBrush/LayerBrushProvider.cs | 2 +- .../Services/Interfaces/ILayerService.cs | 19 +- src/Artemis.Core/Services/LayerService.cs | 47 +-- .../Ninject/Factories/IVMFactory.cs | 3 +- .../LayerPropertiesViewModel.cs | 2 +- .../LayerProperties/LayerPropertyViewModel.cs | 4 +- .../PropertyTrackKeyframeViewModel.cs | 1 - .../ColorBrush.cs | 65 +-- .../ColorBrushProperties.cs | 59 +++ .../NoiseBrush.cs | 89 ++--- .../NoiseBrushProperties.cs | 67 ++++ 38 files changed, 809 insertions(+), 1099 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Keyframe.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs delete mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyCollection.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs rename src/Artemis.Core/Models/Profile/LayerProperties/{ => Types}/FloatLayerProperty.cs (57%) rename src/Artemis.Core/Models/Profile/LayerProperties/{ => Types}/IntLayerProperty.cs (63%) create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs rename src/Artemis.Core/Models/Profile/LayerProperties/{ => Types}/SKColorLayerProperty.cs (83%) rename src/Artemis.Core/Models/Profile/LayerProperties/{ => Types}/SKPointLayerProperty.cs (65%) rename src/Artemis.Core/Models/Profile/LayerProperties/{ => Types}/SKSizeLayerProperty.cs (67%) create mode 100644 src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerTransformProperties.cs create mode 100644 src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 3a632ed07..fca9504ac 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs index 9f16dc690..9ec63f95f 100644 --- a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs +++ b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs @@ -5,11 +5,11 @@ namespace Artemis.Core.Events { public class LayerPropertyEventArgs : EventArgs { - public LayerPropertyEventArgs(BaseLayerProperty layerProperty) + public LayerPropertyEventArgs(LayerProperty layerProperty) { LayerProperty = layerProperty; } - public BaseLayerProperty LayerProperty { get; } + public LayerProperty LayerProperty { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs index 60396b945..e77db585e 100644 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs +++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs @@ -17,7 +17,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines /// /// The layer property this keyframe engine applies to. /// - public BaseLayerProperty LayerProperty { get; private set; } + public LayerProperty LayerProperty { get; private set; } /// /// The total progress @@ -55,7 +55,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines /// Associates the keyframe engine with the provided layer property. /// /// - public void Initialize(BaseLayerProperty layerProperty) + public void Initialize(LayerProperty layerProperty) { if (Initialized) throw new ArtemisCoreException("Cannot initialize the same keyframe engine twice"); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 1de5eb131..f4b8cf4f8 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -3,22 +3,28 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Extensions; -using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.LayerBrush; +using Artemis.Core.Services; +using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile; using SkiaSharp; namespace Artemis.Core.Models.Profile { + /// + /// Represents a layer on a profile. To create new layers use the by injecting + /// into your code + /// public sealed class Layer : ProfileElement { private LayerShape _layerShape; private List _leds; private SKPath _path; - public Layer(Profile profile, ProfileElement parent, string name) + internal Layer(Profile profile, ProfileElement parent, string name) { LayerEntity = new LayerEntity(); EntityId = Guid.NewGuid(); @@ -26,12 +32,10 @@ namespace Artemis.Core.Models.Profile Profile = profile; Parent = parent; Name = name; - Properties = new LayerPropertyCollection(this); + General = new LayerGeneralProperties(); + Transform = new LayerTransformProperties(); _leds = new List(); - - ApplyShapeType(); - Properties.ShapeType.ValueChanged += (sender, args) => ApplyShapeType(); } internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) @@ -43,12 +47,10 @@ namespace Artemis.Core.Models.Profile Parent = parent; Name = layerEntity.Name; Order = layerEntity.Order; - Properties = new LayerPropertyCollection(this); + General = new LayerGeneralProperties(); + Transform = new LayerTransformProperties(); _leds = new List(); - - ApplyShapeType(); - Properties.ShapeType.ValueChanged += (sender, args) => ApplyShapeType(); } internal LayerEntity LayerEntity { get; set; } @@ -93,15 +95,16 @@ namespace Artemis.Core.Models.Profile } } - /// - /// The properties of this layer - /// - public LayerPropertyCollection Properties { get; set; } + [PropertyGroupDescription(Name = "General", Description = "A collection of general properties", ExpandByDefault = true)] + public LayerGeneralProperties General { get; set; } + + [PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties", ExpandByDefault = true)] + public LayerTransformProperties Transform { get; set; } /// /// The brush that will fill the . /// - public LayerBrush LayerBrush { get; internal set; } + public ILayerBrush LayerBrush { get; internal set; } public override string ToString() { @@ -158,6 +161,19 @@ namespace Artemis.Core.Models.Profile #endregion + #region Properties + + internal void InitializeProperties(ILayerService layerService) + { + PropertiesInitialized = true; + + ApplyShapeType(); + } + + public bool PropertiesInitialized { get; private set; } + + #endregion + #region Rendering /// diff --git a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs new file mode 100644 index 000000000..f12489f22 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs @@ -0,0 +1,35 @@ +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile +{ + public class LayerGeneralProperties : LayerPropertyGroup + { + [PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")] + public EnumLayerProperty ShapeType { get; set; } + + [PropertyDescription(Name = "Fill type", Description = "How to make the shape adjust to scale changes")] + public EnumLayerProperty FillType { get; set; } + + [PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")] + public EnumLayerProperty BlendMode { get; set; } + + [PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")] + public LayerBrushReferenceLayerProperty BrushReference { get; set; } + + protected override void OnPropertiesInitialized() + { + // Populate defaults + if (!ShapeType.IsLoadedFromStorage) + ShapeType.BaseValue = LayerShapeType.Rectangle; + if (!FillType.IsLoadedFromStorage) + FillType.BaseValue = LayerFillType.Stretch; + if (!BlendMode.IsLoadedFromStorage) + BlendMode.BaseValue = SKBlendMode.SrcOver; + + // TODO: SpoinkyNL 28-4-2020: Select preferred default brush type with a fallback to the first available + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs deleted file mode 100644 index 201aaefb4..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.Utilities; - -namespace Artemis.Core.Models.Profile.LayerProperties.Abstract -{ - public abstract class LayerProperty - { - private List> _keyframes; - - protected LayerProperty() - { - _keyframes = new List>(); - } - - public T BaseValue { get; set; } - public T CurrentValue { get; set; } - public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); - - /// - /// The total progress on the timeline - /// - public TimeSpan TimelineProgress { get; private set; } - - /// - /// The current keyframe in the timeline - /// - public LayerPropertyKeyFrame CurrentKeyframe { get; protected set; } - - /// - /// The next keyframe in the timeline - /// - public LayerPropertyKeyFrame NextKeyframe { get; protected set; } - - public void Update(double deltaTime) - { - float keyframeProgress; - float keyframeProgressEased; - - TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); - // The current keyframe is the last keyframe before the current time - CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); - // The next keyframe is the first keyframe that's after the current time - NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); - - if (CurrentKeyframe == null) - { - keyframeProgress = 0; - keyframeProgressEased = 0; - } - else if (NextKeyframe == null) - { - keyframeProgress = 1; - keyframeProgressEased = 1; - } - else - { - var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); - keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); - } - - UpdateCurrentValue(keyframeProgress, keyframeProgressEased); - } - - protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased); - - public void OverrideProgress(TimeSpan progress) - { - TimelineProgress = TimeSpan.Zero; - Update(progress.TotalSeconds); - } - - internal void SortKeyframes() - { - _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs index d6eb245d4..ad00c49a9 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs @@ -1,10 +1,42 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Artemis.Core.Models.Profile.LayerProperties.Attributes { public class PropertyDescriptionAttribute : Attribute { + /// + /// The user-friendly name for this property, shown in the UI + /// + public string Name { get; set; } + + /// + /// The user-friendly description for this property, shown in the UI + /// + public string Description { get; set; } + + /// + /// Input prefix to show before input elements in the UI + /// + public string InputPrefix { get; set; } + + /// + /// Input affix to show behind input elements in the UI + /// + public string InputAffix { get; set; } + + /// + /// The input drag step size, used in the UI + /// + public float InputStepSize { get; set; } + + /// + /// Minimum input value, only enforced in the UI + /// + public object MinInputValue { get; set; } + + /// + /// Maximum input value, only enforced in the UI + /// + public object MaxInputValue { get; set; } } -} +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs new file mode 100644 index 000000000..ba725a66a --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Artemis.Core.Models.Profile.LayerProperties.Attributes +{ + public class PropertyGroupDescriptionAttribute : Attribute + { + /// + /// The user-friendly name for this property, shown in the UI. + /// + public string Name { get; set; } + + /// + /// The user-friendly description for this property, shown in the UI. + /// + public string Description { get; set; } + + /// + /// Whether to expand this property by default, this is useful for important parent properties. + /// + public bool ExpandByDefault { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs deleted file mode 100644 index 2f68a2a71..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Artemis.Core.Utilities; - -namespace Artemis.Core.Models.Profile.LayerProperties -{ - public class BaseKeyframe - { - private TimeSpan _position; - - protected BaseKeyframe(Layer layer, BaseLayerProperty property) - { - Layer = layer; - BaseProperty = property; - } - - public Layer Layer { get; set; } - - public TimeSpan Position - { - get => _position; - set - { - if (value == _position) return; - _position = value; - BaseProperty.SortKeyframes(); - } - } - - protected BaseLayerProperty BaseProperty { get; } - public object BaseValue { get; internal set; } - public Easings.Functions EasingFunction { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs deleted file mode 100644 index 78edeed6c..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.Exceptions; -using Artemis.Core.Models.Profile.KeyframeEngines; -using Artemis.Core.Models.Profile.LayerProperties.Abstract; -using Artemis.Core.Plugins.Models; -using Artemis.Core.Utilities; -using Artemis.Storage.Entities.Profile; -using Newtonsoft.Json; -using Stylet; - -namespace Artemis.Core.Models.Profile.LayerProperties -{ - public abstract class BaseLayerProperty : PropertyChangedBase - { - private object _baseValue; - private bool _isHidden; - - protected BaseLayerProperty(Layer layer, PluginInfo pluginInfo, BaseLayerProperty parent, string id, string name, string description, Type type) - { - Layer = layer; - PluginInfo = pluginInfo; - Parent = parent; - Id = id; - Name = name; - Description = description; - Type = type; - CanUseKeyframes = true; - InputStepSize = 1; - - // This can only be null if accessed internally, all public ways of creating enforce a plugin info - if (PluginInfo == null) - PluginInfo = Constants.CorePluginInfo; - - Children = new List(); - BaseKeyframes = new List(); - - - parent?.Children.Add(this); - } - - /// - /// Gets the layer this property applies to - /// - public Layer Layer { get; } - - /// - /// Info of the plugin associated with this property - /// - public PluginInfo PluginInfo { get; } - - /// - /// Gets the parent property of this property. - /// - public BaseLayerProperty Parent { get; } - - /// - /// Gets or sets the child properties of this property. - /// If the layer has children it cannot contain a value or keyframes. - /// - public List Children { get; set; } - - /// - /// Gets or sets a unique identifier for this property, a layer may not contain two properties with the same ID. - /// - public string Id { get; set; } - - /// - /// Gets or sets the user-friendly name for this property, shown in the UI. - /// - public string Name { get; set; } - - /// - /// Gets or sets the user-friendly description for this property, shown in the UI. - /// - public string Description { get; set; } - - /// - /// Gets or sets whether to expand this property by default, this is useful for important parent properties. - /// - public bool ExpandByDefault { get; set; } - - /// - /// Gets or sets the an optional input prefix to show before input elements in the UI. - /// - public string InputPrefix { get; set; } - - /// - /// Gets or sets an optional input affix to show behind input elements in the UI. - /// - public string InputAffix { get; set; } - - /// - /// Gets or sets an optional maximum input value, only enforced in the UI. - /// - public object MaxInputValue { get; set; } - - /// - /// Gets or sets the input drag step size, used in the UI. - /// - public float InputStepSize { get; set; } - - /// - /// Gets or sets an optional minimum input value, only enforced in the UI. - /// - public object MinInputValue { get; set; } - - /// - /// Gets or sets whether this property can use keyframes, True by default. - /// - public bool CanUseKeyframes { get; set; } - - /// - /// Gets or sets whether this property is using keyframes. - /// - public bool IsUsingKeyframes { get; set; } - - /// - /// Gets the type of value this layer property contains. - /// - public Type Type { get; protected set; } - - /// - /// Gets or sets whether this property is hidden in the UI. - /// - public bool IsHidden - { - get => _isHidden; - set - { - _isHidden = value; - OnVisibilityChanged(); - } - } - - /// - /// Gets a list of keyframes defining different values of the property in time, this list contains the untyped - /// . - /// - public IReadOnlyCollection UntypedKeyframes => BaseKeyframes.AsReadOnly(); - - /// - /// Gets the keyframe engine instance of this property - /// - public KeyframeEngine KeyframeEngine { get; internal set; } - - protected List BaseKeyframes { get; set; } - - public object BaseValue - { - get => _baseValue; - internal set - { - if (value != null && value.GetType() != Type) - throw new ArtemisCoreException($"Cannot set value of type {value.GetType()} on property {this}, expected type is {Type}."); - if (!Equals(_baseValue, value)) - { - _baseValue = value; - OnValueChanged(); - } - } - } - - /// - /// Creates a new keyframe for this base property without knowing the type - /// - /// - public BaseKeyframe CreateNewKeyframe(TimeSpan position, object value) - { - // Create a strongly typed keyframe or else it cannot be cast later on - var keyframeType = typeof(Keyframe<>); - var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this); - keyframe.Position = position; - keyframe.BaseValue = value; - BaseKeyframes.Add(keyframe); - SortKeyframes(); - - return keyframe; - } - - /// - /// Removes all keyframes from the property and sets the base value to the current value. - /// - public void ClearKeyframes() - { - if (KeyframeEngine != null) - BaseValue = KeyframeEngine.GetCurrentValue(); - - BaseKeyframes.Clear(); - } - - /// - /// Gets the current value using the regular value or if present, keyframes - /// - public object GetCurrentValue() - { - if (KeyframeEngine == null || !UntypedKeyframes.Any()) - return BaseValue; - - return KeyframeEngine.GetCurrentValue(); - } - - /// - /// Gets the current value using the regular value or keyframes. - /// - /// The value to set. - /// - /// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new - /// or existing keyframe. - /// - public void SetCurrentValue(object value, TimeSpan? time) - { - if (value != null && value.GetType() != Type) - throw new ArtemisCoreException($"Cannot set value of type {value.GetType()} on property {this}, expected type is {Type}."); - - if (time == null || !CanUseKeyframes || !IsUsingKeyframes) - BaseValue = value; - else - { - // If on a keyframe, update the keyframe - var currentKeyframe = UntypedKeyframes.FirstOrDefault(k => k.Position == time.Value); - // Create a new keyframe if none found - if (currentKeyframe == null) - currentKeyframe = CreateNewKeyframe(time.Value, value); - - currentKeyframe.BaseValue = value; - } - - OnValueChanged(); - } - - /// - /// Adds a keyframe to the property. - /// - /// The keyframe to remove - public void AddKeyframe(BaseKeyframe keyframe) - { - BaseKeyframes.Add(keyframe); - SortKeyframes(); - } - - /// - /// Removes a keyframe from the property. - /// - /// The keyframe to remove - public void RemoveKeyframe(BaseKeyframe keyframe) - { - BaseKeyframes.Remove(keyframe); - SortKeyframes(); - } - - /// - /// Returns the flattened index of this property on the layer - /// - /// - public int GetFlattenedIndex() - { - if (Parent == null) - return Layer.Properties.ToList().IndexOf(this); - - // Create a flattened list of all properties in their order as defined by the parent/child hierarchy - var properties = new List(); - // Iterate root properties (those with children) - foreach (var baseLayerProperty in Layer.Properties) - { - // First add self, then add all children - if (baseLayerProperty.Children.Any()) - { - properties.Add(baseLayerProperty); - properties.AddRange(baseLayerProperty.GetAllChildren()); - } - } - - return properties.IndexOf(this); - } - - public override string ToString() - { - return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}"; - } - - internal void ApplyToEntity() - { - var propertyEntity = Layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == Id); - if (propertyEntity == null) - { - propertyEntity = new PropertyEntity {Id = Id}; - Layer.LayerEntity.PropertyEntities.Add(propertyEntity); - } - - propertyEntity.ValueType = Type.Name; - propertyEntity.Value = JsonConvert.SerializeObject(BaseValue); - propertyEntity.IsUsingKeyframes = IsUsingKeyframes; - - propertyEntity.KeyframeEntities.Clear(); - foreach (var baseKeyframe in BaseKeyframes) - { - propertyEntity.KeyframeEntities.Add(new KeyframeEntity - { - Position = baseKeyframe.Position, - Value = JsonConvert.SerializeObject(baseKeyframe.BaseValue), - EasingFunction = (int) baseKeyframe.EasingFunction - }); - } - } - - internal void ApplyToProperty(PropertyEntity propertyEntity) - { - BaseValue = DeserializePropertyValue(propertyEntity.Value); - IsUsingKeyframes = propertyEntity.IsUsingKeyframes; - - BaseKeyframes.Clear(); - foreach (var keyframeEntity in propertyEntity.KeyframeEntities.OrderBy(e => e.Position)) - { - // Create a strongly typed keyframe or else it cannot be cast later on - var keyframeType = typeof(Keyframe<>); - var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this); - keyframe.Position = keyframeEntity.Position; - keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value); - keyframe.EasingFunction = (Easings.Functions) keyframeEntity.EasingFunction; - - BaseKeyframes.Add(keyframe); - } - } - - internal void SortKeyframes() - { - BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList(); - } - - internal IEnumerable GetAllChildren() - { - var children = new List(); - children.AddRange(Children); - foreach (var layerPropertyViewModel in Children) - children.AddRange(layerPropertyViewModel.GetAllChildren()); - - return children; - } - - private object DeserializePropertyValue(string value) - { - if (value == "null") - return Type.IsValueType ? Activator.CreateInstance(Type) : null; - return JsonConvert.DeserializeObject(value, Type); - } - - #region Events - - /// - /// Occurs when this property's value was changed outside regular keyframe updates - /// - public event EventHandler ValueChanged; - - /// - /// Occurs when this property or any of it's ancestors visibility is changed - /// - public event EventHandler VisibilityChanged; - - protected virtual void OnValueChanged() - { - ValueChanged?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnVisibilityChanged() - { - VisibilityChanged?.Invoke(this, EventArgs.Empty); - foreach (var baseLayerProperty in Children) - baseLayerProperty.OnVisibilityChanged(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs new file mode 100644 index 000000000..f07b9c909 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Utilities; +using Artemis.Storage.Entities.Profile; +using Newtonsoft.Json; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + /// + /// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI. + /// + /// Note: You cannot initialize layer properties yourself. If properly placed, the Artemis core will initialize + /// these for you. + /// + /// + /// The type of property encapsulated in this layer property + public abstract class GenericLayerProperty : LayerProperty + { + private T _currentValue; + private List> _keyframes; + private T _baseValue; + + protected GenericLayerProperty() + { + _keyframes = new List>(); + } + + /// + /// Gets or sets the base value of this layer property without any keyframes applied + /// + public T BaseValue + { + get => _baseValue; + set + { + if (_baseValue != null && !_baseValue.Equals(value) || _baseValue == null && value != null) + { + _baseValue = value; + OnBaseValueChanged(); + } + } + } + + /// + /// Gets the current value of this property as it is affected by it's keyframes, updated once every frame + /// + public T CurrentValue + { + get => !KeyframesEnabled || !KeyframesSupported ? BaseValue : _currentValue; + internal set => _currentValue = value; + } + + /// + /// Gets whether keyframes are supported on this property + /// + public bool KeyframesSupported { get; internal set; } + + /// + /// Gets or sets whether keyframes are enabled on this property, has no effect if is + /// False + /// + public bool KeyframesEnabled { get; set; } + + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden { get; set; } + + /// + /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied + /// + public bool IsLoadedFromStorage { get; internal set; } + + /// + /// Gets a read-only list of all the keyframes on this layer property + /// + public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); + + /// + /// Gets the total progress on the timeline + /// + public TimeSpan TimelineProgress { get; private set; } + + /// + /// Gets the current keyframe in the timeline according to the current progress + /// + public LayerPropertyKeyframe CurrentKeyframe { get; protected set; } + + /// + /// Gets the next keyframe in the timeline according to the current progress + /// + public LayerPropertyKeyframe NextKeyframe { get; protected set; } + + /// + /// Updates the property, moving the timeline forwards by the provided + /// + /// The amount of time to move the timeline forwards + public void Update(double deltaTime) + { + TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); + if (!KeyframesSupported || !KeyframesEnabled) + return; + + // The current keyframe is the last keyframe before the current time + CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); + // The next keyframe is the first keyframe that's after the current time + NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); + + // No need to update the current value if either of the keyframes are null + if (CurrentKeyframe == null) + CurrentValue = BaseValue; + else if (NextKeyframe == null) + CurrentValue = CurrentKeyframe.Value; + // Only determine progress and current value if both keyframes are present + else + { + var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; + var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); + UpdateCurrentValue(keyframeProgress, keyframeProgressEased); + } + + OnUpdated(); + } + + /// + /// Overrides the timeline progress to match the provided + /// + /// The new progress to set the layer property timeline to. + public void OverrideProgress(TimeSpan progress) + { + TimelineProgress = TimeSpan.Zero; + Update(progress.TotalSeconds); + } + + /// + /// Adds a keyframe to the layer property + /// + /// The keyframe to add + public void AddKeyframe(LayerPropertyKeyframe keyframe) + { + keyframe.LayerProperty = this; + _keyframes.Add(keyframe); + SortKeyframes(); + OnKeyframeAdded(); + } + + /// + /// Removes a keyframe from the layer property + /// + /// The keyframe to remove + public void RemoveKeyframe(LayerPropertyKeyframe keyframe) + { + _keyframes.Remove(keyframe); + keyframe.LayerProperty = null; + SortKeyframes(); + OnKeyframeRemoved(); + } + + /// + /// Called every update (if keyframes are both supported and enabled) to determine the new + /// based on the provided progress + /// + /// The linear current keyframe progress + /// The current keyframe progress, eased with the current easing function + protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased); + + internal void SortKeyframes() + { + _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); + } + + internal override void LoadFromEntity(PropertyEntity entity) + { + BaseValue = JsonConvert.DeserializeObject(entity.Value); + CurrentValue = BaseValue; + + _keyframes.Clear(); + foreach (var keyframeEntity in entity.KeyframeEntities) + { + var value = JsonConvert.DeserializeObject(keyframeEntity.Value); + var keyframe = new LayerPropertyKeyframe(value, keyframeEntity.Position, (Easings.Functions) keyframeEntity.EasingFunction); + _keyframes.Add(keyframe); + } + SortKeyframes(); + } + + #region Events + + public event EventHandler Updated; + public event EventHandler BaseValueChanged; + public event EventHandler KeyframeAdded; + public event EventHandler KeyframeRemoved; + + protected virtual void OnUpdated() + { + Updated?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnBaseValueChanged() + { + BaseValueChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnKeyframeAdded() + { + KeyframeAdded?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnKeyframeRemoved() + { + KeyframeRemoved?.Invoke(this, EventArgs.Empty); + } + + #endregion + } + + public abstract class LayerProperty + { + internal abstract void LoadFromEntity(PropertyEntity entity); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Keyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Keyframe.cs deleted file mode 100644 index 5cd31a61e..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Keyframe.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Artemis.Core.Models.Profile.LayerProperties -{ - /// - public class Keyframe : BaseKeyframe - { - public Keyframe(Layer layer, LayerProperty propertyBase) : base(layer, propertyBase) - { - } - - public LayerProperty Property => (LayerProperty) BaseProperty; - - public T Value - { - get => BaseValue != null ? (T) BaseValue : default; - set => BaseValue = value; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs deleted file mode 100644 index 644957862..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using Artemis.Core.Plugins.LayerBrush; -using Artemis.Core.Plugins.Models; - -namespace Artemis.Core.Models.Profile.LayerProperties -{ - /// - /// Represents a property on the layer. This property is visible in the profile editor and can be key-framed (unless - /// opted out). - /// To create and register a new LayerProperty use - /// - /// - public class LayerProperty : BaseLayerProperty - { - internal LayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description) - : base(layer, null, parent, id, name, description, typeof(T)) - { - } - - internal LayerProperty(Layer layer, string id, string name, string description) - : base(layer, null, null, id, name, description, typeof(T)) - { - } - - internal LayerProperty(Layer layer, PluginInfo pluginInfo, BaseLayerProperty parent, string id, string name, string description) - : base(layer, pluginInfo, parent, id, name, description, typeof(T)) - { - } - - /// - /// Gets or sets the value of the property without any keyframes applied - /// - public T Value - { - get => BaseValue != null ? (T) BaseValue : default; - set => BaseValue = value; - } - - /// - /// Gets the value of the property with keyframes applied - /// - public T CurrentValue - { - get - { - var currentValue = GetCurrentValue(); - return currentValue == null ? default : (T) currentValue; - } - } - - /// - /// Gets a list of keyframes defining different values of the property in time, this list contains the strongly typed - /// - /// - public ReadOnlyCollection> Keyframes => BaseKeyframes.Cast>().ToList().AsReadOnly(); - - /// - /// Adds a keyframe to the property. - /// - /// The keyframe to remove - public void AddKeyframe(Keyframe keyframe) - { - base.AddKeyframe(keyframe); - } - - /// - /// Removes a keyframe from the property. - /// - /// The keyframe to remove - public void RemoveKeyframe(Keyframe keyframe) - { - base.RemoveKeyframe(keyframe); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyCollection.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyCollection.cs deleted file mode 100644 index 80b5a65e6..000000000 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyCollection.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.Events; -using Artemis.Core.Exceptions; -using Artemis.Core.Plugins.Models; -using SkiaSharp; - -namespace Artemis.Core.Models.Profile.LayerProperties -{ - /// - /// Contains all the properties of the layer and provides easy access to the default properties. - /// - public class LayerPropertyCollection : IEnumerable - { - private readonly Dictionary<(Guid, string), BaseLayerProperty> _properties; - - internal LayerPropertyCollection(Layer layer) - { - _properties = new Dictionary<(Guid, string), BaseLayerProperty>(); - - Layer = layer; - CreateDefaultProperties(); - } - - /// - /// Gets the layer these properties are applied on - /// - public Layer Layer { get; } - - /// - public IEnumerator GetEnumerator() - { - return _properties.Values.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// If found, returns the matching the provided ID - /// - /// The type of the layer property - /// The plugin this property belongs to - /// - /// - public LayerProperty GetLayerPropertyById(PluginInfo pluginInfo, string id) - { - if (!_properties.ContainsKey((pluginInfo.Guid, id))) - return null; - - var property = _properties[(pluginInfo.Guid, id)]; - if (property.Type != typeof(T)) - throw new ArtemisCoreException($"Property type mismatch. Expected property {property} to have type {typeof(T)} but it has {property.Type} instead."); - return (LayerProperty)_properties[(pluginInfo.Guid, id)]; - } - - /// - /// Removes the provided layer property from the layer. - /// - /// The type of value of the layer property - /// The property to remove from the layer - internal void RemoveLayerProperty(LayerProperty layerProperty) - { - RemoveLayerProperty((BaseLayerProperty) layerProperty); - } - - /// - /// Removes the provided layer property from the layer. - /// - /// The property to remove from the layer - internal void RemoveLayerProperty(BaseLayerProperty layerProperty) - { - if (!_properties.ContainsKey((layerProperty.PluginInfo.Guid, layerProperty.Id))) - throw new ArtemisCoreException($"Could not find a property with ID {layerProperty.Id}."); - - var property = _properties[(layerProperty.PluginInfo.Guid, layerProperty.Id)]; - property.Parent?.Children.Remove(property); - _properties.Remove((layerProperty.PluginInfo.Guid, layerProperty.Id)); - - OnLayerPropertyRemoved(new LayerPropertyEventArgs(property)); - } - - /// - /// Adds the provided layer property and its children to the layer. - /// If found, the last stored base value and keyframes will be applied to the provided property. - /// - /// The type of value of the layer property - /// The property to apply to the layer - /// True if an existing value was found and applied, otherwise false. - internal bool RegisterLayerProperty(LayerProperty layerProperty) - { - return RegisterLayerProperty((BaseLayerProperty) layerProperty); - } - - /// - /// Adds the provided layer property to the layer. - /// If found, the last stored base value and keyframes will be applied to the provided property. - /// - /// The property to apply to the layer - /// True if an existing value was found and applied, otherwise false. - internal bool RegisterLayerProperty(BaseLayerProperty layerProperty) - { - if (_properties.ContainsKey((layerProperty.PluginInfo.Guid, layerProperty.Id))) - throw new ArtemisCoreException($"Duplicate property ID detected. Layer already contains a property with ID {layerProperty.Id}."); - - var entity = Layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == layerProperty.Id && p.ValueType == layerProperty.Type.Name); - // TODO: Catch serialization exceptions and log them - if (entity != null) - layerProperty.ApplyToProperty(entity); - - _properties.Add((layerProperty.PluginInfo.Guid, layerProperty.Id), layerProperty); - OnLayerPropertyRegistered(new LayerPropertyEventArgs(layerProperty)); - return entity != null; - } - - #region Default properties - - /// - /// Gets the shape type property of the layer - /// - public LayerProperty ShapeType { get; private set; } - - /// - /// Gets the fill type property of the layer - /// - public LayerProperty FillType { get; private set; } - - /// - /// Gets the blend mode property of the layer - /// - public LayerProperty BlendMode { get; private set; } - - /// - /// Gets the brush reference property of the layer - /// - public LayerProperty BrushReference { get; private set; } - - /// - /// Gets the anchor point property of the layer - /// - public LayerProperty AnchorPoint { get; private set; } - - /// - /// Gets the position of the layer - /// - public LayerProperty Position { get; private set; } - - /// - /// Gets the size property of the layer - /// - public LayerProperty Scale { get; private set; } - - /// - /// Gets the rotation property of the layer range 0 - 360 - /// - public LayerProperty Rotation { get; private set; } - - /// - /// Gets the opacity property of the layer range 0 - 100 - /// - public LayerProperty Opacity { get; private set; } - - private void CreateDefaultProperties() - { - // Shape - var shape = new LayerProperty(Layer, "Core.Shape", "Shape", "A collection of basic shape properties"); - ShapeType = new LayerProperty(Layer, shape, "Core.ShapeType", "Shape type", "The type of shape to draw in this layer") {CanUseKeyframes = false}; - FillType = new LayerProperty(Layer, shape, "Core.FillType", "Fill type", "How to make the shape adjust to scale changes") {CanUseKeyframes = false}; - BlendMode = new LayerProperty(Layer, shape, "Core.BlendMode", "Blend mode", "How to blend this layer into the resulting image") {CanUseKeyframes = false}; - ShapeType.Value = LayerShapeType.Rectangle; - FillType.Value = LayerFillType.Stretch; - BlendMode.Value = SKBlendMode.SrcOver; - - RegisterLayerProperty(shape); - foreach (var shapeProperty in shape.Children) - RegisterLayerProperty(shapeProperty); - - // Brush - var brush = new LayerProperty(Layer, "Core.Brush", "Brush", "A collection of properties that configure the selected brush"); - BrushReference = new LayerProperty(Layer, brush, "Core.BrushReference", "Brush type", "The type of brush to use for this layer") {CanUseKeyframes = false}; - - RegisterLayerProperty(brush); - foreach (var brushProperty in brush.Children) - RegisterLayerProperty(brushProperty); - - // Transform - var transform = new LayerProperty(Layer, "Core.Transform", "Transform", "A collection of transformation properties") {ExpandByDefault = true}; - AnchorPoint = new LayerProperty(Layer, transform, "Core.AnchorPoint", "Anchor Point", "The point at which the shape is attached to its position") {InputStepSize = 0.001f}; - Position = new LayerProperty(Layer, transform, "Core.Position", "Position", "The position of the shape") {InputStepSize = 0.001f}; - Scale = new LayerProperty(Layer, transform, "Core.Scale", "Scale", "The scale of the shape") {InputAffix = "%", MinInputValue = 0f}; - Rotation = new LayerProperty(Layer, transform, "Core.Rotation", "Rotation", "The rotation of the shape in degrees") {InputAffix = "°"}; - Opacity = new LayerProperty(Layer, transform, "Core.Opacity", "Opacity", "The opacity of the shape") {InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f}; - Scale.Value = new SKSize(100, 100); - Opacity.Value = 100; - - RegisterLayerProperty(transform); - foreach (var transformProperty in transform.Children) - RegisterLayerProperty(transformProperty); - } - - #endregion - - #region Events - - public event EventHandler LayerPropertyRegistered; - public event EventHandler LayerPropertyRemoved; - - private void OnLayerPropertyRegistered(LayerPropertyEventArgs e) - { - LayerPropertyRegistered?.Invoke(this, e); - } - - private void OnLayerPropertyRemoved(LayerPropertyEventArgs e) - { - LayerPropertyRemoved?.Invoke(this, e); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index cb94d88e6..ec4694d30 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -3,19 +3,18 @@ using Artemis.Core.Utilities; namespace Artemis.Core.Models.Profile.LayerProperties { - public class LayerPropertyKeyFrame + public class LayerPropertyKeyframe { private TimeSpan _position; - public LayerPropertyKeyFrame(LayerProperty layerProperty, T value, TimeSpan position, Easings.Functions easingFunction) + public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction) { _position = position; Value = value; - LayerProperty = layerProperty; EasingFunction = easingFunction; } - public LayerProperty LayerProperty { get; set; } + public GenericLayerProperty LayerProperty { get; internal set; } public T Value { get; set; } public TimeSpan Position diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs new file mode 100644 index 000000000..c7c5af8d1 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs @@ -0,0 +1,19 @@ +using Artemis.Core.Exceptions; +using Artemis.Core.Models.Profile.Colors; + +namespace Artemis.Core.Models.Profile.LayerProperties.Types +{ + /// + public class ColorGradientLayerProperty : GenericLayerProperty + { + internal ColorGradientLayerProperty() + { + KeyframesSupported = false; + } + + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + throw new ArtemisCoreException("Color Gradients do not support keyframes."); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs new file mode 100644 index 000000000..f10e6253b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs @@ -0,0 +1,18 @@ +using Artemis.Core.Exceptions; + +namespace Artemis.Core.Models.Profile.LayerProperties.Types +{ + /// + public class EnumLayerProperty : GenericLayerProperty where T : System.Enum + { + public EnumLayerProperty() + { + KeyframesSupported = false; + } + + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + throw new ArtemisCoreException("Enum properties do not support keyframes."); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs similarity index 57% rename from src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs index 5fa765240..f7b9b9aa1 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs @@ -1,9 +1,12 @@ -using Artemis.Core.Models.Profile.LayerProperties.Abstract; - -namespace Artemis.Core.Models.Profile.LayerProperties +namespace Artemis.Core.Models.Profile.LayerProperties.Types { - public class FloatLayerProperty : LayerProperty + /// + public class FloatLayerProperty : GenericLayerProperty { + internal FloatLayerProperty() + { + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { var diff = NextKeyframe.Value - CurrentKeyframe.Value; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs similarity index 63% rename from src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs index 7f315a1e3..80cf9ff4a 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs @@ -1,10 +1,14 @@ using System; -using Artemis.Core.Models.Profile.LayerProperties.Abstract; -namespace Artemis.Core.Models.Profile.LayerProperties +namespace Artemis.Core.Models.Profile.LayerProperties.Types { - public class IntLayerProperty : LayerProperty + /// + public class IntLayerProperty : GenericLayerProperty { + internal IntLayerProperty() + { + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { var diff = NextKeyframe.Value - CurrentKeyframe.Value; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs new file mode 100644 index 000000000..654001a56 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs @@ -0,0 +1,20 @@ +using Artemis.Core.Exceptions; + +namespace Artemis.Core.Models.Profile.LayerProperties.Types +{ + /// + /// A special layer property used to configure the selected layer brush + /// + public class LayerBrushReferenceLayerProperty : GenericLayerProperty + { + internal LayerBrushReferenceLayerProperty() + { + KeyframesSupported = false; + } + + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + throw new ArtemisCoreException("Layer brush references do not support keyframes."); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs similarity index 83% rename from src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs index 78635385a..c765d849c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs @@ -1,11 +1,15 @@ using System; -using Artemis.Core.Models.Profile.LayerProperties.Abstract; using SkiaSharp; -namespace Artemis.Core.Models.Profile.LayerProperties +namespace Artemis.Core.Models.Profile.LayerProperties.Types { - public class SKColorLayerProperty : LayerProperty + /// + public class SKColorLayerProperty : GenericLayerProperty { + internal SKColorLayerProperty() + { + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs similarity index 65% rename from src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs index a11719034..d4694d283 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs @@ -1,10 +1,14 @@ -using Artemis.Core.Models.Profile.LayerProperties.Abstract; -using SkiaSharp; +using SkiaSharp; -namespace Artemis.Core.Models.Profile.LayerProperties +namespace Artemis.Core.Models.Profile.LayerProperties.Types { - public class SKPointLayerProperty : LayerProperty + /// + public class SKPointLayerProperty : GenericLayerProperty { + internal SKPointLayerProperty() + { + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { var xDiff = NextKeyframe.Value.X - CurrentKeyframe.Value.X; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs similarity index 67% rename from src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs index 46fceec37..259d4a910 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs @@ -1,10 +1,14 @@ -using Artemis.Core.Models.Profile.LayerProperties.Abstract; -using SkiaSharp; +using SkiaSharp; -namespace Artemis.Core.Models.Profile.LayerProperties +namespace Artemis.Core.Models.Profile.LayerProperties.Types { - public class SKSizeLayerProperty : LayerProperty + /// + public class SKSizeLayerProperty : GenericLayerProperty { + internal SKSizeLayerProperty() + { + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { var widthDiff = NextKeyframe.Value.Width - CurrentKeyframe.Value.Width; diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs new file mode 100644 index 000000000..7e61ec93b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Plugins.Exceptions; +using Artemis.Core.Services.Interfaces; + +namespace Artemis.Core.Models.Profile +{ + public class LayerPropertyGroup + { + public bool PropertiesInitialized { get; private set; } + + /// + /// Called when all layer properties in this property group have been initialized + /// + protected virtual void OnPropertiesInitialized() + { + } + + internal void InitializeProperties(ILayerService layerService, Layer layer, string path) + { + // Get all properties with a PropertyDescriptionAttribute + foreach (var propertyInfo in GetType().GetProperties()) + { + var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); + if (propertyDescription != null) + { + if (!typeof(GenericLayerProperty<>).IsAssignableFrom(propertyInfo.PropertyType)) + throw new ArtemisPluginException("Layer property with PropertyDescription attribute must be of type LayerProperty"); + + var instance = (LayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); + InitializeProperty(layer, path, instance); + propertyInfo.SetValue(this, instance); + } + else + { + var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); + if (propertyGroupDescription != null) + { + if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) + throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup"); + + var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); + instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); + propertyInfo.SetValue(this, instance); + } + } + } + + OnPropertiesInitialized(); + PropertiesInitialized = true; + } + + private void InitializeProperty(Layer layer, string path, LayerProperty instance) + { + var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == path); + if (entity != null) + instance.LoadFromEntity(entity); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs new file mode 100644 index 000000000..cb67e4dfb --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs @@ -0,0 +1,34 @@ +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile +{ + public class LayerTransformProperties : LayerPropertyGroup + { + [PropertyDescription(Description = "The point at which the shape is attached to its position", InputStepSize = 0.001f)] + public SKPointLayerProperty AnchorPoint { get; set; } + + [PropertyDescription(Description = "The position of the shape", InputStepSize = 0.001f)] + public SKPointLayerProperty Position { get; set; } + + [PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)] + public SKSizeLayerProperty Scale { get; set; } + + [PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°")] + public FloatLayerProperty Rotation { get; set; } + + [PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)] + public FloatLayerProperty Opacity { get; set; } + + protected override void OnPropertiesInitialized() + { + // Populate defaults + if (!Scale.IsLoadedFromStorage) + Scale.BaseValue = new SKSize(100, 100); + if (!Opacity.IsLoadedFromStorage) + Opacity.BaseValue = 100; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs new file mode 100644 index 000000000..2b688fe91 --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs @@ -0,0 +1,39 @@ +using System; +using Artemis.Core.Models.Profile; +using Artemis.Core.Services.Interfaces; +using SkiaSharp; + +namespace Artemis.Core.Plugins.LayerBrush +{ + public interface ILayerBrush : IDisposable + { + /// + /// Gets the layer this brush is applied to + /// + Layer Layer { get; } + + /// + /// Gets the descriptor of this brush + /// + LayerBrushDescriptor Descriptor { get; } + + /// + /// Called before rendering every frame, write your update logic here + /// + /// + void Update(double deltaTime); + + /// + /// The main method of rendering anything to the layer. The provided is specific to the layer + /// and matches it's width and height. + /// Called during rendering or layer preview, in the order configured on the layer + /// + /// The layer canvas + /// + /// The path to be filled, represents the shape + /// The paint to be used to fill the shape + void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); + + public void InitializeProperties(ILayerService layerService, string path); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index bd83f5f8e..7103ea0e1 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -1,16 +1,13 @@ -using System; -using System.Linq; -using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; using SkiaSharp; namespace Artemis.Core.Plugins.LayerBrush { - public abstract class LayerBrush : IDisposable + public abstract class LayerBrush : ILayerBrush where T : LayerPropertyGroup { - private ILayerService _layerService; + private T _properties; protected LayerBrush(Layer layer, LayerBrushDescriptor descriptor) { @@ -18,112 +15,63 @@ namespace Artemis.Core.Plugins.LayerBrush Descriptor = descriptor; } + /// public Layer Layer { get; } + + /// public LayerBrushDescriptor Descriptor { get; } + /// public virtual void Dispose() { } - /// - /// Called before rendering every frame, write your update logic here - /// - /// + /// public virtual void Update(double deltaTime) { } - /// - /// The main method of rendering anything to the layer. The provided is specific to the layer - /// and matches it's width and height. - /// Called during rendering or layer preview, in the order configured on the layer - /// - /// The layer canvas - /// - /// The path to be filled, represents the shape - /// The paint to be used to fill the shape + /// public virtual void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { } - /// - /// Provides an easy way to add your own properties to the layer, for more info see . - /// Note: If found, the last value and keyframes are loaded and set when calling this method. - /// - /// - /// The parent of this property, use this to create a tree-hierarchy in the editor - /// A and ID identifying your property, must be unique within your plugin - /// A name for your property, this is visible in the editor - /// A description for your property, this is visible in the editor - /// The default value of the property, if not configured by the user - /// The layer property - protected LayerProperty RegisterLayerProperty(BaseLayerProperty parent, string id, string name, string description, T defaultValue = default) - { - var property = new LayerProperty(Layer, Descriptor.LayerBrushProvider.PluginInfo, parent, id, name, description) {Value = defaultValue}; - Layer.Properties.RegisterLayerProperty(property); - // It's fine if this is null, it'll be picked up by SetLayerService later - _layerService?.InstantiateKeyframeEngine(property); - return property; - } + #region Properties /// - /// Provides an easy way to add your own properties to the layer, for more info see . - /// Note: If found, the last value and keyframes are loaded and set when calling this method. + /// Gets the properties of this brush. /// - /// - /// A and ID identifying your property, must be unique within your plugin - /// A name for your property, this is visible in the editor - /// A description for your property, this is visible in the editor - /// The default value of the property, if not configured by the user - /// The layer property - protected LayerProperty RegisterLayerProperty(string id, string name, string description, T defaultValue = default) + public T Properties { - // Check if the property already exists - var existing = Layer.Properties.FirstOrDefault(p => - p.PluginInfo.Guid == Descriptor.LayerBrushProvider.PluginInfo.Guid && - p.Id == id && - p.Name == name && - p.Description == description); - - if (existing != null) + get { - // If it exists and the types match, return the existing property - if (existing.Type == typeof(T)) - return (LayerProperty) existing; - // If it exists and the types are different, something is wrong - throw new ArtemisPluginException($"Cannot register the property {id} with different types twice."); + // I imagine a null reference here can be confusing, so lets throw an exception explaining what to do + if (_properties == null) + throw new ArtemisPluginException("Cannot access brush properties until OnPropertiesInitialized has been called"); + return _properties; } - - var property = new LayerProperty(Layer, Descriptor.LayerBrushProvider.PluginInfo, Layer.Properties.BrushReference.Parent, id, name, description) - { - Value = defaultValue - }; - - Layer.Properties.RegisterLayerProperty(property); - // It's fine if this is null, it'll be picked up by SetLayerService later - _layerService?.InstantiateKeyframeEngine(property); - return property; + internal set => _properties = value; } - + /// - /// Allows you to remove layer properties previously added by using . + /// Gets whether all properties on this brush are initialized /// - /// - /// - protected void UnRegisterLayerProperty(LayerProperty layerProperty) + public bool PropertiesInitialized { get; private set; } + + /// + /// Called when all layer properties in this brush have been initialized + /// + protected virtual void OnPropertiesInitialized() { - if (layerProperty == null) - return; - - if (Layer.Properties.Any(p => p == layerProperty)) - Layer.Properties.RemoveLayerProperty(layerProperty); } - internal void SetLayerService(ILayerService layerService) + public void InitializeProperties(ILayerService layerService, string path) { - _layerService = layerService; - foreach (var baseLayerProperty in Layer.Properties) - _layerService.InstantiateKeyframeEngine(baseLayerProperty); + Properties.InitializeProperties(layerService, Descriptor.LayerBrushProvider.PluginInfo, path); + OnPropertiesInitialized(); + PropertiesInitialized = true; } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs index 65d73d17d..2631161dd 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs @@ -20,7 +20,7 @@ namespace Artemis.Core.Plugins.LayerBrush public ReadOnlyCollection LayerBrushDescriptors => _layerBrushDescriptors.AsReadOnly(); - protected void AddLayerBrushDescriptor(string displayName, string description, string icon) where T : LayerBrush + protected void AddLayerBrushDescriptor(string displayName, string description, string icon) where T : ILayerBrush { _layerBrushDescriptors.Add(new LayerBrushDescriptor(displayName, description, icon, typeof(T), this)); } diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs index 95b098f1e..7731b655e 100644 --- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs +++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs @@ -13,22 +13,9 @@ namespace Artemis.Core.Services.Interfaces /// /// The layer to instantiate the brush for /// - LayerBrush InstantiateLayerBrush(Layer layer); + ILayerBrush InstantiateLayerBrush(Layer layer); - /// - /// Instantiates and adds a compatible to the provided . - /// If the property already has a compatible keyframe engine, nothing happens. - /// - /// The layer property to apply the keyframe engine to. - /// The resulting keyframe engine, if a compatible engine was found. - KeyframeEngine InstantiateKeyframeEngine(LayerProperty layerProperty); - - /// - /// Instantiates and adds a compatible to the provided . - /// If the property already has a compatible keyframe engine, nothing happens. - /// - /// The layer property to apply the keyframe engine to. - /// The resulting keyframe engine, if a compatible engine was found. - KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty); + void LoadPropertyBaseValue(Layer layer, string path, object layerProperty); + void LoadPropertyKeyframes(Layer layer, string path, object layerProperty); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index e1a4f03ea..5b154ba52 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -4,7 +4,9 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.KeyframeEngines; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; +using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; +using Newtonsoft.Json; using Ninject; using Ninject.Parameters; using Serilog; @@ -24,11 +26,25 @@ namespace Artemis.Core.Services _pluginService = pluginService; } - public LayerBrush InstantiateLayerBrush(Layer layer) + public Layer CreateLayer(Profile profile, ProfileElement parent, string name) + { + var layer = new Layer(profile, parent, name); + + // Layers have two hardcoded property groups, instantiate them + layer.General.InitializeProperties(this, layer, null, null); + layer.Transform.InitializeProperties(this, layer, null, null); + + // With the properties loaded, the layer brush can be instantiated + InstantiateLayerBrush(layer); + + return layer; + } + + public ILayerBrush InstantiateLayerBrush(Layer layer) { RemoveLayerBrush(layer); - var descriptorReference = layer.Properties.BrushReference.CurrentValue; + var descriptorReference = layer.General.BrushReference.CurrentValue; if (descriptorReference == null) return null; @@ -46,36 +62,13 @@ namespace Artemis.Core.Services new ConstructorArgument("layer", layer), new ConstructorArgument("descriptor", descriptor) }; - var layerBrush = (LayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments); - // Set the layer service after the brush was created to avoid constructor clutter, SetLayerService will play catch-up for us. - // If layer brush implementations need the LayerService they can inject it themselves, but don't require it by default - layerBrush.SetLayerService(this); + var layerBrush = (ILayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments); + layerBrush.InitializeProperties(this, null); layer.LayerBrush = layerBrush; return layerBrush; } - public KeyframeEngine InstantiateKeyframeEngine(LayerProperty layerProperty) - { - return InstantiateKeyframeEngine((BaseLayerProperty) layerProperty); - } - - public KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty) - { - if (layerProperty.KeyframeEngine != null && layerProperty.KeyframeEngine.CompatibleTypes.Contains(layerProperty.Type)) - return layerProperty.KeyframeEngine; - - // This creates an instance of each keyframe engine, which is pretty cheap since all the expensive stuff is done during - // Initialize() call but it's not ideal - var keyframeEngines = _kernel.Get>(); - var keyframeEngine = keyframeEngines.FirstOrDefault(k => k.CompatibleTypes.Contains(layerProperty.Type)); - if (keyframeEngine == null) - return null; - - keyframeEngine.Initialize(layerProperty); - return keyframeEngine; - } - public void RemoveLayerBrush(Layer layer) { if (layer.LayerBrush == null) diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index d803e5918..7bd7aae57 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,6 +1,5 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Models.Profile.LayerProperties.Abstract; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Screens.Module; @@ -51,7 +50,7 @@ namespace Artemis.UI.Ninject.Factories public interface ILayerPropertyVmFactory : IVmFactory { - LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent); + LayerPropertyViewModel Create(LayerProperty layerProperty, LayerPropertyViewModel parent); } public interface IPropertyTreeVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index cc4ef6b8f..5dde7e5fd 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -169,7 +169,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties PopulateProperties(e.LayerProperty.Layer); } - private LayerPropertyViewModel CreatePropertyViewModel(BaseLayerProperty layerProperty) + private LayerPropertyViewModel CreatePropertyViewModel(LayerProperty layerProperty) { LayerPropertyViewModel parent = null; // If the property has a parent, find it's VM diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 497e6f379..b3c72586d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private bool _keyframesEnabled; private bool _isExpanded; - public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) + public LayerPropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) { _kernel = kernel; _profileEditorService = profileEditorService; @@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties Parent?.Children.Add(this); } - public BaseLayerProperty LayerProperty { get; } + public LayerProperty LayerProperty { get; } public LayerPropertyViewModel Parent { get; } public List Children { get; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs index 79d4e6483..30876cdbe 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Models.Profile.LayerProperties.Abstract; using Artemis.Core.Utilities; using Artemis.UI.Services.Interfaces; using Stylet; diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index ac9956297..033143871 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -1,15 +1,11 @@ using System; -using System.ComponentModel; -using System.Linq; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.Colors; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; using SkiaSharp; namespace Artemis.Plugins.LayerBrushes.Color { - public class ColorBrush : LayerBrush + public class ColorBrush : LayerBrush { private SKColor _color; private SKPaint _paint; @@ -18,33 +14,16 @@ namespace Artemis.Plugins.LayerBrushes.Color public ColorBrush(Layer layer, LayerBrushDescriptor descriptor) : base(layer, descriptor) { - GradientTypeProperty = RegisterLayerProperty("Brush.GradientType", "Gradient type", "The type of color brush to draw", GradientType.Solid); - GradientTypeProperty.CanUseKeyframes = false; - ColorProperty = RegisterLayerProperty("Brush.Color", "Color", "The color of the brush", new SKColor(255, 0, 0)); - GradientProperty = RegisterLayerProperty("Brush.Gradient", "Gradient", "The gradient of the brush", new ColorGradient()); - GradientProperty.CanUseKeyframes = false; - if (!GradientProperty.Value.Stops.Any()) - GradientProperty.Value.MakeFabulous(); - - UpdateColorProperties(); - Layer.RenderPropertiesUpdated += (sender, args) => CreateShader(); - GradientTypeProperty.ValueChanged += (sender, args) => UpdateColorProperties(); - ColorProperty.ValueChanged += (sender, args) => CreateShader(); - GradientProperty.Value.PropertyChanged += (sender, args) => CreateShader(); } - public LayerProperty ColorProperty { get; set; } - public LayerProperty GradientProperty { get; set; } - public LayerProperty GradientTypeProperty { get; set; } - public override void Update(double deltaTime) { // Only check if a solid is being drawn, because that can be changed by keyframes - if (GradientTypeProperty.Value == GradientType.Solid && _color != ColorProperty.CurrentValue) + if (Properties.GradientType.BaseValue == GradientType.Solid && _color != Properties.Color.CurrentValue) { // If the color was changed since the last frame, recreate the shader - _color = ColorProperty.CurrentValue; + _color = Properties.Color.CurrentValue; CreateShader(); } @@ -63,36 +42,35 @@ namespace Artemis.Plugins.LayerBrushes.Color canvas.DrawPath(path, paint); } - private void UpdateColorProperties() + protected override void OnPropertiesInitialized() { - ColorProperty.IsHidden = GradientTypeProperty.Value != GradientType.Solid; - GradientProperty.IsHidden = GradientTypeProperty.Value == GradientType.Solid; - - CreateShader(); + Properties.GradientType.BaseValueChanged += (sender, args) => CreateShader(); + Properties.Color.BaseValueChanged += (sender, args) => CreateShader(); + Properties.Gradient.BaseValue.PropertyChanged += (sender, args) => CreateShader(); } private void CreateShader() { var center = new SKPoint(_shaderBounds.MidX, _shaderBounds.MidY); - var shader = GradientTypeProperty.CurrentValue switch + var shader = Properties.GradientType.CurrentValue switch { GradientType.Solid => SKShader.CreateColor(_color), GradientType.LinearGradient => SKShader.CreateLinearGradient( new SKPoint(_shaderBounds.Left, _shaderBounds.Top), new SKPoint(_shaderBounds.Right, _shaderBounds.Top), - GradientProperty.Value.GetColorsArray(), - GradientProperty.Value.GetPositionsArray(), + Properties.Gradient.BaseValue.GetColorsArray(), + Properties.Gradient.BaseValue.GetPositionsArray(), SKShaderTileMode.Repeat), GradientType.RadialGradient => SKShader.CreateRadialGradient( center, Math.Min(_shaderBounds.Width, _shaderBounds.Height), - GradientProperty.Value.GetColorsArray(), - GradientProperty.Value.GetPositionsArray(), + Properties.Gradient.BaseValue.GetColorsArray(), + Properties.Gradient.BaseValue.GetPositionsArray(), SKShaderTileMode.Repeat), GradientType.SweepGradient => SKShader.CreateSweepGradient( center, - GradientProperty.Value.GetColorsArray(), - GradientProperty.Value.GetPositionsArray(), + Properties.Gradient.BaseValue.GetColorsArray(), + Properties.Gradient.BaseValue.GetPositionsArray(), SKShaderTileMode.Clamp, 0, 360), @@ -107,19 +85,4 @@ namespace Artemis.Plugins.LayerBrushes.Color oldPaint?.Dispose(); } } - - public enum GradientType - { - [Description("Solid")] - Solid, - - [Description("Linear Gradient")] - LinearGradient, - - [Description("Radial Gradient")] - RadialGradient, - - [Description("Sweep Gradient")] - SweepGradient - } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs new file mode 100644 index 000000000..c76fd5bbe --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; +using SkiaSharp; + +namespace Artemis.Plugins.LayerBrushes.Color +{ + public class ColorBrushProperties : LayerPropertyGroup + { + [PropertyDescription(Description = "The type of color brush to draw")] + public EnumLayerProperty GradientType { get; set; } + + [PropertyDescription(Description = "The color of the brush")] + public SKColorLayerProperty Color { get; set; } + + [PropertyDescription(Description = "The gradient of the brush")] + public ColorGradientLayerProperty Gradient { get; set; } + + protected override void OnPropertiesInitialized() + { + // Populate defaults + if (!GradientType.IsLoadedFromStorage) + GradientType.BaseValue = LayerBrushes.Color.GradientType.Solid; + if (!Color.IsLoadedFromStorage) + Color.BaseValue = new SKColor(255, 0, 0); + if (!Gradient.IsLoadedFromStorage) + { + Gradient.BaseValue = new ColorGradient(); + Gradient.BaseValue.MakeFabulous(); + } + + GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged; + } + + private void GradientTypeOnBaseValueChanged(object? sender, EventArgs e) + { + Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid; + Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid; + } + } + + public enum GradientType + { + [Description("Solid")] + Solid, + + [Description("Linear Gradient")] + LinearGradient, + + [Description("Radial Gradient")] + RadialGradient, + + [Description("Sweep Gradient")] + SweepGradient + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index 81627c599..e70611f69 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -1,9 +1,6 @@ using System; using System.ComponentModel; -using System.Linq; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.Colors; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Services.Interfaces; using Artemis.Plugins.LayerBrushes.Noise.Utilities; @@ -11,18 +8,18 @@ using SkiaSharp; namespace Artemis.Plugins.LayerBrushes.Noise { - public class NoiseBrush : LayerBrush + public class NoiseBrush : LayerBrush { private static readonly Random Rand = new Random(); private readonly OpenSimplexNoise _noise; private readonly IRgbService _rgbService; private SKBitmap _bitmap; + private SKColor[] _colorMap; private float _renderScale; private float _x; private float _y; private float _z; - private SKColor[] _colorMap; public NoiseBrush(Layer layer, LayerBrushDescriptor descriptor, IRgbService rgbService) : base(layer, descriptor) { @@ -31,58 +28,15 @@ namespace Artemis.Plugins.LayerBrushes.Noise _y = Rand.Next(0, 4096); _z = Rand.Next(0, 4096); _noise = new OpenSimplexNoise(Rand.Next(0, 4096)); + DetermineRenderScale(); - - ColorTypeProperty = RegisterLayerProperty("Brush.ColorType", "Color mapping type", "The way the noise is converted to colors", ColorMappingType.Simple); - ColorTypeProperty.CanUseKeyframes = false; - ColorTypeProperty.ValueChanged += (sender, args) => UpdateColorProperties(); - ScaleProperty = RegisterLayerProperty("Brush.Scale", "Scale", "The scale of the noise.", new SKSize(100, 100)); - ScaleProperty.MinInputValue = 0f; - HardnessProperty = RegisterLayerProperty("Brush.Hardness", "Hardness", "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", 500f); - HardnessProperty.MinInputValue = 0f; - HardnessProperty.MaxInputValue = 2048f; - ScrollSpeedProperty = RegisterLayerProperty("Brush.ScrollSpeed", "Movement speed", "The speed at which the noise moves vertically and horizontally"); - ScrollSpeedProperty.MinInputValue = -64f; - ScrollSpeedProperty.MaxInputValue = 64f; - AnimationSpeedProperty = RegisterLayerProperty("Brush.AnimationSpeed", "Animation speed", "The speed at which the noise moves", 25f); - AnimationSpeedProperty.MinInputValue = 0f; - AnimationSpeedProperty.MaxInputValue = 64f; - ScaleProperty.InputAffix = "%"; - MainColorProperty = RegisterLayerProperty("Brush.MainColor", "Main color", "The main color of the noise", new SKColor(255, 0, 0)); - SecondaryColorProperty = RegisterLayerProperty("Brush.SecondaryColor", "Secondary color", "The secondary color of the noise", new SKColor(0, 0, 255)); - GradientColorProperty = RegisterLayerProperty("Brush.GradientColor", "Noise gradient map", "The gradient the noise will map it's value to", new ColorGradient()); - GradientColorProperty.CanUseKeyframes = false; - if (!GradientColorProperty.Value.Stops.Any()) - GradientColorProperty.Value.MakeFabulous(); - GradientColorProperty.Value.PropertyChanged += CreateColorMap; - - UpdateColorProperties(); - CreateColorMap(null, null); - } - - - public LayerProperty ColorTypeProperty { get; set; } - public LayerProperty MainColorProperty { get; set; } - public LayerProperty SecondaryColorProperty { get; set; } - public LayerProperty GradientColorProperty { get; set; } - - public LayerProperty ScaleProperty { get; set; } - public LayerProperty HardnessProperty { get; set; } - public LayerProperty ScrollSpeedProperty { get; set; } - public LayerProperty AnimationSpeedProperty { get; set; } - - private void UpdateColorProperties() - { - GradientColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Gradient; - MainColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Simple; - SecondaryColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Simple; } public override void Update(double deltaTime) { - _x += ScrollSpeedProperty.CurrentValue.X / 500f / (float) deltaTime; - _y += ScrollSpeedProperty.CurrentValue.Y / 500f / (float) deltaTime; - _z += AnimationSpeedProperty.CurrentValue / 500f / 0.04f * (float) deltaTime; + _x += Properties.ScrollSpeed.CurrentValue.X / 500f / (float) deltaTime; + _y += Properties.ScrollSpeed.CurrentValue.Y / 500f / (float) deltaTime; + _z += Properties.AnimationSpeed.CurrentValue / 500f / 0.04f * (float) deltaTime; // A telltale sign of someone who can't do math very well if (float.IsPositiveInfinity(_x) || float.IsNegativeInfinity(_x) || float.IsNaN(_x)) @@ -98,11 +52,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { - var mainColor = MainColorProperty?.CurrentValue; - var gradientColor = GradientColorProperty?.CurrentValue; - var scale = ScaleProperty.CurrentValue; + var mainColor = Properties.MainColor?.CurrentValue; + var gradientColor = Properties.GradientColor?.CurrentValue; + var scale = Properties.Scale.CurrentValue; var opacity = mainColor != null ? (float) Math.Round(mainColor.Value.Alpha / 255.0, 2, MidpointRounding.AwayFromZero) : 0; - var hardness = 127 + HardnessProperty.CurrentValue; + var hardness = 127 + Properties.Hardness.CurrentValue; // Scale down the render path to avoid computing a value for every pixel var width = (int) Math.Floor(path.Bounds.Width * _renderScale); @@ -122,10 +76,8 @@ namespace Artemis.Plugins.LayerBrushes.Noise var v = _noise.Evaluate(evalX, evalY, _z); var alpha = (byte) Math.Max(0, Math.Min(255, v * hardness)); - if (ColorTypeProperty.Value == ColorMappingType.Simple && mainColor != null) - { + if (Properties.ColorType.BaseValue == ColorMappingType.Simple && mainColor != null) _bitmap.SetPixel(x, y, new SKColor(mainColor.Value.Red, mainColor.Value.Green, mainColor.Value.Blue, (byte) (alpha * opacity))); - } else if (gradientColor != null && _colorMap.Length == 101) { var color = _colorMap[(int) Math.Round(alpha / 255f * 100, MidpointRounding.AwayFromZero)]; @@ -141,9 +93,9 @@ namespace Artemis.Plugins.LayerBrushes.Noise ); canvas.ClipPath(path); - if (ColorTypeProperty.Value == ColorMappingType.Simple) + if (Properties.ColorType.BaseValue == ColorMappingType.Simple) { - using var backgroundShader = SKShader.CreateColor(SecondaryColorProperty.CurrentValue); + using var backgroundShader = SKShader.CreateColor(Properties.SecondaryColor.CurrentValue); paint.Shader = backgroundShader; canvas.DrawRect(path.Bounds, paint); } @@ -153,6 +105,17 @@ namespace Artemis.Plugins.LayerBrushes.Noise canvas.DrawRect(path.Bounds, paint); } + protected override void OnPropertiesInitialized() + { + Properties.GradientColor.BaseValue.PropertyChanged += GradientColorChanged; + CreateColorMap(); + } + + private void GradientColorChanged(object sender, PropertyChangedEventArgs e) + { + CreateColorMap(); + } + private void DetermineRenderScale() { @@ -170,11 +133,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise } } - private void CreateColorMap(object sender, EventArgs e) + private void CreateColorMap() { var colorMap = new SKColor[101]; for (var i = 0; i < 101; i++) - colorMap[i] = GradientColorProperty.Value.GetColor(i / 100f); + colorMap[i] = Properties.GradientColor.BaseValue.GetColor(i / 100f); _colorMap = colorMap; } diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs new file mode 100644 index 000000000..89f5f6790 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs @@ -0,0 +1,67 @@ +using System; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.Colors; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; +using SkiaSharp; + +namespace Artemis.Plugins.LayerBrushes.Noise +{ + public class NoiseBrushProperties : LayerPropertyGroup + { + [PropertyDescription(Name = "Color mapping type", Description = "The way the noise is converted to colors")] + public EnumLayerProperty ColorType { get; set; } + + [PropertyDescription(Description = "The main color of the noise")] + public SKColorLayerProperty MainColor { get; set; } + + [PropertyDescription(Description = "The secondary color of the noise")] + public SKColorLayerProperty SecondaryColor { get; set; } + + [PropertyDescription(Name = "Noise gradient map", Description = "The gradient the noise will map it's value to")] + public ColorGradientLayerProperty GradientColor { get; set; } + + + [PropertyDescription(Description = "The scale of the noise", MinInputValue = 0f, InputAffix = "%")] + public SKSizeLayerProperty Scale { get; set; } + + [PropertyDescription(Description = "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", MinInputValue = 0f, MaxInputValue = 2048f)] + public FloatLayerProperty Hardness { get; set; } + + [PropertyDescription(Description = "The speed at which the noise moves vertically and horizontally", MinInputValue = -64f, MaxInputValue = 64f)] + public SKPointLayerProperty ScrollSpeed { get; set; } + + [PropertyDescription(Description = "The speed at which the noise moves", MinInputValue = 0f, MaxInputValue = 64f)] + public FloatLayerProperty AnimationSpeed { get; set; } + + protected override void OnPropertiesInitialized() + { + // Populate defaults + if (!MainColor.IsLoadedFromStorage) + MainColor.BaseValue = new SKColor(255, 0, 0); + if (!SecondaryColor.IsLoadedFromStorage) + SecondaryColor.BaseValue = new SKColor(0, 0, 255); + if (!GradientColor.IsLoadedFromStorage) + { + GradientColor.BaseValue = new ColorGradient(); + GradientColor.BaseValue.MakeFabulous(); + } + + if (!Scale.IsLoadedFromStorage) + Scale.BaseValue = new SKSize(100, 100); + if (!Hardness.IsLoadedFromStorage) + Hardness.BaseValue = 500f; + if (!AnimationSpeed.IsLoadedFromStorage) + AnimationSpeed.BaseValue = 25f; + + ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged; + } + + private void ColorTypeOnBaseValueChanged(object? sender, EventArgs e) + { + GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient; + MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple; + SecondaryColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple; + } + } +} \ No newline at end of file From 660324c980a47aa33f22ad541e1221b9a31f8d04 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 29 Apr 2020 19:44:42 +0200 Subject: [PATCH 04/14] Conditions - Refactor layer properties WIP --- .../Events/LayerPropertyEventArgs.cs | 4 +- .../Events/PropertyGroupUpdatingEventArgs.cs | 20 +++ .../KeyframeEngines/FloatKeyframeEngine.cs | 21 --- .../KeyframeEngines/IntKeyframeEngine.cs | 21 --- .../Profile/KeyframeEngines/KeyframeEngine.cs | 135 --------------- .../KeyframeEngines/SKColorKeyframeEngine.cs | 36 ---- .../KeyframeEngines/SKPointKeyframeEngine.cs | 23 --- .../KeyframeEngines/SKSizeKeyframeEngine.cs | 23 --- src/Artemis.Core/Models/Profile/Layer.cs | 54 +++--- .../LayerProperties/BaseLayerProperty.cs | 25 +++ ...nericLayerProperty.cs => LayerProperty.cs} | 161 +++++++++++------- .../LayerProperties/LayerPropertyKeyFrame.cs | 2 +- .../Types/ColorGradientLayerProperty.cs | 2 +- .../Types/EnumLayerProperty.cs | 2 +- .../Types/FloatLayerProperty.cs | 2 +- .../LayerProperties/Types/IntLayerProperty.cs | 2 +- .../Types/LayerBrushReferenceLayerProperty.cs | 2 +- .../Types/SKColorLayerProperty.cs | 2 +- .../Types/SKPointLayerProperty.cs | 2 +- .../Types/SKSizeLayerProperty.cs | 2 +- .../Models/Profile/LayerPropertyGroup.cs | 68 +++++++- src/Artemis.Core/Ninject/CoreModule.cs | 16 +- .../Plugins/LayerBrush/BaseLayerBrush.cs | 70 ++++++++ .../Plugins/LayerBrush/ILayerBrush.cs | 39 ----- .../Plugins/LayerBrush/LayerBrush.cs | 42 ++--- .../Plugins/LayerBrush/LayerBrushProvider.cs | 2 +- .../Services/Interfaces/ILayerService.cs | 23 ++- src/Artemis.Core/Services/LayerService.cs | 21 +-- .../Services/Storage/ProfileService.cs | 20 +-- .../Entities/Profile/PropertyEntity.cs | 5 +- .../Ninject/Factories/IVMFactory.cs | 2 +- .../LayerPropertiesViewModel.cs | 2 +- .../LayerProperties/LayerPropertyViewModel.cs | 4 +- 33 files changed, 363 insertions(+), 492 deletions(-) create mode 100644 src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/SKColorKeyframeEngine.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs delete mode 100644 src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs rename src/Artemis.Core/Models/Profile/LayerProperties/{GenericLayerProperty.cs => LayerProperty.cs} (72%) create mode 100644 src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs delete mode 100644 src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs diff --git a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs index 9ec63f95f..9f16dc690 100644 --- a/src/Artemis.Core/Events/LayerPropertyEventArgs.cs +++ b/src/Artemis.Core/Events/LayerPropertyEventArgs.cs @@ -5,11 +5,11 @@ namespace Artemis.Core.Events { public class LayerPropertyEventArgs : EventArgs { - public LayerPropertyEventArgs(LayerProperty layerProperty) + public LayerPropertyEventArgs(BaseLayerProperty layerProperty) { LayerProperty = layerProperty; } - public LayerProperty LayerProperty { get; } + public BaseLayerProperty LayerProperty { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs b/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs new file mode 100644 index 000000000..a1f4c3b6c --- /dev/null +++ b/src/Artemis.Core/Events/PropertyGroupUpdatingEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Artemis.Core.Events +{ + public class PropertyGroupUpdatingEventArgs : EventArgs + { + public PropertyGroupUpdatingEventArgs(double deltaTime) + { + DeltaTime = deltaTime; + } + + public PropertyGroupUpdatingEventArgs(TimeSpan overrideTime) + { + OverrideTime = overrideTime; + } + + public double DeltaTime { get; } + public TimeSpan OverrideTime { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs deleted file mode 100644 index 88a922d39..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile.LayerProperties; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - /// - public class FloatKeyframeEngine : KeyframeEngine - { - public sealed override List CompatibleTypes { get; } = new List {typeof(float)}; - - protected override object GetInterpolatedValue() - { - var currentKeyframe = (Keyframe) CurrentKeyframe; - var nextKeyframe = (Keyframe) NextKeyframe; - - var diff = nextKeyframe.Value - currentKeyframe.Value; - return currentKeyframe.Value + diff * KeyframeProgressEased; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs deleted file mode 100644 index 9d25f8148..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile.LayerProperties; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - /// - public class IntKeyframeEngine : KeyframeEngine - { - public sealed override List CompatibleTypes { get; } = new List {typeof(int)}; - - protected override object GetInterpolatedValue() - { - var currentKeyframe = (Keyframe) CurrentKeyframe; - var nextKeyframe = (Keyframe) NextKeyframe; - - var diff = nextKeyframe.Value - currentKeyframe.Value; - return (int) Math.Round(currentKeyframe.Value + diff * KeyframeProgressEased, MidpointRounding.AwayFromZero); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs deleted file mode 100644 index e77db585e..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.Exceptions; -using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Utilities; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - public abstract class KeyframeEngine - { - /// - /// Indicates whether has been called. - /// - public bool Initialized { get; private set; } - - /// - /// The layer property this keyframe engine applies to. - /// - public LayerProperty LayerProperty { get; private set; } - - /// - /// The total progress - /// - public TimeSpan Progress { get; private set; } - - /// - /// The progress from the current keyframe to the next. - /// Range 0.0 to 1.0. - /// - public float KeyframeProgress { get; private set; } - - /// - /// The progress from the current keyframe to the next with the current keyframes easing function applied. - /// Range 0.0 to 1.0 but can be higher than 1.0 depending on easing function. - /// - public float KeyframeProgressEased { get; set; } - - /// - /// The current keyframe - /// - public BaseKeyframe CurrentKeyframe { get; private set; } - - /// - /// The next keyframe - /// - public BaseKeyframe NextKeyframe { get; private set; } - - /// - /// The types this keyframe engine supports. - /// - public abstract List CompatibleTypes { get; } - - /// - /// Associates the keyframe engine with the provided layer property. - /// - /// - public void Initialize(LayerProperty layerProperty) - { - if (Initialized) - throw new ArtemisCoreException("Cannot initialize the same keyframe engine twice"); - if (!CompatibleTypes.Contains(layerProperty.Type)) - throw new ArtemisCoreException($"This property engine does not support the provided type {layerProperty.Type.Name}"); - - LayerProperty = layerProperty; - LayerProperty.KeyframeEngine = this; - Initialized = true; - } - - /// - /// Updates the engine's progress - /// - /// - public void Update(double deltaTime) - { - if (!Initialized) - return; - - var keyframes = LayerProperty.UntypedKeyframes.ToList(); - Progress = Progress.Add(TimeSpan.FromSeconds(deltaTime)); - // The current keyframe is the last keyframe before the current time - CurrentKeyframe = keyframes.LastOrDefault(k => k.Position <= Progress); - // The next keyframe is the first keyframe that's after the current time - NextKeyframe = keyframes.FirstOrDefault(k => k.Position > Progress); - - if (CurrentKeyframe == null) - { - KeyframeProgress = 0; - KeyframeProgressEased = 0; - } - else if (NextKeyframe == null) - { - KeyframeProgress = 1; - KeyframeProgressEased = 1; - } - else - { - var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - KeyframeProgress = (float) ((Progress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); - KeyframeProgressEased = (float) Easings.Interpolate(KeyframeProgress, CurrentKeyframe.EasingFunction); - } - - // LayerProperty determines what's next: reset, stop, continue - } - - - /// - /// Overrides the engine's progress to the provided value - /// - /// - public void OverrideProgress(TimeSpan progress) - { - Progress = TimeSpan.Zero; - Update(progress.TotalSeconds); - } - - /// - /// Gets the current value, if the progress is in between two keyframes the value will be interpolated - /// - /// - public object GetCurrentValue() - { - if (CurrentKeyframe == null && LayerProperty.UntypedKeyframes.Any()) - return LayerProperty.UntypedKeyframes.First().BaseValue; - if (CurrentKeyframe == null) - return LayerProperty.BaseValue; - if (NextKeyframe == null) - return CurrentKeyframe.BaseValue; - - return GetInterpolatedValue(); - } - - protected abstract object GetInterpolatedValue(); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKColorKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKColorKeyframeEngine.cs deleted file mode 100644 index e0f0246b2..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKColorKeyframeEngine.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile.LayerProperties; -using SkiaSharp; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - /// - public class SKColorKeyframeEngine : KeyframeEngine - { - public sealed override List CompatibleTypes { get; } = new List {typeof(SKColor)}; - - protected override object GetInterpolatedValue() - { - var currentKeyframe = (Keyframe) CurrentKeyframe; - var nextKeyframe = (Keyframe) NextKeyframe; - - var redDiff = nextKeyframe.Value.Red - currentKeyframe.Value.Red; - var greenDiff = nextKeyframe.Value.Green - currentKeyframe.Value.Green; - var blueDiff = nextKeyframe.Value.Blue - currentKeyframe.Value.Blue; - var alphaDiff = nextKeyframe.Value.Alpha - currentKeyframe.Value.Alpha; - - return new SKColor( - ClampToByte(currentKeyframe.Value.Red + redDiff * KeyframeProgressEased), - ClampToByte(currentKeyframe.Value.Green + greenDiff * KeyframeProgressEased), - ClampToByte(currentKeyframe.Value.Blue + blueDiff * KeyframeProgressEased), - ClampToByte(currentKeyframe.Value.Alpha + alphaDiff * KeyframeProgressEased) - ); - } - - private byte ClampToByte(float value) - { - return (byte) Math.Max(0, Math.Min(255, value)); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs deleted file mode 100644 index b38250ca6..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile.LayerProperties; -using SkiaSharp; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - /// - public class SKPointKeyframeEngine : KeyframeEngine - { - public sealed override List CompatibleTypes { get; } = new List {typeof(SKPoint)}; - - protected override object GetInterpolatedValue() - { - var currentKeyframe = (Keyframe) CurrentKeyframe; - var nextKeyframe = (Keyframe) NextKeyframe; - - var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X; - var yDiff = nextKeyframe.Value.Y - currentKeyframe.Value.Y; - return new SKPoint(currentKeyframe.Value.X + xDiff * KeyframeProgressEased, currentKeyframe.Value.Y + yDiff * KeyframeProgressEased); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs deleted file mode 100644 index ea9e8fe45..000000000 --- a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile.LayerProperties; -using SkiaSharp; - -namespace Artemis.Core.Models.Profile.KeyframeEngines -{ - /// - public class SKSizeKeyframeEngine : KeyframeEngine - { - public sealed override List CompatibleTypes { get; } = new List {typeof(SKSize)}; - - protected override object GetInterpolatedValue() - { - var currentKeyframe = (Keyframe) CurrentKeyframe; - var nextKeyframe = (Keyframe) NextKeyframe; - - var widthDiff = nextKeyframe.Value.Width - currentKeyframe.Value.Width; - var heightDiff = nextKeyframe.Value.Height - currentKeyframe.Value.Height; - return new SKSize(currentKeyframe.Value.Width + widthDiff * KeyframeProgressEased, currentKeyframe.Value.Height + heightDiff * KeyframeProgressEased); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index f4b8cf4f8..257a63244 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -32,8 +32,8 @@ namespace Artemis.Core.Models.Profile Profile = profile; Parent = parent; Name = name; - General = new LayerGeneralProperties(); - Transform = new LayerTransformProperties(); + General = new LayerGeneralProperties {IsCorePropertyGroup = true}; + Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); } @@ -47,8 +47,8 @@ namespace Artemis.Core.Models.Profile Parent = parent; Name = layerEntity.Name; Order = layerEntity.Order; - General = new LayerGeneralProperties(); - Transform = new LayerTransformProperties(); + General = new LayerGeneralProperties {IsCorePropertyGroup = true}; + Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); } @@ -104,7 +104,7 @@ namespace Artemis.Core.Models.Profile /// /// The brush that will fill the . /// - public ILayerBrush LayerBrush { get; internal set; } + public BaseLayerBrush LayerBrush { get; internal set; } public override string ToString() { @@ -121,8 +121,9 @@ namespace Artemis.Core.Models.Profile LayerEntity.Order = Order; LayerEntity.Name = Name; LayerEntity.ProfileId = Profile.EntityId; - foreach (var layerProperty in Properties) - layerProperty.ApplyToEntity(); + General.ApplyToEntity(); + Transform.ApplyToEntity(); + LayerBrush.ApplyToEntity(); // LEDs LayerEntity.Leds.Clear(); @@ -146,7 +147,7 @@ namespace Artemis.Core.Models.Profile private void ApplyShapeType() { - switch (Properties.ShapeType.CurrentValue) + switch (General.ShapeType.CurrentValue) { case LayerShapeType.Ellipse: LayerShape = new Ellipse(this); @@ -179,20 +180,11 @@ namespace Artemis.Core.Models.Profile /// public override void Update(double deltaTime) { - foreach (var property in Properties) - property.KeyframeEngine?.Update(deltaTime); + General.Update(deltaTime); + Transform.Update(deltaTime); - // For now, reset all keyframe engines after the last keyframe was hit - // This is a placeholder method of repeating the animation until repeat modes are implemented - var lastKeyframe = Properties.SelectMany(p => p.UntypedKeyframes).OrderByDescending(t => t.Position).FirstOrDefault(); - if (lastKeyframe != null) - { - if (Properties.Any(p => p.KeyframeEngine?.Progress > lastKeyframe.Position)) - { - foreach (var baseLayerProperty in Properties) - baseLayerProperty.KeyframeEngine?.OverrideProgress(TimeSpan.Zero); - } - } + LayerBrush?.UpdateProperties(deltaTime); + // TODO: Find the last keyframe and if required, reset the properties LayerBrush?.Update(deltaTime); } @@ -208,10 +200,10 @@ namespace Artemis.Core.Models.Profile using (var paint = new SKPaint()) { - paint.BlendMode = Properties.BlendMode.CurrentValue; - paint.Color = new SKColor(0, 0, 0, (byte) (Properties.Opacity.CurrentValue * 2.55f)); + paint.BlendMode = General.BlendMode.CurrentValue; + paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); - switch (Properties.FillType.CurrentValue) + switch (General.FillType.CurrentValue) { case LayerFillType.Stretch: StretchRender(canvas, canvasInfo, paint); @@ -230,11 +222,11 @@ namespace Artemis.Core.Models.Profile private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { // Apply transformations - var sizeProperty = Properties.Scale.CurrentValue; - var rotationProperty = Properties.Rotation.CurrentValue; + var sizeProperty = Transform.Scale.CurrentValue; + var rotationProperty = Transform.Rotation.CurrentValue; var anchorPosition = GetLayerAnchorPosition(); - var anchorProperty = Properties.AnchorPoint.CurrentValue; + var anchorProperty = Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width; @@ -251,11 +243,11 @@ namespace Artemis.Core.Models.Profile private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { // Apply transformations - var sizeProperty = Properties.Scale.CurrentValue; - var rotationProperty = Properties.Rotation.CurrentValue; + var sizeProperty = Transform.Scale.CurrentValue; + var rotationProperty = Transform.Rotation.CurrentValue; var anchorPosition = GetLayerAnchorPosition(); - var anchorProperty = Properties.AnchorPoint.CurrentValue; + var anchorProperty = Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width; @@ -308,7 +300,7 @@ namespace Artemis.Core.Models.Profile internal SKPoint GetLayerAnchorPosition() { - var positionProperty = Properties.Position.CurrentValue; + var positionProperty = Transform.Position.CurrentValue; // Start at the center of the shape var position = new SKPoint(Bounds.MidX, Bounds.MidY); diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs new file mode 100644 index 000000000..b8daceb02 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -0,0 +1,25 @@ +using Artemis.Storage.Entities.Profile; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + public abstract class BaseLayerProperty + { + /// + /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID + /// + internal bool IsCoreProperty { get; set; } + + /// + /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values + /// + /// + /// + internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup); + + /// + /// Saves the property to the underlying property entity that was configured when calling + /// + /// + internal abstract void ApplyToEntity(); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs similarity index 72% rename from src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs rename to src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index f07b9c909..2b89dfd8d 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/GenericLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using Artemis.Core.Exceptions; using Artemis.Core.Utilities; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; @@ -10,18 +12,19 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI. /// - /// Note: You cannot initialize layer properties yourself. If properly placed, the Artemis core will initialize + /// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will initialize /// these for you. /// /// /// The type of property encapsulated in this layer property - public abstract class GenericLayerProperty : LayerProperty + public abstract class LayerProperty : BaseLayerProperty { - private T _currentValue; - private List> _keyframes; private T _baseValue; + private T _currentValue; + private bool _isInitialized; + private List> _keyframes; - protected GenericLayerProperty() + protected LayerProperty() { _keyframes = new List>(); } @@ -92,47 +95,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public LayerPropertyKeyframe NextKeyframe { get; protected set; } - /// - /// Updates the property, moving the timeline forwards by the provided - /// - /// The amount of time to move the timeline forwards - public void Update(double deltaTime) - { - TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); - if (!KeyframesSupported || !KeyframesEnabled) - return; - - // The current keyframe is the last keyframe before the current time - CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); - // The next keyframe is the first keyframe that's after the current time - NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); - - // No need to update the current value if either of the keyframes are null - if (CurrentKeyframe == null) - CurrentValue = BaseValue; - else if (NextKeyframe == null) - CurrentValue = CurrentKeyframe.Value; - // Only determine progress and current value if both keyframes are present - else - { - var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; - var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); - var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); - UpdateCurrentValue(keyframeProgress, keyframeProgressEased); - } - - OnUpdated(); - } - - /// - /// Overrides the timeline progress to match the provided - /// - /// The new progress to set the layer property timeline to. - public void OverrideProgress(TimeSpan progress) - { - TimelineProgress = TimeSpan.Zero; - Update(progress.TotalSeconds); - } + internal PropertyEntity PropertyEntity { get; set; } + internal LayerPropertyGroup LayerPropertyGroup { get; set; } /// /// Adds a keyframe to the layer property @@ -166,24 +130,106 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// The current keyframe progress, eased with the current easing function protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased); + /// + /// Updates the property, moving the timeline forwards by the provided + /// + /// The amount of time to move the timeline forwards + internal void Update(double deltaTime) + { + TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime)); + if (!KeyframesSupported || !KeyframesEnabled) + return; + + // The current keyframe is the last keyframe before the current time + CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); + // The next keyframe is the first keyframe that's after the current time + NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); + + // No need to update the current value if either of the keyframes are null + if (CurrentKeyframe == null) + CurrentValue = BaseValue; + else if (NextKeyframe == null) + CurrentValue = CurrentKeyframe.Value; + // Only determine progress and current value if both keyframes are present + else + { + var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position; + var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds); + var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction); + UpdateCurrentValue(keyframeProgress, keyframeProgressEased); + } + + OnUpdated(); + } + + /// + /// Overrides the timeline progress to match the provided + /// + /// The new progress to set the layer property timeline to. + internal void OverrideProgress(TimeSpan overrideTime) + { + TimelineProgress = TimeSpan.Zero; + Update(overrideTime.TotalSeconds); + } + + /// + /// Sorts the keyframes in ascending order by position + /// internal void SortKeyframes() { _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); } - internal override void LoadFromEntity(PropertyEntity entity) + internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup) { - BaseValue = JsonConvert.DeserializeObject(entity.Value); - CurrentValue = BaseValue; + // Doubt this will happen but let's make sure + if (_isInitialized) + throw new ArtemisCoreException("Layer property already initialized, wut"); - _keyframes.Clear(); - foreach (var keyframeEntity in entity.KeyframeEntities) + PropertyEntity = entity; + LayerPropertyGroup = layerPropertyGroup; + LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime); + LayerPropertyGroup.PropertyGroupOverriding += (sender, args) => OverrideProgress(args.OverrideTime); + + try { - var value = JsonConvert.DeserializeObject(keyframeEntity.Value); - var keyframe = new LayerPropertyKeyframe(value, keyframeEntity.Position, (Easings.Functions) keyframeEntity.EasingFunction); - _keyframes.Add(keyframe); + IsLoadedFromStorage = true; + BaseValue = JsonConvert.DeserializeObject(entity.Value); + CurrentValue = BaseValue; + + _keyframes.Clear(); + _keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe( + JsonConvert.DeserializeObject(k.Value), + k.Position, + (Easings.Functions) k.EasingFunction) + )); } - SortKeyframes(); + catch (JsonException e) + { + // TODO: Properly log the JSON exception + Debug.WriteLine($"JSON exception while deserializing: {e}"); + IsLoadedFromStorage = false; + } + finally + { + SortKeyframes(); + _isInitialized = true; + } + } + + internal override void ApplyToEntity() + { + if (_isInitialized) + throw new ArtemisCoreException("Layer property is not yet initialized"); + + PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue); + PropertyEntity.KeyframeEntities.Clear(); + PropertyEntity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity + { + Value = JsonConvert.SerializeObject(k.Value), + Position = k.Position, + EasingFunction = (int) k.EasingFunction + })); } #region Events @@ -215,9 +261,4 @@ namespace Artemis.Core.Models.Profile.LayerProperties #endregion } - - public abstract class LayerProperty - { - internal abstract void LoadFromEntity(PropertyEntity entity); - } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index ec4694d30..b89b9fbaa 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -14,7 +14,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties EasingFunction = easingFunction; } - public GenericLayerProperty LayerProperty { get; internal set; } + public LayerProperty LayerProperty { get; internal set; } public T Value { get; set; } public TimeSpan Position diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs index c7c5af8d1..1d32b63a7 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs @@ -4,7 +4,7 @@ using Artemis.Core.Models.Profile.Colors; namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class ColorGradientLayerProperty : GenericLayerProperty + public class ColorGradientLayerProperty : LayerProperty { internal ColorGradientLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs index f10e6253b..0dbcf3332 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs @@ -3,7 +3,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class EnumLayerProperty : GenericLayerProperty where T : System.Enum + public class EnumLayerProperty : LayerProperty where T : System.Enum { public EnumLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs index f7b9b9aa1..bc6e2ed01 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/FloatLayerProperty.cs @@ -1,7 +1,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class FloatLayerProperty : GenericLayerProperty + public class FloatLayerProperty : LayerProperty { internal FloatLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs index 80cf9ff4a..96eab86f2 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/IntLayerProperty.cs @@ -3,7 +3,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class IntLayerProperty : GenericLayerProperty + public class IntLayerProperty : LayerProperty { internal IntLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs index 654001a56..f1d99e642 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerBrushReferenceLayerProperty.cs @@ -5,7 +5,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types /// /// A special layer property used to configure the selected layer brush /// - public class LayerBrushReferenceLayerProperty : GenericLayerProperty + public class LayerBrushReferenceLayerProperty : LayerProperty { internal LayerBrushReferenceLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs index c765d849c..b478cbd77 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs @@ -4,7 +4,7 @@ using SkiaSharp; namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class SKColorLayerProperty : GenericLayerProperty + public class SKColorLayerProperty : LayerProperty { internal SKColorLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs index d4694d283..7595d2d62 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKPointLayerProperty.cs @@ -3,7 +3,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class SKPointLayerProperty : GenericLayerProperty + public class SKPointLayerProperty : LayerProperty { internal SKPointLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs index 259d4a910..f89229005 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs @@ -3,7 +3,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types { /// - public class SKSizeLayerProperty : GenericLayerProperty + public class SKSizeLayerProperty : LayerProperty { internal SKSizeLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 7e61ec93b..60b83d7ca 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Artemis.Core.Events; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Exceptions; @@ -11,6 +12,11 @@ namespace Artemis.Core.Models.Profile { public bool PropertiesInitialized { get; private set; } + /// + /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID + /// + internal bool IsCorePropertyGroup { get; set; } + /// /// Called when all layer properties in this property group have been initialized /// @@ -26,10 +32,10 @@ namespace Artemis.Core.Models.Profile var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) { - if (!typeof(GenericLayerProperty<>).IsAssignableFrom(propertyInfo.PropertyType)) + if (!typeof(LayerProperty<>).IsAssignableFrom(propertyInfo.PropertyType)) throw new ArtemisPluginException("Layer property with PropertyDescription attribute must be of type LayerProperty"); - var instance = (LayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); + var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); InitializeProperty(layer, path, instance); propertyInfo.SetValue(this, instance); } @@ -52,11 +58,61 @@ namespace Artemis.Core.Models.Profile PropertiesInitialized = true; } - private void InitializeProperty(Layer layer, string path, LayerProperty instance) + internal void ApplyToEntity() { - var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == path); - if (entity != null) - instance.LoadFromEntity(entity); + // Get all properties with a PropertyDescriptionAttribute + foreach (var propertyInfo in GetType().GetProperties()) + { + var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); + if (propertyDescription != null) + { + } + else + { + var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); + if (propertyGroupDescription != null) + { + } + } + } } + + internal void Update(double deltaTime) + { + // Since at this point we don't know what properties the group has without using reflection, + // let properties subscribe to the update event and update themselves + OnPropertyGroupUpdating(new PropertyGroupUpdatingEventArgs(deltaTime)); + } + + internal void Override(TimeSpan overrideTime) + { + // Same as above, but now the progress is overridden + OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); + } + + private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) + { + var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid; + var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); + if (entity != null) + instance.ApplyToLayerProperty(entity, this); + } + + #region Events + + internal event EventHandler PropertyGroupUpdating; + internal event EventHandler PropertyGroupOverriding; + + internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e) + { + PropertyGroupUpdating?.Invoke(this, e); + } + + protected virtual void OnPropertyGroupOverriding(PropertyGroupUpdatingEventArgs e) + { + PropertyGroupOverriding?.Invoke(this, e); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index 123f2076e..61701837b 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -1,7 +1,5 @@ -using System; -using System.IO; +using System.IO; using Artemis.Core.Exceptions; -using Artemis.Core.Models.Profile.KeyframeEngines; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Repositories.Interfaces; @@ -53,14 +51,13 @@ namespace Artemis.Core.Ninject catch (LiteException e) { // I don't like this way of error reporting, now I need to use reflection if I want a meaningful error code - if (e.ErrorCode != LiteException.INVALID_DATABASE) + if (e.ErrorCode != LiteException.INVALID_DATABASE) throw new ArtemisCoreException($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); // If the DB is invalid it's probably LiteDB v4 (TODO: we'll have to do something better later) File.Delete($"{Constants.DataFolder}\\database.db"); return new LiteRepository(Constants.ConnectionString); } - }).InSingletonScope(); // Bind all repositories as singletons @@ -73,15 +70,6 @@ namespace Artemis.Core.Ninject .Configure(c => c.InSingletonScope()); }); - // Bind all keyframe engines - Kernel.Bind(x => - { - x.FromAssemblyContaining() - .SelectAllClasses() - .InheritedFrom() - .BindAllBaseClasses(); - }); - Kernel.Bind().ToProvider(); Kernel.Bind().ToProvider(); } diff --git a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs new file mode 100644 index 000000000..1d831f06f --- /dev/null +++ b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs @@ -0,0 +1,70 @@ +using System; +using Artemis.Core.Models.Profile; +using Artemis.Core.Plugins.Models; +using Artemis.Core.Services.Interfaces; +using SkiaSharp; + +namespace Artemis.Core.Plugins.LayerBrush +{ + /// + /// A basic layer brush that does not implement any layer property, to use properties with persistent storage, + /// implement instead + /// + public abstract class BaseLayerBrush : IDisposable + { + /// + /// Gets the layer this brush is applied to + /// + public Layer Layer { get; internal set; } + + /// + /// Gets the descriptor of this brush + /// + public LayerBrushDescriptor Descriptor { get; internal set; } + + /// + /// Gets the plugin info that defined this brush + /// + public PluginInfo PluginInfo => Descriptor.LayerBrushProvider.PluginInfo; + + /// + /// Called when the brush is being removed from the layer + /// + public virtual void Dispose() + { + } + + /// + /// Called before rendering every frame, write your update logic here + /// + /// + public virtual void Update(double deltaTime) + { + } + + /// + /// The main method of rendering anything to the layer. The provided is specific to the layer + /// and matches it's width and height. + /// Called during rendering or layer preview, in the order configured on the layer + /// + /// The layer canvas + /// + /// The path to be filled, represents the shape + /// The paint to be used to fill the shape + public virtual void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + { + } + + internal virtual void InitializeProperties(ILayerService layerService, string path) + { + } + + internal virtual void ApplyToEntity() + { + } + + internal virtual void UpdateProperties(double deltaTime) + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs deleted file mode 100644 index 2b688fe91..000000000 --- a/src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Artemis.Core.Models.Profile; -using Artemis.Core.Services.Interfaces; -using SkiaSharp; - -namespace Artemis.Core.Plugins.LayerBrush -{ - public interface ILayerBrush : IDisposable - { - /// - /// Gets the layer this brush is applied to - /// - Layer Layer { get; } - - /// - /// Gets the descriptor of this brush - /// - LayerBrushDescriptor Descriptor { get; } - - /// - /// Called before rendering every frame, write your update logic here - /// - /// - void Update(double deltaTime); - - /// - /// The main method of rendering anything to the layer. The provided is specific to the layer - /// and matches it's width and height. - /// Called during rendering or layer preview, in the order configured on the layer - /// - /// The layer canvas - /// - /// The path to be filled, represents the shape - /// The paint to be used to fill the shape - void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); - - public void InitializeProperties(ILayerService layerService, string path); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index 7103ea0e1..176c2e062 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -1,11 +1,10 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; -using SkiaSharp; namespace Artemis.Core.Plugins.LayerBrush { - public abstract class LayerBrush : ILayerBrush where T : LayerPropertyGroup + public abstract class LayerBrush : BaseLayerBrush where T : LayerPropertyGroup { private T _properties; @@ -15,27 +14,6 @@ namespace Artemis.Core.Plugins.LayerBrush Descriptor = descriptor; } - /// - public Layer Layer { get; } - - /// - public LayerBrushDescriptor Descriptor { get; } - - /// - public virtual void Dispose() - { - } - - /// - public virtual void Update(double deltaTime) - { - } - - /// - public virtual void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) - { - } - #region Properties /// @@ -52,12 +30,12 @@ namespace Artemis.Core.Plugins.LayerBrush } internal set => _properties = value; } - + /// /// Gets whether all properties on this brush are initialized /// public bool PropertiesInitialized { get; private set; } - + /// /// Called when all layer properties in this brush have been initialized /// @@ -65,13 +43,23 @@ namespace Artemis.Core.Plugins.LayerBrush { } - public void InitializeProperties(ILayerService layerService, string path) + internal override void InitializeProperties(ILayerService layerService, string path) { - Properties.InitializeProperties(layerService, Descriptor.LayerBrushProvider.PluginInfo, path); + Properties.InitializeProperties(layerService, Layer, path); OnPropertiesInitialized(); PropertiesInitialized = true; } + internal override void ApplyToEntity() + { + Properties.ApplyToEntity(); + } + + internal override void UpdateProperties(double deltaTime) + { + Properties.Update(deltaTime); + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs index 2631161dd..53f505f7a 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrushProvider.cs @@ -20,7 +20,7 @@ namespace Artemis.Core.Plugins.LayerBrush public ReadOnlyCollection LayerBrushDescriptors => _layerBrushDescriptors.AsReadOnly(); - protected void AddLayerBrushDescriptor(string displayName, string description, string icon) where T : ILayerBrush + protected void AddLayerBrushDescriptor(string displayName, string description, string icon) where T : BaseLayerBrush { _layerBrushDescriptors.Add(new LayerBrushDescriptor(displayName, description, icon, typeof(T), this)); } diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs index 7731b655e..c59245532 100644 --- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs +++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs @@ -1,6 +1,4 @@ using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.KeyframeEngines; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; namespace Artemis.Core.Services.Interfaces @@ -8,14 +6,27 @@ namespace Artemis.Core.Services.Interfaces public interface ILayerService : IArtemisService { /// - /// Instantiates and adds the described by the provided + /// Creates a new layer + /// + /// + /// + /// + /// + Layer CreateLayer(Profile profile, ProfileElement parent, string name); + + /// + /// Instantiates and adds the described by the provided + /// /// to the . /// /// The layer to instantiate the brush for /// - ILayerBrush InstantiateLayerBrush(Layer layer); + BaseLayerBrush InstantiateLayerBrush(Layer layer); - void LoadPropertyBaseValue(Layer layer, string path, object layerProperty); - void LoadPropertyKeyframes(Layer layer, string path, object layerProperty); + /// + /// Removes the layer brush from the provided layer and disposes it + /// + /// + void RemoveLayerBrush(Layer layer); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index 5b154ba52..4ef8c7981 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.KeyframeEngines; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush; -using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; -using Newtonsoft.Json; using Ninject; using Ninject.Parameters; using Serilog; @@ -31,8 +26,8 @@ namespace Artemis.Core.Services var layer = new Layer(profile, parent, name); // Layers have two hardcoded property groups, instantiate them - layer.General.InitializeProperties(this, layer, null, null); - layer.Transform.InitializeProperties(this, layer, null, null); + layer.General.InitializeProperties(this, layer, null); + layer.Transform.InitializeProperties(this, layer, null); // With the properties loaded, the layer brush can be instantiated InstantiateLayerBrush(layer); @@ -40,7 +35,7 @@ namespace Artemis.Core.Services return layer; } - public ILayerBrush InstantiateLayerBrush(Layer layer) + public BaseLayerBrush InstantiateLayerBrush(Layer layer) { RemoveLayerBrush(layer); @@ -62,7 +57,7 @@ namespace Artemis.Core.Services new ConstructorArgument("layer", layer), new ConstructorArgument("descriptor", descriptor) }; - var layerBrush = (ILayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments); + var layerBrush = (BaseLayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments); layerBrush.InitializeProperties(this, null); layer.LayerBrush = layerBrush; @@ -76,11 +71,9 @@ namespace Artemis.Core.Services var brush = layer.LayerBrush; layer.LayerBrush = null; - - var propertiesToRemove = layer.Properties.Where(l => l.PluginInfo == brush.Descriptor.LayerBrushProvider.PluginInfo).ToList(); - foreach (var layerProperty in propertiesToRemove) - layer.Properties.RemoveLayerProperty(layerProperty); brush.Dispose(); + + layer.LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 8434ea02b..8729211b5 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -94,7 +94,6 @@ namespace Artemis.Core.Services.Storage if (profile != null) { InstantiateProfileLayerBrushes(profile); - InstantiateProfileKeyframeEngines(profile); } } @@ -165,14 +164,7 @@ namespace Artemis.Core.Services.Storage foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) _layerService.InstantiateLayerBrush(layer); } - - private void InstantiateProfileKeyframeEngines(Profile profile) - { - // Only instantiate engines for properties without an existing engine instance - foreach (var layerProperty in profile.GetAllLayers().SelectMany(l => l.Properties).Where(p => p.KeyframeEngine == null)) - _layerService.InstantiateKeyframeEngine(layerProperty); - } - + private void ActiveProfilesPopulateLeds(ArtemisSurface surface) { var profileModules = _pluginService.GetPluginsOfType(); @@ -186,14 +178,7 @@ namespace Artemis.Core.Services.Storage foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) InstantiateProfileLayerBrushes(profileModule.ActiveProfile); } - - private void ActiveProfilesInstantiateKeyframeEngines() - { - var profileModules = _pluginService.GetPluginsOfType(); - foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - InstantiateProfileKeyframeEngines(profileModule.ActiveProfile); - } - + #region Event handlers private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) @@ -212,7 +197,6 @@ namespace Artemis.Core.Services.Storage if (e.PluginInfo.Instance is LayerBrushProvider) { ActiveProfilesInstantiateProfileLayerBrushes(); - ActiveProfilesInstantiateKeyframeEngines(); } else if (e.PluginInfo.Instance is ProfileModule profileModule) { diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index 2f450633d..4c339e22e 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -10,8 +10,9 @@ namespace Artemis.Storage.Entities.Profile KeyframeEntities = new List(); } - public string Id { get; set; } - public string ValueType { get; set; } + public Guid PluginGuid { get; set; } + public string Path { get; set; } + public string Value { get; set; } public bool IsUsingKeyframes { get; set; } diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 7bd7aae57..34986e68d 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -50,7 +50,7 @@ namespace Artemis.UI.Ninject.Factories public interface ILayerPropertyVmFactory : IVmFactory { - LayerPropertyViewModel Create(LayerProperty layerProperty, LayerPropertyViewModel parent); + LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent); } public interface IPropertyTreeVmFactory : IVmFactory diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 5dde7e5fd..cc4ef6b8f 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -169,7 +169,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties PopulateProperties(e.LayerProperty.Layer); } - private LayerPropertyViewModel CreatePropertyViewModel(LayerProperty layerProperty) + private LayerPropertyViewModel CreatePropertyViewModel(BaseLayerProperty layerProperty) { LayerPropertyViewModel parent = null; // If the property has a parent, find it's VM diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index b3c72586d..497e6f379 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private bool _keyframesEnabled; private bool _isExpanded; - public LayerPropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) + public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) { _kernel = kernel; _profileEditorService = profileEditorService; @@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties Parent?.Children.Add(this); } - public LayerProperty LayerProperty { get; } + public BaseLayerProperty LayerProperty { get; } public LayerPropertyViewModel Parent { get; } public List Children { get; } From e97fc046f59108a8c0d754aa8cb3e005b53a5380 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 29 Apr 2020 19:45:13 +0200 Subject: [PATCH 05/14] Core - Added ways for the core to easily access brush properties %*/@ me, dealing with generic types in APIs is tricky --- src/Artemis.Core/Models/Profile/Layer.cs | 22 +++++++-- .../LayerProperties/BaseLayerProperty.cs | 38 ++++++++++++++- .../Profile/LayerProperties/LayerProperty.cs | 45 +++++------------ .../Types/EnumLayerProperty.cs | 7 +-- .../Models/Profile/LayerPropertyGroup.cs | 48 ++++++++++++++++++- .../Plugins/LayerBrush/BaseLayerBrush.cs | 11 +++++ .../Plugins/LayerBrush/LayerBrush.cs | 15 +++++- 7 files changed, 145 insertions(+), 41 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 257a63244..19a20ab39 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Extensions; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.Core.Models.Surface; @@ -180,13 +181,28 @@ namespace Artemis.Core.Models.Profile /// public override void Update(double deltaTime) { + if (LayerBrush == null) + return; + General.Update(deltaTime); Transform.Update(deltaTime); + LayerBrush.UpdateProperties(deltaTime); - LayerBrush?.UpdateProperties(deltaTime); - // TODO: Find the last keyframe and if required, reset the properties + var properties = new List(General.GetAllLayerProperties()); + properties.AddRange(Transform.GetAllLayerProperties()); + properties.AddRange(LayerBrush.GetAllLayerProperties()); - LayerBrush?.Update(deltaTime); + // For now, reset all keyframe engines after the last keyframe was hit + // This is a placeholder method of repeating the animation until repeat modes are implemented + var timeLineEnd = properties.Max(p => p.GetLastKeyframePosition()); + if (properties.Any(p => p.TimelineProgress >= timeLineEnd)) + { + General.Override(TimeSpan.Zero); + Transform.Override(TimeSpan.Zero); + LayerBrush.OverrideProperties(TimeSpan.Zero); + } + + LayerBrush.Update(deltaTime); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index b8daceb02..e27f82c6c 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -1,13 +1,46 @@ -using Artemis.Storage.Entities.Profile; +using System; +using System.Collections.Generic; +using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile.LayerProperties { + /// + /// For internal use only, to implement your own layer property type, extend instead. + /// public abstract class BaseLayerProperty { /// /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID /// internal bool IsCoreProperty { get; set; } + internal PropertyEntity PropertyEntity { get; set; } + internal LayerPropertyGroup LayerPropertyGroup { get; set; } + + /// + /// Gets whether keyframes are supported on this property + /// + public bool KeyframesSupported { get; protected set; } + + /// + /// Gets or sets whether keyframes are enabled on this property, has no effect if is + /// False + /// + public bool KeyframesEnabled { get; set; } + + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden { get; set; } + + /// + /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied + /// + public bool IsLoadedFromStorage { get; internal set; } + + /// + /// Gets the total progress on the timeline + /// + public TimeSpan TimelineProgress { get; internal set; } /// /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values @@ -21,5 +54,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// internal abstract void ApplyToEntity(); + + internal abstract List GetKeyframePositions(); + internal abstract TimeSpan GetLastKeyframePosition(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 2b89dfd8d..1ce9a5ec3 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -53,38 +53,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties get => !KeyframesEnabled || !KeyframesSupported ? BaseValue : _currentValue; internal set => _currentValue = value; } - - /// - /// Gets whether keyframes are supported on this property - /// - public bool KeyframesSupported { get; internal set; } - - /// - /// Gets or sets whether keyframes are enabled on this property, has no effect if is - /// False - /// - public bool KeyframesEnabled { get; set; } - - /// - /// Gets or sets whether the property is hidden in the UI - /// - public bool IsHidden { get; set; } - - /// - /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied - /// - public bool IsLoadedFromStorage { get; internal set; } - + /// /// Gets a read-only list of all the keyframes on this layer property /// public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); - - /// - /// Gets the total progress on the timeline - /// - public TimeSpan TimelineProgress { get; private set; } - + /// /// Gets the current keyframe in the timeline according to the current progress /// @@ -94,10 +68,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// Gets the next keyframe in the timeline according to the current progress /// public LayerPropertyKeyframe NextKeyframe { get; protected set; } - - internal PropertyEntity PropertyEntity { get; set; } - internal LayerPropertyGroup LayerPropertyGroup { get; set; } - + /// /// Adds a keyframe to the layer property /// @@ -232,6 +203,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties })); } + internal override List GetKeyframePositions() + { + return Keyframes.Select(k => k.Position).ToList(); + } + + internal override TimeSpan GetLastKeyframePosition() + { + return Keyframes.LastOrDefault()?.Position ?? TimeSpan.Zero; + } + #region Events public event EventHandler Updated; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs index 0dbcf3332..63cfbbfd7 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/EnumLayerProperty.cs @@ -1,9 +1,10 @@ -using Artemis.Core.Exceptions; +using System; +using Artemis.Core.Exceptions; namespace Artemis.Core.Models.Profile.LayerProperties.Types { - /// - public class EnumLayerProperty : LayerProperty where T : System.Enum + /// + public class EnumLayerProperty : LayerProperty where T : Enum { public EnumLayerProperty() { diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 60b83d7ca..486b36aa2 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Events; +using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Exceptions; @@ -10,13 +13,31 @@ namespace Artemis.Core.Models.Profile { public class LayerPropertyGroup { + private ReadOnlyCollection _allLayerProperties; + + protected LayerPropertyGroup() + { + LayerProperties = new List(); + LayerPropertyGroups = new List(); + } + public bool PropertiesInitialized { get; private set; } /// - /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID + /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID /// internal bool IsCorePropertyGroup { get; set; } + /// + /// A list of all layer properties in this group + /// + internal List LayerProperties { get; set; } + + /// + /// A list of al child groups in this group + /// + internal List LayerPropertyGroups { get; set; } + /// /// Called when all layer properties in this property group have been initialized /// @@ -26,6 +47,10 @@ namespace Artemis.Core.Models.Profile internal void InitializeProperties(ILayerService layerService, Layer layer, string path) { + // Doubt this will happen but let's make sure + if (PropertiesInitialized) + throw new ArtemisCoreException("Layer property group already initialized, wut"); + // Get all properties with a PropertyDescriptionAttribute foreach (var propertyInfo in GetType().GetProperties()) { @@ -38,6 +63,7 @@ namespace Artemis.Core.Models.Profile var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); InitializeProperty(layer, path, instance); propertyInfo.SetValue(this, instance); + LayerProperties.Add(instance); } else { @@ -50,6 +76,7 @@ namespace Artemis.Core.Models.Profile var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); + LayerPropertyGroups.Add(instance); } } } @@ -90,6 +117,25 @@ namespace Artemis.Core.Models.Profile OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); } + /// + /// Recursively gets all layer properties on this group and any subgroups + /// + /// + internal IReadOnlyCollection GetAllLayerProperties() + { + if (!PropertiesInitialized) + return new List(); + if (_allLayerProperties != null) + return _allLayerProperties; + + var result = new List(LayerProperties); + foreach (var layerPropertyGroup in LayerPropertyGroups) + result.AddRange(layerPropertyGroup.GetAllLayerProperties()); + + _allLayerProperties = result.AsReadOnly(); + return _allLayerProperties; + } + private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) { var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid; diff --git a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs index 1d831f06f..8e36e0ab3 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; using SkiaSharp; @@ -66,5 +68,14 @@ namespace Artemis.Core.Plugins.LayerBrush internal virtual void UpdateProperties(double deltaTime) { } + + internal virtual void OverrideProperties(TimeSpan overrideTime) + { + } + + internal virtual IReadOnlyCollection GetAllLayerProperties() + { + return new List(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index 176c2e062..517322571 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -1,4 +1,7 @@ -using Artemis.Core.Models.Profile; +using System; +using System.Collections.Generic; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; @@ -60,6 +63,16 @@ namespace Artemis.Core.Plugins.LayerBrush Properties.Update(deltaTime); } + internal override void OverrideProperties(TimeSpan overrideTime) + { + Properties.Override(overrideTime); + } + + internal override IReadOnlyCollection GetAllLayerProperties() + { + return Properties.GetAllLayerProperties(); + } + #endregion } } \ No newline at end of file From 41b3c772104eef0fc20828ad2b3a2605e8aad33f Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 30 Apr 2020 00:12:24 +0200 Subject: [PATCH 06/14] UI - My poor UI :< I regret starting this refactor --- src/Artemis.Core/Models/Profile/Layer.cs | 20 ++++++++--- .../LayerProperties/BaseLayerProperty.cs | 21 +++++++----- .../BaseLayerPropertyKeyframe.cs | 27 +++++++++++++++ .../Profile/LayerProperties/LayerProperty.cs | 33 +++++++++++-------- .../LayerProperties/LayerPropertyKeyFrame.cs | 17 ++++++++-- .../Plugins/LayerBrush/BaseLayerBrush.cs | 2 ++ .../Plugins/LayerBrush/LayerBrush.cs | 3 ++ src/Artemis.Core/Services/LayerService.cs | 8 ++++- .../Entities/Profile/PropertyEntity.cs | 2 +- .../Ninject/Factories/IVMFactory.cs | 2 +- .../LayerProperties/LayerPropertyViewModel.cs | 32 +++++++++++------- .../PropertyTrackKeyframeViewModel.cs | 11 ++++--- .../Services/ProfileEditorService.cs | 11 ++----- 13 files changed, 130 insertions(+), 59 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 19a20ab39..45b6bc8b3 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -122,6 +122,7 @@ namespace Artemis.Core.Models.Profile LayerEntity.Order = Order; LayerEntity.Name = Name; LayerEntity.ProfileId = Profile.EntityId; + General.ApplyToEntity(); Transform.ApplyToEntity(); LayerBrush.ApplyToEntity(); @@ -184,27 +185,36 @@ namespace Artemis.Core.Models.Profile if (LayerBrush == null) return; - General.Update(deltaTime); - Transform.Update(deltaTime); - LayerBrush.UpdateProperties(deltaTime); - var properties = new List(General.GetAllLayerProperties()); properties.AddRange(Transform.GetAllLayerProperties()); properties.AddRange(LayerBrush.GetAllLayerProperties()); // For now, reset all keyframe engines after the last keyframe was hit // This is a placeholder method of repeating the animation until repeat modes are implemented - var timeLineEnd = properties.Max(p => p.GetLastKeyframePosition()); + var timeLineEnd = properties.Max(p => p.BaseKeyframes.Max(k => k.Position)); if (properties.Any(p => p.TimelineProgress >= timeLineEnd)) { General.Override(TimeSpan.Zero); Transform.Override(TimeSpan.Zero); LayerBrush.OverrideProperties(TimeSpan.Zero); } + else + { + General.Update(deltaTime); + Transform.Update(deltaTime); + LayerBrush.UpdateProperties(deltaTime); + } LayerBrush.Update(deltaTime); } + public void OverrideProgress(TimeSpan timeOverride) + { + General.Override(timeOverride); + Transform.Override(timeOverride); + LayerBrush.OverrideProperties(timeOverride); + } + /// public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index e27f82c6c..9863661da 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -9,12 +9,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public abstract class BaseLayerProperty { - /// - /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID - /// - internal bool IsCoreProperty { get; set; } - internal PropertyEntity PropertyEntity { get; set; } - internal LayerPropertyGroup LayerPropertyGroup { get; set; } + internal BaseLayerProperty() + { + } /// /// Gets whether keyframes are supported on this property @@ -42,6 +39,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public TimeSpan TimelineProgress { get; internal set; } + /// + /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID + /// + internal bool IsCoreProperty { get; set; } + + internal PropertyEntity PropertyEntity { get; set; } + internal LayerPropertyGroup LayerPropertyGroup { get; set; } + internal abstract IReadOnlyList BaseKeyframes { get; } + /// /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values /// @@ -54,8 +60,5 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// internal abstract void ApplyToEntity(); - - internal abstract List GetKeyframePositions(); - internal abstract TimeSpan GetLastKeyframePosition(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs new file mode 100644 index 000000000..b4adf932f --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerPropertyKeyframe.cs @@ -0,0 +1,27 @@ +using System; +using Artemis.Core.Utilities; + +namespace Artemis.Core.Models.Profile.LayerProperties +{ + /// + /// For internal use only, use instead. + /// + public abstract class BaseLayerPropertyKeyframe + { + internal BaseLayerPropertyKeyframe() + { + } + + /// + /// The position of this keyframe in the timeline + /// + public abstract TimeSpan Position { get; set; } + + /// + /// The easing function applied on the value of the keyframe + /// + public abstract Easings.Functions EasingFunction { get; set; } + + internal abstract BaseLayerProperty BaseLayerProperty { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 1ce9a5ec3..a9b0a397a 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -12,7 +12,8 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI. /// - /// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will initialize + /// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will + /// initialize /// these for you. /// /// @@ -53,12 +54,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties get => !KeyframesEnabled || !KeyframesSupported ? BaseValue : _currentValue; internal set => _currentValue = value; } - + /// /// Gets a read-only list of all the keyframes on this layer property /// public IReadOnlyList> Keyframes => _keyframes.AsReadOnly(); - + /// /// Gets the current keyframe in the timeline according to the current progress /// @@ -68,7 +69,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// Gets the next keyframe in the timeline according to the current progress /// public LayerPropertyKeyframe NextKeyframe { get; protected set; } - + + internal override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly(); + /// /// Adds a keyframe to the layer property /// @@ -93,6 +96,16 @@ namespace Artemis.Core.Models.Profile.LayerProperties OnKeyframeRemoved(); } + /// + /// Removes all keyframes from the layer property + /// + public void ClearKeyframes() + { + var keyframes = new List>(_keyframes); + foreach (var layerPropertyKeyframe in keyframes) + RemoveKeyframe(layerPropertyKeyframe); + } + /// /// Called every update (if keyframes are both supported and enabled) to determine the new /// based on the provided progress @@ -167,6 +180,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties IsLoadedFromStorage = true; BaseValue = JsonConvert.DeserializeObject(entity.Value); CurrentValue = BaseValue; + KeyframesEnabled = entity.KeyframesEnabled; _keyframes.Clear(); _keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe( @@ -194,6 +208,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties throw new ArtemisCoreException("Layer property is not yet initialized"); PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue); + PropertyEntity.KeyframesEnabled = KeyframesEnabled; PropertyEntity.KeyframeEntities.Clear(); PropertyEntity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity { @@ -203,16 +218,6 @@ namespace Artemis.Core.Models.Profile.LayerProperties })); } - internal override List GetKeyframePositions() - { - return Keyframes.Select(k => k.Position).ToList(); - } - - internal override TimeSpan GetLastKeyframePosition() - { - return Keyframes.LastOrDefault()?.Position ?? TimeSpan.Zero; - } - #region Events public event EventHandler Updated; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs index b89b9fbaa..7cb1ffcb0 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs @@ -3,7 +3,7 @@ using Artemis.Core.Utilities; namespace Artemis.Core.Models.Profile.LayerProperties { - public class LayerPropertyKeyframe + public class LayerPropertyKeyframe : BaseLayerPropertyKeyframe { private TimeSpan _position; @@ -14,10 +14,18 @@ namespace Artemis.Core.Models.Profile.LayerProperties EasingFunction = easingFunction; } + /// + /// The layer property this keyframe is applied to + /// public LayerProperty LayerProperty { get; internal set; } + + /// + /// The value of this keyframe + /// public T Value { get; set; } - public TimeSpan Position + /// + public override TimeSpan Position { get => _position; set @@ -27,6 +35,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties } } - public Easings.Functions EasingFunction { get; set; } + /// + public sealed override Easings.Functions EasingFunction { get; set; } + + internal override BaseLayerProperty BaseLayerProperty => LayerProperty; } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs index 8e36e0ab3..beecad9b4 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs @@ -29,6 +29,8 @@ namespace Artemis.Core.Plugins.LayerBrush /// public PluginInfo PluginInfo => Descriptor.LayerBrushProvider.PluginInfo; + internal virtual LayerPropertyGroup BaseProperties => null; + /// /// Called when the brush is being removed from the layer /// diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index 517322571..b3807f239 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -46,6 +46,9 @@ namespace Artemis.Core.Plugins.LayerBrush { } + /// + internal override LayerPropertyGroup BaseProperties => Properties; + internal override void InitializeProperties(ILayerService layerService, string path) { Properties.InitializeProperties(layerService, Layer, path); diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index 4ef8c7981..a1cd47f46 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Services.Interfaces; @@ -75,5 +76,10 @@ namespace Artemis.Core.Services layer.LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid); } + + public void GetLayerPropertyGroups(Layer layer) + { + var groups = new List {layer.General, layer.Transform, layer.LayerBrush.BrushProperties}; + } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index 4c339e22e..de95b56b8 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -14,7 +14,7 @@ namespace Artemis.Storage.Entities.Profile public string Path { get; set; } public string Value { get; set; } - public bool IsUsingKeyframes { get; set; } + public bool KeyframesEnabled { get; set; } public List KeyframeEntities { get; set; } } diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 34986e68d..a783c2876 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -70,6 +70,6 @@ namespace Artemis.UI.Ninject.Factories public interface IPropertyTrackKeyframeVmFactory : IVmFactory { - PropertyTrackKeyframeViewModel Create(PropertyTrackViewModel propertyTrackViewModel, BaseKeyframe keyframe); + PropertyTrackKeyframeViewModel Create(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe keyframe); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 497e6f379..2c3572a5f 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Utilities; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; using Artemis.UI.Services.Interfaces; using Ninject; @@ -9,31 +10,30 @@ using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : PropertyChangedBase + public class LayerPropertyViewModel : LayerPropertyViewModel { private readonly IKernel _kernel; private readonly IProfileEditorService _profileEditorService; private bool _keyframesEnabled; private bool _isExpanded; - public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) + public LayerPropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) { _kernel = kernel; _profileEditorService = profileEditorService; - _keyframesEnabled = layerProperty.IsUsingKeyframes; + _keyframesEnabled = layerProperty.KeyframesEnabled; LayerProperty = layerProperty; Parent = parent; Children = new List(); - IsExpanded = layerProperty.ExpandByDefault; + + // TODO: Get from attribute + IsExpanded = false; Parent?.Children.Add(this); } - public BaseLayerProperty LayerProperty { get; } - - public LayerPropertyViewModel Parent { get; } - public List Children { get; } + public LayerProperty LayerProperty { get; } public bool IsExpanded { @@ -58,7 +58,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public PropertyInputViewModel GetPropertyInputViewModel() { // If the type is an enum type, search for Enum instead. - var type = LayerProperty.Type; + var type = typeof(T); if (type.IsEnum) type = typeof(Enum); @@ -72,15 +72,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private void UpdateKeyframes() { + // Either create a new first keyframe or clear all the keyframes if (_keyframesEnabled) - LayerProperty.CreateNewKeyframe(_profileEditorService.CurrentTime, LayerProperty.GetCurrentValue()); + LayerProperty.AddKeyframe(new LayerPropertyKeyframe(LayerProperty.CurrentValue, _profileEditorService.CurrentTime, Easings.Functions.Linear)); else LayerProperty.ClearKeyframes(); // Force the keyframe engine to update, the new keyframe is the current keyframe - LayerProperty.IsUsingKeyframes = _keyframesEnabled; - LayerProperty.KeyframeEngine?.Update(0); + LayerProperty.KeyframesEnabled = _keyframesEnabled; + LayerProperty.Update(0); _profileEditorService.UpdateSelectedProfileElement(); } @@ -88,6 +89,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties #region Events public event EventHandler ExpandedStateChanged; + protected virtual void OnExpandedStateChanged() { ExpandedStateChanged?.Invoke(this, EventArgs.Empty); @@ -97,4 +99,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties #endregion } + + public class LayerPropertyViewModel : PropertyChangedBase + { + public LayerPropertyViewModel Parent { get; protected set; } + public List Children { get; protected set; } + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs index 30876cdbe..a4f0d692c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Input; @@ -10,12 +9,12 @@ using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { - public class PropertyTrackKeyframeViewModel : PropertyChangedBase + public class PropertyTrackKeyframeViewModel : PropertyTrackKeyframeViewModel { private readonly IProfileEditorService _profileEditorService; private int _pixelsPerSecond; - public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, BaseKeyframe keyframe, IProfileEditorService profileEditorService) + public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe keyframe, IProfileEditorService profileEditorService) { _profileEditorService = profileEditorService; @@ -27,7 +26,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public bool IsSelected { get; set; } public PropertyTrackViewModel PropertyTrackViewModel { get; } - public BaseKeyframe Keyframe { get; } + public LayerPropertyKeyframe Keyframe { get; } public BindableCollection EasingViewModels { get; set; } public double X { get; set; } public string Timestamp { get; set; } @@ -170,4 +169,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline #endregion } + + public abstract class PropertyTrackKeyframeViewModel : PropertyChangedBase + { + } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index f552f8ac2..19c497b38 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -74,18 +74,11 @@ namespace Artemis.UI.Services { if (SelectedProfile == null) return; + var delta = CurrentTime - _lastUpdateTime; foreach (var layer in SelectedProfile.GetAllLayers()) { - // Override keyframe progress - foreach (var baseLayerProperty in layer.Properties) - baseLayerProperty.KeyframeEngine?.OverrideProgress(CurrentTime); - - // Force layer shape to redraw - layer.LayerShape?.CalculateRenderProperties(); - // Manually update the layer's engine and brush - foreach (var property in layer.Properties) - property.KeyframeEngine?.Update(delta.TotalSeconds); + layer.OverrideProgress(CurrentTime); layer.LayerBrush?.Update(delta.TotalSeconds); } From 54081b591bafe5688a9b6fb4732883e4af1ea9d6 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 1 May 2020 23:07:21 +0200 Subject: [PATCH 07/14] UI - Restructured layer property VMs --- src/Artemis.Core/Models/Profile/Layer.cs | 10 +- .../LayerProperties/BaseLayerProperty.cs | 10 +- .../Profile/LayerProperties/LayerProperty.cs | 2 +- .../Models/Profile/LayerPropertyGroup.cs | 21 +-- .../Plugins/LayerBrush/BaseLayerBrush.cs | 21 +-- .../Plugins/LayerBrush/LayerBrush.cs | 13 +- src/Artemis.Core/Services/LayerService.cs | 5 - .../Abstract/LayerPropertyBaseViewModel.cs | 11 ++ .../LayerPropertiesViewModel.cs | 136 ++++++------------ .../LayerPropertyGroupViewModel.cs | 67 +++++++++ .../LayerProperties/LayerPropertyViewModel.cs | 101 ++----------- 11 files changed, 163 insertions(+), 234 deletions(-) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 45b6bc8b3..3f9dd807a 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -125,7 +125,7 @@ namespace Artemis.Core.Models.Profile General.ApplyToEntity(); Transform.ApplyToEntity(); - LayerBrush.ApplyToEntity(); + LayerBrush.BaseProperties.ApplyToEntity(); // LEDs LayerEntity.Leds.Clear(); @@ -187,7 +187,7 @@ namespace Artemis.Core.Models.Profile var properties = new List(General.GetAllLayerProperties()); properties.AddRange(Transform.GetAllLayerProperties()); - properties.AddRange(LayerBrush.GetAllLayerProperties()); + properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties()); // For now, reset all keyframe engines after the last keyframe was hit // This is a placeholder method of repeating the animation until repeat modes are implemented @@ -196,13 +196,13 @@ namespace Artemis.Core.Models.Profile { General.Override(TimeSpan.Zero); Transform.Override(TimeSpan.Zero); - LayerBrush.OverrideProperties(TimeSpan.Zero); + LayerBrush.BaseProperties.Override(TimeSpan.Zero); } else { General.Update(deltaTime); Transform.Update(deltaTime); - LayerBrush.UpdateProperties(deltaTime); + LayerBrush.BaseProperties.Update(deltaTime); } LayerBrush.Update(deltaTime); @@ -212,7 +212,7 @@ namespace Artemis.Core.Models.Profile { General.Override(timeOverride); Transform.Override(timeOverride); - LayerBrush.OverrideProperties(timeOverride); + LayerBrush.BaseProperties.Override(timeOverride); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 9863661da..af9a7d590 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -5,7 +5,7 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile.LayerProperties { /// - /// For internal use only, to implement your own layer property type, extend instead. + /// For internal use only, to implement your own layer property type, extend instead. /// public abstract class BaseLayerProperty { @@ -42,11 +42,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Used to declare that this property doesn't belong to a plugin and should use the core plugin GUID /// - internal bool IsCoreProperty { get; set; } + public bool IsCoreProperty { get; internal set; } + + /// + /// Gets a list of all the keyframes in their non-generic base form, without their values being available + /// + public abstract IReadOnlyList BaseKeyframes { get; } internal PropertyEntity PropertyEntity { get; set; } internal LayerPropertyGroup LayerPropertyGroup { get; set; } - internal abstract IReadOnlyList BaseKeyframes { get; } /// /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index a9b0a397a..66d55132d 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -70,7 +70,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public LayerPropertyKeyframe NextKeyframe { get; protected set; } - internal override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly(); + public override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly(); /// /// Adds a keyframe to the layer property diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 486b36aa2..f9057d67d 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -14,29 +14,34 @@ namespace Artemis.Core.Models.Profile public class LayerPropertyGroup { private ReadOnlyCollection _allLayerProperties; + private readonly List _layerProperties; + private readonly List _layerPropertyGroups; protected LayerPropertyGroup() { - LayerProperties = new List(); - LayerPropertyGroups = new List(); + _layerProperties = new List(); + _layerPropertyGroups = new List(); } + /// + /// Gets whether this property group's properties are all initialized + /// public bool PropertiesInitialized { get; private set; } /// /// Used to declare that this property group doesn't belong to a plugin and should use the core plugin GUID /// - internal bool IsCorePropertyGroup { get; set; } + public bool IsCorePropertyGroup { get; internal set; } /// /// A list of all layer properties in this group /// - internal List LayerProperties { get; set; } + public ReadOnlyCollection LayerProperties => _layerProperties.AsReadOnly(); /// /// A list of al child groups in this group /// - internal List LayerPropertyGroups { get; set; } + public ReadOnlyCollection LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); /// /// Called when all layer properties in this property group have been initialized @@ -63,7 +68,7 @@ namespace Artemis.Core.Models.Profile var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); InitializeProperty(layer, path, instance); propertyInfo.SetValue(this, instance); - LayerProperties.Add(instance); + _layerProperties.Add(instance); } else { @@ -76,7 +81,7 @@ namespace Artemis.Core.Models.Profile var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); - LayerPropertyGroups.Add(instance); + _layerPropertyGroups.Add(instance); } } } @@ -121,7 +126,7 @@ namespace Artemis.Core.Models.Profile /// Recursively gets all layer properties on this group and any subgroups /// /// - internal IReadOnlyCollection GetAllLayerProperties() + public IReadOnlyCollection GetAllLayerProperties() { if (!PropertiesInitialized) return new List(); diff --git a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs index beecad9b4..5a06297f8 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/BaseLayerBrush.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; using SkiaSharp; @@ -29,7 +27,7 @@ namespace Artemis.Core.Plugins.LayerBrush /// public PluginInfo PluginInfo => Descriptor.LayerBrushProvider.PluginInfo; - internal virtual LayerPropertyGroup BaseProperties => null; + public virtual LayerPropertyGroup BaseProperties => null; /// /// Called when the brush is being removed from the layer @@ -62,22 +60,5 @@ namespace Artemis.Core.Plugins.LayerBrush internal virtual void InitializeProperties(ILayerService layerService, string path) { } - - internal virtual void ApplyToEntity() - { - } - - internal virtual void UpdateProperties(double deltaTime) - { - } - - internal virtual void OverrideProperties(TimeSpan overrideTime) - { - } - - internal virtual IReadOnlyCollection GetAllLayerProperties() - { - return new List(); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index b3807f239..a27fcdb19 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -47,7 +47,7 @@ namespace Artemis.Core.Plugins.LayerBrush } /// - internal override LayerPropertyGroup BaseProperties => Properties; + public override LayerPropertyGroup BaseProperties => Properties; internal override void InitializeProperties(ILayerService layerService, string path) { @@ -56,22 +56,17 @@ namespace Artemis.Core.Plugins.LayerBrush PropertiesInitialized = true; } - internal override void ApplyToEntity() + internal virtual void ApplyToEntity() { Properties.ApplyToEntity(); } - internal override void UpdateProperties(double deltaTime) - { - Properties.Update(deltaTime); - } - - internal override void OverrideProperties(TimeSpan overrideTime) + internal virtual void OverrideProperties(TimeSpan overrideTime) { Properties.Override(overrideTime); } - internal override IReadOnlyCollection GetAllLayerProperties() + internal virtual IReadOnlyCollection GetAllLayerProperties() { return Properties.GetAllLayerProperties(); } diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index a1cd47f46..6a1c344e3 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -76,10 +76,5 @@ namespace Artemis.Core.Services layer.LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid); } - - public void GetLayerPropertyGroups(Layer layer) - { - var groups = new List {layer.General, layer.Transform, layer.LayerBrush.BrushProperties}; - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs new file mode 100644 index 000000000..694dc9480 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Artemis.Core.Models.Profile.LayerProperties; +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract +{ + public abstract class LayerPropertyBaseViewModel : PropertyChangedBase + { + public abstract List GetKeyframes(bool visibleOnly); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index cc4ef6b8f..4844e8619 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -7,6 +7,7 @@ using System.Windows.Media; using Artemis.Core.Events; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Services; using Artemis.Core.Services.Interfaces; using Artemis.UI.Events; @@ -21,28 +22,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public class LayerPropertiesViewModel : ProfileEditorPanelViewModel { private readonly ICoreService _coreService; - private readonly List _layerPropertyViewModels; - private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly IPropertyTreeVmFactory _propertyTreeVmFactory; private readonly IPropertyTimelineVmFactory _propertyTimelineVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; - private Layer _lastSelectedLayer; public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService, - ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyTreeVmFactory propertyTreeVmFactory, IPropertyTimelineVmFactory propertyTimelineVmFactory) { _profileEditorService = profileEditorService; _coreService = coreService; _settingsService = settingsService; - _layerPropertyVmFactory = layerPropertyVmFactory; _propertyTreeVmFactory = propertyTreeVmFactory; _propertyTimelineVmFactory = propertyTimelineVmFactory; - _layerPropertyViewModels = new List(); PixelsPerSecond = 31; } @@ -67,6 +62,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties set => _profileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / PixelsPerSecond); } + public BindableCollection LayerPropertyGroups { get; set; } public PropertyTreeViewModel PropertyTree { get; set; } public PropertyTimelineViewModel PropertyTimeline { get; set; } @@ -88,12 +84,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; _profileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; - if (_lastSelectedLayer != null) - { - _lastSelectedLayer.Properties.LayerPropertyRegistered -= LayerOnPropertyRegistered; - _lastSelectedLayer.Properties.LayerPropertyRemoved -= LayerOnPropertyRemoved; - } - PropertyTree?.Dispose(); PropertyTimeline?.Dispose(); PropertyTree = null; @@ -122,78 +112,30 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private void PopulateProperties(ProfileElement profileElement) { - if (_lastSelectedLayer != null) - { - _lastSelectedLayer.Properties.LayerPropertyRegistered -= LayerOnPropertyRegistered; - _lastSelectedLayer.Properties.LayerPropertyRemoved -= LayerOnPropertyRemoved; - } - + LayerPropertyGroups.Clear(); if (profileElement is Layer layer) { - // Create VMs for missing properties - foreach (var baseLayerProperty in layer.Properties) + // Add the built-in root groups of the layer + var generalAttribute = Attribute.GetCustomAttribute( + layer.GetType().GetProperty(nameof(layer.General)), + typeof(PropertyGroupDescriptionAttribute) + ); + var transformAttribute = Attribute.GetCustomAttribute( + layer.GetType().GetProperty(nameof(layer.Transform)), + typeof(PropertyGroupDescriptionAttribute) + ); + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); + + // Add the rout group of the brush + // The root group of the brush has no attribute so let's pull one out of our sleeve + var brushDescription = new PropertyGroupDescriptionAttribute { - if (_layerPropertyViewModels.All(vm => vm.LayerProperty != baseLayerProperty)) - CreatePropertyViewModel(baseLayerProperty); - } - - // Remove VMs for extra properties - foreach (var layerPropertyViewModel in _layerPropertyViewModels.ToList()) - { - if (layer.Properties.All(p => p != layerPropertyViewModel.LayerProperty)) - RemovePropertyViewModel(layerPropertyViewModel); - } - - _lastSelectedLayer = layer; - layer.Properties.LayerPropertyRegistered += LayerOnPropertyRegistered; - layer.Properties.LayerPropertyRemoved += LayerOnPropertyRemoved; + Name = layer.LayerBrush.Descriptor.DisplayName, + Description = layer.LayerBrush.Descriptor.Description + }; + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.LayerBrush.BaseProperties, brushDescription)); } - else - { - foreach (var layerPropertyViewModel in _layerPropertyViewModels.ToList()) - RemovePropertyViewModel(layerPropertyViewModel); - - _lastSelectedLayer = null; - } - } - - private void LayerOnPropertyRegistered(object sender, LayerPropertyEventArgs e) - { - Console.WriteLine("LayerOnPropertyRegistered"); - PopulateProperties(e.LayerProperty.Layer); - } - - private void LayerOnPropertyRemoved(object sender, LayerPropertyEventArgs e) - { - Console.WriteLine("LayerOnPropertyRemoved"); - PopulateProperties(e.LayerProperty.Layer); - } - - private LayerPropertyViewModel CreatePropertyViewModel(BaseLayerProperty layerProperty) - { - LayerPropertyViewModel parent = null; - // If the property has a parent, find it's VM - if (layerProperty.Parent != null) - { - parent = _layerPropertyViewModels.FirstOrDefault(vm => vm.LayerProperty == layerProperty.Parent); - // If no VM is found, create it - if (parent == null) - parent = CreatePropertyViewModel(layerProperty.Parent); - } - - var createdViewModel = _layerPropertyVmFactory.Create(layerProperty, parent); - _layerPropertyViewModels.Add(createdViewModel); - PropertyTree.AddLayerProperty(createdViewModel); - PropertyTimeline.AddLayerProperty(createdViewModel); - - return createdViewModel; - } - - private void RemovePropertyViewModel(LayerPropertyViewModel layerPropertyViewModel) - { - PropertyTree.RemoveLayerProperty(layerPropertyViewModel); - PropertyTimeline.RemoveLayerProperty(layerPropertyViewModel); - _layerPropertyViewModels.Remove(layerPropertyViewModel); } #endregion @@ -259,9 +201,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private TimeSpan CalculateEndTime() { - // End time is the last keyframe + 10 sec - var lastKeyFrame = PropertyTimeline.PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault(); - return lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.MaxValue; + if (!(_profileEditorService.SelectedProfileElement is Layer layer)) + return TimeSpan.MaxValue; + + var keyframes = GetKeyframes(false); + + // If there are no keyframes, don't stop at all + if (!keyframes.Any()) + return TimeSpan.MaxValue; + // If there are keyframes, stop after the last keyframe + 10 sec + return keyframes.Max(k => k.Position).Add(TimeSpan.FromSeconds(10)); } private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e) @@ -323,19 +272,24 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties return; } - // If shift is held, snap to closest keyframe - var visibleKeyframes = PropertyTimeline.PropertyTrackViewModels - .Where(t => t.LayerPropertyViewModel.Parent != null && t.LayerPropertyViewModel.Parent.IsExpanded) - .SelectMany(t => t.KeyframeViewModels); + var visibleKeyframes = GetKeyframes(true); + // Take a tolerance of 5 pixels (half a keyframe width) var tolerance = 1000f / PixelsPerSecond * 5; - var closeKeyframe = visibleKeyframes.FirstOrDefault( - kf => Math.Abs(kf.Keyframe.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance - ); - _profileEditorService.CurrentTime = closeKeyframe?.Keyframe.Position ?? newTime; + var closeKeyframe = visibleKeyframes.FirstOrDefault(k => Math.Abs(k.Position.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance); + _profileEditorService.CurrentTime = closeKeyframe?.Position ?? newTime; } } + private List GetKeyframes(bool visibleOnly) + { + var result = new List(); + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + result.AddRange(layerPropertyGroupViewModel.GetKeyframes(visibleOnly)); + + return result; + } + #endregion #region Events diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs new file mode 100644 index 000000000..2c3e0190d --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties +{ + public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel + { + public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription) + { + LayerPropertyGroup = layerPropertyGroup; + PropertyGroupDescription = propertyGroupDescription; + + IsExpanded = PropertyGroupDescription.ExpandByDefault; + + Children = new List(); + PopulateChildren(); + } + + public LayerPropertyGroup LayerPropertyGroup { get; } + public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; } + public bool IsExpanded { get; set; } + + public List Children { get; set; } + + private void PopulateChildren() + { + // Get all properties and property groups and create VMs for them + foreach (var propertyInfo in LayerPropertyGroup.GetType().GetProperties()) + { + var propertyAttribute = (PropertyDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); + var groupAttribute = (PropertyGroupDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); + var value = propertyInfo.GetValue(LayerPropertyGroup); + + // Create VMs for properties on the group + if (propertyAttribute != null && value is BaseLayerProperty) + { + // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line + var genericType = propertyInfo.PropertyType.GetGenericArguments()[0]; + var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType); + var instance = Activator.CreateInstance(genericViewModel, value, propertyAttribute); + Children.Add((LayerPropertyBaseViewModel) instance); + } + // Create VMs for child groups on this group, resulting in a nested structure + else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup) + { + Children.Add(new LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute)); + } + } + } + + public override List GetKeyframes(bool visibleOnly) + { + var result = new List(); + if (!IsExpanded) + return result; + + foreach (var layerPropertyBaseViewModel in Children) + result.AddRange(layerPropertyBaseViewModel.GetKeyframes(visibleOnly)); + + return result; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 2c3572a5f..699c0f0a9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,108 +1,25 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Utilities; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; -using Artemis.UI.Services.Interfaces; -using Ninject; -using Stylet; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : LayerPropertyViewModel + public class LayerPropertyViewModel : LayerPropertyBaseViewModel { - private readonly IKernel _kernel; - private readonly IProfileEditorService _profileEditorService; - private bool _keyframesEnabled; - private bool _isExpanded; - - public LayerPropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService) + public LayerPropertyViewModel(LayerProperty layerProperty, PropertyDescriptionAttribute propertyDescription) { - _kernel = kernel; - _profileEditorService = profileEditorService; - _keyframesEnabled = layerProperty.KeyframesEnabled; - LayerProperty = layerProperty; - Parent = parent; - Children = new List(); - - // TODO: Get from attribute - IsExpanded = false; - - Parent?.Children.Add(this); + PropertyDescription = propertyDescription; } public LayerProperty LayerProperty { get; } + public PropertyDescriptionAttribute PropertyDescription { get; } - public bool IsExpanded + public override List GetKeyframes(bool visibleOnly) { - get => _isExpanded; - set - { - _isExpanded = value; - OnExpandedStateChanged(); - } + return LayerProperty.BaseKeyframes.ToList(); } - - public bool KeyframesEnabled - { - get => _keyframesEnabled; - set - { - _keyframesEnabled = value; - UpdateKeyframes(); - } - } - - public PropertyInputViewModel GetPropertyInputViewModel() - { - // If the type is an enum type, search for Enum instead. - var type = typeof(T); - if (type.IsEnum) - type = typeof(Enum); - - var match = _kernel.Get>().FirstOrDefault(p => p.CompatibleTypes.Contains(type)); - if (match == null) - return null; - - match.Initialize(this); - return match; - } - - private void UpdateKeyframes() - { - - // Either create a new first keyframe or clear all the keyframes - if (_keyframesEnabled) - LayerProperty.AddKeyframe(new LayerPropertyKeyframe(LayerProperty.CurrentValue, _profileEditorService.CurrentTime, Easings.Functions.Linear)); - else - LayerProperty.ClearKeyframes(); - - // Force the keyframe engine to update, the new keyframe is the current keyframe - LayerProperty.KeyframesEnabled = _keyframesEnabled; - LayerProperty.Update(0); - - _profileEditorService.UpdateSelectedProfileElement(); - } - - #region Events - - public event EventHandler ExpandedStateChanged; - - protected virtual void OnExpandedStateChanged() - { - ExpandedStateChanged?.Invoke(this, EventArgs.Empty); - foreach (var layerPropertyViewModel in Children) - layerPropertyViewModel.OnExpandedStateChanged(); - } - - #endregion - } - - public class LayerPropertyViewModel : PropertyChangedBase - { - public LayerPropertyViewModel Parent { get; protected set; } - public List Children { get; protected set; } } } \ No newline at end of file From af21d83487e111f8d729dbbc3ed6a34fd3cf6d18 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 14 May 2020 21:58:30 +0200 Subject: [PATCH 08/14] UI - Further restructuring --- .../Profile/LayerProperties/LayerProperty.cs | 42 +++ .../Models/Profile/LayerPropertyGroup.cs | 5 + .../Ninject/Factories/IVMFactory.cs | 25 -- .../Abstract/LayerPropertyBaseViewModel.cs | 16 +- .../LayerPropertiesViewModel.cs | 31 +-- .../LayerPropertyGroupViewModel.cs | 38 ++- .../LayerPropertyKeyframeViewModel.cs | 14 + .../LayerProperties/LayerPropertyViewModel.cs | 51 +++- .../PropertyInput/PropertyInputViewModel.cs | 92 ------- .../PropertyTree/PropertyTreeChildView.xaml | 54 ---- .../PropertyTreeChildViewModel.cs | 42 --- .../PropertyTree/PropertyTreeItemViewModel.cs | 37 --- .../PropertyTreeParentViewModel.cs | 65 ----- .../PropertyTree/PropertyTreeViewModel.cs | 103 -------- .../{ => Controls}/PropertyTimelineHeader.cs | 0 .../Timeline/PropertyTimelineView.xaml | 55 ---- .../Timeline/PropertyTimelineViewModel.cs | 245 ------------------ .../Timeline/PropertyTrackEasingViewModel.cs | 51 ---- .../PropertyTrackKeyframeViewModel.cs | 176 ------------- .../Timeline/PropertyTrackView.xaml | 111 -------- .../Timeline/PropertyTrackViewModel.cs | 91 ------- .../TimelinePropertyGroupViewModel.cs | 14 + .../Timeline/TimelinePropertyViewModel.cs | 30 +++ .../Timeline/TimelineViewModel.cs | 14 + .../PropertyInput/BrushPropertyInputView.xaml | 0 .../BrushPropertyInputViewModel.cs | 10 +- .../ColorGradientPropertyInputView.xaml | 0 .../ColorGradientPropertyInputViewModel.cs | 0 .../PropertyInput/EnumPropertyInputView.xaml | 0 .../EnumPropertyInputViewModel.cs | 10 +- .../PropertyInput/FloatPropertyInputView.xaml | 0 .../FloatPropertyInputViewModel.cs | 0 .../PropertyInput/IntPropertyInputView.xaml | 0 .../IntPropertyInputViewModel.cs | 0 .../PropertyInput/PropertyInputViewModel.cs | 51 ++++ .../SKColorPropertyInputView.xaml | 0 .../SKColorPropertyInputViewModel.cs | 0 .../SKPointPropertyInputView.xaml | 0 .../SKPointPropertyInputViewModel.cs | 0 .../SKSizePropertyInputView.xaml | 0 .../SKSizePropertyInputViewModel.cs | 0 .../Tree/TreePropertyGroupViewModel.cs | 14 + .../Tree/TreePropertyViewModel.cs | 65 +++++ .../TreeView.xaml} | 35 +-- .../LayerProperties/Tree/TreeViewModel.cs | 14 + .../ProfileTree/ProfileTreeView.xaml | 3 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 8 +- .../Visualization/Tools/EditToolViewModel.cs | 28 +- .../Interfaces/IProfileEditorService.cs | 9 +- src/Artemis.UI/Services/LayerEditorService.cs | 14 +- .../Services/ProfileEditorService.cs | 34 ++- .../ColorBrushProperties.cs | 2 +- .../NoiseBrushProperties.cs | 2 +- 53 files changed, 459 insertions(+), 1242 deletions(-) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyKeyframeViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/{ => Controls}/PropertyTimelineHeader.cs (100%) delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackEasingViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackView.xaml delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/BrushPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/BrushPropertyInputViewModel.cs (89%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/ColorGradientPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/ColorGradientPropertyInputViewModel.cs (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/EnumPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/EnumPropertyInputViewModel.cs (76%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/FloatPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/FloatPropertyInputViewModel.cs (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/IntPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/IntPropertyInputViewModel.cs (100%) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKColorPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKColorPropertyInputViewModel.cs (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKPointPropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKPointPropertyInputViewModel.cs (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKSizePropertyInputView.xaml (100%) rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree => Tree}/PropertyInput/SKSizePropertyInputViewModel.cs (100%) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs rename src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/{PropertyTree/PropertyTreeView.xaml => Tree/TreeView.xaml} (78%) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 66d55132d..c33897687 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -72,6 +72,33 @@ namespace Artemis.Core.Models.Profile.LayerProperties public override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly(); + /// + /// Sets the current value, using either keyframes if enabled or the base value. + /// + /// The value to set. + /// + /// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new + /// or existing keyframe. + /// + public void SetCurrentValue(T value, TimeSpan? time) + { + if (time == null || !KeyframesEnabled || !KeyframesSupported) + BaseValue = value; + else + { + // If on a keyframe, update the keyframe + var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value); + // Create a new keyframe if none found + if (currentKeyframe == null) + AddKeyframe(new LayerPropertyKeyframe(value, time.Value, Easings.Functions.Linear)); + else + currentKeyframe.Value = value; + + // Update the property so that the new keyframe is reflected on the current value + Update(0); + } + } + /// /// Adds a keyframe to the layer property /// @@ -220,9 +247,24 @@ namespace Artemis.Core.Models.Profile.LayerProperties #region Events + /// + /// Occurs once every frame when the layer property is updated + /// public event EventHandler Updated; + + /// + /// Occurs when the base value of the layer property was updated + /// public event EventHandler BaseValueChanged; + + /// + /// Occurs when a new keyframe was added to the layer property + /// public event EventHandler KeyframeAdded; + + /// + /// Occurs when a keyframe was removed from the layer property + /// public event EventHandler KeyframeRemoved; protected virtual void OnUpdated() diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index f9057d67d..c06c94647 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -33,6 +33,11 @@ namespace Artemis.Core.Models.Profile /// public bool IsCorePropertyGroup { get; internal set; } + /// + /// Gets or sets whether the property is hidden in the UI + /// + public bool IsHidden { get; set; } + /// /// A list of all layer properties in this group /// diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index a783c2876..5e96cd8b1 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -47,29 +47,4 @@ namespace Artemis.UI.Ninject.Factories { ProfileLayerViewModel Create(Layer layer); } - - public interface ILayerPropertyVmFactory : IVmFactory - { - LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent); - } - - public interface IPropertyTreeVmFactory : IVmFactory - { - PropertyTreeViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel); - } - - public interface IPropertyTimelineVmFactory : IVmFactory - { - PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel); - } - - public interface IPropertyTrackVmFactory : IVmFactory - { - PropertyTrackViewModel Create(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel); - } - - public interface IPropertyTrackKeyframeVmFactory : IVmFactory - { - PropertyTrackKeyframeViewModel Create(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe keyframe); - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs index 694dc9480..3a4364209 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs @@ -1,11 +1,23 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Artemis.Core.Models.Profile.LayerProperties; using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract { - public abstract class LayerPropertyBaseViewModel : PropertyChangedBase + public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable { + protected LayerPropertyBaseViewModel() + { + Children = new List(); + } + + public bool IsExpanded { get; set; } + public abstract bool IsVisible { get; } + + public List Children { get; set; } + public abstract List GetKeyframes(bool visibleOnly); + public abstract void Dispose(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 4844e8619..9114231c4 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -11,9 +11,8 @@ using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Services; using Artemis.Core.Services.Interfaces; using Artemis.UI.Events; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; using Artemis.UI.Services.Interfaces; using Stylet; @@ -22,22 +21,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public class LayerPropertiesViewModel : ProfileEditorPanelViewModel { private readonly ICoreService _coreService; - private readonly IPropertyTreeVmFactory _propertyTreeVmFactory; - private readonly IPropertyTimelineVmFactory _propertyTimelineVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; - public LayerPropertiesViewModel(IProfileEditorService profileEditorService, - ICoreService coreService, - ISettingsService settingsService, - IPropertyTreeVmFactory propertyTreeVmFactory, - IPropertyTimelineVmFactory propertyTimelineVmFactory) + public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService) { _profileEditorService = profileEditorService; _coreService = coreService; _settingsService = settingsService; - _propertyTreeVmFactory = propertyTreeVmFactory; - _propertyTimelineVmFactory = propertyTimelineVmFactory; PixelsPerSecond = 31; } @@ -63,13 +54,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties } public BindableCollection LayerPropertyGroups { get; set; } - public PropertyTreeViewModel PropertyTree { get; set; } - public PropertyTimelineViewModel PropertyTimeline { get; set; } + public TreeViewModel TreeViewModel { get; set; } + public TimelineViewModel TimelineViewModel { get; set; } protected override void OnInitialActivate() { - PropertyTree = _propertyTreeVmFactory.Create(this); - PropertyTimeline = _propertyTimelineVmFactory.Create(this); + TreeViewModel = new TreeViewModel(LayerPropertyGroups); + TimelineViewModel = new TimelineViewModel(LayerPropertyGroups); PopulateProperties(_profileEditorService.SelectedProfileElement); @@ -84,10 +75,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; _profileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; - PropertyTree?.Dispose(); - PropertyTimeline?.Dispose(); - PropertyTree = null; - PropertyTimeline = null; base.OnClose(); } @@ -124,8 +111,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties layer.GetType().GetProperty(nameof(layer.Transform)), typeof(PropertyGroupDescriptionAttribute) ); - LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); - LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); // Add the rout group of the brush // The root group of the brush has no attribute so let's pull one out of our sleeve @@ -134,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties Name = layer.LayerBrush.Descriptor.DisplayName, Description = layer.LayerBrush.Descriptor.Description }; - LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(layer.LayerBrush.BaseProperties, brushDescription)); + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(_profileEditorService, layer.LayerBrush.BaseProperties, brushDescription)); } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 2c3e0190d..aef93f5c0 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -4,27 +4,37 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; +using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel { - public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription) + public LayerPropertyGroupViewModel(IProfileEditorService profileEditorService, LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription) { + ProfileEditorService = profileEditorService; + LayerPropertyGroup = layerPropertyGroup; PropertyGroupDescription = propertyGroupDescription; - IsExpanded = PropertyGroupDescription.ExpandByDefault; - Children = new List(); + TreePropertyGroupViewModel = new TreePropertyGroupViewModel(this); + TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this); + PopulateChildren(); } + public override bool IsVisible => !LayerPropertyGroup.IsHidden; + + public IProfileEditorService ProfileEditorService { get; } + public LayerPropertyGroup LayerPropertyGroup { get; } public PropertyGroupDescriptionAttribute PropertyGroupDescription { get; } - public bool IsExpanded { get; set; } - public List Children { get; set; } + public TreePropertyGroupViewModel TreePropertyGroupViewModel { get; set; } + public TimelinePropertyGroupViewModel TimelinePropertyGroupViewModel { get; set; } private void PopulateChildren() { @@ -36,18 +46,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties var value = propertyInfo.GetValue(LayerPropertyGroup); // Create VMs for properties on the group - if (propertyAttribute != null && value is BaseLayerProperty) + if (propertyAttribute != null && value is BaseLayerProperty baseLayerProperty) { - // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line - var genericType = propertyInfo.PropertyType.GetGenericArguments()[0]; - var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType); - var instance = Activator.CreateInstance(genericViewModel, value, propertyAttribute); - Children.Add((LayerPropertyBaseViewModel) instance); + var viewModel = ProfileEditorService.CreateLayerPropertyViewModel(baseLayerProperty, propertyAttribute); + if (viewModel != null) + Children.Add(viewModel); } // Create VMs for child groups on this group, resulting in a nested structure else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup) { - Children.Add(new LayerPropertyGroupViewModel(layerPropertyGroup, groupAttribute)); + Children.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layerPropertyGroup, groupAttribute)); } } } @@ -63,5 +71,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties return result; } + + public override void Dispose() + { + foreach (var layerPropertyBaseViewModel in Children) + layerPropertyBaseViewModel.Dispose(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyKeyframeViewModel.cs new file mode 100644 index 000000000..bb60f1d4f --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyKeyframeViewModel.cs @@ -0,0 +1,14 @@ +using Artemis.Core.Models.Profile.LayerProperties; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties +{ + public class LayerKeyframeViewModel + { + public LayerKeyframeViewModel(LayerPropertyKeyframe keyframe) + { + Keyframe = keyframe; + } + + public LayerPropertyKeyframe Keyframe { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 699c0f0a9..6b76b8672 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -3,23 +3,68 @@ using System.Linq; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; +using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { - public class LayerPropertyViewModel : LayerPropertyBaseViewModel + public class LayerPropertyViewModel : LayerPropertyViewModel { - public LayerPropertyViewModel(LayerProperty layerProperty, PropertyDescriptionAttribute propertyDescription) + public LayerPropertyViewModel(IProfileEditorService profileEditorService, LayerProperty layerProperty, PropertyDescriptionAttribute propertyDescription) + : base(profileEditorService, layerProperty) { LayerProperty = layerProperty; PropertyDescription = propertyDescription; + + TreePropertyViewModel = new TreePropertyViewModel(this); + TimelinePropertyViewModel = new TimelinePropertyViewModel(this); + + TreePropertyBaseViewModel = TreePropertyViewModel; + TimelinePropertyBaseViewModel = TimelinePropertyViewModel; } + public override bool IsVisible => !LayerProperty.IsHidden; + public LayerProperty LayerProperty { get; } - public PropertyDescriptionAttribute PropertyDescription { get; } + + public TreePropertyViewModel TreePropertyViewModel { get; set; } + public TimelinePropertyViewModel TimelinePropertyViewModel { get; set; } public override List GetKeyframes(bool visibleOnly) { return LayerProperty.BaseKeyframes.ToList(); } + + public override void Dispose() + { + TreePropertyViewModel.Dispose(); + TimelinePropertyViewModel.Dispose(); + } + + public void SetCurrentValue(T value, bool saveChanges) + { + LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime); + if (saveChanges) + ProfileEditorService.UpdateSelectedProfileElement(); + else + ProfileEditorService.UpdateProfilePreview(); + } + } + + public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel + { + public IProfileEditorService ProfileEditorService { get; } + public BaseLayerProperty BaseLayerProperty { get; } + + protected LayerPropertyViewModel(IProfileEditorService profileEditorService, BaseLayerProperty baseLayerProperty) + { + ProfileEditorService = profileEditorService; + BaseLayerProperty = baseLayerProperty; + } + + public PropertyDescriptionAttribute PropertyDescription { get; protected set; } + public TreePropertyViewModel TreePropertyBaseViewModel { get; set; } + public TimelinePropertyViewModel TimelinePropertyBaseViewModel { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs deleted file mode 100644 index 42b6e9678..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Exceptions; -using Artemis.UI.Services.Interfaces; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput -{ - public abstract class PropertyInputViewModel : PropertyChangedBase, IDisposable - { - protected PropertyInputViewModel(IProfileEditorService profileEditorService) - { - ProfileEditorService = profileEditorService; - } - - protected IProfileEditorService ProfileEditorService { get; } - public abstract List CompatibleTypes { get; } - - public bool Initialized { get; private set; } - public bool InputDragging { get; private set; } - public LayerPropertyViewModel LayerPropertyViewModel { get; private set; } - - protected object InputValue - { - get => LayerPropertyViewModel.LayerProperty.GetCurrentValue(); - set => UpdateInputValue(value); - } - - public void Initialize(LayerPropertyViewModel layerPropertyViewModel) - { - var type = layerPropertyViewModel.LayerProperty.Type; - if (type.IsEnum) - type = typeof(Enum); - if (Initialized) - throw new ArtemisUIException("Cannot initialize the same property input VM twice"); - if (!CompatibleTypes.Contains(type)) - throw new ArtemisUIException($"This input VM does not support the provided type {type.Name}"); - - LayerPropertyViewModel = layerPropertyViewModel; - LayerPropertyViewModel.LayerProperty.ValueChanged += LayerPropertyOnValueChanged; - Update(); - - Initialized = true; - - OnInitialized(); - } - - public abstract void Update(); - - protected virtual void OnInitialized() - { - } - - private void LayerPropertyOnValueChanged(object sender, EventArgs e) - { - Update(); - } - - private void UpdateInputValue(object value) - { - LayerPropertyViewModel.LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime); - // Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress - LayerPropertyViewModel.LayerProperty.KeyframeEngine?.Update(0); - - if (!InputDragging) - ProfileEditorService.UpdateSelectedProfileElement(); - else - ProfileEditorService.UpdateProfilePreview(); - } - - #region Event handlers - - public void InputDragStarted(object sender, EventArgs e) - { - InputDragging = true; - } - - public void InputDragEnded(object sender, EventArgs e) - { - InputDragging = false; - ProfileEditorService.UpdateSelectedProfileElement(); - } - - #endregion - - public virtual void Dispose() - { - if (LayerPropertyViewModel != null) - LayerPropertyViewModel.LayerProperty.ValueChanged -= LayerPropertyOnValueChanged; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml deleted file mode 100644 index 4f4fec459..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs deleted file mode 100644 index 3d32a21f2..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree -{ - public class PropertyTreeChildViewModel : PropertyTreeItemViewModel - { - public PropertyTreeChildViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel) - { - PropertyInputViewModel = layerPropertyViewModel.GetPropertyInputViewModel(); - } - - public PropertyInputViewModel PropertyInputViewModel { get; set; } - - public override void Update(bool forceUpdate) - { - if (forceUpdate) - PropertyInputViewModel?.Update(); - else - { - // Only update if visible and if keyframes are enabled - if (LayerPropertyViewModel.Parent.IsExpanded && LayerPropertyViewModel.KeyframesEnabled) - PropertyInputViewModel?.Update(); - } - } - - public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - } - - public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - } - - public override void Dispose() - { - PropertyInputViewModel?.Dispose(); - PropertyInputViewModel = null; - - base.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs deleted file mode 100644 index bae2fe5dd..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree -{ - public abstract class PropertyTreeItemViewModel : PropertyChangedBase, IDisposable - { - protected PropertyTreeItemViewModel(LayerPropertyViewModel layerPropertyViewModel) - { - LayerPropertyViewModel = layerPropertyViewModel; - } - - public LayerPropertyViewModel LayerPropertyViewModel { get; } - - /// - /// Updates the tree item's input if it is visible and has keyframes enabled - /// - /// Force update regardless of visibility and keyframes - public abstract void Update(bool forceUpdate); - - /// - /// Removes the layer property recursively - /// - /// - public abstract void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel); - - /// - /// Adds the layer property recursively - /// - /// - public abstract void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel); - - public virtual void Dispose() - { - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs deleted file mode 100644 index aa88002a8..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Linq; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree -{ - public class PropertyTreeParentViewModel : PropertyTreeItemViewModel - { - public PropertyTreeParentViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel) - { - Children = new BindableCollection(); - } - - public BindableCollection Children { get; set; } - - public override void Update(bool forceUpdate) - { - foreach (var child in Children) - child.Update(forceUpdate); - } - - // TODO: Change this to not add one by one, this raises far too many events - public override void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - if (layerPropertyViewModel.Parent == LayerPropertyViewModel) - { - lock (Children) - { - var index = layerPropertyViewModel.LayerProperty.Parent.Children.IndexOf(layerPropertyViewModel.LayerProperty); - if (index > Children.Count) - index = Children.Count; - if (layerPropertyViewModel.Children.Any()) - Children.Insert(index, new PropertyTreeParentViewModel(layerPropertyViewModel)); - else - Children.Insert(index, new PropertyTreeChildViewModel(layerPropertyViewModel)); - } - } - else - { - foreach (var propertyTreeItemViewModel in Children) - propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel); - } - } - - // TODO: Change this to not remove one by one, this raises far too many events - public override void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - foreach (var child in Children.ToList()) - { - if (child.LayerPropertyViewModel == layerPropertyViewModel) - { - Children.Remove(child); - child.Dispose(); - } - else - child.RemoveLayerProperty(layerPropertyViewModel); - } - } - - public override void Dispose() - { - foreach (var child in Children.ToList()) - child.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs deleted file mode 100644 index 54f28e915..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using Artemis.UI.Events; -using Artemis.UI.Services.Interfaces; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree -{ - public class PropertyTreeViewModel : PropertyChangedBase, IDisposable - { - private readonly IProfileEditorService _profileEditorService; - - public PropertyTreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService) - { - _profileEditorService = profileEditorService; - - LayerPropertiesViewModel = layerPropertiesViewModel; - PropertyTreeItemViewModels = new BindableCollection(); - - _profileEditorService.CurrentTimeChanged += OnCurrentTimeChanged; - _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; - } - - public LayerPropertiesViewModel LayerPropertiesViewModel { get; } - public BindableCollection PropertyTreeItemViewModels { get; set; } - - public void Dispose() - { - _profileEditorService.CurrentTimeChanged -= OnCurrentTimeChanged; - _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; - - foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels) - propertyTreeItemViewModel.Dispose(); - } - - // TODO: Change this to not add one by one, this raises far too many events - public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - // Add as a root VM - if (layerPropertyViewModel.Parent == null) - PropertyTreeItemViewModels.Add(new PropertyTreeParentViewModel(layerPropertyViewModel)); - // Add recursively to one of the child VMs - else - { - foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels) - propertyTreeItemViewModel.AddLayerProperty(layerPropertyViewModel); - } - } - - // TODO: Change this to not remove one by one, this raises far too many events - public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - // Remove a root VM - var rootVm = PropertyTreeItemViewModels.FirstOrDefault(vm => vm.LayerPropertyViewModel == layerPropertyViewModel); - if (rootVm != null) - PropertyTreeItemViewModels.Remove(rootVm); - // Remove recursively from one of the child VMs - else - { - foreach (var propertyTreeItemViewModel in PropertyTreeItemViewModels) - propertyTreeItemViewModel.RemoveLayerProperty(layerPropertyViewModel); - } - } - - /// - /// Updates the tree item's input if it is visible and has keyframes enabled - /// - /// Force update regardless of visibility and keyframes - public void Update(bool forceUpdate) - { - foreach (var viewModel in PropertyTreeItemViewModels) - viewModel.Update(forceUpdate); - } - - public void PropertyTreePreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - if (e.Handled || !(sender is TreeView)) - return; - - e.Handled = true; - var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) - { - RoutedEvent = UIElement.MouseWheelEvent, - Source = sender - }; - var parent = ((Control) sender).Parent as UIElement; - parent?.RaiseEvent(eventArg); - } - - private void OnCurrentTimeChanged(object sender, EventArgs e) - { - Update(false); - } - - private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e) - { - Update(true); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineHeader.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineHeader.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml deleted file mode 100644 index 239fafe15..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs deleted file mode 100644 index 655c0e007..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.UI.Events; -using Artemis.UI.Ninject.Factories; -using Artemis.UI.Services.Interfaces; -using Artemis.UI.Shared.Utilities; -using Artemis.UI.Utilities; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline -{ - public class PropertyTimelineViewModel : PropertyChangedBase, IDisposable - { - private readonly IProfileEditorService _profileEditorService; - private readonly IPropertyTrackVmFactory _propertyTrackVmFactory; - - public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, - IProfileEditorService profileEditorService, - IPropertyTrackVmFactory propertyTrackVmFactory) - { - _profileEditorService = profileEditorService; - _propertyTrackVmFactory = propertyTrackVmFactory; - - LayerPropertiesViewModel = layerPropertiesViewModel; - PropertyTrackViewModels = new BindableCollection(); - - _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; - LayerPropertiesViewModel.PixelsPerSecondChanged += OnPixelsPerSecondChanged; - - Execute.PostToUIThread(() => SelectionRectangle = new RectangleGeometry()); - } - - public LayerPropertiesViewModel LayerPropertiesViewModel { get; } - - public double Width { get; set; } - public BindableCollection PropertyTrackViewModels { get; set; } - public RectangleGeometry SelectionRectangle { get; set; } - - public void Dispose() - { - _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; - LayerPropertiesViewModel.PixelsPerSecondChanged -= OnPixelsPerSecondChanged; - } - - public void UpdateEndTime() - { - // End time is the last keyframe + 10 sec - var lastKeyFrame = PropertyTrackViewModels.SelectMany(r => r.KeyframeViewModels).OrderByDescending(t => t.Keyframe.Position).FirstOrDefault(); - var endTime = lastKeyFrame?.Keyframe.Position.Add(new TimeSpan(0, 0, 0, 10)) ?? TimeSpan.FromSeconds(10); - - Width = endTime.TotalSeconds * LayerPropertiesViewModel.PixelsPerSecond; - - // Ensure the caret isn't outside the end time - if (_profileEditorService.CurrentTime > endTime) - _profileEditorService.CurrentTime = endTime; - } - - public void PopulateProperties(List properties) - { - var newViewModels = new List(); - foreach (var property in properties) - newViewModels.AddRange(CreateViewModels(property)); - - PropertyTrackViewModels.Clear(); - PropertyTrackViewModels.AddRange(newViewModels); - UpdateEndTime(); - } - - public void AddLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - // Determine the index by flattening all the layer's properties - var index = layerPropertyViewModel.LayerProperty.GetFlattenedIndex(); - if (index > PropertyTrackViewModels.Count) - index = PropertyTrackViewModels.Count; - PropertyTrackViewModels.Insert(index, _propertyTrackVmFactory.Create(this, layerPropertyViewModel)); - } - - public void RemoveLayerProperty(LayerPropertyViewModel layerPropertyViewModel) - { - var vm = PropertyTrackViewModels.FirstOrDefault(v => v.LayerPropertyViewModel == layerPropertyViewModel); - if (vm != null) - PropertyTrackViewModels.Remove(vm); - } - - public void UpdateKeyframePositions() - { - foreach (var viewModel in PropertyTrackViewModels) - viewModel.UpdateKeyframes(LayerPropertiesViewModel.PixelsPerSecond); - - UpdateEndTime(); - } - - /// - /// Updates the time line's keyframes - /// - public void Update() - { - foreach (var viewModel in PropertyTrackViewModels) - viewModel.PopulateKeyframes(); - - UpdateEndTime(); - } - - private void OnSelectedProfileElementUpdated(object sender, ProfileElementEventArgs e) - { - Update(); - } - - private void OnPixelsPerSecondChanged(object sender, EventArgs e) - { - UpdateKeyframePositions(); - } - - private List CreateViewModels(LayerPropertyViewModel property) - { - var result = new List {_propertyTrackVmFactory.Create(this, property)}; - foreach (var child in property.Children) - result.AddRange(CreateViewModels(child)); - - return result; - } - - #region Keyframe movement - - public void MoveSelectedKeyframes(TimeSpan cursorTime) - { - // Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging - SelectionRectangle.Rect = new Rect(); - - var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList(); - foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) - keyframeViewModel.ApplyMovement(cursorTime); - - _profileEditorService.UpdateProfilePreview(); - } - - - public void ReleaseSelectedKeyframes() - { - var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList(); - foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) - keyframeViewModel.ReleaseMovement(); - } - - #endregion - - #region Keyframe selection - - private Point _mouseDragStartPoint; - private bool _mouseDragging; - - // ReSharper disable once UnusedMember.Global - Called from view - public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Released) - return; - - ((IInputElement) sender).CaptureMouse(); - - SelectionRectangle.Rect = new Rect(); - _mouseDragStartPoint = e.GetPosition((IInputElement) sender); - _mouseDragging = true; - e.Handled = true; - } - - // ReSharper disable once UnusedMember.Global - Called from view - public void TimelineCanvasMouseUp(object sender, MouseEventArgs e) - { - if (!_mouseDragging) - return; - - var position = e.GetPosition((IInputElement) sender); - var selectedRect = new Rect(_mouseDragStartPoint, position); - SelectionRectangle.Rect = selectedRect; - - var selectedKeyframes = HitTestUtilities.GetHitViewModels((Visual) sender, SelectionRectangle); - var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList(); - foreach (var keyframeViewModel in keyframeViewModels) - keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel); - - _mouseDragging = false; - e.Handled = true; - ((IInputElement) sender).ReleaseMouseCapture(); - } - - public void TimelineCanvasMouseMove(object sender, MouseEventArgs e) - { - if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed) - { - var position = e.GetPosition((IInputElement) sender); - var selectedRect = new Rect(_mouseDragStartPoint, position); - SelectionRectangle.Rect = selectedRect; - e.Handled = true; - } - } - - public void SelectKeyframe(PropertyTrackKeyframeViewModel clicked, bool selectBetween, bool toggle) - { - var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList(); - if (selectBetween) - { - var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected); - // If nothing is selected, select only the clicked - if (selectedIndex == -1) - { - clicked.IsSelected = true; - return; - } - - foreach (var keyframeViewModel in keyframeViewModels) - keyframeViewModel.IsSelected = false; - - var clickedIndex = keyframeViewModels.IndexOf(clicked); - if (clickedIndex < selectedIndex) - { - foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1)) - keyframeViewModel.IsSelected = true; - } - else - { - foreach (var keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1)) - keyframeViewModel.IsSelected = true; - } - } - else if (toggle) - { - // Toggle only the clicked keyframe, leave others alone - clicked.IsSelected = !clicked.IsSelected; - } - else - { - // Only select the clicked keyframe - foreach (var keyframeViewModel in keyframeViewModels) - keyframeViewModel.IsSelected = false; - clicked.IsSelected = true; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackEasingViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackEasingViewModel.cs deleted file mode 100644 index c1fcebd0e..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackEasingViewModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Windows; -using System.Windows.Media; -using Artemis.Core.Utilities; -using Humanizer; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline -{ - public class PropertyTrackEasingViewModel : PropertyChangedBase - { - private readonly PropertyTrackKeyframeViewModel _keyframeViewModel; - private bool _isEasingModeSelected; - - public PropertyTrackEasingViewModel(PropertyTrackKeyframeViewModel keyframeViewModel, Easings.Functions easingFunction) - { - _keyframeViewModel = keyframeViewModel; - _isEasingModeSelected = keyframeViewModel.Keyframe.EasingFunction == easingFunction; - - EasingFunction = easingFunction; - Description = easingFunction.Humanize(); - - CreateGeometry(); - } - - public Easings.Functions EasingFunction { get; } - public PointCollection EasingPoints { get; set; } - public string Description { get; set; } - - public bool IsEasingModeSelected - { - get => _isEasingModeSelected; - set - { - _isEasingModeSelected = value; - if (_isEasingModeSelected) - _keyframeViewModel.SelectEasingMode(this); - } - } - - private void CreateGeometry() - { - EasingPoints = new PointCollection(); - for (var i = 1; i <= 10; i++) - { - var x = i; - var y = Easings.Interpolate(i / 10.0, EasingFunction) * 10; - EasingPoints.Add(new Point(x, y)); - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs deleted file mode 100644 index a4f0d692c..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackKeyframeViewModel.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Input; -using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Utilities; -using Artemis.UI.Services.Interfaces; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline -{ - public class PropertyTrackKeyframeViewModel : PropertyTrackKeyframeViewModel - { - private readonly IProfileEditorService _profileEditorService; - private int _pixelsPerSecond; - - public PropertyTrackKeyframeViewModel(PropertyTrackViewModel propertyTrackViewModel, LayerPropertyKeyframe keyframe, IProfileEditorService profileEditorService) - { - _profileEditorService = profileEditorService; - - PropertyTrackViewModel = propertyTrackViewModel; - Keyframe = keyframe; - EasingViewModels = new BindableCollection(); - CreateEasingViewModels(); - } - - public bool IsSelected { get; set; } - public PropertyTrackViewModel PropertyTrackViewModel { get; } - public LayerPropertyKeyframe Keyframe { get; } - public BindableCollection EasingViewModels { get; set; } - public double X { get; set; } - public string Timestamp { get; set; } - - public UIElement ParentView { get; set; } - - - public void Update(int pixelsPerSecond) - { - _pixelsPerSecond = pixelsPerSecond; - - X = pixelsPerSecond * Keyframe.Position.TotalSeconds; - Timestamp = $"{Math.Floor(Keyframe.Position.TotalSeconds):00}.{Keyframe.Position.Milliseconds:000}"; - } - - #region Keyframe movement - - public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Released) - return; - - ((IInputElement) sender).CaptureMouse(); - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected) - PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, true, false); - else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, true); - else if (!IsSelected) - PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, false, false); - - e.Handled = true; - } - - public void KeyframeMouseUp(object sender, MouseButtonEventArgs e) - { - _profileEditorService.UpdateSelectedProfileElement(); - PropertyTrackViewModel.PropertyTimelineViewModel.ReleaseSelectedKeyframes(); - - ((IInputElement) sender).ReleaseMouseCapture(); - } - - public void KeyframeMouseMove(object sender, MouseEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView))); - - e.Handled = true; - } - - private TimeSpan GetCursorTime(Point position) - { - // Get the parent grid, need that for our position - var x = Math.Max(0, position.X); - var time = TimeSpan.FromSeconds(x / _pixelsPerSecond); - - // Round the time to something that fits the current zoom level - if (_pixelsPerSecond < 200) - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0); - else if (_pixelsPerSecond < 500) - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0); - else - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds)); - - // If shift is held, snap to the current time - // Take a tolerance of 5 pixels (half a keyframe width) - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { - var tolerance = 1000f / _pixelsPerSecond * 5; - if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance) - time = _profileEditorService.CurrentTime; - } - - return time; - } - - #endregion - - #region Context menu actions - - public void Copy() - { - var keyframe = PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(Keyframe.Position, Keyframe.BaseValue); - keyframe.EasingFunction = Keyframe.EasingFunction; - _profileEditorService.UpdateSelectedProfileElement(); - } - - public void Delete() - { - PropertyTrackViewModel.LayerPropertyViewModel.LayerProperty.RemoveKeyframe(Keyframe); - _profileEditorService.UpdateSelectedProfileElement(); - } - - #endregion - - #region Easing - - private void CreateEasingViewModels() - { - EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new PropertyTrackEasingViewModel(this, v))); - } - - public void SelectEasingMode(PropertyTrackEasingViewModel easingViewModel) - { - Keyframe.EasingFunction = easingViewModel.EasingFunction; - // Set every selection to false except on the VM that made the change - foreach (var propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel)) - propertyTrackEasingViewModel.IsEasingModeSelected = false; - - _profileEditorService.UpdateSelectedProfileElement(); - } - - #endregion - - #region Movement - - private bool _movementReleased = true; - private TimeSpan _startOffset; - - public void ApplyMovement(TimeSpan cursorTime) - { - if (_movementReleased) - { - _movementReleased = false; - _startOffset = cursorTime - Keyframe.Position; - } - else - { - Keyframe.Position = cursorTime - _startOffset; - if (Keyframe.Position < TimeSpan.Zero) - Keyframe.Position = TimeSpan.Zero; - - Update(_pixelsPerSecond); - } - } - - public void ReleaseMovement() - { - _movementReleased = true; - } - - #endregion - } - - public abstract class PropertyTrackKeyframeViewModel : PropertyChangedBase - { - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackView.xaml deleted file mode 100644 index 497e47dff..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackView.xaml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs deleted file mode 100644 index 5c62a5cb5..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Artemis.UI.Ninject.Factories; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline -{ - public class PropertyTrackViewModel : Screen - { - private readonly IPropertyTrackKeyframeVmFactory _propertyTrackKeyframeVmFactory; - - public PropertyTrackViewModel(PropertyTimelineViewModel propertyTimelineViewModel, LayerPropertyViewModel layerPropertyViewModel, - IPropertyTrackKeyframeVmFactory propertyTrackKeyframeVmFactory) - { - _propertyTrackKeyframeVmFactory = propertyTrackKeyframeVmFactory; - PropertyTimelineViewModel = propertyTimelineViewModel; - LayerPropertyViewModel = layerPropertyViewModel; - KeyframeViewModels = new BindableCollection(); - - PopulateKeyframes(); - UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond); - - LayerPropertyViewModel.ExpandedStateChanged += (sender, args) => UpdateMustDisplay(); - LayerPropertyViewModel.LayerProperty.VisibilityChanged += (sender, args) => UpdateMustDisplay(); - UpdateMustDisplay(); - } - - - public PropertyTimelineViewModel PropertyTimelineViewModel { get; } - public LayerPropertyViewModel LayerPropertyViewModel { get; } - public BindableCollection KeyframeViewModels { get; set; } - public bool MustDisplay { get; set; } - - private void UpdateMustDisplay() - { - var expandedTest = LayerPropertyViewModel.Parent; - while (expandedTest != null) - { - if (!expandedTest.IsExpanded) - { - MustDisplay = false; - return; - } - expandedTest = expandedTest.Parent; - } - - var visibilityTest = LayerPropertyViewModel.LayerProperty; - while (visibilityTest != null) - { - if (visibilityTest.IsHidden) - { - MustDisplay = false; - return; - } - visibilityTest = visibilityTest.Parent; - } - - MustDisplay = true; - } - - public void PopulateKeyframes() - { - // Remove old keyframes - KeyframeViewModels.RemoveRange(KeyframeViewModels.ToList().Where(vm => !LayerPropertyViewModel.LayerProperty.UntypedKeyframes.Contains(vm.Keyframe))); - - // Add new keyframes - KeyframeViewModels.AddRange( - LayerPropertyViewModel.LayerProperty.UntypedKeyframes - .Where(k => KeyframeViewModels.All(vm => vm.Keyframe != k)) - .Select(k => _propertyTrackKeyframeVmFactory.Create(this, k)) - ); - UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond); - } - - public void UpdateKeyframes(int pixelsPerSecond) - { - foreach (var keyframeViewModel in KeyframeViewModels) - { - keyframeViewModel.ParentView = View; - keyframeViewModel.Update(pixelsPerSecond); - } - } - - protected override void OnViewLoaded() - { - foreach (var keyframeViewModel in KeyframeViewModels) - keyframeViewModel.ParentView = View; - base.OnViewLoaded(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs new file mode 100644 index 000000000..69994652f --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs @@ -0,0 +1,14 @@ +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline +{ + public class TimelinePropertyGroupViewModel + { + public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) + { + LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel; + } + + public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs new file mode 100644 index 000000000..b675b107d --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -0,0 +1,30 @@ +using System; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline +{ + public class TimelinePropertyViewModel : TimelinePropertyViewModel + { + public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel) + { + LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; + } + + public LayerPropertyViewModel LayerPropertyViewModel { get; } + + public override void Dispose() + { + } + } + + public abstract class TimelinePropertyViewModel : IDisposable + { + protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) + { + LayerPropertyBaseViewModel = layerPropertyBaseViewModel; + } + + public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } + public abstract void Dispose(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs new file mode 100644 index 000000000..865d49a00 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -0,0 +1,14 @@ +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline +{ + public class TimelineViewModel + { + public TimelineViewModel(BindableCollection layerPropertyGroups) + { + LayerPropertyGroups = layerPropertyGroups; + } + + public BindableCollection LayerPropertyGroups { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/BrushPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs similarity index 89% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/BrushPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs index 79c472350..2eb194bb1 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs @@ -5,18 +5,20 @@ using Artemis.Core.Events; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Utilities; using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput { - public class BrushPropertyInputViewModel : PropertyInputViewModel + public class BrushPropertyInputViewModel : PropertyInputViewModel { private readonly ILayerService _layerService; private readonly IPluginService _pluginService; - public BrushPropertyInputViewModel(IProfileEditorService profileEditorService, ILayerService layerService, IPluginService pluginService) : base(profileEditorService) + public BrushPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, ILayerService layerService, IPluginService pluginService) + : base(layerPropertyViewModel) { _layerService = layerService; _pluginService = pluginService; @@ -26,9 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P } public BindableCollection EnumValues { get; } - - public sealed override List CompatibleTypes { get; } = new List {typeof(LayerBrushReference)}; - + public LayerBrushReference BrushInputValue { get => (LayerBrushReference) InputValue; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/EnumPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/EnumPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs similarity index 76% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/EnumPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs index f1e1d7932..2134ab5ea 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/EnumPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs @@ -18,7 +18,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P public object EnumInputValue { - get => InputValue ?? Enum.GetValues(LayerPropertyViewModel.LayerProperty.Type).Cast().First(); + get => InputValue ?? Enum.GetValues(GetLayerPropertyEnumType()).Cast().First(); set => InputValue = value; } @@ -29,7 +29,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P protected override void OnInitialized() { - EnumValues = EnumUtilities.GetAllValuesAndDescriptions(LayerPropertyViewModel.LayerProperty.Type); + EnumValues = EnumUtilities.GetAllValuesAndDescriptions(GetLayerPropertyEnumType()); + } + + + private Type GetLayerPropertyEnumType() + { + return LayerPropertyViewModel.BaseLayerProperty.GetType().GetGenericTypeDefinition(); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs new file mode 100644 index 000000000..b24885717 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs @@ -0,0 +1,51 @@ +using System; +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput +{ + public abstract class PropertyInputViewModel : PropertyChangedBase, IDisposable + { + protected PropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) + { + LayerPropertyViewModel = layerPropertyViewModel; + LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated; + } + + public LayerPropertyViewModel LayerPropertyViewModel { get; } + + public bool InputDragging { get; private set; } + + protected T InputValue + { + get => LayerPropertyViewModel.LayerProperty.CurrentValue; + set => LayerPropertyViewModel.SetCurrentValue(value, !InputDragging); + } + + public abstract void Update(); + + private void LayerPropertyOnUpdated(object? sender, EventArgs e) + { + Update(); + } + + #region Event handlers + + public void InputDragStarted(object sender, EventArgs e) + { + InputDragging = true; + } + + public void InputDragEnded(object sender, EventArgs e) + { + InputDragging = false; + LayerPropertyViewModel.ProfileEditorService.UpdateSelectedProfileElement(); + } + + #endregion + + public void Dispose() + { + LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs similarity index 100% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs new file mode 100644 index 000000000..fcf625ceb --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs @@ -0,0 +1,14 @@ +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree +{ + public class TreePropertyGroupViewModel + { + public TreePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) + { + LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel)layerPropertyBaseViewModel; + } + + public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs new file mode 100644 index 000000000..fc791154a --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Artemis.Core.Models.Profile; +using Artemis.UI.Exceptions; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree +{ + public class TreePropertyViewModel : TreePropertyViewModel + { + public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel) + { + LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; + PropertyInputViewModel = CreatePropertyInputViewModel(); + } + + public LayerPropertyViewModel LayerPropertyViewModel { get; } + public PropertyInputViewModel PropertyInputViewModel { get; set; } + + public override void Dispose() + { + PropertyInputViewModel.Dispose(); + } + + private PropertyInputViewModel CreatePropertyInputViewModel() + { + if (!IsPropertySupported(typeof(T))) + throw new ArtemisUIException($"Failed to create a property input view model, type {typeof(T).Name} is not supported."); + + return (PropertyInputViewModel) Activator.CreateInstance(SupportedTypes[typeof(T)], this); + } + } + + public abstract class TreePropertyViewModel : IDisposable + { + public static ReadOnlyDictionary SupportedTypes = new ReadOnlyDictionary(new Dictionary + { + {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, + {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, + {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, + {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)} + }); + + public static void RegisterPropertyInputViewModel() + { + + } + + public static bool IsPropertySupported(Type type) + { + return SupportedTypes.ContainsKey(type); + } + + protected TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) + { + LayerPropertyBaseViewModel = layerPropertyBaseViewModel; + } + + public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } + public abstract void Dispose(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml similarity index 78% rename from src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeView.xaml rename to src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml index cfb33f63b..41bb9c831 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -1,14 +1,16 @@ - + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance {x:Type local:TreeViewModel}}"> - - - + - - - - + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs new file mode 100644 index 000000000..14723722e --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs @@ -0,0 +1,14 @@ +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree +{ + public class TreeViewModel + { + public TreeViewModel(BindableCollection layerPropertyGroups) + { + LayerPropertyGroups = layerPropertyGroups; + } + + public BindableCollection LayerPropertyGroups { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml index 7bc5e82cc..cfb383c9a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -25,7 +25,7 @@ - + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index b023d3cad..7138ecdc9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -122,9 +122,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem if (!SupportsChildren) throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); - var layer = new Layer(ProfileElement.Profile, ProfileElement, "New layer"); - foreach (var baseLayerProperty in layer.Properties) - _layerService.InstantiateKeyframeEngine(baseLayerProperty); + var layer = _layerService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer"); ProfileElement.AddChild(layer); UpdateProfileElements(); _profileEditorService.UpdateSelectedProfile(); @@ -174,7 +172,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem } // Ensure every child element has an up-to-date VM - if (ProfileElement.Children == null) + if (ProfileElement.Children == null) return; var newChildren = new List(); @@ -192,7 +190,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem } } - if (!newChildren.Any()) + if (!newChildren.Any()) return; // Add the new children in one call, prevent extra UI events diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs index 2994814ce..66eaaec3e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs @@ -94,15 +94,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools difference += 360; else if (difference > 350) difference -= 360; - newRotation = layer.Properties.Rotation.CurrentValue + difference; + + newRotation = layer.Transform.Rotation.CurrentValue + difference; // Round the end-result to increments of 5 as well, to avoid staying on an offset if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) newRotation = (float) Math.Round(newRotation / 5f) * 5f; else newRotation = (float) Math.Round(newRotation, 2, MidpointRounding.AwayFromZero); - - layer.Properties.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime); + layer.Transform.Rotation.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime); ProfileEditorService.UpdateProfilePreview(); } @@ -121,7 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint(); _dragOffset = _layerEditorService.GetDragOffset(layer, dragStart); _dragStart = dragStart + _dragOffset; - _dragStartScale = layer.Properties.Scale.CurrentValue; + _dragStartScale = layer.Transform.Scale.CurrentValue; _isResizing = true; } @@ -159,19 +159,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools break; case ShapeControlPoint.TopCenter: height = VerticalResize(layer, position, ResizeOrigin.Top); - width = layer.Properties.Scale.CurrentValue.Width; + width = layer.Transform.Scale.CurrentValue.Width; break; case ShapeControlPoint.RightCenter: width = HorizontalResize(layer, position, ResizeOrigin.Right); - height = layer.Properties.Scale.CurrentValue.Height; + height = layer.Transform.Scale.CurrentValue.Height; break; case ShapeControlPoint.BottomCenter: - width = layer.Properties.Scale.CurrentValue.Width; + width = layer.Transform.Scale.CurrentValue.Width; height = VerticalResize(layer, position, ResizeOrigin.Bottom); break; case ShapeControlPoint.LeftCenter: width = HorizontalResize(layer, position, ResizeOrigin.Left); - height = layer.Properties.Scale.CurrentValue.Height; + height = layer.Transform.Scale.CurrentValue.Height; break; default: throw new ArgumentOutOfRangeException(); @@ -186,7 +186,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools height = (float) Math.Round(1.0 / bounds.Height * smallestSide, 2, MidpointRounding.AwayFromZero); } - layer.Properties.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); + layer.Transform.Scale.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); ProfileEditorService.UpdateProfilePreview(); } @@ -319,7 +319,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools // Scale down the resulting position and make it relative var scaled = _layerEditorService.GetScaledPoint(layer, position, true); // Round and update the position property - layer.Properties.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime); + layer.Transform.Position.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime); ProfileEditorService.UpdateProfilePreview(); } @@ -338,13 +338,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false); // Update the anchor point, this causes the shape to move - layer.Properties.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime); + layer.Transform.AnchorPoint.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime); // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor var path = _layerEditorService.GetLayerPath(layer, true, true, true); // Calculate the (scaled) difference between the old and now position var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false); // Apply the difference so that the shape effectively stays in place - layer.Properties.Position.SetCurrentValue(RoundPoint(layer.Properties.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime); + layer.Transform.Position.SetCurrentValue(RoundPoint(layer.Transform.Position.CurrentValue + difference, 3), ProfileEditorService.CurrentTime); ProfileEditorService.UpdateProfilePreview(); } @@ -362,9 +362,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { var counterRotatePath = new SKPath(); counterRotatePath.AddPoly(skPoints, false); - counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue * -1, pivot.X, pivot.Y)); + counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y)); if (includeScale) - counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Properties.Scale.CurrentValue.Width / 100f), 1f / (layer.Properties.Scale.CurrentValue.Height / 100f))); + counterRotatePath.Transform(SKMatrix.MakeScale(1f / (layer.Transform.Scale.CurrentValue.Width / 100f), 1f / (layer.Transform.Scale.CurrentValue.Height / 100f))); return counterRotatePath.Points; } diff --git a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs index 453979079..585276011 100644 --- a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs @@ -1,7 +1,10 @@ using System; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Events; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; namespace Artemis.UI.Services.Interfaces { @@ -11,6 +14,7 @@ namespace Artemis.UI.Services.Interfaces ProfileElement SelectedProfileElement { get; } TimeSpan CurrentTime { get; set; } + LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription); void ChangeSelectedProfile(Profile profile); void UpdateSelectedProfile(); void ChangeSelectedProfileElement(ProfileElement profileElement); @@ -18,6 +22,8 @@ namespace Artemis.UI.Services.Interfaces void UpdateProfilePreview(); void UndoUpdateProfile(ProfileModule module); void RedoUpdateProfile(ProfileModule module); + void StopRegularRender(); + void ResumeRegularRender(); /// /// Occurs when a new profile is selected @@ -48,8 +54,5 @@ namespace Artemis.UI.Services.Interfaces /// Occurs when the profile preview has been updated /// event EventHandler ProfilePreviewUpdated; - - void StopRegularRender(); - void ResumeRegularRender(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/LayerEditorService.cs b/src/Artemis.UI/Services/LayerEditorService.cs index 83c654db3..57a405fd5 100644 --- a/src/Artemis.UI/Services/LayerEditorService.cs +++ b/src/Artemis.UI/Services/LayerEditorService.cs @@ -35,7 +35,7 @@ namespace Artemis.UI.Services public Point GetLayerAnchorPosition(Layer layer, SKPoint? positionOverride = null) { var layerBounds = GetLayerBounds(layer).ToSKRect(); - var positionProperty = layer.Properties.Position.CurrentValue; + var positionProperty = layer.Transform.Position.CurrentValue; if (positionOverride != null) positionProperty = positionOverride.Value; @@ -59,7 +59,7 @@ namespace Artemis.UI.Services // the layer using the structure of the XAML while the Core has to deal with that by applying the layer // position to the translation var anchorPosition = GetLayerAnchorPosition(layer); - var anchorProperty = layer.Properties.AnchorPoint.CurrentValue; + var anchorProperty = layer.Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width; @@ -67,8 +67,8 @@ namespace Artemis.UI.Services var transformGroup = new TransformGroup(); transformGroup.Children.Add(new TranslateTransform(x, y)); - transformGroup.Children.Add(new ScaleTransform(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); - transformGroup.Children.Add(new RotateTransform(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); + transformGroup.Children.Add(new ScaleTransform(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); + transformGroup.Children.Add(new RotateTransform(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); return transformGroup; } @@ -83,7 +83,7 @@ namespace Artemis.UI.Services if (anchorOverride != null) anchorPosition = anchorOverride.Value; - var anchorProperty = layer.Properties.AnchorPoint.CurrentValue; + var anchorProperty = layer.Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor var x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width; @@ -94,9 +94,9 @@ namespace Artemis.UI.Services if (includeTranslation) path.Transform(SKMatrix.MakeTranslation(x, y)); if (includeScale) - path.Transform(SKMatrix.MakeScale(layer.Properties.Scale.CurrentValue.Width / 100f, layer.Properties.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y)); if (includeRotation) - path.Transform(SKMatrix.MakeRotationDegrees(layer.Properties.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y)); return path; } diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index 19c497b38..fe3e7a871 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -1,11 +1,20 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Events; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; using Artemis.UI.Services.Interfaces; +using Ninject; +using Ninject.Parameters; namespace Artemis.UI.Services { @@ -13,15 +22,21 @@ namespace Artemis.UI.Services { private readonly ICoreService _coreService; private readonly IProfileService _profileService; + private readonly IKernel _kernel; + private readonly List _registeredProfileEditors; private TimeSpan _currentTime; private TimeSpan _lastUpdateTime; - public ProfileEditorService(ICoreService coreService, IProfileService profileService) + public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel) { _coreService = coreService; _profileService = profileService; + _kernel = kernel; + _registeredProfileEditors = new List(); } + public ReadOnlyCollection RegisteredPropertyEditors => _registeredProfileEditors.AsReadOnly(); + public Profile SelectedProfile { get; private set; } public ProfileElement SelectedProfileElement { get; private set; } @@ -38,6 +53,23 @@ namespace Artemis.UI.Services } } + public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription) + { + // 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().GetGenericArguments()[0]; + // Only create entries for types supported by a tree input VM + if (!TreePropertyViewModel.IsPropertySupported(genericType)) + return null; + var genericViewModel = typeof(LayerPropertyViewModel<>).MakeGenericType(genericType); + var parameters = new IParameter[] + { + new ConstructorArgument("layerProperty", baseLayerProperty), + new ConstructorArgument("propertyDescription", propertyDescription) + }; + + return (LayerPropertyBaseViewModel) _kernel.Get(genericViewModel, parameters); + } + public void ChangeSelectedProfile(Profile profile) { ChangeSelectedProfileElement(null); diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs index c76fd5bbe..0dca4b77e 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs @@ -35,7 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Color GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged; } - private void GradientTypeOnBaseValueChanged(object? sender, EventArgs e) + private void GradientTypeOnBaseValueChanged(object sender, EventArgs e) { Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid; Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid; diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs index 89f5f6790..d01cfa509 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs @@ -57,7 +57,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged; } - private void ColorTypeOnBaseValueChanged(object? sender, EventArgs e) + private void ColorTypeOnBaseValueChanged(object sender, EventArgs e) { GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient; MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple; From e036cbbfe4e5915bb06ee25fd4e17681947a4ab5 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 16 May 2020 22:56:12 +0200 Subject: [PATCH 09/14] Layer properties - Updated property input views and VMs --- .../Ninject/Factories/IVMFactory.cs | 4 - src/Artemis.UI/Ninject/UiModule.cs | 12 +-- .../Abstract/PropertyInputViewModel.cs | 93 +++++++++++++++++++ .../PropertyInput/BrushPropertyInputView.xaml | 14 +-- .../BrushPropertyInputViewModel.cs | 45 +++------ .../ColorGradientPropertyInputView.xaml | 14 ++- .../ColorGradientPropertyInputViewModel.cs | 26 +----- .../PropertyInput/EnumPropertyInputView.xaml | 12 +-- .../EnumPropertyInputViewModel.cs | 38 ++------ .../PropertyInput/FloatPropertyInputView.xaml | 15 +-- .../FloatPropertyInputViewModel.cs | 45 ++++----- .../PropertyInput/IntPropertyInputView.xaml | 19 ++-- .../IntPropertyInputViewModel.cs | 44 ++++----- .../PropertyInput/PropertyInputViewModel.cs | 51 ---------- .../SKColorPropertyInputView.xaml | 13 +-- .../SKColorPropertyInputViewModel.cs | 23 +---- .../SKPointPropertyInputView.xaml | 15 +-- .../SKPointPropertyInputViewModel.cs | 52 ++++++----- .../SKSizePropertyInputView.xaml | 12 +-- .../SKSizePropertyInputViewModel.cs | 54 ++++++----- .../Tree/TreePropertyViewModel.cs | 41 ++------ .../Interfaces/IProfileEditorService.cs | 3 + 22 files changed, 283 insertions(+), 362 deletions(-) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/Abstract/PropertyInputViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 5e96cd8b1..1da8ef7dc 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -1,12 +1,8 @@ using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Surface; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Screens.Module; using Artemis.UI.Screens.Module.ProfileEditor; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem; using Artemis.UI.Screens.Module.ProfileEditor.Visualization; using Artemis.UI.Screens.Settings.Tabs.Devices; diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 6f9550f7b..6280c051a 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -2,7 +2,6 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens; using Artemis.UI.Screens.Module.ProfileEditor; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Services.Dialog; using Artemis.UI.Stylet; @@ -57,16 +56,7 @@ namespace Artemis.UI.Ninject .InheritedFrom() .BindAllBaseClasses(); }); - - // Bind property input VMs - Kernel.Bind(x => - { - x.FromThisAssembly() - .SelectAllClasses() - .InheritedFrom() - .BindAllBaseClasses(); - }); - + // Bind all UI services as singletons Kernel.Bind(x => { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/Abstract/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/Abstract/PropertyInputViewModel.cs new file mode 100644 index 000000000..9a529a886 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/Abstract/PropertyInputViewModel.cs @@ -0,0 +1,93 @@ +using System; +using Stylet; + +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract +{ + public abstract class PropertyInputViewModel : ValidatingModelBase, IDisposable + { + private T _inputValue; + + protected PropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) + { + LayerPropertyViewModel = layerPropertyViewModel; + LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated; + } + + protected PropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, IModelValidator validator) : base(validator) + { + LayerPropertyViewModel = layerPropertyViewModel; + LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated; + } + + public LayerPropertyViewModel LayerPropertyViewModel { get; } + + public bool InputDragging { get; private set; } + + public T InputValue + { + get => _inputValue; + set + { + _inputValue = value; + ApplyInputValue(); + } + } + + public virtual void Dispose() + { + LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated; + } + + protected virtual void OnInputValueChanged() + { + } + + private void LayerPropertyOnUpdated(object sender, EventArgs e) + { + UpdateInputValue(); + } + + private void UpdateInputValue() + { + // Avoid unnecessary UI updates and validator cycles + if (_inputValue.Equals(LayerPropertyViewModel.LayerProperty.CurrentValue)) + return; + + // Override the input value + _inputValue = LayerPropertyViewModel.LayerProperty.CurrentValue; + + // Notify a change in the input value + OnInputValueChanged(); + NotifyOfPropertyChange(nameof(InputValue)); + + // Force the validator to run with the overridden value + Validate(); + } + + private void ApplyInputValue() + { + // Force the validator to run + Validate(); + // Only apply the input value to the layer property if the validator found no errors + if (!HasErrors) + LayerPropertyViewModel.SetCurrentValue(_inputValue, !InputDragging); + + OnInputValueChanged(); + } + + #region Event handlers + + public void InputDragStarted(object sender, EventArgs e) + { + InputDragging = true; + } + + public void InputDragEnded(object sender, EventArgs e) + { + InputDragging = false; + LayerPropertyViewModel.ProfileEditorService.UpdateSelectedProfileElement(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml index 143a229bf..144bc1a21 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputView.xaml @@ -1,23 +1,25 @@ - + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance {x:Type propertyInput:BrushPropertyInputViewModel}}"> - + - + SelectedValue="{Binding Path=InputValue}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs index 2eb194bb1..63284ebcf 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/BrushPropertyInputViewModel.cs @@ -1,16 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Artemis.Core.Events; using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Services.Interfaces; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; using Artemis.UI.Shared.Utilities; using Stylet; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { public class BrushPropertyInputViewModel : PropertyInputViewModel { @@ -22,59 +20,40 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P { _layerService = layerService; _pluginService = pluginService; - EnumValues = new BindableCollection(); + ComboboxValues = new BindableCollection(); _pluginService.PluginLoaded += PluginServiceOnPluginLoaded; + UpdateEnumValues(); } - public BindableCollection EnumValues { get; } + public BindableCollection ComboboxValues { get; } - public LayerBrushReference BrushInputValue - { - get => (LayerBrushReference) InputValue; - set - { - InputValue = value; - _layerService.InstantiateLayerBrush(LayerPropertyViewModel.LayerProperty.Layer); - } - } - public void UpdateEnumValues() { var layerBrushProviders = _pluginService.GetPluginsOfType(); var descriptors = layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors).ToList(); - + var enumValues = new List(); foreach (var layerBrushDescriptor in descriptors) { var brushName = layerBrushDescriptor.LayerBrushType.Name; var brushGuid = layerBrushDescriptor.LayerBrushProvider.PluginInfo.Guid; - if (BrushInputValue != null && BrushInputValue.BrushType == brushName && BrushInputValue.BrushPluginGuid == brushGuid) - enumValues.Add(new ValueDescription {Description = layerBrushDescriptor.DisplayName, Value = BrushInputValue}); + if (InputValue != null && InputValue.BrushType == brushName && InputValue.BrushPluginGuid == brushGuid) + enumValues.Add(new ValueDescription {Description = layerBrushDescriptor.DisplayName, Value = InputValue}); else enumValues.Add(new ValueDescription {Description = layerBrushDescriptor.DisplayName, Value = new LayerBrushReference {BrushType = brushName, BrushPluginGuid = brushGuid}}); } - EnumValues.Clear(); - EnumValues.AddRange(enumValues); - } - public override void Update() - { - NotifyOfPropertyChange(() => BrushInputValue); + ComboboxValues.Clear(); + ComboboxValues.AddRange(enumValues); } - + public override void Dispose() { _pluginService.PluginLoaded -= PluginServiceOnPluginLoaded; base.Dispose(); } - protected override void OnInitialized() - { - UpdateEnumValues(); - base.OnInitialized(); - } - private void PluginServiceOnPluginLoaded(object sender, PluginEventArgs e) { UpdateEnumValues(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml index c871a6cbe..981433336 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputView.xaml @@ -1,22 +1,20 @@ - + d:DataContext="{d:DesignInstance propertyInput:ColorGradientPropertyInputViewModel}"> - + - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs index beeb353c7..e20bbd7b2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/ColorGradientPropertyInputViewModel.cs @@ -1,28 +1,12 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Profile.Colors; -using Artemis.UI.Services.Interfaces; +using Artemis.Core.Models.Profile.Colors; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class ColorGradientPropertyInputViewModel : PropertyInputViewModel + public class ColorGradientPropertyInputViewModel : PropertyInputViewModel { - public ColorGradientPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public ColorGradientPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel) { } - - public sealed override List CompatibleTypes { get; } = new List {typeof(ColorGradient)}; - - public ColorGradient ColorGradientInputValue - { - get => (ColorGradient) InputValue ?? new ColorGradient(); - set => InputValue = value; - } - - public override void Update() - { - NotifyOfPropertyChange(() => ColorGradientInputValue); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml index 7426a53d8..1d5d36fe4 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputView.xaml @@ -1,15 +1,13 @@ - + d:DesignHeight="450" d:DesignWidth="800"> - + - + SelectedValue="{Binding Path=InputValue}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs index 2134ab5ea..57ccfcd44 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/EnumPropertyInputViewModel.cs @@ -1,41 +1,17 @@ using System; using System.Collections.Generic; -using System.Linq; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; using Artemis.UI.Shared.Utilities; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class EnumPropertyInputViewModel : PropertyInputViewModel + public class EnumPropertyInputViewModel : PropertyInputViewModel where T : Enum { - public EnumPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public EnumPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel) { + EnumValues = EnumUtilities.GetAllValuesAndDescriptions(typeof(T)); } - - public IEnumerable EnumValues { get; private set; } - - public sealed override List CompatibleTypes { get; } = new List {typeof(Enum)}; - - public object EnumInputValue - { - get => InputValue ?? Enum.GetValues(GetLayerPropertyEnumType()).Cast().First(); - set => InputValue = value; - } - - public override void Update() - { - NotifyOfPropertyChange(() => EnumInputValue); - } - - protected override void OnInitialized() - { - EnumValues = EnumUtilities.GetAllValuesAndDescriptions(GetLayerPropertyEnumType()); - } - - - private Type GetLayerPropertyEnumType() - { - return LayerPropertyViewModel.BaseLayerProperty.GetType().GetGenericTypeDefinition(); - } + + public IEnumerable EnumValues { get; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml index 3a718dfcb..6f9807ade 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputView.xaml @@ -1,21 +1,22 @@ - + d:DataContext="{d:DesignInstance propertyInput:FloatPropertyInputViewModel}"> - - + - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs index 7fabc6bb2..55954397d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/FloatPropertyInputViewModel.cs @@ -1,39 +1,28 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; +using FluentValidation; +using Stylet; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class FloatPropertyInputViewModel : PropertyInputViewModel + public class FloatPropertyInputViewModel : PropertyInputViewModel { - public FloatPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public FloatPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, IModelValidator validator) + : base(layerPropertyViewModel, validator) { } + } - public sealed override List CompatibleTypes { get; } = new List {typeof(float)}; - - - public float FloatInputValue + public class FloatPropertyInputViewModelValidator : AbstractValidator + { + public FloatPropertyInputViewModelValidator() { - get => (float?) InputValue ?? 0f; - set => InputValue = ApplyInputValue(value); - } + RuleFor(vm => vm.InputValue) + .LessThanOrEqualTo(vm => (float) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is float); - public override void Update() - { - NotifyOfPropertyChange(() => FloatInputValue); - } - - private float ApplyInputValue(float value) - { - if (LayerPropertyViewModel.LayerProperty.MaxInputValue != null && - LayerPropertyViewModel.LayerProperty.MaxInputValue is float maxFloat) - value = Math.Min(value, maxFloat); - if (LayerPropertyViewModel.LayerProperty.MinInputValue != null && - LayerPropertyViewModel.LayerProperty.MinInputValue is float minFloat) - value = Math.Max(value, minFloat); - - return value; + RuleFor(vm => vm.InputValue) + .GreaterThanOrEqualTo(vm => (float) vm.LayerPropertyViewModel.PropertyDescription.MinInputValue) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MinInputValue is float); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml index cf2997f4a..25115253d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputView.xaml @@ -1,21 +1,22 @@ - + d:DataContext="{d:DesignInstance propertyInput:IntPropertyInputViewModel}"> - - - + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs index ff2c45302..adce93b8b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/IntPropertyInputViewModel.cs @@ -1,38 +1,28 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; +using FluentValidation; +using Stylet; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class IntPropertyInputViewModel : PropertyInputViewModel + public class IntPropertyInputViewModel : PropertyInputViewModel { - public IntPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public IntPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, IModelValidator validator) + : base(layerPropertyViewModel, validator) { } + } - public sealed override List CompatibleTypes { get; } = new List {typeof(int)}; - - public int IntInputValue + public class IntPropertyInputViewModelValidator : AbstractValidator + { + public IntPropertyInputViewModelValidator() { - get => (int?) InputValue ?? 0; - set => InputValue = ApplyInputValue(value); - } + RuleFor(vm => vm.InputValue) + .LessThanOrEqualTo(vm => (int) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is int); - public override void Update() - { - NotifyOfPropertyChange(() => IntInputValue); - } - - private int ApplyInputValue(int value) - { - if (LayerPropertyViewModel.LayerProperty.MaxInputValue != null && - LayerPropertyViewModel.LayerProperty.MaxInputValue is int maxInt) - value = Math.Min(value, maxInt); - if (LayerPropertyViewModel.LayerProperty.MinInputValue != null && - LayerPropertyViewModel.LayerProperty.MinInputValue is int minInt) - value = Math.Max(value, minInt); - - return value; + RuleFor(vm => vm.InputValue) + .GreaterThanOrEqualTo(vm => (int) vm.LayerPropertyViewModel.PropertyDescription.MinInputValue) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MinInputValue is int); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs deleted file mode 100644 index b24885717..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/PropertyInputViewModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Stylet; - -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput -{ - public abstract class PropertyInputViewModel : PropertyChangedBase, IDisposable - { - protected PropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) - { - LayerPropertyViewModel = layerPropertyViewModel; - LayerPropertyViewModel.LayerProperty.Updated += LayerPropertyOnUpdated; - } - - public LayerPropertyViewModel LayerPropertyViewModel { get; } - - public bool InputDragging { get; private set; } - - protected T InputValue - { - get => LayerPropertyViewModel.LayerProperty.CurrentValue; - set => LayerPropertyViewModel.SetCurrentValue(value, !InputDragging); - } - - public abstract void Update(); - - private void LayerPropertyOnUpdated(object? sender, EventArgs e) - { - Update(); - } - - #region Event handlers - - public void InputDragStarted(object sender, EventArgs e) - { - InputDragging = true; - } - - public void InputDragEnded(object sender, EventArgs e) - { - InputDragging = false; - LayerPropertyViewModel.ProfileEditorService.UpdateSelectedProfileElement(); - } - - #endregion - - public void Dispose() - { - LayerPropertyViewModel.LayerProperty.Updated -= LayerPropertyOnUpdated; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml index 524991b5e..691cf7544 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputView.xaml @@ -1,24 +1,25 @@ - + d:DataContext="{d:DesignInstance propertyInput:SKColorPropertyInputViewModel}"> - + - + Color="{Binding InputValue, Converter={StaticResource SKColorToColorConverter}}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs index ed5ca0332..8e28eaeb2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKColorPropertyInputViewModel.cs @@ -1,27 +1,12 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; using SkiaSharp; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class SKColorPropertyInputViewModel : PropertyInputViewModel + public class SKColorPropertyInputViewModel : PropertyInputViewModel { - public SKColorPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public SKColorPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel) : base(layerPropertyViewModel) { } - - public sealed override List CompatibleTypes { get; } = new List {typeof(SKColor)}; - - public SKColor SKColorInputValue - { - get => (SKColor?) InputValue ?? new SKColor(); - set => InputValue = value; - } - - public override void Update() - { - NotifyOfPropertyChange(() => SKColorInputValue); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml index bf0243f20..e4c49615d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputView.xaml @@ -1,28 +1,29 @@ - + d:DataContext="{d:DesignInstance propertyInput:SKPointPropertyInputViewModel}"> - + , - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs index 15be6cecc..894b0d338 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKPointPropertyInputViewModel.cs @@ -1,50 +1,56 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; +using FluentValidation; using PropertyChanged; using SkiaSharp; +using Stylet; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class SKPointPropertyInputViewModel : PropertyInputViewModel + public class SKPointPropertyInputViewModel : PropertyInputViewModel { - public SKPointPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public SKPointPropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, IModelValidator validator) : base(layerPropertyViewModel, validator) { } - public sealed override List CompatibleTypes { get; } = new List {typeof(SKPoint)}; - // Since SKPoint is immutable we need to create properties that replace the SKPoint entirely [DependsOn(nameof(InputValue))] public float X { - get => ((SKPoint?) InputValue)?.X ?? 0; - set => InputValue = new SKPoint(ApplyInputValue(value), Y); + get => InputValue.X; + set => InputValue = new SKPoint(value, Y); } [DependsOn(nameof(InputValue))] public float Y { - get => ((SKPoint?) InputValue)?.Y ?? 0; - set => InputValue = new SKPoint(X, ApplyInputValue(value)); + get => InputValue.Y; + set => InputValue = new SKPoint(X, value); } - public override void Update() + protected override void OnInputValueChanged() { - NotifyOfPropertyChange(() => X); - NotifyOfPropertyChange(() => Y); + NotifyOfPropertyChange(nameof(X)); + NotifyOfPropertyChange(nameof(Y)); } + } - private float ApplyInputValue(float value) + public class SKPointPropertyInputViewModelValidator : AbstractValidator + { + public SKPointPropertyInputViewModelValidator() { - if (LayerPropertyViewModel.LayerProperty.MaxInputValue != null && - LayerPropertyViewModel.LayerProperty.MaxInputValue is float maxFloat) - value = Math.Min(value, maxFloat); - if (LayerPropertyViewModel.LayerProperty.MinInputValue != null && - LayerPropertyViewModel.LayerProperty.MinInputValue is float minFloat) - value = Math.Max(value, minFloat); + RuleFor(vm => vm.X) + .LessThanOrEqualTo(vm => ((SKPoint) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).X) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKPoint); + RuleFor(vm => vm.X) + .GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).X) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKPoint); - return value; + RuleFor(vm => vm.Y) + .LessThanOrEqualTo(vm => ((SKPoint) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Y) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKPoint); + RuleFor(vm => vm.Y) + .GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Y) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKPoint); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml index 548616776..d581fa6a9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputView.xaml @@ -1,9 +1,9 @@ - - + , - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs index 7b9e3e204..5f5bb2093 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/PropertyInput/SKSizePropertyInputViewModel.cs @@ -1,50 +1,56 @@ -using System; -using System.Collections.Generic; -using Artemis.UI.Services.Interfaces; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; +using FluentValidation; using PropertyChanged; using SkiaSharp; +using Stylet; -namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput +namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput { - public class SKSizePropertyInputViewModel : PropertyInputViewModel + public class SKSizePropertyInputViewModel : PropertyInputViewModel { - public SKSizePropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService) + public SKSizePropertyInputViewModel(LayerPropertyViewModel layerPropertyViewModel, IModelValidator validator) : base(layerPropertyViewModel, validator) { } - public sealed override List CompatibleTypes { get; } = new List {typeof(SKSize)}; - - // Since SKSize is immutable we need to create properties that replace the SKPoint entirely + // Since SKSize is immutable we need to create properties that replace the SKSize entirely [DependsOn(nameof(InputValue))] public float Width { - get => ((SKSize?) InputValue)?.Width ?? 0; - set => InputValue = new SKSize(ApplyInputValue(value), Height); + get => InputValue.Width; + set => InputValue = new SKSize(value, Height); } [DependsOn(nameof(InputValue))] public float Height { - get => ((SKSize?) InputValue)?.Height ?? 0; - set => InputValue = new SKSize(Width, ApplyInputValue(value)); + get => InputValue.Height; + set => InputValue = new SKSize(Width, value); } - public override void Update() + protected override void OnInputValueChanged() { - NotifyOfPropertyChange(() => Width); - NotifyOfPropertyChange(() => Height); + NotifyOfPropertyChange(nameof(Width)); + NotifyOfPropertyChange(nameof(Height)); } + } - private float ApplyInputValue(float value) + public class SKSizePropertyInputViewModelValidator : AbstractValidator + { + public SKSizePropertyInputViewModelValidator() { - if (LayerPropertyViewModel.LayerProperty.MaxInputValue != null && - LayerPropertyViewModel.LayerProperty.MaxInputValue is float maxFloat) - value = Math.Min(value, maxFloat); - if (LayerPropertyViewModel.LayerProperty.MinInputValue != null && - LayerPropertyViewModel.LayerProperty.MinInputValue is float minFloat) - value = Math.Max(value, minFloat); + RuleFor(vm => vm.Width) + .LessThanOrEqualTo(vm => ((SKSize)vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Width) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKSize); + RuleFor(vm => vm.Width) + .GreaterThanOrEqualTo(vm => ((SKSize)vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Width) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKSize); - return value; + RuleFor(vm => vm.Height) + .LessThanOrEqualTo(vm => ((SKSize)vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Height) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKSize); + RuleFor(vm => vm.Height) + .GreaterThanOrEqualTo(vm => ((SKSize)vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue).Height) + .When(vm => vm.LayerPropertyViewModel.PropertyDescription.MaxInputValue is SKSize); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index fc791154a..d670a3789 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -1,20 +1,15 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Artemis.Core.Models.Profile; -using Artemis.UI.Exceptions; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { public class TreePropertyViewModel : TreePropertyViewModel { - public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel) + public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, PropertyInputViewModel propertyInputViewModel) : base(layerPropertyBaseViewModel) { LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; - PropertyInputViewModel = CreatePropertyInputViewModel(); + PropertyInputViewModel = propertyInputViewModel; } public LayerPropertyViewModel LayerPropertyViewModel { get; } @@ -24,36 +19,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { PropertyInputViewModel.Dispose(); } - - private PropertyInputViewModel CreatePropertyInputViewModel() - { - if (!IsPropertySupported(typeof(T))) - throw new ArtemisUIException($"Failed to create a property input view model, type {typeof(T).Name} is not supported."); - - return (PropertyInputViewModel) Activator.CreateInstance(SupportedTypes[typeof(T)], this); - } } public abstract class TreePropertyViewModel : IDisposable { - public static ReadOnlyDictionary SupportedTypes = new ReadOnlyDictionary(new Dictionary - { - {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, - {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, - {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)}, - {typeof(LayerBrushReference), typeof(BrushPropertyInputViewModel)} - }); - - public static void RegisterPropertyInputViewModel() - { - - } - - public static bool IsPropertySupported(Type type) - { - return SupportedTypes.ContainsKey(type); - } - protected TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { LayerPropertyBaseViewModel = layerPropertyBaseViewModel; @@ -61,5 +30,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } public abstract void Dispose(); + + public static void RegisterPropertyInputViewModel() + { + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs index 585276011..b6ec65f93 100644 --- a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs @@ -5,6 +5,7 @@ using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Abstract; using Artemis.UI.Events; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; namespace Artemis.UI.Services.Interfaces { @@ -54,5 +55,7 @@ namespace Artemis.UI.Services.Interfaces /// Occurs when the profile preview has been updated /// event EventHandler ProfilePreviewUpdated; + + TreePropertyViewModel CreateTreePropertyViewModel(); } } \ No newline at end of file From 7b238e241e50bb23c0f2e410e576ad39eaa3094b Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 17 May 2020 18:54:15 +0200 Subject: [PATCH 10/14] Datamodels - A few small fixes to fix errors during startup Layer properties - Reimplemented property tree Stylet - Add support for binding views to generic VMs --- src/Artemis.Core/Models/Profile/Layer.cs | 4 +- .../LayerProperties/BaseLayerProperty.cs | 5 ++ .../Models/Profile/LayerPropertyGroup.cs | 11 +++- .../Plugins/Abstract/DataModels/DataModel.cs | 2 +- src/Artemis.Core/Services/LayerService.cs | 2 +- .../Services/Storage/ProfileService.cs | 12 +++++ src/Artemis.UI/Ninject/UiModule.cs | 11 +++- .../LayerProperties/LayerPropertiesView.xaml | 15 +++--- .../LayerPropertiesViewModel.cs | 9 ++-- .../LayerProperties/LayerPropertyViewModel.cs | 19 +++++-- .../Timeline/TimelineView.xaml | 12 +++++ .../SKPointPropertyInputViewModel.cs | 3 +- .../SKSizePropertyInputViewModel.cs | 3 +- .../Tree/TreePropertyView.xaml | 52 +++++++++++++++++++ .../Tree/TreePropertyViewModel.cs | 4 -- .../LayerProperties/Tree/TreeView.xaml | 4 +- .../LayerProperties/Tree/TreeViewModel.cs | 20 ++++++- .../Interfaces/IProfileEditorService.cs | 4 +- .../Services/ProfileEditorService.cs | 36 +++++++++++-- src/Artemis.UI/Stylet/ArtemisViewManager.cs | 11 +++- src/Artemis.UI/Stylet/NinjectBootstrapper.cs | 2 +- .../GeneralDataModel.cs | 8 +-- 22 files changed, 207 insertions(+), 42 deletions(-) create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyView.xaml diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 3f9dd807a..6f183e770 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -125,7 +125,7 @@ namespace Artemis.Core.Models.Profile General.ApplyToEntity(); Transform.ApplyToEntity(); - LayerBrush.BaseProperties.ApplyToEntity(); + LayerBrush?.BaseProperties.ApplyToEntity(); // LEDs LayerEntity.Leds.Clear(); @@ -212,7 +212,7 @@ namespace Artemis.Core.Models.Profile { General.Override(timeOverride); Transform.Override(timeOverride); - LayerBrush.BaseProperties.Override(timeOverride); + LayerBrush?.BaseProperties.Override(timeOverride); } /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index af9a7d590..0abc5379a 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -13,6 +13,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties { } + /// + /// The parent group of this layer property, set after construction + /// + public LayerPropertyGroup Parent { get; internal set; } + /// /// Gets whether keyframes are supported on this property /// diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index c06c94647..cb4b87709 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -23,6 +23,11 @@ namespace Artemis.Core.Models.Profile _layerPropertyGroups = new List(); } + /// + /// The parent group of this layer property group, set after construction + /// + public LayerPropertyGroup Parent { get; internal set; } + /// /// Gets whether this property group's properties are all initialized /// @@ -67,10 +72,11 @@ namespace Artemis.Core.Models.Profile var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) { - if (!typeof(LayerProperty<>).IsAssignableFrom(propertyInfo.PropertyType)) + if (!typeof(BaseLayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) throw new ArtemisPluginException("Layer property with PropertyDescription attribute must be of type LayerProperty"); - var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType); + var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); + instance.Parent = this; InitializeProperty(layer, path, instance); propertyInfo.SetValue(this, instance); _layerProperties.Add(instance); @@ -84,6 +90,7 @@ namespace Artemis.Core.Models.Profile throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup"); var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType); + instance.Parent = this; instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); _layerPropertyGroups.Add(instance); diff --git a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs index 114b1aedb..372e4191b 100644 --- a/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs +++ b/src/Artemis.Core/Plugins/Abstract/DataModels/DataModel.cs @@ -50,7 +50,7 @@ namespace Artemis.Core.Plugins.Abstract.DataModels continue; // If the a nested datamodel, ensure the properties on there are valid - if (propertyInfo.PropertyType == typeof(DataModel)) + if (typeof(DataModel).IsAssignableFrom(propertyInfo.PropertyType)) ValidateType(propertyInfo.PropertyType); else if (!SupportedTypes.Contains(propertyInfo.PropertyType)) { diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index 6a1c344e3..ae742b682 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -40,7 +40,7 @@ namespace Artemis.Core.Services { RemoveLayerBrush(layer); - var descriptorReference = layer.General.BrushReference.CurrentValue; + var descriptorReference = layer.General.BrushReference?.CurrentValue; if (descriptorReference == null) return null; diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 8729211b5..d886939f3 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -93,6 +93,7 @@ namespace Artemis.Core.Services.Storage module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface); if (profile != null) { + InitializeCoreProperties(profile); InstantiateProfileLayerBrushes(profile); } } @@ -158,6 +159,17 @@ namespace Artemis.Core.Services.Storage _logger.Debug("Redo profile update - Success"); } + private void InitializeCoreProperties(Profile profile) + { + foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) + { + if (!layer.General.PropertiesInitialized) + layer.General.InitializeProperties(_layerService, layer, null); + if (!layer.Transform.PropertiesInitialized) + layer.Transform.InitializeProperties(_layerService, layer, null); + }; + } + private void InstantiateProfileLayerBrushes(Profile profile) { // Only instantiate brushes for layers without an existing brush instance diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 6280c051a..22d640f81 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -2,6 +2,7 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens; using Artemis.UI.Screens.Module.ProfileEditor; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Services.Dialog; using Artemis.UI.Stylet; @@ -56,7 +57,7 @@ namespace Artemis.UI.Ninject .InheritedFrom() .BindAllBaseClasses(); }); - + // Bind all UI services as singletons Kernel.Bind(x => { @@ -76,6 +77,14 @@ namespace Artemis.UI.Ninject .InheritedFrom() .BindAllInterfaces(); }); + + Kernel.Bind(x => + { + x.FromThisAssembly() + .SelectAllClasses() + .InheritedFrom(typeof(PropertyInputViewModel<>)) + .BindToSelf(); + }); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index cff17995b..f82d7124f 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -18,8 +18,7 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml new file mode 100644 index 000000000..f780cea81 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index b675b107d..e8d2ff611 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -1,30 +1,49 @@ -using System; +using System.Linq; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Services.Interfaces; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { public class TimelinePropertyViewModel : TimelinePropertyViewModel { - public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) : base(layerPropertyBaseViewModel) + private readonly IProfileEditorService _profileEditorService; + + public TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel) { + _profileEditorService = profileEditorService; LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; } public LayerPropertyViewModel LayerPropertyViewModel { get; } - public override void Dispose() + public override void UpdateKeyframes(TimelineViewModel timelineViewModel) { + var keyframes = LayerPropertyViewModel.LayerProperty.Keyframes.ToList(); + TimelineKeyframeViewModels.RemoveRange( + TimelineKeyframeViewModels.Where(t => !keyframes.Contains(t.BaseLayerPropertyKeyframe)) + ); + TimelineKeyframeViewModels.AddRange( + keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k)) + .Select(k => new TimelineKeyframeViewModel(_profileEditorService, timelineViewModel, k)) + ); + + foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) + timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); } } - public abstract class TimelinePropertyViewModel : IDisposable + public abstract class TimelinePropertyViewModel { protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { LayerPropertyBaseViewModel = layerPropertyBaseViewModel; + TimelineKeyframeViewModels = new BindableCollection(); } public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } - public abstract void Dispose(); + public BindableCollection TimelineKeyframeViewModels { get; set; } + + public abstract void UpdateKeyframes(TimelineViewModel timelineViewModel); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml index fbef30ca6..1a13599be 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineView.xaml @@ -4,9 +4,52 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline" - mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> - - + xmlns:s="https://github.com/canton7/Stylet" + mc:Ignorable="d" + d:DesignHeight="25" + d:DesignWidth="800" + d:DataContext="{d:DesignInstance local:TimelineViewModel}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index 865d49a00..113b26e8a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -1,14 +1,174 @@ -using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Shared.Utilities; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { public class TimelineViewModel { - public TimelineViewModel(BindableCollection layerPropertyGroups) + private readonly LayerPropertiesViewModel _layerPropertiesViewModel; + + public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups) { + _layerPropertiesViewModel = layerPropertiesViewModel; LayerPropertyGroups = layerPropertyGroups; + SelectionRectangle = new RectangleGeometry(); + + UpdateKeyframes(); } public BindableCollection LayerPropertyGroups { get; } + + public double Width { get; set; } + public RectangleGeometry SelectionRectangle { get; set; } + + public void UpdateKeyframes() + { + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + { + foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren()) + { + if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel) + layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes(this); + } + } + } + + #region Keyframe movement + + public void MoveSelectedKeyframes(TimeSpan cursorTime) + { + // Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging + SelectionRectangle.Rect = new Rect(); + + var keyframeViewModels = GetAllKeyframeViewModels(); + foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) + keyframeViewModel.ApplyMovement(cursorTime); + + _layerPropertiesViewModel.ProfileEditorService.UpdateProfilePreview(); + } + + + public void ReleaseSelectedKeyframes() + { + var keyframeViewModels = GetAllKeyframeViewModels(); + foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) + keyframeViewModel.ReleaseMovement(); + } + + #endregion + + #region Keyframe selection + + private Point _mouseDragStartPoint; + private bool _mouseDragging; + + // ReSharper disable once UnusedMember.Global - Called from view + public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Released) + return; + + ((IInputElement) sender).CaptureMouse(); + + SelectionRectangle.Rect = new Rect(); + _mouseDragStartPoint = e.GetPosition((IInputElement) sender); + _mouseDragging = true; + e.Handled = true; + } + + // ReSharper disable once UnusedMember.Global - Called from view + public void TimelineCanvasMouseUp(object sender, MouseEventArgs e) + { + if (!_mouseDragging) + return; + + var position = e.GetPosition((IInputElement) sender); + var selectedRect = new Rect(_mouseDragStartPoint, position); + SelectionRectangle.Rect = selectedRect; + + var keyframeViewModels = GetAllKeyframeViewModels(); + var selectedKeyframes = HitTestUtilities.GetHitViewModels((Visual) sender, SelectionRectangle); + foreach (var keyframeViewModel in keyframeViewModels) + keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel); + + _mouseDragging = false; + e.Handled = true; + ((IInputElement) sender).ReleaseMouseCapture(); + } + + public void TimelineCanvasMouseMove(object sender, MouseEventArgs e) + { + if (_mouseDragging && e.LeftButton == MouseButtonState.Pressed) + { + var position = e.GetPosition((IInputElement) sender); + var selectedRect = new Rect(_mouseDragStartPoint, position); + SelectionRectangle.Rect = selectedRect; + e.Handled = true; + } + } + + public void SelectKeyframe(TimelineKeyframeViewModel clicked, bool selectBetween, bool toggle) + { + var keyframeViewModels = GetAllKeyframeViewModels(); + if (selectBetween) + { + var selectedIndex = keyframeViewModels.FindIndex(k => k.IsSelected); + // If nothing is selected, select only the clicked + if (selectedIndex == -1) + { + clicked.IsSelected = true; + return; + } + + foreach (var keyframeViewModel in keyframeViewModels) + keyframeViewModel.IsSelected = false; + + var clickedIndex = keyframeViewModels.IndexOf(clicked); + if (clickedIndex < selectedIndex) + { + foreach (var keyframeViewModel in keyframeViewModels.Skip(clickedIndex).Take(selectedIndex - clickedIndex + 1)) + keyframeViewModel.IsSelected = true; + } + else + { + foreach (var keyframeViewModel in keyframeViewModels.Skip(selectedIndex).Take(clickedIndex - selectedIndex + 1)) + keyframeViewModel.IsSelected = true; + } + } + else if (toggle) + { + // Toggle only the clicked keyframe, leave others alone + clicked.IsSelected = !clicked.IsSelected; + } + else + { + // Only select the clicked keyframe + foreach (var keyframeViewModel in keyframeViewModels) + keyframeViewModel.IsSelected = false; + clicked.IsSelected = true; + } + } + + private List GetAllKeyframeViewModels() + { + var viewModels = new List(); + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + viewModels.AddRange(layerPropertyGroupViewModel.GetAllChildren()); + + var keyframes = viewModels.Where(vm => vm is LayerPropertyViewModel) + .SelectMany(vm => ((LayerPropertyViewModel) vm).TimelinePropertyBaseViewModel.TimelineKeyframeViewModels) + .ToList(); + + return keyframes; + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index ec520b534..8f6be3c11 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -1,13 +1,18 @@ using System; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; +using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { public class TreePropertyViewModel : TreePropertyViewModel { - public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, PropertyInputViewModel propertyInputViewModel) : base(layerPropertyBaseViewModel) + private readonly IProfileEditorService _profileEditorService; + + public TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel, PropertyInputViewModel propertyInputViewModel, + IProfileEditorService profileEditorService) : base(layerPropertyBaseViewModel) { + _profileEditorService = profileEditorService; LayerPropertyViewModel = (LayerPropertyViewModel) layerPropertyBaseViewModel; PropertyInputViewModel = propertyInputViewModel; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml index 95412e80c..02206c91a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -90,7 +90,7 @@ diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs index 1f267e0d9..3835d0b09 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeViewModel.cs @@ -7,8 +7,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { public class TreeViewModel { - public TreeViewModel(BindableCollection layerPropertyGroups) + private readonly LayerPropertiesViewModel _layerPropertiesViewModel; + + public TreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups) { + _layerPropertiesViewModel = layerPropertiesViewModel; LayerPropertyGroups = layerPropertyGroups; } diff --git a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs index dbffd48fb..2a393ecc1 100644 --- a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs +++ b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs @@ -7,7 +7,6 @@ using Artemis.UI.Events; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree; -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree.PropertyInput.Abstract; namespace Artemis.UI.Services.Interfaces { @@ -16,6 +15,7 @@ namespace Artemis.UI.Services.Interfaces Profile SelectedProfile { get; } ProfileElement SelectedProfileElement { get; } TimeSpan CurrentTime { get; set; } + int PixelsPerSecond { get; set; } LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription); void ChangeSelectedProfile(Profile profile); @@ -53,6 +53,11 @@ namespace Artemis.UI.Services.Interfaces /// event EventHandler CurrentTimeChanged; + /// + /// Occurs when the pixels per second (zoom level) is changed + /// + event EventHandler PixelsPerSecondChanged; + /// /// Occurs when the profile preview has been updated /// diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index 0616853d1..cd55e46e7 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -29,6 +29,7 @@ namespace Artemis.UI.Services private readonly IKernel _kernel; private TimeSpan _currentTime; private TimeSpan _lastUpdateTime; + private int _pixelsPerSecond; public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel) { @@ -46,6 +47,7 @@ namespace Artemis.UI.Services {typeof(SKPoint), typeof(SKPointPropertyInputViewModel)}, {typeof(SKSize), typeof(SKSizePropertyInputViewModel)} }; + PixelsPerSecond = 31; } public Dictionary RegisteredPropertyEditors { get; set; } @@ -66,6 +68,17 @@ namespace Artemis.UI.Services } } + public int PixelsPerSecond + { + get => _pixelsPerSecond; + set + { + _pixelsPerSecond = value; + OnPixelsPerSecondChanged(); + } + } + + public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription) { // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line @@ -93,7 +106,7 @@ namespace Artemis.UI.Services { new ConstructorArgument("layerPropertyViewModel", layerPropertyViewModel) }; - return new TreePropertyViewModel(layerPropertyViewModel, (PropertyInputViewModel) _kernel.Get(type, parameters)); + return new TreePropertyViewModel(layerPropertyViewModel, (PropertyInputViewModel) _kernel.Get(type, parameters), this); } public void ChangeSelectedProfile(Profile profile) @@ -181,8 +194,9 @@ namespace Artemis.UI.Services public event EventHandler ProfileElementSelected; public event EventHandler SelectedProfileElementUpdated; public event EventHandler CurrentTimeChanged; + public event EventHandler PixelsPerSecondChanged; public event EventHandler ProfilePreviewUpdated; - + public void StopRegularRender() { _coreService.ModuleUpdatingDisabled = true; @@ -218,6 +232,11 @@ namespace Artemis.UI.Services CurrentTimeChanged?.Invoke(this, EventArgs.Empty); } + protected virtual void OnPixelsPerSecondChanged() + { + PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty); + } + protected virtual void OnProfilePreviewUpdated() { ProfilePreviewUpdated?.Invoke(this, EventArgs.Empty); From 221c8bc7e72caccf6f51121713af780f13d6f224 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 24 May 2020 22:05:04 +0200 Subject: [PATCH 12/14] Layer properties - Restored much functionality on the reworked VMs --- src/Artemis.Core/Models/Profile/Layer.cs | 49 ++++---- .../LayerProperties/BaseLayerProperty.cs | 78 ++++++++++++- .../Profile/LayerProperties/LayerProperty.cs | 61 ++-------- .../Models/Profile/LayerPropertyGroup.cs | 39 ++++++- .../Plugins/LayerBrush/LayerBrush.cs | 1 + src/Artemis.Core/Services/LayerService.cs | 13 +-- .../Services/Storage/ProfileService.cs | 14 +-- .../Controls/DraggableFloat.xaml | 17 ++- .../Controls/DraggableFloat.xaml.cs | 2 + .../Utilities/HitTestUtilities.cs | 4 +- .../LayerPropertiesViewModel.cs | 37 ++++++ .../LayerProperties/LayerPropertyViewModel.cs | 1 + .../Timeline/TimelineEasingViewModel.cs | 4 +- .../Timeline/TimelineKeyframeViewModel.cs | 13 ++- .../Timeline/TimelinePropertyGroupView.xaml | 110 ++++++++++-------- .../Timeline/TimelinePropertyView.xaml | 15 ++- .../Timeline/TimelinePropertyViewModel.cs | 55 +++++++-- .../Timeline/TimelineView.xaml | 17 +-- .../Timeline/TimelineViewModel.cs | 5 +- .../Abstract/PropertyInputViewModel.cs | 16 ++- .../BrushPropertyInputViewModel.cs | 9 +- .../PropertyInput/FloatPropertyInputView.xaml | 8 +- .../PropertyInput/IntPropertyInputView.xaml | 2 + .../Tree/TreePropertyView.xaml | 5 +- .../Tree/TreePropertyViewModel.cs | 30 +++++ .../LayerProperties/Tree/TreeView.xaml | 6 +- .../LayerProperties/Tree/TreeViewModel.cs | 5 +- .../Visualization/ProfileLayerViewModel.cs | 30 ++--- .../Services/ProfileEditorService.cs | 3 +- 29 files changed, 440 insertions(+), 209 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 6f183e770..8ec456c22 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Windows.Media.Animation; using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; @@ -37,6 +38,7 @@ namespace Artemis.Core.Models.Profile Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); + General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; } internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) @@ -52,6 +54,7 @@ namespace Artemis.Core.Models.Profile Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); + General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; } internal LayerEntity LayerEntity { get; set; } @@ -147,6 +150,18 @@ namespace Artemis.Core.Models.Profile #region Shape management + private void GeneralOnPropertyGroupInitialized(object sender, EventArgs e) + { + ApplyShapeType(); + General.ShapeType.BaseValueChanged -= ShapeTypeOnBaseValueChanged; + General.ShapeType.BaseValueChanged += ShapeTypeOnBaseValueChanged; + } + + private void ShapeTypeOnBaseValueChanged(object sender, EventArgs e) + { + ApplyShapeType(); + } + private void ApplyShapeType() { switch (General.ShapeType.CurrentValue) @@ -164,34 +179,21 @@ namespace Artemis.Core.Models.Profile #endregion - #region Properties - - internal void InitializeProperties(ILayerService layerService) - { - PropertiesInitialized = true; - - ApplyShapeType(); - } - - public bool PropertiesInitialized { get; private set; } - - #endregion - #region Rendering /// public override void Update(double deltaTime) { - if (LayerBrush == null) + if (LayerBrush == null || !LayerBrush.BaseProperties.PropertiesInitialized) return; - var properties = new List(General.GetAllLayerProperties()); - properties.AddRange(Transform.GetAllLayerProperties()); - properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties()); + var properties = new List(General.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any())); + properties.AddRange(Transform.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any())); + properties.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties().Where(p => p.BaseKeyframes.Any())); // For now, reset all keyframe engines after the last keyframe was hit // This is a placeholder method of repeating the animation until repeat modes are implemented - var timeLineEnd = properties.Max(p => p.BaseKeyframes.Max(k => k.Position)); + var timeLineEnd = properties.Any() ? properties.Max(p => p.BaseKeyframes.Max(k => k.Position)) : TimeSpan.MaxValue; if (properties.Any(p => p.TimelineProgress >= timeLineEnd)) { General.Override(TimeSpan.Zero); @@ -218,7 +220,7 @@ namespace Artemis.Core.Models.Profile /// public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { - if (Path == null || LayerShape?.Path == null) + if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) return; canvas.Save(); @@ -263,7 +265,8 @@ namespace Artemis.Core.Models.Profile canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); canvas.Translate(x, y); - LayerBrush?.Render(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + if (LayerBrush != null && LayerBrush.BaseProperties.PropertiesInitialized) + LayerBrush.Render(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); } private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) @@ -405,6 +408,7 @@ namespace Artemis.Core.Models.Profile public event EventHandler RenderPropertiesUpdated; public event EventHandler ShapePropertiesUpdated; + public event EventHandler LayerBrushUpdated; private void OnRenderPropertiesUpdated() { @@ -416,6 +420,11 @@ namespace Artemis.Core.Models.Profile ShapePropertiesUpdated?.Invoke(this, EventArgs.Empty); } + internal void OnLayerBrushUpdated() + { + LayerBrushUpdated?.Invoke(this, EventArgs.Empty); + } + #endregion } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 0abc5379a..bff3806a2 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -9,10 +9,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public abstract class BaseLayerProperty { + private bool _keyframesEnabled; + internal BaseLayerProperty() { } + /// + /// The layer this property applies to + /// + public Layer Layer { get; internal set; } + /// /// The parent group of this layer property, set after construction /// @@ -21,13 +28,22 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Gets whether keyframes are supported on this property /// - public bool KeyframesSupported { get; protected set; } + public bool KeyframesSupported { get; protected set; } = true; /// /// Gets or sets whether keyframes are enabled on this property, has no effect if is /// False /// - public bool KeyframesEnabled { get; set; } + public bool KeyframesEnabled + { + get => _keyframesEnabled; + set + { + if (_keyframesEnabled == value) return; + _keyframesEnabled = value; + OnKeyframesToggled(); + } + } /// /// Gets or sets whether the property is hidden in the UI @@ -57,17 +73,73 @@ namespace Artemis.Core.Models.Profile.LayerProperties internal PropertyEntity PropertyEntity { get; set; } internal LayerPropertyGroup LayerPropertyGroup { get; set; } + /// /// Applies the provided property entity to the layer property by deserializing the JSON base value and keyframe values /// /// /// - internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup); + /// + internal abstract void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage); /// /// Saves the property to the underlying property entity that was configured when calling /// /// internal abstract void ApplyToEntity(); + + #region Events + + /// + /// Occurs once every frame when the layer property is updated + /// + public event EventHandler Updated; + + /// + /// Occurs when the base value of the layer property was updated + /// + public event EventHandler BaseValueChanged; + + /// + /// Occurs when keyframes are enabled/disabled + /// + public event EventHandler KeyframesToggled; + + /// + /// Occurs when a new keyframe was added to the layer property + /// + public event EventHandler KeyframeAdded; + + /// + /// Occurs when a keyframe was removed from the layer property + /// + public event EventHandler KeyframeRemoved; + + protected virtual void OnUpdated() + { + Updated?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnBaseValueChanged() + { + BaseValueChanged?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnKeyframesToggled() + { + KeyframesToggled?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnKeyframeAdded() + { + KeyframeAdded?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnKeyframeRemoved() + { + KeyframeRemoved?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 0b284a23c..351593d3f 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -180,12 +180,13 @@ namespace Artemis.Core.Models.Profile.LayerProperties // The current keyframe is the last keyframe before the current time CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress); - // The next keyframe is the first keyframe that's after the current time - NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress); + // Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current + var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1; + NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null; // No need to update the current value if either of the keyframes are null if (CurrentKeyframe == null) - CurrentValue = BaseValue; + CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue; else if (NextKeyframe == null) CurrentValue = CurrentKeyframe.Value; // Only determine progress and current value if both keyframes are present @@ -218,7 +219,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties _keyframes = _keyframes.OrderBy(k => k.Position).ToList(); } - internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup) + internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage) { // Doubt this will happen but let's make sure if (_isInitialized) @@ -231,8 +232,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties try { - IsLoadedFromStorage = true; - BaseValue = JsonConvert.DeserializeObject(entity.Value); + if (entity.Value != null) + BaseValue = JsonConvert.DeserializeObject(entity.Value); + + IsLoadedFromStorage = fromStorage; CurrentValue = BaseValue; KeyframesEnabled = entity.KeyframesEnabled; @@ -259,7 +262,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties internal override void ApplyToEntity() { - if (_isInitialized) + if (!_isInitialized) throw new ArtemisCoreException("Layer property is not yet initialized"); PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue); @@ -272,49 +275,5 @@ namespace Artemis.Core.Models.Profile.LayerProperties EasingFunction = (int) k.EasingFunction })); } - - #region Events - - /// - /// Occurs once every frame when the layer property is updated - /// - public event EventHandler Updated; - - /// - /// Occurs when the base value of the layer property was updated - /// - public event EventHandler BaseValueChanged; - - /// - /// Occurs when a new keyframe was added to the layer property - /// - public event EventHandler KeyframeAdded; - - /// - /// Occurs when a keyframe was removed from the layer property - /// - public event EventHandler KeyframeRemoved; - - protected virtual void OnUpdated() - { - Updated?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnBaseValueChanged() - { - BaseValueChanged?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnKeyframeAdded() - { - KeyframeAdded?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnKeyframeRemoved() - { - KeyframeRemoved?.Invoke(this, EventArgs.Empty); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index cb4b87709..2ea9b5047 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Artemis.Core.Annotations; using Artemis.Core.Events; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; +using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { @@ -23,6 +25,11 @@ namespace Artemis.Core.Models.Profile _layerPropertyGroups = new List(); } + /// + /// The layer this property group applies to + /// + public Layer Layer { get; internal set; } + /// /// The parent group of this layer property group, set after construction /// @@ -60,12 +67,16 @@ namespace Artemis.Core.Models.Profile { } - internal void InitializeProperties(ILayerService layerService, Layer layer, string path) + internal void InitializeProperties(ILayerService layerService, Layer layer, [NotNull] string path) { + if (path == null) + throw new ArgumentNullException(nameof(path)); // Doubt this will happen but let's make sure if (PropertiesInitialized) throw new ArtemisCoreException("Layer property group already initialized, wut"); + Layer = layer; + // Get all properties with a PropertyDescriptionAttribute foreach (var propertyInfo in GetType().GetProperties()) { @@ -77,7 +88,8 @@ namespace Artemis.Core.Models.Profile var instance = (BaseLayerProperty) Activator.CreateInstance(propertyInfo.PropertyType, true); instance.Parent = this; - InitializeProperty(layer, path, instance); + instance.Layer = layer; + InitializeProperty(layer, path + propertyInfo.Name, instance); propertyInfo.SetValue(this, instance); _layerProperties.Add(instance); } @@ -100,6 +112,8 @@ namespace Artemis.Core.Models.Profile OnPropertiesInitialized(); PropertiesInitialized = true; + + OnPropertyGroupInitialized(); } internal void ApplyToEntity() @@ -110,12 +124,16 @@ namespace Artemis.Core.Models.Profile var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); if (propertyDescription != null) { + var layerProperty = (BaseLayerProperty) propertyInfo.GetValue(this); + layerProperty.ApplyToEntity(); } else { var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); if (propertyGroupDescription != null) { + var layerPropertyGroup = (LayerPropertyGroup) propertyInfo.GetValue(this); + layerPropertyGroup.ApplyToEntity(); } } } @@ -157,14 +175,22 @@ namespace Artemis.Core.Models.Profile { var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid; var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); - if (entity != null) - instance.ApplyToLayerProperty(entity, this); + var fromStorage = true; + if (entity == null) + { + fromStorage = false; + entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path}; + layer.LayerEntity.PropertyEntities.Add(entity); + } + + instance.ApplyToLayerProperty(entity, this, fromStorage); } #region Events internal event EventHandler PropertyGroupUpdating; internal event EventHandler PropertyGroupOverriding; + public event EventHandler PropertyGroupInitialized; internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e) { @@ -177,5 +203,10 @@ namespace Artemis.Core.Models.Profile } #endregion + + protected virtual void OnPropertyGroupInitialized() + { + PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs index a27fcdb19..6c518d3c4 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/LayerBrush.cs @@ -51,6 +51,7 @@ namespace Artemis.Core.Plugins.LayerBrush internal override void InitializeProperties(ILayerService layerService, string path) { + Properties = Activator.CreateInstance(); Properties.InitializeProperties(layerService, Layer, path); OnPropertiesInitialized(); PropertiesInitialized = true; diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index ae742b682..b080b8b5a 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -27,8 +27,8 @@ namespace Artemis.Core.Services var layer = new Layer(profile, parent, name); // Layers have two hardcoded property groups, instantiate them - layer.General.InitializeProperties(this, layer, null); - layer.Transform.InitializeProperties(this, layer, null); + layer.General.InitializeProperties(this, layer, "General."); + layer.Transform.InitializeProperties(this, layer, "Transform."); // With the properties loaded, the layer brush can be instantiated InstantiateLayerBrush(layer); @@ -58,11 +58,10 @@ namespace Artemis.Core.Services new ConstructorArgument("layer", layer), new ConstructorArgument("descriptor", descriptor) }; - var layerBrush = (BaseLayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments); - layerBrush.InitializeProperties(this, null); - layer.LayerBrush = layerBrush; - - return layerBrush; + layer.LayerBrush = (BaseLayerBrush)_kernel.Get(descriptor.LayerBrushType, arguments); ; + layer.LayerBrush.InitializeProperties(this, "LayerBrush."); + layer.OnLayerBrushUpdated(); + return layer.LayerBrush; } public void RemoveLayerBrush(Layer layer) diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index d886939f3..cb258039c 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -93,8 +93,8 @@ namespace Artemis.Core.Services.Storage module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface); if (profile != null) { - InitializeCoreProperties(profile); - InstantiateProfileLayerBrushes(profile); + InitializeLayerProperties(profile); + InstantiateLayerBrushes(profile); } } @@ -159,18 +159,18 @@ namespace Artemis.Core.Services.Storage _logger.Debug("Redo profile update - Success"); } - private void InitializeCoreProperties(Profile profile) + private void InitializeLayerProperties(Profile profile) { foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) { if (!layer.General.PropertiesInitialized) - layer.General.InitializeProperties(_layerService, layer, null); + layer.General.InitializeProperties(_layerService, layer, "General."); if (!layer.Transform.PropertiesInitialized) - layer.Transform.InitializeProperties(_layerService, layer, null); + layer.Transform.InitializeProperties(_layerService, layer, "Transform."); }; } - private void InstantiateProfileLayerBrushes(Profile profile) + private void InstantiateLayerBrushes(Profile profile) { // Only instantiate brushes for layers without an existing brush instance foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) @@ -188,7 +188,7 @@ namespace Artemis.Core.Services.Storage { var profileModules = _pluginService.GetPluginsOfType(); foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - InstantiateProfileLayerBrushes(profileModule.ActiveProfile); + InstantiateLayerBrushes(profileModule.ActiveProfile); } #region Event handlers diff --git a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml index 1da088f62..352ba72b4 100644 --- a/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml @@ -4,17 +4,25 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + + - + Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"> + @@ -27,15 +35,14 @@ Foreground="{DynamicResource SecondaryAccentBrush}" MouseDown="InputMouseDown" MouseUp="InputMouseUp" - MouseMove="InputMouseMove" - RequestBringIntoView="Input_OnRequestBringIntoView"/> + MouseMove="InputMouseMove" + RequestBringIntoView="Input_OnRequestBringIntoView" /> HitTestResultBehavior.Continue); var filterCallback = new HitTestFilterCallback(e => { - if (e is FrameworkElement fe && fe.DataContext.GetType() == typeof(T) && !result.Contains((T) fe.DataContext)) - result.Add((T) fe.DataContext); + if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context)) + result.Add(context); return HitTestFilterBehavior.Continue; }); VisualTreeHelper.HitTest(container, filterCallback, resultCallback, hitTestParams); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 466f08679..ae7320010 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -43,6 +43,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / ProfileEditorService.PixelsPerSecond); } + public Layer SelectedLayer { get; set; } public BindableCollection LayerPropertyGroups { get; set; } public TreeViewModel TreeViewModel { get; set; } public TimelineViewModel TimelineViewModel { get; set; } @@ -86,9 +87,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private void PopulateProperties(ProfileElement profileElement) { + if (SelectedLayer != null) + { + SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; + SelectedLayer = null; + } + + foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) + layerPropertyGroupViewModel.Dispose(); LayerPropertyGroups.Clear(); + if (profileElement is Layer layer) { + SelectedLayer = layer; + SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated; + // Add the built-in root groups of the layer var generalAttribute = Attribute.GetCustomAttribute( layer.GetType().GetProperty(nameof(layer.General)), @@ -113,11 +126,35 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, layer.LayerBrush.BaseProperties, brushDescription)); } } + else + SelectedLayer = null; TreeViewModel = new TreeViewModel(this, LayerPropertyGroups); TimelineViewModel = new TimelineViewModel(this, LayerPropertyGroups); } + private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e) + { + // Get rid of the old layer properties group + if (LayerPropertyGroups.Count == 3) + { + LayerPropertyGroups[2].Dispose(); + LayerPropertyGroups.RemoveAt(2); + } + + if (SelectedLayer.LayerBrush != null) + { + // Add the rout group of the brush + // The root group of the brush has no attribute so let's pull one out of our sleeve + var brushDescription = new PropertyGroupDescriptionAttribute + { + Name = SelectedLayer.LayerBrush.Descriptor.DisplayName, + Description = SelectedLayer.LayerBrush.Descriptor.Description + }; + LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, SelectedLayer.LayerBrush.BaseProperties, brushDescription)); + } + } + #endregion #region Controls diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index fb70e24b1..0c193a3d1 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -50,6 +50,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public override void Dispose() { TreePropertyViewModel.Dispose(); + TimelinePropertyViewModel.Dispose(); } public void SetCurrentValue(T value, bool saveChanges) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs index 4103779ee..8193f968a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineEasingViewModel.cs @@ -18,7 +18,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline EasingFunction = easingFunction; Description = easingFunction.Humanize(); - CreateGeometry(); + CreateEasingPoints(); } public Easings.Functions EasingFunction { get; } @@ -36,7 +36,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline } } - private void CreateGeometry() + private void CreateEasingPoints() { EasingPoints = new PointCollection(); for (var i = 1; i <= 10; i++) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index bb2d512f4..f979a5dc1 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -45,7 +45,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline #endregion } - public abstract class TimelineKeyframeViewModel + public abstract class TimelineKeyframeViewModel : PropertyChangedBase { private readonly IProfileEditorService _profileEditorService; private readonly TimelineViewModel _timelineViewModel; @@ -56,6 +56,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline _profileEditorService = profileEditorService; _timelineViewModel = timelineViewModel; BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe; + EasingViewModels = new BindableCollection(); } public BaseLayerPropertyKeyframe BaseLayerPropertyKeyframe { get; } @@ -139,6 +140,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline #region Easing + public void ContextMenuOpening() + { + CreateEasingViewModels(); + } + + public void ContextMenuClosing() + { + EasingViewModels.Clear(); + } + private void CreateEasingViewModels() { EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(this, v))); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml index 68822ff6c..9a54c20ec 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml @@ -12,72 +12,82 @@ d:DataContext="{d:DesignInstance local:TimelinePropertyGroupViewModel}"> - + + - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + VerticalContentAlignment="Stretch" + HorizontalContentAlignment="Stretch" + IsTabStop="False" /> + VerticalContentAlignment="Stretch" + HorizontalContentAlignment="Stretch" + IsTabStop="False" /> diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml index f780cea81..83f6e1afe 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -1,12 +1,12 @@  @@ -35,7 +35,9 @@ s:View.ActionTarget="{Binding}" MouseDown="{s:Action KeyframeMouseDown}" MouseUp="{s:Action KeyframeMouseUp}" - MouseMove="{s:Action KeyframeMouseMove}"> + MouseMove="{s:Action KeyframeMouseMove}" + ContextMenuOpening="{s:Action ContextMenuOpening}" + ContextMenuClosing="{s:Action ContextMenuClosing}"> - + + { + var drawingImage = new DrawingImage(new GeometryDrawing(new SolidColorBrush(Colors.Black), null, LayerGeometry)); + var image = new Image { Source = drawingImage }; + var bitmap = new RenderTargetBitmap( + (int)(LayerGeometry.Bounds.Width * 2.5), + (int)(LayerGeometry.Bounds.Height * 2.5), + 96, + 96, + PixelFormats.Pbgra32 + ); + image.Arrange(new Rect(0, 0, bitmap.Width, bitmap.Height)); + bitmap.Render(image); + bitmap.Freeze(); + LayerGeometryBitmap = bitmap; + }); + } private void CreateShapeGeometry() diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index cd55e46e7..392ba57e4 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -139,8 +139,7 @@ namespace Artemis.UI.Services UpdateProfilePreview(); OnSelectedProfileElementUpdated(new ProfileElementEventArgs(SelectedProfileElement)); } - - + public void UpdateProfilePreview() { if (SelectedProfile == null) From dd000e7bed89e1c79fc74c54ce7ef2ad1801750e Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 29 May 2020 00:09:04 +0200 Subject: [PATCH 13/14] Nuget - Updated packages Brush properties - Added default values Brush properties - Removed option to always auto-expand groups Layer properties - Remember expanded/collapsed groups Storage - Added migration system Storage - Added migration that removes profiles made in the old layer properties format Layer timeline - Added back zoom functionality --- src/Artemis.Core/Artemis.Core.csproj | 10 +-- .../Models/Profile/Colors/ColorGradient.cs | 12 ++- src/Artemis.Core/Models/Profile/Layer.cs | 26 ++++++- .../Models/Profile/LayerGeneralProperties.cs | 19 ++--- .../LayerProperties/BaseLayerProperty.cs | 2 + .../Profile/LayerProperties/LayerProperty.cs | 11 +++ .../Types/ColorGradientLayerProperty.cs | 13 ++++ .../Models/Profile/LayerPropertyGroup.cs | 76 +++++++++++-------- .../Profile/LayerTransformProperties.cs | 14 ++-- .../Models/Profile/ProfileElement.cs | 5 ++ src/Artemis.Core/Ninject/CoreModule.cs | 14 ++++ src/Artemis.Core/Services/CoreService.cs | 10 ++- .../Services/Storage/ProfileService.cs | 2 + src/Artemis.Storage/Artemis.Storage.csproj | 3 +- .../Entities/Profile/LayerEntity.cs | 2 + .../AttributeBasedPropertiesMigration.cs | 19 +++++ .../Interfaces/IStorageMigration.cs | 10 +++ .../StorageMigrationService.cs | 38 ++++++++++ .../Artemis.UI.Shared.csproj | 12 +-- src/Artemis.UI/Artemis.UI.csproj | 12 +-- .../Abstract/LayerPropertyBaseViewModel.cs | 2 +- .../LayerProperties/LayerPropertiesView.xaml | 5 +- .../LayerPropertiesViewModel.cs | 7 ++ .../LayerPropertyGroupViewModel.cs | 7 +- .../Timeline/TimelinePropertyViewModel.cs | 8 ++ .../LayerProperties/Tree/TreeView.xaml | 2 + .../Visualization/ProfileDeviceView.xaml | 4 +- .../Visualization/SurfaceDeviceView.xaml | 4 +- .../Services/ProfileEditorService.cs | 3 +- .../Properties/AssemblyInfo.cs | 5 +- .../CoolerMasterDeviceProvider.cs | 3 +- .../Properties/AssemblyInfo.cs | 5 +- .../Properties/AssemblyInfo.cs | 5 +- .../Artemis.Plugins.Devices.DMX.csproj | 2 +- .../Properties/AssemblyInfo.cs | 5 +- .../ViewModels/DMXConfigurationViewModel.cs | 8 +- .../Properties/AssemblyInfo.cs | 5 +- .../Properties/AssemblyInfo.cs | 5 +- .../Properties/AssemblyInfo.cs | 5 +- .../Properties/AssemblyInfo.cs | 5 +- .../Properties/AssemblyInfo.cs | 5 +- .../RoccatDeviceProvider.cs | 2 - .../Properties/AssemblyInfo.cs | 5 +- .../SteelSeriesDeviceProvider.cs | 3 +- .../Artemis.Plugins.Devices.WS281X.csproj | 2 +- .../Properties/AssemblyInfo.cs | 5 +- .../WS281XDeviceProvider.cs | 3 +- .../Properties/AssemblyInfo.cs | 5 +- .../Artemis.Plugins.LayerBrushes.Color.csproj | 2 +- .../ColorBrushProperties.cs | 18 ++--- .../Properties/AssemblyInfo.cs | 5 +- .../Artemis.Plugins.LayerBrushes.Noise.csproj | 4 +- .../NoiseBrushProperties.cs | 28 +++---- .../Properties/AssemblyInfo.cs | 5 +- .../Artemis.Plugins.Modules.General.csproj | 4 +- .../GeneralDataModel.cs | 9 +-- .../GeneralModule.cs | 2 +- .../Properties/AssemblyInfo.cs | 5 +- 58 files changed, 329 insertions(+), 188 deletions(-) create mode 100644 src/Artemis.Storage/Migrations/AttributeBasedPropertiesMigration.cs create mode 100644 src/Artemis.Storage/Migrations/Interfaces/IStorageMigration.cs create mode 100644 src/Artemis.Storage/StorageMigrationService.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index fca9504ac..a9c4706ed 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -21,11 +21,11 @@ - + - - + + @@ -35,8 +35,8 @@ - - + + diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index f6f2d721a..bc66fb1ee 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -55,7 +55,7 @@ namespace Artemis.Core.Models.Profile.Colors if (right == null || left == right) return left.Color; - + position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2); var a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha); var r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red); @@ -65,15 +65,19 @@ namespace Artemis.Core.Models.Profile.Colors } /// - /// [PH] Looping through HSV, adds 8 rainbow colors + /// Gets a new ColorGradient with colors looping through the HSV-spectrum /// - public void MakeFabulous() + /// + public static ColorGradient GetUnicornBarf() { + var gradient = new ColorGradient(); for (var i = 0; i < 9; i++) { var color = i != 8 ? SKColor.FromHsv(i * 32, 100, 100) : SKColor.FromHsv(0, 100, 100); - Stops.Add(new ColorGradientStop(color, 0.125f * i)); + gradient.Stops.Add(new ColorGradientStop(color, 0.125f * i)); } + + return gradient; } #region PropertyChanged diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 8ec456c22..e9f74c2d9 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Windows.Media.Animation; using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; @@ -22,6 +21,7 @@ namespace Artemis.Core.Models.Profile /// public sealed class Layer : ProfileElement { + private readonly List _expandedPropertyGroups; private LayerShape _layerShape; private List _leds; private SKPath _path; @@ -38,6 +38,8 @@ namespace Artemis.Core.Models.Profile Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); + _expandedPropertyGroups = new List(); + General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; } @@ -54,6 +56,9 @@ namespace Artemis.Core.Models.Profile Transform = new LayerTransformProperties {IsCorePropertyGroup = true}; _leds = new List(); + _expandedPropertyGroups = new List(); + _expandedPropertyGroups.AddRange(layerEntity.ExpandedPropertyGroups); + General.PropertyGroupInitialized += GeneralOnPropertyGroupInitialized; } @@ -99,10 +104,10 @@ namespace Artemis.Core.Models.Profile } } - [PropertyGroupDescription(Name = "General", Description = "A collection of general properties", ExpandByDefault = true)] + [PropertyGroupDescription(Name = "General", Description = "A collection of general properties")] public LayerGeneralProperties General { get; set; } - [PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties", ExpandByDefault = true)] + [PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties")] public LayerTransformProperties Transform { get; set; } /// @@ -115,6 +120,19 @@ namespace Artemis.Core.Models.Profile return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } + public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup) + { + return _expandedPropertyGroups.Contains(layerPropertyGroup.Path); + } + + public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded) + { + if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup)) + _expandedPropertyGroups.Remove(layerPropertyGroup.Path); + else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup)) + _expandedPropertyGroups.Add(layerPropertyGroup.Path); + } + #region Storage internal override void ApplyToEntity() @@ -125,6 +143,8 @@ namespace Artemis.Core.Models.Profile LayerEntity.Order = Order; LayerEntity.Name = Name; LayerEntity.ProfileId = Profile.EntityId; + LayerEntity.ExpandedPropertyGroups.Clear(); + LayerEntity.ExpandedPropertyGroups.AddRange(_expandedPropertyGroups); General.ApplyToEntity(); Transform.ApplyToEntity(); diff --git a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs index f12489f22..e0a2b46d4 100644 --- a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs @@ -1,5 +1,4 @@ -using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerProperties.Types; using SkiaSharp; @@ -19,17 +18,15 @@ namespace Artemis.Core.Models.Profile [PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")] public LayerBrushReferenceLayerProperty BrushReference { get; set; } + protected override void PopulateDefaults() + { + ShapeType.DefaultValue = LayerShapeType.Rectangle; + FillType.DefaultValue = LayerFillType.Stretch; + BlendMode.DefaultValue = SKBlendMode.SrcOver; + } + protected override void OnPropertiesInitialized() { - // Populate defaults - if (!ShapeType.IsLoadedFromStorage) - ShapeType.BaseValue = LayerShapeType.Rectangle; - if (!FillType.IsLoadedFromStorage) - FillType.BaseValue = LayerFillType.Stretch; - if (!BlendMode.IsLoadedFromStorage) - BlendMode.BaseValue = SKBlendMode.SrcOver; - - // TODO: SpoinkyNL 28-4-2020: Select preferred default brush type with a fallback to the first available } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index bff3806a2..9dbed60a3 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -141,5 +141,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties } #endregion + + public abstract void ApplyDefaultValue(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 351593d3f..03184f426 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -55,6 +55,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties internal set => _currentValue = value; } + /// + /// Gets or sets the default value of this layer property. If set, this value is automatically applied if the property has no + /// value in storage + /// + public T DefaultValue { get; set; } + /// /// Gets a read-only list of all the keyframes on this layer property /// @@ -275,5 +281,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties EasingFunction = (int) k.EasingFunction })); } + + public override void ApplyDefaultValue() + { + CurrentValue = DefaultValue; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs index 1d32b63a7..7b6c36d5f 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs @@ -1,5 +1,6 @@ using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile.Colors; +using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile.LayerProperties.Types { @@ -11,6 +12,18 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types KeyframesSupported = false; } + internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage) + { + base.ApplyToLayerProperty(entity, layerPropertyGroup, fromStorage); + + // Don't allow color gradients to be null + if (BaseValue == null) + { + BaseValue = DefaultValue ?? new ColorGradient(); + } + + } + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { throw new ArtemisCoreException("Color Gradients do not support keyframes."); diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 2ea9b5047..c804705de 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -13,11 +13,11 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile { - public class LayerPropertyGroup + public abstract class LayerPropertyGroup { - private ReadOnlyCollection _allLayerProperties; private readonly List _layerProperties; private readonly List _layerPropertyGroups; + private ReadOnlyCollection _allLayerProperties; protected LayerPropertyGroup() { @@ -26,17 +26,22 @@ namespace Artemis.Core.Models.Profile } /// - /// The layer this property group applies to + /// The layer this property group applies to /// public Layer Layer { get; internal set; } + /// + /// The path of this property group + /// + public string Path { get; internal set; } + /// /// The parent group of this layer property group, set after construction /// public LayerPropertyGroup Parent { get; internal set; } /// - /// Gets whether this property group's properties are all initialized + /// Gets whether this property group's properties are all initialized /// public bool PropertiesInitialized { get; private set; } @@ -61,10 +66,39 @@ namespace Artemis.Core.Models.Profile public ReadOnlyCollection LayerPropertyGroups => _layerPropertyGroups.AsReadOnly(); /// - /// Called when all layer properties in this property group have been initialized + /// Recursively gets all layer properties on this group and any subgroups /// - protected virtual void OnPropertiesInitialized() + /// + public IReadOnlyCollection GetAllLayerProperties() { + if (!PropertiesInitialized) + return new List(); + if (_allLayerProperties != null) + return _allLayerProperties; + + var result = new List(LayerProperties); + foreach (var layerPropertyGroup in LayerPropertyGroups) + result.AddRange(layerPropertyGroup.GetAllLayerProperties()); + + _allLayerProperties = result.AsReadOnly(); + return _allLayerProperties; + } + + /// + /// Called before properties are fully initialized to allow you to populate + /// on the properties you want + /// + protected abstract void PopulateDefaults(); + + /// + /// Called when all layer properties in this property group have been initialized, you may access all properties on the + /// group here + /// + protected abstract void OnPropertiesInitialized(); + + protected virtual void OnPropertyGroupInitialized() + { + PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); } internal void InitializeProperties(ILayerService layerService, Layer layer, [NotNull] string path) @@ -76,6 +110,7 @@ namespace Artemis.Core.Models.Profile throw new ArtemisCoreException("Layer property group already initialized, wut"); Layer = layer; + Path = path.TrimEnd('.'); // Get all properties with a PropertyDescriptionAttribute foreach (var propertyInfo in GetType().GetProperties()) @@ -110,9 +145,12 @@ namespace Artemis.Core.Models.Profile } } + PopulateDefaults(); + foreach (var layerProperty in _layerProperties.Where(p => !p.IsLoadedFromStorage)) + layerProperty.ApplyDefaultValue(); + OnPropertiesInitialized(); PropertiesInitialized = true; - OnPropertyGroupInitialized(); } @@ -152,25 +190,6 @@ namespace Artemis.Core.Models.Profile OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); } - /// - /// Recursively gets all layer properties on this group and any subgroups - /// - /// - public IReadOnlyCollection GetAllLayerProperties() - { - if (!PropertiesInitialized) - return new List(); - if (_allLayerProperties != null) - return _allLayerProperties; - - var result = new List(LayerProperties); - foreach (var layerPropertyGroup in LayerPropertyGroups) - result.AddRange(layerPropertyGroup.GetAllLayerProperties()); - - _allLayerProperties = result.AsReadOnly(); - return _allLayerProperties; - } - private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) { var pluginGuid = IsCorePropertyGroup || instance.IsCoreProperty ? Constants.CorePluginInfo.Guid : layer.LayerBrush.PluginInfo.Guid; @@ -203,10 +222,5 @@ namespace Artemis.Core.Models.Profile } #endregion - - protected virtual void OnPropertyGroupInitialized() - { - PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); - } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs index cb67e4dfb..81b35a838 100644 --- a/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerTransformProperties.cs @@ -1,5 +1,4 @@ -using Artemis.Core.Models.Profile.LayerProperties; -using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerProperties.Types; using SkiaSharp; @@ -22,13 +21,14 @@ namespace Artemis.Core.Models.Profile [PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)] public FloatLayerProperty Opacity { get; set; } + protected override void PopulateDefaults() + { + Scale.DefaultValue = new SKSize(100, 100); + Opacity.DefaultValue = 100; + } + protected override void OnPropertiesInitialized() { - // Populate defaults - if (!Scale.IsLoadedFromStorage) - Scale.BaseValue = new SKSize(100, 100); - if (!Opacity.IsLoadedFromStorage) - Opacity.BaseValue = 100; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 89d74aa99..6657ade42 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -131,5 +131,10 @@ namespace Artemis.Core.Models.Profile /// Applies the profile element's properties to the underlying storage entity /// internal abstract void ApplyToEntity(); + + public override string ToString() + { + return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index 61701837b..b61a0f0c5 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -2,6 +2,8 @@ using Artemis.Core.Exceptions; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; +using Artemis.Storage; +using Artemis.Storage.Migrations.Interfaces; using Artemis.Storage.Repositories.Interfaces; using LiteDB; using Ninject.Activation; @@ -60,6 +62,18 @@ namespace Artemis.Core.Ninject } }).InSingletonScope(); + Kernel.Bind().ToSelf().InSingletonScope(); + + // Bind all migrations as singletons + Kernel.Bind(x => + { + x.FromAssemblyContaining() + .SelectAllClasses() + .InheritedFrom() + .BindAllInterfaces() + .Configure(c => c.InSingletonScope()); + }); + // Bind all repositories as singletons Kernel.Bind(x => { diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 896186074..6a07ae5b1 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -9,6 +9,8 @@ using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; +using Artemis.Storage; +using Artemis.Storage.Migrations.Interfaces; using Newtonsoft.Json; using RGB.NET.Core; using Serilog; @@ -30,8 +32,9 @@ namespace Artemis.Core.Services private List _modules; private PluginSetting _loggingLevel; - internal CoreService(ILogger logger, ISettingsService settingsService, IPluginService pluginService, IRgbService rgbService, - ISurfaceService surfaceService, IProfileService profileService) + // ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else + internal CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService, + IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService) { _logger = logger; _pluginService = pluginService; @@ -48,6 +51,7 @@ namespace Artemis.Core.Services _pluginService.PluginEnabled += (sender, args) => _modules = _pluginService.GetPluginsOfType(); _pluginService.PluginDisabled += (sender, args) => _modules = _pluginService.GetPluginsOfType(); + ConfigureJsonConvert(); } @@ -99,7 +103,7 @@ namespace Artemis.Core.Services _logger.Information("Initialized without an active surface entity"); _profileService.ActivateDefaultProfiles(); - + OnInitialized(); } diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index cb258039c..a415ba49c 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -100,11 +100,13 @@ namespace Artemis.Core.Services.Storage public void DeleteProfile(Profile profile) { + _logger.Debug("Removing profile " + profile); _profileRepository.Remove(profile.ProfileEntity); } public void UpdateProfile(Profile profile, bool includeChildren) { + _logger.Debug("Updating profile " + profile); var memento = JsonConvert.SerializeObject(profile.ProfileEntity); profile.RedoStack.Clear(); profile.UndoStack.Push(memento); diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj index 5efccadd8..0ae5807f4 100644 --- a/src/Artemis.Storage/Artemis.Storage.csproj +++ b/src/Artemis.Storage/Artemis.Storage.csproj @@ -5,6 +5,7 @@ 7 - + + \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs index 614e575c3..0792079ca 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs @@ -11,6 +11,7 @@ namespace Artemis.Storage.Entities.Profile Leds = new List(); PropertyEntities = new List(); Condition = new List(); + ExpandedPropertyGroups = new List(); } public Guid Id { get; set; } @@ -22,6 +23,7 @@ namespace Artemis.Storage.Entities.Profile public List Leds { get; set; } public List PropertyEntities { get; set; } public List Condition { get; set; } + public List ExpandedPropertyGroups { get; set; } [BsonRef("ProfileEntity")] public ProfileEntity Profile { get; set; } diff --git a/src/Artemis.Storage/Migrations/AttributeBasedPropertiesMigration.cs b/src/Artemis.Storage/Migrations/AttributeBasedPropertiesMigration.cs new file mode 100644 index 000000000..6fa31eb10 --- /dev/null +++ b/src/Artemis.Storage/Migrations/AttributeBasedPropertiesMigration.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class AttributeBasedPropertiesMigration : IStorageMigration + { + public int UserVersion => 1; + public void Apply(LiteRepository repository) + { + if (repository.Database.CollectionExists("ProfileEntity")) + repository.Database.DropCollection("ProfileEntity"); + } + } +} diff --git a/src/Artemis.Storage/Migrations/Interfaces/IStorageMigration.cs b/src/Artemis.Storage/Migrations/Interfaces/IStorageMigration.cs new file mode 100644 index 000000000..a72a3c92b --- /dev/null +++ b/src/Artemis.Storage/Migrations/Interfaces/IStorageMigration.cs @@ -0,0 +1,10 @@ +using LiteDB; + +namespace Artemis.Storage.Migrations.Interfaces +{ + public interface IStorageMigration + { + int UserVersion { get; } + void Apply(LiteRepository repository); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/StorageMigrationService.cs b/src/Artemis.Storage/StorageMigrationService.cs new file mode 100644 index 000000000..35d642e09 --- /dev/null +++ b/src/Artemis.Storage/StorageMigrationService.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; +using Serilog; + +namespace Artemis.Storage +{ + public class StorageMigrationService + { + private readonly ILogger _logger; + private readonly LiteRepository _repository; + private readonly List _migrations; + + public StorageMigrationService(ILogger logger, LiteRepository repository, List migrations) + { + _logger = logger; + _repository = repository; + _migrations = migrations; + + ApplyPendingMigrations(); + } + + public void ApplyPendingMigrations() + { + foreach (var storageMigration in _migrations.OrderBy(m => m.UserVersion)) + { + if (_repository.Database.UserVersion >= storageMigration.UserVersion) + continue; + + _logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", + storageMigration.GetType().Name, _repository.Database.UserVersion, storageMigration.UserVersion); + storageMigration.Apply(_repository); + _repository.Database.UserVersion = storageMigration.UserVersion; + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index f25f3f433..4714f8226 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -20,15 +20,15 @@ - - - + + + - - - + + + diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 95bf6f818..dd8684199 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -115,21 +115,21 @@ - + - - - + + + - - + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs index 3a4364209..b5d81d425 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs @@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract Children = new List(); } - public bool IsExpanded { get; set; } + public virtual bool IsExpanded { get; set; } public abstract bool IsVisible { get; } public List Children { get; set; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index f82d7124f..5a952bbfc 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -160,7 +160,7 @@ @@ -198,13 +198,12 @@ Background="{DynamicResource MaterialDesignCardBackground}"> - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index ae7320010..746746904 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -54,6 +54,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; base.OnInitialActivate(); } @@ -62,6 +63,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; + ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; base.OnClose(); } @@ -83,6 +85,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties NotifyOfPropertyChange(() => TimeCaretPosition); } + private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(TimeCaretPosition)); + } + #region View model managament private void PopulateProperties(ProfileElement profileElement) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 8481ba003..e87e2409a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -18,7 +18,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties LayerPropertyGroup = layerPropertyGroup; PropertyGroupDescription = propertyGroupDescription; - IsExpanded = PropertyGroupDescription.ExpandByDefault; TreePropertyGroupViewModel = new TreePropertyGroupViewModel(this); TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this); @@ -26,6 +25,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties PopulateChildren(); } + public override bool IsExpanded + { + get => LayerPropertyGroup.Layer.IsPropertyGroupExpanded(LayerPropertyGroup); + set => LayerPropertyGroup.Layer.SetPropertyGroupExpanded(LayerPropertyGroup, value); + } + public override bool IsVisible => !LayerPropertyGroup.IsHidden; public IProfileEditorService ProfileEditorService { get; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index cf2ffb3eb..fe0e3abff 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -19,6 +19,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline LayerPropertyViewModel.LayerProperty.KeyframeAdded += LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframeRemoved += LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframesToggled += LayerPropertyOnKeyframeModified; + _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; } private void LayerPropertyOnKeyframeModified(object sender, EventArgs e) @@ -26,6 +27,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline UpdateKeyframes(); } + private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + { + foreach (var timelineKeyframeViewModel in TimelineKeyframeViewModels) + timelineKeyframeViewModel.Update(_profileEditorService.PixelsPerSecond); + } + public LayerPropertyViewModel LayerPropertyViewModel { get; } public override void UpdateKeyframes() @@ -55,6 +62,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public override void Dispose() { + _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; LayerPropertyViewModel.LayerProperty.KeyframeAdded -= LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframeRemoved -= LayerPropertyOnKeyframeModified; LayerPropertyViewModel.LayerProperty.KeyframesToggled -= LayerPropertyOnKeyframeModified; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml index 301ea5ef7..73bc66f98 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -82,6 +82,8 @@ - + - + - + - + diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index 392ba57e4..a2a226976 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -77,8 +77,7 @@ namespace Artemis.UI.Services OnPixelsPerSecondChanged(); } } - - + public LayerPropertyBaseViewModel CreateLayerPropertyViewModel(BaseLayerProperty baseLayerProperty, PropertyDescriptionAttribute propertyDescription) { // Go through the pain of instantiating a generic type VM now via reflection to make things a lot simpler down the line diff --git a/src/Plugins/Artemis.Plugins.Devices.Asus/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Asus/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Asus/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Asus/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/CoolerMasterDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/CoolerMasterDeviceProvider.cs index 4b4c30871..46c4f0779 100644 --- a/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/CoolerMasterDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/CoolerMasterDeviceProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; diff --git a/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.CoolerMaster/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Corsair/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Corsair/Properties/AssemblyInfo.cs index 5923714b1..6a782c8a2 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Corsair/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Corsair/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a779b2f8-c253-4c4b-8634-6eb8f594e96d")] +[assembly: Guid("a779b2f8-c253-4c4b-8634-6eb8f594e96d")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.DMX/Artemis.Plugins.Devices.DMX.csproj b/src/Plugins/Artemis.Plugins.Devices.DMX/Artemis.Plugins.Devices.DMX.csproj index a8943908b..5e719d5ef 100644 --- a/src/Plugins/Artemis.Plugins.Devices.DMX/Artemis.Plugins.Devices.DMX.csproj +++ b/src/Plugins/Artemis.Plugins.Devices.DMX/Artemis.Plugins.Devices.DMX.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Plugins/Artemis.Plugins.Devices.DMX/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.DMX/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.DMX/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.DMX/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.DMX/ViewModels/DMXConfigurationViewModel.cs b/src/Plugins/Artemis.Plugins.Devices.DMX/ViewModels/DMXConfigurationViewModel.cs index e11e7ec62..010933d59 100644 --- a/src/Plugins/Artemis.Plugins.Devices.DMX/ViewModels/DMXConfigurationViewModel.cs +++ b/src/Plugins/Artemis.Plugins.Devices.DMX/ViewModels/DMXConfigurationViewModel.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.ViewModels; namespace Artemis.Plugins.Devices.DMX.ViewModels @@ -11,7 +8,6 @@ namespace Artemis.Plugins.Devices.DMX.ViewModels public DMXConfigurationViewModel(Plugin plugin) : base(plugin) { var dmxInstance = RGB.NET.Devices.DMX.DMXDeviceProvider.Instance; - } } -} +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Logitech/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Logitech/Properties/AssemblyInfo.cs index 59bea6ed1..18cb6793a 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Logitech/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Logitech/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("235a45c7-24ad-4f47-b9d4-cd67e610a04d")] +[assembly: Guid("235a45c7-24ad-4f47-b9d4-cd67e610a04d")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Msi/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Msi/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Msi/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Msi/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Novation/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Novation/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Novation/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Novation/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Razer/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Razer/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Razer/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Razer/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Roccat/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Roccat/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Roccat/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Roccat/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.Roccat/RoccatDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.Roccat/RoccatDeviceProvider.cs index 68449dad9..c2f24c09f 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Roccat/RoccatDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Roccat/RoccatDeviceProvider.cs @@ -2,8 +2,6 @@ using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; -using RGB.NET.Core; -using RGB.NET.Devices.Roccat; namespace Artemis.Plugins.Devices.Roccat { diff --git a/src/Plugins/Artemis.Plugins.Devices.SteelSeries/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.SteelSeries/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.SteelSeries/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.SteelSeries/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.SteelSeries/SteelSeriesDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.SteelSeries/SteelSeriesDeviceProvider.cs index f406829a7..3e63cf3ee 100644 --- a/src/Plugins/Artemis.Plugins.Devices.SteelSeries/SteelSeriesDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.SteelSeries/SteelSeriesDeviceProvider.cs @@ -1,5 +1,4 @@ -using System.IO; -using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; using RGB.NET.Core; diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/Artemis.Plugins.Devices.WS281X.csproj b/src/Plugins/Artemis.Plugins.Devices.WS281X/Artemis.Plugins.Devices.WS281X.csproj index c8d3b4d15..32ebb090d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.WS281X/Artemis.Plugins.Devices.WS281X.csproj +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/Artemis.Plugins.Devices.WS281X.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.WS281X/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.WS281X/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs index 02a69750f..45111b8f3 100644 --- a/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs @@ -14,7 +14,6 @@ namespace Artemis.Plugins.Devices.WS281X // ReSharper disable once UnusedMember.Global public class WS281XDeviceProvider : DeviceProvider { - public PluginSettings Settings { get; } private readonly IRgbService _rgbService; public WS281XDeviceProvider(PluginInfo pluginInfo, IRgbService rgbService, PluginSettings settings) : base(pluginInfo, RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance) @@ -24,6 +23,8 @@ namespace Artemis.Plugins.Devices.WS281X HasConfigurationViewModel = true; } + public PluginSettings Settings { get; } + public override void EnablePlugin() { var definitions = Settings.GetSetting>("DeviceDefinitions"); diff --git a/src/Plugins/Artemis.Plugins.Devices.Wooting/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Devices.Wooting/Properties/AssemblyInfo.cs index 387240943..8ce0de86d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Wooting/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Wooting/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] +[assembly: Guid("c6bdb6d9-062d-4c28-a280-f3bd6197f07f")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Artemis.Plugins.LayerBrushes.Color.csproj b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Artemis.Plugins.LayerBrushes.Color.csproj index 896e3dd0c..69f924a12 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Artemis.Plugins.LayerBrushes.Color.csproj +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Artemis.Plugins.LayerBrushes.Color.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs index 0dca4b77e..435000945 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs @@ -19,19 +19,15 @@ namespace Artemis.Plugins.LayerBrushes.Color [PropertyDescription(Description = "The gradient of the brush")] public ColorGradientLayerProperty Gradient { get; set; } + protected override void PopulateDefaults() + { + GradientType.DefaultValue = LayerBrushes.Color.GradientType.Solid; + Color.DefaultValue = new SKColor(255, 0, 0); + Gradient.DefaultValue = ColorGradient.GetUnicornBarf(); + } + protected override void OnPropertiesInitialized() { - // Populate defaults - if (!GradientType.IsLoadedFromStorage) - GradientType.BaseValue = LayerBrushes.Color.GradientType.Solid; - if (!Color.IsLoadedFromStorage) - Color.BaseValue = new SKColor(255, 0, 0); - if (!Gradient.IsLoadedFromStorage) - { - Gradient.BaseValue = new ColorGradient(); - Gradient.BaseValue.MakeFabulous(); - } - GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged; } diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Properties/AssemblyInfo.cs index a3ad5f4f3..87d3be987 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0f288a66-6eb0-4589-8595-e33a3a3eaea2")] +[assembly: Guid("0f288a66-6eb0-4589-8595-e33a3a3eaea2")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Artemis.Plugins.LayerBrushes.Noise.csproj b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Artemis.Plugins.LayerBrushes.Noise.csproj index 1e44dc52b..32073afb0 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Artemis.Plugins.LayerBrushes.Noise.csproj +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Artemis.Plugins.LayerBrushes.Noise.csproj @@ -23,8 +23,8 @@ - - + + diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs index d01cfa509..f2fda9f33 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs @@ -34,26 +34,18 @@ namespace Artemis.Plugins.LayerBrushes.Noise [PropertyDescription(Description = "The speed at which the noise moves", MinInputValue = 0f, MaxInputValue = 64f)] public FloatLayerProperty AnimationSpeed { get; set; } + protected override void PopulateDefaults() + { + MainColor.DefaultValue = new SKColor(255, 0, 0); + SecondaryColor.DefaultValue = new SKColor(0, 0, 255); + GradientColor.DefaultValue = ColorGradient.GetUnicornBarf(); + Scale.DefaultValue = new SKSize(100, 100); + Hardness.DefaultValue = 500f; + AnimationSpeed.DefaultValue = 25f; + } + protected override void OnPropertiesInitialized() { - // Populate defaults - if (!MainColor.IsLoadedFromStorage) - MainColor.BaseValue = new SKColor(255, 0, 0); - if (!SecondaryColor.IsLoadedFromStorage) - SecondaryColor.BaseValue = new SKColor(0, 0, 255); - if (!GradientColor.IsLoadedFromStorage) - { - GradientColor.BaseValue = new ColorGradient(); - GradientColor.BaseValue.MakeFabulous(); - } - - if (!Scale.IsLoadedFromStorage) - Scale.BaseValue = new SKSize(100, 100); - if (!Hardness.IsLoadedFromStorage) - Hardness.BaseValue = 500f; - if (!AnimationSpeed.IsLoadedFromStorage) - AnimationSpeed.BaseValue = 25f; - ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged; } diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Properties/AssemblyInfo.cs index c730cc822..dfc61567c 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7f4c7ab0-4c9b-452d-afed-34544c903def")] +[assembly: Guid("7f4c7ab0-4c9b-452d-afed-34544c903def")] \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj b/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj index 3a0227d9f..6336fe8ac 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj +++ b/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj @@ -23,8 +23,8 @@ - - + + diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs index a9d4ee2f4..1ce42c686 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralDataModel.cs @@ -1,7 +1,6 @@ using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.DataModels; using Artemis.Core.Plugins.Abstract.DataModels.Attributes; -using SkiaSharp; namespace Artemis.Plugins.Modules.General { @@ -24,14 +23,14 @@ namespace Artemis.Plugins.Modules.General public class PlayerInfo : DataModel { + public PlayerInfo(Module module) : base(module) + { + } + [DataModelProperty(Name = "A test string", Description = "This is a test string that's not of any use outside testing!")] public string TestString { get; set; } [DataModelProperty(Name = "A test boolean", Description = "This is a test boolean that's not of any use outside testing!")] public bool TestBoolean { get; set; } - - public PlayerInfo(Module module) : base(module) - { - } } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 4c9871d64..d3e52b7dc 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -21,7 +21,7 @@ namespace Artemis.Plugins.Modules.General var testSetting = _settings.GetSetting("TestSetting", DateTime.Now); } - + public override void EnablePlugin() { } diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Properties/AssemblyInfo.cs b/src/Plugins/Artemis.Plugins.Modules.General/Properties/AssemblyInfo.cs index f9899c264..c434b9283 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/Properties/AssemblyInfo.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -7,4 +6,4 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e592f239-faa0-4840-9c85-46e5867d06d5")] +[assembly: Guid("e592f239-faa0-4840-9c85-46e5867d06d5")] \ No newline at end of file From 728baa1b7f190c7599454881661b37b1f85f804f Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 29 May 2020 17:21:29 +0200 Subject: [PATCH 14/14] Layer timeline - Show keyframes of collapsed groups Profile editor - Don't reload UI elements on undo/redo if there's nothing to undo/redo Layer properties - Properly show/hide properties --- .../LayerProperties/BaseLayerProperty.cs | 23 ++++++++++- .../Profile/LayerProperties/LayerProperty.cs | 1 + .../Types/ColorGradientLayerProperty.cs | 22 +++++------ .../Models/Profile/LayerPropertyGroup.cs | 21 +++++++++- .../Storage/Interfaces/IProfileService.cs | 4 +- .../Services/Storage/ProfileService.cs | 18 +++++---- .../Entities/Profile/LayerEntity.cs | 1 + .../Controls/ColorPicker.xaml | 12 ++---- .../Controls/GradientPicker.xaml | 4 -- .../LayerProperties/LayerPropertiesView.xaml | 19 +++++----- .../LayerPropertiesViewModel.cs | 7 ++++ .../LayerPropertyGroupViewModel.cs | 12 +++++- .../LayerProperties/LayerPropertyViewModel.cs | 12 +++++- .../Timeline/TimelinePropertyGroupView.xaml | 31 ++------------- .../TimelinePropertyGroupViewModel.cs | 38 ++++++++++++++++++- .../Timeline/TimelineViewModel.cs | 3 ++ .../ProfileEditor/ProfileEditorViewModel.cs | 17 +++++++++ .../Services/ProfileEditorService.cs | 10 ++++- .../ColorBrushProperties.cs | 5 ++- .../NoiseBrushProperties.cs | 5 ++- 20 files changed, 182 insertions(+), 83 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 9dbed60a3..bed89fd32 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -10,6 +10,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties public abstract class BaseLayerProperty { private bool _keyframesEnabled; + private bool _isHidden; internal BaseLayerProperty() { @@ -48,7 +49,15 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// /// Gets or sets whether the property is hidden in the UI /// - public bool IsHidden { get; set; } + public bool IsHidden + { + get => _isHidden; + set + { + _isHidden = value; + OnVisibilityChanged(); + } + } /// /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied @@ -100,6 +109,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties /// public event EventHandler BaseValueChanged; + /// + /// Occurs when the value of the layer property was updated + /// + public event EventHandler VisibilityChanged; + /// /// Occurs when keyframes are enabled/disabled /// @@ -125,6 +139,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties BaseValueChanged?.Invoke(this, EventArgs.Empty); } + protected virtual void OnVisibilityChanged() + { + VisibilityChanged?.Invoke(this, EventArgs.Empty); + } + protected virtual void OnKeyframesToggled() { KeyframesToggled?.Invoke(this, EventArgs.Empty); @@ -143,5 +162,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties #endregion public abstract void ApplyDefaultValue(); + + } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs index 03184f426..edf61b8d5 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -284,6 +284,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties public override void ApplyDefaultValue() { + BaseValue = DefaultValue; CurrentValue = DefaultValue; } } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs index 7b6c36d5f..11cf760d3 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/ColorGradientLayerProperty.cs @@ -4,7 +4,7 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core.Models.Profile.LayerProperties.Types { - /// + /// public class ColorGradientLayerProperty : LayerProperty { internal ColorGradientLayerProperty() @@ -12,21 +12,17 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types KeyframesSupported = false; } - internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage) - { - base.ApplyToLayerProperty(entity, layerPropertyGroup, fromStorage); - - // Don't allow color gradients to be null - if (BaseValue == null) - { - BaseValue = DefaultValue ?? new ColorGradient(); - } - - } - protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { throw new ArtemisCoreException("Color Gradients do not support keyframes."); } + + internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage) + { + base.ApplyToLayerProperty(entity, layerPropertyGroup, fromStorage); + + // Don't allow color gradients to be null + BaseValue ??= DefaultValue ?? new ColorGradient(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index c804705de..5b0dc8a60 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -18,6 +18,7 @@ namespace Artemis.Core.Models.Profile private readonly List _layerProperties; private readonly List _layerPropertyGroups; private ReadOnlyCollection _allLayerProperties; + private bool _isHidden; protected LayerPropertyGroup() { @@ -53,7 +54,15 @@ namespace Artemis.Core.Models.Profile /// /// Gets or sets whether the property is hidden in the UI /// - public bool IsHidden { get; set; } + public bool IsHidden + { + get => _isHidden; + set + { + _isHidden = value; + OnVisibilityChanged(); + } + } /// /// A list of all layer properties in this group @@ -211,6 +220,11 @@ namespace Artemis.Core.Models.Profile internal event EventHandler PropertyGroupOverriding; public event EventHandler PropertyGroupInitialized; + /// + /// Occurs when the value of the layer property was updated + /// + public event EventHandler VisibilityChanged; + internal virtual void OnPropertyGroupUpdating(PropertyGroupUpdatingEventArgs e) { PropertyGroupUpdating?.Invoke(this, e); @@ -221,6 +235,11 @@ namespace Artemis.Core.Models.Profile PropertyGroupOverriding?.Invoke(this, e); } + protected virtual void OnVisibilityChanged() + { + VisibilityChanged?.Invoke(this, EventArgs.Empty); + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 30b2b12bf..9fe885d6b 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -26,13 +26,13 @@ namespace Artemis.Core.Services.Storage.Interfaces /// /// /// - void UndoUpdateProfile(Profile selectedProfile, ProfileModule module); + bool UndoUpdateProfile(Profile selectedProfile, ProfileModule module); /// /// Attempts to restore the profile to the state it had before the last call. /// /// /// - void RedoUpdateProfile(Profile selectedProfile, ProfileModule module); + bool RedoUpdateProfile(Profile selectedProfile, ProfileModule module); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index a415ba49c..733201487 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -123,12 +123,12 @@ namespace Artemis.Core.Services.Storage _profileRepository.Save(profile.ProfileEntity); } - public void UndoUpdateProfile(Profile profile, ProfileModule module) + public bool UndoUpdateProfile(Profile profile, ProfileModule module) { if (!profile.UndoStack.Any()) { _logger.Debug("Undo profile update - Failed, undo stack empty"); - return; + return false; } ActivateProfile(module, null); @@ -140,14 +140,15 @@ namespace Artemis.Core.Services.Storage ActivateProfile(module, profile); _logger.Debug("Undo profile update - Success"); + return true; } - public void RedoUpdateProfile(Profile profile, ProfileModule module) + public bool RedoUpdateProfile(Profile profile, ProfileModule module) { if (!profile.RedoStack.Any()) { _logger.Debug("Redo profile update - Failed, redo empty"); - return; + return false; } ActivateProfile(module, null); @@ -159,6 +160,7 @@ namespace Artemis.Core.Services.Storage ActivateProfile(module, profile); _logger.Debug("Redo profile update - Success"); + return true; } private void InitializeLayerProperties(Profile profile) @@ -169,7 +171,9 @@ namespace Artemis.Core.Services.Storage layer.General.InitializeProperties(_layerService, layer, "General."); if (!layer.Transform.PropertiesInitialized) layer.Transform.InitializeProperties(_layerService, layer, "Transform."); - }; + } + + ; } private void InstantiateLayerBrushes(Profile profile) @@ -178,7 +182,7 @@ namespace Artemis.Core.Services.Storage foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) _layerService.InstantiateLayerBrush(layer); } - + private void ActiveProfilesPopulateLeds(ArtemisSurface surface) { var profileModules = _pluginService.GetPluginsOfType(); @@ -192,7 +196,7 @@ namespace Artemis.Core.Services.Storage foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) InstantiateLayerBrushes(profileModule.ActiveProfile); } - + #region Event handlers private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs index 0792079ca..d3ab661bd 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using LiteDB; namespace Artemis.Storage.Entities.Profile diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml index 4d6db87f3..faba740fa 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml @@ -9,10 +9,6 @@ d:DesignHeight="101.848" d:DesignWidth="242.956"> - - - - @@ -62,14 +58,14 @@ Grid.Row="1" OpacityMask="{x:Null}"> - + - + + Template="{DynamicResource MaterialDesignColorSliderThumb}"> @@ -124,7 +120,7 @@ diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml index 544726848..43bdc3b20 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml @@ -8,10 +8,6 @@ d:DesignHeight="450" d:DesignWidth="800"> - - - - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index 5a952bbfc..84427a570 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -61,7 +61,7 @@ - + @@ -196,16 +196,15 @@ HorizontalAlignment="Stretch" ZIndex="2" Background="{DynamicResource MaterialDesignCardBackground}"> + - - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 746746904..a26b72885 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -92,6 +92,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties #region View model managament + public List GetAllLayerPropertyGroupViewModels() + { + var groups = LayerPropertyGroups.ToList(); + groups.AddRange(groups.SelectMany(g => g.Children).Where(g => g is LayerPropertyGroupViewModel).Cast()); + return groups; + } + private void PopulateProperties(ProfileElement profileElement) { if (SelectedLayer != null) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index e87e2409a..831e18d62 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -23,8 +23,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this); PopulateChildren(); + LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; } + public override bool IsExpanded { get => LayerPropertyGroup.Layer.IsPropertyGroupExpanded(LayerPropertyGroup); @@ -68,7 +70,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public override List GetKeyframes(bool visibleOnly) { var result = new List(); - if (!IsExpanded) + if (visibleOnly && !IsExpanded) return result; foreach (var layerPropertyBaseViewModel in Children) @@ -81,6 +83,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { foreach (var layerPropertyBaseViewModel in Children) layerPropertyBaseViewModel.Dispose(); + + LayerPropertyGroup.VisibilityChanged -= LayerPropertyGroupOnVisibilityChanged; + TimelinePropertyGroupViewModel.Dispose(); } public List GetAllChildren() @@ -95,5 +100,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties return result; } + + private void LayerPropertyGroupOnVisibilityChanged(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(IsVisible)); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 0c193a3d1..31980c093 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Models.Profile.LayerProperties.Attributes; @@ -33,6 +34,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties else PropertyDescription.Name = $"Unknown {typeof(T).Name} property"; } + + LayerProperty.VisibilityChanged += LayerPropertyOnVisibilityChanged; } public override bool IsVisible => !LayerProperty.IsHidden; @@ -51,6 +54,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { TreePropertyViewModel.Dispose(); TimelinePropertyViewModel.Dispose(); + + LayerProperty.VisibilityChanged -= LayerPropertyOnVisibilityChanged; } public void SetCurrentValue(T value, bool saveChanges) @@ -61,6 +66,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties else ProfileEditorService.UpdateProfilePreview(); } + + private void LayerPropertyOnVisibilityChanged(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(IsVisible)); + } } public abstract class LayerPropertyViewModel : LayerPropertyBaseViewModel diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml index 9a54c20ec..f9458962b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupView.xaml @@ -30,42 +30,17 @@ - - - - + Margin="-5,6,0,0"> diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs index 69994652f..6ddd85e30 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs @@ -1,4 +1,8 @@ -using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using System; +using System.ComponentModel; +using System.Linq; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { @@ -7,8 +11,40 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { LayerPropertyGroupViewModel = (LayerPropertyGroupViewModel) layerPropertyBaseViewModel; + TimelineKeyframeViewModels = new BindableCollection(); + + LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; + LayerPropertyGroupViewModel.PropertyChanged += LayerPropertyGroupViewModelOnPropertyChanged; + UpdateKeyframes(); } public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } + public BindableCollection TimelineKeyframeViewModels { get; set; } + public TimelineViewModel TimelineViewModel { get; set; } + + public void UpdateKeyframes() + { + TimelineKeyframeViewModels.Clear(); + TimelineKeyframeViewModels.AddRange(LayerPropertyGroupViewModel.GetKeyframes(false) + .Select(k => LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecond * k.Position.TotalSeconds)); + } + + + public void Dispose() + { + LayerPropertyGroupViewModel.ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; + LayerPropertyGroupViewModel.PropertyChanged -= LayerPropertyGroupViewModelOnPropertyChanged; + } + + private void LayerPropertyGroupViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(LayerPropertyGroupViewModel.IsExpanded)) + UpdateKeyframes(); + } + + private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e) + { + UpdateKeyframes(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index a1b710930..05c9a466d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -32,6 +32,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) { + layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.TimelineViewModel = this; + layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.UpdateKeyframes(); + foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren()) { if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs index bc1ba03db..dcb3cfdff 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileEditorViewModel.cs @@ -11,6 +11,7 @@ using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Screens.Module.ProfileEditor.Dialogs; using Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties; +using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree; using Artemis.UI.Screens.Module.ProfileEditor.Visualization; using Artemis.UI.Services.Interfaces; @@ -110,12 +111,28 @@ namespace Artemis.UI.Screens.Module.ProfileEditor public void Undo() { + // Expanded status is also undone because undoing works a bit crude, that's annoying + var beforeGroups = LayerPropertiesViewModel.GetAllLayerPropertyGroupViewModels(); + var expandedPaths = beforeGroups.Where(g => g.IsExpanded).Select(g => g.LayerPropertyGroup.Path).ToList(); + _profileEditorService.UndoUpdateProfile(Module); + + // Restore the expanded status + foreach (var allLayerPropertyGroupViewModel in LayerPropertiesViewModel.GetAllLayerPropertyGroupViewModels()) + allLayerPropertyGroupViewModel.IsExpanded = expandedPaths.Contains(allLayerPropertyGroupViewModel.LayerPropertyGroup.Path); } public void Redo() { + // Expanded status is also undone because undoing works a bit crude, that's annoying + var beforeGroups = LayerPropertiesViewModel.GetAllLayerPropertyGroupViewModels(); + var expandedPaths = beforeGroups.Where(g => g.IsExpanded).Select(g => g.LayerPropertyGroup.Path).ToList(); + _profileEditorService.RedoUpdateProfile(Module); + + // Restore the expanded status + foreach (var allLayerPropertyGroupViewModel in LayerPropertiesViewModel.GetAllLayerPropertyGroupViewModels()) + allLayerPropertyGroupViewModel.IsExpanded = expandedPaths.Contains(allLayerPropertyGroupViewModel.LayerPropertyGroup.Path); } protected override void OnInitialActivate() diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index a2a226976..2f3fd1bb1 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -157,7 +157,10 @@ namespace Artemis.UI.Services public void UndoUpdateProfile(ProfileModule module) { - _profileService.UndoUpdateProfile(SelectedProfile, module); + var undid = _profileService.UndoUpdateProfile(SelectedProfile, module); + if (!undid) + return; + OnSelectedProfileChanged(new ProfileElementEventArgs(SelectedProfile, SelectedProfile)); if (SelectedProfileElement != null) @@ -173,7 +176,10 @@ namespace Artemis.UI.Services public void RedoUpdateProfile(ProfileModule module) { - _profileService.RedoUpdateProfile(SelectedProfile, module); + var redid = _profileService.RedoUpdateProfile(SelectedProfile, module); + if (!redid) + return; + OnSelectedProfileChanged(new ProfileElementEventArgs(SelectedProfile, SelectedProfile)); if (SelectedProfileElement != null) diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs index 435000945..bb9b05181 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs @@ -28,10 +28,11 @@ namespace Artemis.Plugins.LayerBrushes.Color protected override void OnPropertiesInitialized() { - GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged; + GradientType.BaseValueChanged += (sender, args) => UpdateVisibility(); + UpdateVisibility(); } - private void GradientTypeOnBaseValueChanged(object sender, EventArgs e) + private void UpdateVisibility() { Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid; Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid; diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs index f2fda9f33..61b39c038 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs @@ -46,10 +46,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise protected override void OnPropertiesInitialized() { - ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged; + ColorType.BaseValueChanged += (sender, args) => UpdateVisibility(); + UpdateVisibility(); } - private void ColorTypeOnBaseValueChanged(object sender, EventArgs e) + private void UpdateVisibility() { GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient; MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple;