From f270d786f0edd0ad5009a85b699dc255e0a082ee Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 15 Sep 2020 19:40:57 +0200 Subject: [PATCH] Data bindings - Added the option for different data binding modes --- .../Artemis.Core.csproj.DotSettings | 1 + .../Profile/DataBindings/DataBinding.cs | 239 ++++------------ .../DataBindings/DataBindingModifier.cs | 41 ++- .../Modes/ConditionalDataBinding.cs | 50 ++++ .../DataBindings/Modes/DirectDataBinding.cs | 255 ++++++++++++++++++ .../DataBindings/Modes/IDataBindingMode.cs | 22 ++ src/Artemis.Core/Models/Profile/Layer.cs | 2 +- .../Types/SKSizeLayerProperty.cs | 2 +- .../ConditionalDataBindingEntity.cs | 14 + .../DataBindingConditionValueEntity.cs | 10 + .../Profile/DataBindings/DataBindingEntity.cs | 12 +- .../DataBindings/DirectDataBindingEntity.cs | 18 ++ .../DataBindings/IDataBindingModeEntity.cs | 6 + .../Migrations/M4ProfileSegments.cs | 6 +- .../Migrations/M5DataBindingTypes.cs | 31 +++ .../Ninject/Factories/IVMFactory.cs | 4 + .../DisplayConditionsView.xaml | 23 ++ .../ConditionalDataBindingModeView.xaml | 20 ++ .../ConditionalDataBindingModeViewModel.cs | 28 ++ .../DataBindings/DataBindingView.xaml | 192 ++++++------- .../DataBindings/DataBindingViewModel.cs | 222 +++++++-------- .../DataBindingModifierView.xaml | 2 +- .../DataBindingModifierView.xaml.cs | 14 +- .../DataBindingModifierViewModel.cs | 8 +- .../DirectDataBindingModeView.xaml | 61 +++++ .../DirectDataBindingModeViewModel.cs | 118 ++++++++ .../DataBindings/IDataBindingModeViewModel.cs | 11 + .../LayerProperties/LayerPropertiesView.xaml | 8 +- .../LayerProperties/LayerPropertyViewModel.cs | 2 + .../Controls/PropertyTimelineHeader.cs | 4 +- .../Tree/TreePropertyViewModel.cs | 6 +- .../LayerProperties/Tree/TreeView.xaml | 5 + 32 files changed, 940 insertions(+), 497 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/Modes/ConditionalDataBinding.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs create mode 100644 src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs create mode 100644 src/Artemis.Storage/Entities/Profile/DataBindings/ConditionalDataBindingEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingConditionValueEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/DataBindings/IDataBindingModeEntity.cs create mode 100644 src/Artemis.Storage/Migrations/M5DataBindingTypes.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/{ => DirectDataBinding}/DataBindingModifierView.xaml (98%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/{ => DirectDataBinding}/DataBindingModifierView.xaml.cs (71%) rename src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/{ => DirectDataBinding}/DataBindingModifierViewModel.cs (95%) create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs create mode 100644 src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index abb086d59..d0a878038 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -16,6 +16,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index f167832fe..9f26d5d28 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.DataBindings; namespace Artemis.Core @@ -24,7 +21,7 @@ namespace Artemis.Core ApplyRegistration(dataBindingRegistration); Save(); - Initialize(); + ApplyDataBindingMode(); } internal DataBinding(LayerProperty layerProperty, DataBindingEntity entity) @@ -33,8 +30,8 @@ namespace Artemis.Core Entity = entity; // Load will add children so be initialized before that - Initialize(); Load(); + ApplyDataBindingMode(); } /// @@ -53,16 +50,9 @@ namespace Artemis.Core public DataBindingConverter Converter { get; private set; } /// - /// Gets the currently used instance of the data model that contains the source of the data binding + /// Gets the data binding mode /// - public DataModel SourceDataModel { get; private set; } - - /// - /// Gets the path of the source property in the - /// - public string SourcePropertyPath { get; private set; } - - public DataBindingMode Mode { get; set; } + public IDataBindingMode DataBindingMode { get; private set; } /// /// Gets or sets the easing time of the data binding @@ -74,15 +64,6 @@ namespace Artemis.Core /// public Easings.Functions EasingFunction { get; set; } - /// - /// Gets a list of modifiers applied to this data binding - /// - public IReadOnlyList> Modifiers => _modifiers.AsReadOnly(); - - /// - /// Gets the compiled function that gets the current value of the data binding target - /// - public Func CompiledTargetAccessor { get; private set; } internal DataBindingEntity Entity { get; } @@ -122,72 +103,9 @@ namespace Artemis.Core { _disposed = true; - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; - - foreach (var dataBindingModifier in Modifiers) - dataBindingModifier.Dispose(); + DataBindingMode?.Dispose(); } - /// - /// Adds a modifier to the data binding's collection - /// - public void AddModifier(DataBindingModifier modifier) - { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); - - if (!_modifiers.Contains(modifier)) - { - modifier.DataBinding = this; - _modifiers.Add(modifier); - - OnModifiersUpdated(); - } - } - - /// - /// Removes a modifier from the data binding's collection - /// - public void RemoveModifier(DataBindingModifier modifier) - { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); - - if (_modifiers.Contains(modifier)) - { - modifier.DataBinding = null; - _modifiers.Remove(modifier); - - OnModifiersUpdated(); - } - } - - /// - /// Updates the source of the data binding and re-compiles the expression - /// - /// The data model of the source - /// The path pointing to the source inside the data model - public void UpdateSource(DataModel dataModel, string path) - { - if (_disposed) - throw new ObjectDisposedException("DataBinding"); - - if (dataModel != null && path == null) - throw new ArtemisCoreException("If a data model is provided, a path is also required"); - if (dataModel == null && path != null) - throw new ArtemisCoreException("If path is provided, a data model is also required"); - - if (dataModel != null) - { - if (!dataModel.ContainsPath(path)) - throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); - } - - SourceDataModel = dataModel; - SourcePropertyPath = path; - CreateExpression(); - } /// /// Gets the current value of the data binding @@ -199,15 +117,11 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBinding"); - if (CompiledTargetAccessor == null || Converter == null) + if (Converter == null || DataBindingMode == null) return baseValue; - var dataBindingValue = CompiledTargetAccessor(SourceDataModel); - foreach (var dataBindingModifier in Modifiers) - dataBindingValue = dataBindingModifier.Apply(dataBindingValue); + var value = DataBindingMode.GetValue(baseValue); - TProperty value = Converter.ConvertFromObject(dataBindingValue); - // If no easing is to be applied simple return whatever the current value is if (EasingTime == TimeSpan.Zero || !Converter.SupportsInterpolate) return value; @@ -228,28 +142,6 @@ namespace Artemis.Core return Registration.PropertyExpression.ReturnType; } - /// - /// Returns the type of the source property of this data binding - /// - public Type GetSourceType() - { - return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); - } - - private void Initialize() - { - DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; - - // Source - if (Entity.SourceDataModelGuid != null && SourceDataModel == null) - { - var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel; - if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath)) - UpdateSource(dataModel, Entity.SourcePropertyPath); - } - } - private void ResetEasing(TProperty value) { _previousValue = GetInterpolatedValue(); @@ -284,25 +176,48 @@ namespace Artemis.Core var easingAmount = _easingProgress.TotalSeconds / EasingTime.TotalSeconds; return Converter.Interpolate(_previousValue, _currentValue, Easings.Interpolate(easingAmount, EasingFunction)); } + + #region Mode management - private void CreateExpression() + /// + /// Changes the data binding mode of the data binding to the specified + /// + public void ChangeDataBindingMode(DataBindingModeType dataBindingMode) { - var listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath); - if (listType != null) - throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list"); + switch (dataBindingMode) + { + case DataBindingModeType.Direct: + Entity.DataBindingMode = new DirectDataBindingEntity(); + break; + case DataBindingModeType.Conditional: + Entity.DataBindingMode = new ConditionalDataBindingEntity(); + break; + default: + Entity.DataBindingMode = null; + break; + } - var parameter = Expression.Parameter(typeof(DataModel), "targetDataModel"); - var accessor = SourcePropertyPath.Split('.').Aggregate( - Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type - Expression.Property - ); - - var returnValue = Expression.Convert(accessor, typeof(object)); - - var lambda = Expression.Lambda>(returnValue, parameter); - CompiledTargetAccessor = lambda.Compile(); + ApplyDataBindingMode(); } + private void ApplyDataBindingMode() + { + DataBindingMode?.Dispose(); + DataBindingMode = null; + + switch (Entity.DataBindingMode) + { + case DirectDataBindingEntity directDataBindingEntity: + DataBindingMode = new DirectDataBinding(this, directDataBindingEntity); + break; + case ConditionalDataBindingEntity conditionalDataBindingEntity: + DataBindingMode = new ConditionalDataBinding(this, conditionalDataBindingEntity); + break; + } + } + + #endregion + #region Storage /// @@ -314,15 +229,10 @@ namespace Artemis.Core var registration = LayerProperty.GetDataBindingRegistration(Entity.TargetExpression); ApplyRegistration(registration); - Mode = (DataBindingMode) Entity.DataBindingMode; EasingTime = Entity.EasingTime; EasingFunction = (Easings.Functions) Entity.EasingFunction; - // Data model is done during Initialize - - // Modifiers - foreach (var dataBindingModifierEntity in Entity.Modifiers) - _modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity)); + DataBindingMode?.Load(); } /// @@ -336,54 +246,10 @@ namespace Artemis.Core // General Entity.TargetExpression = Registration.PropertyExpression.ToString(); - Entity.DataBindingMode = (int) Mode; Entity.EasingTime = EasingTime; Entity.EasingFunction = (int) EasingFunction; - // Data model - if (SourceDataModel != null) - { - Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid; - Entity.SourcePropertyPath = SourcePropertyPath; - } - - // Modifiers - Entity.Modifiers.Clear(); - foreach (var dataBindingModifier in Modifiers) - dataBindingModifier.Save(); - } - - #endregion - - #region Event handlers - - private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) - { - var dataModel = e.Registration.DataModel; - if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath)) - UpdateSource(dataModel, Entity.SourcePropertyPath); - } - - private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) - { - if (SourceDataModel != e.Registration.DataModel) - return; - SourceDataModel = null; - CompiledTargetAccessor = null; - } - - #endregion - - #region Events - - /// - /// Occurs when a modifier is added or removed - /// - public event EventHandler ModifiersUpdated; - - protected virtual void OnModifiersUpdated() - { - ModifiersUpdated?.Invoke(this, EventArgs.Empty); + DataBindingMode?.Save(); } #endregion @@ -392,16 +258,21 @@ namespace Artemis.Core /// /// A mode that determines how the data binding is applied to the layer property /// - public enum DataBindingMode + public enum DataBindingModeType { + /// + /// Disables the data binding + /// + None, + /// /// Replaces the layer property value with the data binding value /// - Replace, + Direct, /// - /// Adds the data binding value to the layer property value + /// Replaces the layer property value with the data binding value /// - Add + Conditional, } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs index 5ad50d0ee..d8b2c117d 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBindingModifier.cs @@ -10,49 +10,40 @@ namespace Artemis.Core /// public class DataBindingModifier : IDataBindingModifier { - private DataBinding _dataBinding; private bool _disposed; /// /// Creates a new instance of the class /// - /// The data binding the modifier is to be applied to + /// The direct data binding the modifier is to be applied to /// The type of the parameter, can either be dynamic (based on a data model value) or static - public DataBindingModifier(DataBinding dataBinding, ProfileRightSideType parameterType) + public DataBindingModifier(DirectDataBinding directDataBinding, ProfileRightSideType parameterType) { - _dataBinding = dataBinding ?? throw new ArgumentNullException(nameof(dataBinding)); + DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding)); ParameterType = parameterType; Entity = new DataBindingModifierEntity(); Initialize(); Save(); } - internal DataBindingModifier(DataBinding dataBinding, DataBindingModifierEntity entity) + internal DataBindingModifier(DirectDataBinding directDataBinding, DataBindingModifierEntity entity) { - _dataBinding = dataBinding; + DirectDataBinding = directDataBinding ?? throw new ArgumentNullException(nameof(directDataBinding)); Entity = entity; Load(); Initialize(); } - /// - /// Gets the data binding this modifier is applied to - /// - public DataBinding DataBinding - { - get => _dataBinding; - internal set - { - _dataBinding = value; - CreateExpression(); - } - } - /// /// Gets the type of modifier that is being applied /// public DataBindingModifierType ModifierType { get; private set; } + /// + /// Gets the direct data binding this modifier is applied to + /// + public DirectDataBinding DirectDataBinding { get; } + /// /// Gets the type of the parameter, can either be dynamic (based on a data model value) or static /// @@ -93,8 +84,8 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBindingModifier"); - if (!DataBinding.Entity.Modifiers.Contains(Entity)) - DataBinding.Entity.Modifiers.Add(Entity); + if (!DirectDataBinding.Entity.Modifiers.Contains(Entity)) + DirectDataBinding.Entity.Modifiers.Add(Entity); // Modifier if (ModifierType != null) @@ -177,7 +168,7 @@ namespace Artemis.Core return; } - var targetType = DataBinding.GetTargetType(); + var targetType = DirectDataBinding.DataBinding.GetTargetType(); if (!modifierType.SupportsType(targetType)) { throw new ArtemisCoreException($"Cannot apply modifier type {modifierType.GetType().Name} to this modifier because " + @@ -229,7 +220,7 @@ namespace Artemis.Core ParameterDataModel = null; ParameterPropertyPath = null; - var targetType = DataBinding.GetTargetType(); + var targetType = DirectDataBinding.DataBinding.GetTargetType(); // If not null ensure the types match and if not, convert it if (staticValue != null && staticValue.GetType() == targetType) @@ -271,7 +262,7 @@ namespace Artemis.Core else if (ParameterType == ProfileRightSideType.Static && Entity.ParameterStaticValue != null && ParameterStaticValue == null) { // Use the target type so JSON.NET has a better idea what to do - var targetType = DataBinding.GetTargetType(); + var targetType = DirectDataBinding.DataBinding.GetTargetType(); object staticValue; try @@ -293,7 +284,7 @@ namespace Artemis.Core { CompiledParameterAccessor = null; - if (ModifierType == null || DataBinding == null) + if (ModifierType == null) return; if (ParameterType == ProfileRightSideType.Dynamic && ModifierType.SupportsParameter) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/ConditionalDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/ConditionalDataBinding.cs new file mode 100644 index 000000000..9d844bd9b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/ConditionalDataBinding.cs @@ -0,0 +1,50 @@ +using Artemis.Storage.Entities.Profile.DataBindings; + +namespace Artemis.Core +{ + /// + /// Represents a data binding mode that applies a value depending on conditions + /// + public class ConditionalDataBinding : IDataBindingMode + { + internal ConditionalDataBinding(DataBinding dataBinding, ConditionalDataBindingEntity entity) + { + DataBinding = dataBinding; + Entity = entity; + } + + /// + public DataBinding DataBinding { get; } + + /// + public TProperty GetValue(TProperty baseValue) + { + return default; + } + + internal ConditionalDataBindingEntity Entity { get; } + + #region Storage + + /// + public void Load() + { + } + + /// + public void Save() + { + } + + #endregion + + #region IDisposable + + /// + public void Dispose() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs new file mode 100644 index 000000000..e31296d73 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/DirectDataBinding.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Artemis.Core.DataModelExpansions; +using Artemis.Storage.Entities.Profile.DataBindings; + +namespace Artemis.Core +{ + /// + /// Represents a data binding mode that directly applies a data model value to a data binding + /// + public class DirectDataBinding : IDataBindingMode + { + private readonly List> _modifiers = new List>(); + private bool _disposed; + + internal DirectDataBinding(DataBinding dataBinding, DirectDataBindingEntity entity) + { + DataBinding = dataBinding; + Entity = entity; + + Initialize(); + Load(); + } + + /// + public DataBinding DataBinding { get; } + + internal DirectDataBindingEntity Entity { get; } + + /// + /// Gets the currently used instance of the data model that contains the source of the data binding + /// + public DataModel SourceDataModel { get; private set; } + + /// + /// Gets the path of the source property in the + /// + public string SourcePropertyPath { get; private set; } + + /// + /// Gets a list of modifiers applied to this data binding + /// + public IReadOnlyList> Modifiers => _modifiers.AsReadOnly(); + + /// + /// Gets the compiled function that gets the current value of the data binding target + /// + public Func CompiledTargetAccessor { get; private set; } + + /// + public TProperty GetValue(TProperty baseValue) + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + if (CompiledTargetAccessor == null) + return baseValue; + + var dataBindingValue = CompiledTargetAccessor(SourceDataModel); + foreach (var dataBindingModifier in Modifiers) + dataBindingValue = dataBindingModifier.Apply(dataBindingValue); + + return DataBinding.Converter.ConvertFromObject(dataBindingValue); + } + + #region IDisposable + + /// + public void Dispose() + { + _disposed = true; + + foreach (var dataBindingModifier in Modifiers) + dataBindingModifier.Dispose(); + } + + #endregion + + #region Storage + + /// + public void Load() + { + // Data model is done during Initialize + + // Modifiers + foreach (var dataBindingModifierEntity in Entity.Modifiers) + _modifiers.Add(new DataBindingModifier(this, dataBindingModifierEntity)); + } + + /// + public void Save() + { + // Data model + if (SourceDataModel != null) + { + Entity.SourceDataModelGuid = SourceDataModel.PluginInfo.Guid; + Entity.SourcePropertyPath = SourcePropertyPath; + } + + // Modifiers + Entity.Modifiers.Clear(); + foreach (var dataBindingModifier in Modifiers) + dataBindingModifier.Save(); + } + + #endregion + + #region Initialization + + private void Initialize() + { + DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; + DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; + + // Source + if (Entity.SourceDataModelGuid != null && SourceDataModel == null) + { + var dataModel = DataModelStore.Get(Entity.SourceDataModelGuid.Value)?.DataModel; + if (dataModel != null && dataModel.ContainsPath(Entity.SourcePropertyPath)) + UpdateSource(dataModel, Entity.SourcePropertyPath); + } + } + + private void CreateExpression() + { + var listType = SourceDataModel.GetListTypeInPath(SourcePropertyPath); + if (listType != null) + throw new ArtemisCoreException($"Cannot create a regular accessor at path {SourcePropertyPath} because the path contains a list"); + + var parameter = Expression.Parameter(typeof(DataModel), "targetDataModel"); + var accessor = SourcePropertyPath.Split('.').Aggregate( + Expression.Convert(parameter, SourceDataModel.GetType()), // Cast to the appropriate type + Expression.Property + ); + + var returnValue = Expression.Convert(accessor, typeof(object)); + + var lambda = Expression.Lambda>(returnValue, parameter); + CompiledTargetAccessor = lambda.Compile(); + } + + #endregion + + #region Source + + /// + /// Returns the type of the source property of this data binding + /// + public Type GetSourceType() + { + return SourceDataModel?.GetTypeAtPath(SourcePropertyPath); + } + + + /// + /// Updates the source of the data binding and re-compiles the expression + /// + /// The data model of the source + /// The path pointing to the source inside the data model + public void UpdateSource(DataModel dataModel, string path) + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + if (dataModel != null && path == null) + throw new ArtemisCoreException("If a data model is provided, a path is also required"); + if (dataModel == null && path != null) + throw new ArtemisCoreException("If path is provided, a data model is also required"); + + if (dataModel != null) + { + if (!dataModel.ContainsPath(path)) + throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); + } + + SourceDataModel = dataModel; + SourcePropertyPath = path; + CreateExpression(); + } + + #endregion + + #region Modifiers + + /// + /// Adds a modifier to the data binding's collection + /// + public DataBindingModifier AddModifier(ProfileRightSideType type) + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + var modifier = new DataBindingModifier(this, type); + _modifiers.Add(modifier); + OnModifiersUpdated(); + + return modifier; + } + + /// + /// Removes a modifier from the data binding's collection and disposes it + /// + public void RemoveModifier(DataBindingModifier modifier) + { + if (_disposed) + throw new ObjectDisposedException("DataBinding"); + + if (_modifiers.Contains(modifier)) + { + _modifiers.Remove(modifier); + modifier.Dispose(); + + OnModifiersUpdated(); + } + } + + #endregion + + #region Event handlers + + private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) + { + var dataModel = e.Registration.DataModel; + if (dataModel.PluginInfo.Guid == Entity.SourceDataModelGuid && dataModel.ContainsPath(Entity.SourcePropertyPath)) + UpdateSource(dataModel, Entity.SourcePropertyPath); + } + + private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) + { + if (SourceDataModel != e.Registration.DataModel) + return; + SourceDataModel = null; + CompiledTargetAccessor = null; + } + + #endregion + + #region Events + + /// + /// Occurs when a modifier is added or removed + /// + public event EventHandler ModifiersUpdated; + + protected virtual void OnModifiersUpdated() + { + ModifiersUpdated?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs b/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs new file mode 100644 index 000000000..d9cea1a86 --- /dev/null +++ b/src/Artemis.Core/Models/Profile/DataBindings/Modes/IDataBindingMode.cs @@ -0,0 +1,22 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents a data binding mode + /// + public interface IDataBindingMode : IStorageModel, IDisposable + { + /// + /// Gets the data binding this mode is applied to + /// + DataBinding DataBinding { get; } + + /// + /// Gets the current value of the data binding + /// + /// The base value of the property the data binding is applied to + /// + TProperty GetValue(TProperty baseValue); + } +} diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 97f705548..fd3f22521 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -738,7 +738,7 @@ namespace Artemis.Core private void LayerBrushStoreOnLayerBrushAdded(object sender, LayerBrushStoreEvent e) { - if (LayerBrush != null) + if (LayerBrush != null || General.BrushReference?.CurrentValue == null) return; var current = General.BrushReference.CurrentValue; diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs index b8f54a741..f23a5ac19 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKSizeLayerProperty.cs @@ -7,8 +7,8 @@ namespace Artemis.Core { internal SKSizeLayerProperty() { - RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter()); RegisterDataBindingProperty(size => size.Height, new FloatDataBindingConverter()); + RegisterDataBindingProperty(size => size.Width, new FloatDataBindingConverter()); } /// diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/ConditionalDataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/ConditionalDataBindingEntity.cs new file mode 100644 index 000000000..02a8768e3 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/ConditionalDataBindingEntity.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Artemis.Storage.Entities.Profile.DataBindings +{ + public class ConditionalDataBindingEntity : IDataBindingModeEntity + { + public ConditionalDataBindingEntity() + { + Values = new List(); + } + + public List Values { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingConditionValueEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingConditionValueEntity.cs new file mode 100644 index 000000000..17bace120 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingConditionValueEntity.cs @@ -0,0 +1,10 @@ +using Artemis.Storage.Entities.Profile.Conditions; + +namespace Artemis.Storage.Entities.Profile.DataBindings +{ + public class DataBindingConditionValueEntity + { + public string Value { get; set; } + public DisplayConditionGroupEntity RootGroup { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs index 14011e683..0a0dc030c 100644 --- a/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -1,23 +1,13 @@ using System; -using System.Collections.Generic; namespace Artemis.Storage.Entities.Profile.DataBindings { public class DataBindingEntity { - public DataBindingEntity() - { - Modifiers = new List(); - } - public string TargetExpression { get; set; } - public Guid? SourceDataModelGuid { get; set; } - public string SourcePropertyPath { get; set; } - public int DataBindingMode { get; set; } public TimeSpan EasingTime { get; set; } public int EasingFunction { get; set; } - public List Modifiers { get; set; } - + public IDataBindingModeEntity DataBindingMode { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs new file mode 100644 index 000000000..ce1fc189e --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/DirectDataBindingEntity.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Artemis.Storage.Entities.Profile.DataBindings +{ + public class DirectDataBindingEntity : IDataBindingModeEntity + { + public DirectDataBindingEntity() + { + Modifiers = new List(); + } + + public Guid? SourceDataModelGuid { get; set; } + public string SourcePropertyPath { get; set; } + + public List Modifiers { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataBindings/IDataBindingModeEntity.cs b/src/Artemis.Storage/Entities/Profile/DataBindings/IDataBindingModeEntity.cs new file mode 100644 index 000000000..d1d189588 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/DataBindings/IDataBindingModeEntity.cs @@ -0,0 +1,6 @@ +namespace Artemis.Storage.Entities.Profile.DataBindings +{ + public interface IDataBindingModeEntity + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs index 144d77e9b..b8d07325e 100644 --- a/src/Artemis.Storage/Migrations/M4ProfileSegments.cs +++ b/src/Artemis.Storage/Migrations/M4ProfileSegments.cs @@ -1,8 +1,8 @@ -using System; -using System.Linq; -using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Entities.Profile; using Artemis.Storage.Migrations.Interfaces; using LiteDB; +using System; +using System.Linq; namespace Artemis.Storage.Migrations { diff --git a/src/Artemis.Storage/Migrations/M5DataBindingTypes.cs b/src/Artemis.Storage/Migrations/M5DataBindingTypes.cs new file mode 100644 index 000000000..daede9a17 --- /dev/null +++ b/src/Artemis.Storage/Migrations/M5DataBindingTypes.cs @@ -0,0 +1,31 @@ +using Artemis.Storage.Migrations.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Migrations +{ + public class M5DataBindingTypes : IStorageMigration + { + public int UserVersion => 5; + + public void Apply(LiteRepository repository) + { + var collection = repository.Database.GetCollection("ProfileEntity"); + foreach (var bsonDocument in collection.FindAll()) + { + foreach (var bsonLayer in bsonDocument["Layers"].AsArray) + { + foreach (var bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray) + bsonPropertyEntity["DataBindingEntities"].AsArray.Clear(); + } + + foreach (var bsonLayer in bsonDocument["Folders"].AsArray) + { + foreach (var bsonPropertyEntity in bsonLayer["PropertyEntities"].AsArray) + bsonPropertyEntity["DataBindingEntities"].AsArray.Clear(); + } + + collection.Update(bsonDocument); + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index cd50678b0..8290e0002 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -6,6 +6,8 @@ using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor.DisplayConditions; using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding; +using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree; @@ -87,6 +89,8 @@ namespace Artemis.UI.Ninject.Factories { IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); DataBindingModifierViewModel DataBindingModifierViewModel(DataBindingModifier modifier); + DirectDataBindingModeViewModel DirectDataBindingModeViewModel(DirectDataBinding directDataBinding); + ConditionalDataBindingModeViewModel ConditionalDataBindingModeViewModel(ConditionalDataBinding conditionalDataBinding); } public interface IPropertyVmFactory diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 14f88f21f..838d58ddf 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -31,6 +31,29 @@ + + + + + + + + + + + + + Click the plus icon to start using display conditions! + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml new file mode 100644 index 000000000..461452e5f --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml @@ -0,0 +1,20 @@ + + + + + + Conditional data bindings not yet implemented + + + This is where you'd provide values and their conditions.. :> + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs new file mode 100644 index 000000000..9ef950be5 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs @@ -0,0 +1,28 @@ +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding +{ + public class ConditionalDataBindingModeViewModel : Screen, IDataBindingModeViewModel + { + public ConditionalDataBindingModeViewModel(ConditionalDataBinding conditionalDataBinding) + { + ConditionalDataBinding = conditionalDataBinding; + } + + public ConditionalDataBinding ConditionalDataBinding { get; } + + public void Dispose() + { + } + + public void Update() + { + } + + public object GetTestValue() + { + return null; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml index 12554e038..fc0637bbf 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml @@ -17,152 +17,112 @@ - + - - - - - - - - - - - - + + + + + + + + - - + SelectedValue="{Binding SelectedDataBindingMode}" + ItemsSource="{Binding DataBindingModes}" + SelectedValuePath="Value" + DisplayMemberPath="Description" > + - - + - + - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - Test result - - - - - - - - - - - - Input - - - Output - - - - - - - - - - - - - - - - - - - - - Enable data binding - - - - - - + + - - + + + + + + + + + + + + Test result + + + + + + + + + + + + Input + + + Output + + + + - + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 3ee2cda64..34c1c4c3a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -4,38 +4,32 @@ using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Shared; -using Artemis.UI.Shared.Input; using Artemis.UI.Shared.Services; -using Ninject.Planning.Targets; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { - public class DataBindingViewModel : Screen, IDataBindingViewModel + public class DataBindingViewModel : Conductor, IDataBindingViewModel { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; - private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; private DataBinding _dataBinding; private int _easingTime; - private bool _isDataBindingEnabled; private bool _isEasingTimeEnabled; - private DataBindingMode _selectedDataBindingMode; + private DataBindingModeType _selectedDataBindingMode; private TimelineEasingViewModel _selectedEasingViewModel; - private DataModelDynamicViewModel _targetSelectionViewModel; + private TProperty _testInputValue; private TProperty _testResultValue; private bool _updating; - private bool _canAddModifier; + private bool _isDataBindingEnabled; public DataBindingViewModel(DataBindingRegistration registration, IProfileEditorService profileEditorService, - IDataModelUIService dataModelUIService, IDataBindingsVmFactory dataBindingsVmFactory) { Registration = registration; _profileEditorService = profileEditorService; - _dataModelUIService = dataModelUIService; _dataBindingsVmFactory = dataBindingsVmFactory; if (Registration.Member != null) @@ -43,15 +37,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings else DisplayName = Registration.LayerProperty.PropertyDescription.Name.ToUpper(); - DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingMode))); + DataBindingModes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(DataBindingModeType))); EasingViewModels = new BindableCollection(); - ModifierViewModels = new BindableCollection>(); DataBinding = Registration.DataBinding; - if (DataBinding != null) - DataBinding.ModifiersUpdated += DataBindingOnModifiersUpdated; - - _isDataBindingEnabled = DataBinding != null; Initialize(); } @@ -60,12 +49,21 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings public BindableCollection DataBindingModes { get; } public BindableCollection EasingViewModels { get; } - public BindableCollection> ModifierViewModels { get; } - public DataBindingMode SelectedDataBindingMode + public DataBindingModeType SelectedDataBindingMode { get => _selectedDataBindingMode; - set => SetAndNotify(ref _selectedDataBindingMode, value); + set + { + if (!SetAndNotify(ref _selectedDataBindingMode, value)) return; + ApplyDataBindingMode(); + } + } + + public bool IsDataBindingEnabled + { + get => _isDataBindingEnabled; + set => SetAndNotify(ref _isDataBindingEnabled, value); } public TimelineEasingViewModel SelectedEasingViewModel @@ -98,39 +96,10 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings } } - public DataModelDynamicViewModel TargetSelectionViewModel - { - get => _targetSelectionViewModel; - private set => SetAndNotify(ref _targetSelectionViewModel, value); - } - - public bool IsDataBindingEnabled - { - get => _isDataBindingEnabled; - set - { - if (!SetAndNotify(ref _isDataBindingEnabled, value)) return; - if (value) - EnableDataBinding(); - else - RemoveDataBinding(); - } - } - - public bool CanAddModifier - { - get => _canAddModifier; - private set => SetAndNotify(ref _canAddModifier, value); - } - public DataBinding DataBinding { get => _dataBinding; - set - { - if (!SetAndNotify(ref _dataBinding, value)) return; - UpdateModifierViewModels(); - } + set => SetAndNotify(ref _dataBinding, value); } public TProperty TestInputValue @@ -145,79 +114,70 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings set => SetAndNotify(ref _testResultValue, value); } - public void EnableDataBinding() + public void Dispose() { - if (DataBinding != null) - return; - - DataBinding = Registration.LayerProperty.EnableDataBinding(Registration); - DataBinding.ModifiersUpdated += DataBindingOnModifiersUpdated; - Update(); - - _profileEditorService.UpdateSelectedProfileElement(); - } - - public void RemoveDataBinding() - { - if (DataBinding == null) - return; - - var toDisable = DataBinding; - DataBinding = null; - Registration.LayerProperty.DisableDataBinding(toDisable); - toDisable.ModifiersUpdated -= DataBindingOnModifiersUpdated; - Update(); - - _profileEditorService.UpdateSelectedProfileElement(); - } - - public void AddModifier() - { - if (DataBinding == null) - return; - - var modifier = new DataBindingModifier(DataBinding, ProfileRightSideType.Dynamic); - DataBinding.AddModifier(modifier); - - _profileEditorService.UpdateSelectedProfileElement(); + _profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated; } private void Initialize() { EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)).Cast().Select(v => new TimelineEasingViewModel(v, false))); - TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); - TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected; _profileEditorService.ProfilePreviewUpdated += ProfileEditorServiceOnProfilePreviewUpdated; + CreateDataBindingModeModeViewModel(); Update(); } + private void CreateDataBindingModeModeViewModel() + { + if (DataBinding?.DataBindingMode == null) + { + ActiveItem = null; + return; + } + + switch (DataBinding.DataBindingMode) + { + case DirectDataBinding directDataBinding: + ActiveItem = _dataBindingsVmFactory.DirectDataBindingModeViewModel(directDataBinding); + break; + case ConditionalDataBinding conditionalDataBinding: + ActiveItem = _dataBindingsVmFactory.ConditionalDataBindingModeViewModel(conditionalDataBinding); + break; + } + } + private void Update() { if (_updating) return; - CanAddModifier = IsDataBindingEnabled && DataBinding.SourceDataModel != null; - if (DataBinding == null) { - TargetSelectionViewModel.IsEnabled = false; IsEasingTimeEnabled = false; return; } _updating = true; - SelectedDataBindingMode = DataBinding.Mode; + IsDataBindingEnabled = ActiveItem != null; EasingTime = (int) DataBinding.EasingTime.TotalMilliseconds; SelectedEasingViewModel = EasingViewModels.First(vm => vm.EasingFunction == DataBinding.EasingFunction); IsEasingTimeEnabled = EasingTime > 0; + switch (DataBinding.DataBindingMode) + { + case DirectDataBinding _: + SelectedDataBindingMode = DataBindingModeType.Direct; + break; + case ConditionalDataBinding _: + SelectedDataBindingMode = DataBindingModeType.Conditional; + break; + default: + SelectedDataBindingMode = DataBindingModeType.None; + break; + } - TargetSelectionViewModel.IsEnabled = true; - TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataBinding.SourceDataModel, DataBinding.SourcePropertyPath); - TargetSelectionViewModel.FilterTypes = new[] {DataBinding.GetTargetType()}; - - UpdateModifierViewModels(); + ActiveItem?.Update(); _updating = false; } @@ -229,7 +189,6 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings if (DataBinding != null) { - DataBinding.Mode = SelectedDataBindingMode; DataBinding.EasingTime = TimeSpan.FromMilliseconds(EasingTime); DataBinding.EasingFunction = SelectedEasingViewModel?.EasingFunction ?? Easings.Functions.Linear; } @@ -238,6 +197,27 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings Update(); } + private void ApplyDataBindingMode() + { + if (_updating) + return; + + if (DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None) + { + RemoveDataBinding(); + CreateDataBindingModeModeViewModel(); + return; + } + + if (DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None) + EnableDataBinding(); + + DataBinding.ChangeDataBindingMode(SelectedDataBindingMode); + CreateDataBindingModeModeViewModel(); + + _profileEditorService.UpdateSelectedProfileElement(); + } + private void UpdateTestResult() { if (DataBinding == null) @@ -247,10 +227,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings return; } - var currentValue = TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); - if (currentValue == null) - currentValue = default(TProperty); - + var currentValue = ActiveItem?.GetTestValue() ?? default(TProperty); + TestInputValue = Registration.Converter.ConvertFromObject(currentValue); if (DataBinding != null) TestResultValue = DataBinding.GetValue(TestInputValue); @@ -258,49 +236,31 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings TestInputValue = default; } - private void UpdateModifierViewModels() + private void EnableDataBinding() { - foreach (var dataBindingModifierViewModel in ModifierViewModels) - dataBindingModifierViewModel.Dispose(); - ModifierViewModels.Clear(); + if (DataBinding != null) + return; + DataBinding = Registration.LayerProperty.EnableDataBinding(Registration); + _profileEditorService.UpdateSelectedProfileElement(); + } + + private void RemoveDataBinding() + { if (DataBinding == null) return; - foreach (var dataBindingModifier in DataBinding.Modifiers) - ModifierViewModels.Add(_dataBindingsVmFactory.DataBindingModifierViewModel(dataBindingModifier)); - } - - private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e) - { - UpdateTestResult(); - } - - private void DataBindingOnModifiersUpdated(object sender, EventArgs e) - { - UpdateModifierViewModels(); - } - - private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) - { - DataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.PropertyPath); + var toDisable = DataBinding; + DataBinding = null; + Registration.LayerProperty.DisableDataBinding(toDisable); Update(); _profileEditorService.UpdateSelectedProfileElement(); } - public void Dispose() + private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e) { - _profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated; - TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected; - TargetSelectionViewModel.Dispose(); - if (DataBinding != null) - DataBinding.ModifiersUpdated -= DataBindingOnModifiersUpdated; - - foreach (var dataBindingModifierViewModel in ModifierViewModels) - dataBindingModifierViewModel.Dispose(); - ModifierViewModels.Clear(); - + UpdateTestResult(); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml similarity index 98% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierView.xaml rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml index 48f6007e6..554ed1a01 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierView.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for DataBindingModifierView.xaml diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs similarity index 95% rename from src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs rename to src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs index 431185848..73e129427 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingModifierViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DataBindingModifierViewModel.cs @@ -7,7 +7,7 @@ using Artemis.UI.Shared.Input; using Artemis.UI.Shared.Services; using Stylet; -namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding { public class DataBindingModifierViewModel : PropertyChangedBase, IDisposable { @@ -63,13 +63,13 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings public void Delete() { - Modifier.DataBinding.RemoveModifier(Modifier); + Modifier.DirectDataBinding.RemoveModifier(Modifier); } public void SwapType() { if (Modifier.ParameterType == ProfileRightSideType.Dynamic) - Modifier.UpdateParameter(Modifier.DataBinding.GetSourceType().GetDefault()); + Modifier.UpdateParameter(Modifier.DirectDataBinding.GetSourceType().GetDefault()); else Modifier.UpdateParameter(null, null); @@ -88,7 +88,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private void Update() { - var sourceType = Modifier.DataBinding.GetSourceType(); + var sourceType = Modifier.DirectDataBinding.GetSourceType(); if (sourceType == null) throw new ArtemisUIException("Cannot use a data binding modifier VM for a data binding without a source"); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml new file mode 100644 index 000000000..c71e0d2a9 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs new file mode 100644 index 000000000..e20f2c33d --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs @@ -0,0 +1,118 @@ +using System; +using Artemis.Core; +using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Input; +using Artemis.UI.Shared.Services; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDataBinding +{ + public class DirectDataBindingModeViewModel : Screen, IDataBindingModeViewModel + { + private readonly IDataBindingsVmFactory _dataBindingsVmFactory; + private readonly IDataModelUIService _dataModelUIService; + private readonly IProfileEditorService _profileEditorService; + private DataModelDynamicViewModel _targetSelectionViewModel; + + public DirectDataBindingModeViewModel(DirectDataBinding directDataBinding, + IProfileEditorService profileEditorService, + IDataModelUIService dataModelUIService, + IDataBindingsVmFactory dataBindingsVmFactory) + { + _profileEditorService = profileEditorService; + _dataModelUIService = dataModelUIService; + _dataBindingsVmFactory = dataBindingsVmFactory; + + DirectDataBinding = directDataBinding; + ModifierViewModels = new BindableCollection>(); + + Initialize(); + } + + + public DirectDataBinding DirectDataBinding { get; } + public BindableCollection> ModifierViewModels { get; } + + public DataModelDynamicViewModel TargetSelectionViewModel + { + get => _targetSelectionViewModel; + private set => SetAndNotify(ref _targetSelectionViewModel, value); + } + + public void Update() + { + TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DirectDataBinding.SourceDataModel, DirectDataBinding.SourcePropertyPath); + TargetSelectionViewModel.FilterTypes = new[] {DirectDataBinding.DataBinding.GetTargetType()}; + + UpdateModifierViewModels(); + } + + public object GetTestValue() + { + return TargetSelectionViewModel.SelectedPropertyViewModel?.GetCurrentValue(); + } + + private void Initialize() + { + DirectDataBinding.ModifiersUpdated += DirectDataBindingOnModifiersUpdated; + TargetSelectionViewModel = _dataModelUIService.GetDynamicSelectionViewModel(_profileEditorService.GetCurrentModule()); + TargetSelectionViewModel.PropertySelected += TargetSelectionViewModelOnPropertySelected; + + Update(); + } + + #region Target + + private void TargetSelectionViewModelOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) + { + DirectDataBinding.UpdateSource(e.DataModelVisualizationViewModel.DataModel, e.DataModelVisualizationViewModel.PropertyPath); + Update(); + + _profileEditorService.UpdateSelectedProfileElement(); + } + + #endregion + + #region Modifiers + + public void AddModifier() + { + DirectDataBinding.AddModifier(ProfileRightSideType.Dynamic); + _profileEditorService.UpdateSelectedProfileElement(); + } + + + private void UpdateModifierViewModels() + { + foreach (var dataBindingModifierViewModel in ModifierViewModels) + dataBindingModifierViewModel.Dispose(); + ModifierViewModels.Clear(); + + foreach (var dataBindingModifier in DirectDataBinding.Modifiers) + ModifierViewModels.Add(_dataBindingsVmFactory.DataBindingModifierViewModel(dataBindingModifier)); + } + + private void DirectDataBindingOnModifiersUpdated(object sender, EventArgs e) + { + UpdateModifierViewModels(); + } + + #endregion + + #region IDisposable + + public void Dispose() + { + TargetSelectionViewModel.PropertySelected -= TargetSelectionViewModelOnPropertySelected; + TargetSelectionViewModel.Dispose(); + DirectDataBinding.ModifiersUpdated -= DirectDataBindingOnModifiersUpdated; + + foreach (var dataBindingModifierViewModel in ModifierViewModels) + dataBindingModifierViewModel.Dispose(); + ModifierViewModels.Clear(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs new file mode 100644 index 000000000..7add83036 --- /dev/null +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs @@ -0,0 +1,11 @@ +using System; +using Stylet; + +namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings +{ + public interface IDataBindingModeViewModel : IScreen, IDisposable + { + void Update(); + object GetTestValue(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml index ae4d3544a..3908cac1c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesView.xaml @@ -78,7 +78,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -208,7 +208,7 @@ - - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs index 0d7b3e9b2..1e5993039 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs @@ -29,6 +29,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties set => SetAndNotify(ref _isVisible, value); } + public bool IsExpanded => false; + public void Dispose() { TreePropertyViewModel?.Dispose(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs index 5eb3cbf83..8fbc466ee 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/Controls/PropertyTimelineHeader.cs @@ -129,9 +129,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Controls var typeFace = new Typeface(FontFamily, new FontStyle(), new FontWeight(), new FontStretch()); var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Fill, null, VisualTreeHelper.GetDpi(this).PixelsPerDip); if (x == 0 && OffsetFirstValue) - drawingContext.DrawText(formattedText, new Point(2, 2)); + drawingContext.DrawText(formattedText, new Point(2, 5)); else - drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 2)); + drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 5)); } private void UpdateTimeScale() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index 45c429033..bfc7950b0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Linq; using Artemis.Core; using Artemis.UI.Shared; @@ -58,7 +59,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree } #endregion - + private void ApplyKeyframesEnabled(bool enable) { // If enabling keyframes for the first time, add a keyframe with the current value at the current position @@ -95,8 +96,9 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree #endregion } - public interface ITreePropertyViewModel : IScreen, IDisposable + public interface ITreePropertyViewModel : IScreen, INotifyPropertyChanged, IDisposable { bool HasPropertyInputViewModel { get; } + bool DataBindingsOpen { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml index 6e1f553b6..006b38186 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeView.xaml @@ -95,6 +95,11 @@