From f95aaf2443d653c8b4930b43731962d4ace88e23 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 10 Mar 2020 19:34:17 +0100 Subject: [PATCH] Gradient editor - WIP commit --- .../Models/Profile/ColorGradient.cs | 47 ++++++----- .../ColorGradientToGradientStopsConverter.cs | 20 ++--- src/Artemis.UI.Shared/GradientPicker.xaml | 2 +- .../GradientEditor/GradientEditor.xaml | 25 +++--- .../GradientEditor/GradientEditor.xaml.cs | 8 +- .../GradientEditor/GradientEditorColor.xaml | 13 ++- .../GradientEditorColor.xaml.cs | 80 +++++++++++++++---- .../Services/Dialog/DialogService.cs | 11 ++- .../Utilities/UIUtilities.cs | 63 +++++++++++++++ .../ColorBrush.cs | 16 ++-- 10 files changed, 217 insertions(+), 68 deletions(-) create mode 100644 src/Artemis.UI.Shared/Utilities/UIUtilities.cs diff --git a/src/Artemis.Core/Models/Profile/ColorGradient.cs b/src/Artemis.Core/Models/Profile/ColorGradient.cs index 8ca202b6c..b702f7e6e 100644 --- a/src/Artemis.Core/Models/Profile/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/ColorGradient.cs @@ -1,38 +1,35 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using Artemis.Core.Annotations; -using PropertyChanged; using SkiaSharp; using Stylet; namespace Artemis.Core.Models.Profile { - [DoNotNotify] public class ColorGradient : INotifyPropertyChanged { - private float _rotation; - public ColorGradient() { Colors = new BindableCollection(); } public BindableCollection Colors { get; } + public float Rotation { get; set; } - public float Rotation + public SKColor[] GetColorsArray() { - get => _rotation; - set - { - if (_rotation != value) - { - _rotation = value; - OnPropertyChanged(nameof(Rotation)); - } - } + return Colors.OrderBy(c => c.Position).Select(c => c.Color).ToArray(); } + public float[] GetColorPositionsArray() + { + return Colors.OrderBy(c => c.Position).Select(c => c.Position).ToArray(); + } + + #region PropertyChanged + public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] @@ -41,13 +38,15 @@ namespace Artemis.Core.Models.Profile PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - public SKColor[] GetColorsArray() + #endregion + + public void OnColorValuesUpdated() { - return Colors.Select(c => c.Color).ToArray(); + OnPropertyChanged(nameof(Colors)); } } - public struct ColorGradientColor + public class ColorGradientColor : INotifyPropertyChanged { public ColorGradientColor(SKColor color, float position) { @@ -57,5 +56,17 @@ namespace Artemis.Core.Models.Profile public SKColor Color { get; set; } public float Position { get; set; } + + #region PropertyChanged + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs index f79bb2606..22e225613 100644 --- a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs @@ -1,9 +1,11 @@ using System; using System.Globalization; +using System.Linq; using System.Windows.Data; using System.Windows.Media; using Artemis.Core.Models.Profile; using SkiaSharp; +using Stylet; namespace Artemis.UI.Shared.Converters { @@ -12,17 +14,17 @@ namespace Artemis.UI.Shared.Converters /// Converts into a /// . /// - [ValueConversion(typeof(ColorGradient), typeof(GradientStopCollection))] + [ValueConversion(typeof(BindableCollection), typeof(GradientStopCollection))] public class ColorGradientToGradientStopsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - var colorGradient = (ColorGradient) value; + var colorGradients = (BindableCollection) value; var collection = new GradientStopCollection(); - if (colorGradient == null) + if (colorGradients == null) return collection; - foreach (var c in colorGradient.Colors) + foreach (var c in colorGradients.OrderBy(s => s.Position)) collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position)); return collection; } @@ -30,13 +32,13 @@ namespace Artemis.UI.Shared.Converters public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { var collection = (GradientStopCollection) value; - var colorGradient = new ColorGradient(); + var colorGradients = new BindableCollection(); if (collection == null) - return colorGradient; + return colorGradients; - foreach (var c in collection) - colorGradient.Colors.Add(new ColorGradientColor(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset)); - return colorGradient; + foreach (var c in collection.OrderBy(s => s.Offset)) + colorGradients.Add(new ColorGradientColor(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset)); + return colorGradients; } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/GradientPicker.xaml b/src/Artemis.UI.Shared/GradientPicker.xaml index 1ec45a4d0..a52591756 100644 --- a/src/Artemis.UI.Shared/GradientPicker.xaml +++ b/src/Artemis.UI.Shared/GradientPicker.xaml @@ -43,7 +43,7 @@ + GradientStops="{Binding ColorGradient.Colors, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorGradientToGradientStopsConverter}}" /> diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml index bf322f738..d996b1624 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml @@ -13,7 +13,6 @@ Title="Gradient Editor" Background="{DynamicResource MaterialDesignPaper}" FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto" - UseLayoutRounding="True" Width="500" Height="600" ResizeMode="NoResize" @@ -42,20 +41,24 @@ Gradient - + - + - - + + - + - + @@ -64,16 +67,18 @@ - + - + Stops diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml.cs index db948dc11..d3e8d5eb1 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using Artemis.Core.Models.Profile; @@ -26,11 +27,12 @@ namespace Artemis.UI.Shared.Screens.GradientEditor public GradientEditor(ColorGradient colorGradient) { - InitializeComponent(); DataContext = this; + + InitializeComponent(); ColorGradient = colorGradient; } - + public ColorGradient ColorGradient { get => (ColorGradient) GetValue(ColorGradientProperty); diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml index f63e3f7e3..4b6d9403d 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml @@ -4,22 +4,29 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Shared.Screens.GradientEditor" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" x:Class="Artemis.UI.Shared.Screens.GradientEditor.GradientEditorColor" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + Cursor="Hand" + MouseDown="ColorStop_MouseDown" + MouseUp="ColorStop_MouseUp" + MouseMove="ColorStop_MouseMove"> - + + + + \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml.cs index dfa4c66ae..fb56d9b5c 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorColor.xaml.cs @@ -1,10 +1,14 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; +using System.Linq; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; using Artemis.Core.Models.Profile; using Artemis.UI.Shared.Annotations; +using Artemis.UI.Shared.Utilities; namespace Artemis.UI.Shared.Screens.GradientEditor { @@ -13,14 +17,32 @@ namespace Artemis.UI.Shared.Screens.GradientEditor /// public partial class GradientEditorColor : UserControl, INotifyPropertyChanged { + private const double CanvasWidth = 435.0; + public static readonly DependencyProperty ColorGradientColorProperty = DependencyProperty.Register(nameof(GradientColor), typeof(ColorGradientColor), typeof(GradientEditorColor), new FrameworkPropertyMetadata(default(ColorGradientColor), ColorGradientColorPropertyChangedCallback)); + public static readonly DependencyProperty ColorGradientProperty = DependencyProperty.Register(nameof(ColorGradient), typeof(ColorGradient), typeof(GradientEditorColor), + new FrameworkPropertyMetadata(default(ColorGradient))); + + private readonly Canvas _previewCanvas; + private bool _inCallback; + private double _mouseDownOffset; + private DateTime _mouseDownTime; public GradientEditorColor() { InitializeComponent(); + + var window = Application.Current.Windows.OfType().FirstOrDefault(); + _previewCanvas = UIUtilities.FindChild(window, "PreviewCanvas"); + } + + public ColorGradient ColorGradient + { + get => (ColorGradient) GetValue(ColorGradientProperty); + set => SetValue(ColorGradientProperty, value); } public ColorGradientColor GradientColor @@ -44,28 +66,52 @@ namespace Artemis.UI.Shared.Screens.GradientEditor return; self._inCallback = true; - self.OnPropertyChanged(nameof(GradientColor)); - // Get the parent canvas - DependencyObject parent = null; - DependencyObject current = self; - while (current != null && !(parent is Canvas)) + + self.OnPropertyChanged(nameof(GradientColor)); + self.Update(); + self._inCallback = false; + } + + private void Update() + { + ColorStop.SetValue(Canvas.LeftProperty, CanvasWidth * GradientColor.Position); + ColorStop.Fill = new SolidColorBrush(Color.FromArgb( + GradientColor.Color.Alpha, + GradientColor.Color.Red, + GradientColor.Color.Green, + GradientColor.Color.Blue + )); + } + + private void ColorStop_MouseDown(object sender, MouseButtonEventArgs e) + { + ((IInputElement) sender).CaptureMouse(); + _mouseDownOffset = (double) ColorStop.GetValue(Canvas.LeftProperty) - e.GetPosition(_previewCanvas).X; + _mouseDownTime = DateTime.Now; + } + + private void ColorStop_MouseUp(object sender, MouseButtonEventArgs e) + { + // On regular click, select this color stop + if (DateTime.Now - _mouseDownTime <= TimeSpan.FromMilliseconds(250)) { - parent = VisualTreeHelper.GetParent(current); - current = parent; } - if (parent is Canvas canvas) - self.ColorStop.SetValue(Canvas.LeftProperty, (double) 435f * self.GradientColor.Position); + ((IInputElement) sender).ReleaseMouseCapture(); + } - self.ColorStop.Fill = new SolidColorBrush(Color.FromArgb( - self.GradientColor.Color.Alpha, - self.GradientColor.Color.Red, - self.GradientColor.Color.Green, - self.GradientColor.Color.Blue - )); + private void ColorStop_MouseMove(object sender, MouseEventArgs e) + { + if (!((IInputElement) sender).IsMouseCaptured) + return; - self._inCallback = false; + var position = e.GetPosition(_previewCanvas); + // Position ranges from 0.0 to 1.0 + var newPosition = Math.Min(1, Math.Max(0, Math.Round((position.X + _mouseDownOffset) / CanvasWidth, 3, MidpointRounding.AwayFromZero))); + GradientColor.Position = (float) newPosition; + ColorGradient.OnColorValuesUpdated(); + Update(); } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs index 3d6a3def7..b56b7cb8e 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs @@ -95,7 +95,14 @@ namespace Artemis.UI.Shared.Services.Dialog new ConstructorArgument("message", message), new ConstructorArgument("exception", exception) }; - await Execute.OnUIThreadAsync(async () => await ShowDialog(arguments)); + try + { + await Execute.OnUIThreadAsync(async () => await ShowDialog(arguments)); + } + catch (Exception) + { + //ignored + } } private async Task ShowDialog(string identifier, DialogViewModelBase viewModel) @@ -105,7 +112,7 @@ namespace Artemis.UI.Shared.Services.Dialog { var view = _viewManager.CreateViewForModel(viewModel); _viewManager.BindViewToModel(view, viewModel); - + if (identifier == null) result = DialogHost.Show(view, viewModel.OnDialogOpened, viewModel.OnDialogClosed); else diff --git a/src/Artemis.UI.Shared/Utilities/UIUtilities.cs b/src/Artemis.UI.Shared/Utilities/UIUtilities.cs new file mode 100644 index 000000000..5babfa48f --- /dev/null +++ b/src/Artemis.UI.Shared/Utilities/UIUtilities.cs @@ -0,0 +1,63 @@ +using System.Windows; +using System.Windows.Media; + +namespace Artemis.UI.Shared.Utilities +{ + // ReSharper disable once InconsistentNaming + public static class UIUtilities + { + /// + /// Finds a Child of a given item in the visual tree. + /// + /// A direct parent of the queried item. + /// The type of the queried item. + /// x:Name or Name of child. + /// + /// The first parent item that matches the submitted type parameter. + /// If not matching item can be found, + /// a null parent is being returned. + /// + public static T FindChild(DependencyObject parent, string childName) where T : DependencyObject + { + // Confirm parent and childName are valid. + if (parent == null) return null; + + T foundChild = null; + + var childrenCount = VisualTreeHelper.GetChildrenCount(parent); + for (var i = 0; i < childrenCount; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + // If the child is not of the request child type child + var childType = child as T; + if (childType == null) + { + // recursively drill down the tree + foundChild = FindChild(child, childName); + + // If the child is found, break so we do not overwrite the found child. + if (foundChild != null) break; + } + else if (!string.IsNullOrEmpty(childName)) + { + var frameworkElement = child as FrameworkElement; + // If the child's name is set for search + if (frameworkElement != null && frameworkElement.Name == childName) + { + // if the child's name is of the request name + foundChild = (T) child; + break; + } + } + else + { + // child element found. + foundChild = (T) child; + break; + } + } + + return foundChild; + } + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index 97576d778..76ada6623 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Linq; using Artemis.Core.Models.Profile; @@ -26,7 +25,8 @@ namespace Artemis.Plugins.LayerBrushes.Color CreateShader(_shaderBounds); Layer.RenderPropertiesUpdated += (sender, args) => CreateShader(_shaderBounds); GradientTypeProperty.ValueChanged += (sender, args) => CreateShader(_shaderBounds); - + GradientProperty.ValueChanged += (sender, args) => CreateShader(_shaderBounds); + GradientProperty.Value.PropertyChanged += (sender, args) => CreateShader(_shaderBounds); if (!GradientProperty.Value.Colors.Any()) { for (var i = 0; i < 9; i++) @@ -72,13 +72,19 @@ namespace Artemis.Plugins.LayerBrushes.Color shader = SKShader.CreateColor(_color); break; case GradientType.LinearGradient: - shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(pathBounds.Width, 0), GradientProperty.Value.GetColorsArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(pathBounds.Width, 0), + GradientProperty.Value.GetColorsArray(), + GradientProperty.Value.GetColorPositionsArray(), SKShaderTileMode.Repeat); break; case GradientType.RadialGradient: - shader = SKShader.CreateRadialGradient(center, Math.Min(pathBounds.Width, pathBounds.Height), GradientProperty.Value.GetColorsArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateRadialGradient(center, Math.Min(pathBounds.Width, pathBounds.Height), + GradientProperty.Value.GetColorsArray(), + GradientProperty.Value.GetColorPositionsArray(), SKShaderTileMode.Repeat); break; case GradientType.SweepGradient: - shader = SKShader.CreateSweepGradient(center, GradientProperty.Value.GetColorsArray(), null, SKShaderTileMode.Clamp, 0, 360); + shader = SKShader.CreateSweepGradient(center, + GradientProperty.Value.GetColorsArray(), + GradientProperty.Value.GetColorPositionsArray(), SKShaderTileMode.Clamp, 0, 360); break; default: throw new ArgumentOutOfRangeException();