diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind
index 939f067a2b3..58b5b51fed6 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerButtonXaml.bind
@@ -33,6 +33,7 @@
+
diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind
index 1502d4ad1f0..2f8d30ce784 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ColorPicker/ColorPickerXaml.bind
@@ -30,7 +30,8 @@
Color="Navy"
ColorSpectrumShape="Box"
IsAlphaEnabled="False"
- IsHexInputVisible="True" />
+ IsHexInputVisible="True"
+ ShowAccentColors="False" />
+ /// Creates an accent color for a base color value.
+ ///
+ public class AccentColorConverter : IValueConverter
+ {
+ ///
+ /// The amount to change the Value channel for each accent color step.
+ ///
+ public const double ValueDelta = 0.1;
+
+ ///
+ /// This does not account for perceptual differences and also does not match with
+ /// system accent color calculation.
+ ///
+ ///
+ /// Use the HSV representation as it's more perceptual.
+ /// In most cases only the value is changed by a fixed percentage so the algorithm is reproducible.
+ ///
+ /// The base color to calculate the accent from.
+ /// The number of accent color steps to move.
+ /// The new accent color.
+ public static HsvColor GetAccent(HsvColor hsvColor, int accentStep)
+ {
+ if (accentStep != 0)
+ {
+ double colorValue = hsvColor.V;
+ colorValue += accentStep * AccentColorConverter.ValueDelta;
+ colorValue = Math.Round(colorValue, 2);
+
+ return new HsvColor()
+ {
+ A = Math.Clamp(hsvColor.A, 0.0, 1.0),
+ H = Math.Clamp(hsvColor.H, 0.0, 360.0),
+ S = Math.Clamp(hsvColor.S, 0.0, 1.0),
+ V = Math.Clamp(colorValue, 0.0, 1.0),
+ };
+ }
+ else
+ {
+ return hsvColor;
+ }
+ }
+
+ ///
+ public object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ string language)
+ {
+ int accentStep;
+ Color? rgbColor = null;
+ HsvColor? hsvColor = null;
+
+ // Get the current color in HSV
+ if (value is Color valueColor)
+ {
+ rgbColor = valueColor;
+ }
+ else if (value is HsvColor valueHsvColor)
+ {
+ hsvColor = valueHsvColor;
+ }
+ else if (value is SolidColorBrush valueBrush)
+ {
+ rgbColor = valueBrush.Color;
+ }
+ else
+ {
+ // Invalid color value provided
+ return DependencyProperty.UnsetValue;
+ }
+
+ // Get the value component delta
+ try
+ {
+ accentStep = int.Parse(parameter?.ToString(), CultureInfo.InvariantCulture);
+ }
+ catch
+ {
+ // Invalid parameter provided, unable to convert to integer
+ return DependencyProperty.UnsetValue;
+ }
+
+ if (hsvColor == null &&
+ rgbColor != null)
+ {
+ hsvColor = rgbColor.Value.ToHsv();
+ }
+
+ if (hsvColor != null)
+ {
+ var hsv = AccentColorConverter.GetAccent(hsvColor.Value, accentStep);
+
+ return Uwp.Helpers.ColorHelper.FromHsv(hsv.H, hsv.S, hsv.V, hsv.A);
+ }
+ else
+ {
+ return DependencyProperty.UnsetValue;
+ }
+ }
+
+ ///
+ public object ConvertBack(
+ object value,
+ Type targetType,
+ object parameter,
+ string language)
+ {
+ return DependencyProperty.UnsetValue;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.Properties.cs
index 641e83131f5..0e248319934 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.Properties.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.Properties.cs
@@ -115,5 +115,33 @@ public bool IsColorPaletteVisible
}
}
}
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty ShowAccentColorsProperty =
+ DependencyProperty.Register(
+ nameof(ShowAccentColors),
+ typeof(bool),
+ typeof(ColorPicker),
+ new PropertyMetadata(
+ true,
+ (s, e) => (s as ColorPicker)?.OnDependencyPropertyChanged(s, e)));
+
+ ///
+ /// Gets or sets a value indicating whether accent colors are shown along
+ /// with the preview color.
+ ///
+ public bool ShowAccentColors
+ {
+ get => (bool)this.GetValue(ShowAccentColorsProperty);
+ set
+ {
+ if (object.Equals(value, this.GetValue(ShowAccentColorsProperty)) == false)
+ {
+ this.SetValue(ShowAccentColorsProperty, value);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.cs
index 7a22dbc8f70..1b195d53542 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.cs
@@ -8,6 +8,8 @@
using System.Globalization;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters;
+using Microsoft.Toolkit.Uwp.UI.Controls.Primitives;
+using Microsoft.UI.Xaml.Controls;
using Windows.System;
using Windows.UI;
using Windows.UI.Xaml;
@@ -22,14 +24,14 @@ 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.AlphaChannelNumberBox), Type = typeof(NumberBox))]
[TemplatePart(Name = nameof(ColorPicker.AlphaChannelSlider), Type = typeof(ColorPickerSlider))]
- [TemplatePart(Name = nameof(ColorPicker.AlphaChannelTextBox), Type = typeof(TextBox))]
+ [TemplatePart(Name = nameof(ColorPicker.Channel1NumberBox), Type = typeof(NumberBox))]
[TemplatePart(Name = nameof(ColorPicker.Channel1Slider), Type = typeof(ColorPickerSlider))]
- [TemplatePart(Name = nameof(ColorPicker.Channel1TextBox), Type = typeof(TextBox))]
+ [TemplatePart(Name = nameof(ColorPicker.Channel2NumberBox), Type = typeof(NumberBox))]
[TemplatePart(Name = nameof(ColorPicker.Channel2Slider), Type = typeof(ColorPickerSlider))]
- [TemplatePart(Name = nameof(ColorPicker.Channel2TextBox), Type = typeof(TextBox))]
+ [TemplatePart(Name = nameof(ColorPicker.Channel3NumberBox), Type = typeof(NumberBox))]
[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))]
[TemplatePart(Name = nameof(ColorPicker.CheckeredBackground3Border), Type = typeof(Border))]
@@ -47,10 +49,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
[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.")]
@@ -72,10 +70,10 @@ public partial class ColorPicker : Microsoft.UI.Xaml.Controls.ColorPicker
private bool isInitialized = false;
// Color information for updates
- private HsvColor? savedHsvColor = null;
- private Color? savedHsvColorRgbEquivalent = null;
- private Color? updatedRgbColor = null;
- private DispatcherQueueTimer dispatcherQueueTimer = null;
+ private HsvColor? savedHsvColor = null;
+ private Color? savedHsvColorRgbEquivalent = null;
+ private Color? updatedRgbColor = null;
+ private DispatcherQueueTimer dispatcherQueueTimer = null;
private ListBox ColorPanelSelector;
private ColorSpectrum ColorSpectrumControl;
@@ -85,19 +83,16 @@ public partial class ColorPicker : Microsoft.UI.Xaml.Controls.ColorPicker
private ToggleButton HsvToggleButton;
private ToggleButton RgbToggleButton;
- private TextBox Channel1TextBox;
- private TextBox Channel2TextBox;
- private TextBox Channel3TextBox;
- private TextBox AlphaChannelTextBox;
+ private NumberBox Channel1NumberBox;
+ private NumberBox Channel2NumberBox;
+ private NumberBox Channel3NumberBox;
+ private NumberBox AlphaChannelNumberBox;
private ColorPickerSlider Channel1Slider;
private ColorPickerSlider Channel2Slider;
private ColorPickerSlider Channel3Slider;
private ColorPickerSlider AlphaChannelSlider;
- private Border N1PreviewBorder;
- private Border N2PreviewBorder;
- private Border P1PreviewBorder;
- private Border P2PreviewBorder;
+ private ColorPreviewer ColorPreviewer;
// Up to 10 checkered backgrounds may be used by name anywhere in the template
private Border CheckeredBackground1Border;
@@ -187,20 +182,17 @@ protected override void OnApplyTemplate()
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.Channel1NumberBox = this.GetTemplateChild(nameof(Channel1NumberBox));
+ this.Channel2NumberBox = this.GetTemplateChild(nameof(Channel2NumberBox));
+ this.Channel3NumberBox = this.GetTemplateChild(nameof(Channel3NumberBox));
+ this.AlphaChannelNumberBox = this.GetTemplateChild(nameof(AlphaChannelNumberBox));
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.ColorPreviewer = this.GetTemplateChild(nameof(ColorPreviewer));
this.CheckeredBackground1Border = this.GetTemplateChild(nameof(CheckeredBackground1Border));
this.CheckeredBackground2Border = this.GetTemplateChild(nameof(CheckeredBackground2Border));
@@ -289,14 +281,10 @@ private void ConnectEvents(bool connected)
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; }
- 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.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; }
@@ -312,10 +300,7 @@ private void ConnectEvents(bool connected)
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.ColorPreviewer != null) { this.ColorPreviewer.ColorChangeRequested += ColorPreviewer_ColorChangeRequested; }
if (this.CheckeredBackground1Border != null) { this.CheckeredBackground1Border.Loaded += CheckeredBackgroundBorder_Loaded; }
if (this.CheckeredBackground2Border != null) { this.CheckeredBackground2Border.Loaded += CheckeredBackgroundBorder_Loaded; }
@@ -343,14 +328,10 @@ private void ConnectEvents(bool connected)
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; }
- 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.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; }
@@ -366,10 +347,7 @@ private void ConnectEvents(bool connected)
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.ColorPreviewer != null) { this.ColorPreviewer.ColorChangeRequested -= ColorPreviewer_ColorChangeRequested; }
if (this.CheckeredBackground1Border != null) { this.CheckeredBackground1Border.Loaded -= CheckeredBackgroundBorder_Loaded; }
if (this.CheckeredBackground2Border != null) { this.CheckeredBackground2Border.Loaded -= CheckeredBackgroundBorder_Loaded; }
@@ -536,57 +514,6 @@ 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.UpdateColorControlValues();
- this.UpdateChannelSliderBackgrounds();
- }
- }
-
- return;
- }
-
///
/// Updates the color values in all editing controls to match the current color.
///
@@ -700,6 +627,12 @@ private void UpdateColorControlValues()
}
}
+ // Update the preview color
+ if (this.ColorPreviewer != null)
+ {
+ this.ColorPreviewer.HsvColor = hsvColor;
+ }
+
// Update all other color channels
if (this.GetActiveColorRepresentation() == ColorRepresentation.Hsva)
{
@@ -710,10 +643,11 @@ private void UpdateColorControlValues()
double alpha = hsvColor.A * 100;
// Hue
- if (this.Channel1TextBox != null)
+ if (this.Channel1NumberBox != null)
{
- this.Channel1TextBox.MaxLength = 3;
- this.Channel1TextBox.Text = hue.ToString(CultureInfo.CurrentUICulture);
+ this.Channel1NumberBox.Minimum = 0;
+ this.Channel1NumberBox.Maximum = 360;
+ this.Channel1NumberBox.Value = hue;
}
if (this.Channel1Slider != null)
@@ -724,10 +658,11 @@ private void UpdateColorControlValues()
}
// Saturation
- if (this.Channel2TextBox != null)
+ if (this.Channel2NumberBox != null)
{
- this.Channel2TextBox.MaxLength = 3;
- this.Channel2TextBox.Text = staturation.ToString(CultureInfo.CurrentUICulture);
+ this.Channel2NumberBox.Minimum = 0;
+ this.Channel2NumberBox.Maximum = 100;
+ this.Channel2NumberBox.Value = staturation;
}
if (this.Channel2Slider != null)
@@ -738,10 +673,11 @@ private void UpdateColorControlValues()
}
// Value
- if (this.Channel3TextBox != null)
+ if (this.Channel3NumberBox != null)
{
- this.Channel3TextBox.MaxLength = 3;
- this.Channel3TextBox.Text = value.ToString(CultureInfo.CurrentUICulture);
+ this.Channel3NumberBox.Minimum = 0;
+ this.Channel3NumberBox.Maximum = 100;
+ this.Channel3NumberBox.Value = value;
}
if (this.Channel3Slider != null)
@@ -752,10 +688,11 @@ private void UpdateColorControlValues()
}
// Alpha
- if (this.AlphaChannelTextBox != null)
+ if (this.AlphaChannelNumberBox != null)
{
- this.AlphaChannelTextBox.MaxLength = 3;
- this.AlphaChannelTextBox.Text = alpha.ToString(CultureInfo.CurrentUICulture);
+ this.AlphaChannelNumberBox.Minimum = 0;
+ this.AlphaChannelNumberBox.Maximum = 100;
+ this.AlphaChannelNumberBox.Value = alpha;
}
if (this.AlphaChannelSlider != null)
@@ -776,10 +713,11 @@ private void UpdateColorControlValues()
else
{
// Red
- if (this.Channel1TextBox != null)
+ if (this.Channel1NumberBox != null)
{
- this.Channel1TextBox.MaxLength = 3;
- this.Channel1TextBox.Text = rgbColor.R.ToString(CultureInfo.CurrentUICulture);
+ this.Channel1NumberBox.Minimum = 0;
+ this.Channel1NumberBox.Maximum = 255;
+ this.Channel1NumberBox.Value = Convert.ToDouble(rgbColor.R);
}
if (this.Channel1Slider != null)
@@ -790,10 +728,11 @@ private void UpdateColorControlValues()
}
// Green
- if (this.Channel2TextBox != null)
+ if (this.Channel2NumberBox != null)
{
- this.Channel2TextBox.MaxLength = 3;
- this.Channel2TextBox.Text = rgbColor.G.ToString(CultureInfo.CurrentUICulture);
+ this.Channel2NumberBox.Minimum = 0;
+ this.Channel2NumberBox.Maximum = 255;
+ this.Channel2NumberBox.Value = Convert.ToDouble(rgbColor.G);
}
if (this.Channel2Slider != null)
@@ -804,10 +743,11 @@ private void UpdateColorControlValues()
}
// Blue
- if (this.Channel3TextBox != null)
+ if (this.Channel3NumberBox != null)
{
- this.Channel3TextBox.MaxLength = 3;
- this.Channel3TextBox.Text = rgbColor.B.ToString(CultureInfo.CurrentUICulture);
+ this.Channel3NumberBox.Minimum = 0;
+ this.Channel3NumberBox.Maximum = 255;
+ this.Channel3NumberBox.Value = Convert.ToDouble(rgbColor.B);
}
if (this.Channel3Slider != null)
@@ -818,10 +758,11 @@ private void UpdateColorControlValues()
}
// Alpha
- if (this.AlphaChannelTextBox != null)
+ if (this.AlphaChannelNumberBox != null)
{
- this.AlphaChannelTextBox.MaxLength = 3;
- this.AlphaChannelTextBox.Text = rgbColor.A.ToString(CultureInfo.CurrentUICulture);
+ this.AlphaChannelNumberBox.Minimum = 0;
+ this.AlphaChannelNumberBox.Maximum = 255;
+ this.AlphaChannelNumberBox.Value = Convert.ToDouble(rgbColor.A);
}
if (this.AlphaChannelSlider != null)
@@ -1005,7 +946,7 @@ private void UpdateChannelSliderBackground(ColorPickerSlider slider)
// 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)
+ if (this.savedHsvColor == null)
{
var rgbColor = this.Color;
@@ -1404,17 +1345,20 @@ private void ColorRepToggleButton_CheckedUnchecked(object sender, RoutedEventArg
}
///
- /// Event handler for when a preview color panel is pressed.
- /// This will update the color to the background of the pressed panel.
+ /// Event handler for when the color previewer requests a new color.
///
- private void PreviewBorder_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
+ private void ColorPreviewer_ColorChangeRequested(object sender, HsvColor hsvColor)
{
- Border border = sender as Border;
+ Color rgbColor = Uwp.Helpers.ColorHelper.FromHsv(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A);
- if (border?.Background is SolidColorBrush brush)
- {
- this.ScheduleColorUpdate(brush.Color);
- }
+ // Regardless of the active color model, the previewer always uses 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
+ this.savedHsvColor = hsvColor;
+ this.savedHsvColorRgbEquivalent = rgbColor;
+
+ this.ScheduleColorUpdate(rgbColor);
return;
}
@@ -1465,29 +1409,32 @@ private void HexInputTextBox_LostFocus(object sender, RoutedEventArgs e)
}
///
- /// Event handler for when a key is pressed within a color channel TextBox.
- /// This is used to trigger a re-evaluation of the color based on the TextBox value.
+ /// Event handler for when the value within one of the channel NumberBoxes is changed.
///
- private void ChannelTextBox_KeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
+ private void ChannelNumberBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
{
- if (e.Key == Windows.System.VirtualKey.Enter)
+ double senderValue = sender.Value;
+
+ if (object.ReferenceEquals(sender, this.Channel1NumberBox))
{
- this.ApplyChannelTextBoxValue(sender as TextBox);
+ this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel1, senderValue);
+ }
+ else if (object.ReferenceEquals(sender, this.Channel2NumberBox))
+ {
+ this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel2, senderValue);
+ }
+ else if (object.ReferenceEquals(sender, this.Channel3NumberBox))
+ {
+ this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Channel3, senderValue);
+ }
+ else if (object.ReferenceEquals(sender, this.AlphaChannelNumberBox))
+ {
+ this.SetColorChannel(this.GetActiveColorRepresentation(), ColorChannel.Alpha, senderValue);
}
return;
}
- ///
- /// Event handler for when a color channel TextBox loses focus.
- /// This is used to trigger a re-evaluation of the color based on the TextBox value.
- ///
- 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.
///
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.xaml
index 3641acb2e3c..3ea32db76e1 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.xaml
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.xaml
@@ -5,6 +5,7 @@
xmlns:localconverters="using:Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters"
xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
+ xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:winuiprimitives="using:Microsoft.UI.Xaml.Controls.Primitives">
@@ -41,13 +42,12 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
-
@@ -216,7 +216,7 @@
BorderBrush="{ThemeResource ToggleButtonBackgroundChecked}"
BorderThickness="1"
Content="RGB"
- CornerRadius="2,0,0,2"
+ CornerRadius="4,0,0,4"
IsThreeState="False" />
@@ -238,7 +238,7 @@
Background="{ThemeResource TextBoxDisabledBackgroundThemeBrush}"
BorderBrush="{ThemeResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
- CornerRadius="2,0,0,2">
+ CornerRadius="4,0,0,4">
+ CornerRadius="4,0,0,4">
-
+
+ CornerRadius="4,0,0,4">
-
+
+ CornerRadius="4,0,0,4">
-
+
+ CornerRadius="4,0,0,4">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -551,7 +460,7 @@
-
+
@@ -578,7 +487,7 @@
-
+
@@ -589,9 +498,9 @@
-
-
-
+
+
+
@@ -612,7 +521,7 @@
-
+
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPickerRenderingHelpers.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPickerRenderingHelpers.cs
index fd0b9de9a39..b66428f8d8e 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPickerRenderingHelpers.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPickerRenderingHelpers.cs
@@ -19,6 +19,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
///
internal class ColorPickerRenderingHelpers
{
+ ///
+ /// Gets the default color used for checkered background squares (alternate squares are transparent).
+ /// Checkered backgrounds are used to help show transparency.
+ ///
+ internal static readonly Color CheckerBackgroundColor = Color.FromArgb(0x19, 0x80, 0x80, 0x80);
+
///
/// 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.
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.Properties.cs
new file mode 100644
index 00000000000..b6b24fded29
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.Properties.cs
@@ -0,0 +1,70 @@
+// 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 Windows.UI;
+using Windows.UI.Xaml;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives
+{
+ ///
+ public partial class ColorPreviewer
+ {
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty HsvColorProperty =
+ DependencyProperty.Register(
+ nameof(HsvColor),
+ typeof(HsvColor),
+ typeof(ColorPreviewer),
+ new PropertyMetadata(
+ Colors.Transparent.ToHsv(),
+ (s, e) => (s as ColorPreviewer)?.OnDependencyPropertyChanged(s, e)));
+
+ ///
+ /// Gets or sets the HSV color represented by the color previewer.
+ /// 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 ShowAccentColorsProperty =
+ DependencyProperty.Register(
+ nameof(ShowAccentColors),
+ typeof(bool),
+ typeof(ColorPreviewer),
+ new PropertyMetadata(
+ true,
+ (s, e) => (s as ColorPreviewer)?.OnDependencyPropertyChanged(s, e)));
+
+ ///
+ /// Gets or sets a value indicating whether accent colors are shown along
+ /// with the preview color.
+ ///
+ public bool ShowAccentColors
+ {
+ get => (bool)this.GetValue(ShowAccentColorsProperty);
+ set
+ {
+ if (object.Equals(value, this.GetValue(ShowAccentColorsProperty)) == false)
+ {
+ this.SetValue(ShowAccentColorsProperty, value);
+ }
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.cs
new file mode 100644
index 00000000000..2e9839553b1
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.cs
@@ -0,0 +1,223 @@
+// 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.Globalization;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace Microsoft.Toolkit.Uwp.UI.Controls.Primitives
+{
+ ///
+ /// Presents a 's preview color with optional accent colors.
+ ///
+ [TemplatePart(Name = nameof(ColorPreviewer.CenterCheckeredBackgroundBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.LeftCheckeredBackgroundBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.RightCheckeredBackgroundBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.P1PreviewBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.P2PreviewBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.N1PreviewBorder), Type = typeof(Border))]
+ [TemplatePart(Name = nameof(ColorPreviewer.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 ColorPreviewer : Control
+ {
+ ///
+ /// Event for when a color change is requested by user interaction with the previewer.
+ ///
+ public event EventHandler ColorChangeRequested;
+
+ private bool eventsConnected = false;
+
+ private Border CenterCheckeredBackgroundBorder;
+ private Border LeftCheckeredBackgroundBorder;
+ private Border RightCheckeredBackgroundBorder;
+
+ private Border N1PreviewBorder;
+ private Border N2PreviewBorder;
+ private Border P1PreviewBorder;
+ private Border P2PreviewBorder;
+
+ /***************************************************************************************
+ *
+ * Constructor/Destructor
+ *
+ ***************************************************************************************/
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorPreviewer()
+ : base()
+ {
+ this.DefaultStyleKey = typeof(ColorPreviewer);
+ }
+
+ /***************************************************************************************
+ *
+ * Methods
+ *
+ ***************************************************************************************/
+
+ ///
+ /// 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 = false)
+ where T : DependencyObject
+ {
+ T child = this.GetTemplateChild(childName) as T;
+ if ((child == null) && isRequired)
+ {
+ ThrowArgumentNullException();
+ }
+
+ return child;
+
+ static void ThrowArgumentNullException() => throw new ArgumentNullException(nameof(childName));
+ }
+
+ ///
+ /// 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.CenterCheckeredBackgroundBorder != null) { this.CenterCheckeredBackgroundBorder.Loaded += CheckeredBackgroundBorder_Loaded; }
+ if (this.LeftCheckeredBackgroundBorder != null) { this.LeftCheckeredBackgroundBorder.Loaded += CheckeredBackgroundBorder_Loaded; }
+ if (this.RightCheckeredBackgroundBorder != null) { this.RightCheckeredBackgroundBorder.Loaded += CheckeredBackgroundBorder_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; }
+
+ this.eventsConnected = true;
+ }
+ else if (connected == false && this.eventsConnected == true)
+ {
+ // Remove all events
+ if (this.CenterCheckeredBackgroundBorder != null) { this.CenterCheckeredBackgroundBorder.Loaded -= CheckeredBackgroundBorder_Loaded; }
+ if (this.LeftCheckeredBackgroundBorder != null) { this.LeftCheckeredBackgroundBorder.Loaded -= CheckeredBackgroundBorder_Loaded; }
+ if (this.RightCheckeredBackgroundBorder != null) { this.RightCheckeredBackgroundBorder.Loaded -= CheckeredBackgroundBorder_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; }
+
+ this.eventsConnected = false;
+ }
+
+ return;
+ }
+
+ /***************************************************************************************
+ *
+ * OnEvent Overridable Methods
+ *
+ ***************************************************************************************/
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ // Remove any existing events present if the control was previously loaded then unloaded
+ this.ConnectEvents(false);
+
+ this.CenterCheckeredBackgroundBorder = this.GetTemplateChild(nameof(CenterCheckeredBackgroundBorder));
+ this.LeftCheckeredBackgroundBorder = this.GetTemplateChild(nameof(LeftCheckeredBackgroundBorder));
+ this.RightCheckeredBackgroundBorder = this.GetTemplateChild(nameof(RightCheckeredBackgroundBorder));
+
+ this.N1PreviewBorder = this.GetTemplateChild(nameof(N1PreviewBorder));
+ this.N2PreviewBorder = this.GetTemplateChild(nameof(N2PreviewBorder));
+ this.P1PreviewBorder = this.GetTemplateChild(nameof(P1PreviewBorder));
+ this.P2PreviewBorder = this.GetTemplateChild(nameof(P2PreviewBorder));
+
+ // Must connect after controls are resolved
+ this.ConnectEvents(true);
+
+ base.OnApplyTemplate();
+ }
+
+ ///
+ /// Method called whenever a dependency property value is changed.
+ /// This may happen through binding directly or a property setter.
+ ///
+ /// The sender of the event.
+ /// The event arguments.
+ protected virtual void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
+ {
+ return;
+ }
+
+ ///
+ /// Called before the event occurs.
+ ///
+ /// The newly requested color.
+ protected virtual void OnColorChangeRequested(HsvColor color)
+ {
+ this.ColorChangeRequested?.Invoke(this, color);
+ return;
+ }
+
+ /***************************************************************************************
+ *
+ * Event Handling
+ *
+ ***************************************************************************************/
+
+ ///
+ /// Event handler to draw checkered backgrounds on-demand as controls are loaded.
+ ///
+ private async void CheckeredBackgroundBorder_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (sender is Border border)
+ {
+ int width = Convert.ToInt32(border.ActualWidth);
+ int height = Convert.ToInt32(border.ActualHeight);
+
+ var bitmap = await ColorPickerRenderingHelpers.CreateCheckeredBitmapAsync(
+ width,
+ height,
+ ColorPickerRenderingHelpers.CheckerBackgroundColor);
+
+ if (bitmap != null)
+ {
+ border.Background = await ColorPickerRenderingHelpers.BitmapToBrushAsync(bitmap, width, height);
+ }
+ }
+
+ 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;
+ int accentStep = 0;
+ HsvColor hsvColor = this.HsvColor;
+
+ // Get the value component delta
+ try
+ {
+ accentStep = int.Parse(border.Tag?.ToString(), CultureInfo.InvariantCulture);
+ }
+ catch { }
+
+ HsvColor newHsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep);
+ this.OnColorChangeRequested(newHsvColor);
+
+ return;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.xaml
new file mode 100644
index 00000000000..31c087c16bc
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPreviewer.xaml
@@ -0,0 +1,120 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorToColorShadeConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorToColorShadeConverter.cs
deleted file mode 100644
index a16820c03a5..00000000000
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorToColorShadeConverter.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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 Microsoft.Toolkit.Uwp.Helpers;
-using Windows.UI;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Media;
-
-namespace Microsoft.Toolkit.Uwp.UI.Controls.ColorPickerConverters
-{
- ///
- /// Creates an accent color shade from a color value.
- /// Only +/- 3 shades from the given color are supported.
- ///
- [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.")]
- public class ColorToColorShadeConverter : IValueConverter
- {
- ///
- public object Convert(
- object value,
- Type targetType,
- object parameter,
- string language)
- {
- int shade;
- byte tolerance = 0x05;
- double valueDelta = 0.25;
- Color rgbColor;
-
- // Get the current color in HSV
- if (value is Color valueColor)
- {
- rgbColor = valueColor;
- }
- else if (value is SolidColorBrush valueBrush)
- {
- rgbColor = valueBrush.Color;
- }
- else
- {
- // Invalid color value provided
- return DependencyProperty.UnsetValue;
- }
-
- // Get the value component delta
- try
- {
- shade = System.Convert.ToInt32(parameter?.ToString());
- }
- catch
- {
- // Invalid parameter provided, unable to convert to integer
- return DependencyProperty.UnsetValue;
- }
-
- // 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);
- }
-
- return rgbColor;
- }
- else if (rgbColor.R >= (0xFF + tolerance) &&
- rgbColor.G >= (0xFF + tolerance) &&
- rgbColor.B >= (0xFF + tolerance))
- {
- 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
- {
- 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.
- // 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)
- {
- colorValue *= 1.0 + (shade * valueDelta);
- }
-
- 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)
- {
- return DependencyProperty.UnsetValue;
- }
- }
-}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/Themes/Generic.xaml
index 761bf93fb6c..89c7790b8a0 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/Themes/Generic.xaml
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/Themes/Generic.xaml
@@ -4,6 +4,7 @@
+