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 @@ +