1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile editor - Added most properties

This commit is contained in:
Robert 2022-01-12 22:11:40 +01:00
parent c04bff1f48
commit 022beb6a48
62 changed files with 1424 additions and 251 deletions

View File

@ -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
/// </summary>
/// <param name="value">The value to check</param>
/// <returns><see langword="true" /> if the value is of a numeric type, otherwise <see langword="false" /></returns>
public static bool IsNumber(this object value)
public static bool IsNumber([NotNullWhenAttribute(true)] this object? value)
{
return value is sbyte
|| value is byte

View File

@ -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");
}

View File

@ -0,0 +1,44 @@
using Artemis.Core;
using Artemis.Core.LayerBrushes;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to change the brush of a layer.
/// </summary>
public class ChangeLayerBrush : IProfileEditorCommand
{
private readonly Layer _layer;
private readonly LayerBrushDescriptor _layerBrushDescriptor;
private readonly LayerBrushDescriptor? _previousDescriptor;
/// <summary>
/// Creates a new instance of the <see cref="ChangeLayerBrush" /> class.
/// </summary>
public ChangeLayerBrush(Layer layer, LayerBrushDescriptor layerBrushDescriptor)
{
_layer = layer;
_layerBrushDescriptor = layerBrushDescriptor;
_previousDescriptor = layer.LayerBrush?.Descriptor;
}
#region Implementation of IProfileEditorCommand
/// <inheritdoc />
public string DisplayName => "Change layer brush";
/// <inheritdoc />
public void Execute()
{
_layer.ChangeLayerBrush(_layerBrushDescriptor);
}
/// <inheritdoc />
public void Undo()
{
if (_previousDescriptor != null)
_layer.ChangeLayerBrush(_previousDescriptor);
}
#endregion
}

View File

