Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ShadeEditorControl.cs « Xamarin.PropertyEditing.Windows - github.com/xamarin/Xamarin.PropertyEditing.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 05bd865f877eac38b3fc5d38c10953694b66e201 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="position">The position for which to infer the color</param>
		/// <returns>The shade</returns>
		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);
		}

		/// <summary>
		/// Finds a position on the shade chooser that corresponds to the passed-in
		/// shade.
		/// </summary>
		/// <param name="shade">The shade for which we want the coordinates</param>
		/// <returns>The coordinates of the shade in the shade chooser</returns>
		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);
		}
	}
}