using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Xamarin.PropertyEditing.Drawing;
namespace Xamarin.PropertyEditing.Windows
{
internal class ShadeEditorControl : CurrentColorCommitterControlBase
{
// Note: the Color property of this control should be bound to the Shade of the solid brush view model, not its color.
public ShadeEditorControl()
{
DefaultStyleKey = typeof (ShadeEditorControl);
}
Rectangle saturationLayer;
Rectangle brightnessLayer;
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register (
nameof(CursorPosition), typeof (Point), typeof (ShadeEditorControl),
new PropertyMetadata (new Point (0, 0)));
public Point CursorPosition {
get => (Point)GetValue (CursorPositionProperty);
set => SetValue (CursorPositionProperty, value);
}
public static readonly DependencyProperty ShadeProperty =
DependencyProperty.Register (
"Shade", typeof (CommonColor), typeof (ShadeEditorControl),
new PropertyMetadata (new CommonColor (0, 0, 0), OnShadeChanged));
public CommonColor Shade
{
get => (CommonColor)GetValue (ShadeProperty);
set => SetValue (ShadeProperty, value);
}
static void OnShadeChanged (DependencyObject source, DependencyPropertyChangedEventArgs e)
=> (source as ShadeEditorControl)?.OnShadeChanged ((CommonColor)e.OldValue, (CommonColor)e.NewValue);
public static readonly DependencyProperty HueProperty =
DependencyProperty.Register (
nameof(HueColor), typeof (CommonColor), typeof (ShadeEditorControl),
new PropertyMetadata (new CommonColor (255, 0, 0), OnHuePropertyChanged));
public CommonColor HueColor {
get => (CommonColor)GetValue (HueProperty);
set => SetValue (HueProperty, value);
}
public override void OnApplyTemplate ()
{
base.OnApplyTemplate ();
this.saturationLayer = GetTemplateChild ("saturationLayer") as Rectangle;
this.brightnessLayer = GetTemplateChild ("brightnessLayer") as Rectangle;
OnHueChanged (HueColor);
if (this.saturationLayer == null)
throw new InvalidOperationException ($"{nameof (ShadeEditorControl)} is missing a child Rectangle named \"saturationLayer\"");
if (this.brightnessLayer == null)
throw new InvalidOperationException ($"{nameof (ShadeEditorControl)} is missing a child Rectangle named \"brightnessLayer\"");
this.brightnessLayer.MouseLeftButtonDown += (s, e) => {
if (!this.brightnessLayer.IsMouseCaptured)
this.brightnessLayer.CaptureMouse ();
};
this.brightnessLayer.MouseMove += (s, e) => {
if (this.brightnessLayer.IsMouseCaptured && e.LeftButton == MouseButtonState.Pressed && this.saturationLayer != null) {
Point cursorPosition = e.GetPosition ((IInputElement)s);
SetShadeFromMousePosition (cursorPosition);
}
};
this.brightnessLayer.MouseLeftButtonUp += (s, e) => {
if (this.brightnessLayer.IsMouseCaptured) this.brightnessLayer.ReleaseMouseCapture ();
Point cursorPosition = e.GetPosition ((IInputElement)s);
SetShadeFromMousePosition (cursorPosition);
RaiseEvent (new RoutedEventArgs (CommitCurrentColorEvent));
};
}
void SetShadeFromMousePosition(Point cursorPosition)
{
if (cursorPosition.X < 0)
cursorPosition.X = 0;
if (cursorPosition.X > this.brightnessLayer.ActualWidth)
cursorPosition.X = this.brightnessLayer.ActualWidth;
if (cursorPosition.Y < 0)
cursorPosition.Y = 0;
if (cursorPosition.Y > this.brightnessLayer.ActualHeight)
cursorPosition.Y = this.brightnessLayer.ActualHeight;
CursorPosition = cursorPosition;
CommonColor newShade = GetShadeFromPosition (cursorPosition);
Shade = new CommonColor (newShade.R, newShade.G, newShade.B);
}
protected override void OnRenderSizeChanged (SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged (sizeInfo);
CursorPosition = GetPositionFromShade (Shade);
}
protected void OnShadeChanged (CommonColor oldShade, CommonColor newShade)
{
if (this.brightnessLayer == null || !(this.brightnessLayer.IsMouseCaptured))
CursorPosition = GetPositionFromShade (newShade);
}
static void OnHuePropertyChanged (DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (source is ShadeEditorControl shadeEditor) {
shadeEditor.OnHueChanged ((CommonColor)e.NewValue);
}
}
private void OnHueChanged (CommonColor newHue)
{
// OnHueChanged may be called before the template is applied, so the layer may not yet be available.
if (this.saturationLayer == null) return;
var newBrush = (LinearGradientBrush)this.saturationLayer.Fill.Clone ();
GradientStopCollection gradientStops = newBrush.GradientStops;
gradientStops.RemoveAt (1);
gradientStops.Add (new GradientStop (Color.FromRgb (newHue.R, newHue.G, newHue.B), 1));
this.saturationLayer.Fill = newBrush;
}
///
/// Maps coordinates within the shade chooser gradients into colors.
/// The gradients have the current hue on the top-right corner,
/// black along the whole bottom border, and white of the top-left.
///
/// For example, with a hue of 128,255,0:
///
/// 255,255,255---192,255,128---128,255,000
/// | | |
/// 128,128,128---096,128,064---064,128,000
/// | | |
/// 000,000,000---000,000,000---000,000,000
///
/// The horizontal axis corresponds roughly to saturation, and the
/// vertical axis to brightness.
///
/// The position for which to infer the color
/// The shade
CommonColor GetShadeFromPosition (Point position)
{
var saturation = position.X / this.saturationLayer.ActualWidth;
var brightness = 1 - position.Y / this.brightnessLayer.ActualHeight;
return CommonColor.FromHSB (HueColor.Hue, saturation, brightness);
}
///
/// Finds a position on the shade chooser that corresponds to the passed-in
/// shade.
///
/// The shade for which we want the coordinates
/// The coordinates of the shade in the shade chooser
Point GetPositionFromShade (CommonColor shade)
{
var brightness = shade.Brightness;
var saturation = shade.Saturation;
// OnHueChanged may be called before the template is applied, so the layer may not yet be available.
if (this.saturationLayer == null || this.brightnessLayer == null) return new Point (0, 0);
return new Point (
saturation * this.saturationLayer.ActualWidth + this.saturationLayer.Margin.Left ,
(1 - brightness) * this.brightnessLayer.ActualHeight + this.brightnessLayer.Margin.Top);
}
}
}