diff options
author | Larry Ewing <lewing@xamarin.com> | 2018-04-19 03:33:59 +0300 |
---|---|---|
committer | Larry Ewing <lewing@microsoft.com> | 2018-07-16 22:05:48 +0300 |
commit | efc1f61c75a59b17b0460277e51c4f3c47dc7074 (patch) | |
tree | d363b6c53e39d91a0e6aab0f02a22d6f1f372ad5 /Xamarin.PropertyEditing.Mac | |
parent | 244ee5077b4e7192797031e55c02b40f32167be0 (diff) |
Add preview swatches under the channel editors
Make ComponentEditor a simple class with more information about the
channel the rework all the gradient selection to use more robust logic
to convert back and forth between position and value.
Diffstat (limited to 'Xamarin.PropertyEditing.Mac')
8 files changed, 457 insertions, 149 deletions
diff --git a/Xamarin.PropertyEditing.Mac/Controls/BrushEditorControl.cs b/Xamarin.PropertyEditing.Mac/Controls/BrushEditorControl.cs index 703b70d..ca7cadb 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/BrushEditorControl.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/BrushEditorControl.cs @@ -40,15 +40,16 @@ namespace Xamarin.PropertyEditing.Mac public BrushEditorControl () { base.TranslatesAutoresizingMaskIntoConstraints = false; - RowHeight = 230f; + this.colorEditor = new BrushTabViewController (); this.popover = new NSPopover (); popover.Behavior = NSPopoverBehavior.Transient; popover.ContentViewController = brushTabViewController = new BrushTabViewController { - PreferredContentSize = new CGSize (300, 200) + PreferredContentSize = new CGSize (200, 200) }; + RowHeight = 200 + 30; this.popUpButton = new ColorPopUpButton { TranslatesAutoresizingMaskIntoConstraints = false, diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/BrushTabViewController.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/BrushTabViewController.cs index 8aea01a..9c50654 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/BrushTabViewController.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/BrushTabViewController.cs @@ -17,7 +17,7 @@ namespace Xamarin.PropertyEditing.Mac public MaterialBrushEditorViewController () { - PreferredContentSize = new CGSize (100, 100); + PreferredContentSize = new CGSize (200, 200); } public override void OnPropertyChanged (object sender, PropertyChangedEventArgs e) @@ -374,6 +374,7 @@ namespace Xamarin.PropertyEditing.Mac public override void OnPropertyChanged (object sender, PropertyChangedEventArgs args) { + base.OnPropertyChanged (sender, args); switch (args.PropertyName) { case nameof (BrushPropertyViewModel.SelectedBrushType): if (BrushTypeTable.TryGetValue (ViewModel.SelectedBrushType, out var index)) { diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentEditor.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentEditor.cs index bf11ac1..50d9e62 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentEditor.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentEditor.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using AppKit; +using CoreAnimation; using CoreGraphics; +using Foundation; using Xamarin.PropertyEditing.Drawing; using Xamarin.PropertyEditing.ViewModels; @@ -13,16 +16,19 @@ namespace Xamarin.PropertyEditing.Mac const int DefaultPropertyButtonSize = 10; const int DefaultActioButtonSize = 16; const int DefaultControlHeight = 22; + const int DefaultGradientHeight = 4; - EditorType EditorType { get; } + ChannelEditorType EditorType { get; } - public ColorComponentEditor (EditorType editorType, CGRect frame) : base (frame) + public bool ClickableGradients { get; set; } = true; + + public ColorComponentEditor (ChannelEditorType editorType, CGRect frame) : base (frame) { EditorType = EditorType; Initialize (); } - public ColorComponentEditor (EditorType editorType) : base () + public ColorComponentEditor (ChannelEditorType editorType) : base () { EditorType = editorType; Initialize (); @@ -31,70 +37,80 @@ namespace Xamarin.PropertyEditing.Mac class ComponentSet { public UnfocusableTextField Label { get; set; } - public ComponentEditor Editor { get; set; } + public ComponentSpinEditor Editor { get; set; } + public CAGradientLayer Gradient { get; set; } } - ComponentSet CreateEditor (string label, double min, double max, double increment, ComponentEditor editor) + ComponentSet CreateEditor (ComponentEditor editor) { var ce = new ComponentSet { - Label = new UnfocusableTextField { StringValue = label }, - Editor = editor, + Label = new UnfocusableTextField { + StringValue = $"{editor.Name}:", + }, + Editor = new ComponentSpinEditor (editor), + Gradient = new CAGradientLayer { + StartPoint = new CGPoint (0, 0), + EndPoint = new CGPoint (1, 0), + BorderWidth = .5f, + } }; - editor.MinimumValue = min; - editor.MaximumValue = max; - editor.IncrementValue = increment; - editor.ValueChanged += UpdateComponent; + + ce.Editor.TranslatesAutoresizingMaskIntoConstraints = true; + ce.Editor.ValueChanged += UpdateComponent; AddSubview (ce.Label); - AddSubview (editor); + AddSubview (ce.Editor); + Layer.AddSublayer (ce.Gradient); return ce; } ComponentSet[] Editors { get; set; } - ComponentSet[] CreateEditors (EditorType type) + ComponentSet[] CreateEditors (ChannelEditorType type) { switch (type) { - case EditorType.Hsb: + case ChannelEditorType.HSB: return new[] { - CreateEditor ("H:", 0, 360, 1, new HsbHueComponentEditor ()), - CreateEditor ("S:", 0, 1, .01, new HsbSaturationComponentEditor ()), - CreateEditor ("B:", 0, 1, .01, new HsbBrightnessComponentEditor ()), - CreateEditor ("A:", 0, 255, 1, new AlphaComponentEditor ()) + CreateEditor (new HsbHueComponentEditor ()), + CreateEditor (new HsbSaturationComponentEditor ()), + CreateEditor (new HsbBrightnessComponentEditor ()), + CreateEditor (new AlphaComponentEditor ()) }; - case EditorType.Hls: + case ChannelEditorType.HLS: return new[] { - CreateEditor ("H:", 0, 360, 1, new HlsHueComponentEditor ()), - CreateEditor ("L:", 0, 1, .01, new HlsLightnessComponentEditor ()), - CreateEditor ("S:", 0, 1, .01, new HlsSaturationComponentEditor ()), - CreateEditor ("A:", 0, 255, 1, new AlphaComponentEditor ()) + CreateEditor (new HlsHueComponentEditor ()), + CreateEditor (new HlsLightnessComponentEditor ()), + CreateEditor (new HlsSaturationComponentEditor ()), + CreateEditor (new AlphaComponentEditor ()) }; - case EditorType.Rgb: + case ChannelEditorType.RGB: return new[] { - CreateEditor ("R:", 0, 255, 1, new RedComponentEditor ()), - CreateEditor ("G:", 0, 255, 1, new GreenComponentEditor ()), - CreateEditor ("B:", 0, 255, 1, new BlueComponentEditor ()), - CreateEditor ("A:", 0, 255, 1, new AlphaComponentEditor ()) + CreateEditor (new RedComponentEditor ()), + CreateEditor (new GreenComponentEditor ()), + CreateEditor (new BlueComponentEditor ()), + CreateEditor (new AlphaComponentEditor ()) }; default: - case EditorType.Cmyk: + case ChannelEditorType.CMYK: return new[] { - CreateEditor ("C:", 0, 1, .01, new CyanComponentEditor ()), - CreateEditor ("M:", 0, 1, .01, new MagentaComponentEditor ()), - CreateEditor ("Y:", 0, 1, .01, new YellowComponentEditor ()), - CreateEditor ("K:", 0, 1, .01, new BlackComponentEditor ()), - CreateEditor ("A:", 0, 255, 1, new AlphaComponentEditor ()) + CreateEditor (new CyanComponentEditor ()), + CreateEditor (new MagentaComponentEditor ()), + CreateEditor (new YellowComponentEditor ()), + CreateEditor (new BlackComponentEditor ()), + CreateEditor (new AlphaComponentEditor ()) }; } } void Initialize () { + WantsLayer = true; Editors = CreateEditors (EditorType); + this.DoConstraints (Editors.Select ( ce => new[] { ce.Editor.ConstraintTo (this, (editor, c) => editor.Width == 90), ce.Editor.ConstraintTo (this, (editor, c) => editor.Height == DefaultControlHeight) - }).SelectMany (e => e).ToArray ()); + }).SelectMany (e => e).ToArray ()); } void UpdateComponent (object sender, EventArgs args) @@ -103,8 +119,8 @@ namespace Xamarin.PropertyEditing.Mac return; var color = ViewModel.Color; - var editor = sender as ComponentEditor; - ViewModel.Color = editor.UpdateColorFromValue (color, editor.Value); + var editor = sender as ComponentSpinEditor; + ViewModel.Color = editor.ComponentEditor.UpdateColorFromValue (color, editor.Value); } protected override void OnPropertyChanged(object sender, PropertyChangedEventArgs e) @@ -114,157 +130,348 @@ namespace Xamarin.PropertyEditing.Mac case nameof (SolidBrushViewModel.Color): foreach (var c in Editors) { var editor = c.Editor; - editor.Value = editor.ValueFromColor(ViewModel.Color); + editor.Value = editor.ComponentEditor.ValueFromColor(ViewModel.Color); + editor.ComponentEditor.UpdateGradientLayer (c.Gradient, ViewModel.Color); } break; } } + ComponentSet set; + public override void MouseDown (NSEvent theEvent) + { + if (!ClickableGradients) { + set = null; + base.MouseDown (theEvent); + return; + } + + var location = ConvertPointFromView (theEvent.LocationInWindow, null); + location = ConvertPointToLayer (location); + + foreach (var layer in Layer.Sublayers) { + var hit = layer.PresentationLayer.HitTest (location) ?? layer.PresentationLayer.HitTest (new CGPoint (location.X, location.Y + 4)); + + for (var c = hit; c != null; c = c.SuperLayer) { + set = Editors.FirstOrDefault (ce => ce.Gradient == c.ModelLayer); + if (set != null) { + var channel = set.Editor.ComponentEditor; + var grad = set.Gradient; + ViewModel.Color = channel.UpdateColorFromValue ( + ViewModel.Color, + channel.ValueFromLocation (grad, Layer.ConvertPointToLayer (location,grad.SuperLayer))); + return; + } + } + } + base.MouseDown (theEvent); + } + + public override void MouseDragged (NSEvent theEvent) + { + var location = ConvertPointFromView (theEvent.LocationInWindow, null); + location = ConvertPointToLayer (location); + + if (set != null) { + var channel = set.Editor.ComponentEditor; + var grad = set.Gradient; + ViewModel.Color = channel.UpdateColorFromValue ( + ViewModel.Color, + channel.ValueFromLocation (grad, Layer.ConvertPointToLayer (location, grad.SuperLayer))); + return; + } + base.MouseMoved (theEvent); + } + + public override void MouseUp(NSEvent theEvent) + { + set = null; + base.MouseUp(theEvent); + } + public override void Layout () { base.Layout (); - var frame = Frame.Bounds ().Border (new CommonThickness (padding)); - var labelFrame = new CGRect (frame.X, frame.Height - 22, 20, DefaultControlHeight); - var editorFrame = new CGRect (labelFrame.X + labelFrame.Width, labelFrame.Y, 90, DefaultControlHeight); + var labelFrame = new CGRect (frame.X, frame.Height - DefaultControlHeight, 20, DefaultControlHeight); + var editorFrame = new CGRect (labelFrame.Right, labelFrame.Y, frame.Width - labelFrame.Right, DefaultControlHeight); + var yOffset = DefaultControlHeight + DefaultGradientHeight + 3; foreach (var e in Editors) { e.Label.Frame = labelFrame; e.Editor.Frame = editorFrame; - labelFrame = labelFrame.Translate (0, -(labelFrame.Height + 3)); - editorFrame = editorFrame.Translate (0, -(labelFrame.Height + 3)); + e.Gradient.Frame = new CGRect (editorFrame.X, editorFrame.Y - DefaultGradientHeight + 1, e.Editor.TextField.Frame.Width, DefaultGradientHeight); + e.Gradient.BorderColor = NSColor.DisabledControlText.CGColor; + e.Gradient.ContentsScale = Window?.Screen?.BackingScaleFactor ?? NSScreen.MainScreen.BackingScaleFactor; + labelFrame = labelFrame.Translate (0, -yOffset); + editorFrame = editorFrame.Translate (0, -yOffset); } } } - public abstract class ComponentEditor : NumericSpinEditor + public abstract class ComponentEditor { + public string Name { get; } + public double MinimumValue { get; } + public double MaximumValue { get; } + public double IncrementValue { get; } + + static IEnumerable<double> LerpSteps (double min, double max, int steps) + => Enumerable.Range (0, steps).Select (v => { + var pos = v / (double)steps; + return max * pos - min * (1 - pos); + }); + + public ComponentEditor (string name, double min, double max, double increment) + { + MinimumValue = min; + MaximumValue = max; + IncrementValue = increment; + Name = name; + } + + public void UpdateGradientLayer (CAGradientLayer layer, CommonColor color) + { + layer.Colors = LerpSteps (MinimumValue, MaximumValue, 7) + .Select (value => UpdateColorFromValue (color.UpdateRGB (a: 255), value).ToCGColor ()).ToArray (); + } + + public double InverseLerp (CGPoint start, CGPoint end, CGPoint loc) + { + var a = new CGVector (end.X - start.X, end.Y - start.Y); + var b = new CGVector (loc.X - start.X, loc.Y - start.Y); + var dot = a.dx * b.dx + a.dy * b.dy; + var len = Math.Sqrt (a.dx * a.dx + a.dy * a.dy); + var pos = dot / len; + return MaximumValue * pos - MinimumValue * (1 - pos); + } + + public CGPoint Lerp (CGPoint start, CGPoint end, double amount) + { + return new CGPoint ( + start.X + (end.X - start.X) * amount, + start.Y + (end.Y - start.Y) * amount); + } + + public double ValueFromLocation (CAGradientLayer layer, CGPoint loc) + { + var rect = layer.Frame; + var unitLoc = new CGPoint ( + (loc.X - rect.X) / rect.Width, + (loc.Y - rect.Y) / rect.Height); + + return Clamp (InverseLerp (layer.StartPoint, layer.EndPoint, unitLoc)); + } + + public CGPoint LocationFromColor (CAGradientLayer layer, CommonColor color) + { + var pos = ValueFromColor (color); + var amount = (pos - MinimumValue) / (MaximumValue - MinimumValue); + var unitLoc = Lerp (layer.StartPoint, layer.EndPoint, amount); + + return new CGPoint ( + layer.Frame.X + unitLoc.X * layer.Frame.Width, + layer.Frame.Y + unitLoc.Y * layer.Frame.Height); + } + + public double Clamp (double value) + => Math.Max (MinimumValue, Math.Min (MaximumValue, value)); + public abstract CommonColor UpdateColorFromValue (CommonColor color, double value); public abstract double ValueFromColor (CommonColor color); } + class ComponentSpinEditor : NumericSpinEditor + { + public ComponentSpinEditor (ComponentEditor component) + { + ComponentEditor = component; + MinimumValue = component.MinimumValue; + MaximumValue = component.MaximumValue; + IncrementValue = component.IncrementValue; + } + + public ComponentEditor ComponentEditor { get; } + } + class RedComponentEditor : ComponentEditor { + public RedComponentEditor () : base ("R", 0d, 255d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.R; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateRGB (r: (byte)value); + => color.UpdateRGB (r: (byte)Clamp (value)); } class GreenComponentEditor : ComponentEditor { + public GreenComponentEditor () : base ("G", 0d, 255d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.G; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateRGB (g: (byte)value); + => color.UpdateRGB (g: (byte)Clamp (value)); } class BlueComponentEditor : ComponentEditor { + public BlueComponentEditor () : base ("B", 0d, 255d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.B; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateRGB (b: (byte)value); + => color.UpdateRGB (b: (byte)Clamp (value)); } class AlphaComponentEditor : ComponentEditor { + public AlphaComponentEditor () : base ("A", 0d, 255d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.A; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateRGB (a: (byte)value); + => color.UpdateRGB (a: (byte)Clamp (value)); } class CyanComponentEditor : ComponentEditor { + public CyanComponentEditor () : base ("C", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.C; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateCMYK (c: value); + => color.UpdateCMYK (c: Clamp (value)); } class MagentaComponentEditor : ComponentEditor { + public MagentaComponentEditor () : base ("M", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.M; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateCMYK (m: value); + => color.UpdateCMYK (m: Clamp (value)); } class YellowComponentEditor : ComponentEditor { + public YellowComponentEditor () : base ("Y", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.Y; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateCMYK (y: value); + => color.UpdateCMYK (y: Clamp (value)); } class BlackComponentEditor : ComponentEditor { + public BlackComponentEditor () : base ("K", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.K; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateCMYK (k: value); + => color.UpdateCMYK (k: Clamp (value)); } class HsbHueComponentEditor : ComponentEditor { + public HsbHueComponentEditor () : base ("H", 0d, 360d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => color.Hue; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHSB (hue: value); + => color.UpdateHSB (hue: Clamp (value)); } class HsbSaturationComponentEditor : ComponentEditor { + public HsbSaturationComponentEditor () : base ("S", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.Saturation; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHSB (saturation: value); + => color.UpdateHSB (saturation: Clamp (value)); } class HsbBrightnessComponentEditor : ComponentEditor { + public HsbBrightnessComponentEditor () : base ("B", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => color.Brightness; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHSB (brightness: value); + => color.UpdateHSB (brightness: Clamp (value)); } class HlsHueComponentEditor : ComponentEditor { + public HlsHueComponentEditor () : base ("H", 0d, 360d, 1d) + { + } + public override double ValueFromColor (CommonColor color) => color.Hue; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHLS (hue: value); + => color.UpdateHLS (hue: Clamp (value)); } class HlsLightnessComponentEditor : ComponentEditor { + public HlsLightnessComponentEditor () : base ("L", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.Lightness; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHLS (lightness: value); + => color.UpdateHLS (lightness: Clamp (value)); } class HlsSaturationComponentEditor : ComponentEditor { + public HlsSaturationComponentEditor () : base ("S", 0d, 1d, .01d) + { + } + public override double ValueFromColor (CommonColor color) => (double)color.Saturation; public override CommonColor UpdateColorFromValue (CommonColor color, double value) - => color.UpdateHLS (saturation: value); + => color.UpdateHLS (saturation: Clamp (value)); } } diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentTabVewController.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentTabVewController.cs index efecd63..4d9df92 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentTabVewController.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentTabVewController.cs @@ -7,12 +7,12 @@ namespace Xamarin.PropertyEditing.Mac { class ColorComponentTabViewController : PropertyTabViewController<SolidBrushViewModel> { - public EditorType EditorType { get; set; } + public ChannelEditorType EditorType { get; set; } public ColorComponentTabViewController () { - foreach (var value in Enum.GetValues (typeof (EditorType))) { - var editorType = (EditorType)value; + foreach (var value in Enum.GetValues (typeof (ChannelEditorType))) { + var editorType = (ChannelEditorType)value; AddTabViewItem (new NSTabViewItem { Label = value.ToString (), ViewController = new ColorComponentViewController (editorType) diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentViewController.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentViewController.cs index 291001e..48541fe 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentViewController.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/ColorComponentViewController.cs @@ -9,7 +9,7 @@ namespace Xamarin.PropertyEditing.Mac { ColorComponentEditor editor; - public ColorComponentViewController (EditorType type) : base () + public ColorComponentViewController (ChannelEditorType type) : base () { this.EditorType = type; } @@ -28,7 +28,7 @@ namespace Xamarin.PropertyEditing.Mac } } - public EditorType EditorType { get; } + public ChannelEditorType EditorType { get; } public override void LoadView () { diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/DrawingExtensions.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/DrawingExtensions.cs index d218e5f..78f85be 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/DrawingExtensions.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/DrawingExtensions.cs @@ -40,6 +40,8 @@ namespace Xamarin.PropertyEditing.Mac public static CGColor ToCGColor (this CommonColor color) => new CGColor (color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + + public static NSColor ToNSColor (this CommonColor color) => NSColor.FromRgba (color.R, color.G, color.B, color.A); diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs index 360a441..da8c42e 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/NumericSpinEditor.cs @@ -35,6 +35,10 @@ namespace Xamarin.PropertyEditing.Mac get { return stepper; } } + public NSTextField TextField { + get { return numericEditor; } + } + public override CGSize IntrinsicContentSize { get { var baseSize = stepper.IntrinsicContentSize; diff --git a/Xamarin.PropertyEditing.Mac/Controls/Custom/SolidColorBrushEditor.cs b/Xamarin.PropertyEditing.Mac/Controls/Custom/SolidColorBrushEditor.cs index f68d6fd..6db6c4a 100644 --- a/Xamarin.PropertyEditing.Mac/Controls/Custom/SolidColorBrushEditor.cs +++ b/Xamarin.PropertyEditing.Mac/Controls/Custom/SolidColorBrushEditor.cs @@ -91,8 +91,16 @@ namespace Xamarin.PropertyEditing.Mac abstract class ColorEditorLayer : CALayer { - abstract public void UpdateFromModel (SolidBrushViewModel viewModel); - abstract public void UpdateFromLocation (SolidBrushViewModel viewModel, CGPoint location); + public ColorEditorLayer () + { + Actions = new NSDictionary ( + new NSString ("onOrderIn"), new NSNull (), + new NSString ("onOrderOut"), new NSNull (), + new NSString ("sublayers"), new NSNull (), + new NSString ("bounds"), new NSNull ()); + } + abstract public void UpdateFromModel (EditorInteraction viewModel); + abstract public void UpdateFromLocation (EditorInteraction viewModel, CGPoint location); } class CommonGradientBrushLayer : CALayer @@ -121,10 +129,20 @@ namespace Xamarin.PropertyEditing.Mac switch (Brush) { case CommonLinearGradientBrush linear: - ctx.DrawLinearGradient (gradient, new CGPoint (0, 0), new CGPoint (0, Bounds.Width), CGGradientDrawingOptions.None); + ctx.DrawLinearGradient ( + gradient, + new CGPoint (0, 0), + new CGPoint (0, Bounds.Width), + CGGradientDrawingOptions.None); break; case CommonRadialGradientBrush radial: - ctx.DrawRadialGradient (gradient, startCenter: center, startRadius: 0f, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions.None); + ctx.DrawRadialGradient ( + gradient, + startCenter: center, + startRadius: 0f, + endCenter: center, + endRadius: radius, + options: CGGradientDrawingOptions.None); break; } } @@ -210,12 +228,12 @@ namespace Xamarin.PropertyEditing.Mac } } - public enum EditorType + public enum ChannelEditorType { - Rgb, - Hls, - Hsb, - Cmyk + RGB, + HLS, + HSB, + CMYK }; class HistoryLayer : ColorEditorLayer @@ -256,17 +274,17 @@ namespace Xamarin.PropertyEditing.Mac Current.Frame = new CGRect (width, 0, width, Clip.Frame.Height); } - public override void UpdateFromModel (SolidBrushViewModel viewModel) + public override void UpdateFromModel (EditorInteraction interaction) { LayoutIfNeeded (); - Current.BackgroundColor = viewModel.Color.ToCGColor (); - Previous.BackgroundColor = viewModel.LastColor.ToCGColor (); + Current.BackgroundColor = interaction.Color.ToCGColor (); + Previous.BackgroundColor = interaction.LastColor.ToCGColor (); } - public override void UpdateFromLocation (SolidBrushViewModel viewModel, CGPoint location) + public override void UpdateFromLocation (EditorInteraction interaction, CGPoint location) { if (Previous == HitTest (location)) - viewModel.Color = viewModel.LastColor; + interaction.Color = interaction.LastColor; } } @@ -275,11 +293,13 @@ namespace Xamarin.PropertyEditing.Mac const float GripRadius = 4; const float BorderRadius = 3; const float Margin = 3; + ComponentEditor saturationEditor = new HsbSaturationComponentEditor (); + ComponentEditor brightnessEditor = new HsbBrightnessComponentEditor (); public ShadeLayer () { - AddSublayer (Colors); - Colors.AddSublayer (Black); + AddSublayer (Saturation); + Saturation.AddSublayer (Brightness); AddSublayer (Grip); float innerRadius = GripRadius - 1; @@ -301,7 +321,7 @@ namespace Xamarin.PropertyEditing.Mac CornerRadius = GripRadius, }; - CALayer Black = new CAGradientLayer { + CAGradientLayer Brightness = new CAGradientLayer { Colors = new[] { new CGColor (0f, 0f, 0f, 1f), new CGColor (0f, 0f, 0f, 0f) @@ -309,7 +329,7 @@ namespace Xamarin.PropertyEditing.Mac CornerRadius = BorderRadius, }; - CAGradientLayer Colors = new CAGradientLayer { + CAGradientLayer Saturation = new CAGradientLayer { Colors = new[] { new CGColor (1f, 1f, 1f), new CGColor (1f, .3f, 0f) @@ -324,47 +344,48 @@ namespace Xamarin.PropertyEditing.Mac { base.LayoutSublayers(); - Colors.Frame = new CGRect (Margin, Margin, Frame.Width - 2 * Margin, Frame.Height - 2 * Margin); - Black.Frame = new CGRect (0, 0, Frame.Width - 2 * Margin, Frame.Height - 2 * Margin); - Colors.StartPoint = new CGPoint (0, .5); - Colors.EndPoint = new CGPoint (1, .5); + Saturation.Frame = new CGRect (Margin, Margin, Frame.Width - 2 * Margin, Frame.Height - 2 * Margin); + Brightness.Frame = new CGRect (0, 0, Frame.Width - 2 * Margin, Frame.Height - 2 * Margin); + Saturation.StartPoint = new CGPoint (0, .5); + Saturation.EndPoint = new CGPoint (1, .5); } - public override void UpdateFromModel (SolidBrushViewModel viewModel) + CommonColor c; + public override void UpdateFromModel (EditorInteraction interaction) { LayoutIfNeeded (); - var color = viewModel.Color; - var frame = Colors.Frame; - var x = color.Saturation * frame.Width + frame.X; - var y = color.Brightness * frame.Height + frame.Y; + var color = interaction.Color; + + var frame = Saturation.Frame; + var sat = saturationEditor.LocationFromColor (Saturation, color); + var bright = brightnessEditor.LocationFromColor (Brightness, color); + + var x = sat.X; + var y = bright.Y + frame.Y; Grip.Frame = new CGRect (x - GripRadius, y - GripRadius, GripRadius * 2, GripRadius * 2); - Colors.Colors = new[] { + Saturation.Colors = new[] { new CGColor (1f, 1f, 1f), - color.HueColor.ToCGColor () + interaction.Color.HueColor.ToCGColor () }; } - public override void UpdateFromLocation (SolidBrushViewModel viewModel, CGPoint location) + public override void UpdateFromLocation (EditorInteraction interaction, CGPoint location) { var loc = location; - var frame = Colors.Frame; - loc.X -= frame.X; - loc.Y -= frame.Y; - - var brightness = loc.Y / frame.Height; - var saturation = loc.X / frame.Width; + var frame = Saturation.Frame; - if (viewModel == null) + if (interaction.ViewModel == null) return; - var color = viewModel.Color; - viewModel.Color = CommonColor.FromHSB ( - Math.Min (360, Math.Max (0, color.Hue)), - Math.Min (1, Math.Max (0, saturation)), - Math.Min (1, Math.Max (0, brightness)), - color.A); + var color = interaction.Color; + var saturation = saturationEditor.ValueFromLocation (Saturation, loc); + var brightness = saturationEditor.ValueFromLocation ( + Brightness, + new CGPoint (loc.X + Brightness.Frame.X, loc.Y + Brightness.Frame.Y)); + + c = interaction.Color = interaction.Color.UpdateHSB (saturation: saturation, brightness: brightness); } } @@ -373,6 +394,7 @@ namespace Xamarin.PropertyEditing.Mac const float Margin = 3; const float BorderRadius = 3; const float GripRadius = 3; + ComponentEditor hueEditor = new HsbHueComponentEditor (); public CGColor GripColor { @@ -382,23 +404,17 @@ namespace Xamarin.PropertyEditing.Mac public HueLayer () { + hueEditor.UpdateGradientLayer (Colors, new CommonColor (0,255,0)); AddSublayer (Colors); AddSublayer (Grip); } CAGradientLayer Colors = new CAGradientLayer { BorderColor = new CGColor (.5f, .5f, .5f, .5f), + StartPoint = new CGPoint (0,1), + EndPoint = new CGPoint (0,0), BorderWidth = 1, CornerRadius = BorderRadius, - Colors = new[] { - new CGColor (1,0,0), - new CGColor (1,0,1), - new CGColor (0,0,1), - new CGColor (0,1,1), - new CGColor (0,1,0), - new CGColor (1,1,0), - new CGColor (1,0,0) - } }; CALayer Grip = new CALayer { @@ -407,20 +423,20 @@ namespace Xamarin.PropertyEditing.Mac CornerRadius = GripRadius, }; - public override void UpdateFromModel (SolidBrushViewModel viewModel) + CommonColor c; + public override void UpdateFromModel (EditorInteraction interaction) { LayoutIfNeeded (); - var color = viewModel.Color; - var hue = color.Hue / 360; - var pos = Colors.Frame.Height * (1 - hue); - var loc = new CGPoint (1, pos); - loc.Y -= Grip.Frame.Height / 2f; + var color = interaction.Color; + if (c == color) + return; - Grip.Frame = new CGRect (loc.X, loc.Y + Colors.Frame.Y, Grip.Frame.Width, Grip.Frame.Height); + var loc = hueEditor.LocationFromColor (Colors, color); + Grip.Frame = new CGRect (1, loc.Y - Grip.Frame.Height / 2f, Grip.Frame.Width, Grip.Frame.Height); } - public override void UpdateFromLocation (SolidBrushViewModel viewModel, CGPoint location) + public override void UpdateFromLocation (EditorInteraction interaction, CGPoint location) { var loc = location; var clos = Math.Min (Colors.Frame.Height, Math.Max (0, loc.Y - Colors.Frame.Y)); @@ -428,15 +444,13 @@ namespace Xamarin.PropertyEditing.Mac Grip.Frame = new CGRect (1, clos + Colors.Frame.Y - Grip.Frame.Height / 2, Frame.Width - 2, 2 * GripRadius); var hue = (1 - clos/ Colors.Frame.Height) * 360; - if (viewModel == null) + if (interaction == null) return; - var color = viewModel.Color; - viewModel.Color = CommonColor.FromHSB ( - Math.Max (0, Math.Min (360, hue)), - Math.Max (0, Math.Min (1, color.Saturation)), - Math.Max (0, Math.Min (1, color.Brightness)), - color.A); + var color = interaction.Color; + c = interaction.Color = hueEditor.UpdateColorFromValue ( + interaction.Color, + hueEditor.ValueFromLocation (Colors, loc)); } public override void LayoutSublayers() @@ -447,17 +461,61 @@ namespace Xamarin.PropertyEditing.Mac } } + class EditorInteraction : NotifyingObject + { + public EditorInteraction (SolidBrushViewModel viewModel, ColorEditorLayer layer) + { + ViewModel = viewModel; + StartColor = Color = viewModel.Color; + Layer = layer; + } + + public ColorEditorLayer Layer { get; set; } + public SolidBrushViewModel ViewModel { get; set; } + + public CommonColor StartColor { get; } + + CommonColor color; + public CommonColor Color + { + get => color; + set { + if (color == value) + return; + + color = value; + OnPropertyChanged (); + } + } + + public CommonColor LastColor { + get => ViewModel.LastColor; + } + + public void Commit () + { + ViewModel.Color = Color; + ViewModel.CommitLastColor (); + } + } + class SolidColorBrushEditor : ColorEditorView { ShadeLayer Shade = new ShadeLayer (); HueLayer Hue = new HueLayer (); HistoryLayer History = new HistoryLayer (); + CALayer Background = new CALayer { CornerRadius = 3, BorderWidth = 1 }; + readonly CALayer componentBackground = new CALayer { + CornerRadius = 3, + BorderWidth = 1 + }; + readonly ColorComponentTabViewController componentTabs = new ColorComponentTabViewController () { - EditorType = EditorType.Rgb + EditorType = ChannelEditorType.RGB }; public override bool AcceptsFirstResponder() => true; @@ -490,57 +548,82 @@ namespace Xamarin.PropertyEditing.Mac Layer.AddSublayer (Shade); Layer.AddSublayer (Hue); Layer.AddSublayer (History); + Layer.AddSublayer (componentBackground); WantsLayer = true; AddSubview (componentTabs.View); } - ColorEditorLayer active; - + EditorInteraction interaction; public override void UpdateFromEvent (NSEvent theEvent) { } - + public override void MouseDown (NSEvent theEvent) { var location = ConvertPointFromView (theEvent.LocationInWindow, null); location = ConvertPointToLayer (location); - active = null; + interaction = null; foreach (var layer in Layer.Sublayers) { var hit = layer.HitTest (location); for (var c = hit; c != null; c = c.SuperLayer) { - active = c as ColorEditorLayer; + var active = c as ColorEditorLayer; if (active != null) { + interaction = new EditorInteraction (ViewModel, active); active.UpdateFromLocation ( - ViewModel, + interaction, Layer.ConvertPointToLayer (location, active)); + OnPropertyChanged (ViewModel, new PropertyChangedEventArgs (nameof (SolidBrushViewModel.Color))); return; } } } } + CGPoint last; public override void MouseDragged(NSEvent theEvent) { var location = ConvertPointFromView (theEvent.LocationInWindow, null); - active?.UpdateFromLocation ( - ViewModel, - Layer.ConvertPointToLayer (location, active)); + var diff = new CGPoint (last.X - location.X, last.Y - location.Y); + + if (diff.X * diff.X < .5 && diff.Y * diff.Y < .5) + return; + + interaction?.Layer?.UpdateFromLocation ( + interaction, + Layer.ConvertPointToLayer (location, interaction.Layer)); + + OnPropertyChanged (ViewModel, new PropertyChangedEventArgs (nameof (SolidBrushViewModel.Color))); } - protected override void OnPropertyChanged (object sender, PropertyChangedEventArgs e) + public override void MouseUp(NSEvent theEvent) + { + base.MouseUp(theEvent); + interaction?.Commit (); + interaction = null; + } + + protected override void OnPropertyChanged (object sender, PropertyChangedEventArgs e) { - foreach (var editor in Layer.Sublayers.OfType<ColorEditorLayer> ()) { - editor.UpdateFromModel (ViewModel); + var inter = interaction ?? new EditorInteraction (ViewModel, null); + + switch (e.PropertyName) { + case nameof (SolidBrushViewModel.Color): + case nameof (SolidBrushViewModel.HueColor): + foreach (var editor in Layer.Sublayers.OfType<ColorEditorLayer> ()) { + editor.UpdateFromModel (inter); + } + break; } } protected override void OnViewModelChanged (SolidBrushViewModel oldModel) { base.OnViewModelChanged(oldModel); + var inter = interaction ?? new EditorInteraction (ViewModel, null); componentTabs.ViewModel = ViewModel; foreach (var editor in Layer.Sublayers.OfType<ColorEditorLayer> ()) { - editor.UpdateFromModel (ViewModel); + editor.UpdateFromModel (inter); } } @@ -558,15 +641,25 @@ namespace Xamarin.PropertyEditing.Mac Background.BackgroundColor = NSColor.ControlBackground.CGColor; Background.Frame = new CGRect (0, 0, Frame.Height, Frame.Height); Background.SetNeedsDisplay (); + + componentBackground.BorderColor = new CGColor (.5f, .5f, .5f, .5f); + componentBackground.BackgroundColor = NSColor.ControlBackground.CGColor; + componentBackground.Frame = new CGRect (0, 0, Frame.Height, Frame.Height); + componentBackground.SetNeedsDisplay (); + var x = Frame.Height + 4 * padding; + componentBackground.Frame = new CGRect (Frame.Height + 4 * padding, 0, Frame.Width - x, Frame.Height); + Hue.Frame = new CGRect (firstStop, secondBase, secondarySpan, primarySpan); Hue.GripColor = NSColor.Text.CGColor; Hue.SetNeedsDisplay (); Shade.Frame = new CGRect (firstBase, secondBase, primarySpan, primarySpan); History.Frame = new CGRect (firstBase, firstBase, primarySpan, secondarySpan); + var inter = interaction ?? new EditorInteraction (ViewModel, null); foreach (var editor in Layer.Sublayers.OfType<ColorEditorLayer> ()) { - editor.UpdateFromModel (ViewModel); + editor.UpdateFromModel (inter); } - componentTabs.View.Frame = new CGRect (Frame.Height, 0, Frame.Width - Frame.Height, Frame.Height); + + componentTabs.View.Frame = componentBackground.Frame.Inset (4 * padding, 2 * padding); } } } |