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

Color properties - Redesigned input

This commit is contained in:
Robert 2022-02-19 23:24:00 +01:00
parent 2a6ed8cc7f
commit 09e7bb5168
11 changed files with 149 additions and 82 deletions

View File

@ -58,7 +58,7 @@ namespace Artemis.UI.Shared.Controls
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_enumComboBox == null)
if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1)
return;
(Enum enumValue, _) = _currentValues[_enumComboBox.SelectedIndex];

View File

@ -17,10 +17,8 @@ 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
{
private T _dragStartValue;
private bool _inputDragging;
[AllowNull] private T _inputValue;
private LayerPropertyPreview<T>? _preview;
private TimeSpan _time;
private bool _updating;
@ -35,7 +33,6 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
PropertyInputService = propertyInputService;
_inputValue = default!;
_dragStartValue = default!;
this.WhenActivated(d =>
{
@ -55,8 +52,6 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
.Subscribe(_ => UpdateDataBinding())
.DisposeWith(d);
});
ValidationContext.ValidationStatusChange.Subscribe(s => Console.WriteLine(s));
}
/// <summary>
@ -80,17 +75,13 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
public IPropertyInputService PropertyInputService { get; }
/// <summary>
/// Gets or sets a boolean indicating whether the input is currently being dragged
/// Gets or boolean indicating whether the current input is being previewed, the value won't be applied until
/// <para>
/// Only applicable when using something like a <see cref="DraggableFloat" />, see
/// <see cref="InputDragStarted" /> and <see cref="InputDragEnded" />
/// <see cref="StartPreview" /> and <see cref="ApplyPreview" />
/// </para>
/// </summary>
public bool InputDragging
{
get => _inputDragging;
private set => this.RaiseAndSetIfChanged(ref _inputDragging, value);
}
public bool IsPreviewing => _preview != null;
/// <summary>
/// Gets or sets the input value
@ -119,27 +110,37 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
internal override object InternalGuard { get; } = new();
/// <summary>
/// Called by the view input drag has started
/// <para>
/// To use, add the following to DraggableFloat in your xaml: <c>DragStarted="{s:Action InputDragStarted}"</c>
/// </para>
/// Starts the preview of the current property, allowing updates without causing real changes to the property.
/// </summary>
public void InputDragStarted(object sender, EventArgs e)
public void StartPreview()
{
InputDragging = true;
_dragStartValue = GetDragStartValue();
_preview?.DiscardPreview();
_preview = new LayerPropertyPreview<T>(LayerProperty, _time);
}
/// <summary>
/// Called by the view when input drag has ended
/// <para>
/// To use, add the following to DraggableFloat in your xaml: <c>DragEnded="{s:Action InputDragEnded}"</c>
/// </para>
/// Applies the current preview to the property.
/// </summary>
public void InputDragEnded(object sender, EventArgs e)
public void ApplyPreview()
{
InputDragging = false;
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _dragStartValue, _time));
if (_preview == null)
return;
if (_preview.DiscardPreview() && _preview.PreviewValue != null)
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, _time));
_preview = null;
}
/// <summary>
/// Discard the preview of the property.
/// </summary>
public void DiscardPreview()
{
if (_preview == null)
return;
_preview.DiscardPreview();
_preview = null;
}
/// <summary>
@ -157,37 +158,31 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
}
/// <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 using an <see cref="IProfileEditorCommand" />.
/// Applies the input value to the layer property or the currently active preview.
/// </summary>
protected virtual void ApplyInputValue()
{
if (_updating)
// Avoid reapplying the latest value by checking if we're currently updating
if (_updating || !ValidationContext.IsValid)
return;
if (InputDragging)
ProfileEditorService.ChangeTime(_time);
else if (ValidationContext.IsValid)
if (_preview != null)
_preview.Preview(_inputValue);
else
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _time));
}
private void UpdateInputValue()
{
// Always run this on the UI thread to avoid race conditions with ApplyInputValue
Dispatcher.UIThread.Post(() =>
{
try
{
_updating = true;
// Avoid unnecessary UI updates and validator cycles
if (_inputValue != null && _inputValue.Equals(LayerProperty.CurrentValue) || _inputValue == null && LayerProperty.CurrentValue == null)
if (Equals(_inputValue, LayerProperty.CurrentValue))
return;
// Override the input value
@ -202,7 +197,6 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
_updating = false;
}
});
}
private void UpdateDataBinding()

View File

