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:
parent
c04bff1f48
commit
022beb6a48
@ -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
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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>
|
||||
@ -69,7 +71,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
||||
public IProfileEditorService ProfileEditorService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property input service
|
||||
/// Gets the property input service
|
||||
/// </summary>
|
||||
public IPropertyInputService PropertyInputService { get; }
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -0,0 +1,8 @@
|
||||
using Artemis.UI.Shared.DataModelVisualization;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.DefaultTypes.DataModel.Display;
|
||||
|
||||
public class SKColorDataModelDisplayViewModel : DataModelDisplayViewModel<SKColor>
|
||||
{
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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}.");
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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}.");
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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<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
|
||||
{
|
||||
get => _history;
|
||||
set => this.RaiseAndSetIfChanged(ref _history, value);
|
||||
}
|
||||
public ProfileEditorHistory? History
|
||||
{
|
||||
get => _history;
|
||||
set => this.RaiseAndSetIfChanged(ref _history, value);
|
||||
}
|
||||
}
|
||||
@ -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}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||
</TreeDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<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>
|
||||
|
||||
@ -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
|
||||
{
|
||||
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<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);
|
||||
// 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<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));
|
||||
|
||||
// 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<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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(() =>
|
||||
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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">
|
||||
@ -62,7 +66,7 @@
|
||||
|
||||
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
|
||||
|
||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4">
|
||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4">
|
||||
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"></ContentControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
@ -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>
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user