Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color picker fixes #4134

Merged
merged 8 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,43 @@
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
<!-- Example 5 -->
<StackPanel Grid.Row="3"
robloo marked this conversation as resolved.
Show resolved Hide resolved
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled <LineBreak />
Only Color Palette Shown
</TextBlock>
</Border>
<controls:ColorPickerButton x:Name="ColorPickerButton5"
SelectedColor="Teal">
<controls:ColorPickerButton.ColorPickerStyle>
<Style TargetType="controls:ColorPicker">
<Setter Property="ColorSpectrumShape"
Value="Ring"/>
<Setter Property="IsAlphaEnabled"
Value="True"/>
<Setter Property="IsHexInputVisible"
Value="True"/>
<Setter Property="IsColorSpectrumVisible"
Value="False"/>
<Setter Property="IsColorPaletteVisible"
Value="True"/>
<Setter Property="IsColorChannelTextInputVisible"
Value="False"/>
</Style>
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
</Grid>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
Alpha channel enabled
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPickerButton3"
<controls:ColorPicker x:Name="ColorPicker3"
Color="Transparent"
ColorSpectrumShape="Ring"
IsAlphaEnabled="True"
Expand All @@ -78,12 +78,33 @@
Saturation+Value spectrum channels
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPickerButton4"
<controls:ColorPicker x:Name="ColorPicker4"
Color="Maroon"
ColorSpectrumShape="Ring"
ColorSpectrumComponents="SaturationValue"
IsAlphaEnabled="True"
IsHexInputVisible="True"/>
<!-- Example 5 -->
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled <LineBreak />
Only Color Palette Shown
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPicker5"
Color="Teal"
ColorSpectrumShape="Ring"
IsAlphaEnabled="True"
IsHexInputVisible="True"
IsColorSpectrumVisible="False"
IsColorPaletteVisible="True"
IsColorChannelTextInputVisible="False"/>
</StackPanel>
</ScrollViewer>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public partial class ColorPicker
nameof(CustomPaletteColors),
typeof(ObservableCollection<Windows.UI.Color>),
typeof(ColorPicker),
new PropertyMetadata(null));
new PropertyMetadata(
null,
(s, e) => (s as ColorPicker)?.OnDependencyPropertyChanged(s, e)));

/// <summary>
/// Gets the list of custom palette colors.
Expand All @@ -38,7 +40,9 @@ public ObservableCollection<Windows.UI.Color> CustomPaletteColors
nameof(CustomPaletteColumnCount),
typeof(int),
typeof(ColorPicker),
new PropertyMetadata(4));
new PropertyMetadata(
4,
(s, e) => (s as ColorPicker)?.OnDependencyPropertyChanged(s, e)));

/// <summary>
/// Gets or sets the number of colors in each row (section) of the custom color palette.
Expand All @@ -64,7 +68,9 @@ public int CustomPaletteColumnCount
nameof(CustomPalette),
typeof(IColorPalette),
typeof(ColorPicker),
new PropertyMetadata(null));
new PropertyMetadata(
null,
(s, e) => (s as ColorPicker)?.OnDependencyPropertyChanged(s, e)));

/// <summary>
/// Gets or sets the custom color palette.
Expand All @@ -91,7 +97,9 @@ public IColorPalette CustomPalette
nameof(IsColorPaletteVisible),
typeof(bool),
typeof(ColorPicker),
new PropertyMetadata(true));
new PropertyMetadata(
true,
(s, e) => (s as ColorPicker)?.OnDependencyPropertyChanged(s, e)));

/// <summary>
/// Gets or sets a value indicating whether the color palette is visible.
Expand Down
141 changes: 92 additions & 49 deletions Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker/ColorPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
[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.ColorPanelSelector), Type = typeof(ListBox))]
[TemplatePart(Name = nameof(ColorPicker.ColorSpectrumControl), Type = typeof(ColorSpectrum))]
[TemplatePart(Name = nameof(ColorPicker.ColorSpectrumAlphaSlider), Type = typeof(ColorPickerSlider))]
[TemplatePart(Name = nameof(ColorPicker.ColorSpectrumThirdDimensionSlider), Type = typeof(ColorPickerSlider))]
Expand All @@ -65,8 +66,6 @@ public partial class ColorPicker : Microsoft.UI.Xaml.Controls.ColorPicker
private const int ColorUpdateInterval = 30; // Milliseconds