@ -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;
/// <typeparam name="T">The type of property this input view model supports</typeparam>
public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
{
[AllowNull]
private T _inputValue;
private bool _inputDragging;
private T _dragStartValue;
private bool _inputDragging;
[AllowNull] private T _inputValue;
private TimeSpan _time;
/// <summary>
@ -89,7 +91,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
/// <summary>
/// Gets or sets the input value
/// </summary>
[AllowNull]
[MaybeNull]
public T InputValue
{
get => _inputValue;
@ -126,13 +128,6 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _dragStartValue, _time));
}
/// <summary>
/// Called when the input value has been applied to the layer property
/// </summary>
protected virtual void OnInputValueApplied()
{
}
/// <summary>
/// Called when the input value has changed
/// </summary>
@ -147,23 +142,23 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
{
}
protected virtual T GetDragStartValue()
/// <summary>
/// Called when dragging starts to get the initial value before dragging begun
/// </summary>
/// <returns>The initial value before dragging begun</returns>
protected virtual T? GetDragStartValue()
{
return InputValue;
}
/// <summary>
/// Applies the input value to the layer property
/// Applies the input value to the layer property using an <see cref="IProfileEditorCommand" />.
/// </summary>
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<T>(LayerProperty, _inputValue, _time));
}
@ -186,23 +181,12 @@ public abstract class PropertyInputViewModel<T> : 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();
}
}
/// <summary>
/// For internal use only, implement <see cref="PropertyInputViewModel" /> instead.
/// </summary>
public abstract class PropertyInputViewModel : ActivatableViewModelBase
public abstract class PropertyInputViewModel : ReactiveValidationObject, IActivatableViewModel, IDisposable
{
/// <summary>
/// Prevents this type being implemented directly, implement
@ -210,4 +194,29 @@ public abstract class PropertyInputViewModel : ActivatableViewModelBase
/// </summary>
// ReSharper disable once UnusedMember.Global
internal abstract object InternalGuard { get; }
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
}
#region Implementation of IActivatableViewModel
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
#endregion
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Padding="50">
<StackPanel Spacing="5">
@ -9,12 +10,20 @@
<TextBox Classes="condensed" Text="Hello Down there" />
<TextBox Watermark="Watermark" />
<TextBox Classes="condensed" Watermark="Watermark" />
<TextBox Text="Test" IsEnabled="False" />
<TextBox Classes="condensed" Text="Test" IsEnabled="False" />
<TextBox Text="Test Clear" Classes="clearButton" />
<TextBox Classes="condensed clearButton" Text="Test Clear" />
<TextBox Text="Test Password" Classes="revealPasswordButton" PasswordChar="*" />
<TextBox Classes="condensed revealPasswordButton" Text="Test Password" PasswordChar="*" />
<controls:NumberBox Value="1337"></controls:NumberBox>
<controls:NumberBox Classes="condensed" Value="1337"></controls:NumberBox>
<ComboBox SelectedIndex="1">
<ComboBoxItem>Bluasdadseheh</ComboBoxItem>
<ComboBoxItem>Bluheheheheh</ComboBoxItem>
<ComboBoxItem>Bluhgfdgdsheheh</ComboBoxItem>
</ComboBox>
<ComboBox SelectedIndex="1" Classes="condensed">
<ComboBoxItem>Bluasdadseheh</ComboBoxItem>
<ComboBoxItem>Bluheheheheh</ComboBoxItem>
<ComboBoxItem>Bluhgfdgdsheheh</ComboBoxItem>
</ComboBox>
</StackPanel>
</Border>
</Design.PreviewWith>
@ -22,7 +31,19 @@
<!-- Add Styles Here -->
<Style Selector="TextBox.condensed">
<Setter Property="Padding" Value="4 2" />
<Setter Property="FontSize" Value="14"></Setter>
<Setter Property="FontSize" Value="14" />
<Setter Property="MinHeight" Value="25" />
</Style>
<Style Selector="controls|NumberBox.condensed /template/ TextBox#InputBox">
<Setter Property="Padding" Value="4 2" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinHeight" Value="25" />
</Style>
<Style Selector="ComboBox.condensed">
<Setter Property="Padding" Value="4 2" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Height" Value="25" />
</Style>
</Styles>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
@ -47,24 +47,4 @@
<Resource Include="Assets\Images\Logo\bow.ico" />
<Resource Include="Assets\Images\Logo\bow.svg" />
</ItemGroup>
<ItemGroup>
<Compile Update="Screens\Debugger\Tabs\Settings\DebugSettingsView.axaml.cs">
<DependentUpon>DebugSettingsView.axaml</DependentUpon>
</Compile>
<Compile Update="Screens\ProfileEditor\Panels\ProfileElementProperties\ProfileElementPropertiesView.axaml.cs">
<DependentUpon>ProfileElementPropertiesView.axaml</DependentUpon>
</Compile>
<Compile Update="Screens\ProfileEditor\Panels\ProfileElementProperties\Windows\BrushConfigurationWindowView.axaml.cs">
<DependentUpon>BrushConfigurationWindowView.axaml</DependentUpon>
</Compile>
<Compile Update="Screens\Sidebar\ContentDialogs\SidebarCategoryEditView.axaml.cs">
<DependentUpon>SidebarCategoryEditView.axaml</DependentUpon>
</Compile>
<Compile Update="Screens\Sidebar\Dialogs\ProfileConfigurationEditView.axaml.cs">
<DependentUpon>ProfileConfigurationEditView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Screens\ProfileEditor\Tools\" />
</ItemGroup>
</Project>

View File

@ -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);
}

View File

