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)
+ };
}