Gradient editor - Implemented picker button and layer property editor

This commit is contained in:
Robert committed 2022-02-27 18:37:33 +01:00
1 parent 1262f84a56
commit 8806348386
22 files changed
+1542 -585

No files matched your search

@@ -0,0 +1,25 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace Artemis.UI.Shared.Controls.Flyouts;
/// <summary>
/// Defines a flyout that hosts a gradient picker.
/// </summary>
public sealed class GradientPickerFlyout : Flyout
{
private GradientPicker.GradientPicker? _picker;
/// <summary>
/// Gets the gradient picker that this flyout hosts
/// </summary>
public GradientPicker.GradientPicker GradientPicker => _picker ??= new GradientPicker.GradientPicker();
/// <inheritdoc />
protected override Control CreatePresenter()
{
_picker ??= new GradientPicker.GradientPicker();
FlyoutPresenter presenter = new() {Content = GradientPicker};
return presenter;
}
}
@@ -1,24 +1,33 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using Artemis.Core;
using Artemis.UI.Shared.Providers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media;
using ReactiveUI;
using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker;
/// <summary>
/// Represents a gradient picker that can be used to edit a gradient.
/// </summary>
public class GradientPicker : TemplatedControl
{
private LinearGradientBrush _linearGradientBrush = new();
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public static readonly StyledProperty<ColorGradient> ColorGradientProperty =
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(Core.ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
/// <summary>
/// Gets or sets the currently selected color stop.
@@ -27,11 +36,55 @@ public class GradientPicker : TemplatedControl
AvaloniaProperty.Register<GradientPicker, ColorGradientStop?>(nameof(SelectedColorStop), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public static readonly StyledProperty<bool> IsCompactProperty =
AvaloniaProperty.Register<GradientPicker, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
AvaloniaProperty.Register<GradientPicker, IColorGradientStorageProvider?>(nameof(StorageProvider), notifying: StorageProviderChanged);
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public static readonly DirectProperty<GradientPicker, LinearGradientBrush> LinearGradientBrushProperty =
AvaloniaProperty.RegisterDirect<GradientPicker, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
/// <summary>
/// Gets the command to execute when deleting stops.
/// </summary>
public static readonly DirectProperty<GradientPicker, ICommand> DeleteStopProperty =
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
private readonly ICommand _deleteStop;
private Button? _flipStops;
private Border? _gradient;
private Button? _rotateStops;
private bool _shiftDown;
private Button? _spreadStops;
private Button? _toggleSeamless;
private ColorGradient? _lastColorGradient;
private ColorPicker? _colorPicker;
public GradientPicker()
{
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>
{
if (ColorGradient.Count <= 2)
return;
int index = ColorGradient.IndexOf(s);
ColorGradient.Remove(s);
if (index > ColorGradient.Count - 1)
index--;
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
});
}
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
@@ -47,39 +100,130 @@ public class GradientPicker : TemplatedControl
public ColorGradientStop? SelectedColorStop
{
get => GetValue(SelectedColorStopProperty);
set => SetValue(SelectedColorStopProperty, value);
set
{
if (_colorPicker != null && SelectedColorStop != null)
_colorPicker.PreviousColor = new Color2(SelectedColorStop.Color.Red, SelectedColorStop.Color.Green, SelectedColorStop.Color.Blue, SelectedColorStop.Color.Alpha);
SetValue(SelectedColorStopProperty, value);
}
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public LinearGradientBrush LinearGradientBrush
public bool IsCompact
{
get => _linearGradientBrush;
private set => SetAndRaise(LinearGradientBrushProperty, ref _linearGradientBrush, value);
get => GetValue(IsCompactProperty);
set => SetValue(IsCompactProperty, value);
}
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public IColorGradientStorageProvider? StorageProvider
{
get => GetValue(StorageProviderProperty);
set => SetValue(StorageProviderProperty, value);
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public LinearGradientBrush LinearGradientBrush { get; } = new();
/// <summary>
/// Gets the command to execute when deleting stops.
/// </summary>
public ICommand DeleteStop
{
get => _deleteStop;
private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_gradient != null)
_gradient.PointerPressed -= GradientOnPointerPressed;
if (_spreadStops != null)
_spreadStops.Click -= SpreadStopsOnClick;
if (_toggleSeamless != null)
_toggleSeamless.Click -= ToggleSeamlessOnClick;
if (_flipStops != null)
_flipStops.Click -= FlipStopsOnClick;
if (_rotateStops != null)
_rotateStops.Click -= RotateStopsOnClick;
_colorPicker = e.NameScope.Find<ColorPicker>("ColorPicker");
_gradient = e.NameScope.Find<Border>("Gradient");
_spreadStops = e.NameScope.Find<Button>("SpreadStops");
_toggleSeamless = e.NameScope.Find<Button>("ToggleSeamless");
_flipStops = e.NameScope.Find<Button>("FlipStops");
_rotateStops = e.NameScope.Find<Button>("RotateStops");
if (_gradient != null)
_gradient.PointerPressed += GradientOnPointerPressed;
if (_spreadStops != null)
_spreadStops.Click += SpreadStopsOnClick;
if (_toggleSeamless != null)
_toggleSeamless.Click += ToggleSeamlessOnClick;
if (_flipStops != null)
_flipStops.Click += FlipStopsOnClick;
if (_rotateStops != null)
_rotateStops.Click += RotateStopsOnClick;
base.OnApplyTemplate(e);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
Subscribe();
KeyUp += OnKeyUp;
KeyDown += OnKeyDown;
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Unsubscribe();
KeyUp -= OnKeyUp;
KeyDown -= OnKeyDown;
_shiftDown = false;
}
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
if (before)
(sender as GradientPicker)?.Unsubscribe();
else
(sender as GradientPicker)?.Subscribe();
(sender as GradientPicker)?.Subscribe();
}
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
{
}
private void Subscribe()
{
Unsubscribe();
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
ColorGradient.StopChanged += ColorGradientOnStopChanged;
SelectedColorStop = ColorGradient.FirstOrDefault();
UpdateGradient();
SelectedColorStop = ColorGradient.FirstOrDefault();
_lastColorGradient = ColorGradient;
}
private void Unsubscribe()
{
ColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
ColorGradient.StopChanged -= ColorGradientOnStopChanged;
if (_lastColorGradient == null)
return;
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
_lastColorGradient = null;
}
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -94,42 +238,75 @@ public class GradientPicker : TemplatedControl
private void UpdateGradient()
{
// Remove old stops
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (ColorGradient == null)
return;
// Add new stops
// Update the display gradient
GradientStops collection = new();
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
LinearGradientBrush = new LinearGradientBrush {GradientStops = collection};
LinearGradientBrush.GradientStops = collection;
}
private void SelectColorStop(object? sender, PointerReleasedEventArgs e)
private void GradientOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is ColorGradientStop colorStop)
SelectedColorStop = colorStop;
if (_gradient == null)
return;
float position = (float) (e.GetPosition(_gradient).X / _gradient.Bounds.Width);
ColorGradientStop newStop = new(ColorGradient.GetColor(position), position);
ColorGradient.Add(newStop);
SelectedColorStop = newStop;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
private void OnKeyDown(object? sender, KeyEventArgs e)
{
Subscribe();
base.OnAttachedToVisualTree(e);
if (e.Key is Key.LeftShift or Key.RightShift)
_shiftDown = true;
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
private void OnKeyUp(object? sender, KeyEventArgs e)
{
Unsubscribe();
base.OnDetachedFromVisualTree(e);
if (e.Key is Key.LeftShift or Key.RightShift)
_shiftDown = false;
if (e.Key != Key.Delete || SelectedColorStop == null || ColorGradient.Count <= 2)
return;
int index = ColorGradient.IndexOf(SelectedColorStop);
ColorGradient.Remove(SelectedColorStop);
if (index > ColorGradient.Count - 1)
index--;
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
e.Handled = true;
}
#endregion
private void SpreadStopsOnClick(object? sender, RoutedEventArgs e)
{
ColorGradient.SpreadStops();
}
private void ToggleSeamlessOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.ToggleSeamless();
}
private void FlipStopsOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.FlipStops();
}
private void RotateStopsOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.RotateStops(_shiftDown);
}
}
@@ -0,0 +1,212 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Controls.Flyouts;
using Artemis.UI.Shared.Providers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Media;
using FluentAvalonia.Core;
using Button = FluentAvalonia.UI.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker;
/// <summary>
/// Represents a gradient picker box that can be used to edit a gradient
/// </summary>
public class GradientPickerButton : TemplatedControl
{
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public static readonly StyledProperty<ColorGradient?> ColorGradientProperty =
AvaloniaProperty.Register<GradientPickerButton, ColorGradient?>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public static readonly StyledProperty<bool> IsCompactProperty =
AvaloniaProperty.Register<GradientPickerButton, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
AvaloniaProperty.Register<GradientPickerButton, IColorGradientStorageProvider?>(nameof(StorageProvider));
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public static readonly DirectProperty<GradientPickerButton, LinearGradientBrush> LinearGradientBrushProperty =
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
private ColorGradient? _lastColorGradient;
private Button? _button;
private GradientPickerFlyout? _flyout;
private bool _flyoutActive;
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public ColorGradient? ColorGradient
{
get => GetValue(ColorGradientProperty);
set => SetValue(ColorGradientProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public bool IsCompact
{
get => GetValue(IsCompactProperty);
set => SetValue(IsCompactProperty, value);
}
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public IColorGradientStorageProvider? StorageProvider
{
get => GetValue(StorageProviderProperty);
set => SetValue(StorageProviderProperty, value);
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public LinearGradientBrush LinearGradientBrush { get; } = new();
/// <summary>
/// Raised when the flyout opens.
/// </summary>
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutOpened;
/// <summary>
/// Raised when the flyout closes.
/// </summary>
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutClosed;
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_button != null)
_button.Click -= OnButtonClick;
base.OnApplyTemplate(e);
_button = e.NameScope.Find<Button>("MainButton");
if (_button != null)
_button.Click += OnButtonClick;
}
#endregion
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
(sender as GradientPickerButton)?.Subscribe();
}
private void Subscribe()
{
Unsubscribe();
if (ColorGradient != null)
{
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
ColorGradient.StopChanged += ColorGradientOnStopChanged;
}
UpdateGradient();
_lastColorGradient = ColorGradient;
}
private void Unsubscribe()
{
if (_lastColorGradient == null)
return;
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
_lastColorGradient = null;
}
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateGradient();
}
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
{
UpdateGradient();
}
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
if (_flyout == null || ColorGradient == null)
return;
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
_flyout.GradientPicker.ColorGradient = ColorGradient;
_flyout.GradientPicker.IsCompact = IsCompact;
_flyout.GradientPicker.StorageProvider = StorageProvider;
_flyout.ShowAt(this);
_flyoutActive = true;
FlyoutOpened?.Invoke(this, EventArgs.Empty);
}
private void OnFlyoutClosed(object? sender, EventArgs e)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
}
}
private void UpdateGradient()
{
// Update the display gradient
GradientStops collection = new();
if (ColorGradient != null)
{
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
}
LinearGradientBrush.GradientStops = collection;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
Subscribe();
if (_flyout == null)
{
_flyout = new GradientPickerFlyout();
_flyout.FlyoutPresenterClasses.Add("gradient-picker-presenter");
}
_flyout.Closed += OnFlyoutClosed;
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Unsubscribe();
if (_flyout != null)
_flyout.Closed -= OnFlyoutClosed;
}
#endregion
}
@@ -29,7 +29,7 @@ public class GradientPickerColorStop : TemplatedControl
else if (self.GradientPicker != null)
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
self.IsSelected = self.GradientPicker?.SelectedColorStop == self.ColorStop;
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
}
/// <summary>
@@ -0,0 +1,29 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters;
/// <summary>
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.SolidColorBrush" />.
/// </summary>
public class SKColorToBrushConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SKColor skColor)
return new SolidColorBrush(new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue));
return new SolidColorBrush(Colors.Transparent);
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SolidColorBrush brush)
return new SKColor(brush.Color.R, brush.Color.G, brush.Color.B, brush.Color.A);
return SKColor.Empty;
}
}
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using Artemis.Core;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for color gradient storage.
/// </summary>
public interface IColorGradientStorageProvider
{
/// <summary>
/// Returns a list containing a copy of all stored color gradients.
/// </summary>
/// <returns>A <see cref="List{T}" /> of <see cref="ColorGradient" /> containing a copy of all stored color gradients.</returns>
public List<ColorGradient> GetColorGradients();
/// <summary>
/// Saves the provided color gradient to storage.
/// </summary>
/// <param name="colorGradient">The color gradient to save.</param>
public void SaveColorGradient(ColorGradient colorGradient);
/// <summary>
/// Deletes the provided color gradient from storage.
/// </summary>
/// <param name="colorGradient">The color gradient to delete.</param>
public void DeleteColorGradient(ColorGradient colorGradient);
}
@@ -1,10 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Controls.Mixins;
using Avalonia.Threading;
using ReactiveUI;
using ReactiveUI.Validation.Helpers;
@@ -18,9 +18,9 @@ namespace Artemis.UI.Shared.Services.PropertyInput;
public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
{
[AllowNull] private T _inputValue;
private LayerPropertyPreview<T>? _preview;
private TimeSpan _time;
private bool _updating;
/// <summary>
@@ -36,7 +36,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
this.WhenActivated(d =>
{
ProfileEditorService.Time.Subscribe(t => _time = t).DisposeWith(d);
ProfileEditorService.Time.Subscribe(t => Time = t).DisposeWith(d);
UpdateInputValue();
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.Updated += x, x => LayerProperty.Updated -= x)
@@ -51,6 +51,8 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
Observable.FromEventPattern<DataBindingEventArgs>(x => LayerProperty.DataBinding.DataBindingDisabled += x, x => LayerProperty.DataBinding.DataBindingDisabled -= x)
.Subscribe(_ => UpdateDataBinding())
.DisposeWith(d);
Disposable.Create(DiscardPreview).DisposeWith(d);
});
}
@@ -107,34 +109,39 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
/// </summary>
public string? Affix => LayerProperty.PropertyDescription.InputAffix;
/// <summary>
/// Gets the current time at which the property is being edited
/// </summary>
protected TimeSpan Time { get; private set; }
internal override object InternalGuard { get; } = new();
/// <summary>
/// Starts the preview of the current property, allowing updates without causing real changes to the property.
/// </summary>
public void StartPreview()
public virtual void StartPreview()
{
_preview?.DiscardPreview();
_preview = new LayerPropertyPreview<T>(LayerProperty, _time);
_preview = new LayerPropertyPreview<T>(LayerProperty, Time);
}
/// <summary>
/// Applies the current preview to the property.
/// </summary>
public void ApplyPreview()
public virtual void ApplyPreview()
{
if (_preview == null)
return;
if (_preview.DiscardPreview() && _preview.PreviewValue != null)
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, _time));
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, Time));
_preview = null;
}
/// <summary>
/// Discard the preview of the property.
/// </summary>
public void DiscardPreview()
public virtual void DiscardPreview()
{
if (_preview == null)
return;
@@ -169,7 +176,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
if (_preview != null)
_preview.Preview(_inputValue);
else
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _time));
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, Time));
}
private void UpdateInputValue()
@@ -11,7 +11,7 @@
</Grid>
</VisualBrush.Visual>
</VisualBrush>
<VisualBrush x:Key="LargeCheckerboardBrush" TileMode="Tile" Stretch="Uniform" SourceRect="0,0,25,25">
<VisualBrush x:Key="LargeCheckerboardBrush" TileMode="Tile" Stretch="Uniform" SourceRect="0,0,25,25">
<VisualBrush.Visual>
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
@@ -33,6 +33,7 @@
<StyleInclude Source="/Styles/TreeView.axaml" />
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
<Style Selector="Window:windows:windows10 /template/ Border#RootBorder">
<!-- This will show if custom accent color setting is used in Settings page-->
@@ -1,7 +1,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls">
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
<Design.PreviewWith>
<Border Padding="50">
<StackPanel Spacing="5">
@@ -28,6 +29,9 @@
<controls:ColorPickerButton Color="Firebrick"></controls:ColorPickerButton>
<controls:ColorPickerButton Color="Firebrick" Classes="condensed"></controls:ColorPickerButton>
<gradientPicker:GradientPickerButton></gradientPicker:GradientPickerButton>
<gradientPicker:GradientPickerButton Classes="condensed"></gradientPicker:GradientPickerButton>
</StackPanel>
</Border>
</Design.PreviewWith>
@@ -65,4 +69,10 @@
</Setter>
</Style>
<Style Selector="gradientPicker|GradientPickerButton.condensed">
<Setter Property="Padding" Value="4 2" />
<Setter Property="FontSize" Value="13" />
<Setter Property="MinHeight" Value="24" />
</Style>
</Styles>
@@ -3,10 +3,22 @@
xmlns:controls="using:Artemis.UI.Shared.Controls.GradientPicker"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:fluent="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters">
<Styles.Resources>
<VisualBrush x:Key="LightCheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="3,0,10,10">
<VisualBrush.Visual>
<Grid Width="10" Height="10" RowDefinitions="*,*" ColumnDefinitions="*,*">
<Rectangle Grid.Row="0" Grid.Column="0" Fill="#c3c3c3" />
<Rectangle Grid.Row="0" Grid.Column="1" Fill="White" />
<Rectangle Grid.Row="1" Grid.Column="0" Fill="White" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="#c3c3c3" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Styles.Resources>
<Design.PreviewWith>
<controls:GradientPicker Width="800" />
<controls:GradientPicker />
</Design.PreviewWith>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle">
@@ -15,17 +27,19 @@
<Setter Property="Height" Value="60" />
<Setter Property="Margin" Value="-9 -10 0 0"></Setter>
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToolTipBorderBrush}" />
<Setter Property="Cursor" Value="Hand" />
</Style>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border">
<Setter Property="CornerRadius" Value="18" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}" />
<Setter Property="Background" Value="{DynamicResource LightCheckerboardBrush}" />
</Style>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border > Border">
<Setter Property="CornerRadius" Value="18" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Margin" Value="-1" />
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
</Style>
@@ -33,17 +47,25 @@
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorLight2}" />
</Style>
<Style Selector="Border.stop-position">
<Style Selector="controls|GradientPicker Border#Gradient">
<Setter Property="Height" Value="40" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="controls|GradientPicker Border#Gradient Border">
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="controls|GradientPicker Border.stop-position">
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Width" Value="40" />
<Setter Property="Height" Value="30" />
<Setter Property="Margin" Value="-20 -15 0 0"></Setter>
<Setter Property="Margin" Value="-20 -15 0 0" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="Border.stop-position TextBlock">
<Style Selector="controls|GradientPicker Border.stop-position TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
@@ -52,79 +74,21 @@
<Style.Resources>
<converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<converters:WidthNormalizedConverter x:Key="WidthNormalizedConverter" />
</Style.Resources>
<Setter Property="Focusable" Value="True" />
<Setter Property="Template">
<ControlTemplate>
<Grid>
<Border Name="Background" Background="{TemplateBinding LinearGradientBrush}" />
<Border Classes="card" Margin="20" Padding="20" Background="{DynamicResource SolidBackgroundFillColorBaseBrush}">
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="Auto,*">
<Border Name="Gradient" Grid.Row="0" Grid.ColumnSpan="2" Height="40" CornerRadius="4" Background="{TemplateBinding LinearGradientBrush}">
<ItemsControl Items="{TemplateBinding ColorGradient}" ClipToBounds="False">
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource WidthNormalizedConverter}">
<Binding Path="Position" />
<Binding Path="Bounds.Width" ElementName="Gradient" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop">
<controls:GradientPickerColorStop ColorStop="{Binding}"
GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}"
PositionReference="{Binding $parent[Border]}">
<controls:GradientPickerColorStop.Styles>
<Style Selector="controls|GradientPickerColorStop">
<Setter Property="ClipToBounds" Value="False"></Setter>
<Setter Property="Template">
<ControlTemplate>
<Border Classes="stop-handle">
<Border>
<Border>
<Border.Background>
<SolidColorBrush Color="{CompiledBinding Color, Converter={StaticResource SKColorToColorConverter}}" />
</Border.Background>
</Border>
</Border>
</Border>
</ControlTemplate>
</Setter>
</Style>
</controls:GradientPickerColorStop.Styles>
</controls:GradientPickerColorStop>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
<Border Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0 0 0 1"
Margin="-20 30"
Height="2"
VerticalAlignment="Center">
</Border>
<ItemsControl Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
VerticalAlignment="Center"
Items="{TemplateBinding ColorGradient}"
ClipToBounds="False">
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="Auto,255" Margin="20">
<Border Name="Gradient"
Grid.Row="0"
Grid.ColumnSpan="2"
Background="{DynamicResource LightCheckerboardBrush}"
Margin="5 0">
<Border Background="{TemplateBinding LinearGradientBrush}">
<ItemsControl Items="{TemplateBinding ColorGradient}" ClipToBounds="False">
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left">
@@ -139,9 +103,11 @@
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop">
<Border Classes="stop-position">
<TextBlock Text="{Binding Position}"></TextBlock>
</Border>
<controls:GradientPickerColorStop ColorStop="{Binding}"
PositionReference="{Binding $parent[Border]}"
Classes="gradient-handle"
GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}">
</controls:GradientPickerColorStop>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
@@ -150,43 +116,147 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<controls1:ColorPicker Grid.Row="2" Grid.Column="0"
ColorTextType="HexAlpha"
UseColorWheel="True"
UseColorTriangle="True"
IsMoreButtonVisible="True"
IsCompact="True"
IsVisible="{TemplateBinding SelectedColorStop, Converter={x:Static ObjectConverters.IsNotNull}}"
Color="{Binding SelectedColorStop.Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource SKColorToColorConverter}}" />
<ListBox Grid.Row="2"
Grid.Column="1"
BorderBrush="{DynamicResource ButtonBorderBrush}"
Items="{TemplateBinding ColorGradient}"
BorderThickness="1 0 0 0"
Margin="10 0 0 0">
<ListBox.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop">
<Grid VerticalAlignment="Center" ColumnDefinitions="34,*,Auto,Auto">
<Border Grid.Column="0" Width="28" Height="28" CornerRadius="4" HorizontalAlignment="Left">
<Border.Background>
<SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}"></SolidColorBrush>
</Border.Background>
</Border>
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}"></TextBox>
<NumericUpDown Grid.Column="2" Value="{Binding Position}" FormatString="F3" ShowButtonSpinner="False" Margin="5 0"></NumericUpDown>
<Button Grid.Column="3" Classes="icon-button">
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Border>
<Border Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0 0 0 1"
Margin="-20 30"
Height="2"
VerticalAlignment="Center">
</Border>
<ItemsControl Name="GradientPositions"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
VerticalAlignment="Center"
Items="{TemplateBinding ColorGradient}"
ClipToBounds="False"
Margin="5 0">
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource WidthNormalizedConverter}">
<Binding Path="Position" />
<Binding Path="#GradientPositions.Bounds.Width" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop">
<Border Classes="stop-position">
<TextBlock Text="{Binding Position}"></TextBlock>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Border Grid.Row="2"
Grid.Column="0"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0 0 1 0"
Padding="0 0 10 0">
<fluent:ColorPicker Name="ColorPicker"
ColorTextType="HexAlpha"
UseColorWheel="True"
UseColorTriangle="True"
IsMoreButtonVisible="True"
IsVisible="{TemplateBinding SelectedColorStop, Converter={x:Static ObjectConverters.IsNotNull}}"
IsCompact="{Binding IsCompact, RelativeSource={RelativeSource TemplatedParent}}"
Color="{Binding SelectedColorStop.Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource SKColorToColorConverter}}" />
</Border>
<Grid Grid.Row="2" Grid.Column="1" RowDefinitions="*,Auto">
<ListBox Grid.Row="0"
MaxHeight="280"
Items="{TemplateBinding ColorGradient}"
SelectedItem="{TemplateBinding SelectedColorStop, Mode=TwoWay}"
Padding="10 0 15 0">
<ListBox.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop">
<Grid VerticalAlignment="Center"
ColumnDefinitions="34,*,Auto,Auto"
Margin="-5 0"
Classes="stop-list-item">
<Border Grid.Column="0" Width="28" Height="28" CornerRadius="4" HorizontalAlignment="Left"
BorderThickness="3"
BorderBrush="{DynamicResource ButtonBorderBrush}"
ClipToBounds="True"
Background="{DynamicResource LightCheckerboardBrush}">
<Border CornerRadius="4" Margin="-2" Background="{Binding Color, Converter={StaticResource SKColorToBrushConverter}}" />
</Border>
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}" />
<NumericUpDown Grid.Column="2" Value="{Binding Position}" FormatString="F3" ShowButtonSpinner="False" Margin="5 0" />
<Button Name="DeleteButton"
Grid.Column="3"
Classes="icon-button"
Command="{Binding DeleteStop, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}"
CommandParameter="{Binding}">
<avalonia:MaterialIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Row="1"
Margin="10 5 0 0"
Padding="0 5 0 0"
BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0 1 0 0">
<StackPanel Orientation="Horizontal" Spacing="5" HorizontalAlignment="Right">
<Button Name="SpreadStops" Classes="icon-button operation-button" ToolTip.Tip="Spread the color stops equally across the gradient.">
<avalonia:MaterialIcon Kind="ArrowLeftRight" />
</Button>
<Button Name="ToggleSeamless" Classes="icon-button operation-button"
ToolTip.Tip="Toggle the gradient being seamless by starting and ending with the same color.">
<avalonia:MaterialIcon Kind="SineWave" />
</Button>
<Button Name="FlipStops" Classes="icon-button operation-button" ToolTip.Tip="Flip the stops of the gradient.">
<avalonia:MaterialIcon Kind="FlipHorizontal" />
</Button>
<Button Name="RotateStops" Classes="icon-button operation-button">
<ToolTip.Tip>
<StackPanel>
<TextBlock>Rotates the stops of the gradient.</TextBlock>
<TextBlock>Hold shift to change direction.</TextBlock>
</StackPanel>
</ToolTip.Tip>
<avalonia:MaterialIcon Kind="AxisZRotateCounterclockwise" />
</Button>
</StackPanel>
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="controls|GradientPickerColorStop.gradient-handle">
<Style.Resources>
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
</Style.Resources>
<Setter Property="ClipToBounds" Value="False"></Setter>
<Setter Property="Template">
<ControlTemplate>
<Border Classes="stop-handle">
<Border>
<Border Background="{Binding Color, Converter={StaticResource SKColorToBrushConverter}}" />
</Border>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>
@@ -0,0 +1,61 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Padding="20">
<gradientPicker:GradientPickerButton IsCompact="True" />
</Border>
</Design.PreviewWith>
<Style Selector="FlyoutPresenter.gradient-picker-presenter">
<Setter Property="Padding" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MaxWidth" Value="1200" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="MaxHeight" Value="1200" />
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
</Style>
<Style Selector="gradientPicker|GradientPickerButton">
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}"/>
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="*,Auto">
<controls:Button Grid.Column="0"
Name="MainButton"
Padding="0 0 25 0"
CornerRadius="{TemplateBinding CornerRadius}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<Border BorderBrush="{DynamicResource ColorPickerButtonOutline}"
BorderThickness="1"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding LinearGradientBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />
</controls:Button>
<Viewbox Grid.Column="1"
Width="18"
Height="18"
IsHitTestVisible="False"
VerticalAlignment="Center"
Margin="-25 0 8 0"
Name="Chevron">
<controls:SymbolIcon Symbol="ChevronDown" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter>
</Style>
</Styles>
@@ -2,7 +2,15 @@
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:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView">
TODO
</UserControl>
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView"
x:DataType="propertyInput:ColorGradientPropertyInputViewModel">
<gradientPicker:GradientPickerButton Classes="condensed"
Width="200"
ColorGradient="{CompiledBinding ColorGradient}"
VerticalAlignment="Center"
FlyoutOpened="GradientPickerButton_OnFlyoutOpened"
FlyoutClosed="GradientPickerButton_OnFlyoutClosed"/>
</UserControl>
@@ -1,20 +1,29 @@
using Avalonia;
using Avalonia.Controls;
using System;
using Artemis.UI.Shared.Controls.GradientPicker;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput
{
public partial class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel>
{
public ColorGradientPropertyInputView()
{
InitializeComponent();
}
namespace Artemis.UI.DefaultTypes.PropertyInput;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel>
{
public ColorGradientPropertyInputView()
{
InitializeComponent();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void GradientPickerButton_OnFlyoutOpened(GradientPickerButton sender, EventArgs args)
{
ViewModel?.StartPreview();
}
private void GradientPickerButton_OnFlyoutClosed(GradientPickerButton sender, EventArgs args)
{
ViewModel?.ApplyPreview();
}
}
@@ -1,19 +1,87 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Artemis.UI.Shared.Services.PropertyInput;
using Avalonia.Media;
using ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput;
public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient>
{
private ColorGradient _colorGradient;
private ColorGradient? _originalGradient;
public ColorGradientPropertyInputViewModel(LayerProperty<ColorGradient> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
: base(layerProperty, profileEditorService, propertyInputService)
{
}
public void DialogClosed(object sender, EventArgs e)
public ColorGradient ColorGradient
{
ApplyInputValue();
get => _colorGradient;
set => this.RaiseAndSetIfChanged(ref _colorGradient, value);
}
protected override void OnInputValueChanged()
{
ColorGradient = new ColorGradient(InputValue);
}
#region Overrides of PropertyInputViewModel<ColorGradient>
/// <inheritdoc />
public override void StartPreview()
{
_originalGradient = LayerProperty.CurrentValue;
// Set the property value to the gradient being edited by the picker, this will cause any updates to show right away because
// ColorGradient is a reference type
LayerProperty.CurrentValue = ColorGradient;
// This won't fly if we ever support keyframes but at that point ColorGradient would have to be a value type anyway and this
// whole VM no longer makes sense
}
/// <inheritdoc />
protected override void ApplyInputValue()
{
// Don't do anything, ColorGradient is a reference type and will update regardless
}
/// <inheritdoc />
public override void ApplyPreview()
{
if (_originalGradient == null)
return;
// Make sure something actually changed
if (Equals(ColorGradient, _originalGradient))
{
LayerProperty.CurrentValue = _originalGradient;
}
else
{
// Update the gradient for realsies, giving the command a reference to the old gradient
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<ColorGradient>(LayerProperty, ColorGradient, _originalGradient, Time));
}
_originalGradient = null;
}
/// <inheritdoc />
public override void DiscardPreview()
{
if (_originalGradient == null)
return;
// Put the old gradient back
InputValue = _originalGradient;
ColorGradient = new ColorGradient(InputValue);
}
#endregion
}
@@ -28,7 +28,8 @@ namespace Artemis.UI.Screens.SurfaceEditor
private double _dragOffsetY;
private SelectionStatus _selectionStatus;
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory, IWindowService windowService)
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory,
IWindowService windowService)
{
_rgbService = rgbService;
_deviceService = deviceService;
@@ -77,29 +78,36 @@ namespace Artemis.UI.Screens.SurfaceEditor
_dragOffsetY = Device.Y - mouseStartPosition.Y;
}
public void UpdateMouseDrag(Point mousePosition)
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
{
if (SelectionStatus != SelectionStatus.Selected)
return;
float roundedX = (float) Math.Round((mousePosition.X + _dragOffsetX) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
float roundedY = (float) Math.Round((mousePosition.Y + _dragOffsetY) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
float x = (float) (mousePosition.X + _dragOffsetX);
float y = (float) (mousePosition.Y + _dragOffsetY);
if (Fits(roundedX, roundedY))
if (round)
{
Device.X = roundedX;
Device.Y = roundedY;
x = (float) Math.Round(x / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
y = (float) Math.Round(y / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
}
else if (Fits(roundedX, Device.Y))
if (Fits(x, y, ignoreOverlap))
{
Device.X = roundedX;
Device.X = x;
Device.Y = y;
}
else if (Fits(Device.X, roundedY))
else if (Fits(x, Device.Y, ignoreOverlap))
{
Device.Y = roundedY;
Device.X = x;
}
else if (Fits(Device.X, y, ignoreOverlap))
{
Device.Y = y;
}
}
private void ExecuteIdentifyDevice(ArtemisDevice device)
{
_deviceService.IdentifyDevice(device);
@@ -110,7 +118,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device));
}
private bool Fits(float x, float y)
private bool Fits(float x, float y, bool ignoreOverlap)
{
if (x < 0 || y < 0)
return false;
@@ -119,16 +127,16 @@ namespace Artemis.UI.Screens.SurfaceEditor
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
return false;
List<SKRect> own = Device.Leds
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height))
.ToList();
List<SKRect> others = _rgbService.EnabledDevices
if (ignoreOverlap)
return true;
IEnumerable<SKRect> own = Device.Leds
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height));
IEnumerable<SKRect> others = _rgbService.EnabledDevices
.Where(d => d != Device && d.IsEnabled)
.SelectMany(d => d.Leds)
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height))
.ToList();
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height));
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
}
}
@@ -73,7 +73,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
{
if (!_dragging)
ViewModel?.StartMouseDrag(e.GetPosition(_containerGrid));
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid));
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
}
_dragging = true;
@@ -93,7 +93,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
if (ReferenceEquals(e.Pointer.Captured, sender))
{
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid));
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
e.Pointer.Capture(null);
}
}
@@ -61,23 +61,22 @@ namespace Artemis.UI.Screens.SurfaceEditor
startedOn.SelectionStatus = SelectionStatus.Selected;
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(vm => vm != startedOn))
device.SelectionStatus = SelectionStatus.None;
}
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
surfaceDeviceViewModel.StartMouseDrag(mousePosition);
}
public void UpdateMouseDrag(Point mousePosition)
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
{
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
}
public void StopMouseDrag(Point mousePosition)
public void StopMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
{
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
if (_saving)
return;
@@ -8,7 +8,7 @@
xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared"
xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="800"
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
x:DataType="workshop:WorkshopViewModel">
<Border Classes="router-container">
@@ -47,7 +47,12 @@
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
</StackPanel>
<gradientPicker:GradientPicker ColorGradient="{CompiledBinding ColorGradient}"></gradientPicker:GradientPicker>
<Button Command="{Binding CreateRandomGradient}">
Create random gradient
</Button>
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True"/>
</StackPanel>
</Border>
</StackPanel>
@@ -5,6 +5,7 @@ using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Input;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.Workshop
{
@@ -13,6 +14,15 @@ namespace Artemis.UI.Screens.Workshop
private readonly INotificationService _notificationService;
private StandardCursorType _selectedCursor;
private readonly ObservableAsPropertyHelper<Cursor> _cursor;
private ColorGradient _colorGradient = new()
{
new ColorGradientStop(new SKColor(0xFFFF6D00), 0f),
new ColorGradientStop(new SKColor(0xFFFE6806), 0.2f),
new ColorGradientStop(new SKColor(0xFFEF1788), 0.4f),
new ColorGradientStop(new SKColor(0xFFEF1788), 0.6f),
new ColorGradientStop(new SKColor(0xFF00FCCC), 0.8f),
new ColorGradientStop(new SKColor(0xFF00FCCC), 1f),
};
public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop")
{
@@ -33,7 +43,16 @@ namespace Artemis.UI.Screens.Workshop
public Cursor Cursor => _cursor.Value;
public ColorGradient ColorGradient { get; set; } = ColorGradient.GetUnicornBarf();
public ColorGradient ColorGradient
{
get => _colorGradient;
set => RaiseAndSetIfChanged(ref _colorGradient, value);
}
public void CreateRandomGradient()
{
ColorGradient = ColorGradient.GetRandom(6);
}
private void ExecuteShowNotification(NotificationSeverity severity)
{