From 2493c2cbf10f549635b199e7141bc42f40b0a70a Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 10 Jun 2020 00:20:43 +0200 Subject: [PATCH] Layer effects - WIP commit Profile editor - Show brush icons at different parts in the editor --- src/Artemis.Core/Models/Profile/Layer.cs | 61 ++++++--- .../Models/Profile/LayerEffectReference.cs | 21 ++++ .../Models/Profile/LayerGeneralProperties.cs | 3 + .../PropertyGroupDescriptionAttribute.cs | 5 - .../LayerEffectReferenceLayerProperty.cs | 22 ++++ .../Models/Profile/LayerPropertyGroup.cs | 14 +++ .../Plugins/Abstract/LayerEffectProvider.cs | 37 +++++- .../LayerBrush/Abstract/BaseLayerBrush.cs | 5 + .../Abstract/PropertiesLayerBrush.cs | 1 + .../LayerEffect/Abstract/LayerEffect.cs | 11 +- .../Services/Interfaces/ILayerService.cs | 11 ++ src/Artemis.Core/Services/LayerService.cs | 33 ++++- .../Services/Storage/ProfileService.cs | 21 ++-- .../Entities/Profile/PropertyEntity.cs | 2 +- .../EffectPropertyInputView.xaml | 57 +++++++++ .../EffectPropertyInputViewModel.cs | 68 ++++++++++ .../LayerPropertiesViewModel.cs | 15 ++- .../LayerPropertyGroupViewModel.cs | 36 +++++- .../Tree/TreePropertyGroupView.xaml | 119 ++++++++++++++++++ .../LayerProperties/Tree/TreeView.xaml | 6 +- .../Tabs/Plugins/PluginSettingsViewModel.cs | 4 +- src/Artemis.UI/Services/LayerEditorService.cs | 1 + src/Artemis.sln | 16 ++- ...Artemis.Plugins.LayerEffects.Filter.csproj | 40 ++++++ .../FilterEffect.cs | 29 +++++ .../FilterEffectProperties.cs | 20 +++ .../FilterEffectProvider.cs | 16 +++ .../plugin.json | 7 ++ 28 files changed, 618 insertions(+), 63 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/LayerEffectReference.cs create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerEffectReferenceLayerProperty.cs create mode 100644 src/Artemis.UI/PropertyInput/EffectPropertyInputView.xaml create mode 100644 src/Artemis.UI/PropertyInput/EffectPropertyInputViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/Artemis.Plugins.LayerEffects.Filter.csproj create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffect.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProperties.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs create mode 100644 src/Plugins/Artemis.Plugins.LayerEffects.Filter/plugin.json diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index ed449c1aa..cf1bdf273 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -116,6 +116,11 @@ namespace Artemis.Core.Models.Profile /// public BaseLayerBrush LayerBrush { get; internal set; } + /// + /// The layer effect that will apply pre- and/or post-processing to the layer + /// + public BaseLayerEffect LayerEffect { get; set; } + public override string ToString() { return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; @@ -150,6 +155,7 @@ namespace Artemis.Core.Models.Profile General.ApplyToEntity(); Transform.ApplyToEntity(); LayerBrush?.BaseProperties.ApplyToEntity(); + LayerEffect?.BaseProperties.ApplyToEntity(); // LEDs LayerEntity.Leds.Clear(); @@ -220,22 +226,26 @@ namespace Artemis.Core.Models.Profile General.Override(TimeSpan.Zero); Transform.Override(TimeSpan.Zero); LayerBrush.BaseProperties.Override(TimeSpan.Zero); + LayerEffect?.BaseProperties?.Override(TimeSpan.Zero); } else { General.Update(deltaTime); Transform.Update(deltaTime); LayerBrush.BaseProperties.Update(deltaTime); + LayerEffect?.BaseProperties?.Update(deltaTime); } LayerBrush.Update(deltaTime); + LayerEffect?.Update(deltaTime); } public void OverrideProgress(TimeSpan timeOverride) { General.Override(timeOverride); Transform.Override(timeOverride); - LayerBrush?.BaseProperties.Override(timeOverride); + LayerBrush?.BaseProperties?.Override(timeOverride); + LayerEffect?.BaseProperties?.Override(timeOverride); } /// @@ -256,22 +266,26 @@ namespace Artemis.Core.Models.Profile paint.BlendMode = General.BlendMode.CurrentValue; paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); - switch (General.FillType.CurrentValue) - { - case LayerFillType.Stretch: - StretchRender(canvas, canvasInfo, paint); - break; - case LayerFillType.Clip: - ClipRender(canvas, canvasInfo, paint); - break; - default: - throw new ArgumentOutOfRangeException(); - } + LayerEffect?.PreProcess(canvas, canvasInfo, Path, paint); + + if (!LayerBrush.SupportsTransformation) + SimpleRender(canvas, canvasInfo, paint); + else if (General.FillType.CurrentValue == LayerFillType.Stretch) + StretchRender(canvas, canvasInfo, paint); + else if (General.FillType.CurrentValue == LayerFillType.Clip) + ClipRender(canvas, canvasInfo, paint); + + LayerEffect?.PostProcess(canvas, canvasInfo, Path, paint); } canvas.Restore(); } + private void SimpleRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) + { + LayerBrush.InternalRender(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + } + private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { // Apply transformations @@ -295,7 +309,7 @@ namespace Artemis.Core.Models.Profile private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { - // Apply transformations + // Apply transformation var sizeProperty = Transform.Scale.CurrentValue; var rotationProperty = Transform.Rotation.CurrentValue; @@ -412,6 +426,7 @@ namespace Artemis.Core.Models.Profile internal void Deactivate() { DeactivateLayerBrush(); + DeactivateLayerEffect(); } internal void DeactivateLayerBrush() @@ -423,7 +438,19 @@ namespace Artemis.Core.Models.Profile LayerBrush = null; brush.Dispose(); - LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid); + LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush.")); + } + + internal void DeactivateLayerEffect() + { + if (LayerEffect == null) + return; + + var effect = LayerEffect; + LayerEffect = null; + effect.Dispose(); + + LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == effect.PluginInfo.Guid && p.Path.StartsWith("LayerEffect.")); } internal void PopulateLeds(ArtemisSurface surface) @@ -451,6 +478,7 @@ namespace Artemis.Core.Models.Profile public event EventHandler RenderPropertiesUpdated; public event EventHandler ShapePropertiesUpdated; public event EventHandler LayerBrushUpdated; + public event EventHandler LayerEffectUpdated; private void OnRenderPropertiesUpdated() { @@ -467,6 +495,11 @@ namespace Artemis.Core.Models.Profile LayerBrushUpdated?.Invoke(this, EventArgs.Empty); } + internal void OnLayerEffectUpdated() + { + LayerEffectUpdated?.Invoke(this, EventArgs.Empty); + } + #endregion } diff --git a/src/Artemis.Core/Models/Profile/LayerEffectReference.cs b/src/Artemis.Core/Models/Profile/LayerEffectReference.cs new file mode 100644 index 000000000..b0aa157a0 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerEffectReference.cs @@ -0,0 +1,21 @@ +using System; +using Artemis.Core.Plugins.LayerEffect; + +namespace Artemis.Core.Models.Profile +{ + /// + /// A reference to a + /// + public class LayerEffectReference + { + /// + /// The GUID of the plugin the effect descriptor resides in + /// + public Guid EffectPluginGuid { get; set; } + + /// + /// The full type name of the effect descriptor + /// + public string EffectType { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs index e0a2b46d4..a2e3cf51b 100644 --- a/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs +++ b/src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs @@ -18,6 +18,9 @@ namespace Artemis.Core.Models.Profile [PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")] public LayerBrushReferenceLayerProperty BrushReference { get; set; } + [PropertyDescription(Name = "Effect type", Description = "The type of effect to use for this layer")] + public LayerEffectReferenceLayerProperty EffectReference { get; set; } + protected override void PopulateDefaults() { ShapeType.DefaultValue = LayerShapeType.Rectangle; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs index ba725a66a..bc43d5eac 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyGroupDescriptionAttribute.cs @@ -13,10 +13,5 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Attributes /// 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/Types/LayerEffectReferenceLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerEffectReferenceLayerProperty.cs new file mode 100644 index 000000000..6162ce42e --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/LayerEffectReferenceLayerProperty.cs @@ -0,0 +1,22 @@ +using Artemis.Core.Exceptions; + +namespace Artemis.Core.Models.Profile.LayerProperties.Types +{ + /// + /// A special layer property used to configure the selected layer effect + /// + public class LayerEffectReferenceLayerProperty : LayerProperty + { + internal LayerEffectReferenceLayerProperty() + { + KeyframesSupported = false; + } + + protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) + { + throw new ArtemisCoreException("Layer effect references do not support keyframes."); + } + + public static implicit operator LayerEffectReference(LayerEffectReferenceLayerProperty p) => p.CurrentValue; + } +} \ 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 405a00b5c..d2e849fbe 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -8,6 +8,8 @@ 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.Plugins.LayerBrush.Abstract; +using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Entities.Profile; @@ -53,6 +55,16 @@ namespace Artemis.Core.Models.Profile public PropertyGroupDescriptionAttribute GroupDescription { get; internal set; } + /// + /// The layer brush this property group belongs to + /// + public BaseLayerBrush LayerBrush { get; internal set; } + + /// + /// The layer effect this property group belongs to + /// + public BaseLayerEffect LayerEffect { get; internal set; } + /// /// Gets or sets whether the property is hidden in the UI /// @@ -158,6 +170,8 @@ namespace Artemis.Core.Models.Profile instance.Parent = this; instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription; + instance.LayerBrush = LayerBrush; + instance.LayerEffect = LayerEffect; instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); diff --git a/src/Artemis.Core/Plugins/Abstract/LayerEffectProvider.cs b/src/Artemis.Core/Plugins/Abstract/LayerEffectProvider.cs index 6d39c7fdd..86140bfb8 100644 --- a/src/Artemis.Core/Plugins/Abstract/LayerEffectProvider.cs +++ b/src/Artemis.Core/Plugins/Abstract/LayerEffectProvider.cs @@ -1,4 +1,8 @@ -using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Artemis.Core.Plugins.Exceptions; +using Artemis.Core.Plugins.LayerEffect; +using Artemis.Core.Plugins.LayerEffect.Abstract; namespace Artemis.Core.Plugins.Abstract { @@ -6,16 +10,37 @@ namespace Artemis.Core.Plugins.Abstract /// /// Allows you to create one or more s usable by profile layers. /// - public class LayerEffectProvider : Plugin + public abstract class LayerEffectProvider : Plugin { - public override void EnablePlugin() + private readonly List _layerEffectDescriptors; + + protected LayerEffectProvider() { - throw new NotImplementedException(); + _layerEffectDescriptors = new List(); } - public override void DisablePlugin() + /// + /// A read-only collection of all layer effects added with + /// + public ReadOnlyCollection LayerEffectDescriptors => _layerEffectDescriptors.AsReadOnly(); + + /// + /// Adds a layer effect descriptor for a given layer effect, so that it appears in the UI. + /// Note: You do not need to manually remove these on disable + /// + /// The type of the layer effect you wish to register + /// The name to display in the UI + /// The description to display in the UI + /// + /// The Material icon to display in the UI, a full reference can be found + /// here + /// + protected void AddLayerEffectDescriptor(string displayName, string description, string icon) where T : BaseLayerEffect { - throw new NotImplementedException(); + if (!Enabled) + throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled"); + + _layerEffectDescriptors.Add(new LayerEffectDescriptor(displayName, description, icon, typeof(T), this)); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrush/Abstract/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/Abstract/BaseLayerBrush.cs index b60ddb7b5..83754837c 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/Abstract/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/Abstract/BaseLayerBrush.cs @@ -36,6 +36,11 @@ namespace Artemis.Core.Plugins.LayerBrush.Abstract /// public virtual LayerPropertyGroup BaseProperties => null; + /// + /// Gets whether the brush supports transformations, RGB.NET brushes never support transformation + /// + public bool SupportsTransformation { get; protected set; } + public void Dispose() { DisableLayerBrush(); diff --git a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PropertiesLayerBrush.cs index 81410945e..2f964052c 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PropertiesLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PropertiesLayerBrush.cs @@ -38,6 +38,7 @@ namespace Artemis.Core.Plugins.LayerBrush.Abstract internal void InitializeProperties(ILayerService layerService) { Properties = Activator.CreateInstance(); + Properties.LayerBrush = this; Properties.InitializeProperties(layerService, Layer, "LayerBrush."); PropertiesInitialized = true; diff --git a/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs index 725a0e90f..2f82d6ead 100644 --- a/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs @@ -35,19 +35,14 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract internal set => _properties = value; } - /// - /// Called when all layer properties in this effect have been initialized - /// - protected virtual void OnPropertiesInitialized() - { - } - internal void InitializeProperties(ILayerService layerService) { Properties = Activator.CreateInstance(); + Properties.LayerEffect = this; Properties.InitializeProperties(layerService, Layer, "LayerEffect."); - OnPropertiesInitialized(); PropertiesInitialized = true; + + EnableLayerEffect(); } internal override void Initialize(ILayerService layerService) diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs index 0081f6bbe..8132471aa 100644 --- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs +++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs @@ -1,6 +1,8 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Plugins.LayerBrush.Abstract; +using Artemis.Core.Plugins.LayerEffect; +using Artemis.Core.Plugins.LayerEffect.Abstract; namespace Artemis.Core.Services.Interfaces { @@ -23,5 +25,14 @@ namespace Artemis.Core.Services.Interfaces /// The layer to instantiate the brush for /// BaseLayerBrush InstantiateLayerBrush(Layer layer); + + /// + /// Instantiates and adds the described by the provided + /// + /// to the . + /// + /// The layer to instantiate the effect for + /// + BaseLayerEffect InstantiateLayerEffect(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 01bede365..fefdafb71 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -5,6 +5,7 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.LayerBrush; using Artemis.Core.Plugins.LayerBrush.Abstract; +using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Services.Interfaces; using Ninject; using Ninject.Parameters; @@ -34,8 +35,9 @@ namespace Artemis.Core.Services layer.General.InitializeProperties(this, layer, "General."); layer.Transform.InitializeProperties(this, layer, "Transform."); - // With the properties loaded, the layer brush can be instantiated + // With the properties loaded, the layer brush and effect can be instantiated InstantiateLayerBrush(layer); + InstantiateLayerEffect(layer); return layer; } @@ -68,5 +70,34 @@ namespace Artemis.Core.Services return brush; } + + public BaseLayerEffect InstantiateLayerEffect(Layer layer) + { + layer.DeactivateLayerEffect(); + + var descriptorReference = layer.General.EffectReference?.CurrentValue; + if (descriptorReference == null) + return null; + + // Get a matching descriptor + var layerEffectProviders = _pluginService.GetPluginsOfType(); + var descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); + var descriptor = descriptors.FirstOrDefault(d => d.LayerEffectProvider.PluginInfo.Guid == descriptorReference.EffectPluginGuid && + d.LayerEffectType.Name == descriptorReference.EffectType); + + if (descriptor == null) + return null; + + var effect = (BaseLayerEffect)_kernel.Get(descriptor.LayerEffectType); + effect.Layer = layer; + effect.Descriptor = descriptor; + layer.LayerEffect = effect; + + effect.Initialize(this); + effect.Update(0); + layer.OnLayerEffectUpdated(); + + return effect; + } } } \ 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 733201487..f694508ae 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -94,7 +94,7 @@ namespace Artemis.Core.Services.Storage if (profile != null) { InitializeLayerProperties(profile); - InstantiateLayerBrushes(profile); + InstantiateLayers(profile); } } @@ -165,22 +165,25 @@ namespace Artemis.Core.Services.Storage private void InitializeLayerProperties(Profile profile) { - foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) + foreach (var layer in profile.GetAllLayers()) { if (!layer.General.PropertiesInitialized) layer.General.InitializeProperties(_layerService, layer, "General."); if (!layer.Transform.PropertiesInitialized) layer.Transform.InitializeProperties(_layerService, layer, "Transform."); } - - ; } - private void InstantiateLayerBrushes(Profile profile) + private void InstantiateLayers(Profile profile) { - // Only instantiate brushes for layers without an existing brush instance - foreach (var layer in profile.GetAllLayers().Where(l => l.LayerBrush == null)) - _layerService.InstantiateLayerBrush(layer); + // Only instantiate brushes for layers without an existing brush/effect instance + foreach (var layer in profile.GetAllLayers()) + { + if (layer.LayerBrush == null) + _layerService.InstantiateLayerBrush(layer); + if (layer.LayerEffect == null) + _layerService.InstantiateLayerEffect(layer); + } } private void ActiveProfilesPopulateLeds(ArtemisSurface surface) @@ -194,7 +197,7 @@ namespace Artemis.Core.Services.Storage { var profileModules = _pluginService.GetPluginsOfType(); foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - InstantiateLayerBrushes(profileModule.ActiveProfile); + InstantiateLayers(profileModule.ActiveProfile); } #region Event handlers diff --git a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs index de95b56b8..82ef13ecc 100644 --- a/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/PropertyEntity.cs @@ -18,7 +18,7 @@ namespace Artemis.Storage.Entities.Profile public List KeyframeEntities { get; set; } } - + public class KeyframeEntity { public TimeSpan Position { get; set; } diff --git a/src/Artemis.UI/PropertyInput/EffectPropertyInputView.xaml b/src/Artemis.UI/PropertyInput/EffectPropertyInputView.xaml new file mode 100644 index 000000000..cd29f5b29 --- /dev/null +++ b/src/Artemis.UI/PropertyInput/EffectPropertyInputView.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/PropertyInput/EffectPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/EffectPropertyInputViewModel.cs new file mode 100644 index 000000000..841e97ed7 --- /dev/null +++ b/src/Artemis.UI/PropertyInput/EffectPropertyInputViewModel.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Core.Events; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties; +using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Plugins.LayerEffect; +using Artemis.Core.Services.Interfaces; +using Artemis.UI.Shared.PropertyInput; +using Artemis.UI.Shared.Services.Interfaces; + +namespace Artemis.UI.PropertyInput +{ + public class EffectPropertyInputViewModel : PropertyInputViewModel + { + private readonly ILayerService _layerService; + private readonly IPluginService _pluginService; + + public EffectPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, + ILayerService layerService, IPluginService pluginService) : base(layerProperty, profileEditorService) + { + _layerService = layerService; + _pluginService = pluginService; + + _pluginService.PluginEnabled += PluginServiceOnPluginLoaded; + _pluginService.PluginDisabled += PluginServiceOnPluginLoaded; + UpdateEnumValues(); + } + + public List Descriptors { get; set; } + + public LayerEffectDescriptor SelectedDescriptor + { + get => Descriptors.FirstOrDefault(d => d.LayerEffectProvider.PluginInfo.Guid == InputValue?.EffectPluginGuid && d.LayerEffectType.Name == InputValue?.EffectType); + set => SetEffectByDescriptor(value); + } + + public void UpdateEnumValues() + { + var layerEffectProviders = _pluginService.GetPluginsOfType(); + Descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); + NotifyOfPropertyChange(nameof(SelectedDescriptor)); + } + + + public override void Dispose() + { + _pluginService.PluginEnabled -= PluginServiceOnPluginLoaded; + _pluginService.PluginDisabled -= PluginServiceOnPluginLoaded; + base.Dispose(); + } + + protected override void OnInputValueApplied() + { + _layerService.InstantiateLayerEffect(LayerProperty.Layer); + } + + private void SetEffectByDescriptor(LayerEffectDescriptor value) + { + InputValue = new LayerEffectReference {EffectPluginGuid = value.LayerEffectProvider.PluginInfo.Guid, EffectType = value.LayerEffectType.Name}; + } + + private void PluginServiceOnPluginLoaded(object sender, PluginEventArgs e) + { + UpdateEnumValues(); + } + } +} \ 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 2bdd44d26..88a63ce9c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -21,6 +21,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { public class LayerPropertiesViewModel : ProfileEditorPanelViewModel { + private LayerPropertyGroupViewModel _brushPropertyGroup; + public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ICoreService coreService, ISettingsService settingsService) { ProfileEditorService = profileEditorService; @@ -111,6 +113,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) layerPropertyGroupViewModel.Dispose(); LayerPropertyGroups.Clear(); + _brushPropertyGroup = null; if (profileElement is Layer layer) { @@ -148,17 +151,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties if (SelectedLayer == null) return; - var hideRenderRelatedProperties = SelectedLayer.LayerBrush != null && SelectedLayer.LayerBrush.BrushType == LayerBrushType.RgbNet; + var hideRenderRelatedProperties = SelectedLayer?.LayerBrush?.BrushType == LayerBrushType.Regular && SelectedLayer.LayerBrush.SupportsTransformation; SelectedLayer.General.ShapeType.IsHidden = hideRenderRelatedProperties; SelectedLayer.General.FillType.IsHidden = hideRenderRelatedProperties; SelectedLayer.General.BlendMode.IsHidden = hideRenderRelatedProperties; SelectedLayer.Transform.IsHidden = hideRenderRelatedProperties; - // Get rid of the old layer properties group - if (LayerPropertyGroups.Count == 3) + if (_brushPropertyGroup != null) { - LayerPropertyGroups[2].Dispose(); - LayerPropertyGroups.RemoveAt(2); + LayerPropertyGroups.Remove(_brushPropertyGroup); + _brushPropertyGroup = null; } if (SelectedLayer.LayerBrush != null) @@ -170,7 +172,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties Name = SelectedLayer.LayerBrush.Descriptor.DisplayName, Description = SelectedLayer.LayerBrush.Descriptor.Description }; - LayerPropertyGroups.Add(new LayerPropertyGroupViewModel(ProfileEditorService, SelectedLayer.LayerBrush.BaseProperties, brushDescription)); + _brushPropertyGroup = new LayerPropertyGroupViewModel(ProfileEditorService, SelectedLayer.LayerBrush.BaseProperties, brushDescription); + LayerPropertyGroups.Add(_brushPropertyGroup); } TimelineViewModel.UpdateKeyframes(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index dbc638142..885794ed6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -15,7 +15,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties { public class LayerPropertyGroupViewModel : LayerPropertyBaseViewModel { - public LayerPropertyGroupViewModel(IProfileEditorService profileEditorService, LayerPropertyGroup layerPropertyGroup, + public enum ViewModelType + { + General, + Transform, + LayerBrushRoot, + LayerEffectRoot, + None + } + + public LayerPropertyGroupViewModel(IProfileEditorService profileEditorService, LayerPropertyGroup layerPropertyGroup, PropertyGroupDescriptionAttribute propertyGroupDescription) { ProfileEditorService = profileEditorService; @@ -26,11 +35,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties TreePropertyGroupViewModel = new TreePropertyGroupViewModel(this); TimelinePropertyGroupViewModel = new TimelinePropertyGroupViewModel(this); - PopulateChildren(); LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged; + PopulateChildren(); + DetermineType(); } - public override bool IsExpanded { get => LayerPropertyGroup.Layer.IsPropertyGroupExpanded(LayerPropertyGroup); @@ -38,6 +47,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties } public override bool IsVisible => !LayerPropertyGroup.IsHidden; + public ViewModelType GroupType { get; set; } public IProfileEditorService ProfileEditorService { get; } @@ -81,6 +91,20 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties return result; } + private void DetermineType() + { + if (LayerPropertyGroup is LayerGeneralProperties) + GroupType = ViewModelType.General; + else if (LayerPropertyGroup is LayerTransformProperties) + GroupType = ViewModelType.Transform; + else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null) + GroupType = ViewModelType.LayerBrushRoot; + else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null) + GroupType = ViewModelType.LayerEffectRoot; + else + GroupType = ViewModelType.None; + } + private void PopulateChildren() { // Get all properties and property groups and create VMs for them @@ -106,10 +130,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private 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().Name == typeof(LayerProperty<>).Name - ? baseLayerProperty.GetType().GetGenericArguments()[0] + var genericType = baseLayerProperty.GetType().Name == typeof(LayerProperty<>).Name + ? baseLayerProperty.GetType().GetGenericArguments()[0] : baseLayerProperty.GetType().BaseType.GetGenericArguments()[0]; - + // Only create entries for types supported by a tree input VM if (!genericType.IsEnum && ProfileEditorService.RegisteredPropertyEditors.All(r => r.SupportedType != genericType)) return null; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml new file mode 100644 index 000000000..b141c3753 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupView.xaml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + General + + + + + + + + + Transform + + + + + + + + + Brush -  + + + + + + + + + + + + + + + + + Effect -  + + + + + + + \ No newline at end of file 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 73bc66f98..bd84c6cb5 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -96,11 +96,7 @@ - - - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index ee9e09929..4bdf74a6d 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -53,7 +53,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins case Core.Plugins.Abstract.Module _: return PackIconKind.GearBox; case LayerBrushProvider _: - return PackIconKind.Brush; + return PackIconKind.Brush; + case LayerEffectProvider _: + return PackIconKind.AutoAwesome; } return PackIconKind.Plugin; diff --git a/src/Artemis.UI/Services/LayerEditorService.cs b/src/Artemis.UI/Services/LayerEditorService.cs index 3ad8008f2..1dc03c6fe 100644 --- a/src/Artemis.UI/Services/LayerEditorService.cs +++ b/src/Artemis.UI/Services/LayerEditorService.cs @@ -30,6 +30,7 @@ namespace Artemis.UI.Services { _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(BrushPropertyInputViewModel)); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(ColorGradientPropertyInputViewModel)); + _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(EffectPropertyInputViewModel)); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(FloatPropertyInputViewModel)); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(IntPropertyInputViewModel)); _profileEditorService.RegisterPropertyInput(Constants.CorePluginInfo, typeof(SKColorPropertyInputViewModel)); diff --git a/src/Artemis.sln b/src/Artemis.sln index e540c5a59..094416019 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -11,10 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI", "Artemis.UI\Ar {DCF7C321-95DC-4507-BB61-A7C5356E58EC} = {DCF7C321-95DC-4507-BB61-A7C5356E58EC} {E592F239-FAA0-4840-9C85-46E5867D06D5} = {E592F239-FAA0-4840-9C85-46E5867D06D5} {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} = {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} + {62214042-667E-4B29-B64E-1A68CE6FE209} = {62214042-667E-4B29-B64E-1A68CE6FE209} {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} = {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} {A46F278A-FC2C-4342-8455-994D957DDA03} = {A46F278A-FC2C-4342-8455-994D957DDA03} {26902C94-3EBC-4132-B7F0-FFCAB8E150DA} = {26902C94-3EBC-4132-B7F0-FFCAB8E150DA} - {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1} = {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1} {7F4C7AB0-4C9B-452D-AFED-34544C903DEF} = {7F4C7AB0-4C9B-452D-AFED-34544C903DEF} {235A45C7-24AD-4F47-B9D4-CD67E610A04D} = {235A45C7-24AD-4F47-B9D4-CD67E610A04D} {D004FEC9-0CF8-4828-B620-95DBA73201A3} = {D004FEC9-0CF8-4828-B620-95DBA73201A3} @@ -69,6 +69,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{B258 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.LayerBrushes.ColorRgbNet", "Plugins\Artemis.Plugins.LayerBrushes.ColorRgbNet\Artemis.Plugins.LayerBrushes.ColorRgbNet.csproj", "{301C3AAA-9F79-46A5-9B9D-86F076C5BDD1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LayerEffects", "LayerEffects", "{2C1477DC-7A5C-4B65-85DB-1F16A18FB2EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.LayerEffects.Filter", "Plugins\Artemis.Plugins.LayerEffects.Filter\Artemis.Plugins.LayerEffects.Filter.csproj", "{62214042-667E-4B29-B64E-1A68CE6FE209}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -237,6 +241,14 @@ Global {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1}.Release|Any CPU.Build.0 = Release|Any CPU {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1}.Release|x64.ActiveCfg = Release|Any CPU {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1}.Release|x64.Build.0 = Release|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Debug|x64.ActiveCfg = Debug|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Debug|x64.Build.0 = Debug|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Release|Any CPU.Build.0 = Release|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Release|x64.ActiveCfg = Release|Any CPU + {62214042-667E-4B29-B64E-1A68CE6FE209}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -261,6 +273,8 @@ Global {A311DC47-42A2-4DD4-B921-50FBF7A33F41} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} {B258A061-FA19-4835-8DC4-E9C3AE3664A0} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} {301C3AAA-9F79-46A5-9B9D-86F076C5BDD1} = {A311DC47-42A2-4DD4-B921-50FBF7A33F41} + {2C1477DC-7A5C-4B65-85DB-1F16A18FB2EC} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} + {62214042-667E-4B29-B64E-1A68CE6FE209} = {2C1477DC-7A5C-4B65-85DB-1F16A18FB2EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A} diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/Artemis.Plugins.LayerEffects.Filter.csproj b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/Artemis.Plugins.LayerEffects.Filter.csproj new file mode 100644 index 000000000..cfb19af70 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/Artemis.Plugins.LayerEffects.Filter.csproj @@ -0,0 +1,40 @@ + + + netcoreapp3.1 + false + Artemis.Plugins.LayerEffects.Filter + Artemis.Plugins.LayerEffects.Filter + Copyright © 2019 + MinimumRecommendedRules.ruleset + bin\$(Platform)\$(Configuration)\ + true + + + full + 7 + + + pdbonly + 7.3 + + + + PreserveNewest + + + + + + + + + + + + false + + + + + + \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffect.cs new file mode 100644 index 000000000..4db0869f4 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffect.cs @@ -0,0 +1,29 @@ +using Artemis.Core.Plugins.LayerEffect.Abstract; +using SkiaSharp; + +namespace Artemis.Plugins.LayerEffects.Filter +{ + public class FilterEffect : LayerEffect + { + public override void EnableLayerEffect() + { + } + + public override void DisableLayerEffect() + { + } + + public override void Update(double deltaTime) + { + } + + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + { + paint.ImageFilter = SKImageFilter.CreateBlur(Properties.BlurAmount.CurrentValue.Width, Properties.BlurAmount.CurrentValue.Height, paint.ImageFilter); + } + + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + { + } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProperties.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProperties.cs new file mode 100644 index 000000000..b24e09e17 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProperties.cs @@ -0,0 +1,20 @@ +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerProperties.Attributes; +using Artemis.Core.Models.Profile.LayerProperties.Types; + +namespace Artemis.Plugins.LayerEffects.Filter +{ + public class FilterEffectProperties : LayerPropertyGroup + { + [PropertyDescription(Description = "The amount of blur to apply")] + public SKSizeLayerProperty BlurAmount { get; set; } + + protected override void PopulateDefaults() + { + } + + protected override void OnPropertiesInitialized() + { + } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs new file mode 100644 index 000000000..436aad3a8 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/FilterEffectProvider.cs @@ -0,0 +1,16 @@ +using Artemis.Core.Plugins.Abstract; + +namespace Artemis.Plugins.LayerEffects.Filter +{ + public class FilterEffectProvider : LayerEffectProvider + { + public override void EnablePlugin() + { + AddLayerEffectDescriptor("Filter", "A layer effect providing different types of filters", "ImageFilterFrames"); + } + + public override void DisablePlugin() + { + } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/plugin.json b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/plugin.json new file mode 100644 index 000000000..51689721b --- /dev/null +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/plugin.json @@ -0,0 +1,7 @@ +{ + "Guid": "fca5b5d6-3f86-4ea7-a271-06ec3fc219e2", + "Name": "Filter layer effect", + "Description": "A layer effect providing different types of filters.", + "Version": "1.0.0.0", + "Main": "Artemis.Plugins.LayerEffects.Filter.dll" +} \ No newline at end of file