From 022beb6a48726fbcd5cad384fcad7dacd575129d Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 12 Jan 2022 22:11:40 +0100 Subject: [PATCH] Profile editor - Added most properties --- src/Artemis.Core/Extensions/TypeExtensions.cs | 3 +- src/Artemis.Core/Models/Profile/Layer.cs | 6 +- .../Commands/ChangeLayerBrush.cs | 44 ++++++ .../PropertyInput/PropertyInputViewModel.cs | 75 +++++----- .../Artemis.UI.Shared/Styles/TextBox.axaml | 37 ++++- src/Avalonia/Artemis.UI/Artemis.UI.csproj | 22 +-- .../Converters/PropertyTreeMarginConverter.cs | 5 +- .../Display/SKColorDataModelDisplayView.xaml | 64 ++++++++ .../SKColorDataModelDisplayViewModel.cs | 8 + .../PropertyInput/BoolPropertyInputView.axaml | 8 + .../BoolPropertyInputView.axaml.cs | 20 +++ .../BoolPropertyInputViewModel.cs | 13 ++ .../BrushPropertyInputView.axaml | 45 ++++++ .../BrushPropertyInputView.axaml.cs | 20 +++ .../BrushPropertyInputViewModel.cs | 82 +++++++++++ .../ColorGradientPropertyInputView.axaml | 8 + .../ColorGradientPropertyInputView.axaml.cs | 20 +++ .../ColorGradientPropertyInputViewModel.cs | 19 +++ .../PropertyInput/EnumPropertyInputView.axaml | 8 + .../EnumPropertyInputView.axaml.cs | 21 +++ .../EnumPropertyInputViewModel.cs | 17 +++ .../FloatPropertyInputView.axaml | 18 +++ .../FloatPropertyInputView.axaml.cs | 25 ++++ .../FloatPropertyInputViewModel.cs | 20 +++ .../FloatRangePropertyInputView.axaml | 8 + .../FloatRangePropertyInputView.axaml.cs | 20 +++ .../FloatRangePropertyInputViewModel.cs | 68 +++++++++ .../PropertyInput/IntPropertyInputView.axaml | 18 +++ .../IntPropertyInputView.axaml.cs | 25 ++++ .../IntPropertyInputViewModel.cs | 20 +++ .../IntRangePropertyInputView.axaml | 8 + .../IntRangePropertyInputView.axaml.cs | 20 +++ .../IntRangePropertyInputViewModel.cs | 68 +++++++++ .../SKColorPropertyInputView.axaml | 8 + .../SKColorPropertyInputView.axaml.cs | 20 +++ .../SKColorPropertyInputViewModel.cs | 17 +++ .../SKPointPropertyInputView.axaml | 26 ++++ .../SKPointPropertyInputView.axaml.cs | 28 ++++ .../SKPointPropertyInputViewModel.cs | 50 +++++++ .../SKSizePropertyInputView.axaml | 27 ++++ .../SKSizePropertyInputView.axaml.cs | 28 ++++ .../SKSizePropertyInputViewModel.cs | 52 +++++++ .../Panels/MenuBar/MenuBarViewModel.cs | 57 ++------ .../ProfileElementPropertiesView.axaml | 19 ++- .../ProfileElementPropertiesViewModel.cs | 138 +++++++----------- .../ProfileElementPropertyGroupViewModel.cs | 51 ++++--- .../ProfileElementPropertyViewModel.cs | 3 + .../Tree/Dialogs/LayerBrushPresetViewModel.cs | 15 ++ .../Tree/TreeGroupView.axaml | 10 +- .../Tree/TreePropertyView.axaml | 59 +++++++- .../Tree/TreePropertyViewModel.cs | 2 - .../ProfileTree/FolderTreeItemViewModel.cs | 5 +- .../ProfileTree/LayerTreeItemViewModel.cs | 5 +- .../ProfileTree/ProfileTreeViewModel.cs | 7 +- .../Panels/ProfileTree/TreeItemViewModel.cs | 15 +- .../Panels/StatusBar/StatusBarView.axaml | 44 ++++++ .../Panels/StatusBar/StatusBarView.axaml.cs | 18 +++ .../Panels/StatusBar/StatusBarViewModel.cs | 62 ++++++++ .../ProfileEditor/ProfileEditorView.axaml | 8 +- .../ProfileEditor/ProfileEditorViewModel.cs | 6 +- .../Artemis.UI/Screens/Root/RootViewModel.cs | 6 + .../Services/RegistrationService.cs | 26 +++- 62 files changed, 1424 insertions(+), 251 deletions(-) create mode 100644 src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayView.xaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarViewModel.cs diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index 672c76e15..9a148caef 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Humanizer; @@ -92,7 +93,7 @@ namespace Artemis.Core /// /// The value to check /// if the value is of a numeric type, otherwise - public static bool IsNumber(this object value) + public static bool IsNumber([NotNullWhenAttribute(true)] this object? value) { return value is sbyte || value is byte diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 90bf0b309..fab80bf52 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -729,7 +729,7 @@ namespace Artemis.Core // Ensure the brush reference matches the brush LayerBrushReference? current = General.BrushReference.BaseValue; if (!descriptor.MatchesLayerBrushReference(current)) - General.BrushReference.BaseValue = new LayerBrushReference(descriptor); + General.BrushReference.SetCurrentValue(new LayerBrushReference(descriptor), null); ActivateLayerBrush(); } @@ -760,6 +760,10 @@ namespace Artemis.Core : null; descriptor?.CreateInstance(this); + General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; + General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; + Transform.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; + OnLayerBrushUpdated(); ClearBrokenState("Failed to initialize layer brush"); } diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs new file mode 100644 index 000000000..2032431a6 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs @@ -0,0 +1,44 @@ +using Artemis.Core; +using Artemis.Core.LayerBrushes; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to change the brush of a layer. +/// +public class ChangeLayerBrush : IProfileEditorCommand +{ + private readonly Layer _layer; + private readonly LayerBrushDescriptor _layerBrushDescriptor; + private readonly LayerBrushDescriptor? _previousDescriptor; + + /// + /// Creates a new instance of the class. + /// + public ChangeLayerBrush(Layer layer, LayerBrushDescriptor layerBrushDescriptor) + { + _layer = layer; + _layerBrushDescriptor = layerBrushDescriptor; + _previousDescriptor = layer.LayerBrush?.Descriptor; + } + + #region Implementation of IProfileEditorCommand + + /// + public string DisplayName => "Change layer brush"; + + /// + public void Execute() + { + _layer.ChangeLayerBrush(_layerBrushDescriptor); + } + + /// + public void Undo() + { + if (_previousDescriptor != null) + _layer.ChangeLayerBrush(_previousDescriptor); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs b/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs index be92954d3..a7a8f18ef 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/PropertyInput/PropertyInputViewModel.cs @@ -6,6 +6,7 @@ using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Avalonia.Controls.Mixins; using ReactiveUI; +using ReactiveUI.Validation.Helpers; namespace Artemis.UI.Shared.Services.PropertyInput; @@ -15,10 +16,11 @@ namespace Artemis.UI.Shared.Services.PropertyInput; /// The type of property this input view model supports public abstract class PropertyInputViewModel : PropertyInputViewModel { - [AllowNull] - private T _inputValue; - private bool _inputDragging; private T _dragStartValue; + private bool _inputDragging; + + [AllowNull] private T _inputValue; + private TimeSpan _time; /// @@ -52,7 +54,7 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel .DisposeWith(d); }); } - + /// /// Gets the layer property this view model is editing /// @@ -69,7 +71,7 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel public IProfileEditorService ProfileEditorService { get; } /// - /// Gets the property input service + /// Gets the property input service /// public IPropertyInputService PropertyInputService { get; } @@ -89,7 +91,7 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel /// /// Gets or sets the input value /// - [AllowNull] + [MaybeNull] public T InputValue { get => _inputValue; @@ -126,13 +128,6 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel ProfileEditorService.ExecuteCommand(new UpdateLayerProperty(LayerProperty, _inputValue, _dragStartValue, _time)); } - /// - /// Called when the input value has been applied to the layer property - /// - protected virtual void OnInputValueApplied() - { - } - /// /// Called when the input value has changed /// @@ -147,23 +142,23 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel { } - protected virtual T GetDragStartValue() + /// + /// Called when dragging starts to get the initial value before dragging begun + /// + /// The initial value before dragging begun + protected virtual T? GetDragStartValue() { return InputValue; } /// - /// Applies the input value to the layer property + /// Applies the input value to the layer property using an . /// - protected void ApplyInputValue() + protected virtual void ApplyInputValue() { - OnInputValueChanged(); - LayerProperty.SetCurrentValue(_inputValue, _time); - OnInputValueApplied(); - if (InputDragging) ProfileEditorService.ChangeTime(_time); - else + else if (ValidationContext.IsValid) ProfileEditorService.ExecuteCommand(new UpdateLayerProperty(LayerProperty, _inputValue, _time)); } @@ -186,23 +181,12 @@ public abstract class PropertyInputViewModel : PropertyInputViewModel this.RaisePropertyChanged(nameof(IsEnabled)); OnDataBindingsChanged(); } - - private void LayerPropertyOnUpdated(object? sender, EventArgs e) - { - UpdateInputValue(); - } - - private void OnDataBindingChange(object? sender, DataBindingEventArgs e) - { - this.RaisePropertyChanged(nameof(IsEnabled)); - OnDataBindingsChanged(); - } } /// /// For internal use only, implement instead. /// -public abstract class PropertyInputViewModel : ActivatableViewModelBase +public abstract class PropertyInputViewModel : ReactiveValidationObject, IActivatableViewModel, IDisposable { /// /// Prevents this type being implemented directly, implement @@ -210,4 +194,29 @@ public abstract class PropertyInputViewModel : ActivatableViewModelBase /// // ReSharper disable once UnusedMember.Global internal abstract object InternalGuard { get; } + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + } + + #region Implementation of IActivatableViewModel + + /// + public ViewModelActivator Activator { get; } = new(); + + #endregion + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml b/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml index acc19236b..5ce52297f 100644 --- a/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml +++ b/src/Avalonia/Artemis.UI.Shared/Styles/TextBox.axaml @@ -1,5 +1,6 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> @@ -9,12 +10,20 @@ - - - - - - + + + + + + Bluasdadseheh + Bluheheheheh + Bluhgfdgdsheheh + + + Bluasdadseheh + Bluheheheheh + Bluhgfdgdsheheh + @@ -22,7 +31,19 @@ + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj index 01e3602d7..e9c0b05c0 100644 --- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj +++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj @@ -1,4 +1,4 @@ - + Library net6.0 @@ -47,24 +47,4 @@ - - - DebugSettingsView.axaml - - - ProfileElementPropertiesView.axaml - - - BrushConfigurationWindowView.axaml - - - SidebarCategoryEditView.axaml - - - ProfileConfigurationEditView.axaml - - - - - \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs b/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs index 71aad6a8f..ccc0676ae 100644 --- a/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs +++ b/src/Avalonia/Artemis.UI/Converters/PropertyTreeMarginConverter.cs @@ -14,9 +14,8 @@ public class PropertyTreeMarginConverter : IValueConverter { if (value is TreeGroupViewModel treeGroupViewModel) return new Thickness(Length * treeGroupViewModel.GetDepth(), 0, 0, 0); - // TODO - // if (value is ITreePropertyViewModel treePropertyViewModel) - // return new Thickness(Length * treePropertyViewModel.GetDepth(), 0, 0, 0); + if (value is ITreePropertyViewModel treePropertyViewModel) + return new Thickness(Length * treePropertyViewModel.GetDepth(), 0, 0, 0); return new Thickness(0); } diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayView.xaml b/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayView.xaml new file mode 100644 index 000000000..e6215d1a6 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayView.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayViewModel.cs new file mode 100644 index 000000000..c08de2224 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/DataModel/Display/SKColorDataModelDisplayViewModel.cs @@ -0,0 +1,8 @@ +using Artemis.UI.Shared.DataModelVisualization; +using SkiaSharp; + +namespace Artemis.UI.DefaultTypes.DataModel.Display; + +public class SKColorDataModelDisplayViewModel : DataModelDisplayViewModel +{ +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml new file mode 100644 index 000000000..84c152c51 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml.cs new file mode 100644 index 000000000..eadaee283 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class BoolPropertyInputView : ReactiveUserControl + { + public BoolPropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs new file mode 100644 index 000000000..60a4fbc8a --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class BoolPropertyInputViewModel : PropertyInputViewModel +{ + public BoolPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml new file mode 100644 index 000000000..adab728e6 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml.cs new file mode 100644 index 000000000..53f2e2f1b --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class BrushPropertyInputView : ReactiveUserControl + { + public BrushPropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs new file mode 100644 index 000000000..796b1e439 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputViewModel.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.Core.LayerBrushes; +using Artemis.Core.Services; +using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.Dialogs; +using Artemis.UI.Shared.Services.Interfaces; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Artemis.UI.Shared.Services.PropertyInput; +using Avalonia.Controls.Mixins; +using Avalonia.Threading; +using ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class BrushPropertyInputViewModel : PropertyInputViewModel +{ + private readonly IPluginManagementService _pluginManagementService; + private readonly IProfileEditorService _profileEditorService; + private readonly IWindowService _windowService; + private ObservableCollection _descriptors; + + public BrushPropertyInputViewModel(LayerProperty layerProperty, IPluginManagementService pluginManagementService, IWindowService windowService, + IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + _pluginManagementService = pluginManagementService; + _windowService = windowService; + _profileEditorService = profileEditorService; + _descriptors = new ObservableCollection(pluginManagementService.GetFeaturesOfType().SelectMany(l => l.LayerBrushDescriptors)); + + this.WhenActivated(d => + { + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) + .Subscribe(e => UpdateDescriptorsIfChanged(e.EventArgs)) + .DisposeWith(d); + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x) + .Subscribe(e => UpdateDescriptorsIfChanged(e.EventArgs)) + .DisposeWith(d); + }); + } + + public ObservableCollection Descriptors + { + get => _descriptors; + set => this.RaiseAndSetIfChanged(ref _descriptors, value); + } + + public LayerBrushDescriptor? SelectedDescriptor + { + get => Descriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(InputValue)); + set => SetBrushByDescriptor(value); + } + + /// + protected override void ApplyInputValue() + { + if (LayerProperty.ProfileElement is not Layer layer || SelectedDescriptor == null) + return; + + _profileEditorService.ExecuteCommand(new ChangeLayerBrush(layer, SelectedDescriptor)); + if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any()) + Dispatcher.UIThread.InvokeAsync(() => _windowService.CreateContentDialog().WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush)).ShowAsync()); + } + + private void UpdateDescriptorsIfChanged(PluginFeatureEventArgs e) + { + if (e.PluginFeature is not LayerBrushProvider) + return; + + Descriptors = new ObservableCollection(_pluginManagementService.GetFeaturesOfType().SelectMany(l => l.LayerBrushDescriptors)); + } + + private void SetBrushByDescriptor(LayerBrushDescriptor? value) + { + if (value != null) + InputValue = new LayerBrushReference(value); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml new file mode 100644 index 000000000..b62cb245a --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs new file mode 100644 index 000000000..26cf7391d --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class ColorGradientPropertyInputView : ReactiveUserControl + { + public ColorGradientPropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs new file mode 100644 index 000000000..262dd7117 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputViewModel.cs @@ -0,0 +1,19 @@ +using System; +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class ColorGradientPropertyInputViewModel : PropertyInputViewModel +{ + public ColorGradientPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + } + + public void DialogClosed(object sender, EventArgs e) + { + ApplyInputValue(); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml new file mode 100644 index 000000000..c8d1d0490 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml.cs new file mode 100644 index 000000000..18ec0a5b3 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml.cs @@ -0,0 +1,21 @@ +using Artemis.UI.Shared.Services.PropertyInput; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class EnumPropertyInputView : ReactiveUserControl + { + public EnumPropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputViewModel.cs new file mode 100644 index 000000000..50242616d --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputViewModel.cs @@ -0,0 +1,17 @@ +using System; +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class EnumPropertyInputViewModel : PropertyInputViewModel where T : Enum +{ + public EnumPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + // TODO: Test if WhenActivated works here + } + + public Type EnumType => typeof(T); +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml new file mode 100644 index 000000000..a60310ccc --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml.cs new file mode 100644 index 000000000..8a219fb5d --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class FloatPropertyInputView : ReactiveUserControl +{ + public FloatPropertyInputView() + { + InitializeComponent(); + AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void OnRoutedKeyUp(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Escape) + FocusManager.Instance!.Focus(null); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs new file mode 100644 index 000000000..0a08915ef --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputViewModel.cs @@ -0,0 +1,20 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI.Validation.Extensions; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class FloatPropertyInputViewModel : PropertyInputViewModel +{ + public FloatPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + this.ValidationRule(vm => vm.InputValue, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + this.ValidationRule(vm => vm.InputValue, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, + $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml new file mode 100644 index 000000000..2db8defef --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml.cs new file mode 100644 index 000000000..4602aad94 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class FloatRangePropertyInputView : ReactiveUserControl + { + public FloatRangePropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs new file mode 100644 index 000000000..16df98f6e --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputViewModel.cs @@ -0,0 +1,68 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI; +using ReactiveUI.Validation.Extensions; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class FloatRangePropertyInputViewModel : PropertyInputViewModel +{ + public FloatRangePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + this.ValidationRule(vm => vm.Start, start => start <= End, "Start value must be less than the end value."); + this.ValidationRule(vm => vm.End, end => end >= Start, "End value must be greater than the start value."); + + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Start, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + this.ValidationRule(vm => vm.End, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + } + + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Start, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, + $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + this.ValidationRule(vm => vm.End, i => i < (float) LayerProperty.PropertyDescription.MaxInputValue, + $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } + } + + public float Start + { + get => InputValue?.Start ?? 0; + set + { + if (InputValue == null) + InputValue = new FloatRange(value, value + 1); + else + InputValue.Start = value; + + this.RaisePropertyChanged(nameof(Start)); + } + } + + public float End + { + get => InputValue?.End ?? 0; + set + { + if (InputValue == null) + InputValue = new FloatRange(value - 1, value); + else + InputValue.End = value; + + this.RaisePropertyChanged(nameof(End)); + } + } + + + protected override void OnInputValueChanged() + { + this.RaisePropertyChanged(nameof(Start)); + this.RaisePropertyChanged(nameof(End)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml new file mode 100644 index 000000000..b18fcb3f4 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml.cs new file mode 100644 index 000000000..e58c32f71 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class IntPropertyInputView : ReactiveUserControl +{ + public IntPropertyInputView() + { + InitializeComponent(); + AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void OnRoutedKeyUp(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Escape) + FocusManager.Instance!.Focus(null); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs new file mode 100644 index 000000000..92f02cfa6 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputViewModel.cs @@ -0,0 +1,20 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI.Validation.Extensions; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class IntPropertyInputViewModel : PropertyInputViewModel +{ + public IntPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + this.ValidationRule(vm => vm.InputValue, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, + $"Value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + this.ValidationRule(vm => vm.InputValue, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, + $"Value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml new file mode 100644 index 000000000..51785b5fe --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml.cs new file mode 100644 index 000000000..841670b58 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class IntRangePropertyInputView : ReactiveUserControl + { + public IntRangePropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs new file mode 100644 index 000000000..3e548ceaf --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputViewModel.cs @@ -0,0 +1,68 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI; +using ReactiveUI.Validation.Extensions; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class IntRangePropertyInputViewModel : PropertyInputViewModel +{ + public IntRangePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + this.ValidationRule(vm => vm.Start, start => start <= End, "Start value must be less than the end value."); + this.ValidationRule(vm => vm.End, end => end >= Start, "End value must be greater than the start value."); + + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Start, i => i >= (int) LayerProperty.PropertyDescription.MinInputValue, + $"Start value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + this.ValidationRule(vm => vm.End, i => i >= (int)LayerProperty.PropertyDescription.MinInputValue, + $"End value must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + } + + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Start, i => i < (int)LayerProperty.PropertyDescription.MaxInputValue, + $"Start value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + this.ValidationRule(vm => vm.End, i => i < (int) LayerProperty.PropertyDescription.MaxInputValue, + $"End value must be smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } + } + + public int Start + { + get => InputValue?.Start ?? 0; + set + { + if (InputValue == null) + InputValue = new IntRange(value, value + 1); + else + InputValue.Start = value; + + this.RaisePropertyChanged(nameof(Start)); + } + } + + public int End + { + get => InputValue?.End ?? 0; + set + { + if (InputValue == null) + InputValue = new IntRange(value - 1, value); + else + InputValue.End = value; + + this.RaisePropertyChanged(nameof(End)); + } + } + + + protected override void OnInputValueChanged() + { + this.RaisePropertyChanged(nameof(Start)); + this.RaisePropertyChanged(nameof(End)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml new file mode 100644 index 000000000..7ac5dc871 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml @@ -0,0 +1,8 @@ + + TODO + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml.cs new file mode 100644 index 000000000..95c3e2618 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class SKColorPropertyInputView : ReactiveUserControl + { + public SKColorPropertyInputView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs new file mode 100644 index 000000000..9c17073f5 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputViewModel.cs @@ -0,0 +1,17 @@ +using Artemis.Core; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using SkiaSharp; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public class SKColorPropertyInputViewModel : PropertyInputViewModel + { + public SKColorPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml new file mode 100644 index 000000000..9ffc8374f --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml @@ -0,0 +1,26 @@ + + + + + , + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml.cs new file mode 100644 index 000000000..53b09208c --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class SKPointPropertyInputView : ReactiveUserControl + { + public SKPointPropertyInputView() + { + InitializeComponent(); + AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void OnRoutedKeyUp(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Escape) + FocusManager.Instance!.Focus(null); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs new file mode 100644 index 000000000..db5dd9efc --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputViewModel.cs @@ -0,0 +1,50 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI; +using ReactiveUI.Validation.Extensions; +using SkiaSharp; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class SKPointPropertyInputViewModel : PropertyInputViewModel +{ + public SKPointPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.X, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"X must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + this.ValidationRule(vm => vm.Y, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"Y must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + } + + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.X, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, + $"X must be equal to or smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + this.ValidationRule(vm => vm.Y, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, + $"Y must be equal to or smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } + } + + public float X + { + get => InputValue.X; + set => InputValue = new SKPoint(value, Y); + } + + public float Y + { + get => InputValue.Y; + set => InputValue = new SKPoint(X, value); + } + + + protected override void OnInputValueChanged() + { + this.RaisePropertyChanged(nameof(X)); + this.RaisePropertyChanged(nameof(Y)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml new file mode 100644 index 000000000..02e3f5152 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml @@ -0,0 +1,27 @@ + + + + + , + + + + diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml.cs new file mode 100644 index 000000000..81fabfb40 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.DefaultTypes.PropertyInput +{ + public partial class SKSizePropertyInputView : ReactiveUserControl + { + public SKSizePropertyInputView() + { + InitializeComponent(); + AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void OnRoutedKeyUp(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Enter || e.Key == Key.Escape) + FocusManager.Instance!.Focus(null); + } + } +} diff --git a/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs new file mode 100644 index 000000000..510d93959 --- /dev/null +++ b/src/Avalonia/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputViewModel.cs @@ -0,0 +1,52 @@ +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.PropertyInput; +using ReactiveUI; +using ReactiveUI.Validation.Extensions; +using SkiaSharp; + +// using PropertyChanged; + +namespace Artemis.UI.DefaultTypes.PropertyInput; + +public class SKSizePropertyInputViewModel : PropertyInputViewModel +{ + public SKSizePropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) + : base(layerProperty, profileEditorService, propertyInputService) + { + if (LayerProperty.PropertyDescription.MinInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Width, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"Width must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + this.ValidationRule(vm => vm.Height, i => i >= (float) LayerProperty.PropertyDescription.MinInputValue, + $"Height must be equal to or greater than {LayerProperty.PropertyDescription.MinInputValue}."); + } + + if (LayerProperty.PropertyDescription.MaxInputValue.IsNumber()) + { + this.ValidationRule(vm => vm.Width, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, + $"Width must be equal to or smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + this.ValidationRule(vm => vm.Height, i => i <= (float) LayerProperty.PropertyDescription.MaxInputValue, + $"Height must be equal to or smaller than {LayerProperty.PropertyDescription.MaxInputValue}."); + } + } + + // Since SKSize is immutable we need to create properties that replace the SKSize entirely + public float Width + { + get => InputValue.Width; + set => InputValue = new SKSize(value, Height); + } + + public float Height + { + get => InputValue.Height; + set => InputValue = new SKSize(Width, value); + } + + protected override void OnInputValueChanged() + { + this.RaisePropertyChanged(nameof(Width)); + this.RaisePropertyChanged(nameof(Height)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index 8234d6a5f..de0dd23f3 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -1,56 +1,23 @@ using System; -using System.Reactive; using System.Reactive.Disposables; -using System.Reactive.Linq; using Artemis.UI.Shared; -using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor; using ReactiveUI; -namespace Artemis.UI.Screens.ProfileEditor.MenuBar +namespace Artemis.UI.Screens.ProfileEditor.MenuBar; + +public class MenuBarViewModel : ActivatableViewModelBase { - public class MenuBarViewModel : ActivatableViewModelBase + private ProfileEditorHistory? _history; + + public MenuBarViewModel(IProfileEditorService profileEditorService) { - private readonly INotificationService _notificationService; - private ProfileEditorHistory? _history; - private Action? _lastMessage; + this.WhenActivated(d => profileEditorService.History.Subscribe(history => History = history).DisposeWith(d)); + } - public MenuBarViewModel(IProfileEditorService profileEditorService, INotificationService notificationService) - { - _notificationService = notificationService; - this.WhenActivated(d => profileEditorService.History.Subscribe(history => History = history).DisposeWith(d)); - this.WhenAnyValue(x => x.History) - .Select(h => h?.Undo ?? Observable.Never()) - .Switch() - .Subscribe(DisplayUndo); - this.WhenAnyValue(x => x.History) - .Select(h => h?.Redo ?? Observable.Never()) - .Switch() - .Subscribe(DisplayRedo); - } - - private void DisplayUndo(IProfileEditorCommand? command) - { - if (command == null || History == null) - return; - - _lastMessage?.Invoke(); - _lastMessage = _notificationService.CreateNotification().WithMessage($"Undid '{command.DisplayName}'.").HavingButton(b => b.WithText("Redo").WithCommand(History.Redo)).Show(); - } - - private void DisplayRedo(IProfileEditorCommand? command) - { - if (command == null || History == null) - return; - - _lastMessage?.Invoke(); - _notificationService.CreateNotification().WithMessage($"Redid '{command.DisplayName}'.").HavingButton(b => b.WithText("Undo").WithCommand(History.Undo)).Show(); ; - } - - public ProfileEditorHistory? History - { - get => _history; - set => this.RaiseAndSetIfChanged(ref _history, value); - } + public ProfileEditorHistory? History + { + get => _history; + set => this.RaiseAndSetIfChanged(ref _history, value); } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml index ead36d35f..031ee5c09 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesView.axaml @@ -5,11 +5,16 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileElementProperties" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView"> - - - - - - - + + + + + + + + + + + + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs index ba22901ab..93e518660 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertiesViewModel.cs @@ -21,6 +21,8 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase private readonly IProfileEditorService _profileEditorService; private ProfileElementPropertyGroupViewModel? _brushPropertyGroup; private ObservableAsPropertyHelper? _profileElement; + private readonly Dictionary> _profileElementGroups; + private ObservableCollection _propertyGroupViewModels; /// public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory) @@ -28,129 +30,95 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase _profileEditorService = profileEditorService; _layerPropertyVmFactory = layerPropertyVmFactory; PropertyGroupViewModels = new ObservableCollection(); + _profileElementGroups = new Dictionary>(); // Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940 - this.WhenAnyValue(x => x.ProfileElement) + this.WhenAnyValue(vm => vm.ProfileElement) .Select(p => p is Layer l ? Observable.FromEventPattern(x => l.LayerBrushUpdated += x, x => l.LayerBrushUpdated -= x) : Observable.Never>()) .Switch() - .Subscribe(_ => ApplyEffects()); - this.WhenAnyValue(x => x.ProfileElement) + .Subscribe(_ => UpdateGroups()); + this.WhenAnyValue(vm => vm.ProfileElement) .Select(p => p != null ? Observable.FromEventPattern(x => p.LayerEffectsUpdated += x, x => p.LayerEffectsUpdated -= x) : Observable.Never>()) .Switch() - .Subscribe(_ => ApplyLayerBrush()); - + .Subscribe(_ => UpdateGroups()); // React to service profile element changes as long as the VM is active - this.WhenActivated(d => - { - _profileElement = _profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d); - _profileEditorService.ProfileElement.Subscribe(p => PopulateProperties(p)).DisposeWith(d); - }); + + this.WhenActivated(d => _profileElement = _profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d)); + this.WhenAnyValue(vm => vm.ProfileElement).Subscribe(_ => UpdateGroups()); } public RenderProfileElement? ProfileElement => _profileElement?.Value; public Layer? Layer => _profileElement?.Value as Layer; - public ObservableCollection PropertyGroupViewModels { get; } - private void PopulateProperties(RenderProfileElement? renderProfileElement) + public ObservableCollection PropertyGroupViewModels { - PropertyGroupViewModels.Clear(); - _brushPropertyGroup = null; + get => _propertyGroupViewModels; + set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value); + } + private void UpdateGroups() + { if (ProfileElement == null) + { + PropertyGroupViewModels.Clear(); return; + } + + if (!_profileElementGroups.TryGetValue(ProfileElement, out List? viewModels)) + { + viewModels = new List(); + _profileElementGroups[ProfileElement] = viewModels; + } + + List groups = new(); - // Add layer root groups if (Layer != null) { - PropertyGroupViewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.General)); - PropertyGroupViewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.Transform)); - ApplyLayerBrush(false); + // Add default layer groups + groups.Add(Layer.General); + groups.Add(Layer.Transform); + // Add brush group + if (Layer.LayerBrush?.BaseProperties != null) + groups.Add(Layer.LayerBrush.BaseProperties); } - ApplyEffects(); - } - - private void ApplyLayerBrush(bool sortProperties = true) - { - if (Layer == null) - return; - - bool hideRenderRelatedProperties = Layer.LayerBrush != null && Layer.LayerBrush.SupportsTransformation; - - Layer.General.ShapeType.IsHidden = hideRenderRelatedProperties; - Layer.General.BlendMode.IsHidden = hideRenderRelatedProperties; - Layer.Transform.IsHidden = hideRenderRelatedProperties; - - if (_brushPropertyGroup != null) - { - PropertyGroupViewModels.Remove(_brushPropertyGroup); - _brushPropertyGroup = null; - } - - if (Layer.LayerBrush?.BaseProperties != null) - { - _brushPropertyGroup = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.LayerBrush.BaseProperties); - PropertyGroupViewModels.Add(_brushPropertyGroup); - } - - if (sortProperties) - SortProperties(); - } - - private void ApplyEffects(bool sortProperties = true) - { - if (ProfileElement == null) - return; - - // Remove VMs of effects no longer applied on the layer - List toRemove = PropertyGroupViewModels - .Where(l => l.LayerPropertyGroup.LayerEffect != null && !ProfileElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect)) - .ToList(); - foreach (ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel in toRemove) - PropertyGroupViewModels.Remove(profileElementPropertyGroupViewModel); - + // Add effect groups foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects) { - if (PropertyGroupViewModels.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect) || layerEffect.BaseProperties == null) - continue; - - PropertyGroupViewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerEffect.BaseProperties)); + if (layerEffect.BaseProperties != null) + groups.Add(layerEffect.BaseProperties); } - if (sortProperties) - SortProperties(); - } + // Remove redundant VMs + viewModels.RemoveAll(vm => !groups.Contains(vm.LayerPropertyGroup)); + + // Create VMs for missing groups + foreach (LayerPropertyGroup group in groups) + { + if (viewModels.All(vm => vm.LayerPropertyGroup != group)) + viewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(group)); + } - private void SortProperties() - { // Get all non-effect properties - List nonEffectProperties = PropertyGroupViewModels + List nonEffectProperties = viewModels .Where(l => l.TreeGroupViewModel.GroupType != LayerPropertyGroupType.LayerEffectRoot) .ToList(); // Order the effects - List effectProperties = PropertyGroupViewModels + List effectProperties = viewModels .Where(l => l.TreeGroupViewModel.GroupType == LayerPropertyGroupType.LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null) .OrderBy(l => l.LayerPropertyGroup.LayerEffect?.Order) .ToList(); - // Put the non-effect properties in front - for (int index = 0; index < nonEffectProperties.Count; index++) - { - ProfileElementPropertyGroupViewModel layerPropertyGroupViewModel = nonEffectProperties[index]; - if (PropertyGroupViewModels.IndexOf(layerPropertyGroupViewModel) != index) - PropertyGroupViewModels.Move(PropertyGroupViewModels.IndexOf(layerPropertyGroupViewModel), index); - } + ObservableCollection propertyGroupViewModels = new(); + foreach (ProfileElementPropertyGroupViewModel viewModel in nonEffectProperties) + propertyGroupViewModels.Add(viewModel); + foreach (ProfileElementPropertyGroupViewModel viewModel in effectProperties) + propertyGroupViewModels.Add(viewModel); - // Put the effect properties after, sorted by their order - for (int index = 0; index < effectProperties.Count; index++) - { - ProfileElementPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index]; - if (PropertyGroupViewModels.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count) - PropertyGroupViewModels.Move(PropertyGroupViewModels.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); - } + PropertyGroupViewModels = propertyGroupViewModels; } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs index 4340c49e3..081c3209c 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyGroupViewModel.cs @@ -15,9 +15,9 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase { private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly IPropertyInputService _propertyInputService; - private bool _isVisible; - private bool _isExpanded; private bool _hasChildren; + private bool _isExpanded; + private bool _isVisible; public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService) { @@ -27,9 +27,34 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase LayerPropertyGroup = layerPropertyGroup; TreeGroupViewModel = layerPropertyVmFactory.TreeGroupViewModel(this); + // TODO: Centralize visibility updating or do it here and dispose + _isVisible = !LayerPropertyGroup.IsHidden; + PopulateChildren(); } + public ObservableCollection Children { get; } + public LayerPropertyGroup LayerPropertyGroup { get; } + public TreeGroupViewModel TreeGroupViewModel { get; } + + public bool IsVisible + { + get => _isVisible; + set => this.RaiseAndSetIfChanged(ref _isVisible, value); + } + + public bool IsExpanded + { + get => _isExpanded; + set => this.RaiseAndSetIfChanged(ref _isExpanded, value); + } + + public bool HasChildren + { + get => _hasChildren; + set => this.RaiseAndSetIfChanged(ref _hasChildren, value); + } + private void PopulateChildren() { // Get all properties and property groups and create VMs for them @@ -56,26 +81,4 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase HasChildren = Children.Any(i => i is ProfileElementPropertyViewModel {IsVisible: true} || i is ProfileElementPropertyGroupViewModel {IsVisible: true}); } - - public ObservableCollection Children { get; } - public LayerPropertyGroup LayerPropertyGroup { get; } - public TreeGroupViewModel TreeGroupViewModel { get; } - - public bool IsVisible - { - get => _isVisible; - set => this.RaiseAndSetIfChanged(ref _isVisible, value); - } - - public bool IsExpanded - { - get => _isExpanded; - set => this.RaiseAndSetIfChanged(ref _isExpanded, value); - } - - public bool HasChildren - { - get => _hasChildren; - set => this.RaiseAndSetIfChanged(ref _hasChildren, value); - } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs index 5fa58821a..b7e5305f7 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/ProfileElementPropertyViewModel.cs @@ -18,6 +18,9 @@ public class ProfileElementPropertyViewModel : ViewModelBase LayerProperty = layerProperty; TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this); TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(LayerProperty, this); + + // TODO: Centralize visibility updating or do it here and dispose + _isVisible = !LayerProperty.IsHidden; } public ILayerProperty LayerProperty { get; } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs new file mode 100644 index 000000000..a379909aa --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/Dialogs/LayerBrushPresetViewModel.cs @@ -0,0 +1,15 @@ +using Artemis.Core.LayerBrushes; +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.Dialogs +{ + public class LayerBrushPresetViewModel : ContentDialogViewModelBase + { + public BaseLayerBrush LayerBrush { get; } + + public LayerBrushPresetViewModel(BaseLayerBrush layerBrush) + { + LayerBrush = layerBrush; + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml index 9f2443089..0ccb5b649 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreeGroupView.axaml @@ -11,24 +11,24 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreeGroupView"> - + + Height="29"> - Welcome to Avalonia! + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs index 63a0c1e8e..558013711 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileElementProperties/Tree/TreePropertyViewModel.cs @@ -11,8 +11,6 @@ internal class TreePropertyViewModel : ActivatableViewModelBase, ITreePropert LayerProperty = layerProperty; ProfileElementPropertyViewModel = profileElementPropertyViewModel; PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty); - - // TODO: Update ProfileElementPropertyViewModel visibility on change } public LayerProperty LayerProperty { get; } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs index aee6dbd16..cbb530f99 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor; @@ -7,8 +8,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { public class FolderTreeItemViewModel : TreeItemViewModel { - public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder, IWindowService windowService, IProfileEditorService profileEditorService, - IProfileEditorVmFactory profileEditorVmFactory) : base(parent, folder, windowService, profileEditorService, profileEditorVmFactory) + public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) + : base(parent, folder, windowService, profileEditorService, rgbService, profileEditorVmFactory) { Folder = folder; } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs index b7bb5f087..ab577c3ce 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor; @@ -7,8 +8,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { public class LayerTreeItemViewModel : TreeItemViewModel { - public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer, IWindowService windowService, IProfileEditorService profileEditorService, - IProfileEditorVmFactory profileEditorVmFactory) : base(parent, layer, windowService, profileEditorService, profileEditorVmFactory) + public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) + : base(parent, layer, windowService, profileEditorService, rgbService, profileEditorVmFactory) { Layer = layer; } diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index 802c6a6e4..7cec467b0 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Disposables; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor; @@ -16,8 +17,8 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { private TreeItemViewModel? _selectedChild; - public ProfileTreeViewModel(IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) - : base(null, null, windowService, profileEditorService, profileEditorVmFactory) + public ProfileTreeViewModel(IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) + : base(null, null, windowService, profileEditorService, rgbService, profileEditorVmFactory) { this.WhenActivated(d => { @@ -42,8 +43,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree if (model?.ProfileElement is RenderProfileElement renderProfileElement) profileEditorService.ChangeCurrentProfileElement(renderProfileElement); }); - - } public TreeItemViewModel? SelectedChild diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 5d16abc25..7b79ca8b5 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -7,6 +7,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services.Interfaces; @@ -27,7 +28,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree private bool _renaming; private string? _renameValue; - protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, IWindowService windowService, IProfileEditorService profileEditorService, + protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, IWindowService windowService, IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory profileEditorVmFactory) { _windowService = windowService; @@ -40,9 +41,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree AddLayer = ReactiveCommand.Create(() => { if (ProfileElement is Layer targetLayer) - profileEditorService.ExecuteCommand(new AddProfileElement(new Layer(targetLayer.Parent, "New layer"), targetLayer.Parent, targetLayer.Order)); + { + Layer layer = new(targetLayer.Parent, "New layer"); + layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); + profileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order)); + } else if (ProfileElement != null) - profileEditorService.ExecuteCommand(new AddProfileElement(new Layer(ProfileElement, "New layer"), ProfileElement, 0)); + { + Layer layer = new(ProfileElement, "New layer"); + layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); + profileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0)); + } }); AddFolder = ReactiveCommand.Create(() => diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml new file mode 100644 index 000000000..7c0f57b06 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml.cs new file mode 100644 index 000000000..a171599d5 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarView.axaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.StatusBar +{ + public partial class StatusBarView : ReactiveUserControl + { + public StatusBarView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarViewModel.cs new file mode 100644 index 000000000..752d6b36a --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/StatusBar/StatusBarViewModel.cs @@ -0,0 +1,62 @@ +using System; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.ProfileEditor; +using Avalonia.Controls.Mixins; +using ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.StatusBar; + +public class StatusBarViewModel : ActivatableViewModelBase +{ + private ProfileEditorHistory? _history; + private RenderProfileElement? _profileElement; + private string? _statusMessage; + private bool _showStatusMessage; + + public StatusBarViewModel(IProfileEditorService profileEditorService) + { + this.WhenActivated(d => + { + profileEditorService.ProfileElement.Subscribe(p => ProfileElement = p).DisposeWith(d); + profileEditorService.History.Subscribe(history => History = history).DisposeWith(d); + }); + + this.WhenAnyValue(vm => vm.History) + .Select(h => h?.Undo ?? Observable.Never()) + .Switch() + .Subscribe(c => StatusMessage = c != null ? $"Undid '{c.DisplayName}'." : "Nothing to undo."); + this.WhenAnyValue(vm => vm.History) + .Select(h => h?.Redo ?? Observable.Never()) + .Switch() + .Subscribe(c => StatusMessage = c != null ? $"Redid '{c.DisplayName}'." : "Nothing to redo."); + + this.WhenAnyValue(vm => vm.StatusMessage).Subscribe(_ => ShowStatusMessage = true); + this.WhenAnyValue(vm => vm.StatusMessage).Throttle(TimeSpan.FromSeconds(3)).Subscribe(_ => ShowStatusMessage = false); + } + + public RenderProfileElement? ProfileElement + { + get => _profileElement; + set => this.RaiseAndSetIfChanged(ref _profileElement, value); + } + + public ProfileEditorHistory? History + { + get => _history; + set => this.RaiseAndSetIfChanged(ref _history, value); + } + + public string? StatusMessage + { + get => _statusMessage; + set => this.RaiseAndSetIfChanged(ref _statusMessage, value); + } + + public bool ShowStatusMessage + { + get => _showStatusMessage; + set => this.RaiseAndSetIfChanged(ref _showStatusMessage, value); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml index 37f8b5d90..7a6236639 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml @@ -36,6 +36,10 @@ + + + + @@ -62,7 +66,7 @@ - + @@ -80,5 +84,7 @@ Conditions + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 00cfb1863..8d52bfbfd 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -4,6 +4,7 @@ using Artemis.Core; using Artemis.UI.Screens.ProfileEditor.MenuBar; using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties; using Artemis.UI.Screens.ProfileEditor.ProfileTree; +using Artemis.UI.Screens.ProfileEditor.StatusBar; using Artemis.UI.Screens.ProfileEditor.VisualEditor; using Artemis.UI.Shared.Services.ProfileEditor; using Ninject; @@ -24,12 +25,14 @@ namespace Artemis.UI.Screens.ProfileEditor ProfileTreeViewModel profileTreeViewModel, ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel, MenuBarViewModel menuBarViewModel, - ProfileElementPropertiesViewModel profileElementPropertiesViewModel) + ProfileElementPropertiesViewModel profileElementPropertiesViewModel, + StatusBarViewModel statusBarViewModel) : base(hostScreen, "profile-editor") { VisualEditorViewModel = visualEditorViewModel; ProfileTreeViewModel = profileTreeViewModel; ProfileElementPropertiesViewModel = profileElementPropertiesViewModel; + StatusBarViewModel = statusBarViewModel; if (OperatingSystem.IsWindows()) TitleBarViewModel = profileEditorTitleBarViewModel; @@ -44,6 +47,7 @@ namespace Artemis.UI.Screens.ProfileEditor public ProfileTreeViewModel ProfileTreeViewModel { get; } public MenuBarViewModel? MenuBarViewModel { get; } public ProfileElementPropertiesViewModel ProfileElementPropertiesViewModel { get; } + public StatusBarViewModel StatusBarViewModel { get; } public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public ProfileEditorHistory? History => _history?.Value; diff --git a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs index 197bb922c..8a35ed42a 100644 --- a/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Root/RootViewModel.cs @@ -27,6 +27,7 @@ namespace Artemis.UI.Screens.Root private readonly IDebugService _debugService; private readonly IClassicDesktopStyleApplicationLifetime _lifeTime; private readonly ISettingsService _settingsService; + private readonly IRegistrationService _registrationService; private readonly ISidebarVmFactory _sidebarVmFactory; private readonly IWindowService _windowService; private SidebarViewModel? _sidebarViewModel; @@ -48,6 +49,7 @@ namespace Artemis.UI.Screens.Root _coreService = coreService; _settingsService = settingsService; + _registrationService = registrationService; _windowService = windowService; _debugService = debugService; _assetLoader = assetLoader; @@ -176,6 +178,10 @@ namespace Artemis.UI.Screens.Root /// public void OpenMainWindow() { + _registrationService.RegisterBuiltInDataModelDisplays(); + _registrationService.RegisterBuiltInDataModelInputs(); + _registrationService.RegisterBuiltInPropertyEditors(); + if (_lifeTime.MainWindow == null) { SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this); diff --git a/src/Avalonia/Artemis.UI/Services/RegistrationService.cs b/src/Avalonia/Artemis.UI/Services/RegistrationService.cs index c2681436a..8330d7a00 100644 --- a/src/Avalonia/Artemis.UI/Services/RegistrationService.cs +++ b/src/Avalonia/Artemis.UI/Services/RegistrationService.cs @@ -1,15 +1,21 @@ -using Artemis.Core.Services; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.DefaultTypes.PropertyInput; using Artemis.UI.Services.Interfaces; +using Artemis.UI.Shared.Services.PropertyInput; namespace Artemis.UI.Services { public class RegistrationService : IRegistrationService { private readonly IInputService _inputService; + private readonly IPropertyInputService _propertyInputService; + private bool _registeredBuiltInPropertyEditors; - public RegistrationService(IInputService inputService) + public RegistrationService(IInputService inputService, IPropertyInputService propertyInputService) { _inputService = inputService; + _propertyInputService = propertyInputService; } public void RegisterBuiltInDataModelDisplays() { @@ -21,6 +27,22 @@ namespace Artemis.UI.Services public void RegisterBuiltInPropertyEditors() { + if (_registeredBuiltInPropertyEditors) + return; + + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + _propertyInputService.RegisterPropertyInput(Constants.CorePlugin); + + _registeredBuiltInPropertyEditors = true; } public void RegisterControllers()