private long tokenColor;
private long tokenCustomPalette;
private long tokenIsColorPaletteVisible;

private bool callbacksConnected = false;
private bool eventsConnected = false;
Expand All @@ -78,6 +77,7 @@ public partial class ColorPicker : Microsoft.UI.Xaml.Controls.ColorPicker
private Color? updatedRgbColor = null;
private DispatcherQueueTimer dispatcherQueueTimer = null;

private ListBox ColorPanelSelector;
private ColorSpectrum ColorSpectrumControl;
private ColorPickerSlider ColorSpectrumAlphaSlider;
private ColorPickerSlider ColorSpectrumThirdDimensionSlider;
Expand Down Expand Up @@ -177,6 +177,8 @@ protected override void OnApplyTemplate()
// We need to disconnect old events first
this.ConnectEvents(false);

this.ColorPanelSelector = this.GetTemplateChild<ListBox>(nameof(ColorPanelSelector));

this.ColorSpectrumControl = this.GetTemplateChild<ColorSpectrum>(nameof(ColorSpectrumControl));
this.ColorSpectrumAlphaSlider = this.GetTemplateChild<ColorPickerSlider>(nameof(ColorSpectrumAlphaSlider));
this.ColorSpectrumThirdDimensionSlider = this.GetTemplateChild<ColorPickerSlider>(nameof(ColorSpectrumThirdDimensionSlider));
Expand Down Expand Up @@ -216,6 +218,7 @@ protected override void OnApplyTemplate()

base.OnApplyTemplate();
this.UpdateVisualState(false);
this.ValidateSelectedPanel();
Copy link
Contributor Author

@robloo robloo Jul 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an issue with validating and updating the selected panel only here. If the IsColorSpectrumVisible or IsColorChannelTextInputVisible properties are changed after the template is applied the selected tab may become invalid. This is because the base WinUI ColorPicker is currenlty responsible for updating the visual state that actually applies tab visibility for these two (it is all done in the style). Only IsColorPaletteVisible is managed by the derived toolkit ColorPicker. To fix this:

  1. Need to monitor for property changes on these properties as well, or
  2. Need to connect to VisualState changed events -- there are more than one and this is a bit ugly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an existing issue, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was waiting to see if its considered enough of an issue to fix. The changes now will work as long as the properties are updated in XAML which is by far the largest use case.