@ -0,0 +1,64 @@
<UserControl x:Class="Artemis.UI.DefaultTypes.DataModel.Display.SKColorDataModelDisplayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:display="clr-namespace:Artemis.UI.DefaultTypes.DataModel.Display"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type display:SKColorDataModelDisplayViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/Resources/ArtemisShared.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:ColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="HexDisplay"
Text="{Binding DisplayValue, Converter={StaticResource SKColorToStringConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
<Border Width="{Binding ActualHeight, ElementName=HexDisplay}"
Height="{Binding ActualHeight, ElementName=HexDisplay}"
CornerRadius="{Binding ActualHeight, ElementName=HexDisplay}"
Margin="5 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Background="{StaticResource Checkerboard}">
<Ellipse Stroke="{DynamicResource NormalBorderBrush}">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding DisplayValue, Converter={StaticResource SKColorToColorConverter}}" />
</Ellipse.Fill>
</Ellipse>
</Border>
</StackPanel>
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared.DataModelVisualization;
using SkiaSharp;
namespace Artemis.UI.DefaultTypes.DataModel.Display;
public class SKColorDataModelDisplayViewModel : DataModelDisplayViewModel<SKColor>
{
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView">
TODO
</UserControl>

View File

@ -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<BoolPropertyInputViewModel>
{
public BoolPropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<bool>
{
public BoolPropertyInputViewModel(LayerProperty<bool> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
}
}

View File

@ -0,0 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.BrushPropertyInputView">
<UserControl.Styles>
<Style Selector="ComboBox.brush /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{Binding Icon}" Height="20" Width="20" VerticalAlignment="Center" Margin="0 0 5 0"/>
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
<ComboBox Classes="brush condensed"
Width="200"
HorizontalAlignment="Left"
Items="{Binding Descriptors}"
SelectedItem="{Binding SelectedDescriptor}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
<avalonia:MaterialIcon Grid.Row="0"
Grid.RowSpan="2"
Kind="{Binding Icon}"
Height="20"
Width="20"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Grid.Row="1" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" MaxWidth="350" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</UserControl>

View File

@ -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<BrushPropertyInputViewModel>
{
public BrushPropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<LayerBrushReference>
{
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private ObservableCollection<LayerBrushDescriptor> _descriptors;
public BrushPropertyInputViewModel(LayerProperty<LayerBrushReference> layerProperty, IPluginManagementService pluginManagementService, IWindowService windowService,
IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
_pluginManagementService = pluginManagementService;
_windowService = windowService;
_profileEditorService = profileEditorService;
_descriptors = new ObservableCollection<LayerBrushDescriptor>(pluginManagementService.GetFeaturesOfType<LayerBrushProvider>().SelectMany(l => l.LayerBrushDescriptors));
this.WhenActivated(d =>
{
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
.Subscribe(e => UpdateDescriptorsIfChanged(e.EventArgs))
.DisposeWith(d);
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(e => UpdateDescriptorsIfChanged(e.EventArgs))
.DisposeWith(d);
});
}
public ObservableCollection<LayerBrushDescriptor> Descriptors
{
get => _descriptors;
set => this.RaiseAndSetIfChanged(ref _descriptors, value);
}
public LayerBrushDescriptor? SelectedDescriptor
{
get => Descriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(InputValue));
set => SetBrushByDescriptor(value);
}
/// <inheritdoc />
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<LayerBrushDescriptor>(_pluginManagementService.GetFeaturesOfType<LayerBrushProvider>().SelectMany(l => l.LayerBrushDescriptors));
}
private void SetBrushByDescriptor(LayerBrushDescriptor? value)
{
if (value != null)
InputValue = new LayerBrushReference(value);
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView">
TODO
</UserControl>

View File

@ -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<ColorGradientPropertyInputViewModel>
{
public ColorGradientPropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<ColorGradient>
{
public ColorGradientPropertyInputViewModel(LayerProperty<ColorGradient> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
}
public void DialogClosed(object sender, EventArgs e)
{
ApplyInputValue();
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView">
TODO
</UserControl>

View File

@ -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<PropertyInputViewModel>
{
public EnumPropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<T> : PropertyInputViewModel<T> where T : Enum
{
public EnumPropertyInputViewModel(LayerProperty<T> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
// TODO: Test if WhenActivated works here
}
public Type EnumType => typeof(T);
}

View File

@ -0,0 +1,18 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.FloatPropertyInputView">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputPrefix}" VerticalAlignment="Center" />
<controls:NumberBox Classes="condensed"
Width="100"
Value="{Binding InputValue}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
AcceptsExpression="True"
VerticalAlignment="Center"/>
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,25 @@
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput;
public class FloatPropertyInputView : ReactiveUserControl<FloatPropertyInputViewModel>
{
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);
}
}

View File

@ -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<float>
{
public FloatPropertyInputViewModel(LayerProperty<float> 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}.");
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.FloatRangePropertyInputView">
TODO
</UserControl>

View File

@ -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<FloatRangePropertyInputViewModel>
{
public FloatRangePropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<FloatRange>
{
public FloatRangePropertyInputViewModel(LayerProperty<FloatRange> 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));
}
}

View File

@ -0,0 +1,18 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.IntPropertyInputView">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputPrefix}" VerticalAlignment="Center" />
<controls:NumberBox Classes="condensed"
Width="100"
Value="{Binding InputValue}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
AcceptsExpression="True"
VerticalAlignment="Center" />
<TextBlock Width="25" Text="{Binding LayerProperty.PropertyDescription.InputAffix}" VerticalAlignment="Center"/>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,25 @@
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput;
public class IntPropertyInputView : ReactiveUserControl<IntPropertyInputViewModel>
{
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);
}
}

View File

@ -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<int>
{
public IntPropertyInputViewModel(LayerProperty<int> 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}.");
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.IntRangePropertyInputView">
TODO
</UserControl>

View File

@ -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<IntRangePropertyInputViewModel>
{
public IntRangePropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<IntRange>
{
public IntRangePropertyInputViewModel(LayerProperty<IntRange> 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));
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView">
TODO
</UserControl>

View File

@ -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<SKColorPropertyInputViewModel>
{
public SKColorPropertyInputView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<SKColor>
{
public SKColorPropertyInputViewModel(LayerProperty<SKColor> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
}
}
}

View File

@ -0,0 +1,26 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKPointPropertyInputView">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputPrefix}"
VerticalAlignment="Center" />
<controls:NumberBox Classes="condensed"
Value="{Binding X}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
ToolTip.Tip="X-coordinate (horizontal)"
VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">,</TextBlock>
<controls:NumberBox Classes="condensed"
Value="{Binding Y}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
VerticalAlignment="Center" />
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
ToolTip.Tip="Y-coordinate (vertical)" />
</StackPanel>
</UserControl>

View File

@ -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<SKPointPropertyInputViewModel>
{
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);
}
}
}

View File

@ -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<SKPoint>
{
public SKPointPropertyInputViewModel(LayerProperty<SKPoint> 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));
}
}