@ -5,25 +5,56 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView"
x:DataType="propertyInput:SKColorPropertyInputViewModel">
<UserControl.Styles>
<Style Selector="controls|ColorPickerButton.contained-color-picker-button">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="4 0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="MinHeight" Value="00" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<controls:Button Name="MainButton"
Padding="0"
BorderThickness="0"
CornerRadius="{TemplateBinding CornerRadius}">
<Border BorderBrush="{DynamicResource ColorPickerButtonOutline}"
BorderThickness="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
MinWidth="18" MinHeight="18"
Background="{TemplateBinding Color, Converter={StaticResource ColorBrushConv}}"
CornerRadius="{TemplateBinding CornerRadius}">
<controls:ColorPicker Name="ColorPicker" IsVisible="False" />
</Border>
</controls:Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="controls|ColorPickerButton.contained-color-picker-button /template/ Viewbox">
<Setter Property="IsVisible" Value="False"></Setter>
</Style>
</UserControl.Styles>
<UserControl.Resources>
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<converters:SKColorToColor2Converter x:Key="SKColorToColor2Converter" />
</UserControl.Resources>
<Grid Height="24" ColumnDefinitions="*,Auto">
<Grid Height="24" ColumnDefinitions="*">
<TextBox Classes="condensed"
Grid.Row="0"
Grid.Column="0"
Text="{CompiledBinding InputValue, Converter={StaticResource SKColorToStringConverter}}" />
<controls:ColorPickerButton Grid.Row="0"
Grid.Column="1"
Text="{CompiledBinding InputValue, Converter={StaticResource SKColorToStringConverter}}"
Padding="2 2 30 2">
</TextBox>
<controls:ColorPickerButton Classes="contained-color-picker-button"
Color="{CompiledBinding InputValue, Converter={StaticResource SKColorToColor2Converter}}"
Classes="condensed"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="-6 0 -8 0" />
ShowAcceptDismissButtons="False" />
</Grid>
</UserControl>

View File

@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
@ -17,4 +18,4 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
AvaloniaXamlLoader.Load(this);
}
}
}
}

View File

@ -3,8 +3,10 @@
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:menuBar="clr-namespace:Artemis.UI.Screens.ProfileEditor.MenuBar"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView">
x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView"
x:DataType="menuBar:MenuBarViewModel">
<Menu VerticalAlignment="Top">
<MenuItem Header="_File">
<MenuItem Header="New">
@ -38,11 +40,11 @@
<avalonia:MaterialIcon Kind="Magic" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Suspend Profile">
<MenuItem Header="Suspend Profile" IsSelected="{CompiledBinding IsSuspended}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsHitTestVisible="False"
IsChecked="{Binding ProfileConfiguration.IsSuspended}" />
IsChecked="{CompiledBinding IsSuspended}"/>
</MenuItem.Icon>
</MenuItem>
<Separator />

View File

@ -1,5 +1,8 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI;
@ -8,11 +11,24 @@ namespace Artemis.UI.Screens.ProfileEditor.MenuBar;
public class MenuBarViewModel : ActivatableViewModelBase
{
private readonly IProfileService _profileService;
private ProfileEditorHistory? _history;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<bool>? _isSuspended;
public MenuBarViewModel(IProfileEditorService profileEditorService)
public MenuBarViewModel(IProfileEditorService profileEditorService, IProfileService profileService)
{
this.WhenActivated(d => profileEditorService.History.Subscribe(history => History = history).DisposeWith(d));
_profileService = profileService;
this.WhenActivated(d =>
{
profileEditorService.History.Subscribe(history => History = history).DisposeWith(d);
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration);
_isSuspended = profileEditorService.ProfileConfiguration
.Select(p => p?.WhenAnyValue(c => c.IsSuspended) ?? Observable.Never<bool>())
.Switch()
.ToProperty(this, vm => vm.IsSuspended)
.DisposeWith(d);
});
}
public ProfileEditorHistory? History
@ -20,4 +36,19 @@ public class MenuBarViewModel : ActivatableViewModelBase
get => _history;
set => RaiseAndSetIfChanged(ref _history, value);
}
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public bool IsSuspended
{
get => _isSuspended?.Value ?? false;
set
{
if (ProfileConfiguration == null)
return;
ProfileConfiguration.IsSuspended = value;
_profileService.SaveProfileCategory(ProfileConfiguration.Category);
}
}
}

View File

