diff --git a/Pinta.Core/Extensions/Gtk/GtkExtensions.Widget.cs b/Pinta.Core/Extensions/Gtk/GtkExtensions.Widget.cs index 342e26cc0..a2c32f9ab 100644 --- a/Pinta.Core/Extensions/Gtk/GtkExtensions.Widget.cs +++ b/Pinta.Core/Extensions/Gtk/GtkExtensions.Widget.cs @@ -237,6 +237,22 @@ public static Gtk.Entry GetEntry (this Gtk.ComboBox box) return (Gtk.Entry) box.Child!; } + /// + /// Helper function to return whether the text field of a Gtk.Entry has focus (i.e. is + /// currently being edited). Gtk.Entry.HasFocus does not match this. + /// + public static bool IsEditingText (this Gtk.Entry entry) + { + Gtk.Widget? entryText = entry.GetFirstChild (); + + if (entryText is null) { + Console.Error.WriteLine ("Failed to find child text widget for Gtk.Entry"); + return false; + } + + return entryText.HasFocus; + } + /// /// Configures a spin button to immediately activate the default widget after pressing Enter, /// by configuring the editable text field. diff --git a/Pinta.Core/Extensions/ToolBarComboBox.cs b/Pinta.Core/Extensions/ToolBarComboBox.cs index 41fe44358..ea252bd21 100644 --- a/Pinta.Core/Extensions/ToolBarComboBox.cs +++ b/Pinta.Core/Extensions/ToolBarComboBox.cs @@ -1,21 +1,21 @@ // // ToolBarComboBox.cs -// +// // Author: // Jonathan Pobst -// +// // Copyright (c) 2010 Jonathan Pobst -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -84,14 +84,7 @@ private void ComboBox_OnChanged (Gtk.ComboBox _, EventArgs __) if (!ComboBox.HasEntry) return; - Gtk.Widget? entryText = ComboBox.GetEntry ().GetFirstChild (); - - if (entryText is null) { - Console.Error.WriteLine ("Failed to find child text widget for Gtk.Entry"); - return; - } - - if (!entryText.HasFocus) { + if (!ComboBox.GetEntry ().IsEditingText ()) { GLib.Functions.IdleAdd (0, () => { if (PintaCore.Workspace.HasOpenDocuments) PintaCore.Workspace.ActiveWorkspace.GrabFocusToCanvas (); diff --git a/Pinta.Gui.Widgets/Widgets/ColorPickerDialog.cs b/Pinta.Gui.Widgets/Widgets/ColorPickerDialog.cs index 386f1b7bc..87b9aef55 100644 --- a/Pinta.Gui.Widgets/Widgets/ColorPickerDialog.cs +++ b/Pinta.Gui.Widgets/Widgets/ColorPickerDialog.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -37,9 +36,6 @@ enum ColorSurfaceType const int SMALL_SLIDER_WIDTH = 150; const int SMALL_SPACING = 2; - const int CPS_PADDING_HEIGHT = 10; - const int CPS_PADDING_WIDTH = 14; - private readonly Gtk.Box top_box; private readonly Gtk.Box swatch_box; private readonly Gtk.Box color_display_box; @@ -344,8 +340,10 @@ internal ColorPickerDialog ( // Handles the ColorPickerSliders + Hex entry. + Color initialColor = ExtractTargetedColor (adjustable, primarySelected); + Gtk.Entry hexEntry = new () { - Text_ = ExtractTargetedColor (adjustable, true).ToHex (), + Text_ = initialColor.ToHex (), MaxWidthChars = 10, }; hexEntry.OnChanged += HexEntry_OnChanged; @@ -359,175 +357,73 @@ internal ColorPickerDialog ( hexBox.Append (hexLabel); hexBox.Append (hexEntry); - ColorPickerSlider.Settings cpsArgs = new () { - Text = string.Empty, - TopWindow = this, - SliderPaddingHeight = CPS_PADDING_HEIGHT, - SliderPaddingWidth = CPS_PADDING_WIDTH, - SliderWidth = slider_width, - MaxWidthChars = 3, - }; - ColorPickerSlider hueSlider = new ( - settings: cpsArgs with { - Max = 360, - Text = Translations.GetString ("Hue"), - InitialValue = ExtractTargetedColor (adjustable, true).ToHsv ().Hue, - } + ColorPickerSlider.Component.Hue, + initialColor, + slider_width ); - hueSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => hueSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - startColor: CurrentColor.CopyHsv (hue: 0), - endColor: CurrentColor.CopyHsv (hue: 360), - range: NumberRange.Create (0, 360), - new Dictionary { - [60] = CurrentColor.CopyHsv (hue: 60), - [120] = CurrentColor.CopyHsv (hue: 120), - [180] = CurrentColor.CopyHsv (hue: 180), - [240] = CurrentColor.CopyHsv (hue: 240), - [300] = CurrentColor.CopyHsv (hue: 300), - } - ) - ) - ); - hueSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor.CopyHsv (hue: args.Value); + hueSlider.OnColorChanged += (_, _) => { + CurrentColor = hueSlider.Color; UpdateView (); }; ColorPickerSlider saturationSlider = new ( - settings: cpsArgs with { - Max = 100, - Text = Translations.GetString ("Sat"), - InitialValue = ExtractTargetedColor (adjustable, true).ToHsv ().Sat * 100.0, - } + ColorPickerSlider.Component.Saturation, + initialColor, + slider_width ); - saturationSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => saturationSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor.CopyHsv (sat: 0), - CurrentColor.CopyHsv (sat: 1)) - ) - ); - saturationSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor.CopyHsv (sat: args.Value / 100.0); + saturationSlider.OnColorChanged += (_, _) => { + CurrentColor = saturationSlider.Color; UpdateView (); }; ColorPickerSlider valueSlider = new ( - settings: cpsArgs with { - Max = 100, - Text = Translations.GetString ("Value"), - InitialValue = ExtractTargetedColor (adjustable, true).ToHsv ().Val * 100.0, - } - ); - valueSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => valueSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor.CopyHsv (value: 0), - CurrentColor.CopyHsv (value: 1)) - ) + ColorPickerSlider.Component.Value, + initialColor, + slider_width ); - valueSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor.CopyHsv (value: args.Value / 100.0); + valueSlider.OnColorChanged += (_, _) => { + CurrentColor = valueSlider.Color; UpdateView (); }; ColorPickerSlider redSlider = new ( - settings: cpsArgs with { - Max = 255, - Text = Translations.GetString ("Red"), - InitialValue = ExtractTargetedColor (adjustable, true).R * 255.0, - } + ColorPickerSlider.Component.Red, + initialColor, + slider_width ); - redSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => redSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor with { R = 0 }, - CurrentColor with { R = 1 }) - ) - ); - redSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor with { R = args.Value / 255.0 }; + redSlider.OnColorChanged += (_, _) => { + CurrentColor = redSlider.Color; UpdateView (); }; ColorPickerSlider greenSlider = new ( - settings: cpsArgs with { - Max = 255, - Text = Translations.GetString ("Green"), - InitialValue = ExtractTargetedColor (adjustable, true).G * 255.0, - } - ); - greenSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => greenSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor with { G = 0 }, - CurrentColor with { G = 1 }) - ) + ColorPickerSlider.Component.Green, + initialColor, + slider_width ); - greenSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor with { G = args.Value / 255.0 }; + greenSlider.OnColorChanged += (_, _) => { + CurrentColor = greenSlider.Color; UpdateView (); }; ColorPickerSlider blueSlider = new ( - settings: cpsArgs with { - Max = 255, - Text = Translations.GetString ("Blue"), - InitialValue = ExtractTargetedColor (adjustable, true).B * 255.0, - } + ColorPickerSlider.Component.Blue, + initialColor, + slider_width ); - blueSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => blueSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor with { B = 0 }, - CurrentColor with { B = 1 }) - ) - ); - blueSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor with { B = args.Value / 255.0 }; + blueSlider.OnColorChanged += (_, _) => { + CurrentColor = blueSlider.Color; UpdateView (); }; ColorPickerSlider alphaSlider = new ( - settings: cpsArgs with { - Max = 255, - Text = Translations.GetString ("Alpha"), - InitialValue = ExtractTargetedColor (adjustable, true).A * 255.0, - } - ); - alphaSlider.Gradient.SetDrawFunc ( - (_, c, w, h) => alphaSlider.DrawGradient ( - c, - w, - h, - ColorGradient.Create ( - CurrentColor with { A = 0 }, - CurrentColor with { A = 1 }) - ) + ColorPickerSlider.Component.Alpha, + initialColor, + slider_width ); - alphaSlider.OnValueChange += (sender, args) => { - CurrentColor = CurrentColor with { A = args.Value / 255.0 }; + alphaSlider.OnColorChanged += (_, _) => { + CurrentColor = alphaSlider.Color; UpdateView (); }; @@ -889,29 +785,26 @@ private void CycleColors () UpdateView (); } - private void UpdateView () { // Redraw picker surfaces picker_surface_cursor.QueueDraw (); picker_surface.QueueDraw (); - // Redraw cps - HsvColor hsv = CurrentColor.ToHsv (); - - hue_slider.SetValue (hsv.Hue); - saturation_slider.SetValue (hsv.Sat * 100.0); - value_slider.SetValue (hsv.Val * 100.0); - - red_slider.SetValue (CurrentColor.R * 255.0); - green_slider.SetValue (CurrentColor.G * 255.0); - blue_slider.SetValue (CurrentColor.B * 255.0); - alpha_slider.SetValue (CurrentColor.A * 255.0); + // Update sliders with current color + Color current = CurrentColor; + hue_slider.Color = current; + saturation_slider.Color = current; + value_slider.Color = current; + red_slider.Color = current; + green_slider.Color = current; + blue_slider.Color = current; + alpha_slider.Color = current; // Update hex if (GetFocus ()?.Parent != hex_entry) - hex_entry.SetText (CurrentColor.ToHex ()); + hex_entry.SetText (current.ToHex ()); // Redraw palette displays foreach (var display in color_displays) diff --git a/Pinta.Gui.Widgets/Widgets/ColorPickerSlider.cs b/Pinta.Gui.Widgets/Widgets/ColorPickerSlider.cs index 1236482fb..c9d9fc420 100644 --- a/Pinta.Gui.Widgets/Widgets/ColorPickerSlider.cs +++ b/Pinta.Gui.Widgets/Widgets/ColorPickerSlider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using Cairo; using Pinta.Core; @@ -11,57 +12,57 @@ namespace Pinta.Gui.Widgets; // with a drawingarea public sealed class ColorPickerSlider : Gtk.Box { - public sealed class OnChangeValueArgs (string senderName, double value) : EventArgs + public enum Component { - public string SenderName { get; } = senderName; - public double Value { get; } = value; + Hue, + Saturation, + Value, + Red, + Green, + Blue, + Alpha } - public readonly record struct Settings ( - int Max, - string Text, // required - double InitialValue, - Gtk.Window TopWindow, // required - int SliderPaddingWidth, - int SliderPaddingHeight, - int SliderWidth, - int MaxWidthChars); - - private readonly Settings settings; - private readonly Gtk.Window top_window; - private readonly Gtk.Label slider_label; + private const int PADDING_WIDTH = 14; + private const int PADDING_HEIGHT = 10; + + private readonly Component component; private readonly Gtk.Scale slider_control; private readonly Gtk.Entry input_field; private readonly Gtk.Overlay slider_overlay; private readonly Gtk.DrawingArea cursor_area; + private Color color; public Gtk.DrawingArea Gradient { get; } - public event EventHandler? OnValueChange; + public event EventHandler? OnColorChanged; - public ColorPickerSlider (Settings settings) + public ColorPickerSlider (Component component, Color initialColor, int initialWidth) { Gtk.Scale sliderControl = new () { - WidthRequest = settings.SliderWidth, + WidthRequest = initialWidth, Opacity = 0, }; sliderControl.SetOrientation (Gtk.Orientation.Horizontal); - sliderControl.SetAdjustment (Gtk.Adjustment.New (0, 0, settings.Max + 1, 1, 1, 1)); - sliderControl.SetValue (settings.InitialValue); + sliderControl.SetAdjustment (Gtk.Adjustment.New (0, 0, GetMaxValue (component) + 1, 1, 1, 1)); + sliderControl.SetValue (ExtractValue (initialColor, component)); sliderControl.OnChangeValue += OnSliderControlChangeValue; Gtk.DrawingArea cursorArea = new (); - cursorArea.SetSizeRequest (settings.SliderWidth, this.GetHeight ()); + cursorArea.SetSizeRequest (initialWidth, this.GetHeight ()); cursorArea.SetDrawFunc (CursorAreaDrawingFunction); Gtk.DrawingArea gradient = new (); - gradient.SetSizeRequest (settings.SliderWidth, this.GetHeight ()); + gradient.SetSizeRequest (initialWidth, this.GetHeight ()); + gradient.SetDrawFunc ((area, context, width, height) => { + DrawGradient (context, width, height, CreateGradient (color, component)); + }); Gtk.Label sliderLabel = new () { WidthRequest = 50 }; - sliderLabel.SetLabel (settings.Text); + sliderLabel.SetLabel (GetLabelText (component)); Gtk.Overlay sliderOverlay = new () { - WidthRequest = settings.SliderWidth, + WidthRequest = initialWidth, HeightRequest = this.GetHeight (), }; sliderOverlay.AddOverlay (gradient); @@ -69,11 +70,11 @@ public ColorPickerSlider (Settings settings) sliderOverlay.AddOverlay (sliderControl); Gtk.Entry inputField = new () { - MaxWidthChars = settings.MaxWidthChars, + MaxWidthChars = 3, WidthRequest = 50, Hexpand = false, }; - inputField.SetText (Convert.ToInt32 (settings.InitialValue).ToString ()); + inputField.SetText (Convert.ToInt32 (ExtractValue (initialColor, component)).ToString ()); inputField.OnChanged += OnInputFieldChanged; // --- Initialization (Gtk.Box) @@ -84,9 +85,7 @@ public ColorPickerSlider (Settings settings) // --- References to keep - top_window = settings.TopWindow; - - slider_label = sliderLabel; + color = initialColor; cursor_area = cursorArea; slider_control = sliderControl; slider_overlay = sliderOverlay; @@ -94,7 +93,7 @@ public ColorPickerSlider (Settings settings) Gradient = gradient; - this.settings = settings; + this.component = component; } private void CursorAreaDrawingFunction ( @@ -105,7 +104,7 @@ private void CursorAreaDrawingFunction ( { const int OUTLINE_WIDTH = 2; - double currentPosition = slider_control.GetValue () / settings.Max * (width - 2 * settings.SliderPaddingWidth) + settings.SliderPaddingWidth; + double currentPosition = slider_control.GetValue () / GetMaxValue (component) * (width - 2 * PADDING_WIDTH) + PADDING_WIDTH; ReadOnlySpan cursorPoly = [ new (currentPosition, height / 2), @@ -133,26 +132,18 @@ private bool OnSliderControlChangeValue ( Gtk.Range.ChangeValueSignalArgs args) { // The provided value is from the scroll action, so we need to clamp to the range! - double clampedValue = Math.Clamp (args.Value, 0, settings.Max); - - OnChangeValueArgs e = new ( - senderName: slider_label.GetLabel (), - value: clampedValue); + double clampedValue = Math.Clamp (args.Value, 0, GetMaxValue (component)); input_field.SetText (clampedValue.ToString (CultureInfo.InvariantCulture)); - OnValueChange?.Invoke (this, e); + color = UpdateValue (color, component, clampedValue); + OnColorChanged?.Invoke (this, new ()); return false; } private void OnInputFieldChanged (Gtk.Editable inputField, EventArgs e) { - - // see SetValue about suppression - if (suppress_event) - return; - string text = inputField.GetText (); bool success = double.TryParse ( @@ -160,19 +151,17 @@ private void OnInputFieldChanged (Gtk.Editable inputField, EventArgs e) CultureInfo.InvariantCulture, out double parsed); - if (parsed > settings.Max) { - parsed = settings.Max; + double maxValue = GetMaxValue (component); + if (parsed > maxValue) { + parsed = maxValue; inputField.SetText (Convert.ToInt32 (parsed).ToString ()); } if (!success) return; - OnChangeValueArgs e2 = new ( - senderName: slider_label.GetLabel (), - value: parsed); - - OnValueChange?.Invoke (this, e2); + color = UpdateValue (color, component, parsed); + OnColorChanged?.Invoke (this, new ()); } public void SetSliderWidth (int sliderWidth) @@ -183,45 +172,45 @@ public void SetSliderWidth (int sliderWidth) slider_overlay.WidthRequest = sliderWidth; } - private bool suppress_event = false; - public void SetValue (double val) - { - slider_control.SetValue (val); - // Make sure we do not set the text if we are editing it right now - // This is the only reason top_window is passed in as an arg, and despite my best efforts I cannot find a way - // to get that info from GTK programmatically. - if (top_window.GetFocus ()?.Parent != input_field) { - // hackjob - // prevents OnValueChange from firing when we change the value internally - // because OnValueChange eventually calls SetValue so it causes a stack overflow - suppress_event = true; - input_field.SetText (Convert.ToInt32 (val).ToString ()); + public Color Color { + get => color; + set { + color = value; + double componentValue = ExtractValue (value, component); + slider_control.SetValue (componentValue); + + if (!input_field.IsEditingText ()) { + // Ensure we don't get an infinite loop of "value changed" events + string newText = Convert.ToInt32 (componentValue).ToString (); + if (newText != input_field.GetText ()) + input_field.SetText (newText); + } + + Gradient.QueueDraw (); + cursor_area.QueueDraw (); } - Gradient.QueueDraw (); - cursor_area.QueueDraw (); - suppress_event = false; } - public void DrawGradient (Context context, int width, int height, ColorGradient colors) + private void DrawGradient (Context context, int width, int height, ColorGradient colors) { context.Antialias = Antialias.None; Size drawSize = new ( - Width: width - settings.SliderPaddingWidth * 2, - Height: height - settings.SliderPaddingHeight * 2); + Width: width - PADDING_WIDTH * 2, + Height: height - PADDING_HEIGHT * 2); PointI p = new ( - X: settings.SliderPaddingWidth + drawSize.Width, - Y: settings.SliderPaddingHeight + drawSize.Height); + X: PADDING_WIDTH + drawSize.Width, + Y: PADDING_HEIGHT + drawSize.Height); int bsize = drawSize.Height / 2; // Draw transparency background context.FillRectangle ( - new RectangleD (settings.SliderPaddingWidth, settings.SliderPaddingHeight, drawSize.Width, drawSize.Height), + new RectangleD (PADDING_WIDTH, PADDING_HEIGHT, drawSize.Width, drawSize.Height), new Color (1, 1, 1)); - for (int x = settings.SliderPaddingWidth; x < p.X; x += bsize * 2) { + for (int x = PADDING_WIDTH; x < p.X; x += bsize * 2) { int bwidth = (x + bsize > p.X) @@ -229,11 +218,11 @@ public void DrawGradient (Context context, int width, int height, ColorGradient< : bsize; context.FillRectangle ( - new RectangleD (x, settings.SliderPaddingHeight, bwidth, bsize), + new RectangleD (x, PADDING_HEIGHT, bwidth, bsize), new Color (.8, .8, .8)); } - for (int x = settings.SliderPaddingWidth + bsize; x < p.X; x += bsize * 2) { + for (int x = PADDING_WIDTH + bsize; x < p.X; x += bsize * 2) { int bwidth = (x + bsize > p.X) @@ -241,13 +230,13 @@ public void DrawGradient (Context context, int width, int height, ColorGradient< : bsize; context.FillRectangle ( - new RectangleD (x, settings.SliderPaddingHeight + drawSize.Height / 2, bwidth, bsize), + new RectangleD (x, PADDING_HEIGHT + drawSize.Height / 2, bwidth, bsize), new Color (.8, .8, .8)); } LinearGradient pat = new ( - x0: settings.SliderPaddingWidth, - y0: settings.SliderPaddingHeight, + x0: PADDING_WIDTH, + y0: PADDING_HEIGHT, x1: p.X, y1: p.Y); @@ -261,8 +250,8 @@ public void DrawGradient (Context context, int width, int height, ColorGradient< pat.AddColorStop (normalized.Range.Upper, normalized.EndColor); context.Rectangle ( - settings.SliderPaddingWidth, - settings.SliderPaddingHeight, + PADDING_WIDTH, + PADDING_HEIGHT, drawSize.Width, drawSize.Height); @@ -270,4 +259,87 @@ public void DrawGradient (Context context, int width, int height, ColorGradient< context.Fill (); } + private static double ExtractValue (Color color, Component component) => component switch { + Component.Hue => color.ToHsv ().Hue, + Component.Saturation => color.ToHsv ().Sat * 100.0, + Component.Value => color.ToHsv ().Val * 100.0, + Component.Red => color.R * 255.0, + Component.Green => color.G * 255.0, + Component.Blue => color.B * 255.0, + Component.Alpha => color.A * 255.0, + _ => throw new ArgumentOutOfRangeException (nameof (component), component, null) + }; + + private static Color UpdateValue (Color color, Component component, double val) => component switch { + Component.Hue => color.CopyHsv (hue: val), + Component.Saturation => color.CopyHsv (sat: val / 100.0), + Component.Value => color.CopyHsv (value: val / 100.0), + Component.Red => color with { R = val / 255.0 }, + Component.Green => color with { G = val / 255.0 }, + Component.Blue => color with { B = val / 255.0 }, + Component.Alpha => color with { A = val / 255.0 }, + _ => throw new ArgumentOutOfRangeException (nameof (component), component, null) + }; + + private static double GetMaxValue (Component component) => component switch { + Component.Hue => 360, + Component.Saturation or Component.Value => 100, + Component.Red or Component.Green or Component.Blue or Component.Alpha => 255, + _ => throw new ArgumentOutOfRangeException () + }; + + private static ColorGradient CreateGradient (Color color, Component component) + { + return component switch { + Component.Hue => ColorGradient.Create ( + startColor: color.CopyHsv (hue: 0), + endColor: color.CopyHsv (hue: 360), + range: NumberRange.Create (0, 360), + new Dictionary { + [60] = color.CopyHsv (hue: 60), + [120] = color.CopyHsv (hue: 120), + [180] = color.CopyHsv (hue: 180), + [240] = color.CopyHsv (hue: 240), + [300] = color.CopyHsv (hue: 300), + } + ), + Component.Saturation => ColorGradient.Create ( + color.CopyHsv (sat: 0), + color.CopyHsv (sat: 1) + ), + Component.Value => ColorGradient.Create ( + color.CopyHsv (value: 0), + color.CopyHsv (value: 1) + ), + Component.Red => ColorGradient.Create ( + color with { R = 0 }, + color with { R = 1 } + ), + Component.Green => ColorGradient.Create ( + color with { G = 0 }, + color with { G = 1 } + ), + Component.Blue => ColorGradient.Create ( + color with { B = 0 }, + color with { B = 1 } + ), + Component.Alpha => ColorGradient.Create ( + color with { A = 0 }, + color with { A = 1 } + ), + _ => throw new ArgumentOutOfRangeException (nameof (component), component, null) + }; + } + + private static string GetLabelText (Component component) => component switch { + Component.Hue => Translations.GetString ("Hue"), + // Translators: this is an abbreviation for "Saturation" + Component.Saturation => Translations.GetString ("Sat"), + Component.Value => Translations.GetString ("Value"), + Component.Red => Translations.GetString ("Red"), + Component.Green => Translations.GetString ("Green"), + Component.Blue => Translations.GetString ("Blue"), + Component.Alpha => Translations.GetString ("Alpha"), + _ => throw new ArgumentOutOfRangeException (nameof (component), component, null) + }; }