View File

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKSizePropertyInputView">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputPrefix}"
VerticalAlignment="Center" />
<controls:NumberBox Classes="condensed"
Value="{Binding Height}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
ToolTip.Tip="Height"
VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center">,</TextBlock>
<controls:NumberBox Classes="condensed"
Value="{Binding Width}"
SmallChange="{Binding LayerProperty.PropertyDescription.InputStepSize}"
VerticalAlignment="Center"/>
<TextBlock Width="25"
Text="{Binding LayerProperty.PropertyDescription.InputAffix}"
ToolTip.Tip="Width"
VerticalAlignment="Center"/>
</StackPanel>
</UserControl>

View File

@ -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<SKSizePropertyInputViewModel>
{
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);
}
}
}

View File

@ -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<SKSize>
{
public SKSizePropertyInputViewModel(LayerProperty<SKSize> 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));
}
}

View File

@ -1,50 +1,18 @@
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
{
private readonly INotificationService _notificationService;
private ProfileEditorHistory? _history;
private Action? _lastMessage;
public MenuBarViewModel(IProfileEditorService profileEditorService, INotificationService notificationService)
public MenuBarViewModel(IProfileEditorService profileEditorService)
{
_notificationService = notificationService;
this.WhenActivated(d => profileEditorService.History.Subscribe(history => History = history).DisposeWith(d));
this.WhenAnyValue(x => x.History)
.Select(h => h?.Undo ?? Observable.Never<IProfileEditorCommand?>())
.Switch()
.Subscribe(DisplayUndo);
this.WhenAnyValue(x => x.History)
.Select(h => h?.Redo ?? Observable.Never<IProfileEditorCommand?>())
.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
@ -53,4 +21,3 @@ namespace Artemis.UI.Screens.ProfileEditor.MenuBar
set => this.RaiseAndSetIfChanged(ref _history, value);
}
}
}

View File

@ -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">
<ItemsControl Items="{Binding PropertyGroupViewModels}">
<Grid ColumnDefinitions="*,Auto,*">
<ItemsControl Grid.Column="0" Items="{Binding PropertyGroupViewModels}">
<ItemsControl.ItemTemplate>
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
<ContentControl Content="{Binding TreeGroupViewModel}" />
</TreeDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<GridSplitter Grid.Column="1"></GridSplitter>
</Grid>
</UserControl>

View File