@ -52,15 +52,17 @@ public class VisualEditorViewModel : ActivatableViewModelBase
Tools = tools;
this.WhenAnyValue(vm => vm.ProfileConfiguration)
.Select(p => p?.Profile != null
? Observable.FromEventPattern<ProfileElementEventArgs>(x => p.Profile.DescendentAdded += x, x => p.Profile.DescendentAdded -= x)
.Select(p => p?.Profile)
.Select(p => p != null
? Observable.FromEventPattern<ProfileElementEventArgs>(x => p.DescendentAdded += x, x => p.DescendentAdded -= x)
: Observable.Never<EventPattern<ProfileElementEventArgs>>())
.Switch()
.Subscribe(AddElement)
.DisposeWith(d);
this.WhenAnyValue(vm => vm.ProfileConfiguration)
.Select(p => p?.Profile != null
? Observable.FromEventPattern<ProfileElementEventArgs>(x => p.Profile.DescendentRemoved += x, x => p.Profile.DescendentRemoved -= x)
.Select(p => p?.Profile)
.Select(p => p != null
? Observable.FromEventPattern<ProfileElementEventArgs>(x => p.DescendentRemoved += x, x => p.DescendentRemoved -= x)
: Observable.Never<EventPattern<ProfileElementEventArgs>>())
.Switch()
.Subscribe(RemoveElement)

View File

@ -14,7 +14,7 @@
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="450">
<Grid RowDefinitions="250,50,Auto,*" IsHitTestVisible="False">
<Image Grid.Column="0" Stretch="Uniform">
<Image Grid.Column="0" Stretch="Uniform" Width="200">
<Image.Source>
<svg:SvgImage Source="/Assets/Images/Logo/bow.svg" />
</Image.Source>
@ -22,7 +22,6 @@
<TextBlock Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
FontSize="16"
TextWrapping="Wrap">
Artemis is initializing...
</TextBlock>

View File

@ -83,7 +83,7 @@
</Button>
<ToggleButton Classes="category-button icon-button icon-button-small"
Grid.Column="3"
ToolTip.Tip="Suspend profile"
ToolTip.Tip="Suspend category"
Margin="5 0"
IsChecked="{Binding IsSuspended}">
<avalonia:MaterialIcon Kind="Pause" />

View File

@ -5,8 +5,10 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarProfileConfigurationView">
x:Class="Artemis.UI.Screens.Sidebar.SidebarProfileConfigurationView"
x:DataType="sidebar:SidebarProfileConfigurationViewModel">
<UserControl.Resources>
<converters:ValuesAdditionConverter x:Key="ValuesAddition" />
</UserControl.Resources>
@ -65,17 +67,11 @@
</MenuItem>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
<controls:ProfileConfigurationIcon Grid.Column="0"
x:Name="ProfileIcon"
VerticalAlignment="Center"
ConfigurationIcon="{Binding ProfileConfiguration.Icon}"
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
Width="20"
Height="20" />
@ -85,7 +81,7 @@
Margin="10 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding ProfileConfiguration.Name}"
Text="{CompiledBinding ProfileConfiguration.Name}"
TextTrimming="CharacterEllipsis" />
<Border Grid.Column="0"
@ -117,7 +113,7 @@
Grid.Column="3"
ToolTip.Tip="Suspend profile"
Margin="2 0 0 0"
IsChecked="{Binding IsSuspended}">
IsChecked="{CompiledBinding IsSuspended}">
<avalonia:MaterialIcon Kind="Pause" />
</ToggleButton>
</Grid>

View File

@ -1,16 +1,20 @@
using System.Threading.Tasks;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
{
public class SidebarProfileConfigurationViewModel : ViewModelBase
public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
{
private readonly SidebarViewModel _sidebarViewModel;
private readonly IProfileService _profileService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _isSuspended;
public ProfileConfiguration ProfileConfiguration { get; }
public SidebarProfileConfigurationViewModel(SidebarViewModel sidebarViewModel, ProfileConfiguration profileConfiguration, IProfileService profileService, IWindowService windowService)
@ -18,8 +22,15 @@ namespace Artemis.UI.Screens.Sidebar
_sidebarViewModel = sidebarViewModel;
_profileService = profileService;
_windowService = windowService;
ProfileConfiguration = profileConfiguration;
this.WhenActivated(d =>
{
_isSuspended = ProfileConfiguration.WhenAnyValue(c => c.IsSuspended)
.ToProperty(this, vm => vm.IsSuspended)
.DisposeWith(d);
});
_profileService.LoadProfileConfigurationIcon(ProfileConfiguration);
}
@ -27,7 +38,7 @@ namespace Artemis.UI.Screens.Sidebar
public bool IsSuspended
{
get => ProfileConfiguration.IsSuspended;
get => _isSuspended?.Value ?? false;
set
{
ProfileConfiguration.IsSuspended = value;