this.isInitialized = true;
this.SetActiveColorRepresentation(ColorRepresentation.Rgba);
this.UpdateColorControlValues(); // TODO: This will also connect events after, can we optimize vs. doing it twice with the ConnectEvents above?
Expand Down Expand Up @@ -247,23 +250,19 @@ private T GetTemplateChild<T>(string childName, bool isRequired = false)
/// <param name="connected">True to connect callbacks, otherwise false.</param>
private void ConnectCallbacks(bool connected)
{
if ((connected == true) &&
(this.callbacksConnected == false))
if (connected == true &&
this.callbacksConnected == false)
{
// Add callbacks for dependency properties
this.tokenColor = this.RegisterPropertyChangedCallback(ColorProperty, OnColorChanged);
this.tokenCustomPalette = this.RegisterPropertyChangedCallback(CustomPaletteProperty, OnCustomPaletteChanged);
this.tokenIsColorPaletteVisible = this.RegisterPropertyChangedCallback(IsColorPaletteVisibleProperty, OnIsColorPaletteVisibleChanged);
this.tokenColor = this.RegisterPropertyChangedCallback(ColorProperty, OnColorChanged);

this.callbacksConnected = true;
}
else if ((connected == false) &&
(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.UnregisterPropertyChangedCallback(IsColorPaletteVisibleProperty, this.tokenIsColorPaletteVisible);
this.UnregisterPropertyChangedCallback(ColorProperty, this.tokenColor);

this.callbacksConnected = false;
}
Expand All @@ -277,8 +276,8 @@ private void ConnectCallbacks(bool connected)
/// <param name="connected">True to connect event handlers, otherwise false.</param>
private void ConnectEvents(bool connected)
{
if ((connected == true) &&
(this.eventsConnected == false))
if (connected == true &&
this.eventsConnected == false)
{
// Add all events
if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.ColorChanged += ColorSpectrum_ColorChanged; }
Expand Down Expand Up @@ -331,8 +330,8 @@ private void ConnectEvents(bool connected)

this.eventsConnected = true;
}
else if ((connected == false) &&
(this.eventsConnected == true))
else if (connected == false &&
this.eventsConnected == true)
{
// Remove all events
if (this.ColorSpectrumControl != null) { this.ColorSpectrumControl.ColorChanged -= ColorSpectrum_ColorChanged; }
Expand Down Expand Up @@ -1065,6 +1064,83 @@ private void SetDefaultPalette()
return;
}

/// <summary>
/// Validates and updates the current 'tab' or 'panel' selection.
/// If the currently selected tab is collapsed, the next visible tab will be selected.
/// </summary>
private void ValidateSelectedPanel()
{
object selectedItem = null;

if (this.ColorPanelSelector != null)
{
if (this.ColorPanelSelector.SelectedItem == null &&
this.ColorPanelSelector.Items.Count > 0)
{
// As a failsafe, forcefully select the first item
selectedItem = this.ColorPanelSelector.Items[0];
}
else
{
selectedItem = this.ColorPanelSelector.SelectedItem;
}

if (selectedItem is UIElement selectedElement &&
selectedElement.Visibility == Visibility.Collapsed)
{
// Select the first visible item instead
foreach (object item in this.ColorPanelSelector.Items)
{
if (item is UIElement element &&
element.Visibility == Visibility.Visible)
{
selectedItem = item;
break;
}
}
}

this.ColorPanelSelector.SelectedItem = selectedItem;
}

return;
}

private void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject senderControl = sender as DependencyObject;

/* Note: ColorProperty is defined in the base class and cannot be used here
* See the OnColorChanged callback below
*/

if (object.ReferenceEquals(args.Property, CustomPaletteProperty))
{
IColorPalette palette = this.CustomPalette;

if (palette != null)
{
this.CustomPaletteColumnCount = 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));
}
}
}
}
else if (object.ReferenceEquals(args.Property, IsColorPaletteVisibleProperty))
{
this.UpdateVisualState(false);
this.ValidateSelectedPanel();
}

return;
}

/***************************************************************************************
*
* Color Update Timer
Expand Down Expand Up @@ -1147,39 +1223,6 @@ private void OnColorChanged(DependencyObject d, DependencyProperty e)
return;
}

/// <summary>
/// Callback for when the <see cref="CustomPalette"/> dependency property value changes.
/// </summary>
private void OnCustomPaletteChanged(DependencyObject d, DependencyProperty e)
{
IColorPalette palette = this.CustomPalette;

if (palette != null)
{
this.CustomPaletteColumnCount = 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;
}

/// <summary>
/// Callback for when the <see cref="IsColorPaletteVisible"/> dependency property value changes.
/// </summary>
private void OnIsColorPaletteVisibleChanged(DependencyObject d, DependencyProperty e)
{
this.UpdateVisualState(false);
return;
}

/***************************************************************************************
*
* Event Handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@
CornerRadius="4" />
<ListBox x:Name="ColorPanelSelector"
Grid.Row="0"
HorizontalAlignment="Center"
HorizontalAlignment="Stretch"
Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemContainerStyle="{StaticResource UnderlineListBoxItemStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
<controls:UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem x:Name="SpectrumListBoxItem"
Expand Down Expand Up @@ -1180,9 +1181,6 @@
<Setter Property="Padding" Value="{StaticResource ListBoxItemPadding}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="8,0,8,0" />
<Setter Property="Padding" Value="24,0,24,0" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="Template" Value="{StaticResource UnderlineListBoxItemTemplate}" />
</Style>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public ColorRepresentation ColorRepresentation
typeof(Brush),
typeof(ColorPickerSlider),
new PropertyMetadata(
new SolidColorBrush(Colors.Gray),
null,
(s, e) => (s as ColorPickerSlider)?.OnDependencyPropertyChanged(s, e)));

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
TargetType="controls:ColorPickerSlider">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="IsThumbToolTipEnabled" Value="False" />
<Setter Property="DefaultForeground" Value="Gray" />
robloo marked this conversation as resolved.
Show resolved Hide resolved
<Setter Property="Template">
<Setter.Value>
<!-- Based on WinUI version 2.4.2 -->
Expand Down