From 307beca9c1860d1c2d6aed2747035015bbf4c158 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 15:43:39 -0400 Subject: [PATCH 01/73] Initial port of ColorPickerButton to the toolkit --- .../ColorPickerButton.Properties.cs | 81 + .../ColorPickerButton.Rendering.cs | 437 +++++ .../ColorPickerButton/ColorPickerButton.cs | 1683 +++++++++++++++++ .../ColorPickerButton/ColorPickerButton.xaml | 748 ++++++++ .../ColorPickerButton/ColorPickerSlider.cs | 41 + .../ColorPickerButton/IColorPalette.cs | 36 + .../Themes/Generic.xaml | 1 + 7 files changed, 3027 insertions(+) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerSlider.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs new file mode 100644 index 00000000000..1ce21f403d0 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.ObjectModel; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public partial class ColorPickerButton + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CustomPaletteColorsProperty = + DependencyProperty.Register(nameof(CustomPaletteColors), + typeof(ObservableCollection), + typeof(ColorPickerButton), + new PropertyMetadata(Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00))); + + /// + /// Gets the list of custom palette colors. + /// + public ObservableCollection CustomPaletteColors + { + get => (ObservableCollection)base.GetValue(CustomPaletteColorsProperty); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CustomPaletteSectionCountProperty = + DependencyProperty.Register(nameof(CustomPaletteSectionCount), + typeof(int), + typeof(ColorPickerButton), + new PropertyMetadata(4)); + + /// + /// Gets or sets the number of colors in each section of the custom color palette. + /// A section is the number of columns within an entire row in the palette. + /// Within a standard palette, rows are shades and columns are unique colors. + /// + public int CustomPaletteSectionCount + { + get => (int)base.GetValue(CustomPaletteSectionCountProperty); + set + { + if (object.Equals(value, base.GetValue(CustomPaletteSectionCountProperty)) == false) + { + base.SetValue(CustomPaletteSectionCountProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CustomPaletteProperty = + DependencyProperty.Register(nameof(CustomPalette), + typeof(IColorPalette), + typeof(ColorPickerButton), + new PropertyMetadata(DependencyProperty.UnsetValue)); + + /// + /// Gets or sets the custom color palette. + /// This will automatically set and + /// overwriting any existing values. + /// + public IColorPalette CustomPalette + { + get => (IColorPalette)base.GetValue(CustomPaletteProperty); + set + { + if (object.Equals(value, base.GetValue(CustomPaletteProperty)) == false) + { + base.SetValue(CustomPaletteProperty, value); + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs new file mode 100644 index 00000000000..fdc7a7ba6b9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs @@ -0,0 +1,437 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.Helpers; +using System; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.UI; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public partial class ColorPickerButton + { + /// + /// Generates a new bitmap of the specified size by changing a specific color channel. + /// This will produce a gradient representing all possible differences of that color channel. + /// + /// The pixel width (X, horizontal) of the resulting bitmap. + /// The pixel height (Y, vertical) of the resulting bitmap. + /// The orientation of the resulting bitmap (gradient direction). + /// The color representation being used: RGBA or HSVA. + /// The specific color channel to vary. + /// The base RGB color used for channels not being changed. + /// A new bitmap representing a gradient of color channel values. + private async Task CreateChannelBitmapAsync(int width, + int height, + Orientation orientation, + ColorRepresentation colorRepresentation, + ColorChannel channel, + Color baseRgbColor, + Color? checkerColor) + { + if (width == 0 || height == 0) + { + return null; + } + + var bitmap = await Task.Run(async () => + { + int pixelDataIndex = 0; + double channelStep; + byte[] bgraPixelData; + byte[] bgraCheckeredPixelData = null; + HsvColor baseHsvColor; + Color rgbColor; + int bgraPixelDataHeight; + int bgraPixelDataWidth; + + // Allocate the buffer + // BGRA formatted color channels 1 byte each (4 bytes in a pixel) + bgraPixelData = new byte[width * height * 4]; + bgraPixelDataHeight = height * 4; + bgraPixelDataWidth = width * 4; + + // Convert RGB to HSV once + if (colorRepresentation == ColorRepresentation.Hsva) + { + baseHsvColor = baseRgbColor.ToHsv(); + } + else + { + baseHsvColor = Colors.White.ToHsv(); + } + + // Create a checkered background + if (checkerColor != null) + { + bgraCheckeredPixelData = await this.CreateCheckeredBitmapAsync(width, + height, + checkerColor.Value); + } + + // Create the color channel gradient + if (orientation == Orientation.Horizontal) + { + // Determine the numerical increment of the color steps within the channel + if (colorRepresentation == ColorRepresentation.Hsva) + { + if (channel == ColorChannel.Channel1) + { + channelStep = 360.0 / width; + } + else + { + channelStep = 1.0 / width; + } + } + else + { + channelStep = 255.0 / width; + } + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (y == 0) + { + rgbColor = GetColor(x * channelStep); + + // Get a new color + bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 3] = rgbColor.A; + } + else + { + // Use the color in the row above + // Remember the pixel data is 1 dimensional instead of 2 + bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex + 0 - bgraPixelDataWidth]; + bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex + 1 - bgraPixelDataWidth]; + bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex + 2 - bgraPixelDataWidth]; + bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex + 3 - bgraPixelDataWidth]; + } + + pixelDataIndex += 4; + } + } + } + else + { + // Determine the numerical increment of the color steps within the channel + if (colorRepresentation == ColorRepresentation.Hsva) + { + if (channel == ColorChannel.Channel1) + { + channelStep = 360.0 / height; + } + else + { + channelStep = 1.0 / height; + } + } + else + { + channelStep = 255.0 / height; + } + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (x == 0) + { + // The lowest channel value should be at the 'bottom' of the bitmap + rgbColor = GetColor((height - 1 - y) * channelStep); + + // Get a new color + bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R * rgbColor.A / 255); + bgraPixelData[pixelDataIndex + 3] = rgbColor.A; + } + else + { + // Use the color in the column to the left + // Remember the pixel data is 1 dimensional instead of 2 + bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex - 4]; + bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex - 3]; + bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex - 2]; + bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex - 1]; + } + + pixelDataIndex += 4; + } + } + } + + // Composite the checkered background with color channel gradient for final result + // The height/width are not checked as both bitmaps were built with the same values + if ((checkerColor != null) && + (bgraCheckeredPixelData != null)) + { + pixelDataIndex = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // The following algorithm is used to blend the two bitmaps creating the final composite. + // In this formula, pixel data is normalized 0..1, actual pixel data is in the range 0..255. + // The color channel gradient should apply OVER the checkered background. + // + // R = R0 * A0 * (1 - A1) + R1 * A1 = RA0 * (1 - A1) + RA1 + // G = G0 * A0 * (1 - A1) + G1 * A1 = GA0 * (1 - A1) + GA1 + // B = B0 * A0 * (1 - A1) + B1 * A1 = BA0 * (1 - A1) + BA1 + // A = A0 * (1 - A1) + A1 = A0 * (1 - A1) + A1 + // + // Considering only the red channel, some algebraic transformation is applied to + // make the math quicker to solve. + // + // => ((RA0 / 255.0) * (1.0 - A1 / 255.0) + (RA1 / 255.0)) * 255.0 + // => ((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255 + + // Bottom layer + byte RA0 = bgraCheckeredPixelData[pixelDataIndex + 2]; + byte GA0 = bgraCheckeredPixelData[pixelDataIndex + 1]; + byte BA0 = bgraCheckeredPixelData[pixelDataIndex + 0]; + byte A0 = bgraCheckeredPixelData[pixelDataIndex + 3]; + + // Top layer + byte RA1 = bgraPixelData[pixelDataIndex + 2]; + byte GA1 = bgraPixelData[pixelDataIndex + 1]; + byte BA1 = bgraPixelData[pixelDataIndex + 0]; + byte A1 = bgraPixelData[pixelDataIndex + 3]; + + bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(((BA0 * 255) - (BA0 * A1) + (BA1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(((GA0 * 255) - (GA0 * A1) + (GA1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 3] = Convert.ToByte(((A0 * 255) - (A0 * A1) + (A1 * 255)) / 255); + + pixelDataIndex += 4; + } + } + } + + Color GetColor(double channelValue) + { + Color newRgbColor = Colors.White; + + switch (channel) + { + case ColorChannel.Channel1: + { + if (colorRepresentation == ColorRepresentation.Hsva) + { + // Sweep hue + newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + ( + Math.Clamp(channelValue, 0.0, 360.0), + baseHsvColor.S, + baseHsvColor.V, + baseHsvColor.A + ); + } + else + { + // Sweep red + newRgbColor = new Color(); + newRgbColor.R = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); + newRgbColor.G = baseRgbColor.G; + newRgbColor.B = baseRgbColor.B; + newRgbColor.A = baseRgbColor.A; + } + + break; + } + case ColorChannel.Channel2: + { + if (colorRepresentation == ColorRepresentation.Hsva) + { + // Sweep saturation + newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + ( + baseHsvColor.H, + Math.Clamp(channelValue, 0.0, 1.0), + baseHsvColor.V, + baseHsvColor.A + ); + } + else + { + // Sweep green + newRgbColor = new Color(); + newRgbColor.R = baseRgbColor.R; + newRgbColor.G = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); + newRgbColor.B = baseRgbColor.B; + newRgbColor.A = baseRgbColor.A; + } + + break; + } + case ColorChannel.Channel3: + { + if (colorRepresentation == ColorRepresentation.Hsva) + { + // Sweep value + newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + ( + baseHsvColor.H, + baseHsvColor.S, + Math.Clamp(channelValue, 0.0, 1.0), + baseHsvColor.A + ); + } + else + { + // Sweep blue + newRgbColor = new Color(); + newRgbColor.R = baseRgbColor.R; + newRgbColor.G = baseRgbColor.G; + newRgbColor.B = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); + newRgbColor.A = baseRgbColor.A; + } + + break; + } + case ColorChannel.Alpha: + { + if (colorRepresentation == ColorRepresentation.Hsva) + { + // Sweep alpha + newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + ( + baseHsvColor.H, + baseHsvColor.S, + baseHsvColor.V, + Math.Clamp(channelValue, 0.0, 1.0) + ); + } + else + { + // Sweep alpha + newRgbColor = new Color(); + newRgbColor.R = baseRgbColor.R; + newRgbColor.G = baseRgbColor.G; + newRgbColor.B = baseRgbColor.B; + newRgbColor.A = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); + } + + break; + } + } + + return newRgbColor; + } + + return bgraPixelData; + }); + + return bitmap; + } + + /// + /// Generates a new checkered bitmap of the specified size. + /// + /// + /// This is a port and heavy modification of the code here: + /// https://github.com/microsoft/microsoft-ui-xaml/blob/865e4fcc00e8649baeaec1ba7daeca398671aa72/dev/ColorPicker/ColorHelpers.cpp#L363 + /// UWP needs TiledBrush support. + /// + /// The pixel width (X, horizontal) of the checkered bitmap. + /// The pixel height (Y, vertical) of the checkered bitmap. + /// The color of the checker square. + /// A new checkered bitmap of the specified size. + private async Task CreateCheckeredBitmapAsync(int width, + int height, + Color checkerColor) + { + // The size of the checker is important. You want it big enough that the grid is clearly discernible. + // However, the squares should be small enough they don't appear unnaturally cut at the edge of backgrounds. + int checkerSize = 4; + + if (width == 0 || height == 0) + { + return null; + } + + var bitmap = await Task.Run(() => + { + int pixelDataIndex = 0; + byte[] bgraPixelData; + + // Allocate the buffer + // BGRA formatted color channels 1 byte each (4 bytes in a pixel) + bgraPixelData = new byte[width * height * 4]; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // We want the checkered pattern to alternate both vertically and horizontally. + // In order to achieve that, we'll toggle visibility of the current pixel on or off + // depending on both its x- and its y-position. If x == CheckerSize, we'll turn visibility off, + // but then if y == CheckerSize, we'll turn it back on. + // The below is a shorthand for the above intent. + bool pixelShouldBeBlank = (x / checkerSize + y / checkerSize) % 2 == 0 ? true : false; + + // Remember, use BGRA pixel format with pre-multiplied alpha values + if (pixelShouldBeBlank) + { + bgraPixelData[pixelDataIndex + 0] = 0; + bgraPixelData[pixelDataIndex + 1] = 0; + bgraPixelData[pixelDataIndex + 2] = 0; + bgraPixelData[pixelDataIndex + 3] = 0; + } + else + { + bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(checkerColor.B * checkerColor.A / 255); + bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(checkerColor.G * checkerColor.A / 255); + bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(checkerColor.R * checkerColor.A / 255); + bgraPixelData[pixelDataIndex + 3] = checkerColor.A; + } + + pixelDataIndex += 4; + } + } + + return bgraPixelData; + }); + + return bitmap; + } + + /// + /// Converts the given bitmap (in raw BGRA pre-multiplied alpha pixels) into an image brush + /// that can be used in the UI. + /// + /// The bitmap (in raw BGRA pre-multiplied alpha pixels) to convert to a brush. + /// The pixel width of the bitmap. + /// The pixel height of the bitmap. + /// A new ImageBrush. + private async Task BitmapToBrushAsync(byte[] bitmap, + int width, + int height) + { + var writableBitmap = new WriteableBitmap(width, height); + using (Stream stream = writableBitmap.PixelBuffer.AsStream()) + { + await stream.WriteAsync(bitmap, 0, bitmap.Length); + } + + var brush = new ImageBrush() + { + ImageSource = writableBitmap, + Stretch = Stretch.None + }; + + return brush; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs new file mode 100644 index 00000000000..0fdf8d0d5dc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -0,0 +1,1683 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.Helpers; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Windows.Foundation; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelTextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel1Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel1TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel2Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel2TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel3Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.Channel3TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground1Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground2Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground3Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground4Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground5Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground6Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground7Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground8Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground9Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground10Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrum), Type = typeof(ColorSpectrum))] + [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumAlphaSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPickerButton.ColorRepresentationComboBox), Type = typeof(ComboBox))] + [TemplatePart(Name = nameof(ColorPickerButton.HexInputTextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.PaletteGridView), Type = typeof(GridView))] + [TemplatePart(Name = nameof(ColorPickerButton.P1PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.P2PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.N1PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPickerButton.N2PreviewBorder), Type = typeof(Border))] + public partial class ColorPickerButton : Windows.UI.Xaml.Controls.ColorPicker + { + /// + /// The period that scheduled color updates will be applied. + /// This is only used when updating colors using the ScheduleColorUpdate() method. + /// Color changes made directly to the Color property will apply instantly. + /// + private const int ColorUpdateInterval = 30; // Milliseconds + + /// + /// Defines how colors are represented. + /// + private enum ColorRepresentation + { + /// + /// Color is represented by hue, saturation, value and alpha channels. + /// + Hsva, + + /// + /// Color is represented by red, green, blue and alpha channels. + /// + Rgba + }; + + /// + /// Defines a specific channel within a color representation. + /// + private enum ColorChannel + { + /// + /// Represents the alpha channel. + /// + Alpha, + + /// + /// Represents the first color channel which is Red when RGB or Hue when HSV. + /// + Channel1, + + /// + /// Represents the second color channel which is Green when RGB or Saturation when HSV. + /// + Channel2, + + /// + /// Represents the third color channel which is Blue when RGB or Value when HSV. + /// + Channel3 + } + + private long tokenColor; + private long tokenCustomPalette; + + private Dictionary cachedSliderSizes = new Dictionary(); + private bool callbacksConnected = false; + private Color checkerBackgroundColor = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later + private bool eventsConnected = false; + private bool isInitialized = false; + + // Color information for updates + private HsvColor? savedHsvColor = null; + private Color? savedHsvColorRgbEquivalent = null; + private Color? updatedRgbColor = null; + private DispatcherTimer dispatcherTimer = null; + + private ColorSpectrum ColorSpectrum; + private Slider ColorSpectrumAlphaSlider; + private Slider ColorSpectrumThirdDimensionSlider; + private ComboBox ColorRepresentationComboBox; + private TextBox HexInputTextBox; + private GridView PaletteGridView; + + private TextBox Channel1TextBox; + private TextBox Channel2TextBox; + private TextBox Channel3TextBox; + private TextBox AlphaChannelTextBox; + private Slider Channel1Slider; + private Slider Channel2Slider; + private Slider Channel3Slider; + private Slider AlphaChannelSlider; + + private Border N1PreviewBorder; + private Border N2PreviewBorder; + private Border P1PreviewBorder; + private Border P2PreviewBorder; + + // Up to 8 checkered backgrounds may be used by name anywhere in the template + private Border CheckeredBackground1Border; + private Border CheckeredBackground2Border; + private Border CheckeredBackground3Border; + private Border CheckeredBackground4Border; + private Border CheckeredBackground5Border; + private Border CheckeredBackground6Border; + private Border CheckeredBackground7Border; + private Border CheckeredBackground8Border; + private Border CheckeredBackground9Border; + private Border CheckeredBackground10Border; + + /*************************************************************************************** + * + * Constructor/Destructor + * + ***************************************************************************************/ + + /// + /// Constructor. + /// + public ColorPickerButton() + { + this.DefaultStyleKey = typeof(ColorPickerButton); + + // Setup collections + base.SetValue(CustomPaletteColorsProperty, new ObservableCollection()); + this.CustomPaletteColors.CollectionChanged += CustomPaletteColors_CollectionChanged; + + this.Loaded += ColorPickerButton_Loaded; + + // Checkered background color is found only one time for performance + // This may need to change in the future if theme changes should be supported + this.checkerBackgroundColor = (Color)Application.Current.Resources["SystemListLowColor"]; + + this.SetDefaultPalette(); + + this.ConnectCallbacks(true); + this.StartDispatcherTimer(); + } + + /// + /// Destructor. + /// + ~ColorPickerButton() + { + this.StopDispatcherTimer(); + this.CustomPaletteColors.CollectionChanged -= CustomPaletteColors_CollectionChanged; + } + + /*************************************************************************************** + * + * Property Accessors + * + ***************************************************************************************/ + + + + /*************************************************************************************** + * + * Methods + * + ***************************************************************************************/ + + /// + /// Overrides when a template is applied in order to get the required controls. + /// + protected override void OnApplyTemplate() + { + this.ColorSpectrum = this.GetTemplateChild("ColorSpectrum", false); + this.ColorSpectrumAlphaSlider = this.GetTemplateChild("ColorSpectrumAlphaSlider", false); + this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild("ColorSpectrumThirdDimensionSlider", false); + + this.PaletteGridView = this.GetTemplateChild("PaletteGridView", false); + + this.ColorRepresentationComboBox = this.GetTemplateChild("ColorRepresentationComboBox", false); + this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); + + this.Channel1TextBox = this.GetTemplateChild("Channel1TextBox", false); + this.Channel2TextBox = this.GetTemplateChild("Channel2TextBox", false); + this.Channel3TextBox = this.GetTemplateChild("Channel3TextBox", false); + this.AlphaChannelTextBox = this.GetTemplateChild("AlphaChannelTextBox", false); + + this.Channel1Slider = this.GetTemplateChild("Channel1Slider", false); + this.Channel2Slider = this.GetTemplateChild("Channel2Slider", false); + this.Channel3Slider = this.GetTemplateChild("Channel3Slider", false); + this.AlphaChannelSlider = this.GetTemplateChild("AlphaChannelSlider", false); + + this.N1PreviewBorder = this.GetTemplateChild("N1PreviewBorder", false); + this.N2PreviewBorder = this.GetTemplateChild("N2PreviewBorder", false); + this.P1PreviewBorder = this.GetTemplateChild("P1PreviewBorder", false); + this.P2PreviewBorder = this.GetTemplateChild("P2PreviewBorder", false); + + this.CheckeredBackground1Border = this.GetTemplateChild("CheckeredBackground1Border", false); + this.CheckeredBackground2Border = this.GetTemplateChild("CheckeredBackground2Border", false); + this.CheckeredBackground3Border = this.GetTemplateChild("CheckeredBackground3Border", false); + this.CheckeredBackground4Border = this.GetTemplateChild("CheckeredBackground4Border", false); + this.CheckeredBackground5Border = this.GetTemplateChild("CheckeredBackground5Border", false); + this.CheckeredBackground6Border = this.GetTemplateChild("CheckeredBackground6Border", false); + this.CheckeredBackground7Border = this.GetTemplateChild("CheckeredBackground7Border", false); + this.CheckeredBackground8Border = this.GetTemplateChild("CheckeredBackground8Border", false); + this.CheckeredBackground9Border = this.GetTemplateChild("CheckeredBackground9Border", false); + this.CheckeredBackground10Border = this.GetTemplateChild("CheckeredBackground10Border", false); + + // Sync the active color + if (this.ColorSpectrum != null) + { + this.ColorSpectrum.Color = (Color)base.GetValue(ColorProperty); + } + + // Set initial state + if (base.IsEnabled == false) + { + VisualStateManager.GoToState(this, "Disabled", false); + } + else + { + VisualStateManager.GoToState(this, "Normal", false); + } + + // Must connect after controls are resolved + this.ConnectEvents(true); + + base.OnApplyTemplate(); + this.isInitialized = true; + } + + /// + /// Retrieves the named element in the instantiated ControlTemplate visual tree. + /// + /// The name of the element to find. + /// The template child matching the given name and type. + private T GetTemplateChild(string childName, bool isRequired = true) where T : DependencyObject + { + T child = base.GetTemplateChild(childName) as T; + if ((child == null) && isRequired) + { + throw new NullReferenceException(childName); + } + + return (child); + } + + /// + /// Connects or disconnects all dependency property callbacks. + /// + /// True to connect callbacks, otherwise false. + private void ConnectCallbacks(bool connected) + { + if ((connected == true) && + (this.callbacksConnected == false)) + { + // Add callbacks for dependency properties + this.tokenColor = this.RegisterPropertyChangedCallback(ColorProperty, OnColorChanged); + this.tokenCustomPalette = this.RegisterPropertyChangedCallback(CustomPaletteProperty, OnCustomPaletteChanged); + + this.callbacksConnected = true; + } + else if ((connected == false) && + (this.callbacksConnected == true)) + { + // Remove callbacks for dependency properties + this.UnregisterPropertyChangedCallback(ColorProperty, this.tokenColor); + this.UnregisterPropertyChangedCallback(CustomPaletteProperty, this.tokenCustomPalette); + + this.callbacksConnected = false; + } + + return; + } + + /// + /// Connects or disconnects all control event handlers. + /// + /// True to connect event handlers, otherwise false. + private void ConnectEvents(bool connected) + { + if ((connected == true) && + (this.eventsConnected == false)) + { + // Add all events + if (this.ColorSpectrum != null) { this.ColorSpectrum.ColorChanged += ColorSpectrum_ColorChanged; } + if (this.ColorSpectrum != null) { this.ColorSpectrum.GotFocus += ColorSpectrum_GotFocus; } + if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged += ColorRepresentationComboBox_SelectionChanged; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown += HexInputTextBox_KeyDown; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus += HexInputTextBox_LostFocus; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.TextChanged += HexInputTextBox_TextChanged; } + if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } + + //if (this.Channel1NumberBox != null) { this.Channel1NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } + //if (this.Channel2NumberBox != null) { this.Channel2NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } + //if (this.Channel3NumberBox != null) { this.Channel3NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } + //if (this.AlphaChannelNumberBox != null) { this.AlphaChannelNumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } + + if (this.Channel1Slider != null) { this.Channel1Slider.ValueChanged += ChannelSlider_ValueChanged; } + if (this.Channel2Slider != null) { this.Channel2Slider.ValueChanged += ChannelSlider_ValueChanged; } + if (this.Channel3Slider != null) { this.Channel3Slider.ValueChanged += ChannelSlider_ValueChanged; } + if (this.AlphaChannelSlider != null) { this.AlphaChannelSlider.ValueChanged += ChannelSlider_ValueChanged; } + if (this.ColorSpectrumAlphaSlider != null) { this.ColorSpectrumAlphaSlider.ValueChanged += ChannelSlider_ValueChanged; } + if (this.ColorSpectrumThirdDimensionSlider != null) { this.ColorSpectrumThirdDimensionSlider.ValueChanged += ChannelSlider_ValueChanged; } + + if (this.Channel1Slider != null) { this.Channel1Slider.Loaded += ChannelSlider_Loaded; } + if (this.Channel2Slider != null) { this.Channel2Slider.Loaded += ChannelSlider_Loaded; } + if (this.Channel3Slider != null) { this.Channel3Slider.Loaded += ChannelSlider_Loaded; } + if (this.AlphaChannelSlider != null) { this.AlphaChannelSlider.Loaded += ChannelSlider_Loaded; } + if (this.ColorSpectrumAlphaSlider != null) { this.ColorSpectrumAlphaSlider.Loaded += ChannelSlider_Loaded; } + if (this.ColorSpectrumThirdDimensionSlider != null) { this.ColorSpectrumThirdDimensionSlider.Loaded += ChannelSlider_Loaded; } + + if (this.N1PreviewBorder != null) { this.N1PreviewBorder.PointerPressed += PreviewBorder_PointerPressed; } + if (this.N2PreviewBorder != null) { this.N2PreviewBorder.PointerPressed += PreviewBorder_PointerPressed; } + if (this.P1PreviewBorder != null) { this.P1PreviewBorder.PointerPressed += PreviewBorder_PointerPressed; } + if (this.P2PreviewBorder != null) { this.P2PreviewBorder.PointerPressed += PreviewBorder_PointerPressed; } + + if (this.CheckeredBackground1Border != null) { this.CheckeredBackground1Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground2Border != null) { this.CheckeredBackground2Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground3Border != null) { this.CheckeredBackground3Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground4Border != null) { this.CheckeredBackground4Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground5Border != null) { this.CheckeredBackground5Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground6Border != null) { this.CheckeredBackground6Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground7Border != null) { this.CheckeredBackground7Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground8Border != null) { this.CheckeredBackground8Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground9Border != null) { this.CheckeredBackground9Border.Loaded += CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground10Border != null) { this.CheckeredBackground10Border.Loaded += CheckeredBackgroundBorder_Loaded; } + + this.eventsConnected = true; + } + else if ((connected == false) && + (this.eventsConnected == true)) + { + // Remove all events + if (this.ColorSpectrum != null) { this.ColorSpectrum.ColorChanged -= ColorSpectrum_ColorChanged; } + if (this.ColorSpectrum != null) { this.ColorSpectrum.GotFocus -= ColorSpectrum_GotFocus; } + if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged -= ColorRepresentationComboBox_SelectionChanged; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown -= HexInputTextBox_KeyDown; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus -= HexInputTextBox_LostFocus; } + if (this.HexInputTextBox != null) { this.HexInputTextBox.TextChanged -= HexInputTextBox_TextChanged; } + if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } + + //if (this.Channel1TextBox != null) { this.Channel1TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } + //if (this.Channel2TextBox != null) { this.Channel2TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } + //if (this.Channel3TextBox != null) { this.Channel3TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } + //if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.ValueChanged -= ChannelTextBox_ValueChanged; } + + if (this.Channel1Slider != null) { this.Channel1Slider.ValueChanged -= ChannelSlider_ValueChanged; } + if (this.Channel2Slider != null) { this.Channel2Slider.ValueChanged -= ChannelSlider_ValueChanged; } + if (this.Channel3Slider != null) { this.Channel3Slider.ValueChanged -= ChannelSlider_ValueChanged; } + if (this.AlphaChannelSlider != null) { this.AlphaChannelSlider.ValueChanged -= ChannelSlider_ValueChanged; } + if (this.ColorSpectrumAlphaSlider != null) { this.ColorSpectrumAlphaSlider.ValueChanged -= ChannelSlider_ValueChanged; } + if (this.ColorSpectrumThirdDimensionSlider != null) { this.ColorSpectrumThirdDimensionSlider.ValueChanged -= ChannelSlider_ValueChanged; } + + if (this.Channel1Slider != null) { this.Channel1Slider.Loaded -= ChannelSlider_Loaded; } + if (this.Channel2Slider != null) { this.Channel2Slider.Loaded -= ChannelSlider_Loaded; } + if (this.Channel3Slider != null) { this.Channel3Slider.Loaded -= ChannelSlider_Loaded; } + if (this.AlphaChannelSlider != null) { this.AlphaChannelSlider.Loaded -= ChannelSlider_Loaded; } + if (this.ColorSpectrumAlphaSlider != null) { this.ColorSpectrumAlphaSlider.Loaded -= ChannelSlider_Loaded; } + if (this.ColorSpectrumThirdDimensionSlider != null) { this.ColorSpectrumThirdDimensionSlider.Loaded -= ChannelSlider_Loaded; } + + if (this.N1PreviewBorder != null) { this.N1PreviewBorder.PointerPressed -= PreviewBorder_PointerPressed; } + if (this.N2PreviewBorder != null) { this.N2PreviewBorder.PointerPressed -= PreviewBorder_PointerPressed; } + if (this.P1PreviewBorder != null) { this.P1PreviewBorder.PointerPressed -= PreviewBorder_PointerPressed; } + if (this.P2PreviewBorder != null) { this.P2PreviewBorder.PointerPressed -= PreviewBorder_PointerPressed; } + + if (this.CheckeredBackground1Border != null) { this.CheckeredBackground1Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground2Border != null) { this.CheckeredBackground2Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground3Border != null) { this.CheckeredBackground3Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground4Border != null) { this.CheckeredBackground4Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground5Border != null) { this.CheckeredBackground5Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground6Border != null) { this.CheckeredBackground6Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground7Border != null) { this.CheckeredBackground7Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground8Border != null) { this.CheckeredBackground8Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground9Border != null) { this.CheckeredBackground9Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + if (this.CheckeredBackground10Border != null) { this.CheckeredBackground10Border.Loaded -= CheckeredBackgroundBorder_Loaded; } + + this.eventsConnected = false; + } + + return; + } + + /// + /// Gets the active representation of the color: HSV or RGB. + /// + private ColorRepresentation GetActiveColorRepresentation() + { + // This is kind-of an ugly way to see if the HSV color channel representation is active + // However, it is the same technique used in the ColorPicker + // The order and number of items in the template is fixed and very important + if ((this.ColorRepresentationComboBox != null) && + (this.ColorRepresentationComboBox.SelectedIndex == 1)) + { + return ColorRepresentation.Hsva; + } + + return ColorRepresentation.Rgba; + } + + /// + /// Gets the active third dimension in the color spectrum: Hue, Saturation or Value. + /// + private ColorChannel GetActiveColorSpectrumThirdDimension() + { + switch (this.ColorSpectrumComponents) + { + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.SaturationValue: + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.ValueSaturation: + { + // Hue + return ColorChannel.Channel1; + } + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.HueValue: + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.ValueHue: + { + // Saturation + return ColorChannel.Channel2; + } + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.HueSaturation: + case Windows.UI.Xaml.Controls.ColorSpectrumComponents.SaturationHue: + { + // Value + return ColorChannel.Channel3; + } + } + + return ColorChannel.Alpha; // Error, should never get here + } + + /// + /// Gets whether or not the color is considered empty (all fields zero). + /// In the future Color.IsEmpty will hopefully be added to UWP. + /// + /// The Windows.UI.Color to calculate with. + /// Whether the color is considered empty. + private static bool IsColorEmpty(Color color) + { + return color.A == 0x00 && + color.R == 0x00 && + color.G == 0x00 && + color.B == 0x00; + } + + /// + /// Declares a new color to set to the control. + /// Application of this color will be scheduled to avoid overly rapid updates. + /// + /// The new color to set to the control. + private void ScheduleColorUpdate(Color newColor) + { + // Coerce the value as needed + if (this.IsAlphaEnabled == false) + { + newColor = new Color() + { + R = newColor.R, + G = newColor.G, + B = newColor.B, + A = 255 + }; + } + + this.updatedRgbColor = newColor; + + return; + } + + /// + /// Updates the color channel values in all editing controls to match the current color. + /// + private void UpdateChannelControlValues() + { + bool eventsDisconnectedByMethod = false; + Color rgbColor = this.Color; + HsvColor hsvColor; + + if (this.isInitialized) + { + // Disable events during the update + if (this.eventsConnected) + { + this.ConnectEvents(false); + eventsDisconnectedByMethod = true; + } + + this.HexInputTextBox.Text = rgbColor.ToHex().Replace("#", ""); + + // Regardless of the active color representation, the spectrum is always HSV + // Therefore, always calculate HSV color here + // Warning: Always maintain/use HSV information in the saved HSV color + // This avoids loss of precision and drift caused by continuously converting to/from RGB + if (this.savedHsvColor == null) + { + hsvColor = rgbColor.ToHsv(); + + // Round the channels, be sure rounding matches with the scaling next + // Rounding of SVA requires at MINIMUM 2 decimal places + int decimals = 0; + hsvColor = new HsvColor() + { + H = Math.Round(hsvColor.H, decimals), + S = Math.Round(hsvColor.S, 2 + decimals), + V = Math.Round(hsvColor.V, 2 + decimals), + A = Math.Round(hsvColor.A, 2 + decimals) + }; + + // Must update HSV color + this.savedHsvColor = hsvColor; + this.savedHsvColorRgbEquivalent = rgbColor; + } + else + { + hsvColor = this.savedHsvColor.Value; + } + + // Update the color spectrum third dimension channel + if (this.ColorSpectrumThirdDimensionSlider != null) + { + // Convert the channels into a usable range for the user + double hue = hsvColor.H; + double staturation = hsvColor.S * 100; + double value = hsvColor.V * 100; + + switch (this.GetActiveColorSpectrumThirdDimension()) + { + case ColorChannel.Channel1: + { + // Hue + this.ColorSpectrumThirdDimensionSlider.Minimum = 0; + this.ColorSpectrumThirdDimensionSlider.Maximum = 360; + this.ColorSpectrumThirdDimensionSlider.Value = hue; + break; + } + case ColorChannel.Channel2: + { + // Saturation + this.ColorSpectrumThirdDimensionSlider.Minimum = 0; + this.ColorSpectrumThirdDimensionSlider.Maximum = 100; + this.ColorSpectrumThirdDimensionSlider.Value = staturation; + break; + } + case ColorChannel.Channel3: + { + // Value + this.ColorSpectrumThirdDimensionSlider.Minimum = 0; + this.ColorSpectrumThirdDimensionSlider.Maximum = 100; + this.ColorSpectrumThirdDimensionSlider.Value = value; + break; + } + } + } + + // Update all other color channels + if (this.GetActiveColorRepresentation() == ColorRepresentation.Hsva) + { + // Convert the channels into a usable range for the user + double hue = hsvColor.H; + double staturation = hsvColor.S * 100; + double value = hsvColor.V * 100; + double alpha = hsvColor.A * 100; + + // Hue + if (this.Channel1TextBox != null) + { + this.Channel1TextBox.Text = hue.ToString(); + } + + if (this.Channel1Slider != null) + { + this.Channel1Slider.Minimum = 0; + this.Channel1Slider.Maximum = 360; + this.Channel1Slider.Value = hue; + } + + // Saturation + if (this.Channel2TextBox != null) + { + this.Channel2TextBox.Text = staturation.ToString(); + } + + if (this.Channel2Slider != null) + { + this.Channel2Slider.Minimum = 0; + this.Channel2Slider.Maximum = 100; + this.Channel2Slider.Value = staturation; + } + + // Value + if (this.Channel3TextBox != null) + { + this.Channel3TextBox.Text = value.ToString(); + } + + if (this.Channel3Slider != null) + { + this.Channel3Slider.Minimum = 0; + this.Channel3Slider.Maximum = 100; + this.Channel3Slider.Value = value; + } + + // Alpha + if (this.AlphaChannelTextBox != null) + { + this.AlphaChannelTextBox.Text = alpha.ToString(); + } + + if (this.AlphaChannelSlider != null) + { + this.AlphaChannelSlider.Minimum = 0; + this.AlphaChannelSlider.Maximum = 100; + this.AlphaChannelSlider.Value = alpha; + } + + // Color spectrum alpha + if (this.ColorSpectrumAlphaSlider != null) + { + this.ColorSpectrumAlphaSlider.Minimum = 0; + this.ColorSpectrumAlphaSlider.Maximum = 100; + this.ColorSpectrumAlphaSlider.Value = alpha; + } + } + else + { + // Red + if (this.Channel1TextBox != null) + { + this.Channel1TextBox.Text = rgbColor.R.ToString();; + } + + if (this.Channel1Slider != null) + { + this.Channel1Slider.Minimum = 0; + this.Channel1Slider.Maximum = 255; + this.Channel1Slider.Value = Convert.ToDouble(rgbColor.R); + } + + // Green + if (this.Channel2TextBox != null) + { + this.Channel2TextBox.Text = rgbColor.G.ToString();; + } + + if (this.Channel2Slider != null) + { + this.Channel2Slider.Minimum = 0; + this.Channel2Slider.Maximum = 255; + this.Channel2Slider.Value = Convert.ToDouble(rgbColor.G); + } + + // Blue + if (this.Channel3TextBox != null) + { + this.Channel3TextBox.Text = rgbColor.B.ToString();; + } + + if (this.Channel3Slider != null) + { + this.Channel3Slider.Minimum = 0; + this.Channel3Slider.Maximum = 255; + this.Channel3Slider.Value = Convert.ToDouble(rgbColor.B); + } + + // Alpha + if (this.AlphaChannelTextBox != null) + { + this.AlphaChannelTextBox.Text = rgbColor.A.ToString();; + } + + if (this.AlphaChannelSlider != null) + { + this.AlphaChannelSlider.Minimum = 0; + this.AlphaChannelSlider.Maximum = 255; + this.AlphaChannelSlider.Value = Convert.ToDouble(rgbColor.A); + } + + // Color spectrum alpha + if (this.ColorSpectrumAlphaSlider != null) + { + this.ColorSpectrumAlphaSlider.Minimum = 0; + this.ColorSpectrumAlphaSlider.Maximum = 255; + this.ColorSpectrumAlphaSlider.Value = Convert.ToDouble(rgbColor.A); + } + } + + if (eventsDisconnectedByMethod) + { + this.ConnectEvents(true); + } + } + + return; + } + + /// + /// Sets a new color channel value to the current color. + /// Only the specified color channel will be modified. + /// + /// The color representation of the given channel. + /// The specified color channel to modify. + /// The new color channel value. + private void SetColorChannel(ColorRepresentation colorRepresentation, + ColorChannel channel, + double newValue) + { + Color oldRgbColor = this.Color; + Color newRgbColor; + HsvColor oldHsvColor; + + if (colorRepresentation == ColorRepresentation.Hsva) + { + // Warning: Always maintain/use HSV information in the saved HSV color + // This avoids loss of precision and drift caused by continuously converting to/from RGB + if (this.savedHsvColor == null) + { + oldHsvColor = oldRgbColor.ToHsv(); + } + else + { + oldHsvColor = this.savedHsvColor.Value; + } + + double hue = oldHsvColor.H; + double saturation = oldHsvColor.S; + double value = oldHsvColor.V; + double alpha = oldHsvColor.A; + + switch (channel) + { + case ColorChannel.Channel1: + { + hue = Math.Clamp((double.IsNaN(newValue) ? 0 : newValue), 0, 360); + break; + } + case ColorChannel.Channel2: + { + saturation = Math.Clamp((double.IsNaN(newValue) ? 0 : newValue) / 100, 0, 1); + break; + } + case ColorChannel.Channel3: + { + value = Math.Clamp((double.IsNaN(newValue) ? 0 : newValue) / 100, 0, 1); + break; + } + case ColorChannel.Alpha: + { + // Unlike color channels, default to no transparency + alpha = Math.Clamp((double.IsNaN(newValue) ? 100 : newValue) / 100, 0, 1); + break; + } + } + + newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(hue, + saturation, + value, + alpha); + + // Must update HSV color + this.savedHsvColor = new HsvColor() + { + H = hue, + S = saturation, + V = value, + A = alpha + }; + this.savedHsvColorRgbEquivalent = newRgbColor; + } + else + { + byte red = oldRgbColor.R; + byte green = oldRgbColor.G; + byte blue = oldRgbColor.B; + byte alpha = oldRgbColor.A; + + switch (channel) + { + case ColorChannel.Channel1: + { + red = Convert.ToByte(Math.Clamp((double.IsNaN(newValue) ? 0 : newValue), 0, 255)); + break; + } + case ColorChannel.Channel2: + { + green = Convert.ToByte(Math.Clamp((double.IsNaN(newValue) ? 0 : newValue), 0, 255)); + break; + } + case ColorChannel.Channel3: + { + blue = Convert.ToByte(Math.Clamp((double.IsNaN(newValue) ? 0 : newValue), 0, 255)); + break; + } + case ColorChannel.Alpha: + { + // Unlike color channels, default to no transparency + alpha = Convert.ToByte(Math.Clamp((double.IsNaN(newValue) ? 255 : newValue), 0, 255)); + break; + } + } + + newRgbColor = new Color() + { + R = red, + G = green, + B = blue, + A = alpha + }; + + // Must clear saved HSV color + this.savedHsvColor = null; + this.savedHsvColorRgbEquivalent = null; + } + + this.ScheduleColorUpdate(newRgbColor); + return; + } + + /// + /// Updates all channel slider control backgrounds. + /// + private void UpdateChannelSliderBackgrounds() + { + this.UpdateChannelSliderBackground(this.Channel1Slider); + this.UpdateChannelSliderBackground(this.Channel2Slider); + this.UpdateChannelSliderBackground(this.Channel3Slider); + this.UpdateChannelSliderBackground(this.AlphaChannelSlider); + this.UpdateChannelSliderBackground(this.ColorSpectrumAlphaSlider); + this.UpdateChannelSliderBackground(this.ColorSpectrumThirdDimensionSlider); + return; + } + + /// + /// Generates a new background image for the specified color channel slider and applies it. + /// A new image will only be generated if it differs from the last color used to generate a background. + /// This provides some performance improvement. + /// + /// The color channel slider to apply the generated background to. + private async void UpdateChannelSliderBackground(Slider slider) + { + byte[] bitmap = null; + int width = 0; + int height = 0; + Color baseColor = this.Color; + + // Updates may be requested when sliders are not in the visual tree. + // For first-time load this is handled by the Loaded event. + // However, after that problems may arise, consider the following case: + // + // (1) Backgrounds are drawn normally the first time on Loaded. + // Actual height/width are available. + // (2) The palette tab is selected which has no sliders + // (3) The picker flyout is closed + // (4) Externally the color is changed + // The color change will trigger slider background updates but + // with the flyout closed, actual height/width are zero. + // No zero size bitmap can be generated. + // (5) The picker flyout is re-opened by the user and the default + // last-opened tab will be viewed: palette. + // No loaded events will be fired for sliders. The color change + // event was already handled in (4). The sliders will never + // be updated. + // + // In this case the sliders become out of sync with the Color because there is no way + // to tell when they actually come into view. To work around this, force a re-render of + // the background with the last size of the slider. This last size will be when it was + // last loaded or updated. + // + // In the future additional consideration may be required for SizeChanged of the control. + // This work-around will also cause issues if display scaling changes in the special + // case where cached sizes are required. + if (slider != null) + { + width = Convert.ToInt32(slider.ActualWidth); + height = Convert.ToInt32(slider.ActualHeight); + + if (width == 0 || height == 0) + { + // Attempt to use the last size if it was available + if (this.cachedSliderSizes.ContainsKey(slider)) + { + Size cachedSize = this.cachedSliderSizes[slider]; + width = Convert.ToInt32(cachedSize.Width); + height = Convert.ToInt32(cachedSize.Height); + } + } + else + { + // Update the cached slider size + if (this.cachedSliderSizes.ContainsKey(slider)) + { + this.cachedSliderSizes[slider] = new Size(width, height); + } + else + { + this.cachedSliderSizes.Add(slider, new Size(width, height)); + } + } + } + + if (object.ReferenceEquals(slider, this.Channel1Slider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel1, + baseColor, + this.checkerBackgroundColor); + } + else if (object.ReferenceEquals(slider, this.Channel2Slider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel2, + baseColor, + this.checkerBackgroundColor); + } + else if (object.ReferenceEquals(slider, this.Channel3Slider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel3, + baseColor, + this.checkerBackgroundColor); + } + else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Alpha, + baseColor, + this.checkerBackgroundColor); + } + else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Vertical, + this.GetActiveColorRepresentation(), + ColorChannel.Alpha, + baseColor, + this.checkerBackgroundColor); + } + else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) + { + bitmap = await this.CreateChannelBitmapAsync(width, + height, + Orientation.Vertical, + ColorRepresentation.Hsva, // Always HSV + this.GetActiveColorSpectrumThirdDimension(), + baseColor, + this.checkerBackgroundColor); + } + + if (bitmap != null) + { + slider.Background = await this.BitmapToBrushAsync(bitmap, width, height); + } + + return; + } + + /// + /// Sets the default color palette to the control. + /// + private void SetDefaultPalette() + { + this.CustomPalette = null; + + this.CustomPaletteColors.Add(Colors.Black); + this.CustomPaletteColors.Add(Colors.Gray); + this.CustomPaletteColors.Add(Colors.Silver); + this.CustomPaletteColors.Add(Colors.White); + this.CustomPaletteColors.Add(Colors.Maroon); + this.CustomPaletteColors.Add(Colors.Red); + this.CustomPaletteColors.Add(Colors.Olive); + this.CustomPaletteColors.Add(Colors.Yellow); + this.CustomPaletteColors.Add(Colors.Green); + this.CustomPaletteColors.Add(Colors.Lime); + this.CustomPaletteColors.Add(Colors.Teal); + this.CustomPaletteColors.Add(Colors.Aqua); + this.CustomPaletteColors.Add(Colors.Navy); + this.CustomPaletteColors.Add(Colors.Blue); + this.CustomPaletteColors.Add(Colors.Purple); + this.CustomPaletteColors.Add(Colors.Fuchsia); + + this.CustomPaletteSectionCount = 4; + + return; + } + + + + /*************************************************************************************** + * + * Color Update Timer + * + ***************************************************************************************/ + + private void StartDispatcherTimer() + { + this.dispatcherTimer = new DispatcherTimer() + { + Interval = new TimeSpan(0, 0, 0, 0, ColorUpdateInterval) + }; + this.dispatcherTimer.Tick += DispatcherTimer_Tick; + this.dispatcherTimer.Start(); + + return; + } + + private void StopDispatcherTimer() + { + if (this.dispatcherTimer != null) + { + this.dispatcherTimer.Stop(); + } + return; + } + + private void DispatcherTimer_Tick(object sender, object e) + { + if (this.updatedRgbColor != null) + { + var newColor = this.updatedRgbColor.Value; + + // Clear first to avoid timing issues if it takes longer than the timer interval to set the new color + this.updatedRgbColor = null; + + // An equality check here is important + // Without it, OnColorChanged would continuously be invoked and preserveHsvColor overwritten when not wanted + if (object.Equals(newColor, base.GetValue(ColorProperty)) == false) + { + // Disable events here so the color update isn't repeated as other controls in the UI are updated through binding. + // For example, the Spectrum should be bound to Color, as soon as Color is changed here the Spectrum is updated. + // Then however, the ColorSpectrum.ColorChanged event would fire which would schedule a new color update -- + // with the same color. This causes several problems: + // 1. Layout cycle that may crash the app + // 2. A performance hit recalculating for no reason + // 3. preserveHsvColor gets overwritten unexpectedly by the ColorChanged handler + this.ConnectEvents(false); + base.SetValue(ColorProperty, newColor); + this.ConnectEvents(true); + } + } + + return; + } + + /*************************************************************************************** + * + * Callbacks + * + ***************************************************************************************/ + + /// + /// Callback for when the dependency property value changes. + /// + private void OnColorChanged(DependencyObject d, DependencyProperty e) + { + // TODO: Coerce the value if Alpha is disabled, is this handled in the base ColorPicker? + if ((this.savedHsvColor != null) && + (object.Equals(d.GetValue(e), this.savedHsvColorRgbEquivalent) == false)) + { + // The color was updated from an unknown source + // The RGB and HSV colors are no longer in sync so the HSV color must be cleared + this.savedHsvColor = null; + this.savedHsvColorRgbEquivalent = null; + } + + this.UpdateChannelControlValues(); + this.UpdateChannelSliderBackgrounds(); + + return; + } + + /// + /// Callback for when the dependency property value changes. + /// + private void OnCustomPaletteChanged(DependencyObject d, DependencyProperty e) + { + IColorPalette palette = this.CustomPalette; + + if (palette != null) + { + this.CustomPaletteSectionCount = palette.ColorCount; + this.CustomPaletteColors.Clear(); + + for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++) + { + for (int colorIndex = 0; colorIndex < palette.ColorCount; colorIndex++) + { + this.CustomPaletteColors.Add(palette.GetColor(colorIndex, shadeIndex)); + } + } + } + + return; + } + + /*************************************************************************************** + * + * Event Handling + * + ***************************************************************************************/ + + /// + /// Event handler for when the control has finished loaded. + /// + private void ColorPickerButton_Loaded(object sender, RoutedEventArgs e) + { + // Available but not currently used + return; + } + + /// + /// Event handler for when a color channel slider is loaded. + /// This will draw an initial background. + /// + private void ChannelSlider_Loaded(object sender, RoutedEventArgs e) + { + this.UpdateChannelSliderBackground(sender as Slider); + return; + } + + /// + /// Event handler to draw checkered backgrounds on-demand as controls are loaded. + /// + private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e) + { + Border border = sender as Border; + int width; + int height; + + if (border != null) + { + width = Convert.ToInt32(border.ActualWidth); + height = Convert.ToInt32(border.ActualHeight); + + var bitmap = await this.CreateCheckeredBitmapAsync(width, + height, + this.checkerBackgroundColor); + + if (bitmap != null) + { + border.Background = await this.BitmapToBrushAsync(bitmap, width, height); + } + } + + return; + } + + /// + /// Event handler for when the grid view showing all colors in the palette is loaded. + /// This will set the correct column count to any UniformGrid panel. + /// + private void PaletteGridView_Loaded(object sender, RoutedEventArgs e) + { + // RelativeSource binding of an ancestor type doesn't work in UWP. + // Therefore, setting this property must be done here in code-behind. + var palettePanel = (sender as DependencyObject)?.FindDescendant(); + + if (palettePanel != null) + { + palettePanel.Columns = this.CustomPaletteSectionCount; + } + + return; + } + + /// + /// Event handler for when the list of custom palette colors is changed. + /// + private void CustomPaletteColors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Available but not currently used + return; + } + + /// + /// Event handler for when the color spectrum color is changed. + /// This occurs when the user presses on the spectrum to select a new color. + /// + private void ColorSpectrum_ColorChanged(ColorSpectrum sender, Windows.UI.Xaml.Controls.ColorChangedEventArgs args) + { + this.ScheduleColorUpdate(this.ColorSpectrum.Color); + return; + } + + /// + /// Event handler for when the color spectrum is focused. + /// This is used only to work around some bugs that cause usability problems. + /// + private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) + { + /* If this control has a color that is currently empty (#00000000), + * selecting a new color directly in the spectrum will fail. This is + * a bug in the color spectrum. Selecting a new color in the spectrum will + * keep zero for all channels (including alpha and the third dimension). + * + * In practice this means a new color cannot be selected using the spectrum + * until both the alpha and third dimension slider are raised above zero. + * This is extremely user unfriendly and must be corrected as best as possible. + * + * In order to work around this, detect when the color spectrum has selected + * a new color and then automatically set the alpha and third dimension + * channel to maximum. However, the color spectrum has a second bug, the + * ColorChanged event is never raised if the color is empty. This prevents + * automatically setting the other channels where it normally should be done + * (in the ColorChanged event). + * + * In order to work around this second bug, the GotFocus event is used + * to detect when the spectrum is engaged by the user. It's somewhat equivalent + * to ColorChanged for this purpose. Then when the GotFocus event is fired + * set the alpha and third channel values to maximum. The problem here is that + * the GotFocus event does not have access to the new color that was selected + * in the spectrum. It is not available due to the afore mentioned bug or due to + * timing. This means the best that can be done is to just set a 'neutral' + * color such as white. + * + * There is still a small usability issue with this as it requires two + * presses to set a color. That's far better than having to slide up both + * sliders though. + * + * 1. If the color is empty, the first press on the spectrum will set white + * and ignore the pressed color on the spectrum + * 2. The second press on the spectrum will be correctly handled. + * + */ + + if (IsColorEmpty(this.Color)) // In the future Color.IsEmpty will hopefully be added to UWP + { + // The following code may be used in the future if ever the selected color is available + // + //Color newColor = this.ColorSpectrum.Color; + //HsvColor newHsvColor = newColor.ToHsv(); + + //switch (this.GetActiveColorSpectrumThirdDimension()) + //{ + // case ColorChannel.Channel1: + // { + // newColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + // ( + // 360.0, + // newHsvColor.S, + // newHsvColor.V, + // 100.0 + // ); + // break; + // } + // case ColorChannel.Channel2: + // { + // newColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + // ( + // newHsvColor.H, + // 100.0, + // newHsvColor.V, + // 100.0 + // ); + // break; + // } + // case ColorChannel.Channel3: + // { + // newColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv + // ( + // newHsvColor.H, + // newHsvColor.S, + // 100.0, + // 100.0 + // ); + // break; + // } + //} + + this.Color = Colors.White; + } + + return; + } + + /// + /// Event handler for when the selection changes within the color representation ComboBox. + /// This will convert between RGB and HSV. + /// + private void ColorRepresentationComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + this.UpdateChannelControlValues(); + this.UpdateChannelSliderBackgrounds(); + return; + } + + /// + /// Event handler for when a preview color panel is pressed. + /// This will update the color to the background of the pressed panel. + /// + private void PreviewBorder_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + Border border = sender as Border; + + if (border?.Background is SolidColorBrush brush) + { + this.ScheduleColorUpdate(brush.Color); + } + + return; + } + + /// + /// 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 HexInputTextBox_KeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e) + { + if (e.Key == Windows.System.VirtualKey.Enter) + { + try + { + ColorToHexConverter converter = new ColorToHexConverter(); + this.Color = (Color)converter.ConvertBack(((TextBox)sender).Text, typeof(TextBox), null, null); + } + catch + { + // Reset hex value + this.UpdateChannelControlValues(); + this.UpdateChannelSliderBackgrounds(); + } + } + + return; + } + + /// + /// 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 HexInputTextBox_LostFocus(object sender, RoutedEventArgs e) + { + try + { + ColorToHexConverter converter = new ColorToHexConverter(); + this.Color = (Color)converter.ConvertBack(((TextBox)sender).Text, typeof(TextBox), null, null); + } + catch + { + // Reset hex value + this.UpdateChannelControlValues(); + this.UpdateChannelSliderBackgrounds(); + } + + return; + } + + private void HexInputTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + // Use LostFocus and KeyDown events instead + return; + } + + /// + /// Event handler for when the value within one of the channel NumberBoxes is changed. + /// + //private void ChannelNumberBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) + //{ + // double senderValue = sender.Value; + + // if (object.ReferenceEquals(sender, this.Channel1TextBox)) + // { + // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel1, senderValue); + // } + // else if (object.ReferenceEquals(sender, this.Channel2TextBox)) + // { + // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel2, senderValue); + // } + // else if (object.ReferenceEquals(sender, this.Channel3TextBox)) + // { + // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel3, senderValue); + // } + // else if (object.ReferenceEquals(sender, this.AlphaChannelTextBox)) + // { + // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, senderValue); + // } + + // return; + //} + + /// + /// Event handler for when the value within one of the channel Sliders is changed. + /// + private void ChannelSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + double senderValue = (sender as Slider)?.Value ?? double.NaN; + + if (object.ReferenceEquals(sender, this.Channel1Slider)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel1, senderValue); + } + else if (object.ReferenceEquals(sender, this.Channel2Slider)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel2, senderValue); + } + else if (object.ReferenceEquals(sender, this.Channel3Slider)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel3, senderValue); + } + else if (object.ReferenceEquals(sender, this.AlphaChannelSlider)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, senderValue); + } + else if (object.ReferenceEquals(sender, this.ColorSpectrumAlphaSlider)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, senderValue); + } + else if (object.ReferenceEquals(sender, this.ColorSpectrumThirdDimensionSlider)) + { + // Regardless of the active color representation, the spectrum is always HSV + this.SetColorChannel(ColorRepresentation.Hsva, this.GetActiveColorSpectrumThirdDimension(), senderValue); + } + + return; + } + } + + /// + /// Adjust the value component of a color in the HSV model. + /// The value % change must be supplied using the parameter. + /// 0 = 0% and 100 = 100% with value clipped to this scale. + /// Both positive and negative adjustments are supported as this applies + /// the delta to the existing color. + /// + public class HsvValueAdjustmentConverter : IValueConverter + { + public object Convert(object value, + Type targetType, + object parameter, + string language) + { + double valueDelta; + HsvColor hsvColor; + + // Get the value component delta + try + { + valueDelta = System.Convert.ToDouble(parameter?.ToString()); + } + catch + { + throw new ArgumentException("Invalid parameter provided, unable to convert to double"); + } + + // Get the current color in HSV + try + { + hsvColor = ((Color)value).ToHsv(); + } + catch + { + throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); + } + + // Add the value delta to the HSV color and convert it back to RGB + hsvColor = new HsvColor() + { + H = hsvColor.H, + S = hsvColor.S, + V = Math.Clamp(hsvColor.V + (valueDelta / 100.0), 0.0, 1.0), + A = hsvColor.A, + }; + + return Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(hsvColor.H, + hsvColor.S, + hsvColor.V, + hsvColor.A); + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + string language) + { + throw new NotImplementedException(); + } + } + + /// + /// + /// Only +/- 3 shades from the given color are supported. + /// + public class AccentColorShadeConverter : IValueConverter + { + public object Convert(object value, + Type targetType, + object parameter, + string language) + { + int shade; + HsvColor hsvColor; + + // Get the value component delta + try + { + shade = System.Convert.ToInt32(parameter?.ToString()); + } + catch + { + throw new ArgumentException("Invalid parameter provided, unable to convert to double"); + } + + // Get the current color in HSV + try + { + hsvColor = ((Color)value).ToHsv(); + } + catch + { + throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); + } + + double colorHue = hsvColor.H; + double colorSaturation = hsvColor.S; + double colorValue = hsvColor.V; + double colorAlpha = hsvColor.A; + + // Use the HSV representation as it's more perceptual + switch (shade) + { + case -3: + { + colorHue = colorHue * 1.0; + colorSaturation = colorSaturation * 1.10; + colorValue = colorValue * 0.40; + break; + } + case -2: + { + colorHue = colorHue * 1.0; + colorSaturation = colorSaturation * 1.05; + colorValue = colorValue * 0.50; + break; + } + case -1: + { + colorHue = colorHue * 1.0; + colorSaturation = colorSaturation * 1.0; + colorValue = colorValue * 0.75; + break; + } + case 0: + { + // No change + break; + } + case 1: + { + colorHue = colorHue * 1.00; + colorSaturation = colorSaturation * 1.00; + colorValue = colorValue * 1.05; + break; + } + case 2: + { + colorHue = colorHue * 1.00; + colorSaturation = colorSaturation * 0.75; + colorValue = colorValue * 1.05; + break; + } + case 3: + { + colorHue = colorHue * 1.00; + colorSaturation = colorSaturation * 0.65; + colorValue = colorValue * 1.05; + break; + } + } + + return Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(Math.Clamp(colorHue, 0.0, 360.0), + Math.Clamp(colorSaturation, 0.0, 1.0), + Math.Clamp(colorValue, 0.0, 1.0), + Math.Clamp(colorAlpha, 0.0, 1.0)); + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + string language) + { + throw new NotImplementedException(); + } + } + + /// + /// Converts a color to a hex string and vice versa. + /// + public class ColorToHexConverter : IValueConverter + { + public object Convert(object value, + Type targetType, + object parameter, + string language) + { + Color color; + + // Get the changing color to compare against + try + { + color = (Color)value; + } + catch + { + throw new ArgumentException("Invalid color value provided"); + } + + string hexColor = color.ToHex().Replace("#", ""); + return hexColor; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + string language) + { + string hexValue = value.ToString(); + + if (hexValue.StartsWith("#")) + { + try + { + return hexValue.ToColor(); + } + catch + { + throw new ArgumentException("Invalid hex color value provided"); + } + } + else + { + try + { + return ("#" + hexValue).ToColor(); + } + catch + { + throw new ArgumentException("Invalid hex color value provided"); + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml new file mode 100644 index 00000000000..fe0a00f78f1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -0,0 +1,748 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerSlider.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerSlider.cs new file mode 100644 index 00000000000..ce9be6cb522 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerSlider.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Foundation; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// A implementation for use in the . + /// + /// + /// + /// ColorPickerSlider is currently the same as Slider except it fixes a critical bug. + /// For details see: + /// + /// * https://github.com/microsoft/microsoft-ui-xaml/issues/477 + /// * https://social.msdn.microsoft.com/Forums/sqlserver/en-US/0d3a2e64-d192-4250-b583-508a02bd75e1/uwp-bug-crash-layoutcycleexception-because-of-slider-under-certain-circumstances?forum=wpdevelop + /// + /// An added benefit of being a separate, derived class is more logic can be added + /// here in the future just like ColorPicker. + /// + /// + public class ColorPickerSlider : Slider + { + private Size oldSize; + private Size measuredSize; + + protected override Size MeasureOverride(Size availableSize) + { + if (!Size.Equals(oldSize, availableSize)) + { + measuredSize = base.MeasureOverride(availableSize); + oldSize = availableSize; + } + + return measuredSize; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs new file mode 100644 index 00000000000..f260bf8be6e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.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. + /// + 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. + /// + 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); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 592fbb45626..551444a6805 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -4,6 +4,7 @@ + From d51bbc80111a444a01ede3d20765de53cc853743 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 15:48:37 -0400 Subject: [PATCH 02/73] Add the ColorPickerButton to SampleApp Added just to be functional. Requires further work. --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 7 +++++++ .../ColorPickerButtonPage.xaml | 14 +++++++++++++ .../ColorPickerButtonPage.xaml.cs | 20 +++++++++++++++++++ .../SamplePages/samples.json | 8 ++++++++ 4 files changed, 49 insertions(+) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index f59e5ef0f25..a31d1c563cc 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -516,6 +516,9 @@ + + ColorPickerButtonPage.xaml + TilesBrushPage.xaml @@ -995,6 +998,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml new file mode 100644 index 00000000000..08874e26172 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs new file mode 100644 index 00000000000..e82b33e08b6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Controls; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the control. + /// + public sealed partial class ColorPickerButtonPage : Page + { + public ColorPickerButtonPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 19d92d44eaa..584a7bff671 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -46,6 +46,14 @@ "Icon": "/SamplePages/Carousel/Carousel.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/Carousel.md" }, + { + "Name": "ColorPickerButton", + "Type": "ColorPickerButtonPage", + "Subcategory": "Input", + "About": "TBD", + "CodeUrl": "TBD", + "DocumentationUrl": "TBD" + }, { "Name": "AdaptiveGridView", "Type": "AdaptiveGridViewPage", From d173f9d301d1afcc4108d2a72cf33a8f29d91c2d Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 19:37:03 -0400 Subject: [PATCH 03/73] Rename 'ColorPickerSlider' to 'ColorPickerButtonSlider' --- .../ColorPickerButton/ColorPickerButton.xaml | 96 +++++++++---------- ...erSlider.cs => ColorPickerButtonSlider.cs} | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) rename Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/{ColorPickerSlider.cs => ColorPickerButtonSlider.cs} (96%) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index fe0a00f78f1..e60ce343373 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -45,7 +45,7 @@ + + + + + + @@ -525,10 +539,10 @@ + HorizontalAlignment="Stretch" /> @@ -549,9 +563,9 @@ VerticalAlignment="Center" /> + + + + + + + + \ No newline at end of file From 58108c93863835599cce9cc247fa216fdaf02801 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 20:17:11 -0400 Subject: [PATCH 05/73] Add XML comment --- .../ColorPickerButtonSlider.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButtonSlider.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButtonSlider.cs index b3aad81c7ba..ec9166b7bf9 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButtonSlider.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButtonSlider.cs @@ -11,22 +11,31 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// A implementation for use in the . /// /// - /// + /// /// ColorPickerSlider is currently the same as Slider except it fixes a critical bug. /// For details see: - /// + /// /// * https://github.com/microsoft/microsoft-ui-xaml/issues/477 /// * https://social.msdn.microsoft.com/Forums/sqlserver/en-US/0d3a2e64-d192-4250-b583-508a02bd75e1/uwp-bug-crash-layoutcycleexception-because-of-slider-under-certain-circumstances?forum=wpdevelop - /// + /// /// An added benefit of being a separate, derived class is more logic can be added /// here in the future just like ColorPicker. - /// + /// /// public class ColorPickerButtonSlider : Slider { private Size oldSize; private Size measuredSize; + /// + /// Measures the size in layout required for child elements and determines a size for the + /// FrameworkElement-derived class. + /// + /// The available size that this element can give to child elements. + /// Infinity can be specified as a value to indicate that the element will size to whatever content + /// is available. + /// The size that this element determines it needs during layout, + /// based on its calculations of child element sizes. protected override Size MeasureOverride(Size availableSize) { if (!Size.Equals(oldSize, availableSize)) From 3957abf2a4014367762b24e3944395254e0586c9 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 21:17:42 -0400 Subject: [PATCH 06/73] Several formatting changes to align with the toolkit --- .../ColorPickerButton/ColorPickerButton.cs | 280 +++++++++--------- 1 file changed, 145 insertions(+), 135 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 0fdf8d0d5dc..61cebd0e7a1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Toolkit.Uwp.Helpers; -using Microsoft.Toolkit.Uwp.UI.Extensions; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using Microsoft.Toolkit.Uwp.Helpers; +using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.Foundation; using Windows.UI; using Windows.UI.Xaml; @@ -18,6 +18,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// Presents a color spectrum, a palette of colors, and color channel sliders for user selection of a color. + /// [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelSlider), Type = typeof(Slider))] [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelTextBox), Type = typeof(TextBox))] [TemplatePart(Name = nameof(ColorPickerButton.Channel1Slider), Type = typeof(Slider))] @@ -123,10 +126,10 @@ private enum ColorChannel private TextBox Channel2TextBox; private TextBox Channel3TextBox; private TextBox AlphaChannelTextBox; - private Slider Channel1Slider; - private Slider Channel2Slider; - private Slider Channel3Slider; - private Slider AlphaChannelSlider; + private Slider Channel1Slider; + private Slider Channel2Slider; + private Slider Channel3Slider; + private Slider AlphaChannelSlider; private Border N1PreviewBorder; private Border N2PreviewBorder; @@ -152,14 +155,14 @@ private enum ColorChannel ***************************************************************************************/ /// - /// Constructor. + /// Initializes a new instance of the class. /// public ColorPickerButton() { this.DefaultStyleKey = typeof(ColorPickerButton); // Setup collections - base.SetValue(CustomPaletteColorsProperty, new ObservableCollection()); + this.SetValue(CustomPaletteColorsProperty, new ObservableCollection()); this.CustomPaletteColors.CollectionChanged += CustomPaletteColors_CollectionChanged; this.Loaded += ColorPickerButton_Loaded; @@ -175,7 +178,7 @@ public ColorPickerButton() } /// - /// Destructor. + /// Finalizes an instance of the class. /// ~ColorPickerButton() { @@ -183,14 +186,6 @@ public ColorPickerButton() this.CustomPaletteColors.CollectionChanged -= CustomPaletteColors_CollectionChanged; } - /*************************************************************************************** - * - * Property Accessors - * - ***************************************************************************************/ - - - /*************************************************************************************** * * Methods @@ -240,11 +235,11 @@ protected override void OnApplyTemplate() // Sync the active color if (this.ColorSpectrum != null) { - this.ColorSpectrum.Color = (Color)base.GetValue(ColorProperty); + this.ColorSpectrum.Color = (Color)this.GetValue(ColorProperty); } // Set initial state - if (base.IsEnabled == false) + if (this.IsEnabled == false) { VisualStateManager.GoToState(this, "Disabled", false); } @@ -264,16 +259,18 @@ protected override void OnApplyTemplate() /// Retrieves the named element in the instantiated ControlTemplate visual tree. /// /// The name of the element to find. + /// Whether the element is required and will throw an exception if missing. /// The template child matching the given name and type. - private T GetTemplateChild(string childName, bool isRequired = true) where T : DependencyObject + private T GetTemplateChild(string childName, bool isRequired = true) + where T : DependencyObject { - T child = base.GetTemplateChild(childName) as T; + T child = this.GetTemplateChild(childName) as T; if ((child == null) && isRequired) { throw new NullReferenceException(childName); } - return (child); + return child; } /// @@ -872,35 +869,36 @@ private async void UpdateChannelSliderBackground(Slider slider) int height = 0; Color baseColor = this.Color; - // Updates may be requested when sliders are not in the visual tree. - // For first-time load this is handled by the Loaded event. - // However, after that problems may arise, consider the following case: - // - // (1) Backgrounds are drawn normally the first time on Loaded. - // Actual height/width are available. - // (2) The palette tab is selected which has no sliders - // (3) The picker flyout is closed - // (4) Externally the color is changed - // The color change will trigger slider background updates but - // with the flyout closed, actual height/width are zero. - // No zero size bitmap can be generated. - // (5) The picker flyout is re-opened by the user and the default - // last-opened tab will be viewed: palette. - // No loaded events will be fired for sliders. The color change - // event was already handled in (4). The sliders will never - // be updated. - // - // In this case the sliders become out of sync with the Color because there is no way - // to tell when they actually come into view. To work around this, force a re-render of - // the background with the last size of the slider. This last size will be when it was - // last loaded or updated. - // - // In the future additional consideration may be required for SizeChanged of the control. - // This work-around will also cause issues if display scaling changes in the special - // case where cached sizes are required. + /* Updates may be requested when sliders are not in the visual tree. + * For first-time load this is handled by the Loaded event. + * However, after that problems may arise, consider the following case: + * + * (1) Backgrounds are drawn normally the first time on Loaded. + * Actual height/width are available. + * (2) The palette tab is selected which has no sliders + * (3) The picker flyout is closed + * (4) Externally the color is changed + * The color change will trigger slider background updates but + * with the flyout closed, actual height/width are zero. + * No zero size bitmap can be generated. + * (5) The picker flyout is re-opened by the user and the default + * last-opened tab will be viewed: palette. + * No loaded events will be fired for sliders. The color change + * event was already handled in (4). The sliders will never + * be updated. + * + * In this case the sliders become out of sync with the Color because there is no way + * to tell when they actually come into view. To work around this, force a re-render of + * the background with the last size of the slider. This last size will be when it was + * last loaded or updated. + * + * In the future additional consideration may be required for SizeChanged of the control. + * This work-around will also cause issues if display scaling changes in the special + * case where cached sizes are required. + */ if (slider != null) { - width = Convert.ToInt32(slider.ActualWidth); + width = Convert.ToInt32(slider.ActualWidth); height = Convert.ToInt32(slider.ActualHeight); if (width == 0 || height == 0) @@ -909,7 +907,7 @@ private async void UpdateChannelSliderBackground(Slider slider) if (this.cachedSliderSizes.ContainsKey(slider)) { Size cachedSize = this.cachedSliderSizes[slider]; - width = Convert.ToInt32(cachedSize.Width); + width = Convert.ToInt32(cachedSize.Width); height = Convert.ToInt32(cachedSize.Height); } } @@ -929,63 +927,69 @@ private async void UpdateChannelSliderBackground(Slider slider) if (object.ReferenceEquals(slider, this.Channel1Slider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel1, - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel1, + baseColor, + this.checkerBackgroundColor); } else if (object.ReferenceEquals(slider, this.Channel2Slider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel2, - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel2, + baseColor, + this.checkerBackgroundColor); } else if (object.ReferenceEquals(slider, this.Channel3Slider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel3, - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Channel3, + baseColor, + this.checkerBackgroundColor); } else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Alpha, - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Horizontal, + this.GetActiveColorRepresentation(), + ColorChannel.Alpha, + baseColor, + this.checkerBackgroundColor); } else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Vertical, - this.GetActiveColorRepresentation(), - ColorChannel.Alpha, - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Vertical, + this.GetActiveColorRepresentation(), + ColorChannel.Alpha, + baseColor, + this.checkerBackgroundColor); } else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) { - bitmap = await this.CreateChannelBitmapAsync(width, - height, - Orientation.Vertical, - ColorRepresentation.Hsva, // Always HSV - this.GetActiveColorSpectrumThirdDimension(), - baseColor, - this.checkerBackgroundColor); + bitmap = await this.CreateChannelBitmapAsync( + width, + height, + Orientation.Vertical, + ColorRepresentation.Hsva, // Always HSV + this.GetActiveColorSpectrumThirdDimension(), + baseColor, + this.checkerBackgroundColor); } if (bitmap != null) @@ -1025,8 +1029,6 @@ private void SetDefaultPalette() return; } - - /*************************************************************************************** * * Color Update Timer @@ -1065,7 +1067,7 @@ private void DispatcherTimer_Tick(object sender, object e) // An equality check here is important // Without it, OnColorChanged would continuously be invoked and preserveHsvColor overwritten when not wanted - if (object.Equals(newColor, base.GetValue(ColorProperty)) == false) + if (object.Equals(newColor, this.GetValue(ColorProperty)) == false) { // Disable events here so the color update isn't repeated as other controls in the UI are updated through binding. // For example, the Spectrum should be bound to Color, as soon as Color is changed here the Spectrum is updated. @@ -1075,7 +1077,7 @@ private void DispatcherTimer_Tick(object sender, object e) // 2. A performance hit recalculating for no reason // 3. preserveHsvColor gets overwritten unexpectedly by the ColorChanged handler this.ConnectEvents(false); - base.SetValue(ColorProperty, newColor); + this.SetValue(ColorProperty, newColor); this.ConnectEvents(true); } } @@ -1231,20 +1233,20 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) { /* If this control has a color that is currently empty (#00000000), * selecting a new color directly in the spectrum will fail. This is - * a bug in the color spectrum. Selecting a new color in the spectrum will + * a bug in the color spectrum. Selecting a new color in the spectrum will * keep zero for all channels (including alpha and the third dimension). - * + * * In practice this means a new color cannot be selected using the spectrum * until both the alpha and third dimension slider are raised above zero. * This is extremely user unfriendly and must be corrected as best as possible. - * + * * In order to work around this, detect when the color spectrum has selected - * a new color and then automatically set the alpha and third dimension + * a new color and then automatically set the alpha and third dimension * channel to maximum. However, the color spectrum has a second bug, the * ColorChanged event is never raised if the color is empty. This prevents * automatically setting the other channels where it normally should be done * (in the ColorChanged event). - * + * * In order to work around this second bug, the GotFocus event is used * to detect when the spectrum is engaged by the user. It's somewhat equivalent * to ColorChanged for this purpose. Then when the GotFocus event is fired @@ -1253,18 +1255,19 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) * in the spectrum. It is not available due to the afore mentioned bug or due to * timing. This means the best that can be done is to just set a 'neutral' * color such as white. - * - * There is still a small usability issue with this as it requires two - * presses to set a color. That's far better than having to slide up both + * + * There is still a small usability issue with this as it requires two + * presses to set a color. That's far better than having to slide up both * sliders though. - * + * * 1. If the color is empty, the first press on the spectrum will set white * and ignore the pressed color on the spectrum * 2. The second press on the spectrum will be correctly handled. - * + * */ - if (IsColorEmpty(this.Color)) // In the future Color.IsEmpty will hopefully be added to UWP + // In the future Color.IsEmpty will hopefully be added to UWP + if (IsColorEmpty(this.Color)) { // The following code may be used in the future if ever the selected color is available // @@ -1465,10 +1468,11 @@ private void ChannelSlider_ValueChanged(object sender, RangeBaseValueChangedEven /// public class HsvValueAdjustmentConverter : IValueConverter { - public object Convert(object value, - Type targetType, - object parameter, - string language) + public object Convert( + object value, + Type targetType, + object parameter, + string language) { double valueDelta; HsvColor hsvColor; @@ -1502,16 +1506,18 @@ public object Convert(object value, A = hsvColor.A, }; - return Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(hsvColor.H, - hsvColor.S, - hsvColor.V, - hsvColor.A); + return Uwp.Helpers.ColorHelper.FromHsv( + hsvColor.H, + hsvColor.S, + hsvColor.V, + hsvColor.A); } - public object ConvertBack(object value, - Type targetType, - object parameter, - string language) + public object ConvertBack( + object value, + Type targetType, + object parameter, + string language) { throw new NotImplementedException(); } @@ -1523,10 +1529,11 @@ public object ConvertBack(object value, /// public class AccentColorShadeConverter : IValueConverter { - public object Convert(object value, - Type targetType, - object parameter, - string language) + public object Convert( + object value, + Type targetType, + object parameter, + string language) { int shade; HsvColor hsvColor; @@ -1614,10 +1621,11 @@ public object Convert(object value, Math.Clamp(colorAlpha, 0.0, 1.0)); } - public object ConvertBack(object value, - Type targetType, - object parameter, - string language) + public object ConvertBack( + object value, + Type targetType, + object parameter, + string language) { throw new NotImplementedException(); } @@ -1628,10 +1636,11 @@ public object ConvertBack(object value, /// public class ColorToHexConverter : IValueConverter { - public object Convert(object value, - Type targetType, - object parameter, - string language) + public object Convert( + object value, + Type targetType, + object parameter, + string language) { Color color; @@ -1649,10 +1658,11 @@ public object Convert(object value, return hexColor; } - public object ConvertBack(object value, - Type targetType, - object parameter, - string language) + public object ConvertBack( + object value, + Type targetType, + object parameter, + string language) { string hexValue = value.ToString(); From 84e5106b5e5bff397127d821b6dfaa458bd32082 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 21:32:04 -0400 Subject: [PATCH 07/73] Several formatting changes to align with the toolkit --- .../ColorPickerButton.Rendering.cs | 177 +++++++++--------- 1 file changed, 93 insertions(+), 84 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs index fdc7a7ba6b9..1faae2cf6c3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Toolkit.Uwp.Helpers; using System; using System.IO; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.UI; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; @@ -14,6 +14,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// Contains the rendering methods used within . + /// public partial class ColorPickerButton { /// @@ -26,14 +29,16 @@ public partial class ColorPickerButton /// The color representation being used: RGBA or HSVA. /// The specific color channel to vary. /// The base RGB color used for channels not being changed. + /// The color of the checker background square. /// A new bitmap representing a gradient of color channel values. - private async Task CreateChannelBitmapAsync(int width, - int height, - Orientation orientation, - ColorRepresentation colorRepresentation, - ColorChannel channel, - Color baseRgbColor, - Color? checkerColor) + private async Task CreateChannelBitmapAsync( + int width, + int height, + Orientation orientation, + ColorRepresentation colorRepresentation, + ColorChannel channel, + Color baseRgbColor, + Color? checkerColor) { if (width == 0 || height == 0) { @@ -70,9 +75,10 @@ private async Task CreateChannelBitmapAsync(int width, // Create a checkered background if (checkerColor != null) { - bgraCheckeredPixelData = await this.CreateCheckeredBitmapAsync(width, - height, - checkerColor.Value); + bgraCheckeredPixelData = await this.CreateCheckeredBitmapAsync( + width, + height, + checkerColor.Value); } // Create the color channel gradient @@ -182,37 +188,38 @@ private async Task CreateChannelBitmapAsync(int width, { for (int x = 0; x < width; x++) { - // The following algorithm is used to blend the two bitmaps creating the final composite. - // In this formula, pixel data is normalized 0..1, actual pixel data is in the range 0..255. - // The color channel gradient should apply OVER the checkered background. - // - // R = R0 * A0 * (1 - A1) + R1 * A1 = RA0 * (1 - A1) + RA1 - // G = G0 * A0 * (1 - A1) + G1 * A1 = GA0 * (1 - A1) + GA1 - // B = B0 * A0 * (1 - A1) + B1 * A1 = BA0 * (1 - A1) + BA1 - // A = A0 * (1 - A1) + A1 = A0 * (1 - A1) + A1 - // - // Considering only the red channel, some algebraic transformation is applied to - // make the math quicker to solve. - // - // => ((RA0 / 255.0) * (1.0 - A1 / 255.0) + (RA1 / 255.0)) * 255.0 - // => ((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255 + /* The following algorithm is used to blend the two bitmaps creating the final composite. + * In this formula, pixel data is normalized 0..1, actual pixel data is in the range 0..255. + * The color channel gradient should apply OVER the checkered background. + * + * R = R0 * A0 * (1 - A1) + R1 * A1 = RA0 * (1 - A1) + RA1 + * G = G0 * A0 * (1 - A1) + G1 * A1 = GA0 * (1 - A1) + GA1 + * B = B0 * A0 * (1 - A1) + B1 * A1 = BA0 * (1 - A1) + BA1 + * A = A0 * (1 - A1) + A1 = A0 * (1 - A1) + A1 + * + * Considering only the red channel, some algebraic transformation is applied to + * make the math quicker to solve. + * + * => ((RA0 / 255.0) * (1.0 - A1 / 255.0) + (RA1 / 255.0)) * 255.0 + * => ((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255 + */ // Bottom layer - byte RA0 = bgraCheckeredPixelData[pixelDataIndex + 2]; - byte GA0 = bgraCheckeredPixelData[pixelDataIndex + 1]; - byte BA0 = bgraCheckeredPixelData[pixelDataIndex + 0]; - byte A0 = bgraCheckeredPixelData[pixelDataIndex + 3]; + byte rXa0 = bgraCheckeredPixelData[pixelDataIndex + 2]; + byte gXa0 = bgraCheckeredPixelData[pixelDataIndex + 1]; + byte bXa0 = bgraCheckeredPixelData[pixelDataIndex + 0]; + byte a0 = bgraCheckeredPixelData[pixelDataIndex + 3]; // Top layer - byte RA1 = bgraPixelData[pixelDataIndex + 2]; - byte GA1 = bgraPixelData[pixelDataIndex + 1]; - byte BA1 = bgraPixelData[pixelDataIndex + 0]; - byte A1 = bgraPixelData[pixelDataIndex + 3]; + byte rXa1 = bgraPixelData[pixelDataIndex + 2]; + byte gXa1 = bgraPixelData[pixelDataIndex + 1]; + byte bXa1 = bgraPixelData[pixelDataIndex + 0]; + byte a1 = bgraPixelData[pixelDataIndex + 3]; - bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(((BA0 * 255) - (BA0 * A1) + (BA1 * 255)) / 255); - bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(((GA0 * 255) - (GA0 * A1) + (GA1 * 255)) / 255); - bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255); - bgraPixelData[pixelDataIndex + 3] = Convert.ToByte(((A0 * 255) - (A0 * A1) + (A1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(((bXa0 * 255) - (bXa0 * a1) + (bXa1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(((gXa0 * 255) - (gXa0 * a1) + (gXa1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(((rXa0 * 255) - (rXa0 * a1) + (rXa1 * 255)) / 255); + bgraPixelData[pixelDataIndex + 3] = Convert.ToByte(((a0 * 255) - (a0 * a1) + (a1 * 255)) / 255); pixelDataIndex += 4; } @@ -230,22 +237,22 @@ Color GetColor(double channelValue) if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep hue - newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv - ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv ( Math.Clamp(channelValue, 0.0, 360.0), baseHsvColor.S, baseHsvColor.V, - baseHsvColor.A - ); + baseHsvColor.A); } else { // Sweep red - newRgbColor = new Color(); - newRgbColor.R = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); - newRgbColor.G = baseRgbColor.G; - newRgbColor.B = baseRgbColor.B; - newRgbColor.A = baseRgbColor.A; + newRgbColor = new Color + { + R = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)), + G = baseRgbColor.G, + B = baseRgbColor.B, + A = baseRgbColor.A + }; } break; @@ -255,22 +262,22 @@ Color GetColor(double channelValue) if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep saturation - newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv - ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv ( baseHsvColor.H, Math.Clamp(channelValue, 0.0, 1.0), baseHsvColor.V, - baseHsvColor.A - ); + baseHsvColor.A); } else { // Sweep green - newRgbColor = new Color(); - newRgbColor.R = baseRgbColor.R; - newRgbColor.G = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); - newRgbColor.B = baseRgbColor.B; - newRgbColor.A = baseRgbColor.A; + newRgbColor = new Color + { + R = baseRgbColor.R, + G = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)), + B = baseRgbColor.B, + A = baseRgbColor.A + }; } break; @@ -280,22 +287,22 @@ Color GetColor(double channelValue) if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep value - newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv - ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, baseHsvColor.S, Math.Clamp(channelValue, 0.0, 1.0), - baseHsvColor.A - ); + baseHsvColor.A); } else { // Sweep blue - newRgbColor = new Color(); - newRgbColor.R = baseRgbColor.R; - newRgbColor.G = baseRgbColor.G; - newRgbColor.B = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); - newRgbColor.A = baseRgbColor.A; + newRgbColor = new Color + { + R = baseRgbColor.R, + G = baseRgbColor.G, + B = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)), + A = baseRgbColor.A + }; } break; @@ -305,22 +312,22 @@ Color GetColor(double channelValue) if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep alpha - newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv - ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, baseHsvColor.S, baseHsvColor.V, - Math.Clamp(channelValue, 0.0, 1.0) - ); + Math.Clamp(channelValue, 0.0, 1.0)); } else { // Sweep alpha - newRgbColor = new Color(); - newRgbColor.R = baseRgbColor.R; - newRgbColor.G = baseRgbColor.G; - newRgbColor.B = baseRgbColor.B; - newRgbColor.A = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)); + newRgbColor = new Color + { + R = baseRgbColor.R, + G = baseRgbColor.G, + B = baseRgbColor.B, + A = Convert.ToByte(Math.Clamp(channelValue, 0.0, 255.0)) + }; } break; @@ -348,11 +355,12 @@ Color GetColor(double channelValue) /// The pixel height (Y, vertical) of the checkered bitmap. /// The color of the checker square. /// A new checkered bitmap of the specified size. - private async Task CreateCheckeredBitmapAsync(int width, - int height, - Color checkerColor) + private async Task CreateCheckeredBitmapAsync( + int width, + int height, + Color checkerColor) { - // The size of the checker is important. You want it big enough that the grid is clearly discernible. + // The size of the checker is important. You want it big enough that the grid is clearly discernible. // However, the squares should be small enough they don't appear unnaturally cut at the edge of backgrounds. int checkerSize = 4; @@ -368,7 +376,7 @@ private async Task CreateCheckeredBitmapAsync(int width, // Allocate the buffer // BGRA formatted color channels 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new byte[width * height * 4]; for (int y = 0; y < height; y++) { @@ -379,7 +387,7 @@ private async Task CreateCheckeredBitmapAsync(int width, // depending on both its x- and its y-position. If x == CheckerSize, we'll turn visibility off, // but then if y == CheckerSize, we'll turn it back on. // The below is a shorthand for the above intent. - bool pixelShouldBeBlank = (x / checkerSize + y / checkerSize) % 2 == 0 ? true : false; + bool pixelShouldBeBlank = ((x / checkerSize) + (y / checkerSize)) % 2 == 0 ? true : false; // Remember, use BGRA pixel format with pre-multiplied alpha values if (pixelShouldBeBlank) @@ -408,16 +416,17 @@ private async Task CreateCheckeredBitmapAsync(int width, } /// - /// Converts the given bitmap (in raw BGRA pre-multiplied alpha pixels) into an image brush + /// Converts the given bitmap (in raw BGRA pre-multiplied alpha pixels) into an image brush /// that can be used in the UI. /// /// The bitmap (in raw BGRA pre-multiplied alpha pixels) to convert to a brush. /// The pixel width of the bitmap. /// The pixel height of the bitmap. /// A new ImageBrush. - private async Task BitmapToBrushAsync(byte[] bitmap, - int width, - int height) + private async Task BitmapToBrushAsync( + byte[] bitmap, + int width, + int height) { var writableBitmap = new WriteableBitmap(width, height); using (Stream stream = writableBitmap.PixelBuffer.AsStream()) @@ -428,7 +437,7 @@ private async Task BitmapToBrushAsync(byte[] bitmap, var brush = new ImageBrush() { ImageSource = writableBitmap, - Stretch = Stretch.None + Stretch = Stretch.None }; return brush; From 1fe4104a0fa0ac0293a09e94dc46fa512218d56d Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 22:21:30 -0400 Subject: [PATCH 08/73] Add ColorPickerButton icon and description --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 1 + .../ColorPickerButton/ColorPickerButton.png | Bin 0 -> 7476 bytes .../SamplePages/samples.json | 3 ++- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButton.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index a31d1c563cc..95b86aea253 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -271,6 +271,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButton.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButton.png new file mode 100644 index 0000000000000000000000000000000000000000..9a821466c4e9f93b3c62228b115fc467b7ba5021 GIT binary patch literal 7476 zcmb7|1yGw&wC97n7K&3GO3~mD+>1-G;#wNKxVsbyRvd~Hm*UdkPO(Cf;!vQtJ1MYV zXJ=>LzTJ8Ib~4H2OTK%*d(S=R{D0@fXsRpXVN+s*Kp;G2B{^*n2#F5(-i?V4e156z zYzBd_;(T=t+_f#d=v>^KZS5Uw=-ho=Z0Kyf?QKCI@5Q4GXfjg^Uij1eOMg`2brcA` zaw1V@2<(JFprHY(Okd3z5!fbHQu*SIJaOi*>coe~t4YM)u&9it(itbm#x(C#tw_~X zgZoF}&qJ^iH};c*7R5*Z@kyBE*xg;jX}sGE4A$Wv`1SKz5L^o(>Q=ONOFwUBoNu-{ z3PZU$K^YAidpFPj6Gnh|^!mEgv18@Y!2kV+#PciO{);2QoYu1B&UAsi+^+A4M+poK ztBhQg^TwaQ+b)Ka1y+pfC4K&6Yj`GzIsY-(QrcmwmtQ+kh2t({@k3+mI;TC#D$94% ziYm6X?y^(@hiFc`#i$tl{^*V@b9@*%k@zerx?z`hb1WC{mSBk-@CY(sav9cAh}Ty~ zN%WU=t#l6Q67Qm&BVrAknI3LeTX7=k940oivwCLDgIQGfhxG?eaOWi}4KjUh(+(f6i;asdluvm& z>f%S2R(3f)8;i4VojQ)Lp7m~7nK&vF`Pn2aTmzf`+D*8dqjy z#mT`##GhtTEGgC-0m@nZT-E-2h2sn<;U`)=6S1AMHZD!ALI1xDl1!}$rnbtFL3g-h zfg%@(A;rM+eluFJJ?lFzsiKAJz{EA(VDGd#M%w~eA%^5W7OMfqVB9oH%XuM6wo!ba zZLW0xx7RadTRzr!ZGsQK=Sxon|CmnfMa(iTs3MqjWIGguNw7K-E`oOmHKl^ z^1&@=m$VM2Fl|uMgLj!;`ZCz2qhcZ)15EzdlL|O}@IXz~4Q^cvH@b;pu6H=b<8wFs zwjAR6wkkP;zL#?TT#D4*R@8%+ZDm%Tsz%3#Rq11_`w6M(TZVquzUA4{<_*U!QLl(z zS;|Yh8s+M?KAm3rysG|@_K)C(_pm%IA6bJFf%4EWg_1&EV%uGn8}Uz`n7)MLZf=oE zP`zPvvVx>9C4O>0lYMiT&#>(o_&VcN$P7p2YYypjYXWLG#N8e^XI|A7FOWjNF$-jQ zrqx>~X5QOl--&H}`X-M8pR{aIev7DaOQbGsM_Uu)uNz$OD;sk?eEGrR@zP%WW?})R zlcl{E!!_Q2w&SteTtCg09r>n}Dg0_f>??WhRxbTkaeEJzh-ayzc;pLY*fRy2-$gF2A3kWw3dRH z3WIFj|_56AzW&c4}(+*!6M`WhkrvbAWscGny!ubklN|5h!B zVb;y*4Ew!{ly#^Nm8Ip$fZX^KbIn37X%ac9;9~AkBr!rFn1u@@k~YKAcSW~1svHh- zh)HGJO|9r5&Fxy##^HG$bsVdYaiwpy;-I8EOiI%Rj2VZ`b@GeOiWzpS3hJuD-Osj1 zXI@O#Iup)jCbSwe%113R@?^$%AS0xVU0ky!><6jU<=>)Dj5;^*F(Rep z#I&*VQ=_pYrwCfrL@U`glpn;LU5FxiZVt)v5r<<{s7gE&-9_!M{b45iS{y~sa1b+{ zdm7zP!F=G`-H3VD2=@m$edACrQO%GS2SbI@FYQ;tw_TFJ{dWCU5s~%3MlWZ-zbr~p zcwH#hze7ljE`4YAXw)huzwMeni)!7ua8v+xK#!t^(EpIRGxdeAIV*7%CO^)c-DN)W zv&L1DnFAyLRCDjM@$}$WcdQzIptt?>3L4L#vkNAqtC_7#!R47{sgv!c6KoDkG?VVo zVeRgh#NU_J*SC+B6ic+l$u^z})uIs;E-ivF?#e~SJwcaewXOT+)~DqaU>Anf&lb3l z6Moi6lQ0?zHua@N-Lk3Uiwu{Y=om#|D#JQyV`(vTG$|Fi2V6$$zL{4X6^!dR+|8xO zuk1p<1+uWHWASW9_!1Kt6j`W|CDGm3qW2Y@25QUsstb{PdE-yA(8JPLHD^J?UxJZ; z)1OIX#DDmXJhI5HZoAZRe4Y!RR9w=5oGK>WAyjy4JVCG~qrctY$5ABQWMX$PkkCu< z^tN3KR-7_!PyXu8O(Hsuz=0Vwc@x8?23gK%3YYGZX8*gm2 zyONz|IMnJPRRbct65tT~(}%i!i&b)|5Tl5S)CPja&}jt<>}e@?IT<#x`|c7dJ5C36 zr7+qgKYe+X&oWI$DY%;}=`ppgSgnx9X{bt)^0ZHOYeVQZ@8aho3q!?n+ z?!%e`J)vdMMY?sE&tJbSc!QCW{wSRi$uuya<-#d^+hE|~;yO3m^M(*LNGgXqm&D{t zKO$KsmYf^1I^=Uhy$UBFq$DlLGIdhu4e#kkknegQc`q^Fm7Fgx%H;jG zQuPWr+vNE%qT&8i`v~Z>5@g7|k71QAZ=unB0?|*emrU~e!*ACxE~UT29t+z-6Y^1L zc@$u9xzBIHI)g*=wlKpH&6v^{#*Yc>a?Z z8*Tj|uJa`Z?kaKs^my55M>qfk=C9nQT3;t5=hVLbHU#$*+qrhufCftH?Yrd^rK-;Y zyo_$Cq9_MC`1j&xQwDwjVYw(7egJ{+`~JO=hMdc+K_H?^WjSeG0GwAGHFdX+^TB{!~jV5mrmTH6upTlKIgdH=YrxW=Xjoyq;gKnw1ru4_*w%5)D-{7tu?}+Ono7 zhyfQeqj&nVp;{O3VYgzkj~>_iNqT!6Zya*r9M*IE4Km9K@D#DT$=M8=xX9TV!0+yo zma|5~e?qjxMTJIx%N)xa3XZOdK$4TgrDL!J5fSkQM@Iu!{U8PgbV4E`l;~)3ssH-V zf4BY{3EcW`Brwmvk^klaM*gQi#WTW4Z%q37G-irg&+((94bT|>+hhO3#Q)6&CQ0K% zP_2i5)mafee~#|#?A!{6Tiu>+xVXCZbcdi!bp-_p3q#uc`6=;c-V4x*`-Yd4u*}TO z$#A7_4#ranyKP~zv$K2iD07Q}p3pUIBLe16Pj4U_8ygvtK@u#g86Y8{7CZW%hVPpw z-P)*aY;1&m&YAFENQ{Loe`xc(c(lf)3x&Z5ByQJHP*G94$H&JD5fwEx3Kkah)6>(Z zOU_+q>wLlFQUSB=J{PsNk0oGH%PsJn`GJp%*@2aw**WPRsMH zkzbtwVxgg->$|(}rq>#qn`H_#^9u@);^X7rIXOkXE=C67lJkJn)z!%%-aKElUq<57 zd<=ZJ&ula)){Y4cMRr(h!~#|d5y7QH*K|%xO$~2t6~?6zo!Q+1?C^bb)b(JFlZ1?H z^Y(0$MTo`E*O!5xpQN+1^Yr>UGCf@%9-?bz2D`j;cUWqUXN^JX2kkprgDI|$mZB*5 zY`Z5W)KBc(MQPYyz0xu^jypVb$PxFO<3Qx`+2J!VFsvUO#A+2OP^<(2HqiTOIu55t zCqyZ0G3f9WASNc}arnj4aRO~JN=-}q4II(%_wV()UNH#?3Jwk&z*7#7uDoO`D=TH) z+`xbZz0ZApKvyX1XzP-Cw{>TbwX_ziA$%q#CiBzv9^Vt;*D5MW85z-lsZvu@4gMmG zJA6qqGc#MgPH2~xmrr|fxT7UD4-exE8*F<g4ILe1RxSpQj7hWa$)yfLVn8lb#XBT0)xTw7vI_0VPvYJD=I2BV;SnTv>@>Y`zF1q)UyDV zMb2X$oZntoM?@v$)L&x)7j0T;`>-PcEz1-0;-Zxdj9Y4Pe!C?E9Nlv@HMI<0tH}J6 zFC87?CF;2!ffzCF^v`64r=t=s1^Z$Fag`$(MB5idK(=yMW!6QDxIQ{?_i);qE)L~+ zXK&xL(HB)`Ie;DekS740qhn@9BaV^NG}YIST3f=FK?I{XWLVZX6!sN=iyf2hrQv z*~L)_;kUK5LH0V<@uTB^bVylRG9V)(%WaE@h$ty3?OuB7*BINoU`7DDSoJ#*kA18e z&lWns%gW@lvji*xIqLvbnhUD4q--p=eegbR+dRrXWj!7b5G6mgP0#M(Xo6EDa9`3Hc$PqF~ z@d_vrG9iEe`m5$a+E%(u;GUe6l$5A8eUW&56S-nONWX;96Y+4cu%umFYV%b~e_A{} zKCp3d9iLyHtad;zANKd(r!;Tw?Zs>k!5D>w$+gQ#9`3KgblC?E7aEe%)90NqNl8iR z85w1DX{o8>5)%ncF>&eYBRvl0V)BTA=i$9=cHkYo=Hug=T3oES5eGI>qzKDu%zZ%I zQ03UWZVe}~2qZf%w-OZ>7l(pluLvjdDqJG-qs#7X|WY8iyeR z+pCu^d!S!CY%foUpFR5=@aJYNNXQ3qvaj#3tYigeBLV--9S7l(~GN!AkNeJfS+t}V_ zW_vpL+C@l1<5>3y=}t+yk&VRVy$|%`6_c>X3ur|AmZX}xcn!fkwPOI(qm(Gv3n=sFor6kXc zy!E;2x1sZZSG3&z;{HYHFx+PJc@g~^Q`3b0ez|N>{QJd_LD-8)oNy$$ zw3+KYeJJ!*0*%<*@mjSBT+bQPq$_Zx@Z#R!`$!6_d@eC(M~!&qi=|)()g`PU66^@& zBig&WJ95cDNODRFuj_9)kVd4VyUjOz=sjQw*QdvTo-^;qyB(hp9$>ADH7B#4sos8L z0_s()@7}#5L`{s1#o;E@>lsc)$ERlVepv4blknr9q@s$arLqJiWi|yMm*}!14(r~D zDb4-bvIj8a!%XH9?xJqFZosucJh*Dd?{bts-n=oH8~+0~o*^vcT0Y~0*YuZu%p7uSy*gJuM%@tm#p4-U|D zYUgHVqJ0&pegg`(wPih)Bcf9~mQYs<_yw`Hwzeq$L*&bUBKhl=!4yA@QT6jEd>U{$ zw0j_y4DAC$xh{K3AQ?sf)Ty;G;D)CkT|ERu@ej*$a;UdG0$e0MPGTUv(dZAMkUA>7>Dh}-olXWOL!xKd$fV7C}+w8L`6jzAQ0-EiCn(XqT||H9$_&tDt|MEq-d76KT93c z!xdq)XUKKD4owZbRDurOh4QiM8yobP0P113pAAx#RaD3TCl6o-1Okaek5n)1TY-CN z>FA&oYh#g-k;ReoA~Px`kf`2X>6)9loS8j?VvP#;&od#39f8tVLRoI2=d^6Kls;ZaeH zJUlcd@%9x0kEiAeVPGS>v-Bt*eu|R6p%lM(}%RQ=hPx@{kwvzfe6Q`it0!f zaYl5)OhZ6c^s0@R-=_+00jV2MmhNz@XR4;^tbpN8+Ak8=!l`L!#2~R7PY6J~60@@_ zy+%II&FT5yZKn%=_!Bqfxcm01sl#5mY-bOn8>c&{s`kX2_OYhT^N8~Lcm)TLG9@Nr zC8lJ%+_Tiz%s>ph0N90&kr5^KIj4+;g+N+vHnWTFOBS1%!mG0Cj|jRE26e@9eYtE1jQi$aZ{gm;wLb<>f6z zs*;zNr!oDNNG@e<1-Os=&%nPAH*1BHhVLnMQ&UN%Ry0&paI|%FeB8*TW|lbLHmlBD z7drRIs&Jqjot(fjGRU=jq@_a^c3uMY%oPt{`V5JHZ~3al+8*Vivqa_P<%LNn*b#;L z)u%lm2CzAx=7m^mI5;@28@*^#cHsSb_uK7Nl5EJ!Qq>ph@QuRB5pfIo^qX=lM@G%xL3MteFBIQ6BBcNgjW)O zAU}^84A$25Jp9EQwtJEs7iQykRl4j+sj2<0X1H|wI6xrLu_GQ4Cu)y~34UT$R#rv{2~!dY z%6N2On@TDwn=uw3Ewj__ufAfM3JD0%zkE4ji4gNX1!_G8xmm7U^(cUY9ah^jS&P#x zs0bn4xy<*p|yH$3P`0w_nu!1fdi`1k{KML^jor<0E?rKW~gQBjev zUCv|P4FcjCmja9mz?*CozCc@tQn_wne*V9*!V*LF;NZG|9G4FK{_cDmSS^_c?qLPM z_CO&(FCg$q=l$gd$dWWJ?)$_rIkD)kcqz@4H0$8at#JV3r)|NR>o;Ock$lx4H;A-L|OlrKEZ#e_5b%K!Z01itK+3lQJN>%tqz1gD6;16V9H5NoG9=F{8HFK2Z)#Ykr%tQYNVgUcX literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 584a7bff671..2d499ed82f0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -50,8 +50,9 @@ "Name": "ColorPickerButton", "Type": "ColorPickerButtonPage", "Subcategory": "Input", - "About": "TBD", + "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", "CodeUrl": "TBD", + "Icon": "/SamplePages/ColorPickerButton/ColorPickerButton.png", "DocumentationUrl": "TBD" }, { From 459de36edb48cd12c25d20d0d9ac46d3ba1ad722 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 22:56:56 -0400 Subject: [PATCH 09/73] Fully implement all color channel TextBoxes This completes the switch from NumberBox to TextBox --- .../ColorPickerButton/ColorPickerButton.cs | 146 ++++++++++++------ 1 file changed, 102 insertions(+), 44 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 61cebd0e7a1..1d0a7584ff1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Globalization; using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.Foundation; @@ -253,6 +254,7 @@ protected override void OnApplyTemplate() base.OnApplyTemplate(); this.isInitialized = true; + this.UpdateChannelControlValues(); } /// @@ -316,13 +318,16 @@ private void ConnectEvents(bool connected) if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged += ColorRepresentationComboBox_SelectionChanged; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown += HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus += HexInputTextBox_LostFocus; } - if (this.HexInputTextBox != null) { this.HexInputTextBox.TextChanged += HexInputTextBox_TextChanged; } if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } - //if (this.Channel1NumberBox != null) { this.Channel1NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } - //if (this.Channel2NumberBox != null) { this.Channel2NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } - //if (this.Channel3NumberBox != null) { this.Channel3NumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } - //if (this.AlphaChannelNumberBox != null) { this.AlphaChannelNumberBox.ValueChanged += ChannelNumberBox_ValueChanged; } + if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown += ChannelTextBox_KeyDown; } + if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown += ChannelTextBox_KeyDown; } + if (this.Channel3TextBox != null) { this.Channel3TextBox.KeyDown += ChannelTextBox_KeyDown; } + if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.KeyDown += ChannelTextBox_KeyDown; } + if (this.Channel1TextBox != null) { this.Channel1TextBox.LostFocus += ChannelTextBox_LostFocus; } + if (this.Channel2TextBox != null) { this.Channel2TextBox.LostFocus += ChannelTextBox_LostFocus; } + if (this.Channel3TextBox != null) { this.Channel3TextBox.LostFocus += ChannelTextBox_LostFocus; } + if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.LostFocus += ChannelTextBox_LostFocus; } if (this.Channel1Slider != null) { this.Channel1Slider.ValueChanged += ChannelSlider_ValueChanged; } if (this.Channel2Slider != null) { this.Channel2Slider.ValueChanged += ChannelSlider_ValueChanged; } @@ -365,13 +370,16 @@ private void ConnectEvents(bool connected) if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged -= ColorRepresentationComboBox_SelectionChanged; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown -= HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus -= HexInputTextBox_LostFocus; } - if (this.HexInputTextBox != null) { this.HexInputTextBox.TextChanged -= HexInputTextBox_TextChanged; } if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } - //if (this.Channel1TextBox != null) { this.Channel1TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } - //if (this.Channel2TextBox != null) { this.Channel2TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } - //if (this.Channel3TextBox != null) { this.Channel3TextBox.ValueChanged -= ChannelTextBox_ValueChanged; } - //if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.ValueChanged -= ChannelTextBox_ValueChanged; } + if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown -= ChannelTextBox_KeyDown; } + if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown -= ChannelTextBox_KeyDown; } + if (this.Channel3TextBox != null) { this.Channel3TextBox.KeyDown -= ChannelTextBox_KeyDown; } + if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.KeyDown -= ChannelTextBox_KeyDown; } + if (this.Channel1TextBox != null) { this.Channel1TextBox.LostFocus -= ChannelTextBox_LostFocus; } + if (this.Channel2TextBox != null) { this.Channel2TextBox.LostFocus -= ChannelTextBox_LostFocus; } + if (this.Channel3TextBox != null) { this.Channel3TextBox.LostFocus -= ChannelTextBox_LostFocus; } + if (this.AlphaChannelTextBox != null) { this.AlphaChannelTextBox.LostFocus -= ChannelTextBox_LostFocus; } if (this.Channel1Slider != null) { this.Channel1Slider.ValueChanged -= ChannelSlider_ValueChanged; } if (this.Channel2Slider != null) { this.Channel2Slider.ValueChanged -= ChannelSlider_ValueChanged; } @@ -494,6 +502,57 @@ private void ScheduleColorUpdate(Color newColor) return; } + /// + /// Applies the value of the given color channel TextBox to the current color. + /// + /// The color channel TextBox to apply the value from. + private void ApplyChannelTextBoxValue(TextBox channelTextBox) + { + double channelValue; + + if (channelTextBox != null) + { + try + { + if (string.IsNullOrWhiteSpace(channelTextBox.Text)) + { + // An empty string is allowed and happens when the clear TextBox button is pressed + // This case should be interpreted as zero + channelValue = 0; + } + else + { + channelValue = double.Parse(channelTextBox.Text, CultureInfo.CurrentUICulture); + } + + if (object.ReferenceEquals(channelTextBox, this.Channel1TextBox)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel1, channelValue); + } + else if (object.ReferenceEquals(channelTextBox, this.Channel2TextBox)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel2, channelValue); + } + else if (object.ReferenceEquals(channelTextBox, this.Channel3TextBox)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel3, channelValue); + } + else if (object.ReferenceEquals(channelTextBox, this.AlphaChannelTextBox)) + { + this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, channelValue); + } + } + catch + { + // Reset TextBox values + this.UpdateChannelControlValues(); + this.UpdateChannelSliderBackgrounds(); + } + } + + return; + } + /// /// Updates the color channel values in all editing controls to match the current color. /// @@ -591,7 +650,8 @@ private void UpdateChannelControlValues() // Hue if (this.Channel1TextBox != null) { - this.Channel1TextBox.Text = hue.ToString(); + this.Channel1TextBox.MaxLength = 3; + this.Channel1TextBox.Text = hue.ToString(CultureInfo.CurrentUICulture); } if (this.Channel1Slider != null) @@ -604,7 +664,8 @@ private void UpdateChannelControlValues() // Saturation if (this.Channel2TextBox != null) { - this.Channel2TextBox.Text = staturation.ToString(); + this.Channel2TextBox.MaxLength = 3; + this.Channel2TextBox.Text = staturation.ToString(CultureInfo.CurrentUICulture); } if (this.Channel2Slider != null) @@ -617,7 +678,8 @@ private void UpdateChannelControlValues() // Value if (this.Channel3TextBox != null) { - this.Channel3TextBox.Text = value.ToString(); + this.Channel3TextBox.MaxLength = 3; + this.Channel3TextBox.Text = value.ToString(CultureInfo.CurrentUICulture); } if (this.Channel3Slider != null) @@ -630,7 +692,8 @@ private void UpdateChannelControlValues() // Alpha if (this.AlphaChannelTextBox != null) { - this.AlphaChannelTextBox.Text = alpha.ToString(); + this.AlphaChannelTextBox.MaxLength = 3; + this.AlphaChannelTextBox.Text = alpha.ToString(CultureInfo.CurrentUICulture); } if (this.AlphaChannelSlider != null) @@ -653,7 +716,8 @@ private void UpdateChannelControlValues() // Red if (this.Channel1TextBox != null) { - this.Channel1TextBox.Text = rgbColor.R.ToString();; + this.Channel1TextBox.MaxLength = 3; + this.Channel1TextBox.Text = rgbColor.R.ToString(CultureInfo.CurrentUICulture); } if (this.Channel1Slider != null) @@ -666,7 +730,8 @@ private void UpdateChannelControlValues() // Green if (this.Channel2TextBox != null) { - this.Channel2TextBox.Text = rgbColor.G.ToString();; + this.Channel2TextBox.MaxLength = 3; + this.Channel2TextBox.Text = rgbColor.G.ToString(CultureInfo.CurrentUICulture); } if (this.Channel2Slider != null) @@ -679,7 +744,8 @@ private void UpdateChannelControlValues() // Blue if (this.Channel3TextBox != null) { - this.Channel3TextBox.Text = rgbColor.B.ToString();; + this.Channel3TextBox.MaxLength = 3; + this.Channel3TextBox.Text = rgbColor.B.ToString(CultureInfo.CurrentUICulture); } if (this.Channel3Slider != null) @@ -692,7 +758,8 @@ private void UpdateChannelControlValues() // Alpha if (this.AlphaChannelTextBox != null) { - this.AlphaChannelTextBox.Text = rgbColor.A.ToString();; + this.AlphaChannelTextBox.MaxLength = 3; + this.AlphaChannelTextBox.Text = rgbColor.A.ToString(CultureInfo.CurrentUICulture); } if (this.AlphaChannelSlider != null) @@ -1389,38 +1456,29 @@ private void HexInputTextBox_LostFocus(object sender, RoutedEventArgs e) return; } - private void HexInputTextBox_TextChanged(object sender, TextChangedEventArgs e) + /// + /// Event handler for when a key is pressed within a color channel TextBox. + /// This is used to trigger re-evaluation of the color based on the TextBox value. + /// + private void ChannelTextBox_KeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e) { - // Use LostFocus and KeyDown events instead + if (e.Key == Windows.System.VirtualKey.Enter) + { + this.ApplyChannelTextBoxValue(sender as TextBox); + } + return; } /// - /// Event handler for when the value within one of the channel NumberBoxes is changed. + /// Event handler for when a color channel TextBox looses focus. + /// This is used to trigger re-evaluation of the color based on the TextBox value. /// - //private void ChannelNumberBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) - //{ - // double senderValue = sender.Value; - - // if (object.ReferenceEquals(sender, this.Channel1TextBox)) - // { - // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel1, senderValue); - // } - // else if (object.ReferenceEquals(sender, this.Channel2TextBox)) - // { - // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel2, senderValue); - // } - // else if (object.ReferenceEquals(sender, this.Channel3TextBox)) - // { - // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel3, senderValue); - // } - // else if (object.ReferenceEquals(sender, this.AlphaChannelTextBox)) - // { - // this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, senderValue); - // } - - // return; - //} + private void ChannelTextBox_LostFocus(object sender, RoutedEventArgs e) + { + this.ApplyChannelTextBoxValue(sender as TextBox); + return; + } /// /// Event handler for when the value within one of the channel Sliders is changed. From 5d9872b0a3e4dec5517cad6250e8a3062845e6d6 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 5 Jul 2020 22:57:09 -0400 Subject: [PATCH 10/73] More formatting changes --- .../ColorPickerButton/ColorPickerButton.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 1d0a7584ff1..9d63effb061 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -845,10 +845,11 @@ private void SetColorChannel(ColorRepresentation colorRepresentation, } } - newRgbColor = Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(hue, - saturation, - value, - alpha); + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( + hue, + saturation, + value, + alpha); // Must update HSV color this.savedHsvColor = new HsvColor() @@ -1673,10 +1674,11 @@ public object Convert( } } - return Microsoft.Toolkit.Uwp.Helpers.ColorHelper.FromHsv(Math.Clamp(colorHue, 0.0, 360.0), - Math.Clamp(colorSaturation, 0.0, 1.0), - Math.Clamp(colorValue, 0.0, 1.0), - Math.Clamp(colorAlpha, 0.0, 1.0)); + return Uwp.Helpers.ColorHelper.FromHsv( + Math.Clamp(colorHue, 0.0, 360.0), + Math.Clamp(colorSaturation, 0.0, 1.0), + Math.Clamp(colorValue, 0.0, 1.0), + Math.Clamp(colorAlpha, 0.0, 1.0)); } public object ConvertBack( From 83cb6e0e6a4c5ad3c731e395198108a7d87901eb Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 18:52:14 -0400 Subject: [PATCH 11/73] Add WindowsColorPalette as the default --- .../ColorPickerButton/ColorPickerButton.cs | 24 +-- .../ColorPickerButton/WindowsColorPalette.cs | 175 ++++++++++++++++++ 2 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 9d63effb061..71dc727a221 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -172,9 +172,8 @@ public ColorPickerButton() // This may need to change in the future if theme changes should be supported this.checkerBackgroundColor = (Color)Application.Current.Resources["SystemListLowColor"]; - this.SetDefaultPalette(); - this.ConnectCallbacks(true); + this.SetDefaultPalette(); this.StartDispatcherTimer(); } @@ -1073,26 +1072,7 @@ private async void UpdateChannelSliderBackground(Slider slider) /// private void SetDefaultPalette() { - this.CustomPalette = null; - - this.CustomPaletteColors.Add(Colors.Black); - this.CustomPaletteColors.Add(Colors.Gray); - this.CustomPaletteColors.Add(Colors.Silver); - this.CustomPaletteColors.Add(Colors.White); - this.CustomPaletteColors.Add(Colors.Maroon); - this.CustomPaletteColors.Add(Colors.Red); - this.CustomPaletteColors.Add(Colors.Olive); - this.CustomPaletteColors.Add(Colors.Yellow); - this.CustomPaletteColors.Add(Colors.Green); - this.CustomPaletteColors.Add(Colors.Lime); - this.CustomPaletteColors.Add(Colors.Teal); - this.CustomPaletteColors.Add(Colors.Aqua); - this.CustomPaletteColors.Add(Colors.Navy); - this.CustomPaletteColors.Add(Colors.Blue); - this.CustomPaletteColors.Add(Colors.Purple); - this.CustomPaletteColors.Add(Colors.Fuchsia); - - this.CustomPaletteSectionCount = 4; + this.CustomPalette = new WindowsColorPalette(); return; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs new file mode 100644 index 00000000000..db25e60adab --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Color = Windows.UI.Color; // Type can be changed to CoreColor, etc. + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Implements the standard Windows 10 color palette. + /// + public class WindowsColorPalette : 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 */ + } + }; + + /*************************************************************************************** + * + * Color Indexes + * + ***************************************************************************************/ + + /// + /// 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; + + /*************************************************************************************** + * + * Property Accessors + * + ***************************************************************************************/ + + /////////////////////////////////////////////////////////// + // Palette + /////////////////////////////////////////////////////////// + + /// + /// 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 { return 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 { return colorChart.GetLength(1); } + } + + /*************************************************************************************** + * + * Methods + * + ***************************************************************************************/ + + /// + /// 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. + public Color GetColor( + int colorIndex, + int shadeIndex) + { + return colorChart[ + Math.Clamp(colorIndex, 0, colorChart.GetLength(0)), + Math.Clamp(shadeIndex, 0, colorChart.GetLength(1))]; + } + } +} From 6c7bbb66818b997ef1cc1d14e2b9fd60cdac2075 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 18:53:59 -0400 Subject: [PATCH 12/73] Several formatting changes to align with the toolkit --- .../ColorPickerButton.Properties.cs | 48 +++++++++++-------- .../ColorPickerButton.Rendering.cs | 11 +++-- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs index 1ce21f403d0..1335132fa59 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs @@ -7,33 +7,38 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// Contains all properties for the . + /// public partial class ColorPickerButton { /// /// Identifies the dependency property. /// public static readonly DependencyProperty CustomPaletteColorsProperty = - DependencyProperty.Register(nameof(CustomPaletteColors), - typeof(ObservableCollection), - typeof(ColorPickerButton), - new PropertyMetadata(Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00))); + DependencyProperty.Register( + nameof(CustomPaletteColors), + typeof(ObservableCollection), + typeof(ColorPickerButton), + new PropertyMetadata(Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00))); /// /// Gets the list of custom palette colors. /// public ObservableCollection CustomPaletteColors { - get => (ObservableCollection)base.GetValue(CustomPaletteColorsProperty); + get => (ObservableCollection)this.GetValue(CustomPaletteColorsProperty); } /// /// Identifies the dependency property. /// public static readonly DependencyProperty CustomPaletteSectionCountProperty = - DependencyProperty.Register(nameof(CustomPaletteSectionCount), - typeof(int), - typeof(ColorPickerButton), - new PropertyMetadata(4)); + DependencyProperty.Register( + nameof(CustomPaletteSectionCount), + typeof(int), + typeof(ColorPickerButton), + new PropertyMetadata(4)); /// /// Gets or sets the number of colors in each section of the custom color palette. @@ -42,12 +47,12 @@ public ObservableCollection CustomPaletteColors /// public int CustomPaletteSectionCount { - get => (int)base.GetValue(CustomPaletteSectionCountProperty); - set + get => (int)this.GetValue(CustomPaletteSectionCountProperty); + set { - if (object.Equals(value, base.GetValue(CustomPaletteSectionCountProperty)) == false) + if (object.Equals(value, this.GetValue(CustomPaletteSectionCountProperty)) == false) { - base.SetValue(CustomPaletteSectionCountProperty, value); + this.SetValue(CustomPaletteSectionCountProperty, value); } } } @@ -56,24 +61,25 @@ public int CustomPaletteSectionCount /// Identifies the dependency property. /// public static readonly DependencyProperty CustomPaletteProperty = - DependencyProperty.Register(nameof(CustomPalette), - typeof(IColorPalette), - typeof(ColorPickerButton), - new PropertyMetadata(DependencyProperty.UnsetValue)); + DependencyProperty.Register( + nameof(CustomPalette), + typeof(IColorPalette), + typeof(ColorPickerButton), + new PropertyMetadata(DependencyProperty.UnsetValue)); /// /// Gets or sets the custom color palette. - /// This will automatically set and + /// This will automatically set and /// overwriting any existing values. /// public IColorPalette CustomPalette { - get => (IColorPalette)base.GetValue(CustomPaletteProperty); + get => (IColorPalette)this.GetValue(CustomPaletteProperty); set { - if (object.Equals(value, base.GetValue(CustomPaletteProperty)) == false) + if (object.Equals(value, this.GetValue(CustomPaletteProperty)) == false) { - base.SetValue(CustomPaletteProperty, value); + this.SetValue(CustomPaletteProperty, value); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs index 1faae2cf6c3..bff45ef5f0e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs @@ -58,9 +58,9 @@ private async Task CreateChannelBitmapAsync( // Allocate the buffer // BGRA formatted color channels 1 byte each (4 bytes in a pixel) - bgraPixelData = new byte[width * height * 4]; + bgraPixelData = new byte[width * height * 4]; bgraPixelDataHeight = height * 4; - bgraPixelDataWidth = width * 4; + bgraPixelDataWidth = width * 4; // Convert RGB to HSV once if (colorRepresentation == ColorRepresentation.Hsva) @@ -237,7 +237,7 @@ Color GetColor(double channelValue) if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep hue - newRgbColor = Uwp.Helpers.ColorHelper.FromHsv ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( Math.Clamp(channelValue, 0.0, 360.0), baseHsvColor.S, baseHsvColor.V, @@ -257,12 +257,13 @@ Color GetColor(double channelValue) break; } + case ColorChannel.Channel2: { if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep saturation - newRgbColor = Uwp.Helpers.ColorHelper.FromHsv ( + newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, Math.Clamp(channelValue, 0.0, 1.0), baseHsvColor.V, @@ -282,6 +283,7 @@ Color GetColor(double channelValue) break; } + case ColorChannel.Channel3: { if (colorRepresentation == ColorRepresentation.Hsva) @@ -307,6 +309,7 @@ Color GetColor(double channelValue) break; } + case ColorChannel.Alpha: { if (colorRepresentation == ColorRepresentation.Hsva) From a0e6d88bbde0d5e684ed0419be6a01facb6ed60f Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 21:08:44 -0400 Subject: [PATCH 13/73] Switch to RadioButtons for color representation --- .../ColorPickerButton/ColorPickerButton.cs | 105 +++++++++++++++--- .../ColorPickerButton/ColorPickerButton.xaml | 30 +++-- 2 files changed, 101 insertions(+), 34 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 71dc727a221..ee8b388ac1d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -43,9 +43,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrum), Type = typeof(ColorSpectrum))] [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumAlphaSlider), Type = typeof(Slider))] [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.ColorRepresentationComboBox), Type = typeof(ComboBox))] - [TemplatePart(Name = nameof(ColorPickerButton.HexInputTextBox), Type = typeof(TextBox))] [TemplatePart(Name = nameof(ColorPickerButton.PaletteGridView), Type = typeof(GridView))] + [TemplatePart(Name = nameof(ColorPickerButton.HexInputTextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPickerButton.HsvRadioButton), Type = typeof(RadioButton))] + [TemplatePart(Name = nameof(ColorPickerButton.RgbRadioButton), Type = typeof(RadioButton))] [TemplatePart(Name = nameof(ColorPickerButton.P1PreviewBorder), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPickerButton.P2PreviewBorder), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPickerButton.N1PreviewBorder), Type = typeof(Border))] @@ -119,9 +120,10 @@ private enum ColorChannel private ColorSpectrum ColorSpectrum; private Slider ColorSpectrumAlphaSlider; private Slider ColorSpectrumThirdDimensionSlider; - private ComboBox ColorRepresentationComboBox; - private TextBox HexInputTextBox; private GridView PaletteGridView; + private TextBox HexInputTextBox; + private RadioButton HsvRadioButton; + private RadioButton RgbRadioButton; private TextBox Channel1TextBox; private TextBox Channel2TextBox; @@ -203,8 +205,9 @@ protected override void OnApplyTemplate() this.PaletteGridView = this.GetTemplateChild("PaletteGridView", false); - this.ColorRepresentationComboBox = this.GetTemplateChild("ColorRepresentationComboBox", false); - this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); + this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); + this.HsvRadioButton = this.GetTemplateChild("HsvRadioButton", false); + this.RgbRadioButton = this.GetTemplateChild("RgbRadioButton", false); this.Channel1TextBox = this.GetTemplateChild("Channel1TextBox", false); this.Channel2TextBox = this.GetTemplateChild("Channel2TextBox", false); @@ -253,6 +256,7 @@ protected override void OnApplyTemplate() base.OnApplyTemplate(); this.isInitialized = true; + this.SetActiveColorRepresentation(ColorRepresentation.Rgba); this.UpdateChannelControlValues(); } @@ -314,10 +318,13 @@ private void ConnectEvents(bool connected) // Add all events if (this.ColorSpectrum != null) { this.ColorSpectrum.ColorChanged += ColorSpectrum_ColorChanged; } if (this.ColorSpectrum != null) { this.ColorSpectrum.GotFocus += ColorSpectrum_GotFocus; } - if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged += ColorRepresentationComboBox_SelectionChanged; } + if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown += HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus += HexInputTextBox_LostFocus; } - if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } + if (this.HsvRadioButton != null) { this.HsvRadioButton.Checked += ColorRepRadioButton_CheckedUnchecked; } + if (this.HsvRadioButton != null) { this.HsvRadioButton.Unchecked += ColorRepRadioButton_CheckedUnchecked; } + if (this.RgbRadioButton != null) { this.RgbRadioButton.Checked += ColorRepRadioButton_CheckedUnchecked; } + if (this.RgbRadioButton != null) { this.RgbRadioButton.Unchecked += ColorRepRadioButton_CheckedUnchecked; } if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown += ChannelTextBox_KeyDown; } if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown += ChannelTextBox_KeyDown; } @@ -366,10 +373,13 @@ private void ConnectEvents(bool connected) // Remove all events if (this.ColorSpectrum != null) { this.ColorSpectrum.ColorChanged -= ColorSpectrum_ColorChanged; } if (this.ColorSpectrum != null) { this.ColorSpectrum.GotFocus -= ColorSpectrum_GotFocus; } - if (this.ColorRepresentationComboBox != null) { this.ColorRepresentationComboBox.SelectionChanged -= ColorRepresentationComboBox_SelectionChanged; } + if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown -= HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus -= HexInputTextBox_LostFocus; } - if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } + if (this.HsvRadioButton != null) { this.HsvRadioButton.Checked -= ColorRepRadioButton_CheckedUnchecked; } + if (this.HsvRadioButton != null) { this.HsvRadioButton.Unchecked -= ColorRepRadioButton_CheckedUnchecked; } + if (this.RgbRadioButton != null) { this.RgbRadioButton.Checked -= ColorRepRadioButton_CheckedUnchecked; } + if (this.RgbRadioButton != null) { this.RgbRadioButton.Unchecked -= ColorRepRadioButton_CheckedUnchecked; } if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown -= ChannelTextBox_KeyDown; } if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown -= ChannelTextBox_KeyDown; } @@ -421,11 +431,10 @@ private void ConnectEvents(bool connected) /// private ColorRepresentation GetActiveColorRepresentation() { - // This is kind-of an ugly way to see if the HSV color channel representation is active - // However, it is the same technique used in the ColorPicker - // The order and number of items in the template is fixed and very important - if ((this.ColorRepresentationComboBox != null) && - (this.ColorRepresentationComboBox.SelectedIndex == 1)) + // If the HSV representation control is missing for whatever reason, + // the default will be RGB + if (this.HsvRadioButton != null && + this.HsvRadioButton.IsChecked == true) { return ColorRepresentation.Hsva; } @@ -433,6 +442,67 @@ private ColorRepresentation GetActiveColorRepresentation() return ColorRepresentation.Rgba; } + /// + /// Sets the active color representation in the UI controls. + /// + /// The color representation to set. + /// Setting to null (the default) will attempt to keep the current state. + private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentation = null) + { + bool eventsDisconnectedByMethod = false; + + if (colorRepresentation == null) + { + // Use the control's current value + colorRepresentation = this.GetActiveColorRepresentation(); + } + + // Disable events during the update + if (this.eventsConnected) + { + this.ConnectEvents(false); + eventsDisconnectedByMethod = true; + } + + // Sync the UI controls and visual state + // The default is always RGBA + if (colorRepresentation == ColorRepresentation.Hsva) + { + if (this.RgbRadioButton != null) + { + this.RgbRadioButton.IsChecked = false; + } + + if (this.HsvRadioButton != null) + { + this.HsvRadioButton.IsChecked = true; + } + + VisualStateManager.GoToState(this, "HsvSelected", false); + } + else + { + if (this.RgbRadioButton != null) + { + this.RgbRadioButton.IsChecked = true; + } + + if (this.HsvRadioButton != null) + { + this.HsvRadioButton.IsChecked = false; + } + + VisualStateManager.GoToState(this, "RgbSelected", false); + } + + if (eventsDisconnectedByMethod) + { + this.ConnectEvents(true); + } + + return; + } + /// /// Gets the active third dimension in the color spectrum: Hue, Saturation or Value. /// @@ -1366,11 +1436,12 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) } /// - /// Event handler for when the selection changes within the color representation ComboBox. + /// Event handler for when the selected color representation changes. /// This will convert between RGB and HSV. /// - private void ColorRepresentationComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + private void ColorRepRadioButton_CheckedUnchecked(object sender, RoutedEventArgs e) { + this.SetActiveColorRepresentation(); this.UpdateChannelControlValues(); this.UpdateChannelSliderBackgrounds(); return; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 23a17c1242f..16b8f09b190 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -501,25 +501,21 @@ + Grid.Row="0"> - - + + + + - - - - - - - + + + + From 2f2a256b3f49755a1a4c5d82aac842738f30c326 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 21:08:55 -0400 Subject: [PATCH 14/73] Remove unused XAML --- .../ColorPickerButton/ColorPickerButton.xaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 16b8f09b190..b508537db2a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -408,23 +408,6 @@ HorizontalAlignment="Center" VerticalAlignment="Stretch" Margin="12,0,0,0" /> - From 49c3ff9b56bd77dfe00d34e57266cab79b4c7789 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 22:08:26 -0400 Subject: [PATCH 15/73] Add SampleApp XAML for ColorPickerButton --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 1 + .../ColorPickerButtonPage.xaml | 8 +- .../ColorPickerButtonXaml.bind | 116 ++++++++++++++++++ .../SamplePages/samples.json | 1 + 4 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 95b86aea253..319586810a9 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -621,6 +621,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml index 08874e26172..506863f971d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml @@ -6,9 +6,7 @@ xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - - - + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind new file mode 100644 index 00000000000..34acff76c34 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + Ring-shaped spectrum + Alpha channel disabled + + + + + + + Box-shaped spectrum + Alpha channel disabled + + + + + + + Ring-shaped spectrum + Alpha channel enabled + + + + + + + Ring-shaped spectrum + Alpha channel enabled + Saturation+Value spectrum channels + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 2d499ed82f0..1dfa2e74be9 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -52,6 +52,7 @@ "Subcategory": "Input", "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", "CodeUrl": "TBD", + "XamlCodeFile": "ColorPickerButtonXaml.bind", "Icon": "/SamplePages/ColorPickerButton/ColorPickerButton.png", "DocumentationUrl": "TBD" }, From c235308485a0a6d19439ddae56d549e75bf1c942 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 22:19:01 -0400 Subject: [PATCH 16/73] Keep ColorPicker default property values in style --- .../ColorPickerButton/ColorPickerButton.xaml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index b508537db2a..82cb492d9f5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -16,17 +16,6 @@ Value="64" /> - - - - - - From 4d6f9d507ec829a6611c195de66dc3ef3ef32030 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 22:19:31 -0400 Subject: [PATCH 17/73] Update SampleApp XAML for ColorPickerButton --- .../ColorPickerButton/ColorPickerButtonXaml.bind | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind index 34acff76c34..694fded8ab7 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind @@ -29,7 +29,7 @@ Padding="10"> - Ring-shaped spectrum + Box-shaped spectrum Alpha channel disabled @@ -37,7 +37,7 @@ Grid.Column="0" Grid.Row="0" Color="Navy" - ColorSpectrumShape="Ring" + ColorSpectrumShape="Box" IsAlphaEnabled="False" HorizontalAlignment="Center" VerticalAlignment="Bottom" @@ -53,7 +53,7 @@ Box-shaped spectrum - Alpha channel disabled + Alpha channel enabled @@ -83,6 +83,7 @@ Grid.Column="0" Grid.Row="1" Color="Transparent" + ColorSpectrumShape="Ring" IsAlphaEnabled="True" HorizontalAlignment="Center" VerticalAlignment="Bottom" @@ -106,6 +107,7 @@ Grid.Column="1" Grid.Row="1" Color="Maroon" + ColorSpectrumShape="Ring" ColorSpectrumComponents="SaturationValue" IsAlphaEnabled="True" Width="200" From 2de382f3f31d90fe1f6953b9c15dce201ad21baa Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 6 Jul 2020 22:33:36 -0400 Subject: [PATCH 18/73] Remove alpha from hex text when alpha is disabled --- .../ColorPickerButton/ColorPickerButton.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index ee8b388ac1d..a946a0ff440 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -640,7 +640,19 @@ private void UpdateChannelControlValues() eventsDisconnectedByMethod = true; } - this.HexInputTextBox.Text = rgbColor.ToHex().Replace("#", ""); + if (this.HexInputTextBox != null) + { + if (this.IsAlphaEnabled) + { + // Remove only the "#" sign + this.HexInputTextBox.Text = rgbColor.ToHex().Replace("#", string.Empty); + } + else + { + // Remove the "#" sign and alpha hex + this.HexInputTextBox.Text = rgbColor.ToHex().Replace("#", string.Empty).Substring(2); + } + } // Regardless of the active color representation, the spectrum is always HSV // Therefore, always calculate HSV color here From da777c341af9b2eb1361d8ed2cd23c14d3d69a7a Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 7 Jul 2020 18:29:23 -0400 Subject: [PATCH 19/73] Move ColorPickerButton-specific converters to separate files --- .../ColorPickerButton/ColorPickerButton.cs | 233 ------------------ .../ColorPickerButton/ColorPickerButton.xaml | 11 +- .../ColorToColorShadeConverter.cs | 129 ++++++++++ .../ColorPickerButton/ColorToHexConverter.cs | 73 ++++++ 4 files changed, 207 insertions(+), 239 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToHexConverter.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index a946a0ff440..68c163e9405 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -1580,237 +1580,4 @@ private void ChannelSlider_ValueChanged(object sender, RangeBaseValueChangedEven return; } } - - /// - /// Adjust the value component of a color in the HSV model. - /// The value % change must be supplied using the parameter. - /// 0 = 0% and 100 = 100% with value clipped to this scale. - /// Both positive and negative adjustments are supported as this applies - /// the delta to the existing color. - /// - public class HsvValueAdjustmentConverter : IValueConverter - { - public object Convert( - object value, - Type targetType, - object parameter, - string language) - { - double valueDelta; - HsvColor hsvColor; - - // Get the value component delta - try - { - valueDelta = System.Convert.ToDouble(parameter?.ToString()); - } - catch - { - throw new ArgumentException("Invalid parameter provided, unable to convert to double"); - } - - // Get the current color in HSV - try - { - hsvColor = ((Color)value).ToHsv(); - } - catch - { - throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); - } - - // Add the value delta to the HSV color and convert it back to RGB - hsvColor = new HsvColor() - { - H = hsvColor.H, - S = hsvColor.S, - V = Math.Clamp(hsvColor.V + (valueDelta / 100.0), 0.0, 1.0), - A = hsvColor.A, - }; - - return Uwp.Helpers.ColorHelper.FromHsv( - hsvColor.H, - hsvColor.S, - hsvColor.V, - hsvColor.A); - } - - public object ConvertBack( - object value, - Type targetType, - object parameter, - string language) - { - throw new NotImplementedException(); - } - } - - /// - /// - /// Only +/- 3 shades from the given color are supported. - /// - public class AccentColorShadeConverter : IValueConverter - { - public object Convert( - object value, - Type targetType, - object parameter, - string language) - { - int shade; - HsvColor hsvColor; - - // Get the value component delta - try - { - shade = System.Convert.ToInt32(parameter?.ToString()); - } - catch - { - throw new ArgumentException("Invalid parameter provided, unable to convert to double"); - } - - // Get the current color in HSV - try - { - hsvColor = ((Color)value).ToHsv(); - } - catch - { - throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); - } - - double colorHue = hsvColor.H; - double colorSaturation = hsvColor.S; - double colorValue = hsvColor.V; - double colorAlpha = hsvColor.A; - - // Use the HSV representation as it's more perceptual - switch (shade) - { - case -3: - { - colorHue = colorHue * 1.0; - colorSaturation = colorSaturation * 1.10; - colorValue = colorValue * 0.40; - break; - } - case -2: - { - colorHue = colorHue * 1.0; - colorSaturation = colorSaturation * 1.05; - colorValue = colorValue * 0.50; - break; - } - case -1: - { - colorHue = colorHue * 1.0; - colorSaturation = colorSaturation * 1.0; - colorValue = colorValue * 0.75; - break; - } - case 0: - { - // No change - break; - } - case 1: - { - colorHue = colorHue * 1.00; - colorSaturation = colorSaturation * 1.00; - colorValue = colorValue * 1.05; - break; - } - case 2: - { - colorHue = colorHue * 1.00; - colorSaturation = colorSaturation * 0.75; - colorValue = colorValue * 1.05; - break; - } - case 3: - { - colorHue = colorHue * 1.00; - colorSaturation = colorSaturation * 0.65; - colorValue = colorValue * 1.05; - break; - } - } - - return Uwp.Helpers.ColorHelper.FromHsv( - Math.Clamp(colorHue, 0.0, 360.0), - Math.Clamp(colorSaturation, 0.0, 1.0), - Math.Clamp(colorValue, 0.0, 1.0), - Math.Clamp(colorAlpha, 0.0, 1.0)); - } - - public object ConvertBack( - object value, - Type targetType, - object parameter, - string language) - { - throw new NotImplementedException(); - } - } - - /// - /// Converts a color to a hex string and vice versa. - /// - public class ColorToHexConverter : IValueConverter - { - public object Convert( - object value, - Type targetType, - object parameter, - string language) - { - Color color; - - // Get the changing color to compare against - try - { - color = (Color)value; - } - catch - { - throw new ArgumentException("Invalid color value provided"); - } - - string hexColor = color.ToHex().Replace("#", ""); - return hexColor; - } - - public object ConvertBack( - object value, - Type targetType, - object parameter, - string language) - { - string hexValue = value.ToString(); - - if (hexValue.StartsWith("#")) - { - try - { - return hexValue.ToColor(); - } - catch - { - throw new ArgumentException("Invalid hex color value provided"); - } - } - else - { - try - { - return ("#" + hexValue).ToColor(); - } - catch - { - throw new ArgumentException("Invalid hex color value provided"); - } - } - } - } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 82cb492d9f5..e67837be825 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -23,8 +23,7 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - - + @@ -652,7 +651,7 @@ - + - Date: Tue, 7 Jul 2020 22:12:03 -0400 Subject: [PATCH 22/73] Switch to ToggleButton for RGB/HSV selection --- .../ColorPickerButton/ColorPickerButton.cs | 61 +++++++++++-------- .../ColorPickerButton/ColorPickerButton.xaml | 22 ++++--- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 32b60ab07c2..347c3d72b8a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -44,8 +44,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] [TemplatePart(Name = nameof(ColorPickerButton.PaletteGridView), Type = typeof(GridView))] [TemplatePart(Name = nameof(ColorPickerButton.HexInputTextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.HsvRadioButton), Type = typeof(RadioButton))] - [TemplatePart(Name = nameof(ColorPickerButton.RgbRadioButton), Type = typeof(RadioButton))] + [TemplatePart(Name = nameof(ColorPickerButton.HsvToggleButton), Type = typeof(ToggleButton))] + [TemplatePart(Name = nameof(ColorPickerButton.RgbToggleButton), Type = typeof(ToggleButton))] [TemplatePart(Name = nameof(ColorPickerButton.P1PreviewBorder), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPickerButton.P2PreviewBorder), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPickerButton.N1PreviewBorder), Type = typeof(Border))] @@ -124,8 +124,8 @@ private enum ColorChannel private Slider ColorSpectrumThirdDimensionSlider; private GridView PaletteGridView; private TextBox HexInputTextBox; - private RadioButton HsvRadioButton; - private RadioButton RgbRadioButton; + private ToggleButton HsvToggleButton; + private ToggleButton RgbToggleButton; private TextBox Channel1TextBox; private TextBox Channel2TextBox; @@ -222,8 +222,8 @@ protected override void OnApplyTemplate() this.PaletteGridView = this.GetTemplateChild("PaletteGridView", false); this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); - this.HsvRadioButton = this.GetTemplateChild("HsvRadioButton", false); - this.RgbRadioButton = this.GetTemplateChild("RgbRadioButton", false); + this.HsvToggleButton = this.GetTemplateChild("HsvToggleButton", false); + this.RgbToggleButton = this.GetTemplateChild("RgbToggleButton", false); this.Channel1TextBox = this.GetTemplateChild("Channel1TextBox", false); this.Channel2TextBox = this.GetTemplateChild("Channel2TextBox", false); @@ -337,10 +337,10 @@ private void ConnectEvents(bool connected) if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown += HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus += HexInputTextBox_LostFocus; } - if (this.HsvRadioButton != null) { this.HsvRadioButton.Checked += ColorRepRadioButton_CheckedUnchecked; } - if (this.HsvRadioButton != null) { this.HsvRadioButton.Unchecked += ColorRepRadioButton_CheckedUnchecked; } - if (this.RgbRadioButton != null) { this.RgbRadioButton.Checked += ColorRepRadioButton_CheckedUnchecked; } - if (this.RgbRadioButton != null) { this.RgbRadioButton.Unchecked += ColorRepRadioButton_CheckedUnchecked; } + if (this.HsvToggleButton != null) { this.HsvToggleButton.Checked += ColorRepToggleButton_CheckedUnchecked; } + if (this.HsvToggleButton != null) { this.HsvToggleButton.Unchecked += ColorRepToggleButton_CheckedUnchecked; } + if (this.RgbToggleButton != null) { this.RgbToggleButton.Checked += ColorRepToggleButton_CheckedUnchecked; } + if (this.RgbToggleButton != null) { this.RgbToggleButton.Unchecked += ColorRepToggleButton_CheckedUnchecked; } if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown += ChannelTextBox_KeyDown; } if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown += ChannelTextBox_KeyDown; } @@ -392,10 +392,10 @@ private void ConnectEvents(bool connected) if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown -= HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus -= HexInputTextBox_LostFocus; } - if (this.HsvRadioButton != null) { this.HsvRadioButton.Checked -= ColorRepRadioButton_CheckedUnchecked; } - if (this.HsvRadioButton != null) { this.HsvRadioButton.Unchecked -= ColorRepRadioButton_CheckedUnchecked; } - if (this.RgbRadioButton != null) { this.RgbRadioButton.Checked -= ColorRepRadioButton_CheckedUnchecked; } - if (this.RgbRadioButton != null) { this.RgbRadioButton.Unchecked -= ColorRepRadioButton_CheckedUnchecked; } + if (this.HsvToggleButton != null) { this.HsvToggleButton.Checked -= ColorRepToggleButton_CheckedUnchecked; } + if (this.HsvToggleButton != null) { this.HsvToggleButton.Unchecked -= ColorRepToggleButton_CheckedUnchecked; } + if (this.RgbToggleButton != null) { this.RgbToggleButton.Checked -= ColorRepToggleButton_CheckedUnchecked; } + if (this.RgbToggleButton != null) { this.RgbToggleButton.Unchecked -= ColorRepToggleButton_CheckedUnchecked; } if (this.Channel1TextBox != null) { this.Channel1TextBox.KeyDown -= ChannelTextBox_KeyDown; } if (this.Channel2TextBox != null) { this.Channel2TextBox.KeyDown -= ChannelTextBox_KeyDown; } @@ -449,8 +449,8 @@ private ColorRepresentation GetActiveColorRepresentation() { // If the HSV representation control is missing for whatever reason, // the default will be RGB - if (this.HsvRadioButton != null && - this.HsvRadioButton.IsChecked == true) + if (this.HsvToggleButton != null && + this.HsvToggleButton.IsChecked == true) { return ColorRepresentation.Hsva; } @@ -484,28 +484,28 @@ private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentati // The default is always RGBA if (colorRepresentation == ColorRepresentation.Hsva) { - if (this.RgbRadioButton != null) + if (this.RgbToggleButton != null) { - this.RgbRadioButton.IsChecked = false; + this.RgbToggleButton.IsChecked = false; } - if (this.HsvRadioButton != null) + if (this.HsvToggleButton != null) { - this.HsvRadioButton.IsChecked = true; + this.HsvToggleButton.IsChecked = true; } VisualStateManager.GoToState(this, "HsvSelected", false); } else { - if (this.RgbRadioButton != null) + if (this.RgbToggleButton != null) { - this.RgbRadioButton.IsChecked = true; + this.RgbToggleButton.IsChecked = true; } - if (this.HsvRadioButton != null) + if (this.HsvToggleButton != null) { - this.HsvRadioButton.IsChecked = false; + this.HsvToggleButton.IsChecked = false; } VisualStateManager.GoToState(this, "RgbSelected", false); @@ -1467,11 +1467,20 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) /// Event handler for when the selected color representation changes. /// This will convert between RGB and HSV. /// - private void ColorRepRadioButton_CheckedUnchecked(object sender, RoutedEventArgs e) + private void ColorRepToggleButton_CheckedUnchecked(object sender, RoutedEventArgs e) { - this.SetActiveColorRepresentation(); + if (object.ReferenceEquals(sender, this.HsvToggleButton)) + { + this.SetActiveColorRepresentation(ColorRepresentation.Hsva); + } + else + { + this.SetActiveColorRepresentation(ColorRepresentation.Rgba); + } + this.UpdateChannelControlValues(); this.UpdateChannelSliderBackgrounds(); + return; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 5bd6eafb60d..bea34fe466f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -496,13 +496,21 @@ - - - + + + From 0a2598e2ca16b1f3541a4eef8f3f9a24ead37017 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 7 Jul 2020 22:24:31 -0400 Subject: [PATCH 23/73] Rename 'CustomPaletteSectionCount' to 'CustomPaletteColumns' --- .../ColorPickerButton.Properties.cs | 16 ++++++++-------- .../ColorPickerButton/ColorPickerButton.cs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs index 1335132fa59..0ce69bad8da 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs @@ -31,28 +31,28 @@ public ObservableCollection CustomPaletteColors } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty CustomPaletteSectionCountProperty = + public static readonly DependencyProperty CustomPaletteColumnsProperty = DependencyProperty.Register( - nameof(CustomPaletteSectionCount), + nameof(CustomPaletteColumns), typeof(int), typeof(ColorPickerButton), new PropertyMetadata(4)); /// - /// Gets or sets the number of colors in each section of the custom color palette. + /// Gets or sets the number of colors in each row (section) of the custom color palette. /// A section is the number of columns within an entire row in the palette. /// Within a standard palette, rows are shades and columns are unique colors. /// - public int CustomPaletteSectionCount + public int CustomPaletteColumns { - get => (int)this.GetValue(CustomPaletteSectionCountProperty); + get => (int)this.GetValue(CustomPaletteColumnsProperty); set { - if (object.Equals(value, this.GetValue(CustomPaletteSectionCountProperty)) == false) + if (object.Equals(value, this.GetValue(CustomPaletteColumnsProperty)) == false) { - this.SetValue(CustomPaletteSectionCountProperty, value); + this.SetValue(CustomPaletteColumnsProperty, value); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index 347c3d72b8a..c57eadd4139 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -1265,7 +1265,7 @@ private void OnCustomPaletteChanged(DependencyObject d, DependencyProperty e) if (palette != null) { - this.CustomPaletteSectionCount = palette.ColorCount; + this.CustomPaletteColumns = palette.ColorCount; this.CustomPaletteColors.Clear(); for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++) @@ -1343,7 +1343,7 @@ private void PaletteGridView_Loaded(object sender, RoutedEventArgs e) if (palettePanel != null) { - palettePanel.Columns = this.CustomPaletteSectionCount; + palettePanel.Columns = this.CustomPaletteColumns; } return; From 710e5d8ddc3b09caa8e805885076b01489ca6da6 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 8 Jul 2020 15:06:45 -0400 Subject: [PATCH 24/73] Adjust RGB/HSV ToggleButton style --- .../ColorPickerButton/ColorPickerButton.xaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index bea34fe466f..296487c22f5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -497,11 +497,13 @@ + Date: Wed, 8 Jul 2020 15:34:55 -0400 Subject: [PATCH 25/73] Set slider thumb border brush based on selected color This also changes to circles that fit within the track --- .../ColorPickerButton/ColorPickerButton.cs | 25 ++++++ .../ColorPickerButton/ColorPickerButton.xaml | 26 +++--- .../ContrastColorConverter.cs | 87 +++++++++++++++++++ 3 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ContrastColorConverter.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index c57eadd4139..c5a210ad9c9 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -1156,6 +1156,31 @@ private async void UpdateChannelSliderBackground(Slider slider) if (bitmap != null) { + if (baseColor.A / 255.0 < 0.5) + { + /* If the transparency is less than 50 %, just use the normal text brush + * Note that normally the code would be: + * + * slider.BorderBrush = (Brush)Application.Current.Resources["TextControlForeground"]; + * + * However, this does not lookup the correct value when only the page theme is changed. + * Therefore, the Slider.Foreground color is set in XAML using the ThemeResource above. + * Then the code here simply gets the current Foreground color which always matches + * the page theme. + * + * It's currently not known how to access the correct theme resource scoped to a page. + * If anyone figures it out this should be changed in the future. + * + */ + slider.BorderBrush = slider.Foreground; + } + else + { + // Chose a white/black brush based on contrast to the base color + ContrastColorConverter converter = new ContrastColorConverter(); + slider.BorderBrush = new SolidColorBrush((Color)converter.Convert(baseColor, typeof(Color), null, null)); + } + slider.Background = await this.BitmapToBrushAsync(bitmap, width, height); } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 296487c22f5..61244a1009a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -43,6 +43,8 @@ Value="Center" /> + @@ -51,8 +53,6 @@ @@ -384,6 +404,7 @@ Style="{StaticResource ColorPickerButtonSliderStyle}" BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrush}, ConverterParameter={ThemeResource TextControlForeground}}" Orientation="Vertical" + Width="20" HorizontalAlignment="Center" VerticalAlignment="Stretch" Margin="0,0,12,0" /> @@ -409,6 +430,7 @@ Style="{StaticResource ColorPickerButtonSliderStyle}" BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrush}, ConverterParameter={ThemeResource TextControlForeground}}" Orientation="Vertical" + Width="20" HorizontalAlignment="Center" VerticalAlignment="Stretch" Margin="12,0,0,0" /> @@ -526,7 +548,7 @@ BorderThickness="1,1,0,1" CornerRadius="2,0,0,2"> @@ -551,7 +573,7 @@ VerticalAlignment="Center"> @@ -580,7 +602,7 @@ VerticalAlignment="Center"> @@ -609,7 +631,7 @@ VerticalAlignment="Center"> @@ -639,7 +661,7 @@ VerticalAlignment="Center"> @@ -661,7 +683,9 @@ - + @@ -681,7 +705,8 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> + Grid.Column="0" + CornerRadius="2,0,0,2"> @@ -718,7 +743,8 @@ + Grid.Column="1" + CornerRadius="0,2,2,0"> @@ -735,12 +761,15 @@ VerticalContentAlignment="Stretch"> - + @@ -748,7 +777,7 @@ - + From 70a66cd9c74c81272e770ed34ca0c7607452d046 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 13:46:36 -0400 Subject: [PATCH 30/73] Fix comment --- .../ColorPickerButton/ColorPickerButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index c57eadd4139..ba146860bc5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -141,7 +141,7 @@ private enum ColorChannel private Border P1PreviewBorder; private Border P2PreviewBorder; - // Up to 8 checkered backgrounds may be used by name anywhere in the template + // Up to 10 checkered backgrounds may be used by name anywhere in the template private Border CheckeredBackground1Border; private Border CheckeredBackground2Border; private Border CheckeredBackground3Border; From 0cc1db077edea6e9537b93c78db020fe3d2c4f82 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 14:49:55 -0400 Subject: [PATCH 31/73] Update sample app XAML --- .../SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind index 694fded8ab7..6b3e66fda79 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind @@ -39,6 +39,7 @@ Color="Navy" ColorSpectrumShape="Box" IsAlphaEnabled="False" + IsHexInputVisible="True" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20" /> @@ -62,6 +63,7 @@ Color="Green" ColorSpectrumShape="Box" IsAlphaEnabled="True" + IsHexInputVisible="False" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20" /> @@ -85,6 +87,7 @@ Color="Transparent" ColorSpectrumShape="Ring" IsAlphaEnabled="True" + IsHexInputVisible="True" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20" /> @@ -110,6 +113,7 @@ ColorSpectrumShape="Ring" ColorSpectrumComponents="SaturationValue" IsAlphaEnabled="True" + IsHexInputVisible="True" Width="200" HorizontalAlignment="Center" VerticalAlignment="Bottom" From 09433554aecf7e1ec660a9872f2bf312bbb1e41e Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 14:53:22 -0400 Subject: [PATCH 32/73] Add ColorToDisplayNameConverter --- .../ColorToDisplayNameConverter.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs new file mode 100644 index 00000000000..65361b31a3a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Gets the approximated display name for the color. + /// + public class ColorToDisplayNameConverter : IValueConverter + { + /// + public object Convert( + object value, + Type targetType, + object parameter, + string language) + { + Color color; + + if (value is Color valueColor) + { + color = valueColor; + } + else if (value is SolidColorBrush valueBrush) + { + color = valueBrush.Color; + } + else + { + throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); + } + + return ColorHelper.ToDisplayName(color); + } + + /// + public object ConvertBack( + object value, + Type targetType, + object parameter, + string language) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file From f081e905bbd905f282a48f950c918eb9e7f15111 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 14:54:47 -0400 Subject: [PATCH 33/73] Add all AutomationProperties --- .../ColorPickerButton/ColorPickerButton.cs | 12 +++-- .../ColorPickerButton/ColorPickerButton.xaml | 52 ++++++++++++++----- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index ba146860bc5..ea681ff1f44 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -484,12 +484,14 @@ private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentati // The default is always RGBA if (colorRepresentation == ColorRepresentation.Hsva) { - if (this.RgbToggleButton != null) + if (this.RgbToggleButton != null && + (bool)this.RgbToggleButton.IsChecked) { this.RgbToggleButton.IsChecked = false; } - if (this.HsvToggleButton != null) + if (this.HsvToggleButton != null && + (bool)this.HsvToggleButton.IsChecked == false) { this.HsvToggleButton.IsChecked = true; } @@ -498,12 +500,14 @@ private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentati } else { - if (this.RgbToggleButton != null) + if (this.RgbToggleButton != null && + (bool)this.RgbToggleButton.IsChecked == false) { this.RgbToggleButton.IsChecked = true; } - if (this.HsvToggleButton != null) + if (this.HsvToggleButton != null && + (bool)this.HsvToggleButton.IsChecked) { this.HsvToggleButton.IsChecked = false; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 462a239cab3..e0d17300886 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -24,6 +24,7 @@ VerticalAlignment="Stretch"> + @@ -297,6 +298,18 @@ Value="S" /> + + + + + + @@ -380,10 +393,9 @@ Value="0" /> - + - @@ -410,6 +422,7 @@ Margin="0,0,12,0" /> - + - @@ -476,7 +489,8 @@ - @@ -487,10 +501,9 @@ - + - @@ -526,6 +539,7 @@ BorderThickness="1" Background="Transparent" HorizontalAlignment="Stretch" + IsThreeState="False" contract7Present:CornerRadius="2,0,0,2"/> @@ -556,6 +571,7 @@ @@ -579,13 +595,15 @@ VerticalAlignment="Center" /> Date: Sun, 19 Jul 2020 15:23:08 -0400 Subject: [PATCH 34/73] Fix SampleApp.csproj after merge --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 37651b44e94..2c4cce232cc 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -520,6 +520,7 @@ ColorPickerButtonPage.xaml + EnumValuesExtensionPage.xaml @@ -1008,6 +1009,7 @@ Designer MSBuild:Compile + MSBuild:Compile Designer From 9ee2c612d2e60707fb57b7bcd385ff78302b6f31 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 15:57:06 -0400 Subject: [PATCH 35/73] Re-template GridViewItem so that the border has correct contrast with selected color --- .../ColorPickerButton/ColorPickerButton.xaml | 552 +++++++++++++++++- 1 file changed, 549 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index e0d17300886..06122149828 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -473,6 +473,18 @@ From 3adef02fe008d647e852b3bf67b2dab95e2843b4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 20:46:15 -0400 Subject: [PATCH 36/73] Add IsColorPaletteVisible property --- .../ColorPickerButton.Properties.cs | 27 +++++++++- .../ColorPickerButton/ColorPickerButton.cs | 50 ++++++++++++------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs index 0ce69bad8da..9b1a9317f9e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs @@ -69,7 +69,7 @@ public int CustomPaletteColumns /// /// Gets or sets the custom color palette. - /// This will automatically set and + /// This will automatically set and /// overwriting any existing values. /// public IColorPalette CustomPalette @@ -83,5 +83,30 @@ public IColorPalette CustomPalette } } } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsColorPaletteVisibleProperty = + DependencyProperty.Register( + nameof(IsColorPaletteVisible), + typeof(bool), + typeof(ColorPickerButton), + new PropertyMetadata(true)); + + /// + /// Gets or sets a value indicating whether the color palette is visible. + /// + public bool IsColorPaletteVisible + { + get => (bool)this.GetValue(IsColorPaletteVisibleProperty); + set + { + if (object.Equals(value, this.GetValue(IsColorPaletteVisibleProperty)) == false) + { + this.SetValue(IsColorPaletteVisibleProperty, value); + } + } + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index ea681ff1f44..fcfce3bd551 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -106,6 +106,7 @@ private enum ColorChannel private long tokenColor; private long tokenCustomPalette; + private long tokenIsColorPaletteVisible; private Dictionary cachedSliderSizes = new Dictionary(); private bool callbacksConnected = false; @@ -257,20 +258,11 @@ protected override void OnApplyTemplate() this.ColorSpectrum.Color = (Color)this.GetValue(ColorProperty); } - // Set initial state - if (this.IsEnabled == false) - { - VisualStateManager.GoToState(this, "Disabled", false); - } - else - { - VisualStateManager.GoToState(this, "Normal", false); - } - // Must connect after controls are resolved this.ConnectEvents(true); base.OnApplyTemplate(); + this.UpdateVisualState(false); this.isInitialized = true; this.SetActiveColorRepresentation(ColorRepresentation.Rgba); this.UpdateChannelControlValues(); @@ -304,8 +296,9 @@ private void ConnectCallbacks(bool connected) (this.callbacksConnected == false)) { // Add callbacks for dependency properties - this.tokenColor = this.RegisterPropertyChangedCallback(ColorProperty, OnColorChanged); - this.tokenCustomPalette = this.RegisterPropertyChangedCallback(CustomPaletteProperty, OnCustomPaletteChanged); + this.tokenColor = this.RegisterPropertyChangedCallback(ColorProperty, OnColorChanged); + this.tokenCustomPalette = this.RegisterPropertyChangedCallback(CustomPaletteProperty, OnCustomPaletteChanged); + this.tokenIsColorPaletteVisible = this.RegisterPropertyChangedCallback(IsColorPaletteVisibleProperty, OnIsColorPaletteVisibleChanged); this.callbacksConnected = true; } @@ -313,8 +306,9 @@ private void ConnectCallbacks(bool connected) (this.callbacksConnected == true)) { // Remove callbacks for dependency properties - this.UnregisterPropertyChangedCallback(ColorProperty, this.tokenColor); - this.UnregisterPropertyChangedCallback(CustomPaletteProperty, this.tokenCustomPalette); + this.UnregisterPropertyChangedCallback(ColorProperty, this.tokenColor); + this.UnregisterPropertyChangedCallback(CustomPaletteProperty, this.tokenCustomPalette); + this.UnregisterPropertyChangedCallback(IsColorPaletteVisibleProperty, this.tokenIsColorPaletteVisible); this.callbacksConnected = false; } @@ -442,6 +436,19 @@ private void ConnectEvents(bool connected) return; } + /// + /// Updates all visual states based on current control properties. + /// + /// Whether transitions should occur when changing states. + private void UpdateVisualState(bool useTransitions) + { + VisualStateManager.GoToState(this, this.IsEnabled ? "Normal" : "Disabled", useTransitions); + VisualStateManager.GoToState(this, this.GetActiveColorRepresentation() == ColorRepresentation.Hsva ? "HsvSelected" : "RgbSelected", useTransitions); + VisualStateManager.GoToState(this, this.IsColorPaletteVisible ? "ColorPaletteVisible" : "ColorPaletteCollapsed", useTransitions); + + return; + } + /// /// Gets the active representation of the color: HSV or RGB. /// @@ -495,8 +502,6 @@ private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentati { this.HsvToggleButton.IsChecked = true; } - - VisualStateManager.GoToState(this, "HsvSelected", false); } else { @@ -511,10 +516,10 @@ private void SetActiveColorRepresentation(ColorRepresentation? colorRepresentati { this.HsvToggleButton.IsChecked = false; } - - VisualStateManager.GoToState(this, "RgbSelected", false); } + this.UpdateVisualState(false); + if (eventsDisconnectedByMethod) { this.ConnectEvents(true); @@ -1284,6 +1289,15 @@ private void OnCustomPaletteChanged(DependencyObject d, DependencyProperty e) return; } + /// + /// Callback for when the dependency property value changes. + /// + private void OnIsColorPaletteVisibleChanged(DependencyObject d, DependencyProperty e) + { + this.UpdateVisualState(false); + return; + } + /*************************************************************************************** * * Event Handling From db75308ddddba1f234299cf0cdf591b28ee132e4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 19 Jul 2020 20:49:11 -0400 Subject: [PATCH 37/73] Implement all VisualStateGroups --- .../ColorPickerButton/ColorPickerButton.xaml | 108 ++++++++++++++++-- 1 file changed, 98 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 06122149828..34762dc8ce5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -227,6 +227,7 @@ + @@ -260,9 +261,50 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -277,17 +319,34 @@ - + + + + + + + + + + + + + + + + + + - - @@ -313,6 +372,31 @@ + + + + + + + + + + + + + + + + + + + + - - - Box-shaped spectrum - Alpha channel disabled - - - + + + + Box-shaped spectrum + Alpha channel disabled + + + + - - - Box-shaped spectrum - Alpha channel enabled - - - + + + + Box-shaped spectrum + Alpha channel enabled + + + + - - - Ring-shaped spectrum - Alpha channel enabled - - - + + + + Ring-shaped spectrum + Alpha channel enabled + + + + - - - Ring-shaped spectrum - Alpha channel enabled - Saturation+Value spectrum channels - - - + + + + Ring-shaped spectrum + Alpha channel enabled + Saturation+Value spectrum channels + + + + \ No newline at end of file From 1fdf917bf6ecc115e46a19de6358d5cee1448b96 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 1 Aug 2020 20:03:54 -0400 Subject: [PATCH 40/73] Specially handle min/max in ColorToColorShadeConverter This ensures shades are always available to move to lighter or darker colors --- .../ColorToColorShadeConverter.cs | 172 +++++++++++------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs index f97191f769c..c5caacfe5ee 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs @@ -25,16 +25,20 @@ public object Convert( string language) { int shade; + byte tolerance = 0x05; HsvColor hsvColor; + Color rgbColor; // Get the current color in HSV if (value is Color valueColor) { - hsvColor = valueColor.ToHsv(); + rgbColor = valueColor; + hsvColor = rgbColor.ToHsv(); } else if (value is SolidColorBrush valueBrush) { - hsvColor = valueBrush.Color.ToHsv(); + rgbColor = valueBrush.Color; + hsvColor = rgbColor.ToHsv(); } else { @@ -51,74 +55,110 @@ public object Convert( throw new ArgumentException("Invalid parameter provided, unable to convert to double"); } - double colorHue = hsvColor.H; - double colorSaturation = hsvColor.S; - double colorValue = hsvColor.V; - double colorAlpha = hsvColor.A; + // Specially handle minimum (black) and maximum (white) + if (rgbColor.R <= (0x00 + tolerance) && + rgbColor.G <= (0x00 + tolerance) && + rgbColor.B <= (0x00 + tolerance)) + { + switch (shade) + { + case 1: + return Color.FromArgb(rgbColor.A, 0x3F, 0x3F, 0x3F); + case 2: + return Color.FromArgb(rgbColor.A, 0x80, 0x80, 0x80); + case 3: + return Color.FromArgb(rgbColor.A, 0xBF, 0xBF, 0xBF); + } - // Use the HSV representation as it's more perceptual - switch (shade) + return rgbColor; + } + else if (rgbColor.R >= (0xFF + tolerance) && + rgbColor.G >= (0xFF + tolerance) && + rgbColor.B >= (0xFF + tolerance)) { - case -3: - { - colorHue *= 1.0; - colorSaturation *= 1.10; - colorValue *= 0.40; - break; - } - - case -2: - { - colorHue *= 1.0; - colorSaturation *= 1.05; - colorValue *= 0.50; - break; - } - - case -1: - { - colorHue *= 1.0; - colorSaturation *= 1.0; - colorValue *= 0.75; - break; - } - - case 0: - { - // No change - break; - } - - case 1: - { - colorHue *= 1.00; - colorSaturation *= 1.00; - colorValue *= 1.05; - break; - } - - case 2: - { - colorHue *= 1.00; - colorSaturation *= 0.75; - colorValue *= 1.05; - break; - } - - case 3: - { - colorHue *= 1.00; - colorSaturation *= 0.65; - colorValue *= 1.05; - break; - } + switch (shade) + { + case -1: + return Color.FromArgb(rgbColor.A, 0xBF, 0xBF, 0xBF); + case -2: + return Color.FromArgb(rgbColor.A, 0x80, 0x80, 0x80); + case -3: + return Color.FromArgb(rgbColor.A, 0x3F, 0x3F, 0x3F); + } + + return rgbColor; } + else + { + double colorHue = hsvColor.H; + double colorSaturation = hsvColor.S; + double colorValue = hsvColor.V; + double colorAlpha = hsvColor.A; + + // Use the HSV representation as it's more perceptual + switch (shade) + { + case -3: + { + colorHue *= 1.0; + colorSaturation *= 1.10; + colorValue *= 0.40; + break; + } + + case -2: + { + colorHue *= 1.0; + colorSaturation *= 1.05; + colorValue *= 0.50; + break; + } - return Uwp.Helpers.ColorHelper.FromHsv( - Math.Clamp(colorHue, 0.0, 360.0), - Math.Clamp(colorSaturation, 0.0, 1.0), - Math.Clamp(colorValue, 0.0, 1.0), - Math.Clamp(colorAlpha, 0.0, 1.0)); + case -1: + { + colorHue *= 1.0; + colorSaturation *= 1.0; + colorValue *= 0.75; + break; + } + + case 0: + { + // No change + break; + } + + case 1: + { + colorHue *= 1.00; + colorSaturation *= 1.00; + colorValue *= 1.05; + break; + } + + case 2: + { + colorHue *= 1.00; + colorSaturation *= 0.75; + colorValue *= 1.05; + break; + } + + case 3: + { + colorHue *= 1.00; + colorSaturation *= 0.65; + colorValue *= 1.05; + break; + } + } + + return Uwp.Helpers.ColorHelper.FromHsv( + Math.Clamp(colorHue, 0.0, 360.0), + Math.Clamp(colorSaturation, 0.0, 1.0), + Math.Clamp(colorValue, 0.0, 1.0), + Math.Clamp(colorAlpha, 0.0, 1.0)); + } } /// From 9203f00129ebd35d2565584c7f7ba464c1710061 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 1 Aug 2020 20:25:16 -0400 Subject: [PATCH 41/73] Simplify ColorToColorShadeConverter algorithm --- .../ColorToColorShadeConverter.cs | 67 +++---------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs index c5caacfe5ee..467bef4fce2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs @@ -26,19 +26,17 @@ public object Convert( { int shade; byte tolerance = 0x05; - HsvColor hsvColor; + double valueDelta = 0.25; Color rgbColor; // Get the current color in HSV if (value is Color valueColor) { rgbColor = valueColor; - hsvColor = rgbColor.ToHsv(); } else if (value is SolidColorBrush valueBrush) { rgbColor = valueBrush.Color; - hsvColor = rgbColor.ToHsv(); } else { @@ -90,67 +88,20 @@ public object Convert( } else { + HsvColor hsvColor = rgbColor.ToHsv(); + double colorHue = hsvColor.H; double colorSaturation = hsvColor.S; double colorValue = hsvColor.V; double colorAlpha = hsvColor.A; - // Use the HSV representation as it's more perceptual - switch (shade) + // Use the HSV representation as it's more perceptual. + // Only the value is changed by a fixed percentage so the algorithm is reproducible. + // This does not account for perceptual differences and also does not match with + // system accent color calculation. + if (shade != 0) { - case -3: - { - colorHue *= 1.0; - colorSaturation *= 1.10; - colorValue *= 0.40; - break; - } - - case -2: - { - colorHue *= 1.0; - colorSaturation *= 1.05; - colorValue *= 0.50; - break; - } - - case -1: - { - colorHue *= 1.0; - colorSaturation *= 1.0; - colorValue *= 0.75; - break; - } - - case 0: - { - // No change - break; - } - - case 1: - { - colorHue *= 1.00; - colorSaturation *= 1.00; - colorValue *= 1.05; - break; - } - - case 2: - { - colorHue *= 1.00; - colorSaturation *= 0.75; - colorValue *= 1.05; - break; - } - - case 3: - { - colorHue *= 1.00; - colorSaturation *= 0.65; - colorValue *= 1.05; - break; - } + colorValue *= 1.0 + (shade * valueDelta); } return Uwp.Helpers.ColorHelper.FromHsv( From e2692c0264ceeeb232204d7682c4da5bdf928fbb Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 1 Aug 2020 20:30:48 -0400 Subject: [PATCH 42/73] Rename 'CustomPaletteColumns' to 'CustomPaletteColumnCount' --- .../ColorPickerButton.Properties.cs | 17 ++++++++--------- .../ColorPickerButton/ColorPickerButton.cs | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs index 9b1a9317f9e..6944096ce18 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs @@ -31,28 +31,27 @@ public ObservableCollection CustomPaletteColors } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty CustomPaletteColumnsProperty = + public static readonly DependencyProperty CustomPaletteColumnCountProperty = DependencyProperty.Register( - nameof(CustomPaletteColumns), + nameof(CustomPaletteColumnCount), typeof(int), typeof(ColorPickerButton), new PropertyMetadata(4)); /// /// Gets or sets the number of colors in each row (section) of the custom color palette. - /// A section is the number of columns within an entire row in the palette. /// Within a standard palette, rows are shades and columns are unique colors. /// - public int CustomPaletteColumns + public int CustomPaletteColumnCount { - get => (int)this.GetValue(CustomPaletteColumnsProperty); + get => (int)this.GetValue(CustomPaletteColumnCountProperty); set { - if (object.Equals(value, this.GetValue(CustomPaletteColumnsProperty)) == false) + if (object.Equals(value, this.GetValue(CustomPaletteColumnCountProperty)) == false) { - this.SetValue(CustomPaletteColumnsProperty, value); + this.SetValue(CustomPaletteColumnCountProperty, value); } } } @@ -69,7 +68,7 @@ public int CustomPaletteColumns /// /// Gets or sets the custom color palette. - /// This will automatically set and + /// This will automatically set and /// overwriting any existing values. /// public IColorPalette CustomPalette diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index fcfce3bd551..24f8c7c4d1d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -1274,7 +1274,7 @@ private void OnCustomPaletteChanged(DependencyObject d, DependencyProperty e) if (palette != null) { - this.CustomPaletteColumns = palette.ColorCount; + this.CustomPaletteColumnCount = palette.ColorCount; this.CustomPaletteColors.Clear(); for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++) @@ -1361,7 +1361,7 @@ private void PaletteGridView_Loaded(object sender, RoutedEventArgs e) if (palettePanel != null) { - palettePanel.Columns = this.CustomPaletteColumns; + palettePanel.Columns = this.CustomPaletteColumnCount; } return; From b51df4d328757beb5dd23facfd6dcaf4c78a51ef Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 1 Aug 2020 20:42:35 -0400 Subject: [PATCH 43/73] Adjust margin before/after channel sliders As a group there is now 24px (2 x 12px) margin above and below the sliders. --- .../ColorPickerButton/ColorPickerButton.xaml | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 34762dc8ce5..707e19099b7 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -1148,10 +1148,13 @@ + + + Date: Sat, 1 Aug 2020 20:47:54 -0400 Subject: [PATCH 44/73] Fix converter exception messages --- .../ColorPickerButton/ColorToColorShadeConverter.cs | 4 ++-- .../ColorPickerButton/ColorToDisplayNameConverter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs index 467bef4fce2..e83543fea8d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs @@ -40,7 +40,7 @@ public object Convert( } else { - throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); + throw new ArgumentException("Invalid color value provided"); } // Get the value component delta @@ -50,7 +50,7 @@ public object Convert( } catch { - throw new ArgumentException("Invalid parameter provided, unable to convert to double"); + throw new ArgumentException("Invalid parameter provided, unable to convert to integer"); } // Specially handle minimum (black) and maximum (white) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs index 65361b31a3a..965491f316f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToDisplayNameConverter.cs @@ -33,7 +33,7 @@ public object Convert( } else { - throw new ArgumentException("Invalid color value provided, unable to convert to HsvColor"); + throw new ArgumentException("Invalid color value provided"); } return ColorHelper.ToDisplayName(color); From 1b282400966cd7413f5323c5b7dfe581c648d2ab Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 1 Aug 2020 20:52:18 -0400 Subject: [PATCH 45/73] Move ColorToDisplayNameConverter into Microsoft.Toolkit.Uwp.UI.Converters ColorToDisplayNameConverter is considered general-purpose --- .../ColorPickerButton/ColorPickerButton.xaml | 5 +++-- .../Converters}/ColorToDisplayNameConverter.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename {Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton => Microsoft.Toolkit.Uwp.UI/Converters}/ColorToDisplayNameConverter.cs (96%) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml index 707e19099b7..9b9687fb696 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml @@ -2,7 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)" xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)" - xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"> + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"> - + Date: Mon, 3 Aug 2020 15:37:20 -0400 Subject: [PATCH 50/73] Ensure ColorPickerButton/Slider don't get optimized away by .NET native --- .../ColorPickerButton/ColorPickerButtonPage.xaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml index 506863f971d..b8938de16d6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml @@ -7,6 +7,10 @@ mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - + + + + From 4e72f2bee403347c4f931fbb1ac81f836f4723f0 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 3 Aug 2020 16:12:15 -0400 Subject: [PATCH 51/73] Fix how color coercion is applied when interacting with spectrum --- .../ColorPickerButton/ColorPickerButton.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs index a90f9297973..7ca287765a1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs @@ -1402,6 +1402,8 @@ private void ColorSpectrum_ColorChanged(ColorSpectrum sender, Windows.UI.Xaml.Co /// private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) { + Color rgbColor = this.ColorSpectrumControl.Color; + /* If this control has a color that is currently empty (#00000000), * selecting a new color directly in the spectrum will fail. This is * a bug in the color spectrum. Selecting a new color in the spectrum will @@ -1438,7 +1440,7 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) */ // In the future Color.IsEmpty will hopefully be added to UWP - if (IsColorEmpty(this.Color)) + if (IsColorEmpty(rgbColor)) { /* The following code may be used in the future if ever the selected color is available @@ -1485,15 +1487,14 @@ private void ColorSpectrum_GotFocus(object sender, RoutedEventArgs e) } */ - this.Color = Colors.White; + this.ScheduleColorUpdate(Colors.White); } - - // As an additional usability improvement, reset alpha to maximum when the spectrum is used. - // The color spectrum has no alpha channel and it is much more intuitive to do this for the user - // especially if the picker was initially set with Colors.Transparent. - if (this.Color.A == 0x00) + else if (rgbColor.A == 0x00) { - this.Color = Color.FromArgb(0xFF, this.Color.R, this.Color.G, this.Color.B); + // As an additional usability improvement, reset alpha to maximum when the spectrum is used. + // The color spectrum has no alpha channel and it is much more intuitive to do this for the user + // especially if the picker was initially set with Colors.Transparent. + this.ScheduleColorUpdate(Color.FromArgb(0xFF, rgbColor.R, rgbColor.G, rgbColor.B)); } return; From 7b1fef8caf2afab8d770c807a9aa86b7ea6cd574 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Thu, 3 Sep 2020 14:41:17 -0700 Subject: [PATCH 52/73] Initial Refactor of ColorPickerButton to ColorPicker Need to clean things up a bit more and having issues with Pivot, added notes to original PR --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 12 +- .../ColorPicker.png} | Bin .../ColorPickerButtonPage.xaml | 0 .../ColorPickerButtonPage.xaml.cs | 2 +- .../ColorPickerButtonXaml.bind | 0 .../ColorPicker/ColorPickerPage.xaml | 16 + .../ColorPicker/ColorPickerPage.xaml.cs | 20 + .../ColorPicker/ColorPickerXaml.bind | 89 ++ .../SamplePages/samples.json | 20 +- .../ColorPicker.Properties.cs} | 12 +- .../ColorPicker.Rendering.cs} | 4 +- .../ColorPicker.cs} | 96 +- .../ColorPicker/ColorPicker.xaml | 1407 +++++++++++++++++ .../ColorPickerButton.xaml | 185 +-- .../ColorPickerSlider.cs} | 12 +- .../ColorToColorShadeConverter.cs | 2 +- .../ColorToHexConverter.cs | 2 +- .../ContrastBrushConverter.cs | 2 +- .../IColorPalette.cs | 0 .../WindowsColorPalette.cs | 0 .../InfiniteCanvas/InfiniteCanvas.TextBox.cs | 2 +- .../InfiniteCanvas/InfiniteCanvas.cs | 6 +- .../InfiniteCanvas/InfiniteCanvas.xaml | 1 + .../Microsoft.Toolkit.Uwp.UI.Controls.csproj | 13 + .../Themes/Generic.xaml | 3 +- 25 files changed, 1634 insertions(+), 272 deletions(-) rename Microsoft.Toolkit.Uwp.SampleApp/SamplePages/{ColorPickerButton/ColorPickerButton.png => ColorPicker/ColorPicker.png} (100%) rename Microsoft.Toolkit.Uwp.SampleApp/SamplePages/{ColorPickerButton => ColorPicker}/ColorPickerButtonPage.xaml (100%) rename Microsoft.Toolkit.Uwp.SampleApp/SamplePages/{ColorPickerButton => ColorPicker}/ColorPickerButtonPage.xaml.cs (86%) rename Microsoft.Toolkit.Uwp.SampleApp/SamplePages/{ColorPickerButton => ColorPicker}/ColorPickerButtonXaml.bind (100%) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton/ColorPickerButton.Properties.cs => ColorPicker/ColorPicker.Properties.cs} (93%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton/ColorPickerButton.Rendering.cs => ColorPicker/ColorPicker.Rendering.cs} (99%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton/ColorPickerButton.cs => ColorPicker/ColorPicker.cs} (94%) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/ColorPickerButton.xaml (89%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton/ColorPickerButtonSlider.cs => ColorPicker/ColorPickerSlider.cs} (87%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/ColorToColorShadeConverter.cs (98%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/ColorToHexConverter.cs (96%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/ContrastBrushConverter.cs (98%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/IColorPalette.cs (100%) rename Microsoft.Toolkit.Uwp.UI.Controls/{ColorPickerButton => ColorPicker}/WindowsColorPalette.cs (100%) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 2c4cce232cc..5944f463d54 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -272,7 +272,7 @@ - + @@ -518,8 +518,8 @@ - - ColorPickerButtonPage.xaml + + ColorPickerPage.xaml EnumValuesExtensionPage.xaml @@ -625,9 +625,9 @@ - + @@ -1006,9 +1006,9 @@ Designer MSBuild:Compile - - Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButton.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPicker.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButton.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPicker.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml.cs similarity index 86% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml.cs index e82b33e08b6..72e50046b8f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml.cs @@ -8,7 +8,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages { /// - /// A page that shows how to use the control. + /// A page that shows how to use the control. /// public sealed partial class ColorPickerButtonPage : Page { diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPickerButton/ColorPickerButtonXaml.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml new file mode 100644 index 00000000000..f3f82b0397d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml.cs new file mode 100644 index 00000000000..15d297b2527 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Controls; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the control. + /// + public sealed partial class ColorPickerPage : Page + { + public ColorPickerPage() + { + this.InitializeComponent(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind new file mode 100644 index 00000000000..a8a6d994f19 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind @@ -0,0 +1,89 @@ + + + + + + + + + + + + Box-shaped spectrum + Alpha channel disabled + + + + + + + Box-shaped spectrum + Alpha channel enabled + + + + + + + Ring-shaped spectrum + Alpha channel enabled + + + + + + + Ring-shaped spectrum + Alpha channel enabled + Saturation+Value spectrum channels + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 19fb0e27424..8a2d0e51251 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -47,15 +47,25 @@ "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/Carousel.md" }, { - "Name": "ColorPickerButton", - "Type": "ColorPickerButtonPage", + "Name": "ColorPicker", + "Type": "ColorPickerPage", "Subcategory": "Input", - "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", + "About": "An improved color picker control providing more options to select colors.", "CodeUrl": "TBD", - "XamlCodeFile": "ColorPickerButtonXaml.bind", - "Icon": "/SamplePages/ColorPickerButton/ColorPickerButton.png", + "XamlCodeFile": "ColorPickerXaml.bind", + "Icon": "/SamplePages/ColorPicker/ColorPicker.png", "DocumentationUrl": "TBD" }, + //{ + // "Name": "ColorPickerButton", + // "Type": "ColorPickerButtonPage", + // "Subcategory": "Input", + // "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", + // "CodeUrl": "TBD", + // "XamlCodeFile": "ColorPickerButtonXaml.bind", + // "Icon": "/SamplePages/ColorPicker/ColorPicker.png", + // "DocumentationUrl": "TBD" + //}, { "Name": "AdaptiveGridView", "Type": "AdaptiveGridViewPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Properties.cs similarity index 93% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Properties.cs index 6944096ce18..b21772a2ea9 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Properties.cs @@ -8,9 +8,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { /// - /// Contains all properties for the . + /// Contains all properties for the . /// - public partial class ColorPickerButton + public partial class ColorPicker { /// /// Identifies the dependency property. @@ -19,7 +19,7 @@ public partial class ColorPickerButton DependencyProperty.Register( nameof(CustomPaletteColors), typeof(ObservableCollection), - typeof(ColorPickerButton), + typeof(ColorPicker), new PropertyMetadata(Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00))); /// @@ -37,7 +37,7 @@ public ObservableCollection CustomPaletteColors DependencyProperty.Register( nameof(CustomPaletteColumnCount), typeof(int), - typeof(ColorPickerButton), + typeof(ColorPicker), new PropertyMetadata(4)); /// @@ -63,7 +63,7 @@ public int CustomPaletteColumnCount DependencyProperty.Register( nameof(CustomPalette), typeof(IColorPalette), - typeof(ColorPickerButton), + typeof(ColorPicker), new PropertyMetadata(DependencyProperty.UnsetValue)); /// @@ -90,7 +90,7 @@ public IColorPalette CustomPalette DependencyProperty.Register( nameof(IsColorPaletteVisible), typeof(bool), - typeof(ColorPickerButton), + typeof(ColorPicker), new PropertyMetadata(true)); /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Rendering.cs similarity index 99% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Rendering.cs index bff45ef5f0e..271c9179e0f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.Rendering.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.Rendering.cs @@ -15,9 +15,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { /// - /// Contains the rendering methods used within . + /// Contains the rendering methods used within . /// - public partial class ColorPickerButton + public partial class ColorPicker { /// /// Generates a new bitmap of the specified size by changing a specific color channel. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs similarity index 94% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index 7ca287765a1..65616625b32 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -8,7 +8,7 @@ using System.Collections.Specialized; using System.Globalization; using Microsoft.Toolkit.Uwp.Helpers; -using Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerButtonConverters; +using Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.Foundation; using Windows.UI; @@ -22,39 +22,38 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// /// Presents a color spectrum, a palette of colors, and color channel sliders for user selection of a color. /// - [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelSlider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.AlphaChannelTextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel1Slider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel1TextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel2Slider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel2TextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel3Slider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.Channel3TextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground1Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground2Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground3Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground4Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground5Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground6Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground7Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground8Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground9Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.CheckeredBackground10Border), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumControl), Type = typeof(ColorSpectrum))] - [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumAlphaSlider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPickerButton.PaletteGridView), Type = typeof(GridView))] - [TemplatePart(Name = nameof(ColorPickerButton.HexInputTextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPickerButton.HsvToggleButton), Type = typeof(ToggleButton))] - [TemplatePart(Name = nameof(ColorPickerButton.RgbToggleButton), Type = typeof(ToggleButton))] - [TemplatePart(Name = nameof(ColorPickerButton.P1PreviewBorder), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.P2PreviewBorder), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.N1PreviewBorder), Type = typeof(Border))] - [TemplatePart(Name = nameof(ColorPickerButton.N2PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.AlphaChannelSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.AlphaChannelTextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPicker.Channel1Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel1TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPicker.Channel2Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel2TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPicker.Channel3Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel3TextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground1Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground2Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground3Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground4Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground5Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground6Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground7Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground8Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground9Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground10Border), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumControl), Type = typeof(ColorSpectrum))] + [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumAlphaSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.HexInputTextBox), Type = typeof(TextBox))] + [TemplatePart(Name = nameof(ColorPicker.HsvToggleButton), Type = typeof(ToggleButton))] + [TemplatePart(Name = nameof(ColorPicker.RgbToggleButton), Type = typeof(ToggleButton))] + [TemplatePart(Name = nameof(ColorPicker.P1PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.P2PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.N1PreviewBorder), Type = typeof(Border))] + [TemplatePart(Name = nameof(ColorPicker.N2PreviewBorder), Type = typeof(Border))] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501:Statement should not be on a single line", Justification = "Inline brackets are used to improve code readability with repeated null checks.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1025:Code should not contain multiple whitespace in a row", Justification = "Whitespace is used to align code in columns for readability.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Only template parts start with a capital letter. This differentiates them from other fields.")] - public partial class ColorPickerButton : Windows.UI.Xaml.Controls.ColorPicker + public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker { /// /// The period that scheduled color updates will be applied. @@ -124,7 +123,6 @@ private enum ColorChannel private ColorSpectrum ColorSpectrumControl; private Slider ColorSpectrumAlphaSlider; private Slider ColorSpectrumThirdDimensionSlider; - private GridView PaletteGridView; private TextBox HexInputTextBox; private ToggleButton HsvToggleButton; private ToggleButton RgbToggleButton; @@ -162,11 +160,11 @@ private enum ColorChannel ***************************************************************************************/ /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ColorPickerButton() + public ColorPicker() { - this.DefaultStyleKey = typeof(ColorPickerButton); + this.DefaultStyleKey = typeof(ColorPicker); // Setup collections this.SetValue(CustomPaletteColorsProperty, new ObservableCollection()); @@ -184,9 +182,9 @@ public ColorPickerButton() } /// - /// Finalizes an instance of the class. + /// Finalizes an instance of the class. /// - ~ColorPickerButton() + ~ColorPicker() { this.StopDispatcherTimer(); this.CustomPaletteColors.CollectionChanged -= CustomPaletteColors_CollectionChanged; @@ -221,8 +219,6 @@ protected override void OnApplyTemplate() this.ColorSpectrumAlphaSlider = this.GetTemplateChild("ColorSpectrumAlphaSlider", false); this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild("ColorSpectrumThirdDimensionSlider", false); - this.PaletteGridView = this.GetTemplateChild("PaletteGridView", false); - this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); this.HsvToggleButton = this.GetTemplateChild("HsvToggleButton", false); this.RgbToggleButton = this.GetTemplateChild("RgbToggleButton", false); @@ -323,7 +319,6 @@ private void ConnectEvents(bool connected) // Add all events if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.ColorChanged += ColorSpectrum_ColorChanged; } if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.GotFocus += ColorSpectrum_GotFocus; } - if (this.PaletteGridView != null) { this.PaletteGridView.Loaded += PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown += HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus += HexInputTextBox_LostFocus; } if (this.HsvToggleButton != null) { this.HsvToggleButton.Checked += ColorRepToggleButton_CheckedUnchecked; } @@ -378,7 +373,6 @@ private void ConnectEvents(bool connected) // Remove all events if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.ColorChanged -= ColorSpectrum_ColorChanged; } if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.GotFocus -= ColorSpectrum_GotFocus; } - if (this.PaletteGridView != null) { this.PaletteGridView.Loaded -= PaletteGridView_Loaded; } if (this.HexInputTextBox != null) { this.HexInputTextBox.KeyDown -= HexInputTextBox_KeyDown; } if (this.HexInputTextBox != null) { this.HexInputTextBox.LostFocus -= HexInputTextBox_LostFocus; } if (this.HsvToggleButton != null) { this.HsvToggleButton.Checked -= ColorRepToggleButton_CheckedUnchecked; } @@ -1254,7 +1248,7 @@ private void DispatcherTimer_Tick(object sender, object e) ***************************************************************************************/ /// - /// Callback for when the dependency property value changes. + /// Callback for when the dependency property value changes. /// private void OnColorChanged(DependencyObject d, DependencyProperty e) { @@ -1358,24 +1352,6 @@ private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventAr return; } - /// - /// Event handler for when the grid view showing all colors in the palette is loaded. - /// This will set the correct column count to any UniformGrid panel. - /// - private void PaletteGridView_Loaded(object sender, RoutedEventArgs e) - { - // RelativeSource binding of an ancestor type doesn't work in UWP. - // Therefore, setting this property must be done here in code-behind. - var palettePanel = (sender as DependencyObject)?.FindDescendant(); - - if (palettePanel != null) - { - palettePanel.Columns = this.CustomPaletteColumnCount; - } - - return; - } - /// /// Event handler for when the list of custom palette colors is changed. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml new file mode 100644 index 00000000000..b91c9e8aafa --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -0,0 +1,1407 @@ + + + + + Transparent + + + + + #70F5F5F5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml similarity index 89% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml index 13940d25389..80abd02ecd7 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml @@ -3,7 +3,7 @@ xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)" xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" - xmlns:localconverters="using:Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerButtonConverters" + xmlns:localconverters="using:Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -496,11 +319,10 @@ - - - /// A implementation for use in the . + /// A implementation for use in the . /// /// /// @@ -22,11 +23,18 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// here in the future just like ColorPicker. /// /// - public class ColorPickerButtonSlider : Slider + public class ColorPickerSlider : Slider { private Size oldSize; private Size measuredSize; + /// + public ColorPickerSlider() + : base() + { + this.DefaultStyleKey = typeof(ColorPickerSlider); + } + /// /// Measures the size in layout required for child elements and determines a size for the /// FrameworkElement-derived class. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToColorShadeConverter.cs similarity index 98% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToColorShadeConverter.cs index 532e91f3d41..a74e6e4ae54 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToColorShadeConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToColorShadeConverter.cs @@ -8,7 +8,7 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerButtonConverters +namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters { /// /// Creates an accent color shade from a color value. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToHexConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToHexConverter.cs similarity index 96% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToHexConverter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToHexConverter.cs index 2622b246180..b460c8820fa 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ColorToHexConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorToHexConverter.cs @@ -8,7 +8,7 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerButtonConverters +namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters { /// /// Converts a color to a hex string and vice versa. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ContrastBrushConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs similarity index 98% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ContrastBrushConverter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs index c05fd1a67aa..97d0f6bc7bb 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/ContrastBrushConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs @@ -7,7 +7,7 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerButtonConverters +namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters { /// /// Gets a color, either black or white, depending on the brightness of the supplied color. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/IColorPalette.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/IColorPalette.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/IColorPalette.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/WindowsColorPalette.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPickerButton/WindowsColorPalette.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/WindowsColorPalette.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs index 94a537fbb53..6edfd098f16 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.TextBox.cs @@ -108,7 +108,7 @@ private void CanvasTextBox_SizeChanged(object sender, SizeChangedEventArgs e) SelectedTextDrawable?.UpdateBounds(_canvasTextBox.ActualWidth, _canvasTextBox.ActualHeight); } - private void CanvasTextBoxColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args) + private void CanvasTextBoxColorPicker_ColorChanged(Windows.UI.Xaml.Controls.ColorPicker sender, ColorChangedEventArgs args) { if (SelectedTextDrawable != null) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs index 23cb0a3ef3c..be4e955b281 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs @@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// InfiniteCanvas is a canvas that supports Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export canvas data, Import canvas data. /// [TemplatePart(Name = CanvasTextBoxToolsName, Type = typeof(StackPanel))] - [TemplatePart(Name = CanvasTextBoxColorPickerName, Type = typeof(ColorPicker))] + [TemplatePart(Name = CanvasTextBoxColorPickerName, Type = typeof(Windows.UI.Xaml.Controls.ColorPicker))] [TemplatePart(Name = CanvasTextBoxFontSizeTextBoxName, Type = typeof(TextBox))] [TemplatePart(Name = CanvasTextBoxItalicButtonName, Type = typeof(ToggleButton))] [TemplatePart(Name = CanvasTextBoxBoldButtonName, Type = typeof(ToggleButton))] @@ -69,7 +69,7 @@ public partial class InfiniteCanvas : Control private InkToolbarCustomToggleButton _enableTouchInkingButton; private InfiniteCanvasTextBox _canvasTextBox; private StackPanel _canvasTextBoxTools; - private ColorPicker _canvasTextBoxColorPicker; + private Windows.UI.Xaml.Controls.ColorPicker _canvasTextBoxColorPicker; private TextBox _canvasTextBoxFontSizeTextBox; private ToggleButton _canvasTextBoxItalicButton; @@ -243,7 +243,7 @@ public InfiniteCanvas() protected override void OnApplyTemplate() { _canvasTextBoxTools = (StackPanel)GetTemplateChild(CanvasTextBoxToolsName); - _canvasTextBoxColorPicker = (ColorPicker)GetTemplateChild(CanvasTextBoxColorPickerName); + this._canvasTextBoxColorPicker = (Windows.UI.Xaml.Controls.ColorPicker)GetTemplateChild(CanvasTextBoxColorPickerName); _canvasTextBoxFontSizeTextBox = (TextBox)GetTemplateChild(CanvasTextBoxFontSizeTextBoxName); _canvasTextBoxItalicButton = (ToggleButton)GetTemplateChild(CanvasTextBoxItalicButtonName); _canvasTextBoxBoldButton = (ToggleButton)GetTemplateChild(CanvasTextBoxBoldButtonName); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml index 9d78d87c390..975ef857783 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml @@ -63,6 +63,7 @@ + protected override void OnApplyTemplate() { - this.ColorSpectrumControl = this.GetTemplateChild("ColorSpectrumControl", false); - this.ColorSpectrumAlphaSlider = this.GetTemplateChild("ColorSpectrumAlphaSlider", false); - this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild("ColorSpectrumThirdDimensionSlider", false); - - this.HexInputTextBox = this.GetTemplateChild("HexInputTextBox", false); - this.HsvToggleButton = this.GetTemplateChild("HsvToggleButton", false); - this.RgbToggleButton = this.GetTemplateChild("RgbToggleButton", false); - - this.Channel1TextBox = this.GetTemplateChild("Channel1TextBox", false); - this.Channel2TextBox = this.GetTemplateChild("Channel2TextBox", false); - this.Channel3TextBox = this.GetTemplateChild("Channel3TextBox", false); - this.AlphaChannelTextBox = this.GetTemplateChild("AlphaChannelTextBox", false); - - this.Channel1Slider = this.GetTemplateChild("Channel1Slider", false); - this.Channel2Slider = this.GetTemplateChild("Channel2Slider", false); - this.Channel3Slider = this.GetTemplateChild("Channel3Slider", false); - this.AlphaChannelSlider = this.GetTemplateChild("AlphaChannelSlider", false); - - this.N1PreviewBorder = this.GetTemplateChild("N1PreviewBorder", false); - this.N2PreviewBorder = this.GetTemplateChild("N2PreviewBorder", false); - this.P1PreviewBorder = this.GetTemplateChild("P1PreviewBorder", false); - this.P2PreviewBorder = this.GetTemplateChild("P2PreviewBorder", false); - - this.CheckeredBackground1Border = this.GetTemplateChild("CheckeredBackground1Border", false); - this.CheckeredBackground2Border = this.GetTemplateChild("CheckeredBackground2Border", false); - this.CheckeredBackground3Border = this.GetTemplateChild("CheckeredBackground3Border", false); - this.CheckeredBackground4Border = this.GetTemplateChild("CheckeredBackground4Border", false); - this.CheckeredBackground5Border = this.GetTemplateChild("CheckeredBackground5Border", false); - this.CheckeredBackground6Border = this.GetTemplateChild("CheckeredBackground6Border", false); - this.CheckeredBackground7Border = this.GetTemplateChild("CheckeredBackground7Border", false); - this.CheckeredBackground8Border = this.GetTemplateChild("CheckeredBackground8Border", false); - this.CheckeredBackground9Border = this.GetTemplateChild("CheckeredBackground9Border", false); - this.CheckeredBackground10Border = this.GetTemplateChild("CheckeredBackground10Border", false); + this.ColorSpectrumControl = this.GetTemplateChild(nameof(ColorSpectrumControl)); + this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); + this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild(nameof(ColorSpectrumThirdDimensionSlider)); + + this.HexInputTextBox = this.GetTemplateChild(nameof(HexInputTextBox)); + this.HsvToggleButton = this.GetTemplateChild(nameof(HsvToggleButton)); + this.RgbToggleButton = this.GetTemplateChild(nameof(RgbToggleButton)); + + this.Channel1TextBox = this.GetTemplateChild(nameof(Channel1TextBox)); + this.Channel2TextBox = this.GetTemplateChild(nameof(Channel2TextBox)); + this.Channel3TextBox = this.GetTemplateChild(nameof(Channel3TextBox)); + this.AlphaChannelTextBox = this.GetTemplateChild(nameof(AlphaChannelTextBox)); + + this.Channel1Slider = this.GetTemplateChild(nameof(Channel1Slider)); + this.Channel2Slider = this.GetTemplateChild(nameof(Channel2Slider)); + this.Channel3Slider = this.GetTemplateChild(nameof(Channel3Slider)); + this.AlphaChannelSlider = this.GetTemplateChild(nameof(AlphaChannelSlider)); + + this.N1PreviewBorder = this.GetTemplateChild(nameof(N1PreviewBorder)); + this.N2PreviewBorder = this.GetTemplateChild(nameof(N2PreviewBorder)); + this.P1PreviewBorder = this.GetTemplateChild(nameof(P1PreviewBorder)); + this.P2PreviewBorder = this.GetTemplateChild(nameof(P2PreviewBorder)); + + this.CheckeredBackground1Border = this.GetTemplateChild(nameof(CheckeredBackground1Border)); + this.CheckeredBackground2Border = this.GetTemplateChild(nameof(CheckeredBackground2Border)); + this.CheckeredBackground3Border = this.GetTemplateChild(nameof(CheckeredBackground3Border)); + this.CheckeredBackground4Border = this.GetTemplateChild(nameof(CheckeredBackground4Border)); + this.CheckeredBackground5Border = this.GetTemplateChild(nameof(CheckeredBackground5Border)); + this.CheckeredBackground6Border = this.GetTemplateChild(nameof(CheckeredBackground6Border)); + this.CheckeredBackground7Border = this.GetTemplateChild(nameof(CheckeredBackground7Border)); + this.CheckeredBackground8Border = this.GetTemplateChild(nameof(CheckeredBackground8Border)); + this.CheckeredBackground9Border = this.GetTemplateChild(nameof(CheckeredBackground9Border)); + this.CheckeredBackground10Border = this.GetTemplateChild(nameof(CheckeredBackground10Border)); // Must connect after controls are resolved this.ConnectEvents(true); @@ -265,13 +265,13 @@ protected override void OnApplyTemplate() /// The name of the element to find. /// Whether the element is required and will throw an exception if missing. /// The template child matching the given name and type. - private T GetTemplateChild(string childName, bool isRequired = true) + private T GetTemplateChild(string childName, bool isRequired = false) where T : DependencyObject { T child = this.GetTemplateChild(childName) as T; if ((child == null) && isRequired) { - throw new NullReferenceException(childName); + ThrowHelper.ThrowArgumentNullException(childName); } return child; From 4bbef930cabfa86ca2353335607eb8f14062f8ca Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Fri, 23 Oct 2020 14:40:01 -0700 Subject: [PATCH 55/73] Apply XAML Styler to ColorPicker.xaml --- .../ColorPicker/ColorPicker.xaml | 1703 ++++++++--------- 1 file changed, 845 insertions(+), 858 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 5e953079e99..68955118a88 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -1,9 +1,9 @@  + xmlns:ex="using:Microsoft.Toolkit.Uwp.UI.Extensions" + xmlns:localconverters="using:Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters"> @@ -18,235 +18,67 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + VerticalAlignment="Top" + Background="{ThemeResource SystemControlAcrylicElementBrush}" + CornerRadius="4" /> - + - - + + - + @@ -273,65 +105,66 @@ - - + Grid.Row="1" + Grid.RowSpan="2" + Grid.Column="0" + Width="20" + Margin="0,0,12,0" + HorizontalAlignment="Center" + VerticalAlignment="Stretch" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" + Orientation="Vertical" /> + + + Grid.Row="0" + Grid.RowSpan="2" + Grid.Column="1" + MinWidth="256" + MinHeight="256" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + AutomationProperties.Name="Third Dimension" + Components="{TemplateBinding ColorSpectrumComponents}" + MaxHue="{TemplateBinding MaxHue}" + MaxSaturation="{TemplateBinding MaxSaturation}" + MaxValue="{TemplateBinding MaxValue}" + MinHue="{TemplateBinding MinHue}" + MinSaturation="{TemplateBinding MinSaturation}" + MinValue="{TemplateBinding MinValue}" + Shape="{TemplateBinding ColorSpectrumShape}" /> + Grid.Row="1" + Grid.RowSpan="2" + Grid.Column="2" + Width="20" + Margin="12,0,0,0" + HorizontalAlignment="Center" + VerticalAlignment="Stretch" + AutomationProperties.Name="Alpha Channel" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" + Orientation="Vertical" /> - + - + - + @@ -354,201 +187,201 @@ - + - - + + - - + + + Grid.Column="0" + HorizontalAlignment="Stretch" + Background="Transparent" + BorderBrush="{ThemeResource ToggleButtonBackgroundChecked}" + BorderThickness="1" + Content="RGB" + CornerRadius="2,0,0,2" + IsThreeState="False" /> + Grid.Column="1" + HorizontalAlignment="Stretch" + Background="Transparent" + BorderBrush="{ThemeResource ToggleButtonBackgroundChecked}" + BorderThickness="1" + Content="HSV" + CornerRadius="0,2,2,0" + IsThreeState="False" /> + Grid.Column="3"> - + Height="32" + Background="{ThemeResource TextBoxDisabledBackgroundThemeBrush}" + BorderBrush="{ThemeResource TextControlBorderBrush}" + BorderThickness="1,1,0,1" + CornerRadius="2,0,0,2"> + - + + Grid.Column="1" + Height="32" + HorizontalAlignment="Stretch" + AutomationProperties.Name="Hexadecimal Color Input" + Style="{StaticResource InputTextBoxStyle}" /> - - - + + + + HorizontalAlignment="Center" + VerticalAlignment="Center" + FontWeight="SemiBold" + Foreground="{ThemeResource TextBoxDisabledForegroundThemeBrush}" + Text="R" /> + Grid.Row="2" + Grid.Column="1" + VerticalAlignment="Center" + AutomationProperties.Name="Red Channel" + Style="{StaticResource InputTextBoxStyle}" /> - - + Grid.Row="2" + Grid.Column="2" + Margin="12,0,0,0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + AutomationProperties.Name="Red Channel" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" /> + + + HorizontalAlignment="Center" + VerticalAlignment="Center" + FontWeight="SemiBold" + Foreground="{ThemeResource TextBoxDisabledForegroundThemeBrush}" + Text="G" /> + Grid.Row="3" + Grid.Column="1" + VerticalAlignment="Center" + AutomationProperties.Name="Green Channel" + Style="{StaticResource InputTextBoxStyle}" /> - - + Grid.Row="3" + Grid.Column="2" + Margin="12,0,0,0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + AutomationProperties.Name="Green Channel" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" /> + + + HorizontalAlignment="Center" + VerticalAlignment="Center" + FontWeight="SemiBold" + Foreground="{ThemeResource TextBoxDisabledForegroundThemeBrush}" + Text="B" /> + Grid.Row="4" + Grid.Column="1" + VerticalAlignment="Center" + AutomationProperties.Name="Blue Channel" + Style="{StaticResource InputTextBoxStyle}" /> - + Grid.Row="4" + Grid.Column="2" + Margin="12,0,0,0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + AutomationProperties.Name="Blue Channel" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" /> + + Grid.Row="5" + Grid.Column="0" + Height="{Binding ElementName=AlphaChannelTextBox, Path=ActualHeight}" + VerticalAlignment="Center" + Background="{ThemeResource TextBoxDisabledBackgroundThemeBrush}" + BorderBrush="{ThemeResource TextControlBorderBrush}" + BorderThickness="1,1,0,1" + CornerRadius="2,0,0,2"> + HorizontalAlignment="Center" + VerticalAlignment="Center" + FontWeight="SemiBold" + Foreground="{ThemeResource TextBoxDisabledForegroundThemeBrush}" + Text="A" /> + Grid.Row="5" + Grid.Column="1" + VerticalAlignment="Center" + AutomationProperties.Name="Alpha Channel" + Style="{StaticResource InputTextBoxStyle}" /> + Grid.Row="5" + Grid.Column="2" + Margin="12,0,0,0" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + AutomationProperties.Name="Alpha Channel" + BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Color, Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" /> - + - - - + + + @@ -557,78 +390,80 @@ - + + Grid.Row="1" + Grid.Column="0" + Grid.ColumnSpan="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" /> + Grid.Row="1" + Grid.Column="0" + VerticalAlignment="Stretch" + CornerRadius="2,0,0,2"> - + + Grid.Row="1" + Grid.Column="1"> - + + Grid.Row="1" + Grid.Column="3" + Grid.ColumnSpan="2" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" /> + Grid.Row="1" + Grid.Column="3"> + Grid.Row="1" + Grid.Column="4" + CornerRadius="0,2,2,0"> - - + + + ShadowOpacity="0.75" + Color="Black"> - + + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + CornerRadius="2" /> + CornerRadius="2"> @@ -636,102 +471,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Background="{ThemeResource SliderContainerBackground}" + Control.IsTemplateFocusTarget="True"> + MinHeight="20"> @@ -746,33 +664,33 @@ + Grid.Row="1" + Grid.ColumnSpan="3" + Height="20" + Fill="{TemplateBinding Background}" + RadiusX="10" + RadiusY="10" /> + Grid.Row="1" + Fill="Transparent" + RadiusX="8" + RadiusY="8" /> + Grid.Row="0" + Grid.RowSpan="3" + Grid.Column="1" + Width="20" + Height="20" + AutomationProperties.AccessibilityView="Raw" + BorderBrush="{TemplateBinding BorderBrush}" + CornerRadius="10" + DataContext="{TemplateBinding Value}" + FocusVisualMargin="-14,-6,-14,-6" + Style="{StaticResource SliderThumbStyle}" /> + MinWidth="20" + Visibility="Collapsed"> @@ -787,84 +705,175 @@ + Grid.RowSpan="3" + Grid.Column="1" + Width="20" + Fill="{TemplateBinding Background}" + RadiusX="10" + RadiusY="10" /> + Grid.Row="2" + Grid.Column="1" + Fill="Transparent" + RadiusX="8" + RadiusY="8" /> + Grid.Row="1" + Grid.Column="0" + Grid.ColumnSpan="3" + Width="20" + Height="20" + AutomationProperties.AccessibilityView="Raw" + BorderBrush="{TemplateBinding BorderBrush}" + CornerRadius="10" + DataContext="{TemplateBinding Value}" + FocusVisualMargin="-6,-14,-6,-14" + Style="{StaticResource SliderThumbStyle}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + - - + + @@ -1417,8 +1395,10 @@ - - + + @@ -1426,11 +1406,15 @@ - - + + - - + + @@ -1438,11 +1422,15 @@ - - + + - - + + @@ -1450,44 +1438,60 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -1495,23 +1499,6 @@ - - - From 1758a358fbf7ad43a1f82fbc2c6265bd316174ec Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Fri, 23 Oct 2020 14:40:24 -0700 Subject: [PATCH 56/73] Fix extra unneeded file references in csproj for Controls --- .../Microsoft.Toolkit.Uwp.UI.Controls.csproj | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index b71a1cb9ac9..294ac153355 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -11,6 +11,7 @@ - BladeView: Provides a horizontal collection of blades for master-detail scenarios. - CameraPreview: Easily preview video from camera sources and get realtime frames from the selected source. - Carousel: Presents items in a carousel control. + - ColorPicker/ColorPickerButton: Improved ColorPicker and DropDownButton version. - DockPanel: Define areas where you can arrange child elements either horizontally or vertically, relative to each other. - DropShadowPanel: DropShadowPanel control allows the creation of a DropShadow for any Xaml FrameworkElement in markup. - Expander: Expander allows user to show/hide content based on a boolean state. @@ -44,16 +45,6 @@ 8.0 - - - - - - - - - - @@ -67,40 +58,13 @@ - - - - Designer - - - MSBuild:Compile - - - MSBuild:Compile - - - - - - Designer - - - MSBuild:Compile - - - MSBuild:Compile - - - - - From ea75c8a11ff24c9c78b0d4ef0d29964e6d77d44b Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Tue, 27 Oct 2020 15:38:44 -0700 Subject: [PATCH 57/73] Add in ColorPickerButton again to Codebase --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 8 + .../ColorPicker/ColorPickerButtonPage.xaml | 12 +- .../ColorPicker/ColorPickerButtonXaml.bind | 73 +- .../SamplePages/samples.json | 26 +- .../ColorPicker/ColorPicker.xaml | 3 +- .../ColorPicker/ColorPickerButton.cs | 130 ++ .../ColorPicker/ColorPickerButton.xaml | 1435 ++--------------- .../Themes/Generic.xaml | 2 +- 8 files changed, 356 insertions(+), 1333 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 058b072324b..e9ad480bf2d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -510,6 +510,9 @@ AutoFocusBehaviorPage.xaml + + ColorPickerButtonPage.xaml + ColorPickerPage.xaml @@ -625,6 +628,7 @@ + @@ -998,6 +1002,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml index b8938de16d6..2593dd56537 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml @@ -1,16 +1,14 @@  + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" + mc:Ignorable="d"> - + - + - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind index 027e075addf..c06c3eb8e49 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind @@ -11,17 +11,14 @@ - - - - + + - + SelectedColor="Navy"> + + + + - + SelectedColor="Green"> + + + + - + SelectedColor="Transparent"> + + + + - + SelectedColor="Maroon"> + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index f623da4311d..83e1adf985d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -39,21 +39,21 @@ "Type": "ColorPickerPage", "Subcategory": "Input", "About": "An improved color picker control providing more options to select colors.", - "CodeUrl": "TBD", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker", "XamlCodeFile": "ColorPickerXaml.bind", "Icon": "/SamplePages/ColorPicker/ColorPicker.png", - "DocumentationUrl": "TBD" - }, - //{ - // "Name": "ColorPickerButton", - // "Type": "ColorPickerButtonPage", - // "Subcategory": "Input", - // "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", - // "CodeUrl": "TBD", - // "XamlCodeFile": "ColorPickerButtonXaml.bind", - // "Icon": "/SamplePages/ColorPicker/ColorPicker.png", - // "DocumentationUrl": "TBD" - //}, + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPicker.md" + }, + { + "Name": "ColorPickerButton", + "Type": "ColorPickerButtonPage", + "Subcategory": "Input", + "About": "A color picker within a flyout opened by pressing a drop down button containing the selected color.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker", + "XamlCodeFile": "/SamplePages/ColorPicker/ColorPickerButtonXaml.bind", + "Icon": "/SamplePages/ColorPicker/ColorPicker.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPickerButton.md" + }, { "Name": "AdaptiveGridView", "Type": "AdaptiveGridViewPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 68955118a88..4b2128abc72 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -45,7 +45,7 @@ @@ -92,6 +92,7 @@ diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs new file mode 100644 index 00000000000..cc70b8fcb83 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// A which displays a color as its Content and it's Flyout is a . + /// + [TemplatePart(Name = nameof(ColorPicker), Type = typeof(ColorPicker))] + public class ColorPickerButton : DropDownButton + { + /// + /// Gets the instances contained by the . + /// + public ColorPicker ColorPicker { get; private set; } + + /// + /// Gets or sets the for the control used in the button. + /// + public Style ColorPickerStyle + { + get + { + return (Style)GetValue(ColorPickerStyleProperty); + } + + set + { + SetValue(ColorPickerStyleProperty, value); + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorPickerStyleProperty = DependencyProperty.Register("ColorPickerStyle", typeof(Style), typeof(ColorPickerButton), new PropertyMetadata(default(Style))); + + /// + /// Gets or sets the for the used within the of the . + /// + public Style FlyoutPresenterStyle + { + get + { + return (Style)GetValue(FlyoutPresenterStyleProperty); + } + + set + { + SetValue(FlyoutPresenterStyleProperty, value); + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty FlyoutPresenterStyleProperty = DependencyProperty.Register("FlyoutPresenterStyle", typeof(Style), typeof(ColorPickerButton), new PropertyMetadata(default(Style))); + + /// + /// Gets or sets the selected the user has picked from the . + /// + public Color SelectedColor + { + get { return (Color)GetValue(SelectedColorProperty); } + set { SetValue(SelectedColorProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SelectedColorProperty = + DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPickerButton), new PropertyMetadata(null)); + + /// + /// Initializes a new instance of the class. + /// + public ColorPickerButton() + { + this.DefaultStyleKey = typeof(ColorPickerButton); + } + + /// + protected override void OnApplyTemplate() + { + if (ColorPicker != null) + { + // TODO: Unregister + } + + base.OnApplyTemplate(); + + if (ColorPickerStyle != null) + { + ColorPicker = new ColorPicker() { Style = ColorPickerStyle }; + } + else + { + ColorPicker = new ColorPicker(); + } + + ColorPicker.Color = SelectedColor; + + this.ColorPicker.ColorChanged += (sender, args) => + { + SelectedColor = args.NewColor; + }; + + if (Flyout == null) + { + Flyout = new Flyout() + { + // TODO: Expose Placement + Placement = Windows.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft, + FlyoutPresenterStyle = FlyoutPresenterStyle, + Content = ColorPicker + }; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml index 80abd02ecd7..a406e53cc9c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml @@ -1,1286 +1,159 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"> + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index c7c9ede9f94..2b9d86e9c2f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -5,7 +5,7 @@ - + From 9b561de7b845fd6fdd56f4922db046eac0df1da3 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Tue, 27 Oct 2020 16:01:05 -0700 Subject: [PATCH 58/73] Add Transparency to the ColorPickerButton Preview --- .../ColorPicker/ColorChannel.cs | 36 ++++++++ .../ColorPicker/ColorPicker.cs | 84 ++++--------------- .../ColorPicker/ColorPickerButton.cs | 26 +++++- .../ColorPicker/ColorPickerButton.xaml | 9 +- ...ring.cs => ColorPickerRenderingHelpers.cs} | 29 +++++-- .../ColorPicker/ColorRepresentation.cs | 26 ++++++ 6 files changed, 131 insertions(+), 79 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs rename Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/{ColorPicker.Rendering.cs => ColorPickerRenderingHelpers.cs} (95%) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs new file mode 100644 index 00000000000..02d2711c0ca --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Defines a specific channel within a color representation. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public enum ColorChannel + { + /// + /// Represents the alpha channel. + /// + Alpha, + + /// + /// Represents the first color channel which is Red when RGB or Hue when HSV. + /// + Channel1, + + /// + /// Represents the second color channel which is Green when RGB or Saturation when HSV. + /// + Channel2, + + /// + /// Represents the third color channel which is Blue when RGB or Value when HSV. + /// + Channel3 + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index 6b25acdb40c..2065a620935 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -62,55 +62,13 @@ public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker /// private const int ColorUpdateInterval = 30; // Milliseconds - /// - /// Defines how colors are represented. - /// - private enum ColorRepresentation - { - /// - /// Color is represented by hue, saturation, value and alpha channels. - /// - Hsva, - - /// - /// Color is represented by red, green, blue and alpha channels. - /// - Rgba - } - - /// - /// Defines a specific channel within a color representation. - /// - private enum ColorChannel - { - /// - /// Represents the alpha channel. - /// - Alpha, - - /// - /// Represents the first color channel which is Red when RGB or Hue when HSV. - /// - Channel1, - - /// - /// Represents the second color channel which is Green when RGB or Saturation when HSV. - /// - Channel2, - - /// - /// Represents the third color channel which is Blue when RGB or Value when HSV. - /// - Channel3 - } - private long tokenColor; private long tokenCustomPalette; private long tokenIsColorPaletteVisible; private Dictionary cachedSliderSizes = new Dictionary(); private bool callbacksConnected = false; - private Color checkerBackgroundColor = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later + internal Color checkerBackgroundColor = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later private bool eventsConnected = false; private bool isInitialized = false; @@ -215,6 +173,8 @@ private static bool IsColorEmpty(Color color) /// protected override void OnApplyTemplate() { + // TODO: We need to disconnect old events first. + this.ColorSpectrumControl = this.GetTemplateChild(nameof(ColorSpectrumControl)); this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild(nameof(ColorSpectrumThirdDimensionSlider)); @@ -256,7 +216,7 @@ protected override void OnApplyTemplate() this.UpdateVisualState(false); this.isInitialized = true; this.SetActiveColorRepresentation(ColorRepresentation.Rgba); - this.UpdateColorControlValues(); + this.UpdateColorControlValues(); // TODO: This will also connect events after, can we optimize vs. doing it twice with the ConnectEvents above? } /// @@ -1101,7 +1061,7 @@ private async void UpdateChannelSliderBackground(Slider slider) if (object.ReferenceEquals(slider, this.Channel1Slider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Horizontal, @@ -1112,7 +1072,7 @@ private async void UpdateChannelSliderBackground(Slider slider) } else if (object.ReferenceEquals(slider, this.Channel2Slider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Horizontal, @@ -1123,7 +1083,7 @@ private async void UpdateChannelSliderBackground(Slider slider) } else if (object.ReferenceEquals(slider, this.Channel3Slider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Horizontal, @@ -1134,7 +1094,7 @@ private async void UpdateChannelSliderBackground(Slider slider) } else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Horizontal, @@ -1145,7 +1105,7 @@ private async void UpdateChannelSliderBackground(Slider slider) } else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Vertical, @@ -1156,7 +1116,7 @@ private async void UpdateChannelSliderBackground(Slider slider) } else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) { - bitmap = await this.CreateChannelBitmapAsync( + bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( width, height, Orientation.Vertical, @@ -1168,7 +1128,7 @@ private async void UpdateChannelSliderBackground(Slider slider) if (bitmap != null) { - slider.Background = await this.BitmapToBrushAsync(bitmap, width, height); + slider.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height); } return; @@ -1331,25 +1291,9 @@ private void ChannelSlider_Loaded(object sender, RoutedEventArgs e) /// private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e) { - Border border = sender as Border; - - if (border != null) - { - int width = Convert.ToInt32(border.ActualWidth); - int height = Convert.ToInt32(border.ActualHeight); - - var bitmap = await this.CreateCheckeredBitmapAsync( - width, - height, - this.checkerBackgroundColor); - - if (bitmap != null) - { - border.Background = await this.BitmapToBrushAsync(bitmap, width, height); - } - } - - return; + await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync( + sender as Border, + checkerBackgroundColor); } /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs index cc70b8fcb83..3843057b866 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs @@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// /// A which displays a color as its Content and it's Flyout is a . /// - [TemplatePart(Name = nameof(ColorPicker), Type = typeof(ColorPicker))] + [TemplatePart(Name = nameof(CheckeredBackgroundBorder), Type = typeof(Border))] public class ColorPickerButton : DropDownButton { /// @@ -81,6 +81,11 @@ public Color SelectedColor public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPickerButton), new PropertyMetadata(null)); + #pragma warning disable SA1306 // Field names should begin with lower-case letter + //// Template Parts + private Border CheckeredBackgroundBorder; + #pragma warning restore SA1306 // Field names should begin with lower-case letter + /// /// Initializes a new instance of the class. /// @@ -125,6 +130,25 @@ protected override void OnApplyTemplate() Content = ColorPicker }; } + + if (CheckeredBackgroundBorder != null) + { + CheckeredBackgroundBorder.Loaded -= this.CheckeredBackgroundBorder_Loaded; + } + + CheckeredBackgroundBorder = GetTemplateChild(nameof(CheckeredBackgroundBorder)) as Border; + + if (CheckeredBackgroundBorder != null) + { + CheckeredBackgroundBorder.Loaded += this.CheckeredBackgroundBorder_Loaded; + } + } + + private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e) + { + await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync( + sender as Border, + ColorPicker.checkerBackgroundColor); // TODO: Check initialization } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml index a406e53cc9c..566115dfb52 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml @@ -44,12 +44,15 @@ - + + - - + /// Contains the rendering methods used within . /// - public partial class ColorPicker + internal class ColorPickerRenderingHelpers { /// /// Generates a new bitmap of the specified size by changing a specific color channel. @@ -31,7 +31,7 @@ public partial class ColorPicker /// The base RGB color used for channels not being changed. /// The color of the checker background square. /// A new bitmap representing a gradient of color channel values. - private async Task CreateChannelBitmapAsync( + public static async Task CreateChannelBitmapAsync( int width, int height, Orientation orientation, @@ -75,7 +75,7 @@ private async Task CreateChannelBitmapAsync( // Create a checkered background if (checkerColor != null) { - bgraCheckeredPixelData = await this.CreateCheckeredBitmapAsync( + bgraCheckeredPixelData = await CreateCheckeredBitmapAsync( width, height, checkerColor.Value); @@ -358,7 +358,7 @@ Color GetColor(double channelValue) /// The pixel height (Y, vertical) of the checkered bitmap. /// The color of the checker square. /// A new checkered bitmap of the specified size. - private async Task CreateCheckeredBitmapAsync( + public static async Task CreateCheckeredBitmapAsync( int width, int height, Color checkerColor) @@ -426,7 +426,7 @@ private async Task CreateCheckeredBitmapAsync( /// The pixel width of the bitmap. /// The pixel height of the bitmap. /// A new ImageBrush. - private async Task BitmapToBrushAsync( + public static async Task BitmapToBrushAsync( byte[] bitmap, int width, int height) @@ -445,5 +445,24 @@ private async Task BitmapToBrushAsync( return brush; } + + public static async Task UpdateBorderBackgroundWithCheckerAsync(Border border, Color color) + { + if (border != null) + { + int width = Convert.ToInt32(border.ActualWidth); + int height = Convert.ToInt32(border.ActualHeight); + + var bitmap = await ColorPickerRenderingHelpers.CreateCheckeredBitmapAsync( + width, + height, + color); + + if (bitmap != null) + { + border.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height); + } + } + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs new file mode 100644 index 00000000000..9011a1b0d8a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Defines how colors are represented. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public enum ColorRepresentation + { + /// + /// Color is represented by hue, saturation, value and alpha channels. + /// + Hsva, + + /// + /// Color is represented by red, green, blue and alpha channels. + /// + Rgba + } +} From 5767df45fe3377ca08ee3d5c0d045dcdfde568cb Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Tue, 27 Oct 2020 16:10:00 -0700 Subject: [PATCH 59/73] Fix ColorPickerButton Event Registration --- .../ColorPicker/ColorPickerButton.cs | 13 +++++++------ .../ColorPicker/ColorPickerRenderingHelpers.cs | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs index 3843057b866..efdd02bebec 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs @@ -99,7 +99,7 @@ protected override void OnApplyTemplate() { if (ColorPicker != null) { - // TODO: Unregister + ColorPicker.ColorChanged -= ColorPicker_ColorChanged; } base.OnApplyTemplate(); @@ -114,11 +114,7 @@ protected override void OnApplyTemplate() } ColorPicker.Color = SelectedColor; - - this.ColorPicker.ColorChanged += (sender, args) => - { - SelectedColor = args.NewColor; - }; + ColorPicker.ColorChanged += ColorPicker_ColorChanged; if (Flyout == null) { @@ -144,6 +140,11 @@ protected override void OnApplyTemplate() } } + private void ColorPicker_ColorChanged(Windows.UI.Xaml.Controls.ColorPicker sender, ColorChangedEventArgs args) + { + SelectedColor = args.NewColor; + } + private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e) { await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync( diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs index cb50eaf4d15..deaf9a7448b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs @@ -446,6 +446,12 @@ public static async Task BitmapToBrushAsync( return brush; } + /// + /// Centralizes code to create a checker brush for a . + /// + /// Border which will have its Background modified. + /// Color to use for transparent checkerboard. + /// Task public static async Task UpdateBorderBackgroundWithCheckerAsync(Border border, Color color) { if (border != null) From 288a0ccafc0e069bc1429fef2fd9efca29ec61b0 Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:15:31 -0800 Subject: [PATCH 60/73] Fix Style-Cop Issues --- .../ColorPicker/ColorChannel.cs | 6 +++++- .../ColorPicker/ColorPicker.cs | 21 ++++++++++--------- .../ColorPicker/ColorPickerButton.cs | 4 +++- .../ColorPicker/ColorPickerSlider.cs | 4 +++- .../ColorPicker/ColorRepresentation.cs | 6 +++++- .../SwitchPresenter/CaseCollection.cs | 3 +++ 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs index 02d2711c0ca..77df7b01c24 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorChannel.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index 2065a620935..d8450eff1b5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -55,6 +55,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Only template parts start with a capital letter. This differentiates them from other fields.")] public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker { + internal Color CheckerBackgroundColor { get; set; } = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later + /// /// The period that scheduled color updates will be applied. /// This is only used when updating colors using the ScheduleColorUpdate() method. @@ -68,7 +70,6 @@ public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker private Dictionary cachedSliderSizes = new Dictionary(); private bool callbacksConnected = false; - internal Color checkerBackgroundColor = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later private bool eventsConnected = false; private bool isInitialized = false; @@ -132,7 +133,7 @@ public ColorPicker() // Checkered background color is found only one time for performance // This may need to change in the future if theme changes should be supported - this.checkerBackgroundColor = (Color)Application.Current.Resources["SystemListLowColor"]; + this.CheckerBackgroundColor = (Color)Application.Current.Resources["SystemListLowColor"]; this.ConnectCallbacks(true); this.SetDefaultPalette(); @@ -173,7 +174,7 @@ private static bool IsColorEmpty(Color color) /// protected override void OnApplyTemplate() { - // TODO: We need to disconnect old events first. + //// TODO: We need to disconnect old events first. this.ColorSpectrumControl = this.GetTemplateChild(nameof(ColorSpectrumControl)); this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); @@ -1068,7 +1069,7 @@ private async void UpdateChannelSliderBackground(Slider slider) this.GetActiveColorRepresentation(), ColorChannel.Channel1, baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } else if (object.ReferenceEquals(slider, this.Channel2Slider)) { @@ -1079,7 +1080,7 @@ private async void UpdateChannelSliderBackground(Slider slider) this.GetActiveColorRepresentation(), ColorChannel.Channel2, baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } else if (object.ReferenceEquals(slider, this.Channel3Slider)) { @@ -1090,7 +1091,7 @@ private async void UpdateChannelSliderBackground(Slider slider) this.GetActiveColorRepresentation(), ColorChannel.Channel3, baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) { @@ -1101,7 +1102,7 @@ private async void UpdateChannelSliderBackground(Slider slider) this.GetActiveColorRepresentation(), ColorChannel.Alpha, baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) { @@ -1112,7 +1113,7 @@ private async void UpdateChannelSliderBackground(Slider slider) this.GetActiveColorRepresentation(), ColorChannel.Alpha, baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) { @@ -1123,7 +1124,7 @@ private async void UpdateChannelSliderBackground(Slider slider) ColorRepresentation.Hsva, // Always HSV this.GetActiveColorSpectrumThirdDimension(), baseColor, - this.checkerBackgroundColor); + this.CheckerBackgroundColor); } if (bitmap != null) @@ -1293,7 +1294,7 @@ private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventAr { await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync( sender as Border, - checkerBackgroundColor); + CheckerBackgroundColor); } /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs index efdd02bebec..eb6b25f228b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.cs @@ -66,9 +66,11 @@ public Style FlyoutPresenterStyle /// public static readonly DependencyProperty FlyoutPresenterStyleProperty = DependencyProperty.Register("FlyoutPresenterStyle", typeof(Style), typeof(ColorPickerButton), new PropertyMetadata(default(Style))); + #pragma warning disable CS0419 // Ambiguous reference in cref attribute /// /// Gets or sets the selected the user has picked from the . /// + #pragma warning restore CS0419 // Ambiguous reference in cref attribute public Color SelectedColor { get { return (Color)GetValue(SelectedColorProperty); } @@ -149,7 +151,7 @@ private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventAr { await ColorPickerRenderingHelpers.UpdateBorderBackgroundWithCheckerAsync( sender as Border, - ColorPicker.checkerBackgroundColor); // TODO: Check initialization + ColorPicker.CheckerBackgroundColor); // TODO: Check initialization } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs index a6cb9fb13ee..f5aaf88c23b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs @@ -28,7 +28,9 @@ public class ColorPickerSlider : Slider private Size oldSize; private Size measuredSize; - /// + /// + /// Initializes a new instance of the class. + /// public ColorPickerSlider() : base() { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs index 9011a1b0d8a..39823709620 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorRepresentation.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/SwitchPresenter/CaseCollection.cs b/Microsoft.Toolkit.Uwp.UI.Controls/SwitchPresenter/CaseCollection.cs index 26051720ad7..d1f3ee96187 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/SwitchPresenter/CaseCollection.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/SwitchPresenter/CaseCollection.cs @@ -36,6 +36,9 @@ private void ValueChanged(object sender, EventArgs e) CaseCollectionChanged?.Invoke(this, EventArgs.Empty); } + /// + /// Initializes a new instance of the class. + /// public CaseCollection() { } From 415a8189a0c907ea7d0dbbb313a405a639f729f7 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 10:51:03 -0500 Subject: [PATCH 61/73] Rename 'WindowsColorPalette' to 'FluentColorPalette' --- Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs | 2 +- .../{WindowsColorPalette.cs => FluentColorPalette.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/{WindowsColorPalette.cs => FluentColorPalette.cs} (99%) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index d8450eff1b5..a2a9f09e8d1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -1140,7 +1140,7 @@ private async void UpdateChannelSliderBackground(Slider slider) /// private void SetDefaultPalette() { - this.CustomPalette = new WindowsColorPalette(); + this.CustomPalette = new FluentColorPalette(); return; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/WindowsColorPalette.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/FluentColorPalette.cs similarity index 99% rename from Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/WindowsColorPalette.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/FluentColorPalette.cs index db25e60adab..25b848ecf18 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/WindowsColorPalette.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/FluentColorPalette.cs @@ -10,7 +10,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// /// Implements the standard Windows 10 color palette. /// - public class WindowsColorPalette : IColorPalette + 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 From bf502c63ddd5f1b2fef9352821d24e9ffd22da93 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 11:16:05 -0500 Subject: [PATCH 62/73] Use the WinUI algorithm for light/dark contrast color switching --- .../ColorPicker/ContrastBrushConverter.cs | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs index 97d0f6bc7bb..037af7805e1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ContrastBrushConverter.cs @@ -63,15 +63,13 @@ public object Convert( else { // Chose a white/black brush based on contrast to the base color - if (this.GetBrightness(comparisonColor) > 0.5) + if (this.UseLightContrastColor(comparisonColor)) { - // Bright color, use a dark for contrast - return new SolidColorBrush(Colors.Black); + return new SolidColorBrush(Colors.White); } else { - // Dark color, use a light for contrast - return new SolidColorBrush(Colors.White); + return new SolidColorBrush(Colors.Black); } } } @@ -87,37 +85,36 @@ public object ConvertBack( } /// - /// Gets the perceived brightness or intensity of the color. - /// This value is normalized between zero (black) and one (white). + /// Determines whether a light or dark contrast color should be used with the given displayed color. /// /// - /// - /// The base formula for luminance is from Rec. ITU-R BT.601-7 (): - /// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf - /// Section 2.5.1 Construction of luminance (EY) and colour-difference (ER–EY) and (EB–EY) signals. - /// - /// This formula accounts for physiological aspects: the human eyeball is most sensitive to green light, - /// less to red and least to blue. - /// - /// Luminance = (0.299 * Red) + (0.587 * Green) + (0.114 * Blue) - /// - /// This formula is also recommended by the W3C Techniques For Accessibility Evaluation And Repair Tools - /// https://www.w3.org/TR/AERT/#color-contrast - /// - /// Contrary to the above formula, this is not called luminance and is called brightness instead. - /// This value is not measurable and is subjective which better fits the definition of brightness: - /// - Luminance is the luminous intensity, projected on a given area and direction. - /// Luminance is an objectively measurable attribute. The unit is 'Candela per Square Meter' (cd/m2). - /// - Brightness is a subjective attribute of light. The monitor can be adjusted to a level of light - /// between very dim and very bright. Brightness is perceived and cannot be measured objectively. - /// - /// Other useful information can be found here: - /// http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx - /// + /// This code is using the WinUI algorithm. /// - private double GetBrightness(Color color) + private bool UseLightContrastColor(Color displayedColor) { - return ((0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B)) / 255; + // The selection ellipse should be light if and only if the chosen color + // contrasts more with black than it does with white. + // To find how much something contrasts with white, we use the equation + // for relative luminance, which is given by + // + // L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg + // + // where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise } + // + // If L is closer to 1, then the color is closer to white; if it is closer to 0, + // then the color is closer to black. This is based on the fact that the human + // eye perceives green to be much brighter than red, which in turn is perceived to be + // brighter than blue. + // + // If the third dimension is value, then we won't be updating the spectrum's displayed colors, + // so in that case we should use a value of 1 when considering the backdrop + // for the selection ellipse. + + double rg = displayedColor.R <= 10 ? displayedColor.R / 3294.0 : Math.Pow((displayedColor.R / 269.0) + 0.0513, 2.4); + double gg = displayedColor.G <= 10 ? displayedColor.G / 3294.0 : Math.Pow((displayedColor.G / 269.0) + 0.0513, 2.4); + double bg = displayedColor.B <= 10 ? displayedColor.B / 3294.0 : Math.Pow((displayedColor.B / 269.0) + 0.0513, 2.4); + + return (0.2126 * rg) + (0.7152 * gg) + (0.0722 * bg) <= 0.5; } } } From 8cc36dd182f7dde91933c42e1b070f627b30ccea Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 11:22:58 -0500 Subject: [PATCH 63/73] Add corner radius to ColorPickerButton flyout --- .../ColorPicker/ColorPickerButton.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml index 566115dfb52..4c994c154e5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml @@ -158,5 +158,6 @@ \ No newline at end of file From 21aea6f37dac04daabb75fee825d8fa4548454cc Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 11:29:46 -0500 Subject: [PATCH 64/73] Disconnect any old events in OnApplyTemplate() --- Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index a2a9f09e8d1..867ff765792 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -174,7 +174,8 @@ private static bool IsColorEmpty(Color color) /// protected override void OnApplyTemplate() { - //// TODO: We need to disconnect old events first. + // We need to disconnect old events first + this.ConnectEvents(false); this.ColorSpectrumControl = this.GetTemplateChild(nameof(ColorSpectrumControl)); this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); From 5f0d8759b188eeb925e50e3ed32468761d713072 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 11:33:15 -0500 Subject: [PATCH 65/73] Show palette color in a ToolTip --- Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 4b2128abc72..4b24d82daae 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -165,7 +165,8 @@ + AutomationProperties.Name="{Binding Converter={StaticResource ColorToDisplayNameConverter}}" + ToolTipService.ToolTip="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"> From eff0f2531d171aa43b4565068c733468fcdbfe9c Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 12:44:36 -0500 Subject: [PATCH 66/73] Separate ColorPickerSlider XAML into new file --- .../ColorPicker/ColorPicker.xaml | 170 ----------------- .../ColorPicker/ColorPickerSlider.xaml | 174 ++++++++++++++++++ .../Themes/Generic.xaml | 1 + 3 files changed, 175 insertions(+), 170 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 4b24d82daae..9be4116486b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -616,176 +616,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 2b9d86e9c2f..6422f8690d2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -6,6 +6,7 @@ + From 38774570d0da3c29f0e219ddf8b7aad7784e7638 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 13:16:17 -0500 Subject: [PATCH 67/73] Use correct resource for overlay corner radius --- .../ColorPicker/ColorPickerButton.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml index 4c994c154e5..a99f95dbc6d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerButton.xaml @@ -158,6 +158,6 @@ \ No newline at end of file From 278bbf747ea3417063d45298663167957ee8da5a Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 13:16:44 -0500 Subject: [PATCH 68/73] Adjust default width to align content --- .../ColorPicker/ColorPicker.xaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 9be4116486b..250276f5346 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -23,7 +23,7 @@ From 4064179663f7e69297faa7e52e939ae5ff850205 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 8 Nov 2020 22:25:17 -0500 Subject: [PATCH 69/73] Move ColorPickerSlider foreground/background calculation into the control itself * Simplifies template by moving logic for foreground calulation into the slider itself * Switches the sliders to work entirely within HSV which means its background stops turning red when you get to a minimum value * Exposes properties to fix the Saturation/Value and Alpha channels at maximum when calculating backgrounds * Moves slider into Primitives namespace --- .../ColorPicker/ColorPickerButtonPage.xaml | 3 +- .../ColorPicker/ColorPickerPage.xaml | 3 +- .../ColorPicker/ColorPicker.cs | 237 +++++---------- .../ColorPicker/ColorPicker.xaml | 107 +++---- .../ColorPickerRenderingHelpers.cs | 75 ++++- .../ColorPickerSlider.Properties.cs | 237 +++++++++++++++ .../ColorPicker/ColorPickerSlider.cs | 277 ++++++++++++++++-- .../ColorPicker/ColorPickerSlider.xaml | 14 +- 8 files changed, 704 insertions(+), 249 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.Properties.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml index 2593dd56537..979f2b497d4 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonPage.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" @@ -9,6 +10,6 @@ - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml index f3f82b0397d..f92683d69d6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerPage.xaml @@ -4,13 +4,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs index 867ff765792..dddad929dbf 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.cs @@ -16,19 +16,20 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Media; +using ColorPickerSlider = Microsoft.Toolkit.Uwp.UI.Controls.Primitives.ColorPickerSlider; namespace Microsoft.Toolkit.Uwp.UI.Controls { /// /// Presents a color spectrum, a palette of colors, and color channel sliders for user selection of a color. /// - [TemplatePart(Name = nameof(ColorPicker.AlphaChannelSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.AlphaChannelSlider), Type = typeof(ColorPickerSlider))] [TemplatePart(Name = nameof(ColorPicker.AlphaChannelTextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPicker.Channel1Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel1Slider), Type = typeof(ColorPickerSlider))] [TemplatePart(Name = nameof(ColorPicker.Channel1TextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPicker.Channel2Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel2Slider), Type = typeof(ColorPickerSlider))] [TemplatePart(Name = nameof(ColorPicker.Channel2TextBox), Type = typeof(TextBox))] - [TemplatePart(Name = nameof(ColorPicker.Channel3Slider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.Channel3Slider), Type = typeof(ColorPickerSlider))] [TemplatePart(Name = nameof(ColorPicker.Channel3TextBox), Type = typeof(TextBox))] [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground1Border), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground2Border), Type = typeof(Border))] @@ -41,8 +42,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground9Border), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPicker.CheckeredBackground10Border), Type = typeof(Border))] [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumControl), Type = typeof(ColorSpectrum))] - [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumAlphaSlider), Type = typeof(Slider))] - [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumThirdDimensionSlider), Type = typeof(Slider))] + [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumAlphaSlider), Type = typeof(ColorPickerSlider))] + [TemplatePart(Name = nameof(ColorPicker.ColorSpectrumThirdDimensionSlider), Type = typeof(ColorPickerSlider))] [TemplatePart(Name = nameof(ColorPicker.HexInputTextBox), Type = typeof(TextBox))] [TemplatePart(Name = nameof(ColorPicker.HsvToggleButton), Type = typeof(ToggleButton))] [TemplatePart(Name = nameof(ColorPicker.RgbToggleButton), Type = typeof(ToggleButton))] @@ -68,10 +69,9 @@ public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker private long tokenCustomPalette; private long tokenIsColorPaletteVisible; - private Dictionary cachedSliderSizes = new Dictionary(); - private bool callbacksConnected = false; - private bool eventsConnected = false; - private bool isInitialized = false; + private bool callbacksConnected = false; + private bool eventsConnected = false; + private bool isInitialized = false; // Color information for updates private HsvColor? savedHsvColor = null; @@ -79,21 +79,21 @@ public partial class ColorPicker : Windows.UI.Xaml.Controls.ColorPicker private Color? updatedRgbColor = null; private DispatcherTimer dispatcherTimer = null; - private ColorSpectrum ColorSpectrumControl; - private Slider ColorSpectrumAlphaSlider; - private Slider ColorSpectrumThirdDimensionSlider; - private TextBox HexInputTextBox; - private ToggleButton HsvToggleButton; - private ToggleButton RgbToggleButton; - - private TextBox Channel1TextBox; - private TextBox Channel2TextBox; - private TextBox Channel3TextBox; - private TextBox AlphaChannelTextBox; - private Slider Channel1Slider; - private Slider Channel2Slider; - private Slider Channel3Slider; - private Slider AlphaChannelSlider; + private ColorSpectrum ColorSpectrumControl; + private ColorPickerSlider ColorSpectrumAlphaSlider; + private ColorPickerSlider ColorSpectrumThirdDimensionSlider; + private TextBox HexInputTextBox; + private ToggleButton HsvToggleButton; + private ToggleButton RgbToggleButton; + + private TextBox Channel1TextBox; + private TextBox Channel2TextBox; + private TextBox Channel3TextBox; + private TextBox AlphaChannelTextBox; + private ColorPickerSlider Channel1Slider; + private ColorPickerSlider Channel2Slider; + private ColorPickerSlider Channel3Slider; + private ColorPickerSlider AlphaChannelSlider; private Border N1PreviewBorder; private Border N2PreviewBorder; @@ -178,8 +178,8 @@ protected override void OnApplyTemplate() this.ConnectEvents(false); this.ColorSpectrumControl = this.GetTemplateChild(nameof(ColorSpectrumControl)); - this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); - this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild(nameof(ColorSpectrumThirdDimensionSlider)); + this.ColorSpectrumAlphaSlider = this.GetTemplateChild(nameof(ColorSpectrumAlphaSlider)); + this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild(nameof(ColorSpectrumThirdDimensionSlider)); this.HexInputTextBox = this.GetTemplateChild(nameof(HexInputTextBox)); this.HsvToggleButton = this.GetTemplateChild(nameof(HsvToggleButton)); @@ -190,10 +190,10 @@ protected override void OnApplyTemplate() this.Channel3TextBox = this.GetTemplateChild(nameof(Channel3TextBox)); this.AlphaChannelTextBox = this.GetTemplateChild(nameof(AlphaChannelTextBox)); - this.Channel1Slider = this.GetTemplateChild(nameof(Channel1Slider)); - this.Channel2Slider = this.GetTemplateChild(nameof(Channel2Slider)); - this.Channel3Slider = this.GetTemplateChild(nameof(Channel3Slider)); - this.AlphaChannelSlider = this.GetTemplateChild(nameof(AlphaChannelSlider)); + this.Channel1Slider = this.GetTemplateChild(nameof(Channel1Slider)); + this.Channel2Slider = this.GetTemplateChild(nameof(Channel2Slider)); + this.Channel3Slider = this.GetTemplateChild(nameof(Channel3Slider)); + this.AlphaChannelSlider = this.GetTemplateChild(nameof(AlphaChannelSlider)); this.N1PreviewBorder = this.GetTemplateChild(nameof(N1PreviewBorder)); this.N2PreviewBorder = this.GetTemplateChild(nameof(N2PreviewBorder)); @@ -993,144 +993,61 @@ private void UpdateChannelSliderBackgrounds() } /// - /// Generates a new background image for the specified color channel slider and applies it. - /// A new image will only be generated if it differs from the last color used to generate a background. - /// This provides some performance improvement. + /// Updates a specific channel slider control background. /// - /// The color channel slider to apply the generated background to. - private async void UpdateChannelSliderBackground(Slider slider) + /// The color channel slider to update the background for. + private void UpdateChannelSliderBackground(ColorPickerSlider slider) { - byte[] bitmap = null; - int width = 0; - int height = 0; - Color baseColor = this.Color; - - /* Updates may be requested when sliders are not in the visual tree. - * For first-time load this is handled by the Loaded event. - * However, after that problems may arise, consider the following case: - * - * (1) Backgrounds are drawn normally the first time on Loaded. - * Actual height/width are available. - * (2) The palette tab is selected which has no sliders - * (3) The picker flyout is closed - * (4) Externally the color is changed - * The color change will trigger slider background updates but - * with the flyout closed, actual height/width are zero. - * No zero size bitmap can be generated. - * (5) The picker flyout is re-opened by the user and the default - * last-opened tab will be viewed: palette. - * No loaded events will be fired for sliders. The color change - * event was already handled in (4). The sliders will never - * be updated. - * - * In this case the sliders become out of sync with the Color because there is no way - * to tell when they actually come into view. To work around this, force a re-render of - * the background with the last size of the slider. This last size will be when it was - * last loaded or updated. - * - * In the future additional consideration may be required for SizeChanged of the control. - * This work-around will also cause issues if display scaling changes in the special - * case where cached sizes are required. - */ if (slider != null) { - width = Convert.ToInt32(slider.ActualWidth); - height = Convert.ToInt32(slider.ActualHeight); + // Regardless of the active color representation, the sliders always use HSV + // Therefore, always calculate HSV color here + // Warning: Always maintain/use HSV information in the saved HSV color + // This avoids loss of precision and drift caused by continuously converting to/from RGB + if (this.savedHsvColor == null) + { + var rgbColor = this.Color; + + // Must update HSV color + this.savedHsvColor = rgbColor.ToHsv(); + this.savedHsvColorRgbEquivalent = rgbColor; + } + + slider.IsAutoUpdatingEnabled = false; - if (width == 0 || height == 0) + if (object.ReferenceEquals(slider, this.Channel1Slider)) { - // Attempt to use the last size if it was available - if (this.cachedSliderSizes.ContainsKey(slider)) - { - Size cachedSize = this.cachedSliderSizes[slider]; - width = Convert.ToInt32(cachedSize.Width); - height = Convert.ToInt32(cachedSize.Height); - } + slider.ColorChannel = ColorChannel.Channel1; + slider.ColorRepresentation = this.GetActiveColorRepresentation(); } - else + else if (object.ReferenceEquals(slider, this.Channel2Slider)) { - // Update the cached slider size - if (this.cachedSliderSizes.ContainsKey(slider)) - { - this.cachedSliderSizes[slider] = new Size(width, height); - } - else - { - this.cachedSliderSizes.Add(slider, new Size(width, height)); - } + slider.ColorChannel = ColorChannel.Channel2; + slider.ColorRepresentation = this.GetActiveColorRepresentation(); + } + else if (object.ReferenceEquals(slider, this.Channel3Slider)) + { + slider.ColorChannel = ColorChannel.Channel3; + slider.ColorRepresentation = this.GetActiveColorRepresentation(); + } + else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) + { + slider.ColorChannel = ColorChannel.Alpha; + slider.ColorRepresentation = this.GetActiveColorRepresentation(); + } + else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) + { + slider.ColorChannel = ColorChannel.Alpha; + slider.ColorRepresentation = this.GetActiveColorRepresentation(); + } + else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) + { + slider.ColorChannel = this.GetActiveColorSpectrumThirdDimension(); + slider.ColorRepresentation = ColorRepresentation.Hsva; // Always HSV } - } - - if (object.ReferenceEquals(slider, this.Channel1Slider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel1, - baseColor, - this.CheckerBackgroundColor); - } - else if (object.ReferenceEquals(slider, this.Channel2Slider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel2, - baseColor, - this.CheckerBackgroundColor); - } - else if (object.ReferenceEquals(slider, this.Channel3Slider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Channel3, - baseColor, - this.CheckerBackgroundColor); - } - else if (object.ReferenceEquals(slider, this.AlphaChannelSlider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Horizontal, - this.GetActiveColorRepresentation(), - ColorChannel.Alpha, - baseColor, - this.CheckerBackgroundColor); - } - else if (object.ReferenceEquals(slider, this.ColorSpectrumAlphaSlider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Vertical, - this.GetActiveColorRepresentation(), - ColorChannel.Alpha, - baseColor, - this.CheckerBackgroundColor); - } - else if (object.ReferenceEquals(slider, this.ColorSpectrumThirdDimensionSlider)) - { - bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( - width, - height, - Orientation.Vertical, - ColorRepresentation.Hsva, // Always HSV - this.GetActiveColorSpectrumThirdDimension(), - baseColor, - this.CheckerBackgroundColor); - } - if (bitmap != null) - { - slider.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height); + slider.HsvColor = this.savedHsvColor.Value; + slider.UpdateColors(); } return; @@ -1284,7 +1201,7 @@ private void ColorPickerButton_Loaded(object sender, RoutedEventArgs e) /// private void ChannelSlider_Loaded(object sender, RoutedEventArgs e) { - this.UpdateChannelSliderBackground(sender as Slider); + this.UpdateChannelSliderBackground(sender as ColorPickerSlider); return; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml index 250276f5346..3e3ad41e006 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPicker.xaml @@ -1,6 +1,7 @@  @@ -107,16 +108,16 @@ - + - + @@ -274,14 +275,14 @@ VerticalAlignment="Center" AutomationProperties.Name="Red Channel" Style="{StaticResource InputTextBoxStyle}" /> - + - + - + - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs index deaf9a7448b..8571cb54022 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerRenderingHelpers.cs @@ -28,8 +28,13 @@ internal class ColorPickerRenderingHelpers /// The orientation of the resulting bitmap (gradient direction). /// The color representation being used: RGBA or HSVA. /// The specific color channel to vary. - /// The base RGB color used for channels not being changed. + /// The base HSV color used for channels not being changed. /// The color of the checker background square. + /// Fix the alpha channel value to maximum during calculation. + /// This will remove any alpha/transparency from the other channel backgrounds. + /// Fix the saturation and value channels to maximum + /// during calculation in HSVA color representation. + /// This will ensure colors are always discernible regardless of saturation/value. /// A new bitmap representing a gradient of color channel values. public static async Task CreateChannelBitmapAsync( int width, @@ -37,8 +42,10 @@ public static async Task CreateChannelBitmapAsync( Orientation orientation, ColorRepresentation colorRepresentation, ColorChannel channel, - Color baseRgbColor, - Color? checkerColor) + HsvColor baseHsvColor, + Color? checkerColor, + bool isAlphaMaxForced, + bool isSaturationValueMaxForced) { if (width == 0 || height == 0) { @@ -51,7 +58,7 @@ public static async Task CreateChannelBitmapAsync( double channelStep; byte[] bgraPixelData; byte[] bgraCheckeredPixelData = null; - HsvColor baseHsvColor; + Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; int bgraPixelDataWidth; @@ -62,14 +69,64 @@ public static async Task CreateChannelBitmapAsync( bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; - // Convert RGB to HSV once - if (colorRepresentation == ColorRepresentation.Hsva) + // Maximize alpha channel value + if (isAlphaMaxForced && + channel != ColorChannel.Alpha) { - baseHsvColor = baseRgbColor.ToHsv(); + baseHsvColor = new HsvColor() + { + H = baseHsvColor.H, + S = baseHsvColor.S, + V = baseHsvColor.V, + A = 1.0 + }; } - else + + // Convert HSV to RGB once + if (colorRepresentation == ColorRepresentation.Rgba) + { + baseRgbColor = Uwp.Helpers.ColorHelper.FromHsv( + baseHsvColor.H, + baseHsvColor.S, + baseHsvColor.V, + baseHsvColor.A); + } + + // Maximize Saturation and Value channels when in HSVA mode + if (isSaturationValueMaxForced && + colorRepresentation == ColorRepresentation.Hsva && + channel != ColorChannel.Alpha) { - baseHsvColor = Colors.White.ToHsv(); + switch (channel) + { + case ColorChannel.Channel1: + baseHsvColor = new HsvColor() + { + H = baseHsvColor.H, + S = 1.0, + V = 1.0, + A = baseHsvColor.A + }; + break; + case ColorChannel.Channel2: + baseHsvColor = new HsvColor() + { + H = baseHsvColor.H, + S = baseHsvColor.S, + V = 1.0, + A = baseHsvColor.A + }; + break; + case ColorChannel.Channel3: + baseHsvColor = new HsvColor() + { + H = baseHsvColor.H, + S = 1.0, + V = baseHsvColor.V, + A = baseHsvColor.A + }; + break; + } } // Create a checkered background diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.Properties.cs new file mode 100644 index 00000000000..c6503ea7e64 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.Properties.cs @@ -0,0 +1,237 @@ +using Microsoft.Toolkit.Uwp.Helpers; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives +{ + /// + public partial class ColorPickerSlider : Slider + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorProperty = + DependencyProperty.Register( + nameof(Color), + typeof(Color), + typeof(ColorPickerSlider), + new PropertyMetadata( + Colors.White, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets the RGB color represented by the slider. + /// For accuracy use instead. + /// + public Color Color + { + get => (Color)this.GetValue(ColorProperty); + set + { + if (object.Equals(value, this.GetValue(ColorProperty)) == false) + { + this.SetValue(ColorProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorChannelProperty = + DependencyProperty.Register( + nameof(ColorChannel), + typeof(ColorChannel), + typeof(ColorPickerSlider), + new PropertyMetadata( + ColorChannel.Channel1, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets the color channel represented by the slider. + /// + public ColorChannel ColorChannel + { + get => (ColorChannel)this.GetValue(ColorChannelProperty); + set + { + if (object.Equals(value, this.GetValue(ColorChannelProperty)) == false) + { + this.SetValue(ColorChannelProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorRepresentationProperty = + DependencyProperty.Register( + nameof(ColorRepresentation), + typeof(ColorRepresentation), + typeof(ColorPickerSlider), + new PropertyMetadata( + ColorRepresentation.Rgba, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets the color representation used by the slider. + /// + public ColorRepresentation ColorRepresentation + { + get => (ColorRepresentation)this.GetValue(ColorRepresentationProperty); + set + { + if (object.Equals(value, this.GetValue(ColorRepresentationProperty)) == false) + { + this.SetValue(ColorRepresentationProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DefaultForegroundProperty = + DependencyProperty.Register( + nameof(DefaultForeground), + typeof(Brush), + typeof(ColorPickerSlider), + new PropertyMetadata( + new SolidColorBrush(Colors.Gray), + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets the default foreground brush to use when the slider background is hardly visible and nearly transparent. + /// Generally, this should be the default Foreground text brush. + /// + public Brush DefaultForeground + { + get => (Brush)this.GetValue(DefaultForegroundProperty); + set + { + if (object.Equals(value, this.GetValue(DefaultForegroundProperty)) == false) + { + this.SetValue(DefaultForegroundProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HsvColorProperty = + DependencyProperty.Register( + nameof(HsvColor), + typeof(HsvColor), + typeof(ColorPickerSlider), + new PropertyMetadata( + Colors.White.ToHsv(), + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets the HSV color represented by the slider. + /// This is the preferred color property for accuracy. + /// + public HsvColor HsvColor + { + get => (HsvColor)this.GetValue(HsvColorProperty); + set + { + if (object.Equals(value, this.GetValue(HsvColorProperty)) == false) + { + this.SetValue(HsvColorProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsAlphaMaxForcedProperty = + DependencyProperty.Register( + nameof(IsAlphaMaxForced), + typeof(bool), + typeof(ColorPickerSlider), + new PropertyMetadata( + true, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets a value indicating whether the alpha channel is always forced to maximum for channels + /// other than . + /// This ensures that the background is always visible and never transparent regardless of the actual color. + /// + public bool IsAlphaMaxForced + { + get => (bool)this.GetValue(IsAlphaMaxForcedProperty); + set + { + if (object.Equals(value, this.GetValue(IsAlphaMaxForcedProperty)) == false) + { + this.SetValue(IsAlphaMaxForcedProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsAutoUpdatingEnabledProperty = + DependencyProperty.Register( + nameof(IsAutoUpdatingEnabled), + typeof(bool), + typeof(ColorPickerSlider), + new PropertyMetadata( + true, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// 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 => (bool)this.GetValue(IsAutoUpdatingEnabledProperty); + set + { + if (object.Equals(value, this.GetValue(IsAutoUpdatingEnabledProperty)) == false) + { + this.SetValue(IsAutoUpdatingEnabledProperty, value); + } + } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsSaturationValueMaxForcedProperty = + DependencyProperty.Register( + nameof(IsSaturationValueMaxForced), + typeof(bool), + typeof(ColorPickerSlider), + new PropertyMetadata( + true, + (s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e))); + + /// + /// Gets or sets a value indicating whether the saturation and value channels are always forced to maximum values + /// when in HSVA color representation. Only channel values other than will be changed. + /// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color. + /// + public bool IsSaturationValueMaxForced + { + get => (bool)this.GetValue(IsSaturationValueMaxForcedProperty); + set + { + if (object.Equals(value, this.GetValue(IsSaturationValueMaxForcedProperty)) == false) + { + this.SetValue(IsSaturationValueMaxForcedProperty, value); + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs index f5aaf88c23b..139b9a57553 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.cs @@ -2,31 +2,34 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using Microsoft.Toolkit.Uwp.Helpers; +using Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters; using Windows.Foundation; +using Windows.UI; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; -// TODO: Do we want this in a subnamespace at least? -namespace Microsoft.Toolkit.Uwp.UI.Controls +namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives { /// - /// A implementation for use in the . + /// A slider that represents a single color channel for use in the . /// - /// - /// - /// ColorPickerSlider is currently the same as Slider except it fixes a critical bug. - /// For details see: - /// - /// * https://github.com/microsoft/microsoft-ui-xaml/issues/477 - /// * https://social.msdn.microsoft.com/Forums/sqlserver/en-US/0d3a2e64-d192-4250-b583-508a02bd75e1/uwp-bug-crash-layoutcycleexception-because-of-slider-under-certain-circumstances?forum=wpdevelop - /// - /// An added benefit of being a separate, derived class is more logic can be added - /// here in the future just like ColorPicker. - /// - /// - public class ColorPickerSlider : Slider + public partial class ColorPickerSlider : Slider { - private Size oldSize; - private Size measuredSize; + // TODO Combine this with the ColorPicker field or make a property + internal Color CheckerBackgroundColor { get; set; } = Color.FromArgb(0x19, 0x80, 0x80, 0x80); // Overridden later + + private Size oldSize = Size.Empty; + private Size measuredSize = Size.Empty; + private Size cachedSize = Size.Empty; + + /*************************************************************************************** + * + * Constructor/Destructor + * + ***************************************************************************************/ /// /// Initializes a new instance of the class. @@ -37,10 +40,232 @@ public ColorPickerSlider() this.DefaultStyleKey = typeof(ColorPickerSlider); } + /*************************************************************************************** + * + * Methods + * + ***************************************************************************************/ + + /// + /// Update the slider's Foreground and Background brushes based on the current slider state and color. + /// + /// + /// Manually refreshes the background gradient of the slider. + /// This is callable separately for performance reasons. + /// + public void UpdateColors() + { + HsvColor hsvColor = this.HsvColor; + + // Calculate and set the background + this.UpdateBackground(hsvColor); + + // Calculate and set the foreground ensuring contrast with the background + Color rgbColor = Uwp.Helpers.ColorHelper.FromHsv(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A); + Color selectedRgbColor; + double sliderPercent = this.Value / (this.Maximum - this.Minimum); + + if (this.ColorRepresentation == ColorRepresentation.Hsva) + { + if (this.IsAlphaMaxForced && + this.ColorChannel != ColorChannel.Alpha) + { + hsvColor = new HsvColor() + { + H = hsvColor.H, + S = hsvColor.S, + V = hsvColor.V, + A = 1.0 + }; + } + + switch (this.ColorChannel) + { + case ColorChannel.Channel1: + { + var channelValue = Math.Clamp(sliderPercent * 360.0, 0.0, 360.0); + + hsvColor = new HsvColor() + { + H = channelValue, + S = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.S, + V = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.V, + A = hsvColor.A + }; + break; + } + + case ColorChannel.Channel2: + { + var channelValue = Math.Clamp(sliderPercent * 1.0, 0.0, 1.0); + + hsvColor = new HsvColor() + { + H = hsvColor.H, + S = channelValue, + V = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.V, + A = hsvColor.A + }; + break; + } + + case ColorChannel.Channel3: + { + var channelValue = Math.Clamp(sliderPercent * 1.0, 0.0, 1.0); + + hsvColor = new HsvColor() + { + H = hsvColor.H, + S = this.IsSaturationValueMaxForced ? 1.0 : hsvColor.S, + V = channelValue, + A = hsvColor.A + }; + break; + } + } + + selectedRgbColor = Uwp.Helpers.ColorHelper.FromHsv( + hsvColor.H, + hsvColor.S, + hsvColor.V, + hsvColor.A); + } + else + { + if (this.IsAlphaMaxForced && + this.ColorChannel != ColorChannel.Alpha) + { + rgbColor = new Color() + { + R = rgbColor.R, + G = rgbColor.G, + B = rgbColor.B, + A = 255 + }; + } + + byte channelValue = Convert.ToByte(Math.Clamp(sliderPercent * 255.0, 0.0, 255.0)); + + switch (this.ColorChannel) + { + case ColorChannel.Channel1: + rgbColor = new Color() + { + R = channelValue, + G = rgbColor.G, + B = rgbColor.B, + A = rgbColor.A + }; + break; + case ColorChannel.Channel2: + rgbColor = new Color() + { + R = rgbColor.R, + G = channelValue, + B = rgbColor.B, + A = rgbColor.A + }; + break; + case ColorChannel.Channel3: + rgbColor = new Color() + { + R = rgbColor.R, + G = rgbColor.G, + B = channelValue, + A = rgbColor.A + }; + break; + } + + selectedRgbColor = rgbColor; + } + + var converter = new ContrastBrushConverter(); + this.Foreground = converter.Convert(selectedRgbColor, typeof(Brush), this.DefaultForeground, null) as Brush; + + return; + } + + /// + /// Generates a new background image for the color channel slider and applies it. + /// + private async void UpdateBackground(HsvColor color) + { + /* Updates may be requested when sliders are not in the visual tree. + * For first-time load this is handled by the Loaded event. + * However, after that problems may arise, consider the following case: + * + * (1) Backgrounds are drawn normally the first time on Loaded. + * Actual height/width are available. + * (2) The palette tab is selected which has no sliders + * (3) The picker flyout is closed + * (4) Externally the color is changed + * The color change will trigger slider background updates but + * with the flyout closed, actual height/width are zero. + * No zero size bitmap can be generated. + * (5) The picker flyout is re-opened by the user and the default + * last-opened tab will be viewed: palette. + * No loaded events will be fired for sliders. The color change + * event was already handled in (4). The sliders will never + * be updated. + * + * In this case the sliders become out of sync with the Color because there is no way + * to tell when they actually come into view. To work around this, force a re-render of + * the background with the last size of the slider. This last size will be when it was + * last loaded or updated. + * + * In the future additional consideration may be required for SizeChanged of the control. + * This work-around will also cause issues if display scaling changes in the special + * case where cached sizes are required. + */ + var width = Convert.ToInt32(this.ActualWidth); + var height = Convert.ToInt32(this.ActualHeight); + + if (width == 0 || height == 0) + { + // Attempt to use the last size if it was available + if (this.cachedSize.IsEmpty == false) + { + width = Convert.ToInt32(this.cachedSize.Width); + height = Convert.ToInt32(this.cachedSize.Height); + } + } + else + { + this.cachedSize = new Size(width, height); + } + + var bitmap = await ColorPickerRenderingHelpers.CreateChannelBitmapAsync( + width, + height, + this.Orientation, + this.ColorRepresentation, + this.ColorChannel, + color, + this.CheckerBackgroundColor, + this.IsAlphaMaxForced, + this.IsSaturationValueMaxForced); + + if (bitmap != null) + { + this.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height); + } + + return; + } + /// /// Measures the size in layout required for child elements and determines a size for the /// FrameworkElement-derived class. /// + /// + /// + /// Slider has some critical bugs: + /// + /// * https://github.com/microsoft/microsoft-ui-xaml/issues/477 + /// * https://social.msdn.microsoft.com/Forums/sqlserver/en-US/0d3a2e64-d192-4250-b583-508a02bd75e1/uwp-bug-crash-layoutcycleexception-because-of-slider-under-certain-circumstances?forum=wpdevelop + /// + /// /// The available size that this element can give to child elements. /// Infinity can be specified as a value to indicate that the element will size to whatever content /// is available. @@ -56,5 +281,21 @@ protected override Size MeasureOverride(Size availableSize) return measuredSize; } + + private void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args) + { + if (object.ReferenceEquals(args.Property, ColorProperty)) + { + // Sync with HSV (which is primary) + this.HsvColor = this.Color.ToHsv(); + } + + if (this.IsAutoUpdatingEnabled) + { + this.UpdateColors(); + } + + return; + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml index 74046cd4d9c..70d15c20190 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ColorPicker/ColorPickerSlider.xaml @@ -1,6 +1,6 @@  + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives">