From 52a92538469fb133f5856caf0aa6e627eb4cdac3 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 19 Jan 2021 19:32:31 +0100 Subject: [PATCH] Layer property - Removed generic events in favor of regular ones Layer property - Added API for easy hiding/showing properties depending on the state of another property Layer property - Made properties on property groups opt-out instead of opt-in, you can opt out with [LayerPropertyIgnore] attribute --- .../Properties/ColorGradientLayerProperty.cs | 19 ++- .../Properties/FloatRangeLayerProperty.cs | 2 +- .../Properties/IntRangeLayerProperty.cs | 2 +- .../Events/Profiles/LayerPropertyEventArgs.cs | 16 -- .../LayerPropertyIgnoreAttribute.cs | 11 ++ .../Profile/LayerProperties/ILayerProperty.cs | 53 ++++++- .../Profile/LayerProperties/LayerProperty.cs | 139 ++++++++++++------ .../Models/Profile/LayerPropertyGroup.cs | 23 +-- .../PropertyInput/PropertyInputViewModel.cs | 2 +- .../LayerPropertyGroupViewModel.cs | 22 +-- .../Timeline/TimelinePropertyViewModel.cs | 6 +- .../Tree/TreePropertyViewModel.cs | 6 +- 12 files changed, 209 insertions(+), 92 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/LayerProperties/Attributes/LayerPropertyIgnoreAttribute.cs diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs index e3c6fbccf..449461bec 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs @@ -7,7 +7,8 @@ { KeyframesSupported = false; DataBindingsSupported = false; - + DefaultValue = new ColorGradient(); + CurrentValueSet += OnCurrentValueSet; } @@ -25,11 +26,25 @@ throw new ArtemisCoreException("Color Gradients do not support keyframes."); } - private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) + private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) { // Don't allow color gradients to be null if (BaseValue == null) BaseValue = DefaultValue ?? new ColorGradient(); } + + #region Overrides of LayerProperty + + /// + protected override void OnInitialize() + { + // Don't allow color gradients to be null + if (BaseValue == null) + BaseValue = DefaultValue ?? new ColorGradient(); + + base.OnInitialize(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs index 8fa32a603..4b54aca95 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/FloatRangeLayerProperty.cs @@ -22,7 +22,7 @@ ); } - private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) + private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) { // Don't allow the int range to be null BaseValue ??= DefaultValue ?? new FloatRange(0, 0); diff --git a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs index 7c7ef0240..2a9f498fe 100644 --- a/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs +++ b/src/Artemis.Core/DefaultTypes/Properties/IntRangeLayerProperty.cs @@ -22,7 +22,7 @@ ); } - private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) + private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e) { // Don't allow the int range to be null BaseValue ??= DefaultValue ?? new IntRange(0, 0); diff --git a/src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs b/src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs index e08331b9a..86904790a 100644 --- a/src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs +++ b/src/Artemis.Core/Events/Profiles/LayerPropertyEventArgs.cs @@ -2,22 +2,6 @@ namespace Artemis.Core { - /// - /// Provides strongly typed data for layer property events of type . - /// - public class LayerPropertyEventArgs : EventArgs - { - internal LayerPropertyEventArgs(LayerProperty layerProperty) - { - LayerProperty = layerProperty; - } - - /// - /// Gets the layer property this event is related to - /// - public LayerProperty LayerProperty { get; } - } - /// /// Provides data for layer property events. /// diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/LayerPropertyIgnoreAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/LayerPropertyIgnoreAttribute.cs new file mode 100644 index 000000000..2926ae774 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/LayerPropertyIgnoreAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents an attribute that marks a layer property to be ignored + /// + public class LayerPropertyIgnoreAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs index 02a8f1f40..d726d4b2e 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs @@ -29,9 +29,9 @@ namespace Artemis.Core string Path { get; } /// - /// Gets the type of the property + /// Gets the type of the property /// - Type PropertyType { get; } + Type PropertyType { get; } /// /// Initializes the layer property @@ -59,5 +59,54 @@ namespace Artemis.Core /// /// The timeline to apply to the property void Update(Timeline timeline); + + #region Events + + /// + /// Occurs when the layer property is disposed + /// + public event EventHandler Disposed; + + /// + /// Occurs once every frame when the layer property is updated + /// + public event EventHandler? Updated; + + /// + /// Occurs when the current value of the layer property was updated by some form of input + /// + public event EventHandler? CurrentValueSet; + + /// + /// Occurs when the value of the layer property was updated + /// + public event EventHandler? VisibilityChanged; + + /// + /// 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; + + /// + /// Occurs when a data binding has been enabled + /// + public event EventHandler? DataBindingEnabled; + + /// + /// Occurs when a data binding has been disabled + /// + public event EventHandler? DataBindingDisabled; + + #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 9e1123694..bb9760a91 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs @@ -79,6 +79,8 @@ namespace Artemis.Core foreach (IDataBinding dataBinding in _dataBindings) dataBinding.Dispose(); + + Disposed?.Invoke(this, EventArgs.Empty); } } @@ -442,7 +444,7 @@ namespace Artemis.Core DataBinding dataBinding = new(dataBindingRegistration); _dataBindings.Add(dataBinding); - OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); + OnDataBindingEnabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); return dataBinding; } @@ -460,7 +462,7 @@ namespace Artemis.Core if (dataBinding.Registration != null) dataBinding.Registration.DataBinding = null; dataBinding.Dispose(); - OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); + OnDataBindingDisabled(new LayerPropertyEventArgs(dataBinding.LayerProperty)); } private void UpdateDataBindings(Timeline timeline) @@ -474,6 +476,61 @@ namespace Artemis.Core #endregion + #region Visbility + + /// + /// Set up a condition to hide the provided layer property when the condition evaluates to + /// Note: overrides previous calls to IsHiddenWhen and IsVisibleWhen + /// + /// The type of the target layer property + /// The target layer property + /// The condition to evaluate to determine whether to hide the current layer property + public void IsHiddenWhen(TP layerProperty, Func condition) where TP : ILayerProperty + { + IsHiddenWhen(layerProperty, condition, false); + } + + /// + /// Set up a condition to show the provided layer property when the condition evaluates to + /// Note: overrides previous calls to IsHiddenWhen and IsVisibleWhen + /// + /// The type of the target layer property + /// The target layer property + /// The condition to evaluate to determine whether to hide the current layer property + public void IsVisibleWhen(TP layerProperty, Func condition) where TP : ILayerProperty + { + IsHiddenWhen(layerProperty, condition, true); + } + + private void IsHiddenWhen(TP layerProperty, Func condition, bool inverse) where TP : ILayerProperty + { + layerProperty.VisibilityChanged += LayerPropertyChanged; + layerProperty.CurrentValueSet += LayerPropertyChanged; + layerProperty.Disposed += LayerPropertyOnDisposed; + + void LayerPropertyChanged(object? sender, LayerPropertyEventArgs e) + { + if (inverse) + IsHidden = !condition(layerProperty); + else + IsHidden = condition(layerProperty); + } + + void LayerPropertyOnDisposed(object? sender, EventArgs e) + { + layerProperty.VisibilityChanged -= LayerPropertyChanged; + layerProperty.CurrentValueSet -= LayerPropertyChanged; + layerProperty.Disposed -= LayerPropertyOnDisposed; + } + + if (inverse) + IsHidden = !condition(layerProperty); + else + IsHidden = condition(layerProperty); + } + + #endregion + #region Storage private bool _isInitialized; @@ -502,6 +559,8 @@ namespace Artemis.Core if (PropertyDescription.DisableKeyframes) KeyframesSupported = false; + + OnInitialize(); } /// @@ -516,7 +575,6 @@ namespace Artemis.Core if (!IsLoadedFromStorage) ApplyDefaultValue(null); else - { try { if (Entity.Value != null) @@ -526,7 +584,6 @@ namespace Artemis.Core { // ignored for now } - } CurrentValue = BaseValue; KeyframesEnabled = Entity.KeyframesEnabled; @@ -572,56 +629,50 @@ namespace Artemis.Core dataBinding.Save(); } + /// + /// Called when the layer property has been initialized + /// + protected virtual void OnInitialize() + { + } + #endregion #region Events - /// - /// Occurs once every frame when the layer property is updated - /// - public event EventHandler>? Updated; + /// + public event EventHandler? Disposed; - /// - /// Occurs when the current value of the layer property was updated by some form of input - /// - public event EventHandler>? CurrentValueSet; + /// + public event EventHandler? Updated; - /// - /// Occurs when the value of the layer property was updated - /// - public event EventHandler>? VisibilityChanged; + /// + public event EventHandler? CurrentValueSet; - /// - /// Occurs when keyframes are enabled/disabled - /// - public event EventHandler>? KeyframesToggled; + /// + public event EventHandler? VisibilityChanged; - /// - /// Occurs when a new keyframe was added to the layer property - /// - public event EventHandler>? KeyframeAdded; + /// + public event EventHandler? KeyframesToggled; - /// - /// Occurs when a keyframe was removed from the layer property - /// - public event EventHandler>? KeyframeRemoved; + /// + public event EventHandler? KeyframeAdded; - /// - /// Occurs when a data binding has been enabled - /// - public event EventHandler>? DataBindingEnabled; + /// + public event EventHandler? KeyframeRemoved; - /// - /// Occurs when a data binding has been disabled - /// - public event EventHandler>? DataBindingDisabled; + /// + public event EventHandler? DataBindingEnabled; + + /// + public event EventHandler? DataBindingDisabled; /// /// Invokes the event /// protected virtual void OnUpdated() { - Updated?.Invoke(this, new LayerPropertyEventArgs(this)); + Updated?.Invoke(this, new LayerPropertyEventArgs(this)); } /// @@ -629,7 +680,7 @@ namespace Artemis.Core /// protected virtual void OnCurrentValueSet() { - CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); + CurrentValueSet?.Invoke(this, new LayerPropertyEventArgs(this)); LayerPropertyGroup.OnLayerPropertyOnCurrentValueSet(new LayerPropertyEventArgs(this)); } @@ -638,7 +689,7 @@ namespace Artemis.Core /// protected virtual void OnVisibilityChanged() { - VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); + VisibilityChanged?.Invoke(this, new LayerPropertyEventArgs(this)); } /// @@ -646,7 +697,7 @@ namespace Artemis.Core /// protected virtual void OnKeyframesToggled() { - KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); + KeyframesToggled?.Invoke(this, new LayerPropertyEventArgs(this)); } /// @@ -654,7 +705,7 @@ namespace Artemis.Core /// protected virtual void OnKeyframeAdded() { - KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); + KeyframeAdded?.Invoke(this, new LayerPropertyEventArgs(this)); } /// @@ -662,13 +713,13 @@ namespace Artemis.Core /// protected virtual void OnKeyframeRemoved() { - KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); + KeyframeRemoved?.Invoke(this, new LayerPropertyEventArgs(this)); } /// /// Invokes the event /// - protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e) + protected virtual void OnDataBindingEnabled(LayerPropertyEventArgs e) { DataBindingEnabled?.Invoke(this, e); } @@ -676,7 +727,7 @@ namespace Artemis.Core /// /// Invokes the event /// - protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e) + protected virtual void OnDataBindingDisabled(LayerPropertyEventArgs e) { DataBindingDisabled?.Invoke(this, e); } diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index ebc09376a..dda5d03cb 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -57,6 +57,7 @@ namespace Artemis.Core /// /// The parent group of this group /// + [LayerPropertyIgnore] public LayerPropertyGroup? Parent { get; internal set; } /// @@ -127,7 +128,7 @@ namespace Artemis.Core protected abstract void PopulateDefaults(); /// - /// Called when the property group is aactivated + /// Called when the property group is activated /// protected abstract void EnableProperties(); @@ -156,19 +157,23 @@ namespace Artemis.Core ProfileElement = profileElement ?? throw new ArgumentNullException(nameof(profileElement)); Path = path.TrimEnd('.'); - // Get all properties with a PropertyDescriptionAttribute + // Get all properties implementing ILayerProperty or LayerPropertyGroup foreach (PropertyInfo propertyInfo in GetType().GetProperties()) { - Attribute? propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); - if (propertyDescription != null) + if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute))) + continue; + + if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) { - InitializeProperty(propertyInfo, (PropertyDescriptionAttribute) propertyDescription); + PropertyDescriptionAttribute? propertyDescription = + (PropertyDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); + InitializeProperty(propertyInfo, propertyDescription ?? new PropertyDescriptionAttribute()); } - else + else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) { - Attribute? propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); - if (propertyGroupDescription != null) - InitializeChildGroup(propertyInfo, (PropertyGroupDescriptionAttribute) propertyGroupDescription); + PropertyGroupDescriptionAttribute? propertyGroupDescription = + (PropertyGroupDescriptionAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); + InitializeChildGroup(propertyInfo, propertyGroupDescription ?? new PropertyGroupDescriptionAttribute()); } } diff --git a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs index 96c5bb5a5..1f15dbd53 100644 --- a/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs +++ b/src/Artemis.UI.Shared/PropertyInput/PropertyInputViewModel.cs @@ -194,7 +194,7 @@ namespace Artemis.UI.Shared UpdateInputValue(); } - private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs e) + private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs e) { OnDataBindingsChanged(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index 3407026fe..f0cb27d88 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -152,20 +152,22 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties // The group has methods for getting this without reflection but then we lose the order of the properties as they are defined on the group foreach (PropertyInfo propertyInfo in LayerPropertyGroup.GetType().GetProperties()) { - PropertyDescriptionAttribute propertyAttribute = (PropertyDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute)); - PropertyGroupDescriptionAttribute groupAttribute = (PropertyGroupDescriptionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute)); - object value = propertyInfo.GetValue(LayerPropertyGroup); + if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute))) + continue; - // Create VMs for properties on the group - if (propertyAttribute != null && value is ILayerProperty layerProperty) + if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType)) { + ILayerProperty value = (ILayerProperty) propertyInfo.GetValue(LayerPropertyGroup); // Ensure a supported input VM was found, otherwise don't add it - if (_profileEditorService.CanCreatePropertyInputViewModel(layerProperty)) - Items.Add(_layerPropertyVmFactory.LayerPropertyViewModel(layerProperty)); + if (value != null && _profileEditorService.CanCreatePropertyInputViewModel(value)) + Items.Add(_layerPropertyVmFactory.LayerPropertyViewModel(value)); + } + else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType)) + { + LayerPropertyGroup value = (LayerPropertyGroup) propertyInfo.GetValue(LayerPropertyGroup); + if (value != null) + Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(value)); } - // Create VMs for child groups on this group, resulting in a nested structure - else if (groupAttribute != null && value is LayerPropertyGroup layerPropertyGroup) - Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerPropertyGroup)); } } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index d2b041d41..3294fec4b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -77,17 +77,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline base.OnInitialActivate(); } - private void LayerPropertyOnKeyframesToggled(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnKeyframesToggled(object sender, LayerPropertyEventArgs e) { UpdateKeyframes(); } - private void LayerPropertyOnKeyframeRemoved(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnKeyframeRemoved(object sender, LayerPropertyEventArgs e) { UpdateKeyframes(); } - private void LayerPropertyOnKeyframeAdded(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnKeyframeAdded(object sender, LayerPropertyEventArgs e) { UpdateKeyframes(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index d216d8690..c6dc54879 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -119,17 +119,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree LayerPropertyViewModel.IsHighlighted = _profileEditorService.SelectedDataBinding == LayerProperty; } - private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnVisibilityChanged(object sender, LayerPropertyEventArgs e) { LayerPropertyViewModel.IsVisible = !LayerProperty.IsHidden; } - private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs e) { NotifyOfPropertyChange(nameof(HasDataBinding)); } - private void LayerPropertyOnKeyframesToggled(object sender, LayerPropertyEventArgs e) + private void LayerPropertyOnKeyframesToggled(object sender, LayerPropertyEventArgs e) { NotifyOfPropertyChange(nameof(KeyframesEnabled)); }