1
0
mirror of https://github.com/DarthAffe/RGBSyncPlus synced 2025-12-12 17:08:31 +00:00
RGBSyncPlus/RGBSync+/Controls/GradientEditor.cs
2019-05-26 12:30:20 +02:00

425 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using RGB.NET.Brushes.Gradients;
using RGB.NET.Core;
using Color = System.Windows.Media.Color;
using Point = System.Windows.Point;
using Size = System.Windows.Size;
using Rectangle = System.Windows.Shapes.Rectangle;
using GradientStop = RGB.NET.Brushes.Gradients.GradientStop;
namespace RGBSyncPlus.Controls
{
[TemplatePart(Name = "PART_Gradient", Type = typeof(Canvas))]
[TemplatePart(Name = "PART_Stops", Type = typeof(Canvas))]
public class GradientEditor : Control
{
#region Properties & Fields
private Canvas _gradientContainer;
private Canvas _stopContainer;
private readonly List<Rectangle> _previewRectangles = new List<Rectangle>();
private readonly Dictionary<GradientStop, ContentControl> _stops = new Dictionary<GradientStop, ContentControl>();
private ContentControl _draggingStop;
private AdornerLayer _adornerLayer;
private ColorPickerAdorner _adorner;
private Window _window;
#endregion
#region DepdencyProperties
public static readonly DependencyProperty GradientProperty = DependencyProperty.Register(
"Gradient", typeof(LinearGradient), typeof(GradientEditor), new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnGradientChanged));
public LinearGradient Gradient
{
get => (LinearGradient)GetValue(GradientProperty);
set => SetValue(GradientProperty, value);
}
public static readonly DependencyProperty GradientStopStyleProperty = DependencyProperty.Register(
"GradientStopStyle", typeof(Style), typeof(GradientEditor), new PropertyMetadata(default(Style)));
public Style GradientStopStyle
{
get => (Style)GetValue(GradientStopStyleProperty);
set => SetValue(GradientStopStyleProperty, value);
}
public static readonly DependencyProperty SelectedStopProperty = DependencyProperty.Register(
"SelectedStop", typeof(GradientStop), typeof(GradientEditor), new PropertyMetadata(default(GradientStop), SelectedStopChanged));
public GradientStop SelectedStop
{
get => (GradientStop)GetValue(SelectedStopProperty);
set => SetValue(SelectedStopProperty, value);
}
public static readonly DependencyProperty ColorSelectorTemplateProperty = DependencyProperty.Register(
"ColorSelectorTemplate", typeof(DataTemplate), typeof(GradientEditor), new PropertyMetadata(default(DataTemplate)));
public DataTemplate ColorSelectorTemplate
{
get => (DataTemplate)GetValue(ColorSelectorTemplateProperty);
set => SetValue(ColorSelectorTemplateProperty, value);
}
public static readonly DependencyProperty CanAddOrDeleteStopsProperty = DependencyProperty.Register(
"CanAddOrDeleteStops", typeof(bool), typeof(GradientEditor), new PropertyMetadata(true));
public bool CanAddOrDeleteStops
{
get => (bool)GetValue(CanAddOrDeleteStopsProperty);
set => SetValue(CanAddOrDeleteStopsProperty, value);
}
#endregion
#region AttachedProperties
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached(
"IsSelected", typeof(bool), typeof(GradientEditor), new PropertyMetadata(default(bool)));
public static void SetIsSelected(DependencyObject element, bool value) => element.SetValue(IsSelectedProperty, value);
public static bool GetIsSelected(DependencyObject element) => (bool)element.GetValue(IsSelectedProperty);
#endregion
#region Constructors
public GradientEditor()
{
if (Gradient == null)
Gradient = new LinearGradient();
}
#endregion
#region Methods
public override void OnApplyTemplate()
{
if ((_gradientContainer = GetTemplateChild("PART_Gradient") as Canvas) != null)
{
_gradientContainer.SizeChanged += (sender, args) => UpdateGradientPreview();
_gradientContainer.MouseDown += GradientContainerOnMouseDown;
}
if ((_stopContainer = GetTemplateChild("PART_Stops") as Canvas) != null)
_stopContainer.SizeChanged += (sender, args) => UpdateGradientStops();
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
_window = Window.GetWindow(this);
if (_window != null)
{
_window.PreviewMouseDown += WindowMouseDown;
_window.PreviewKeyDown += (sender, args) =>
{
if (args.Key == Key.Escape)
SelectedStop = null;
};
}
UpdateGradientPreview();
UpdateGradientStops();
}
private void UpdateGradientPreview()
{
if ((_gradientContainer == null) || (Gradient == null)) return;
List<GradientStop> gradientStops = Gradient.GradientStops.OrderBy(x => x.Offset).ToList();
if (gradientStops.Count == 0)
UpdatePreviewRectangleCount(gradientStops.Count);
else if (gradientStops.Count == 1)
{
UpdatePreviewRectangleCount(gradientStops.Count);
GradientStop firstStop = gradientStops[0];
UpdatePreviewRectangle(_previewRectangles[0], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, 0, 1, firstStop.Color, firstStop.Color);
}
else
{
UpdatePreviewRectangleCount(gradientStops.Count + 1);
GradientStop firstStop = gradientStops[0];
UpdatePreviewRectangle(_previewRectangles[0], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, 0, firstStop.Offset, firstStop.Color, firstStop.Color);
for (int i = 0; i < (gradientStops.Count - 1); i++)
{
GradientStop stop = gradientStops[i];
GradientStop nextStop = gradientStops[i + 1];
Rectangle rect = _previewRectangles[i + 1];
UpdatePreviewRectangle(rect, _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, stop.Offset, nextStop.Offset, stop.Color, nextStop.Color);
}
GradientStop lastStop = gradientStops[gradientStops.Count - 1];
UpdatePreviewRectangle(_previewRectangles[_previewRectangles.Count - 1], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, lastStop.Offset, 1, lastStop.Color, lastStop.Color);
}
}
private void UpdatePreviewRectangle(Rectangle rect, double referenceWidth, double referenceHeight, double from, double to,
RGB.NET.Core.Color startColor, RGB.NET.Core.Color endColor)
{
rect.Fill = new LinearGradientBrush(Color.FromArgb(startColor.GetA(), startColor.GetR(), startColor.GetG(), startColor.GetB()),
Color.FromArgb(endColor.GetA(), endColor.GetR(), endColor.GetG(), endColor.GetB()),
new Point(0, 0.5), new Point(1, 0.5));
//DarthAffe 09.02.2018: Forced rounding to prevent render issues on resize
Canvas.SetLeft(rect, Math.Floor(referenceWidth * from.Clamp(0, 1)));
rect.Width = Math.Ceiling(referenceWidth * (to.Clamp(0, 1) - from.Clamp(0, 1)));
Canvas.SetTop(rect, 0);
rect.Height = referenceHeight;
}
private void UpdatePreviewRectangleCount(int gradientCount)
{
int countDiff = gradientCount - _previewRectangles.Count;
if (countDiff > 0)
for (int i = 0; i < countDiff; i++)
{
Rectangle rect = new Rectangle { VerticalAlignment = VerticalAlignment.Stretch };
_previewRectangles.Add(rect);
_gradientContainer.Children.Add(rect);
}
if (countDiff < 0)
for (int i = 0; i < Math.Abs(countDiff); i++)
{
int index = _previewRectangles.Count - i - 1;
Rectangle rect = _previewRectangles[index];
_previewRectangles.RemoveAt(index);
_gradientContainer.Children.Remove(rect);
}
}
private void UpdateGradientStops()
{
if (Gradient == null) return;
List<GradientStop> gradientStops = Gradient.GradientStops.OrderBy(x => x.Offset).ToList();
UpdateGradientStopsCount(gradientStops);
foreach (GradientStop stop in gradientStops)
UpdateGradientStop(_stops[stop], _stopContainer.ActualWidth, _stopContainer.ActualHeight, stop);
}
private void UpdateGradientStop(ContentControl control, double referenceWidth, double referenceHeight, GradientStop stop)
{
control.Background = new SolidColorBrush(Color.FromArgb(stop.Color.GetA(), stop.Color.GetR(), stop.Color.GetG(), stop.Color.GetB()));
Canvas.SetLeft(control, (referenceWidth * stop.Offset.Clamp(0, 1)) - (control.Width / 2.0));
Canvas.SetTop(control, 0);
control.Height = referenceHeight;
}
private void UpdateGradientStopsCount(List<GradientStop> gradientStops)
{
foreach (GradientStop stop in gradientStops)
{
if (!_stops.ContainsKey(stop))
{
ContentControl control = new ContentControl
{
VerticalAlignment = VerticalAlignment.Stretch,
Style = GradientStopStyle,
Content = stop
};
control.MouseDown += GradientStopOnMouseDown;
_stops.Add(stop, control);
_stopContainer.Children.Add(control);
}
}
List<GradientStop> stopsToRemove = new List<GradientStop>();
foreach (KeyValuePair<GradientStop, ContentControl> stopPair in _stops)
if (!gradientStops.Contains(stopPair.Key))
{
ContentControl control = stopPair.Value;
control.MouseDown -= GradientStopOnMouseDown;
stopsToRemove.Add(stopPair.Key);
_stopContainer.Children.Remove(control);
}
foreach (GradientStop stop in stopsToRemove)
_stops.Remove(stop);
}
private void AttachGradient(AbstractGradient gradient) => gradient.GradientChanged += GradientChanged;
private void DetachGradient(AbstractGradient gradient) => gradient.GradientChanged -= GradientChanged;
private void GradientChanged(object o, EventArgs eventArgs)
{
UpdateGradientPreview();
UpdateGradientStops();
}
private static void OnGradientChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is GradientEditor ge)) return;
if (dependencyPropertyChangedEventArgs.OldValue is AbstractGradient oldGradient)
ge.DetachGradient(oldGradient);
if (dependencyPropertyChangedEventArgs.NewValue is AbstractGradient newGradient)
ge.AttachGradient(newGradient);
}
private void GradientContainerOnMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs)
{
if ((mouseButtonEventArgs.ChangedButton != MouseButton.Left) || (Gradient == null) || !CanAddOrDeleteStops) return;
double offset = mouseButtonEventArgs.GetPosition(_gradientContainer).X / _gradientContainer.ActualWidth;
RGB.NET.Core.Color color = Gradient.GetColor(offset);
GradientStop newStop = new GradientStop(offset, color);
Gradient.GradientStops.Add(newStop);
SelectedStop = newStop;
}
private void GradientStopOnMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs)
{
if (!((o as ContentControl)?.Content is GradientStop stop) || (Gradient == null)) return;
if (mouseButtonEventArgs.ChangedButton == MouseButton.Right)
{
if (CanAddOrDeleteStops)
Gradient.GradientStops.Remove(stop);
}
else if (mouseButtonEventArgs.ChangedButton == MouseButton.Left)
{
SelectedStop = stop;
_draggingStop = (ContentControl)o;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (_draggingStop?.Content is GradientStop stop)
{
double location = e.GetPosition(_gradientContainer).X;
stop.Offset = (location / _gradientContainer.ActualWidth).Clamp(0, 1);
}
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
_draggingStop = null;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
_draggingStop = null;
}
private static void SelectedStopChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is GradientEditor gradientEditor)) return;
if (gradientEditor._adorner != null)
gradientEditor._adornerLayer.Remove(gradientEditor._adorner);
if (dependencyPropertyChangedEventArgs.OldValue is GradientStop oldStop)
{
if (gradientEditor._stops.TryGetValue(oldStop, out ContentControl oldcontrol))
SetIsSelected(oldcontrol, false);
}
if (dependencyPropertyChangedEventArgs.NewValue is GradientStop stop)
{
ContentControl stopContainer = gradientEditor._stops[stop];
SetIsSelected(stopContainer, true);
if (gradientEditor._adornerLayer != null)
{
ContentControl contentControl = new ContentControl
{
ContentTemplate = gradientEditor.ColorSelectorTemplate,
Content = stop
};
ColorPickerAdorner adorner = new ColorPickerAdorner(stopContainer, contentControl);
gradientEditor._adorner = adorner;
gradientEditor._adornerLayer.Add(adorner);
}
}
}
private void WindowMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs)
{
if ((_adorner != null) && (VisualTreeHelper.HitTest(_adorner, mouseButtonEventArgs.GetPosition(_adorner)) == null))
SelectedStop = null;
}
#endregion
}
public class ColorPickerAdorner : Adorner
{
#region Properties & Fields
private readonly VisualCollection _visualChildren;
private readonly FrameworkElement _colorSelector;
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => _colorSelector;
#endregion
#region Constructors
public ColorPickerAdorner(UIElement adornedElement, FrameworkElement colorSelector)
: base(adornedElement)
{
this._colorSelector = colorSelector;
_visualChildren = new VisualCollection(this) { colorSelector };
}
#endregion
#region Methods
protected override Size ArrangeOverride(Size finalSize)
{
Window referenceWindow = Window.GetWindow(AdornedElement);
Point referenceLocation = AdornedElement.TranslatePoint(new Point(0, 0), referenceWindow);
double referenceWidth = ((FrameworkElement)AdornedElement).ActualWidth / 2.0;
double referenceHeight = ((FrameworkElement)AdornedElement).Height;
double referenceX = referenceLocation.X + referenceWidth;
double halfWidth = finalSize.Width / 2.0;
double maxOffset = referenceWindow.Width - halfWidth;
double offset = (referenceX < halfWidth ? referenceX
: (((referenceX + (referenceWidth * 2)) > maxOffset)
? halfWidth - ((maxOffset - referenceX) - (referenceWidth * 2))
: halfWidth));
_colorSelector.Arrange(new Rect(new Point(referenceWidth - offset, referenceHeight), finalSize));
return _colorSelector.RenderSize;
}
protected override Size MeasureOverride(Size constraint)
{
_colorSelector.Measure(constraint);
return _colorSelector.DesiredSize;
}
#endregion
}
}