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()