From fcb7d254f9d0f147dd85c38200a3ebd896c65b3c Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 20 May 2022 00:01:21 -0400 Subject: [PATCH 01/58] Add IColorPalette --- .../ColorPalette/IColorPalette.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs new file mode 100644 index 00000000000..7c6ebc3f6a3 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs @@ -0,0 +1,38 @@ +using Avalonia.Media; + +namespace Avalonia.Controls +{ + /// + /// Interface to define a color palette. + /// + public interface IColorPalette + { + /// + /// Gets the total number of colors in this palette. + /// A color is not necessarily a single value and may be composed of several shades. + /// + /// + /// Represents total columns in a table. + /// + int ColorCount { get; } + + /// + /// Gets the total number of shades for each color in this palette. + /// Shades are usually a variation of the color lightening or darkening it. + /// + /// + /// Represents total rows in a table. + /// + int ShadeCount { get; } + + /// + /// Gets a color in the palette by index. + /// + /// The index of the color in the palette. + /// The index must be between zero and . + /// The index of the color shade in the palette. + /// The index must be between zero and . + /// The color at the specified index or an exception. + Color GetColor(int colorIndex, int shadeIndex); + } +} From fa5a47b4426d32a00b01233540e25342f2013646 Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 20 May 2022 00:01:44 -0400 Subject: [PATCH 02/58] Add initial ColorView.Properties --- .../ColorSpectrum/ColorSpectrum.Properties.cs | 2 +- .../ColorView/ColorView.Properties.cs | 362 ++++++++++++++++++ .../ColorView/ColorView.cs | 21 + 3 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs create mode 100644 src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs index 587a89ee38a..00d84f5dd3e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs @@ -97,7 +97,7 @@ public partial class ColorSpectrum /// Gets or sets the currently selected color in the RGB color model. /// /// - /// For control authors use instead to avoid loss + /// For control authors, use instead to avoid loss /// of precision and color drifting. /// public Color Color diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs new file mode 100644 index 00000000000..aa5dfb5fc4d --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -0,0 +1,362 @@ +using System.Collections.ObjectModel; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Media; + +namespace Avalonia.Controls +{ + public partial class ColorView + { + // SelectedColorModel ActiveColorModel? + // SelectedTab + + /// + /// Defines the property. + /// + public static readonly StyledProperty ColorProperty = + AvaloniaProperty.Register( + nameof(Color), + Colors.White, + defaultBindingMode: BindingMode.TwoWay); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ColorSpectrumComponentsProperty = + AvaloniaProperty.Register( + nameof(ColorSpectrumComponents), + ColorSpectrumComponents.HueSaturation); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ColorSpectrumShapeProperty = + AvaloniaProperty.Register( + nameof(ColorSpectrumShape), + ColorSpectrumShape.Box); + + /// + /// Defines the property. + /// + public static readonly DirectProperty> CustomPaletteColorsProperty = + AvaloniaProperty.RegisterDirect>( + nameof(CustomPaletteColors), + o => o.CustomPaletteColors); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CustomPaletteColumnCountProperty = + AvaloniaProperty.Register( + nameof(CustomPaletteColumnCount), + 4); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CustomPaletteProperty = + AvaloniaProperty.Register( + nameof(CustomPalette), + null); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HsvColorProperty = + AvaloniaProperty.Register( + nameof(HsvColor), + Colors.White.ToHsv(), + defaultBindingMode: BindingMode.TwoWay); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAlphaEnabledProperty = + AvaloniaProperty.Register( + nameof(IsAlphaEnabled), + false); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAlphaSliderVisibleProperty = + AvaloniaProperty.Register( + nameof(IsAlphaSliderVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAlphaTextInputVisibleProperty = + AvaloniaProperty.Register( + nameof(IsAlphaTextInputVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsColorChannelTextInputVisibleProperty = + AvaloniaProperty.Register( + nameof(IsColorChannelTextInputVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsColorPaletteVisibleProperty = + AvaloniaProperty.Register( + nameof(IsColorPaletteVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsColorPreviewVisibleProperty = + AvaloniaProperty.Register( + nameof(IsColorPreviewVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsColorSliderVisibleProperty = + AvaloniaProperty.Register( + nameof(IsColorSliderVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsColorSpectrumVisibleProperty = + AvaloniaProperty.Register( + nameof(IsColorSpectrumVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHexInputVisibleProperty = + AvaloniaProperty.Register( + nameof(IsHexInputVisible), + true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MaxHueProperty = + AvaloniaProperty.Register( + nameof(MaxHue), + 359); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MaxSaturationProperty = + AvaloniaProperty.Register( + nameof(MaxSaturation), + 100); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MaxValueProperty = + AvaloniaProperty.Register( + nameof(MaxValue), + 100); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MinHueProperty = + AvaloniaProperty.Register( + nameof(MinHue), + 0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MinSaturationProperty = + AvaloniaProperty.Register( + nameof(MinSaturation), + 0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MinValueProperty = + AvaloniaProperty.Register( + nameof(MinValue), + 0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ShowAccentColorsProperty = + AvaloniaProperty.Register( + nameof(ShowAccentColors), + true); + + /// + public Color Color + { + get => GetValue(ColorProperty); + set => SetValue(ColorProperty, value); + } + + /// + public ColorSpectrumComponents ColorSpectrumComponents + { + get => GetValue(ColorSpectrumComponentsProperty); + set => SetValue(ColorSpectrumComponentsProperty, value); + } + + /// + public ColorSpectrumShape ColorSpectrumShape + { + get => GetValue(ColorSpectrumShapeProperty); + set => SetValue(ColorSpectrumShapeProperty, value); + } + + /// + /// Gets the list of custom palette colors. + /// + public ObservableCollection CustomPaletteColors + { + get => _customPaletteColors; + } + + /// + /// Gets or sets the number of colors in each row (section) of the custom color palette. + /// Within a standard palette, rows are shades and columns are colors. + /// + public int CustomPaletteColumnCount + { + get => GetValue(CustomPaletteColumnCountProperty); + set => SetValue(CustomPaletteColumnCountProperty, value); + } + + /// + /// Gets or sets the custom color palette. + /// This will automatically set and + /// overwriting any existing values. + /// + public IColorPalette? CustomPalette + { + get => GetValue(CustomPaletteProperty); + set => SetValue(CustomPaletteProperty, value); + } + + /// + public HsvColor HsvColor + { + get => GetValue(HsvColorProperty); + set => SetValue(HsvColorProperty, value); + } + + public bool IsAlphaEnabled + { + get => GetValue(IsAlphaEnabledProperty); + set => SetValue(IsAlphaEnabledProperty, value); + } + + public bool IsAlphaSliderVisible + { + get => GetValue(IsAlphaSliderVisibleProperty); + set => SetValue(IsAlphaSliderVisibleProperty, value); + } + + public bool IsAlphaTextInputVisible + { + get => GetValue(IsAlphaTextInputVisibleProperty); + set => SetValue(IsAlphaTextInputVisibleProperty, value); + } + + public bool IsColorChannelTextInputVisible // TODO: Component + { + get => GetValue(IsColorChannelTextInputVisibleProperty); + set => SetValue(IsColorChannelTextInputVisibleProperty, value); + } + + /// + /// Gets or sets a value indicating whether the color palette is visible. + /// + public bool IsColorPaletteVisible + { + get => GetValue(IsColorPaletteVisibleProperty); + set => SetValue(IsColorPaletteVisibleProperty, value); + } + + public bool IsColorPreviewVisible + { + get => GetValue(IsColorPreviewVisibleProperty); + set => SetValue(IsColorPreviewVisibleProperty, value); + } + + public bool IsColorSliderVisible + { + get => GetValue(IsColorSliderVisibleProperty); + set => SetValue(IsColorSliderVisibleProperty, value); + } + + public bool IsColorSpectrumVisible + { + get => GetValue(IsColorSpectrumVisibleProperty); + set => SetValue(IsColorSpectrumVisibleProperty, value); + } + + public bool IsHexInputVisible + { + get => GetValue(IsHexInputVisibleProperty); + set => SetValue(IsHexInputVisibleProperty, value); + } + + /// + public int MaxHue + { + get => GetValue(MaxHueProperty); + set => SetValue(MaxHueProperty, value); + } + + /// + public int MaxSaturation + { + get => GetValue(MaxSaturationProperty); + set => SetValue(MaxSaturationProperty, value); + } + + /// + public int MaxValue + { + get => GetValue(MaxValueProperty); + set => SetValue(MaxValueProperty, value); + } + + /// + public int MinHue + { + get => GetValue(MinHueProperty); + set => SetValue(MinHueProperty, value); + } + + /// + public int MinSaturation + { + get => GetValue(MinSaturationProperty); + set => SetValue(MinSaturationProperty, value); + } + + /// + public int MinValue + { + get => GetValue(MinValueProperty); + set => SetValue(MinValueProperty, value); + } + + /// + public bool ShowAccentColors + { + get => GetValue(ShowAccentColorsProperty); + set => SetValue(ShowAccentColorsProperty, value); + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs new file mode 100644 index 00000000000..3aff4614a50 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Primitives; +using Avalonia.Media; + +namespace Avalonia.Controls +{ + /// + /// Presents a color for user editing using a spectrum, palette and component sliders. + /// + public partial class ColorView : TemplatedControl + { + private ObservableCollection _customPaletteColors = new ObservableCollection(); + + + } +} From d3bad7bd1d5612901ff0f3f74080ca5349091228 Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 20 May 2022 00:01:55 -0400 Subject: [PATCH 03/58] Add ColorViewTab enum --- .../ColorView/ColorViewTab.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs new file mode 100644 index 00000000000..d8c7d5163cd --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Controls +{ + /// + /// Defines a specific tab (subview) within the . + /// + public enum ColorViewTab + { + /// + /// The components view with sliders and numeric input boxes. + /// + Components, + + /// + /// The color palette view with a grid of colors and shades. + /// + Palette, + + /// + /// The color spectrum view with a box/ring spectrum and sliders. + /// + Spectrum, + } +} From 77e36914d4c5bb49e12c8a79bb7d5de30a71f19c Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 22 May 2022 12:06:58 -0400 Subject: [PATCH 04/58] Add FluentColorPalette --- .../ColorPalette/FluentColorPalette.cs | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs new file mode 100644 index 00000000000..89400280a9e --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs @@ -0,0 +1,142 @@ +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implements the standard Windows 10 color palette. + /// + public class FluentColorPalette : IColorPalette + { + // Values were taken from the Settings App, Personalization > Colors which match with + // https://docs.microsoft.com/en-us/windows/uwp/whats-new/windows-docs-december-2017 + // + // The default ordering and grouping of colors was undesirable so was modified. + // Colors were transposed: the colors in rows within the Settings app became columns here. + // This is because columns in an IColorPalette generally should contain different shades of + // the same color. In the settings app this concept is somewhat loosely reversed. + // The first 'column' ordering, after being transposed, was then reversed so 'red' colors + // were near to each other. + // + // This new ordering most closely follows the Windows standard while: + // + // 1. Keeping colors in a 'spectrum' order + // 2. Keeping like colors next to each both in rows and columns + // (which is unique for the windows palette). + // For example, similar red colors are next to each other in both + // rows within the same column and rows within the column next to it. + // This follows a 'snake-like' pattern as illustrated below. + // 3. A downside of this ordering is colors don't follow strict 'shades' + // as in other palettes. + // + // The colors will be displayed in the below pattern. + // This pattern follows a spectrum while keeping like-colors near to one + // another across both rows and columns. + // + // ┌Red───┐ ┌Blue──┐ ┌Gray──┐ + // │ │ │ │ │ | + // │ │ │ │ │ | + // Yellow └Violet┘ └Green─┘ Brown + + private static Color[,] colorChart = new Color[,] + { + { + // Ordering reversed for this section only + Color.FromArgb(255, 255, 67, 67), /* #FF4343 */ + Color.FromArgb(255, 209, 52, 56), /* #D13438 */ + Color.FromArgb(255, 239, 105, 80), /* #EF6950 */ + Color.FromArgb(255, 218, 59, 1), /* #DA3B01 */ + Color.FromArgb(255, 202, 80, 16), /* #CA5010 */ + Color.FromArgb(255, 247, 99, 12), /* #F7630C */ + Color.FromArgb(255, 255, 140, 0), /* #FF8C00 */ + Color.FromArgb(255, 255, 185, 0), /* #FFB900 */ + }, + { + Color.FromArgb(255, 231, 72, 86), /* #E74856 */ + Color.FromArgb(255, 232, 17, 35), /* #E81123 */ + Color.FromArgb(255, 234, 0, 94), /* #EA005E */ + Color.FromArgb(255, 195, 0, 82), /* #C30052 */ + Color.FromArgb(255, 227, 0, 140), /* #E3008C */ + Color.FromArgb(255, 191, 0, 119), /* #BF0077 */ + Color.FromArgb(255, 194, 57, 179), /* #C239B3 */ + Color.FromArgb(255, 154, 0, 137), /* #9A0089 */ + }, + { + Color.FromArgb(255, 0, 120, 215), /* #0078D7 */ + Color.FromArgb(255, 0, 99, 177), /* #0063B1 */ + Color.FromArgb(255, 142, 140, 216), /* #8E8CD8 */ + Color.FromArgb(255, 107, 105, 214), /* #6B69D6 */ + Color.FromArgb(255, 135, 100, 184), /* #8764B8 */ + Color.FromArgb(255, 116, 77, 169), /* #744DA9 */ + Color.FromArgb(255, 177, 70, 194), /* #B146C2 */ + Color.FromArgb(255, 136, 23, 152), /* #881798 */ + }, + { + Color.FromArgb(255, 0, 153, 188), /* #0099BC */ + Color.FromArgb(255, 45, 125, 154), /* #2D7D9A */ + Color.FromArgb(255, 0, 183, 195), /* #00B7C3 */ + Color.FromArgb(255, 3, 131, 135), /* #038387 */ + Color.FromArgb(255, 0, 178, 148), /* #00B294 */ + Color.FromArgb(255, 1, 133, 116), /* #018574 */ + Color.FromArgb(255, 0, 204, 106), /* #00CC6A */ + Color.FromArgb(255, 16, 137, 62), /* #10893E */ + }, + { + Color.FromArgb(255, 122, 117, 116), /* #7A7574 */ + Color.FromArgb(255, 93, 90, 80), /* #5D5A58 */ + Color.FromArgb(255, 104, 118, 138), /* #68768A */ + Color.FromArgb(255, 81, 92, 107), /* #515C6B */ + Color.FromArgb(255, 86, 124, 115), /* #567C73 */ + Color.FromArgb(255, 72, 104, 96), /* #486860 */ + Color.FromArgb(255, 73, 130, 5), /* #498205 */ + Color.FromArgb(255, 16, 124, 16), /* #107C10 */ + }, + { + Color.FromArgb(255, 118, 118, 118), /* #767676 */ + Color.FromArgb(255, 76, 74, 72), /* #4C4A48 */ + Color.FromArgb(255, 105, 121, 126), /* #69797E */ + Color.FromArgb(255, 74, 84, 89), /* #4A5459 */ + Color.FromArgb(255, 100, 124, 100), /* #647C64 */ + Color.FromArgb(255, 82, 94, 84), /* #525E54 */ + Color.FromArgb(255, 132, 117, 69), /* #847545 */ + Color.FromArgb(255, 126, 115, 95), /* #7E735F */ + } + }; + + /// + /// Gets the index of the default shade of colors in this palette. + /// This has little meaning in this palette as colors are not strictly separated by shade. + /// + public const int DefaultShadeIndex = 0; + + /// + /// Gets the total number of colors in this palette. + /// A color is not necessarily a single value and may be composed of several shades. + /// This has little meaning in this palette as colors are not strictly separated. + /// + /// + public int ColorCount + { + get => colorChart.GetLength(0); + } + + /// + /// Gets the total number of shades for each color in this palette. + /// Shades are usually a variation of the color lightening or darkening it. + /// This has little meaning in this palette as colors are not strictly separated by shade. + /// + /// + public int ShadeCount + { + get => colorChart.GetLength(1); + } + + /// + public Color GetColor(int colorIndex, int shadeIndex) + { + return colorChart[ + MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0)), + MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1))]; + } + } +} From 33637f651a3456f7791dbe01f3e16c79fd0725d6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 22 May 2022 12:07:09 -0400 Subject: [PATCH 05/58] Fix ColorToHexConverter --- .../Converters/ColorToHexConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs index 9b09073d9dc..8d5f2332be2 100644 --- a/src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs +++ b/src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs @@ -42,7 +42,7 @@ public class ColorToHexConverter : IValueConverter return AvaloniaProperty.UnsetValue; } - string hexColor = color.ToString(); + string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant(); if (includeSymbol == false) { From 2835767b42ddfc23d40e64c34ea0719312ab8586 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 22 May 2022 12:09:28 -0400 Subject: [PATCH 06/58] Add incomplete ColorView control template --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 9 +- .../ColorView/ColorView.Properties.cs | 4 +- .../ColorView/ColorView.cs | 86 ++++++ .../ColorView/ColorViewTab.cs | 6 +- .../Themes/Fluent/ColorView.xaml | 259 ++++++++++++++++++ .../Themes/Fluent/Fluent.xaml | 3 + 6 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index c0c83d6a351..47a407821cb 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -13,8 +13,11 @@ - - + + - diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index aa5dfb5fc4d..eddbeaf112f 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -292,7 +292,9 @@ public bool IsColorPreviewVisible set => SetValue(IsColorPreviewVisibleProperty, value); } - public bool IsColorSliderVisible + // IsColorComponentsVisible + + public bool IsColorSliderVisible // ColorSpectrumSlider { get => GetValue(IsColorSliderVisibleProperty); set => SetValue(IsColorSliderVisibleProperty, value); diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 3aff4614a50..76e3cfe3e1c 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -14,8 +14,94 @@ namespace Avalonia.Controls /// public partial class ColorView : TemplatedControl { + /// + /// Event for when the selected color changes within the slider. + /// + public event EventHandler? ColorChanged; + + private bool disableUpdates = false; + private ObservableCollection _customPaletteColors = new ObservableCollection(); + /// + /// Initializes a new instance of the class. + /// + public ColorView() : base() + { + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + this.CustomPalette = new FluentColorPalette(); + + base.OnApplyTemplate(e); + } + + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (disableUpdates) + { + base.OnPropertyChanged(change); + return; + } + + // Always keep the two color properties in sync + if (change.Property == ColorProperty) + { + disableUpdates = true; + + HsvColor = Color.ToHsv(); + + OnColorChanged(new ColorChangedEventArgs( + change.GetOldValue(), + change.GetNewValue())); + + disableUpdates = false; + } + else if (change.Property == HsvColorProperty) + { + disableUpdates = true; + + Color = HsvColor.ToRgb(); + + OnColorChanged(new ColorChangedEventArgs( + change.GetOldValue().ToRgb(), + change.GetNewValue().ToRgb())); + + disableUpdates = false; + } + else if (change.Property == CustomPaletteProperty) + { + IColorPalette? palette = CustomPalette; + + // Any custom palette change must be automatically synced with the + // bound properties controlling the palette grid + if (palette != null) + { + CustomPaletteColumnCount = palette.ColorCount; + CustomPaletteColors.Clear(); + + for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++) + { + for (int colorIndex = 0; colorIndex < palette.ColorCount; colorIndex++) + { + CustomPaletteColors.Add(palette.GetColor(colorIndex, shadeIndex)); + } + } + } + } + + base.OnPropertyChanged(change); + } + /// + /// Called before the event occurs. + /// + /// The defining old/new colors. + protected virtual void OnColorChanged(ColorChangedEventArgs e) + { + ColorChanged?.Invoke(this, e); + } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs index d8c7d5163cd..677cdb8674b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs @@ -6,17 +6,17 @@ public enum ColorViewTab { /// - /// The components view with sliders and numeric input boxes. + /// The components subview with sliders and numeric input boxes. /// Components, /// - /// The color palette view with a grid of colors and shades. + /// The color palette subview with a grid of selectable colors. /// Palette, /// - /// The color spectrum view with a box/ring spectrum and sliders. + /// The color spectrum subview with a box/ring spectrum and sliders. /// Spectrum, } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml new file mode 100644 index 00000000000..9f4324594d0 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index c25d79727fc..c55766e07cf 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -25,4 +25,7 @@ + + + From 197ab047a510c58b30a32216fb96c8cfd2239c3a Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 27 May 2022 22:45:56 -0400 Subject: [PATCH 07/58] Add braces --- src/Avalonia.Controls/Primitives/TemplatedControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index db029d38c0b..7c8c2f882f7 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -291,7 +291,9 @@ public sealed override void ApplyTemplate() // Existing code kinda expect to see a NameScope even if it's empty if (nameScope == null) + { nameScope = new NameScope(); + } var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); From 5ed084123540fae249cc1881a21e67dad42ad0c1 Mon Sep 17 00:00:00 2001 From: robloo Date: Fri, 27 May 2022 22:46:12 -0400 Subject: [PATCH 08/58] Fix index clamping --- .../ColorPalette/FluentColorPalette.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs index 89400280a9e..b6f9a244b14 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs @@ -135,8 +135,8 @@ public int ShadeCount public Color GetColor(int colorIndex, int shadeIndex) { return colorChart[ - MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0)), - MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1))]; + MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1), + MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)]; } } } From e5b18d0b9c263b65b9121a25e19b6103e21d1d07 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 01:25:59 -0400 Subject: [PATCH 09/58] Further work on ColorView --- .../Converters/ContrastBrushConverter.cs | 84 +++++++++ .../Themes/Fluent/ColorView.xaml | 165 +++++++++++++++++- 2 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs new file mode 100644 index 00000000000..574f23dfae7 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs @@ -0,0 +1,84 @@ +using System; +using System.Globalization; +using Avalonia.Controls.Converters; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace Avalonia.Controls.Primitives.Converters +{ + /// + /// Gets a , either black or white, depending on the luminance of the supplied color. + /// A default color supplied in the converter parameter may be returned if alpha is below the set threshold. + /// + public class ContrastBrushConverter : IValueConverter + { + private ToColorConverter toColorConverter = new ToColorConverter(); + + /// + /// Gets or sets the alpha channel threshold below which a default color is used instead of black/white. + /// + public byte AlphaThreshold { get; set; } = 128; + + /// + public object? Convert( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + Color comparisonColor; + Color? defaultColor = null; + + // Get the changing color to compare against + var convertedValue = toColorConverter.Convert(value, targetType, parameter, culture); + if (convertedValue is Color valueColor) + { + comparisonColor = valueColor; + } + else + { + // Invalid color value provided + return AvaloniaProperty.UnsetValue; + } + + // Get the default color when transparency is high + var convertedParameter = toColorConverter.Convert(parameter, targetType, parameter, culture); + if (convertedParameter is Color parameterColor) + { + defaultColor = parameterColor; + } + + if (comparisonColor.A < AlphaThreshold && + defaultColor.HasValue) + { + // If the transparency is less than the threshold, just use the default brush + // This can commonly be something like the TextControlForeground brush + return new SolidColorBrush(defaultColor.Value); + } + else + { + // Chose a white/black brush based on contrast to the base color + if (ColorHelper.GetRelativeLuminance(comparisonColor) <= 0.5) + { + // Dark color, return light for contrast + return new SolidColorBrush(Colors.White); + } + else + { + // Bright color, return dark for contrast + return new SolidColorBrush(Colors.Black); + } + } + } + + /// + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + return AvaloniaProperty.UnsetValue; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 9f4324594d0..5d3642a3b1a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -3,12 +3,15 @@ xmlns:converters="using:Avalonia.Controls.Converters" xmlns:primitives="using:Avalonia.Controls.Primitives" xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker" + xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib" x:CompileBindings="False"> + + + + + + + @@ -195,11 +230,11 @@ - + + + + + + + + + + + + + + + + From 53a08f126382f9a7eae4d5eda331efd1a7080bd5 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 12:04:26 -0400 Subject: [PATCH 10/58] Add new EnumToBooleanConverter --- .../Converters/EnumToBooleanConverter.cs | 57 +++++++++++++++++++ .../Converters/EnumValueEqualsConverter.cs | 12 +++- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Controls/Converters/EnumToBooleanConverter.cs diff --git a/src/Avalonia.Controls/Converters/EnumToBooleanConverter.cs b/src/Avalonia.Controls/Converters/EnumToBooleanConverter.cs new file mode 100644 index 00000000000..ba1c4cab3e5 --- /dev/null +++ b/src/Avalonia.Controls/Converters/EnumToBooleanConverter.cs @@ -0,0 +1,57 @@ +using System; +using System.Globalization; +using Avalonia.Data; +using Avalonia.Data.Converters; + +namespace Avalonia.Controls.Converters +{ + /// + /// Converter to convert an enum value to bool by comparing to the given parameter. + /// Both value and parameter must be of the same enum type. + /// + /// + /// This converter is useful to enable binding of radio buttons with a selected enum value. + /// + public class EnumToBooleanConverter : IValueConverter + { + /// + public object? Convert( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + if (value == null && + parameter == null) + { + return true; + } + else if (value == null || + parameter == null) + { + return false; + } + else + { + return value!.Equals(parameter); + } + } + + /// + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) + { + if (value is bool boolValue) + { + return boolValue ? parameter : BindingOperations.DoNothing; + } + else + { + return BindingOperations.DoNothing; + } + } + } +} diff --git a/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs b/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs index 1a33a82ca44..abd0fe1dfd6 100644 --- a/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs +++ b/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs @@ -10,7 +10,11 @@ namespace Avalonia.Controls.Converters public class EnumValueEqualsConverter : IValueConverter { /// - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + public object? Convert( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) { // Note: Unlike string comparisons, null/empty is not supported // Both 'value' and 'parameter' must exist and if both are missing they are not considered equal @@ -46,7 +50,11 @@ public class EnumValueEqualsConverter : IValueConverter } /// - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture) { throw new System.NotImplementedException(); } From 13b82a0d1d4f70eba999e6e29ae075aed3b6cd1b Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 12:08:16 -0400 Subject: [PATCH 11/58] Implement ColorModel switching in ColorView --- .../ColorSlider/ColorSlider.cs | 14 ++ .../ColorView/ColorView.Properties.cs | 20 +- .../Themes/Fluent/ColorView.xaml | 172 +++++++++++++----- 3 files changed, 163 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 3c38c6ed1b6..78a796e93a8 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -332,6 +332,20 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang disableUpdates = false; } + else if (change.Property == ColorModelProperty) + { + disableUpdates = true; + + if (IsAutoUpdatingEnabled) + { + SetColorToSliderValues(); + UpdateBackground(); + } + + UpdatePseudoClasses(); + + disableUpdates = false; + } else if (change.Property == HsvColorProperty) { disableUpdates = true; diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index eddbeaf112f..c56811b8a3d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -7,7 +7,6 @@ namespace Avalonia.Controls { public partial class ColorView { - // SelectedColorModel ActiveColorModel? // SelectedTab /// @@ -19,6 +18,14 @@ public partial class ColorView Colors.White, defaultBindingMode: BindingMode.TwoWay); + /// + /// Defines the property. + /// + public static readonly StyledProperty ColorModelProperty = + AvaloniaProperty.Register( + nameof(ColorModel), + ColorModel.Rgba); + /// /// Defines the property. /// @@ -203,6 +210,17 @@ public Color Color set => SetValue(ColorProperty, value); } + /// + /// + /// This property is only applicable to the components tab. + /// The spectrum tab must always be in HSV and the palette tab is pre-defined colors. + /// + public ColorModel ColorModel + { + get => GetValue(ColorModelProperty); + set => SetValue(ColorModelProperty, value); + } + /// public ColorSpectrumComponents ColorSpectrumComponents { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 5d3642a3b1a..b037d149560 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -1,5 +1,6 @@  + @@ -156,6 +158,11 @@ + + + + + + + + + + + + + + + + + + + + Grid.Column="2"> @@ -260,12 +333,17 @@ BorderThickness="1,1,0,1" CornerRadius="4,0,0,4" VerticalAlignment="Center"> - + + + + - + + + + - + + + + Date: Sat, 28 May 2022 12:08:28 -0400 Subject: [PATCH 12/58] Improve comments --- .../Converters/AccentColorConverter.cs | 4 +++- .../Converters/ContrastBrushConverter.cs | 3 +++ .../Converters/ThirdComponentConverter.cs | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs index 4d05222e315..2c8e09adc9a 100644 --- a/src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs +++ b/src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs @@ -7,8 +7,10 @@ namespace Avalonia.Controls.Primitives.Converters { /// /// Creates an accent color for a given base color value and step parameter. - /// This is a highly-specialized converter for the color picker. /// + /// + /// This is a highly-specialized converter for the color picker. + /// public class AccentColorConverter : IValueConverter { /// diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs index 574f23dfae7..8b66b1a4e56 100644 --- a/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs +++ b/src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs @@ -10,6 +10,9 @@ namespace Avalonia.Controls.Primitives.Converters /// Gets a , either black or white, depending on the luminance of the supplied color. /// A default color supplied in the converter parameter may be returned if alpha is below the set threshold. /// + /// + /// This is a highly-specialized converter for the color picker. + /// public class ContrastBrushConverter : IValueConverter { private ToColorConverter toColorConverter = new ToColorConverter(); diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs index 220a993f99d..11e33c74f0d 100644 --- a/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs +++ b/src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs @@ -7,8 +7,10 @@ namespace Avalonia.Controls.Primitives.Converters /// /// Gets the third corresponding with a given /// that represents the other two components. - /// This is a highly-specialized converter for the color picker. /// + /// + /// This is a highly-specialized converter for the color picker. + /// public class ThirdComponentConverter : IValueConverter { /// From 4a267b69d61dbe8d784a3c7046c3af652632b9c8 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 12:15:04 -0400 Subject: [PATCH 13/58] Set MaxHue of ColorSlider to the same value as ColorSpectrum --- .../ColorSlider/ColorSlider.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 78a796e93a8..4c7df0fda72 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -20,7 +20,15 @@ public partial class ColorSlider : Slider /// public event EventHandler? ColorChanged; - private const double MaxHue = 359.99999999999999999; // 17 decimal places + /// + /// Defines the maximum hue component value + /// (other components are always 0..100 or 0.255). + /// + /// + /// This should match the default property. + /// + private const double MaxHue = 359; + private bool disableUpdates = false; /// From 36f85325bc7242487f20eeb3fb2fe3142aa0872a Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 12:28:23 -0400 Subject: [PATCH 14/58] Remove EnumValueEqualsConverter replaced by EnumToBooleanConverter --- .../Themes/Default/ColorSpectrum.xaml | 15 ++--- .../Themes/Fluent/ColorSpectrum.xaml | 15 ++--- .../Converters/EnumValueEqualsConverter.cs | 62 ------------------- 3 files changed, 16 insertions(+), 76 deletions(-) delete mode 100644 src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml index 78e6da8aa34..891e040e9fa 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml @@ -1,10 +1,11 @@  - + @@ -24,26 +25,26 @@ IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml index ac8e2a9c061..779f228b975 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml @@ -1,10 +1,11 @@  - + @@ -24,26 +25,26 @@ IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> diff --git a/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs b/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs deleted file mode 100644 index abd0fe1dfd6..00000000000 --- a/src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Controls.Converters -{ - /// - /// Converter that checks if an enum value is equal to the given parameter enum value. - /// - public class EnumValueEqualsConverter : IValueConverter - { - /// - public object? Convert( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - // Note: Unlike string comparisons, null/empty is not supported - // Both 'value' and 'parameter' must exist and if both are missing they are not considered equal - if (value != null && - parameter != null) - { - Type type = value.GetType(); - - if (type.IsEnum) - { - var valueStr = value?.ToString(); - var paramStr = parameter?.ToString(); - - if (string.Equals(valueStr, paramStr, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - /* - // TODO: When .net Standard 2.0 is no longer supported the code can be changed to below - // This is a little more type safe - if (type.IsEnum && - Enum.TryParse(type, value?.ToString(), true, out object? valueEnum) && - Enum.TryParse(type, parameter?.ToString(), true, out object? paramEnum)) - { - return valueEnum == paramEnum; - } - */ - } - - return false; - } - - /// - public object? ConvertBack( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - throw new System.NotImplementedException(); - } - } -} From 043b56c7da836dc6e8976c91957ce82626ca5b26 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 13:46:40 -0400 Subject: [PATCH 15/58] Remove IsAutoUpdatingEnabled property from ColorSlider This was unused and is an unnecessary complexity --- .../ColorSlider/ColorSlider.Properties.cs | 21 ------------- .../ColorSlider/ColorSlider.cs | 31 ++++++------------- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs index 31bd296288f..b1be7947948 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs @@ -48,14 +48,6 @@ public partial class ColorSlider nameof(IsAlphaMaxForced), true); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsAutoUpdatingEnabledProperty = - AvaloniaProperty.Register( - nameof(IsAutoUpdatingEnabled), - true); - /// /// Defines the property. /// @@ -119,19 +111,6 @@ public bool IsAlphaMaxForced set => SetValue(IsAlphaMaxForcedProperty, value); } - /// - /// Gets or sets a value indicating whether automatic background and foreground updates will be - /// calculated when the set color changes. - /// - /// - /// This can be disabled for performance reasons when working with multiple sliders. - /// - public bool IsAutoUpdatingEnabled - { - get => GetValue(IsAutoUpdatingEnabledProperty); - set => SetValue(IsAutoUpdatingEnabledProperty, value); - } - /// /// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values /// when using the HSVA color model. Only component values other than will be changed. diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 4c7df0fda72..641516c474a 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -327,13 +327,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang HsvColor = Color.ToHsv(); - if (IsAutoUpdatingEnabled) - { - SetColorToSliderValues(); - UpdateBackground(); - } - + SetColorToSliderValues(); + UpdateBackground(); UpdatePseudoClasses(); + OnColorChanged(new ColorChangedEventArgs( change.GetOldValue(), change.GetNewValue())); @@ -344,12 +341,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { disableUpdates = true; - if (IsAutoUpdatingEnabled) - { - SetColorToSliderValues(); - UpdateBackground(); - } - + SetColorToSliderValues(); + UpdateBackground(); UpdatePseudoClasses(); disableUpdates = false; @@ -360,13 +353,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang Color = HsvColor.ToRgb(); - if (IsAutoUpdatingEnabled) - { - SetColorToSliderValues(); - UpdateBackground(); - } - + SetColorToSliderValues(); + UpdateBackground(); UpdatePseudoClasses(); + OnColorChanged(new ColorChangedEventArgs( change.GetOldValue().ToRgb(), change.GetNewValue().ToRgb())); @@ -375,10 +365,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang } else if (change.Property == BoundsProperty) { - if (IsAutoUpdatingEnabled) - { - UpdateBackground(); - } + UpdateBackground(); } else if (change.Property == ValueProperty || change.Property == MinimumProperty || From 8ef440ceda8237a746c409a3eaa9e230b820cdb9 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 14:24:00 -0400 Subject: [PATCH 16/58] Finish ColorView design/style --- .../Themes/Fluent/ColorView.xaml | 215 +++++++++++------- 1 file changed, 136 insertions(+), 79 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index b037d149560..09feeb38f9a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -14,35 +14,125 @@ + 48 + + + M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614 + 16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843 + 16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695 + 14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268 + 17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222 + 16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431 + 7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386 + 2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691 + 7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549 + 9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17 + 10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5 + 15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z + + + + M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582 + 5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642 + 7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12 + 7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75 + 13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579 + 15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5 + 11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25 + 14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75 + 13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972 + 2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925 + 2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983 + 11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404 + 5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332 + 11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579 + 11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774 + 8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379 + 13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744 + 8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093 + 18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842 + 13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426 + 10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624 + 14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305 + 16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556 + 9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642 + 13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541 + 9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648 + 10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179 + 11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39 + 4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473 + 10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272 + 5.28471 4.63649 6.29249 4.01905Z + + + + M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386 + 5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297 + 8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18 + 5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11 + 4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999 + 14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386 + 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5 + 17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 + 17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5 + 13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z + + - - + + + + RowDefinitions="Auto,*" + Margin="12"> - - + + + + SelectedItem="{Binding Color, ElementName=ColorSpectrum}" + UseLayoutRounding="False" + Margin="12"> @@ -281,17 +343,11 @@ @@ -497,7 +553,8 @@ + HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" + Margin="12,0,12,12" /> From 12a8ecb9239f315767448bc5d434b7652b7987f8 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 15:17:40 -0400 Subject: [PATCH 17/58] Implement the hex input TextBox in ColorView --- .../ColorView/ColorView.cs | 85 +++++++++++++++++-- .../Themes/Fluent/ColorView.xaml | 19 ++--- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 76e3cfe3e1c..0363d9c182e 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; +using Avalonia.Controls.Converters; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Media; @@ -12,6 +11,7 @@ namespace Avalonia.Controls /// /// Presents a color for user editing using a spectrum, palette and component sliders. /// + [TemplatePart("PART_HexTextBox", typeof(TextBox))] public partial class ColorView : TemplatedControl { /// @@ -19,20 +19,70 @@ public partial class ColorView : TemplatedControl /// public event EventHandler? ColorChanged; - private bool disableUpdates = false; + // XAML template parts + private TextBox? _hexTextBox; private ObservableCollection _customPaletteColors = new ObservableCollection(); + private ColorToHexConverter colorToHexConverter = new ColorToHexConverter(); + private bool disableUpdates = false; /// /// Initializes a new instance of the class. /// public ColorView() : base() { + this.CustomPalette = new FluentColorPalette(); + } + + /// + /// Gets the value of the hex TextBox and sets it as the current . + /// If invalid, the TextBox hex text will revert back to the last valid color. + /// + private void GetColorFromHexTextBox() + { + if (_hexTextBox != null) + { + var convertedColor = colorToHexConverter.ConvertBack(_hexTextBox.Text, typeof(Color), null, CultureInfo.CurrentCulture); + + if (convertedColor is Color color) + { + Color = color; + } + + // Re-apply the hex value + // This ensure the hex color value is always valid and formatted correctly + _hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string; + } + } + + /// + /// Sets the current to the hex TextBox. + /// + private void SetColorToHexTextBox() + { + if (_hexTextBox != null) + { + _hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string; + } } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - this.CustomPalette = new FluentColorPalette(); + if (_hexTextBox != null) + { + _hexTextBox.KeyDown -= HexTextBox_KeyDown; + _hexTextBox.LostFocus -= HexTextBox_LostFocus; + } + + _hexTextBox = e.NameScope.Find("PART_HexTextBox"); + SetColorToHexTextBox(); + + if (_hexTextBox != null) + { + _hexTextBox.KeyDown += HexTextBox_KeyDown; + _hexTextBox.LostFocus += HexTextBox_LostFocus; + } base.OnApplyTemplate(e); } @@ -52,6 +102,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang disableUpdates = true; HsvColor = Color.ToHsv(); + SetColorToHexTextBox(); OnColorChanged(new ColorChangedEventArgs( change.GetOldValue(), @@ -64,6 +115,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang disableUpdates = true; Color = HsvColor.ToRgb(); + SetColorToHexTextBox(); OnColorChanged(new ColorChangedEventArgs( change.GetOldValue().ToRgb(), @@ -103,5 +155,26 @@ protected virtual void OnColorChanged(ColorChangedEventArgs e) { ColorChanged?.Invoke(this, e); } + + /// + /// Event handler for when a key is pressed within the Hex RGB value TextBox. + /// This is used to trigger re-evaluation of the color based on the TextBox value. + /// + private void HexTextBox_KeyDown(object? sender, Input.KeyEventArgs e) + { + if (e.Key == Input.Key.Enter) + { + GetColorFromHexTextBox(); + } + } + + /// + /// Event handler for when the Hex RGB value TextBox looses focus. + /// This is used to trigger re-evaluation of the color based on the TextBox value. + /// + private void HexTextBox_LostFocus(object? sender, Interactivity.RoutedEventArgs e) + { + GetColorFromHexTextBox(); + } } } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 09feeb38f9a..9800b1b3a5f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -152,8 +152,7 @@ Grid.Row="0" Grid.RowSpan="2" Components="{TemplateBinding ColorSpectrumComponents}" - Color="{TemplateBinding Color}" - HsvColor="{TemplateBinding HsvColor}" + HsvColor="{Binding $parent[ColorView].HsvColor}" MinHue="{TemplateBinding MinHue}" MaxHue="{TemplateBinding MaxHue}" MinSaturation="{TemplateBinding MinSaturation}" @@ -188,7 +187,7 @@ @@ -369,12 +368,12 @@ HorizontalAlignment="Center" VerticalAlignment="Center" /> - + @@ -419,7 +418,7 @@ Orientation="Horizontal" ColorComponent="Component1" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" - HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" + HsvColor="{Binding $parent[ColorView].HsvColor}" Margin="12,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> @@ -462,7 +461,7 @@ Orientation="Horizontal" ColorComponent="Component2" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" - HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" + HsvColor="{Binding $parent[ColorView].HsvColor}" Margin="12,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> @@ -505,7 +504,7 @@ Orientation="Horizontal" ColorComponent="Component3" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" - HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" + HsvColor="{Binding $parent[ColorView].HsvColor}" Margin="12,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> @@ -543,7 +542,7 @@ Orientation="Horizontal" ColorComponent="Alpha" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" - HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" + HsvColor="{Binding $parent[ColorView].HsvColor}" Margin="12,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" /> @@ -553,7 +552,7 @@ From a5b3bb9cb6c80b9d83c38ea372c9a64cc663acb6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 15:58:48 -0400 Subject: [PATCH 18/58] Implement ColorSlider component value rounding --- .../ColorSlider/ColorSlider.Properties.cs | 21 +++++++++++ .../ColorSlider/ColorSlider.cs | 35 +++++++++++++++++-- .../Themes/Fluent/ColorView.xaml | 12 +++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs index b1be7947948..e2a34a7f909 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs @@ -48,6 +48,14 @@ public partial class ColorSlider nameof(IsAlphaMaxForced), true); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsRoundingEnabledProperty = + AvaloniaProperty.Register( + nameof(IsRoundingEnabled), + false); + /// /// Defines the property. /// @@ -111,6 +119,19 @@ public bool IsAlphaMaxForced set => SetValue(IsAlphaMaxForcedProperty, value); } + /// + /// Gets or sets a value indicating whether rounding of color component values is enabled. + /// + /// + /// This is applicable for the HSV color model only. The struct uses double + /// values while the struct uses byte. Only double types need rounding. + /// + public bool IsRoundingEnabled + { + get => GetValue(IsRoundingEnabledProperty); + set => SetValue(IsRoundingEnabledProperty, value); + } + /// /// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values /// when using the HSVA color model. Only component values other than will be changed. diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 641516c474a..957e5e7b775 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -115,6 +115,21 @@ private async void UpdateBackground() } } + /// + /// Rounds the component values of the given . + /// This is useful for user-display and to ensure a color matches user selection exactly. + /// + /// The to round component values for. + /// A new with rounded component values. + private HsvColor RoundComponentValues(HsvColor hsvColor) + { + return new HsvColor( + Math.Round(hsvColor.A, 2, MidpointRounding.AwayFromZero), + Math.Round(hsvColor.H, 0, MidpointRounding.AwayFromZero), + Math.Round(hsvColor.S, 2, MidpointRounding.AwayFromZero), + Math.Round(hsvColor.V, 2, MidpointRounding.AwayFromZero)); + } + /// /// Updates the slider property values by applying the current color. /// @@ -130,6 +145,11 @@ private void SetColorToSliderValues() if (ColorModel == ColorModel.Hsva) { + if (IsRoundingEnabled) + { + hsvColor = RoundComponentValues(hsvColor); + } + // Note: Components converted into a usable range for the user switch (component) { @@ -222,7 +242,7 @@ private void SetColorToSliderValues() } } - return (hsvColor.ToRgb(), hsvColor); + rgbColor = hsvColor.ToRgb(); } else { @@ -244,8 +264,15 @@ private void SetColorToSliderValues() break; } - return (rgbColor, rgbColor.ToHsv()); + hsvColor = rgbColor.ToHsv(); } + + if (IsRoundingEnabled) + { + hsvColor = RoundComponentValues(hsvColor); + } + + return (rgbColor, hsvColor); } /// @@ -363,6 +390,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang disableUpdates = false; } + else if (change.Property == IsRoundingEnabledProperty) + { + SetColorToSliderValues(); + } else if (change.Property == BoundsProperty) { UpdateBackground(); diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 9800b1b3a5f..1edc2768a5e 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -416,6 +416,9 @@ Grid.Column="2" Grid.Row="2" Orientation="Horizontal" + IsRoundingEnabled="True" + IsSnapToTickEnabled="True" + TickFrequency="1" ColorComponent="Component1" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" HsvColor="{Binding $parent[ColorView].HsvColor}" @@ -459,6 +462,9 @@ Grid.Column="2" Grid.Row="3" Orientation="Horizontal" + IsRoundingEnabled="True" + IsSnapToTickEnabled="True" + TickFrequency="1" ColorComponent="Component2" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" HsvColor="{Binding $parent[ColorView].HsvColor}" @@ -502,6 +508,9 @@ Grid.Column="2" Grid.Row="4" Orientation="Horizontal" + IsRoundingEnabled="True" + IsSnapToTickEnabled="True" + TickFrequency="1" ColorComponent="Component3" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" HsvColor="{Binding $parent[ColorView].HsvColor}" @@ -540,6 +549,9 @@ Grid.Column="2" Grid.Row="5" Orientation="Horizontal" + IsRoundingEnabled="True" + IsSnapToTickEnabled="True" + TickFrequency="1" ColorComponent="Alpha" ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" HsvColor="{Binding $parent[ColorView].HsvColor}" From 0824c874fc7c5bbc8b98f758e47f5e6194f7b595 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 28 May 2022 16:42:58 -0400 Subject: [PATCH 19/58] Add initial ColorPicker control --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 5 +- .../ColorPicker/ColorPicker.cs | 15 ++++ .../Themes/Fluent/ColorPicker.xaml | 71 +++++++++++++++++++ .../Themes/Fluent/Fluent.xaml | 1 + 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs create mode 100644 src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index 47a407821cb..eca52e796a4 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -13,7 +13,10 @@ - + + diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs new file mode 100644 index 00000000000..cb84c77d206 --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls +{ + /// + /// + /// + public class ColorPicker : ColorView + { + } +} diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml new file mode 100644 index 00000000000..3fa3ab5eadc --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index c55766e07cf..03e8238a441 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -26,6 +26,7 @@ + From 5cd0ea68053394bb85f7db96c65e7203448dd1cf Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 30 May 2022 20:39:17 -0400 Subject: [PATCH 20/58] Complete initial ColorPicker --- .../ColorPicker/ColorPicker.cs | 3 +- .../Themes/Fluent/ColorPicker.xaml | 31 +++++++++++++------ .../Themes/Fluent/ColorView.xaml | 16 +++++----- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index cb84c77d206..140a24d6a10 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -7,7 +7,8 @@ namespace Avalonia.Controls { /// - /// + /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down. + /// Editing is available when the drop down flyout is opened; otherwise, only the preview color is shown. /// public class ColorPicker : ColorView { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 3fa3ab5eadc..a4f52f111c3 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -2,10 +2,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Avalonia.Controls" xmlns:converters="using:Avalonia.Controls.Converters" - x:CompileBindings="True"> + x:CompileBindings="False"> + - + @@ -150,7 +150,8 @@ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" HorizontalAlignment="Center" VerticalAlignment="Stretch" - Margin="0,0,12,0" /> + Margin="0,0,12,0" + IsVisible="{TemplateBinding IsColorSpectrumSliderVisible}"/> - + @@ -254,7 +255,7 @@ - + @@ -590,7 +591,7 @@ From adfac7b69dbcdc79a676b04aaebe1db4d4d63b40 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 01:22:39 -0400 Subject: [PATCH 24/58] Implement tab selection validation and automatic width --- .../ColorView/ColorView.cs | 93 ++++++++++++++++++- .../Themes/Fluent/ColorView.xaml | 23 +++-- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 0363d9c182e..96de734cc70 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Media; +using Avalonia.Threading; namespace Avalonia.Controls { @@ -12,6 +13,7 @@ namespace Avalonia.Controls /// Presents a color for user editing using a spectrum, palette and component sliders. /// [TemplatePart("PART_HexTextBox", typeof(TextBox))] + [TemplatePart("PART_TabControl", typeof(TabControl))] public partial class ColorView : TemplatedControl { /// @@ -20,7 +22,8 @@ public partial class ColorView : TemplatedControl public event EventHandler? ColorChanged; // XAML template parts - private TextBox? _hexTextBox; + private TextBox? _hexTextBox; + private TabControl? _tabControl; private ObservableCollection _customPaletteColors = new ObservableCollection(); private ColorToHexConverter colorToHexConverter = new ColorToHexConverter(); @@ -31,7 +34,7 @@ public partial class ColorView : TemplatedControl /// public ColorView() : base() { - this.CustomPalette = new FluentColorPalette(); + CustomPalette = new FluentColorPalette(); } /// @@ -66,6 +69,77 @@ private void SetColorToHexTextBox() } } + /// + /// Validates the selected subview/tab taking into account the visibility of each subview/tab + /// as well as the current selection. + /// + private void ValidateSelectedTab() + { + if (_tabControl != null && + _tabControl.Items != null) + { + // Determine if any item is visible + bool isAnyItemVisible = false; + foreach (var item in _tabControl.Items) + { + if (item is Control control && + control.IsVisible) + { + isAnyItemVisible = true; + break; + } + } + + if (isAnyItemVisible) + { + object? selectedItem = null; + + if (_tabControl.SelectedItem == null && + _tabControl.ItemCount > 0) + { + // As a failsafe, forcefully select the first item + foreach (var item in _tabControl.Items) + { + selectedItem = item; + break; + } + } + else + { + selectedItem = _tabControl.SelectedItem; + } + + if (selectedItem is Control selectedControl && + selectedControl.IsVisible == false) + { + // Select the first visible item instead + foreach (var item in _tabControl.Items) + { + if (item is Control control && + control.IsVisible) + { + selectedItem = item; + break; + } + } + } + + _tabControl.SelectedItem = selectedItem; + _tabControl.IsVisible = true; + } + else + { + // Special case when all items are hidden + // If TabControl ever properly supports no selected item / + // all items hidden this can be removed + _tabControl.SelectedItem = null; + _tabControl.IsVisible = false; + } + } + + return; + } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -76,6 +150,8 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) } _hexTextBox = e.NameScope.Find("PART_HexTextBox"); + _tabControl = e.NameScope.Find("PART_TabControl"); + SetColorToHexTextBox(); if (_hexTextBox != null) @@ -85,6 +161,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) } base.OnApplyTemplate(e); + ValidateSelectedTab(); } /// @@ -143,6 +220,18 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang } } } + else if (change.Property == IsColorComponentsVisibleProperty || + change.Property == IsColorPaletteVisibleProperty || + change.Property == IsColorSpectrumVisibleProperty) + { + // When the property changed notification is received here the visibility + // of individual tab items has not yet been updated though the bindings. + // Therefore, the validation is delayed until after bindings update. + Dispatcher.UIThread.Post(() => + { + ValidateSelectedTab(); + }, DispatcherPriority.Background); + } base.OnPropertyChanged(change); } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index afc8682a66f..420e3b2ae98 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -109,22 +109,29 @@ BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderThickness="0,1,0,0" />--> - 0,0,0,0 - - + @@ -181,8 +188,7 @@ - + @@ -257,8 +263,7 @@ - + From 55de1523c42ae438ef8997c0a72cc89b338ff2db Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 20:20:43 -0400 Subject: [PATCH 25/58] Support ignoring property changes in derived Color controls --- .../ColorPicker/ColorPicker.cs | 9 ++++++++ .../ColorSlider/ColorSlider.cs | 22 +++++++++---------- .../ColorView/ColorView.cs | 12 +++++----- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index 140a24d6a10..d34a91d1bbb 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -12,5 +12,14 @@ namespace Avalonia.Controls /// public class ColorPicker : ColorView { + /// + /// Initializes a new instance of the class. + /// + public ColorPicker() : base() + { + // Completely ignore property changes here + // The ColorView in the control template is responsible to manage this + base.ignorePropertyChanged = true; + } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index 957e5e7b775..a9ba5a20fa7 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -29,7 +29,7 @@ public partial class ColorSlider : Slider /// private const double MaxHue = 359; - private bool disableUpdates = false; + protected bool ignorePropertyChanged = false; /// /// Initializes a new instance of the class. @@ -135,7 +135,7 @@ private HsvColor RoundComponentValues(HsvColor hsvColor) /// /// /// Warning: This will trigger property changed updates. - /// Consider using externally. + /// Consider using externally. /// private void SetColorToSliderValues() { @@ -341,7 +341,7 @@ private Color GetEquivalentBackgroundColor(Color rgbColor) /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (disableUpdates) + if (ignorePropertyChanged) { base.OnPropertyChanged(change); return; @@ -350,7 +350,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang // Always keep the two color properties in sync if (change.Property == ColorProperty) { - disableUpdates = true; + ignorePropertyChanged = true; HsvColor = Color.ToHsv(); @@ -362,21 +362,21 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.GetOldValue(), change.GetNewValue())); - disableUpdates = false; + ignorePropertyChanged = false; } else if (change.Property == ColorModelProperty) { - disableUpdates = true; + ignorePropertyChanged = true; SetColorToSliderValues(); UpdateBackground(); UpdatePseudoClasses(); - disableUpdates = false; + ignorePropertyChanged = false; } else if (change.Property == HsvColorProperty) { - disableUpdates = true; + ignorePropertyChanged = true; Color = HsvColor.ToRgb(); @@ -388,7 +388,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.GetOldValue().ToRgb(), change.GetNewValue().ToRgb())); - disableUpdates = false; + ignorePropertyChanged = false; } else if (change.Property == IsRoundingEnabledProperty) { @@ -402,7 +402,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.Property == MinimumProperty || change.Property == MaximumProperty) { - disableUpdates = true; + ignorePropertyChanged = true; Color oldColor = Color; (var color, var hsvColor) = GetColorFromSliderValues(); @@ -421,7 +421,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang UpdatePseudoClasses(); OnColorChanged(new ColorChangedEventArgs(oldColor, Color)); - disableUpdates = false; + ignorePropertyChanged = false; } base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 96de734cc70..c19daf5f402 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -27,7 +27,7 @@ public partial class ColorView : TemplatedControl private ObservableCollection _customPaletteColors = new ObservableCollection(); private ColorToHexConverter colorToHexConverter = new ColorToHexConverter(); - private bool disableUpdates = false; + protected bool ignorePropertyChanged = false; /// /// Initializes a new instance of the class. @@ -167,7 +167,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (disableUpdates) + if (ignorePropertyChanged) { base.OnPropertyChanged(change); return; @@ -176,7 +176,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang // Always keep the two color properties in sync if (change.Property == ColorProperty) { - disableUpdates = true; + ignorePropertyChanged = true; HsvColor = Color.ToHsv(); SetColorToHexTextBox(); @@ -185,11 +185,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.GetOldValue(), change.GetNewValue())); - disableUpdates = false; + ignorePropertyChanged = false; } else if (change.Property == HsvColorProperty) { - disableUpdates = true; + ignorePropertyChanged = true; Color = HsvColor.ToRgb(); SetColorToHexTextBox(); @@ -198,7 +198,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.GetOldValue().ToRgb(), change.GetNewValue().ToRgb())); - disableUpdates = false; + ignorePropertyChanged = false; } else if (change.Property == CustomPaletteProperty) { From 36225e9132719d209d727cd4a6f7ef42c5b7f054 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 20:21:06 -0400 Subject: [PATCH 26/58] Use more standard binding in ColorPicker This doesn't currently work for some reason --- .../Themes/Fluent/ColorPicker.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index b3424d2cc15..6e5228039dc 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -36,7 +36,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="1,1,0,1" /> - Date: Thu, 2 Jun 2022 20:21:16 -0400 Subject: [PATCH 27/58] Small improvements --- src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index c19daf5f402..d0ee5f9acd1 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -54,7 +54,7 @@ private void GetColorFromHexTextBox() // Re-apply the hex value // This ensure the hex color value is always valid and formatted correctly - _hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string; + SetColorToHexTextBox(); } } @@ -225,7 +225,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.Property == IsColorSpectrumVisibleProperty) { // When the property changed notification is received here the visibility - // of individual tab items has not yet been updated though the bindings. + // of individual tab items has not yet been updated through the bindings. // Therefore, the validation is delayed until after bindings update. Dispatcher.UIThread.Post(() => { From b8be7ba4cb59eb1c406d794d76a6ad74d1d21a3a Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 21:05:47 -0400 Subject: [PATCH 28/58] Implement SelectedTabIndex in ColorView --- .../ColorView/ColorView.Properties.cs | 18 +++++++++++++++++- .../ColorView/ColorView.cs | 16 +++++++++++++++- .../ColorView/ColorViewTab.cs | 15 +++++++++------ .../Themes/Fluent/ColorView.xaml | 3 ++- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index a3267d88f23..a4897c99a29 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -8,7 +8,6 @@ namespace Avalonia.Controls /// public partial class ColorView { - // SelectedTabIndex // IsColorModelSelectorVisible // IsComponentSliderVisible @@ -198,6 +197,14 @@ public partial class ColorView nameof(MinValue), 0); + /// + /// Defines the property. + /// + public static readonly StyledProperty SelectedTabIndexProperty = + AvaloniaProperty.Register( + nameof(SelectedTabIndex), + (int)ColorViewTab.Spectrum); + /// /// Defines the property. /// @@ -413,6 +420,15 @@ public int MinValue set => SetValue(MinValueProperty, value); } + /// + /// Gets or sets the index of the selected subview/tab. + /// + public int SelectedTabIndex + { + get => GetValue(SelectedTabIndexProperty); + set => SetValue(SelectedTabIndexProperty, value); + } + /// public bool ShowAccentColors { diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index d0ee5f9acd1..9809f1312b7 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -73,7 +73,10 @@ private void SetColorToHexTextBox() /// Validates the selected subview/tab taking into account the visibility of each subview/tab /// as well as the current selection. /// - private void ValidateSelectedTab() + /// + /// Derived controls may re-implement this based on their default style / control template. + /// + protected virtual void ValidateSelectedTab() { if (_tabControl != null && _tabControl.Items != null) @@ -135,6 +138,8 @@ private void ValidateSelectedTab() _tabControl.SelectedItem = null; _tabControl.IsVisible = false; } + + SelectedTabIndex = _tabControl.SelectedIndex; } return; @@ -232,6 +237,15 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang ValidateSelectedTab(); }, DispatcherPriority.Background); } + else if (change.Property == SelectedTabIndexProperty) + { + // Again, it is necessary to wait for the SelectedTabIndex value to + // be applied to the TabControl through binding before validation occurs. + Dispatcher.UIThread.Post(() => + { + ValidateSelectedTab(); + }, DispatcherPriority.Background); + } base.OnPropertyChanged(change); } diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs index 534e8336313..8e3433d8193 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs @@ -1,23 +1,26 @@ namespace Avalonia.Controls { /// - /// Defines a specific subview (tab) within the . + /// Defines a specific subview/tab within the . /// + /// + /// This is indexed to match the default control template ordering. + /// public enum ColorViewTab { /// - /// The components subview with sliders and numeric input boxes. + /// The color spectrum subview with a box/ring spectrum and sliders. /// - Components, + Spectrum = 0, /// /// The color palette subview with a grid of selectable colors. /// - Palette, + Palette = 1, /// - /// The color spectrum subview with a box/ring spectrum and sliders. + /// The components subview with sliders and numeric input boxes. /// - Spectrum, + Components = 2, } } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 420e3b2ae98..d6bf83ff445 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -112,7 +112,8 @@ + Width="350" + SelectedIndex="{Binding SelectedTabIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"> 0,0,0,0 From 10bc7b38cbb4795ef4d7a2ad8a32e13e4194d125 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 22:20:44 -0400 Subject: [PATCH 29/58] Rename 'EnumToBooleanConverter' to 'EnumToBoolConverter' Bool is shorter and is also slightly more commonly used in other converters. Bool matches the type as written in C# as well. --- .../Themes/Default/ColorSpectrum.xaml | 14 +++++++------- .../Themes/Fluent/ColorSpectrum.xaml | 14 +++++++------- .../Themes/Fluent/ColorView.xaml | 18 +++++++++--------- ...leanConverter.cs => EnumToBoolConverter.cs} | 13 ++++++------- 4 files changed, 29 insertions(+), 30 deletions(-) rename src/Avalonia.Controls/Converters/{EnumToBooleanConverter.cs => EnumToBoolConverter.cs} (82%) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml index 891e040e9fa..c29f8f51e56 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml @@ -5,7 +5,7 @@ x:CompileBindings="True"> - + @@ -25,26 +25,26 @@ IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml index 779f228b975..6dd7ddd3739 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml @@ -5,7 +5,7 @@ x:CompileBindings="True"> - + @@ -25,26 +25,26 @@ IsHitTestVisible="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> + IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index d6bf83ff445..4b01de15a0f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -12,7 +12,7 @@ - + 48 @@ -351,12 +351,12 @@ Grid.Column="0" Content="RGB" CornerRadius="4,0,0,4" - IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolean}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=TwoWay}" /> + IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=TwoWay}" /> + IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Hsva}, Mode=TwoWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=OneWay}"/> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Hsva}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Hsva}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Hsva}, Mode=OneWay}" /> /// This converter is useful to enable binding of radio buttons with a selected enum value. /// - public class EnumToBooleanConverter : IValueConverter + public class EnumToBoolConverter : IValueConverter { /// public object? Convert( @@ -44,14 +44,13 @@ public class EnumToBooleanConverter : IValueConverter object? parameter, CultureInfo culture) { - if (value is bool boolValue) + if (value is bool boolValue && + boolValue == true) { - return boolValue ? parameter : BindingOperations.DoNothing; - } - else - { - return BindingOperations.DoNothing; + return parameter; } + + return BindingOperations.DoNothing; } } } From e0c936dbb4e2d48bc3367e0f13e3e2ac1d15002e Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 22:37:54 -0400 Subject: [PATCH 30/58] Add FlatColorPalette --- .../ColorPalette/FlatColorPalette.cs | 284 ++++++++++++++++++ .../ColorPalette/FluentColorPalette.cs | 6 - 2 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs new file mode 100644 index 00000000000..130d7e0eddf --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs @@ -0,0 +1,284 @@ +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implements a reduced flat design or flat UI color palette. + /// + /// + /// See: + /// - https://htmlcolorcodes.com/color-chart/ + /// - https://htmlcolorcodes.com/color-chart/flat-design-color-chart/ + /// - http://designmodo.github.io/Flat-UI/ + /// + /// The GitHub project is licensed as MIT: https://github.com/designmodo/Flat-UI. + /// + /// + public class FlatColorPalette : IColorPalette + { + // The full Flat UI color chart has 10 rows and 20 columns + // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png + // This is a reduced palette for usability + private static Color[,] colorChart = new Color[,] + { + // Pomegranate + { + Color.FromArgb(0xFF, 0xF9, 0xEB, 0xEA), + Color.FromArgb(0xFF, 0xE6, 0xB0, 0xAA), + Color.FromArgb(0xFF, 0xCD, 0x61, 0x55), + Color.FromArgb(0xFF, 0xA9, 0x32, 0x26), + Color.FromArgb(0xFF, 0x7B, 0x24, 0x1C), + }, + + // Amethyst + { + Color.FromArgb(0xFF, 0xF5, 0xEE, 0xF8), + Color.FromArgb(0xFF, 0xD7, 0xBD, 0xE2), + Color.FromArgb(0xFF, 0xAF, 0x7A, 0xC5), + Color.FromArgb(0xFF, 0x88, 0x4E, 0xA0), + Color.FromArgb(0xFF, 0x63, 0x39, 0x74), + }, + + // Belize Hole + { + Color.FromArgb(0xFF, 0xEA, 0xF2, 0xF8), + Color.FromArgb(0xFF, 0xA9, 0xCC, 0xE3), + Color.FromArgb(0xFF, 0x54, 0x99, 0xC7), + Color.FromArgb(0xFF, 0x24, 0x71, 0xA3), + Color.FromArgb(0xFF, 0x1A, 0x52, 0x76), + }, + + // Turquoise + { + Color.FromArgb(0xFF, 0xE8, 0xF8, 0xF5), + Color.FromArgb(0xFF, 0xA3, 0xE4, 0xD7), + Color.FromArgb(0xFF, 0x48, 0xC9, 0xB0), + Color.FromArgb(0xFF, 0x17, 0xA5, 0x89), + Color.FromArgb(0xFF, 0x11, 0x78, 0x64), + }, + + // Nephritis + { + Color.FromArgb(0xFF, 0xE9, 0xF7, 0xEF), + Color.FromArgb(0xFF, 0xA9, 0xDF, 0xBF), + Color.FromArgb(0xFF, 0x52, 0xBE, 0x80), + Color.FromArgb(0xFF, 0x22, 0x99, 0x54), + Color.FromArgb(0xFF, 0x19, 0x6F, 0x3D), + }, + + // Sunflower + { + Color.FromArgb(0xFF, 0xFE, 0xF9, 0xE7), + Color.FromArgb(0xFF, 0xF9, 0xE7, 0x9F), + Color.FromArgb(0xFF, 0xF4, 0xD0, 0x3F), + Color.FromArgb(0xFF, 0xD4, 0xAC, 0x0D), + Color.FromArgb(0xFF, 0x9A, 0x7D, 0x0A), + }, + + // Carrot + { + Color.FromArgb(0xFF, 0xFD, 0xF2, 0xE9), + Color.FromArgb(0xFF, 0xF5, 0xCB, 0xA7), + Color.FromArgb(0xFF, 0xEB, 0x98, 0x4E), + Color.FromArgb(0xFF, 0xCA, 0x6F, 0x1E), + Color.FromArgb(0xFF, 0x93, 0x51, 0x16), + }, + + // Clouds + { + Color.FromArgb(0xFF, 0xFD, 0xFE, 0xFE), + Color.FromArgb(0xFF, 0xF7, 0xF9, 0xF9), + Color.FromArgb(0xFF, 0xF0, 0xF3, 0xF4), + Color.FromArgb(0xFF, 0xD0, 0xD3, 0xD4), + Color.FromArgb(0xFF, 0x97, 0x9A, 0x9A), + }, + + // Concrete + { + Color.FromArgb(0xFF, 0xF4, 0xF6, 0xF6), + Color.FromArgb(0xFF, 0xD5, 0xDB, 0xDB), + Color.FromArgb(0xFF, 0xAA, 0xB7, 0xB8), + Color.FromArgb(0xFF, 0x83, 0x91, 0x92), + Color.FromArgb(0xFF, 0x5F, 0x6A, 0x6A), + }, + + // Wet Asphalt + { + Color.FromArgb(0xFF, 0xEB, 0xED, 0xEF), + Color.FromArgb(0xFF, 0xAE, 0xB6, 0xBF), + Color.FromArgb(0xFF, 0x5D, 0x6D, 0x7E), + Color.FromArgb(0xFF, 0x2E, 0x40, 0x53), + Color.FromArgb(0xFF, 0x21, 0x2F, 0x3C), + }, + }; + + /// + /// Gets the index of the default shade of colors in this palette. + /// + public const int DefaultShadeIndex = 2; + + /// + /// The index in the color palette of the 'Pomegranate' color. + /// This index can correspond to multiple color shades. + /// + public const int PomegranateIndex = 0; + + /// + /// The index in the color palette of the 'Amethyst' color. + /// This index can correspond to multiple color shades. + /// + public const int AmethystIndex = 1; + + /// + /// The index in the color palette of the 'BelizeHole' color. + /// This index can correspond to multiple color shades. + /// + public const int BelizeHoleIndex = 2; + + /// + /// The index in the color palette of the 'Turquoise' color. + /// This index can correspond to multiple color shades. + /// + public const int TurquoiseIndex = 3; + + /// + /// The index in the color palette of the 'Nephritis' color. + /// This index can correspond to multiple color shades. + /// + public const int NephritisIndex = 4; + + /// + /// The index in the color palette of the 'Sunflower' color. + /// This index can correspond to multiple color shades. + /// + public const int SunflowerIndex = 5; + + /// + /// The index in the color palette of the 'Carrot' color. + /// This index can correspond to multiple color shades. + /// + public const int CarrotIndex = 6; + + /// + /// The index in the color palette of the 'Clouds' color. + /// This index can correspond to multiple color shades. + /// + public const int CloudsIndex = 7; + + /// + /// The index in the color palette of the 'Concrete' color. + /// This index can correspond to multiple color shades. + /// + public const int ConcreteIndex = 8; + + /// + /// The index in the color palette of the 'WetAsphalt' color. + /// This index can correspond to multiple color shades. + /// + public const int WetAsphaltIndex = 9; + + /// + public int ColorCount + { + // Table is transposed compared to the reference chart + get => colorChart.GetLength(0); + } + + /// + public int ShadeCount + { + // Table is transposed compared to the reference chart + get => colorChart.GetLength(1); + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFC0392B. + /// + public static Color Pomegranate + { + get => colorChart[PomegranateIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF9B59B6. + /// + public static Color Amethyst + { + get => colorChart[AmethystIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF2980B9. + /// + public static Color BelizeHole + { + get => colorChart[BelizeHoleIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF1ABC9C. + /// + public static Color Turquoise + { + get => colorChart[TurquoiseIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF27AE60. + /// + public static Color Nephritis + { + get => colorChart[NephritisIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFF1C40F. + /// + public static Color Sunflower + { + get => colorChart[SunflowerIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFE67E22. + /// + public static Color Carrot + { + get => colorChart[CarrotIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFECF0F1. + /// + public static Color Clouds + { + get => colorChart[CloudsIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF95A5A6. + /// + public static Color Concrete + { + get => colorChart[ConcreteIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF34495E. + /// + public static Color WetAsphalt + { + get => colorChart[WetAsphaltIndex, DefaultShadeIndex]; + } + + /// + public Color GetColor(int colorIndex, int shadeIndex) + { + // Table is transposed compared to the reference chart + return colorChart[ + MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1), + MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)]; + } + } +} diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs index b6f9a244b14..013e69ce20d 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs @@ -103,12 +103,6 @@ public class FluentColorPalette : IColorPalette } }; - /// - /// Gets the index of the default shade of colors in this palette. - /// This has little meaning in this palette as colors are not strictly separated by shade. - /// - public const int DefaultShadeIndex = 0; - /// /// Gets the total number of colors in this palette. /// A color is not necessarily a single value and may be composed of several shades. From fe2d51b111696298e35c3db629751006f8e5ceec Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 22:42:56 -0400 Subject: [PATCH 31/58] Add SixteenColorPalette --- .../ColorPalette/SixteenColorPalette.cs | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs new file mode 100644 index 00000000000..f3abfdfd7fb --- /dev/null +++ b/src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs @@ -0,0 +1,302 @@ +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Implements the standard sixteen color palette from the HTML 4.01 specification. + /// + /// + /// See https://en.wikipedia.org/wiki/Web_colors#HTML_color_names. + /// + public class SixteenColorPalette : IColorPalette + { + // The 16 standard colors from HTML and early Windows computers + // https://en.wikipedia.org/wiki/List_of_software_palettes + // https://en.wikipedia.org/wiki/Web_colors#HTML_color_names + private static Color[,] colorChart = new Color[,] + { + { + Colors.White, + Colors.Silver + }, + { + Colors.Gray, + Colors.Black + }, + { + Colors.Red, + Colors.Maroon + }, + { + Colors.Yellow, + Colors.Olive + }, + { + Colors.Lime, + Colors.Green + }, + { + Colors.Aqua, + Colors.Teal + }, + { + Colors.Blue, + Colors.Navy + }, + { + Colors.Fuchsia, + Colors.Purple + } + }; + + /// + /// Gets the index of the default shade of colors in this palette. + /// + public const int DefaultShadeIndex = 0; + + /// + /// The index in the color palette of the 'White' color. + /// This index can correspond to multiple color shades. + /// + public const int WhiteIndex = 0; + + /// + /// The index in the color palette of the 'Silver' color. + /// This index can correspond to multiple color shades. + /// + public const int SilverIndex = 1; + + /// + /// The index in the color palette of the 'Gray' color. + /// This index can correspond to multiple color shades. + /// + public const int GrayIndex = 2; + + /// + /// The index in the color palette of the 'Black' color. + /// This index can correspond to multiple color shades. + /// + public const int BlackIndex = 3; + + /// + /// The index in the color palette of the 'Red' color. + /// This index can correspond to multiple color shades. + /// + public const int RedIndex = 4; + + /// + /// The index in the color palette of the 'Maroon' color. + /// This index can correspond to multiple color shades. + /// + public const int MaroonIndex = 5; + + /// + /// The index in the color palette of the 'Yellow' color. + /// This index can correspond to multiple color shades. + /// + public const int YellowIndex = 6; + + /// + /// The index in the color palette of the 'Olive' color. + /// This index can correspond to multiple color shades. + /// + public const int OliveIndex = 7; + + /// + /// The index in the color palette of the 'Lime' color. + /// This index can correspond to multiple color shades. + /// + public const int LimeIndex = 8; + + /// + /// The index in the color palette of the 'Green' color. + /// This index can correspond to multiple color shades. + /// + public const int GreenIndex = 9; + + /// + /// The index in the color palette of the 'Aqua' color. + /// This index can correspond to multiple color shades. + /// + public const int AquaIndex = 10; + + /// + /// The index in the color palette of the 'Teal' color. + /// This index can correspond to multiple color shades. + /// + public const int TealIndex = 11; + + /// + /// The index in the color palette of the 'Blue' color. + /// This index can correspond to multiple color shades. + /// + public const int BlueIndex = 12; + + /// + /// The index in the color palette of the 'Navy' color. + /// This index can correspond to multiple color shades. + /// + public const int NavyIndex = 13; + + /// + /// The index in the color palette of the 'Fuchsia' color. + /// This index can correspond to multiple color shades. + /// + public const int FuchsiaIndex = 14; + + /// + /// The index in the color palette of the 'Purple' color. + /// This index can correspond to multiple color shades. + /// + public const int PurpleIndex = 15; + + /// + public int ColorCount + { + get => colorChart.GetLength(0); + } + + /// + public int ShadeCount + { + get => colorChart.GetLength(1); + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFFFFFFF. + /// + public static Color White + { + get => colorChart[WhiteIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFC0C0C0. + /// + public static Color Silver + { + get => colorChart[SilverIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF808080. + /// + public static Color Gray + { + get => colorChart[GrayIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF000000. + /// + public static Color Black + { + get => colorChart[BlackIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFFF0000. + /// + public static Color Red + { + get => colorChart[RedIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF800000. + /// + public static Color Maroon + { + get => colorChart[MaroonIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFFFFF00. + /// + public static Color Yellow + { + get => colorChart[YellowIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF808000. + /// + public static Color Olive + { + get => colorChart[OliveIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF00FF00. + /// + public static Color Lime + { + get => colorChart[LimeIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF008000. + /// + public static Color Green + { + get => colorChart[GreenIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF00FFFF. + /// + public static Color Aqua + { + get => colorChart[AquaIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF008080. + /// + public static Color Teal + { + get => colorChart[TealIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF0000FF. + /// + public static Color Blue + { + get => colorChart[BlueIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF000080. + /// + public static Color Navy + { + get => colorChart[NavyIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FFFF00FF. + /// + public static Color Fuchsia + { + get => colorChart[FuchsiaIndex, DefaultShadeIndex]; + } + + /// + /// Gets the palette defined color that has an ARGB value of #FF800080. + /// + public static Color Purple + { + get => colorChart[PurpleIndex, DefaultShadeIndex]; + } + + /// + public Color GetColor(int colorIndex, int shadeIndex) + { + return colorChart[ + MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1), + MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)]; + } + } +} From 5e8f0fb70119e88335a555c80082311c9ef26664 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 23:00:08 -0400 Subject: [PATCH 32/58] Add comment explaining rounding value selection --- src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs index 32a898ee715..7dc340ea16f 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs @@ -64,6 +64,10 @@ public static string ToDisplayName(Color color) // It is also needlessly large as there are only ~140 known/named colors. // Therefore, rounding of the input color's component values is done to // reduce the color space into something more useful. + // + // The rounding value of 5 is specially chosen. + // It is a factor of 255 and therefore evenly divisible which improves + // the quality of the calculations. double rounding = 5; var roundedColor = new Color( 0xFF, From 4dcf13623d7e78698141f5f64742ec949e62afbb Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 23:02:18 -0400 Subject: [PATCH 33/58] Make ColorPalettes directory plural --- .../{ColorPalette => ColorPalettes}/FlatColorPalette.cs | 0 .../{ColorPalette => ColorPalettes}/FluentColorPalette.cs | 0 .../{ColorPalette => ColorPalettes}/IColorPalette.cs | 0 .../{ColorPalette => ColorPalettes}/SixteenColorPalette.cs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Controls.ColorPicker/{ColorPalette => ColorPalettes}/FlatColorPalette.cs (100%) rename src/Avalonia.Controls.ColorPicker/{ColorPalette => ColorPalettes}/FluentColorPalette.cs (100%) rename src/Avalonia.Controls.ColorPicker/{ColorPalette => ColorPalettes}/IColorPalette.cs (100%) rename src/Avalonia.Controls.ColorPicker/{ColorPalette => ColorPalettes}/SixteenColorPalette.cs (100%) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs similarity index 100% rename from src/Avalonia.Controls.ColorPicker/ColorPalette/FlatColorPalette.cs rename to src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FluentColorPalette.cs similarity index 100% rename from src/Avalonia.Controls.ColorPicker/ColorPalette/FluentColorPalette.cs rename to src/Avalonia.Controls.ColorPicker/ColorPalettes/FluentColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/IColorPalette.cs similarity index 100% rename from src/Avalonia.Controls.ColorPicker/ColorPalette/IColorPalette.cs rename to src/Avalonia.Controls.ColorPicker/ColorPalettes/IColorPalette.cs diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs similarity index 100% rename from src/Avalonia.Controls.ColorPicker/ColorPalette/SixteenColorPalette.cs rename to src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs From 63d1bdec3c3b05964b4b3f158d3dfde6ddbc4bcf Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 2 Jun 2022 23:08:24 -0400 Subject: [PATCH 34/58] Add resources to control accent color section size in ColorPreviewer --- .../Themes/Default/ColorPreviewer.xaml | 10 ++++++---- .../Themes/Fluent/ColorPreviewer.xaml | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index 15e5ca16554..ac1531fe7ab 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -9,6 +9,8 @@ + 80 + 40 - + - + - + + @@ -316,40 +316,40 @@ VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> - - + - + - + - + - + - From 58f9b443d9a40aff415d1021835794f003b5c025 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 27 Jun 2022 00:45:54 -0400 Subject: [PATCH 45/58] Implement IsAlphaEnabled using coercion --- .../ColorView/ColorView.Properties.cs | 16 +++-- .../ColorView/ColorView.cs | 66 +++++++++++++++++++ .../Themes/Fluent/ColorView.xaml | 18 ++++- 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index c7652833bf0..e1da427542b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -15,7 +15,8 @@ public partial class ColorView AvaloniaProperty.Register( nameof(Color), Colors.White, - defaultBindingMode: BindingMode.TwoWay); + defaultBindingMode: BindingMode.TwoWay, + coerce: CoerceColor) ; /// /// Defines the property. @@ -48,7 +49,8 @@ public partial class ColorView AvaloniaProperty.Register( nameof(HsvColor), Colors.White.ToHsv(), - defaultBindingMode: BindingMode.TwoWay); + defaultBindingMode: BindingMode.TwoWay, + coerce: CoerceHsvColor); /// /// Defines the property. @@ -267,7 +269,8 @@ public HsvColor HsvColor /// /// Gets or sets a value indicating whether the alpha component is enabled. - /// When disabled (set to false) the alpha component will be fixed to maximum. + /// When disabled (set to false) the alpha component will be fixed to maximum and + /// editing controls hidden. /// public bool IsAlphaEnabled { @@ -277,7 +280,8 @@ public bool IsAlphaEnabled /// /// Gets or sets a value indicating whether the alpha component editing controls - /// (both Slider and TextBox) are visible. + /// (Slider(s) and TextBox) are visible. When hidden, the existing alpha component + /// value is maintained. /// /// /// Note that also controls the alpha @@ -353,7 +357,7 @@ public bool IsColorSpectrumSliderVisible /// /// /// All color components are controlled by this property but alpha can also be - /// controlled with . + /// controlled with and . /// public bool IsComponentSliderVisible { @@ -366,7 +370,7 @@ public bool IsComponentSliderVisible /// /// /// All color components are controlled by this property but alpha can also be - /// controlled with . + /// controlled with and . /// public bool IsComponentTextInputVisible { diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 4f95b4acfe8..8fb80980d5c 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -237,6 +237,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang PaletteColors = newPaletteColors; } } + else if (change.Property == IsAlphaEnabledProperty) + { + // Manually coerce the HsvColor value + // (Color will be coerced automatically if HsvColor changes) + HsvColor = OnCoerceHsvColor(HsvColor); + } else if (change.Property == IsColorComponentsVisibleProperty || change.Property == IsColorPaletteVisibleProperty || change.Property == IsColorSpectrumVisibleProperty) @@ -271,6 +277,66 @@ protected virtual void OnColorChanged(ColorChangedEventArgs e) ColorChanged?.Invoke(this, e); } + /// + /// Called when the property has to be coerced. + /// + /// The value to coerce. + protected virtual Color OnCoerceColor(Color value) + { + if (IsAlphaEnabled == false) + { + return new Color(255, value.R, value.G, value.B); + } + + return value; + } + + /// + /// Called when the property has to be coerced. + /// + /// The value to coerce. + protected virtual HsvColor OnCoerceHsvColor(HsvColor value) + { + if (IsAlphaEnabled == false) + { + return new HsvColor(1.0, value.H, value.S, value.V); + } + + return value; + } + + /// + /// Coerces/validates the property value. + /// + /// The instance. + /// The value to coerce. + /// The coerced/validated value. + private static Color CoerceColor(IAvaloniaObject instance, Color value) + { + if (instance is ColorView colorView) + { + return colorView.OnCoerceColor(value); + } + + return value; + } + + /// + /// Coerces/validates the property value. + /// + /// The instance. + /// The value to coerce. + /// The coerced/validated value. + private static HsvColor CoerceHsvColor(IAvaloniaObject instance, HsvColor value) + { + if (instance is ColorView colorView) + { + return colorView.OnCoerceHsvColor(value); + } + + return value; + } + /// /// Event handler for when a key is pressed within the Hex RGB value TextBox. /// This is used to trigger re-evaluation of the color based on the TextBox value. diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index af2c8e061fe..97760ba94a8 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -187,8 +187,16 @@ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" HorizontalAlignment="Center" VerticalAlignment="Stretch" - Margin="12,0,0,0" - IsVisible="{TemplateBinding IsAlphaVisible}" /> + Margin="12,0,0,0"> + + + + + + + @@ -565,6 +573,8 @@ VerticalAlignment="Center" /> + + + Date: Mon, 27 Jun 2022 00:49:59 -0400 Subject: [PATCH 46/58] Only set slider IsVisible once --- src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 97760ba94a8..d43bc7e0045 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -618,8 +618,7 @@ ColorModel="{TemplateBinding ColorModel, Mode=OneWay}" HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" HorizontalAlignment="Stretch" - VerticalAlignment="Center" - IsVisible="{TemplateBinding IsAlphaVisible}"> + VerticalAlignment="Center"> Date: Mon, 27 Jun 2022 00:53:49 -0400 Subject: [PATCH 47/58] Only disable alpha editing controls when IsAlphaEnabled is false Rely on IsAlphaVisible for visibility --- .../ColorView/ColorView.Properties.cs | 6 +++--- .../Themes/Fluent/ColorView.xaml | 20 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index e1da427542b..d59c4d544a8 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -270,7 +270,7 @@ public HsvColor HsvColor /// /// Gets or sets a value indicating whether the alpha component is enabled. /// When disabled (set to false) the alpha component will be fixed to maximum and - /// editing controls hidden. + /// editing controls disabled. /// public bool IsAlphaEnabled { @@ -357,7 +357,7 @@ public bool IsColorSpectrumSliderVisible /// /// /// All color components are controlled by this property but alpha can also be - /// controlled with and . + /// controlled with . /// public bool IsComponentSliderVisible { @@ -370,7 +370,7 @@ public bool IsComponentSliderVisible /// /// /// All color components are controlled by this property but alpha can also be - /// controlled with and . + /// controlled with . /// public bool IsComponentTextInputVisible { diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index d43bc7e0045..563f230cc96 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -187,11 +187,10 @@ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}" HorizontalAlignment="Center" VerticalAlignment="Stretch" - Margin="12,0,0,0"> + Margin="12,0,0,0" + IsEnabled="{TemplateBinding IsAlphaEnabled}"> - @@ -564,7 +563,8 @@ BorderBrush="{DynamicResource TextControlBorderBrush}" BorderThickness="1,1,0,1" CornerRadius="4,0,0,4" - VerticalAlignment="Center"> + VerticalAlignment="Center" + IsEnabled="{TemplateBinding IsAlphaEnabled}"> - + Value="{Binding Value, ElementName=AlphaComponentSlider}" + IsEnabled="{TemplateBinding IsAlphaEnabled}"> - + VerticalAlignment="Center" + IsEnabled="{TemplateBinding IsAlphaEnabled}"> - Date: Mon, 27 Jun 2022 00:56:56 -0400 Subject: [PATCH 48/58] Remove unused ValueConverterGroup --- .../Converters/ValueConverterGroup.cs | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs diff --git a/src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs b/src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs deleted file mode 100644 index 2710c220f41..00000000000 --- a/src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace Avalonia.Controls.Primitives.Converters -{ - /// - /// Converter to chain together multiple converters. - /// - public class ValueConverterGroup : List, IValueConverter - { - /// - /// - public object? Convert( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - object? curValue; - - curValue = value; - for (int i = 0; i < Count; i++) - { - curValue = this[i].Convert(curValue, targetType, parameter, culture); - } - - return curValue; - } - - /// - public object? ConvertBack( - object? value, - Type targetType, - object? parameter, - CultureInfo culture) - { - object? curValue; - - curValue = value; - for (int i = (Count - 1); i >= 0; i--) - { - curValue = this[i].ConvertBack(curValue, targetType, parameter, culture); - } - - return curValue; - } - } -} From 6370ae38de78ab9c4bafa343e8870ee6d531b49c Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 5 Jul 2022 21:58:26 -0400 Subject: [PATCH 49/58] Switch to TemplateBinding in ColorPicker where possible --- .../Themes/Fluent/ColorPicker.xaml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 878d7819ebe..46d627b3a1d 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -49,31 +49,31 @@ + Color="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + ColorModel="{Binding ColorModel, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + ColorSpectrumComponents="{TemplateBinding ColorSpectrumComponents}" + ColorSpectrumShape="{TemplateBinding ColorSpectrumShape}" + HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + IsAlphaEnabled="{TemplateBinding IsAlphaEnabled}" + IsAlphaVisible="{TemplateBinding IsAlphaVisible}" + IsColorComponentsVisible="{TemplateBinding IsColorComponentsVisible}" + IsColorPaletteVisible="{TemplateBinding IsColorPaletteVisible}" + IsColorPreviewVisible="{TemplateBinding IsColorPreviewVisible}" + IsColorSpectrumVisible="{TemplateBinding IsColorSpectrumVisible}" + IsColorSpectrumSliderVisible="{TemplateBinding IsColorSpectrumSliderVisible}" + IsComponentTextInputVisible="{TemplateBinding IsComponentTextInputVisible}" + IsHexInputVisible="{TemplateBinding IsHexInputVisible}" + MaxHue="{TemplateBinding MaxHue}" + MaxSaturation="{TemplateBinding MaxSaturation}" + MaxValue="{TemplateBinding MaxValue}" + MinHue="{TemplateBinding MinHue}" + MinSaturation="{TemplateBinding MinSaturation}" + MinValue="{TemplateBinding MinValue}" + PaletteColors="{TemplateBinding PaletteColors}" + PaletteColumnCount="{TemplateBinding PaletteColumnCount}" + Palette="{TemplateBinding Palette}" + SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + ShowAccentColors="{TemplateBinding ShowAccentColors}" /> From a24720816f593e24bda2dd58ff6b6a56cdb23fd1 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 5 Jul 2022 22:12:18 -0400 Subject: [PATCH 50/58] Standardize ColorControl resource names This follows the Fluent standard where the resource is prefixed with the control itself --- .../Themes/Default/ColorPreviewer.xaml | 18 +++++------ .../Themes/Default/ColorSlider.xaml | 4 +-- .../Themes/Default/Default.xaml | 2 +- .../Themes/Fluent/ColorPicker.xaml | 2 +- .../Themes/Fluent/ColorPreviewer.xaml | 18 +++++------ .../Themes/Fluent/ColorSlider.xaml | 4 +-- .../Themes/Fluent/ColorView.xaml | 30 +++++++++---------- .../Themes/Fluent/Fluent.xaml | 2 +- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index 24242fc251b..6ac09824081 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -9,8 +9,8 @@ - 80 - 40 + 80 + 40 + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index 867e0f223f9..9e795d81a2c 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -13,6 +13,8 @@ + + 48 30 @@ -90,29 +92,26 @@ - - + + - - - + CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource BottomCornerRadiusFilterConverter}}" + Background="Transparent" + BorderBrush="Transparent" + BorderThickness="0,1,0,0" /> Date: Tue, 5 Jul 2022 23:32:35 -0400 Subject: [PATCH 52/58] Add code to hide the tab strip when only one tab is visible --- .../ColorView/ColorView.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index 8fb80980d5c..bea982b7ea7 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -4,9 +4,11 @@ using System.Globalization; using Avalonia.Controls.Converters; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Media; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -82,19 +84,19 @@ protected virtual void ValidateSelection() if (_tabControl != null && _tabControl.Items != null) { - // Determine if any item is visible - bool isAnyItemVisible = false; + // Determine the number of visible tab items + int numVisibleItems = 0; foreach (var item in _tabControl.Items) { if (item is Control control && control.IsVisible) { - isAnyItemVisible = true; - break; + numVisibleItems++; } } - if (isAnyItemVisible) + // Verify the selection + if (numVisibleItems > 0) { object? selectedItem = null; @@ -140,6 +142,23 @@ protected virtual void ValidateSelection() _tabControl.IsVisible = false; } + // Hide the "tab strip" if there is only one tab + // This allows, for example, to view only the palette + /* + var itemsPresenter = _tabControl.FindDescendantOfType(); + if (itemsPresenter != null) + { + if (numVisibleItems == 1) + { + itemsPresenter.IsVisible = false; + } + else + { + itemsPresenter.IsVisible = true; + } + } + */ + // Note that if externally the SelectedIndex is set to 4 or something // outside the valid range, the TabControl will ignore it and replace it // with a valid SelectedIndex. This however is not propagated back through From 012d3763693d332e05c12e6ba6ae0e06cf3b72e4 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 7 Jul 2022 20:48:37 -0400 Subject: [PATCH 53/58] Standardize converters --- .../Themes/Default/ColorPreviewer.xaml | 16 +++----- .../Themes/Default/ColorSlider.xaml | 22 ++++------- .../Themes/Default/ColorSpectrum.xaml | 31 ++++++--------- .../Themes/Default/Default.xaml | 14 ++++++- .../Themes/Fluent/ColorPicker.xaml | 13 +++---- .../Themes/Fluent/ColorPreviewer.xaml | 16 +++----- .../Themes/Fluent/ColorSlider.xaml | 22 ++++------- .../Themes/Fluent/ColorSpectrum.xaml | 31 ++++++--------- .../Themes/Fluent/ColorView.xaml | 39 +++++++++---------- .../Themes/Fluent/Fluent.xaml | 14 ++++++- 10 files changed, 101 insertions(+), 117 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index 6ac09824081..ad07da2b160 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -1,14 +1,10 @@  - - - - + 80 40 @@ -36,11 +32,11 @@ Grid.Column="0" CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" Tag="-2" - Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-2'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-1'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='1'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='2'}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml index bf2f632fd88..729c5be313f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml @@ -1,13 +1,7 @@  - - - - - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml index 8d77f8d3c16..84a94486c83 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml @@ -1,14 +1,10 @@  - - - - + 80 40 @@ -36,11 +32,11 @@ Grid.Column="0" CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" Tag="-2" - Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-2'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-1'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='1'}" /> + Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='2'}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml index bb6e026f9ee..594a2c694fe 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml @@ -1,13 +1,7 @@  - - - - - @@ -255,8 +252,8 @@ - @@ -363,12 +360,12 @@ Grid.Column="0" Content="RGB" CornerRadius="4,0,0,4" - IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBool}, ConverterParameter={x:Static local:ColorModel.Rgba}, Mode=TwoWay}" /> + IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" /> + IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}"/> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" /> + IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" /> + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converters="using:Avalonia.Controls.Converters"> + + + + + + + + + + + From 38bdb8a7b34051e4898aa9c0a1dc3a3fff25ae52 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 7 Jul 2022 20:53:21 -0400 Subject: [PATCH 54/58] Remove unused code --- .../ColorPicker/ColorPicker.cs | 8 +------- src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs index d34a91d1bbb..39369bcbdbf 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Controls +namespace Avalonia.Controls { /// /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down. diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs index bea982b7ea7..89f1afb1acd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs @@ -28,7 +28,6 @@ public partial class ColorView : TemplatedControl private TextBox? _hexTextBox; private TabControl? _tabControl; - private ObservableCollection _paletteColors = new ObservableCollection(); private ColorToHexConverter colorToHexConverter = new ColorToHexConverter(); protected bool ignorePropertyChanged = false; From 443977b85d1985dadf9f5248b4ca7f194503ab76 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 7 Jul 2022 21:10:02 -0400 Subject: [PATCH 55/58] Specify TargetType and DataType in most templates --- .../Themes/Default/ColorPreviewer.xaml | 2 +- .../Themes/Default/ColorSlider.xaml | 4 ++-- .../Themes/Default/ColorSpectrum.xaml | 2 +- .../Themes/Fluent/ColorPicker.xaml | 2 +- .../Themes/Fluent/ColorPreviewer.xaml | 2 +- .../Themes/Fluent/ColorSlider.xaml | 4 ++-- .../Themes/Fluent/ColorSpectrum.xaml | 2 +- .../Themes/Fluent/ColorView.xaml | 10 ++++++---- 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index ad07da2b160..3a4efaa42ac 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -13,7 +13,7 @@ - + - + @@ -93,7 +93,7 @@ - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml index 95af16d12e6..0e57f6b4836 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml @@ -6,7 +6,7 @@ - + + - - + From 0bf3a6d94487c0bf795bf5521c115d5b12a4ac8f Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 14 Jul 2022 23:57:27 -0400 Subject: [PATCH 58/58] Rename `ShowAccentColors` to `IsAccentColorsVisible` and disable drop shadow when false --- .../ControlCatalog/Pages/ColorPickerPage.xaml | 2 +- .../ColorPreviewer.Properties.cs | 14 +- .../ColorView/ColorView.Properties.cs | 32 ++--- .../Themes/Default/ColorPreviewer.xaml | 122 ++++++++++-------- .../Themes/Fluent/ColorPicker.xaml | 4 +- .../Themes/Fluent/ColorPreviewer.xaml | 122 ++++++++++-------- .../Themes/Fluent/ColorView.xaml | 2 +- 7 files changed, 167 insertions(+), 131 deletions(-) diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index 486a36956fd..1590be25ba6 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -47,7 +47,7 @@ ColorModel="Hsva" HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty ShowAccentColorsProperty = + public static readonly StyledProperty IsAccentColorsVisibleProperty = AvaloniaProperty.Register( - nameof(ShowAccentColors), + nameof(IsAccentColorsVisible), true); /// @@ -38,13 +38,13 @@ public HsvColor HsvColor } /// - /// Gets or sets a value indicating whether accent colors are shown along + /// Gets or sets a value indicating whether accent colors are visible along /// with the preview color. /// - public bool ShowAccentColors + public bool IsAccentColorsVisible { - get => GetValue(ShowAccentColorsProperty); - set => SetValue(ShowAccentColorsProperty, value); + get => GetValue(IsAccentColorsVisibleProperty); + set => SetValue(IsAccentColorsVisibleProperty, value); } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index d59c4d544a8..b76059037b5 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -52,6 +52,14 @@ public partial class ColorView defaultBindingMode: BindingMode.TwoWay, coerce: CoerceHsvColor); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsAccentColorsVisibleProperty = + AvaloniaProperty.Register( + nameof(IsAccentColorsVisible), + true); + /// /// Defines the property. /// @@ -220,14 +228,6 @@ public partial class ColorView nameof(SelectedIndex), (int)ColorViewTab.Spectrum); - /// - /// Defines the property. - /// - public static readonly StyledProperty ShowAccentColorsProperty = - AvaloniaProperty.Register( - nameof(ShowAccentColors), - true); - /// public Color Color { @@ -267,6 +267,13 @@ public HsvColor HsvColor set => SetValue(HsvColorProperty, value); } + /// + public bool IsAccentColorsVisible + { + get => GetValue(IsAccentColorsVisibleProperty); + set => SetValue(IsAccentColorsVisibleProperty, value); + } + /// /// Gets or sets a value indicating whether the alpha component is enabled. /// When disabled (set to false) the alpha component will be fixed to maximum and @@ -325,7 +332,7 @@ public bool IsColorPaletteVisible /// /// /// Note that accent color visibility is controlled separately by - /// . + /// . /// public bool IsColorPreviewVisible { @@ -484,12 +491,5 @@ public int SelectedIndex get => GetValue(SelectedIndexProperty); set => SetValue(SelectedIndexProperty, value); } - - /// - public bool ShowAccentColors - { - get => GetValue(ShowAccentColorsProperty); - set => SetValue(ShowAccentColorsProperty, value); - } } } diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml index 3a4efaa42ac..c3bc7df4a43 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml @@ -14,69 +14,87 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + - - + BoxShadow="0 0 10 2 #BF000000" + CornerRadius="{TemplateBinding CornerRadius}" + Margin="10"> + + + + + - - + + - - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index b34ac68be50..a4d5ff673ff 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -53,6 +53,7 @@ ColorSpectrumComponents="{TemplateBinding ColorSpectrumComponents}" ColorSpectrumShape="{TemplateBinding ColorSpectrumShape}" HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" + IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}" IsAlphaEnabled="{TemplateBinding IsAlphaEnabled}" IsAlphaVisible="{TemplateBinding IsAlphaVisible}" IsColorComponentsVisible="{TemplateBinding IsColorComponentsVisible}" @@ -73,8 +74,7 @@ PaletteColors="{TemplateBinding PaletteColors}" PaletteColumnCount="{TemplateBinding PaletteColumnCount}" Palette="{TemplateBinding Palette}" - SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" - ShowAccentColors="{TemplateBinding ShowAccentColors}" /> + SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" /> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml index 77059f14fb9..74f33d1258c 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml @@ -14,69 +14,87 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + - - + BoxShadow="0 0 10 2 #BF000000" + CornerRadius="{TemplateBinding CornerRadius}" + Margin="10"> + + + + + - - + + - - + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index bcdb72d96c9..e25e822f3f9 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -638,8 +638,8 @@