@ -21,6 +21,8 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
private readonly IProfileEditorService _profileEditorService;
private ProfileElementPropertyGroupViewModel? _brushPropertyGroup;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
private readonly Dictionary<RenderProfileElement, List<ProfileElementPropertyGroupViewModel>> _profileElementGroups;
private ObservableCollection<ProfileElementPropertyGroupViewModel> _propertyGroupViewModels;
/// <inheritdoc />
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory)
@ -28,129 +30,95 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
_profileEditorService = profileEditorService;
_layerPropertyVmFactory = layerPropertyVmFactory;
PropertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
_profileElementGroups = new Dictionary<RenderProfileElement, List<ProfileElementPropertyGroupViewModel>>();
// 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<EventPattern<object>>())
.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<EventPattern<object>>())
.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<ProfileElementPropertyGroupViewModel> PropertyGroupViewModels { get; }
private void PopulateProperties(RenderProfileElement? renderProfileElement)
public ObservableCollection<ProfileElementPropertyGroupViewModel> PropertyGroupViewModels
{
get => _propertyGroupViewModels;
set => this.RaiseAndSetIfChanged(ref _propertyGroupViewModels, value);
}
private void UpdateGroups()
{
if (ProfileElement == null)
{
PropertyGroupViewModels.Clear();
_brushPropertyGroup = null;
if (ProfileElement == null)
return;
}
if (!_profileElementGroups.TryGetValue(ProfileElement, out List<ProfileElementPropertyGroupViewModel>? viewModels))
{
viewModels = new List<ProfileElementPropertyGroupViewModel>();
_profileElementGroups[ProfileElement] = viewModels;
}
List<LayerPropertyGroup> groups = new();
// Add layer root groups
if (Layer != null)
{
PropertyGroupViewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.General));
PropertyGroupViewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.Transform));
ApplyLayerBrush(false);
}
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;
}
// Add default layer groups
groups.Add(Layer.General);
groups.Add(Layer.Transform);
// Add brush group
if (Layer.LayerBrush?.BaseProperties != null)
{
_brushPropertyGroup = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(Layer.LayerBrush.BaseProperties);
PropertyGroupViewModels.Add(_brushPropertyGroup);
groups.Add(Layer.LayerBrush.BaseProperties);
}
if (sortProperties)
SortProperties();
}
private void ApplyEffects(bool sortProperties = true)
{
if (ProfileElement == null)
return;
// Remove VMs of effects no longer applied on the layer
List<ProfileElementPropertyGroupViewModel> 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));
private void SortProperties()
// Create VMs for missing groups
foreach (LayerPropertyGroup group in groups)
{
if (viewModels.All(vm => vm.LayerPropertyGroup != group))
viewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(group));
}
// Get all non-effect properties
List<ProfileElementPropertyGroupViewModel> nonEffectProperties = PropertyGroupViewModels
List<ProfileElementPropertyGroupViewModel> nonEffectProperties = viewModels
.Where(l => l.TreeGroupViewModel.GroupType != LayerPropertyGroupType.LayerEffectRoot)
.ToList();
// Order the effects
List<ProfileElementPropertyGroupViewModel> effectProperties = PropertyGroupViewModels
List<ProfileElementPropertyGroupViewModel> 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<ProfileElementPropertyGroupViewModel> 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;
}
}

View File

@ -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<ViewModelBase> 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<ViewModelBase> 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);
}
}

View File

@ -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; }

View File

@ -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;
}
}
}

View File

@ -11,24 +11,24 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreeGroupView">
<UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" />
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20"/>
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
<Setter Property="RenderTransform" Value="rotate(180deg)" />
<Setter Property="RenderTransform" Value="rotate(-90deg)" />
</Style>
</UserControl.Styles>
<StackPanel>
<Border Name="Bd"
BorderBrush="{DynamicResource MaterialDesignDivider}"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0,0,0,1"
Height="25">
Height="29">
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="19,*">
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding !ProfileElementPropertyGroupViewModel.IsExpanded}"
IsVisible="{Binding ProfileElementPropertyGroupViewModel.HasChildren}"
Kind="ChevronUp"
Kind="ChevronDown"
Grid.Column="0"
Margin="5 0"
PointerPressed="InputElement_OnPointerPressed"

View File

@ -2,7 +2,64 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree.TreePropertyView">
Welcome to Avalonia!
<UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
<sharedConverters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
</UserControl.Resources>
<Border Name="Bd"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0,0,0,1"
Height="29">
<Grid Margin="{Binding Converter={StaticResource PropertyTreeMarginConverter}}" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<ToggleButton Grid.Column="0"
Classes="icon-button"
ToolTip.Tip="Toggle key-framing"
Width="24"
Height="24"
IsChecked="{Binding KeyframesEnabled}"
IsEnabled="{Binding LayerProperty.KeyframesSupported}"
VerticalAlignment="Center" Padding="-25">
<avalonia:MaterialIcon Kind="Stopwatch" />
</ToggleButton>
<TextBlock Grid.Column="1"
Margin="5,0,0,0"
Padding="0,0,5,0"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
Text="{Binding LayerProperty.PropertyDescription.Name}"
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"
HorizontalAlignment="Left" />
<ContentControl Grid.Column="2"
Margin="5 0"
Content="{Binding PropertyInputViewModel}"
ToolTip.Tip="{Binding LayerProperty.PropertyDescription.Description}"/>
<Button Grid.Column="3"
Command="{Binding ResetToDefault}"
Classes="icon-button"
ToolTip.Tip="Reset the property to its default value."
Width="24"
Height="24">
<avalonia:MaterialIcon Kind="BackupRestore" />
</Button>
<ToggleButton Grid.Column="4" Classes="icon-button"
ToolTip.Tip="Change the property's data binding"
Width="24"
Height="24"
VerticalAlignment="Center"
IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
IsChecked="{Binding HasDataBinding, Mode=OneWay}">
<avalonia:MaterialIcon Kind="VectorLink" />
</ToggleButton>
</Grid>
</Border>
</UserControl>

View File

@ -11,8 +11,6 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
LayerProperty = layerProperty;
ProfileElementPropertyViewModel = profileElementPropertyViewModel;
PropertyInputViewModel = propertyInputService.CreatePropertyInputViewModel(LayerProperty);
// TODO: Update ProfileElementPropertyViewModel visibility on change
}
public LayerProperty<T> LayerProperty { get; }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

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

View File

@ -0,0 +1,44 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="23"
x:Class="Artemis.UI.Screens.ProfileEditor.StatusBar.StatusBarView">
<UserControl.Styles>
<Style Selector="Border.status-message-border">
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}"></Setter>
<Setter Property="BorderThickness" Value="1 0 0 0"></Setter>
<Setter Property="Margin" Value="5 2 0 2"></Setter>
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0.2"></DoubleTransition>
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Border.status-message-border.hidden">
<Setter Property="Opacity" Value="0" />
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="Auto, Auto,*" Height="23" Margin="5 0">
<ContentControl Grid.Column="0" Content="{Binding ProfileElement}">
<ContentControl.DataTemplates>
<DataTemplate DataType="core:Folder">
<avalonia:MaterialIcon Kind="Folder" Margin="0 0 5 0" />
</DataTemplate>
<DataTemplate DataType="core:Layer">
<avalonia:MaterialIcon Kind="{Binding LayerBrush.Descriptor.Icon}" Margin="0 0 5 0" />
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
<TextBlock Grid.Column="1" Text="{Binding ProfileElement.Name, FallbackValue=Select a layer to get started}" VerticalAlignment="Center" />
<Border Grid.Column="2" Classes="status-message-border" Classes.hidden="{Binding !ShowStatusMessage}">
<TextBlock Margin="5 0 0 0" Text="{Binding StatusMessage}" />
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.StatusBar
{
public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
{
public StatusBarView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -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<IProfileEditorCommand?>())
.Switch()
.Subscribe(c => StatusMessage = c != null ? $"Undid '{c.DisplayName}'." : "Nothing to undo.");
this.WhenAnyValue(vm => vm.History)
.Select(h => h?.Redo ?? Observable.Never<IProfileEditorCommand?>())
.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);
}
}

View File

@ -36,6 +36,10 @@
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="4*,Auto,*" Classes="editor-grid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Content="{Binding MenuBarViewModel}"></ContentControl>
<Grid Grid.Column="0" RowDefinitions="3*,Auto,*">
<Border Grid.Row="0" Classes="card" Padding="0" Margin="4 0 4 4" ClipToBounds="True">
@ -80,5 +84,7 @@
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">Conditions</TextBlock>
</Border>
</Grid>
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding StatusBarViewModel}"></ContentControl>
</Grid>
</UserControl>

View File

@ -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;

View File

@ -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
/// <inheritdoc />
public void OpenMainWindow()
{
_registrationService.RegisterBuiltInDataModelDisplays();
_registrationService.RegisterBuiltInDataModelInputs();
_registrationService.RegisterBuiltInPropertyEditors();
if (_lifeTime.MainWindow == null)
{
SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this);

View File

@ -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<BrushPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<ColorGradientPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKColorPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKPointPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKSizePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<BoolPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatRangePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntRangePropertyInputViewModel>(Constants.CorePlugin);
_registeredBuiltInPropertyEditors = true;
}
public void RegisterControllers()