Browse Source

Merge branch 'master' into rndr4

pull/8105/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
7b4545b0e5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  2. 284
      src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs
  3. 136
      src/Avalonia.Controls.ColorPicker/ColorPalettes/FluentColorPalette.cs
  4. 38
      src/Avalonia.Controls.ColorPicker/ColorPalettes/IColorPalette.cs
  5. 302
      src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs
  6. 19
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  7. 14
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.Properties.cs
  8. 53
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs
  9. 20
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs
  10. 109
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  11. 2
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  12. 3
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  13. 495
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  14. 379
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  15. 26
      src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs
  16. 4
      src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs
  17. 2
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  18. 87
      src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs
  19. 4
      src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs
  20. 50
      src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs
  21. 4
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  22. 136
      src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml
  23. 30
      src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml
  24. 34
      src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml
  25. 16
      src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml
  26. 91
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  27. 136
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml
  28. 30
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  29. 34
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml
  30. 650
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  31. 20
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
  32. 56
      src/Avalonia.Controls/Converters/EnumToBoolConverter.cs
  33. 54
      src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs
  34. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs

44
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -13,8 +13,14 @@
<pc:ThirdComponentConverter x:Key="ThirdComponent" /> <pc:ThirdComponentConverter x:Key="ThirdComponent" />
</UserControl.Resources> </UserControl.Resources>
<Grid ColumnDefinitions="Auto,10,Auto"> <Grid ColumnDefinitions="Auto,10,Auto,10,Auto"
<Grid Grid.Column="0" RowDefinitions="Auto,Auto">
<ColorPicker Grid.Column="0"
Grid.Row="1" />
<ColorView Grid.Column="0"
Grid.Row="0"
ColorSpectrumShape="Ring" />
<Grid Grid.Column="2"
Grid.Row="0" Grid.Row="0"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
<ColorSpectrum x:Name="ColorSpectrum1" <ColorSpectrum x:Name="ColorSpectrum1"
@ -41,39 +47,11 @@
ColorModel="Hsva" ColorModel="Hsva"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
<ColorPreviewer Grid.Row="5" <ColorPreviewer Grid.Row="5"
ShowAccentColors="True" IsAccentColorsVisible="True"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" /> HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />
</Grid> </Grid>
<Grid Grid.Column="2" <Grid Grid.Column="4"
Grid.Row="0" Grid.Row="0">
ColumnDefinitions="Auto,Auto,Auto"
RowDefinitions="Auto,Auto">
<ColorSlider Grid.Column="0"
Grid.Row="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
ColorComponent="{Binding Components, ElementName=ColorSpectrum2, Converter={StaticResource ThirdComponent}}"
ColorModel="Hsva"
Orientation="Vertical"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
<ColorSpectrum x:Name="ColorSpectrum2"
Grid.Column="1"
Grid.Row="0"
Color="Green"
Shape="Ring"
Height="256"
Width="256" />
<ColorSlider Grid.Column="2"
Grid.Row="0"
ColorComponent="Alpha"
ColorModel="Hsva"
Orientation="Vertical"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
<ColorPreviewer Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="1"
ShowAccentColors="True"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum2}" />
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

284
src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs

@ -0,0 +1,284 @@
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Implements a reduced flat design or flat UI color palette.
/// </summary>
/// <remarks>
/// See:
/// - https://htmlcolorcodes.com/color-chart/
/// - https://htmlcolorcodes.com/color-chart/flat-design-color-chart/
/// - http://designmodo.github.io/Flat-UI/
///
/// The GitHub project is licensed as MIT: https://github.com/designmodo/Flat-UI.
///
/// </remarks>
public class FlatColorPalette : IColorPalette
{
// The full Flat UI color chart has 10 rows and 20 columns
// See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png
// This is a reduced palette for usability
private static Color[,] colorChart = new Color[,]
{
// Pomegranate
{
Color.FromArgb(0xFF, 0xF9, 0xEB, 0xEA),
Color.FromArgb(0xFF, 0xE6, 0xB0, 0xAA),
Color.FromArgb(0xFF, 0xCD, 0x61, 0x55),
Color.FromArgb(0xFF, 0xA9, 0x32, 0x26),
Color.FromArgb(0xFF, 0x7B, 0x24, 0x1C),
},
// Amethyst
{
Color.FromArgb(0xFF, 0xF5, 0xEE, 0xF8),
Color.FromArgb(0xFF, 0xD7, 0xBD, 0xE2),
Color.FromArgb(0xFF, 0xAF, 0x7A, 0xC5),
Color.FromArgb(0xFF, 0x88, 0x4E, 0xA0),
Color.FromArgb(0xFF, 0x63, 0x39, 0x74),
},
// Belize Hole
{
Color.FromArgb(0xFF, 0xEA, 0xF2, 0xF8),
Color.FromArgb(0xFF, 0xA9, 0xCC, 0xE3),
Color.FromArgb(0xFF, 0x54, 0x99, 0xC7),
Color.FromArgb(0xFF, 0x24, 0x71, 0xA3),
Color.FromArgb(0xFF, 0x1A, 0x52, 0x76),
},
// Turquoise
{
Color.FromArgb(0xFF, 0xE8, 0xF8, 0xF5),
Color.FromArgb(0xFF, 0xA3, 0xE4, 0xD7),
Color.FromArgb(0xFF, 0x48, 0xC9, 0xB0),
Color.FromArgb(0xFF, 0x17, 0xA5, 0x89),
Color.FromArgb(0xFF, 0x11, 0x78, 0x64),
},
// Nephritis
{
Color.FromArgb(0xFF, 0xE9, 0xF7, 0xEF),
Color.FromArgb(0xFF, 0xA9, 0xDF, 0xBF),
Color.FromArgb(0xFF, 0x52, 0xBE, 0x80),
Color.FromArgb(0xFF, 0x22, 0x99, 0x54),
Color.FromArgb(0xFF, 0x19, 0x6F, 0x3D),
},
// Sunflower
{
Color.FromArgb(0xFF, 0xFE, 0xF9, 0xE7),
Color.FromArgb(0xFF, 0xF9, 0xE7, 0x9F),
Color.FromArgb(0xFF, 0xF4, 0xD0, 0x3F),
Color.FromArgb(0xFF, 0xD4, 0xAC, 0x0D),
Color.FromArgb(0xFF, 0x9A, 0x7D, 0x0A),
},
// Carrot
{
Color.FromArgb(0xFF, 0xFD, 0xF2, 0xE9),
Color.FromArgb(0xFF, 0xF5, 0xCB, 0xA7),
Color.FromArgb(0xFF, 0xEB, 0x98, 0x4E),
Color.FromArgb(0xFF, 0xCA, 0x6F, 0x1E),
Color.FromArgb(0xFF, 0x93, 0x51, 0x16),
},
// Clouds
{
Color.FromArgb(0xFF, 0xFD, 0xFE, 0xFE),
Color.FromArgb(0xFF, 0xF7, 0xF9, 0xF9),
Color.FromArgb(0xFF, 0xF0, 0xF3, 0xF4),
Color.FromArgb(0xFF, 0xD0, 0xD3, 0xD4),
Color.FromArgb(0xFF, 0x97, 0x9A, 0x9A),
},
// Concrete
{
Color.FromArgb(0xFF, 0xF4, 0xF6, 0xF6),
Color.FromArgb(0xFF, 0xD5, 0xDB, 0xDB),
Color.FromArgb(0xFF, 0xAA, 0xB7, 0xB8),
Color.FromArgb(0xFF, 0x83, 0x91, 0x92),
Color.FromArgb(0xFF, 0x5F, 0x6A, 0x6A),
},
// Wet Asphalt
{
Color.FromArgb(0xFF, 0xEB, 0xED, 0xEF),
Color.FromArgb(0xFF, 0xAE, 0xB6, 0xBF),
Color.FromArgb(0xFF, 0x5D, 0x6D, 0x7E),
Color.FromArgb(0xFF, 0x2E, 0x40, 0x53),
Color.FromArgb(0xFF, 0x21, 0x2F, 0x3C),
},
};
/// <summary>
/// Gets the index of the default shade of colors in this palette.
/// </summary>
public const int DefaultShadeIndex = 2;
/// <summary>
/// The index in the color palette of the 'Pomegranate' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int PomegranateIndex = 0;
/// <summary>
/// The index in the color palette of the 'Amethyst' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int AmethystIndex = 1;
/// <summary>
/// The index in the color palette of the 'BelizeHole' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BelizeHoleIndex = 2;
/// <summary>
/// The index in the color palette of the 'Turquoise' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int TurquoiseIndex = 3;
/// <summary>
/// The index in the color palette of the 'Nephritis' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int NephritisIndex = 4;
/// <summary>
/// The index in the color palette of the 'Sunflower' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int SunflowerIndex = 5;
/// <summary>
/// The index in the color palette of the 'Carrot' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int CarrotIndex = 6;
/// <summary>
/// The index in the color palette of the 'Clouds' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int CloudsIndex = 7;
/// <summary>
/// The index in the color palette of the 'Concrete' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int ConcreteIndex = 8;
/// <summary>
/// The index in the color palette of the 'WetAsphalt' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int WetAsphaltIndex = 9;
/// <inheritdoc/>
public int ColorCount
{
// Table is transposed compared to the reference chart
get => colorChart.GetLength(0);
}
/// <inheritdoc/>
public int ShadeCount
{
// Table is transposed compared to the reference chart
get => colorChart.GetLength(1);
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFC0392B.
/// </summary>
public static Color Pomegranate
{
get => colorChart[PomegranateIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF9B59B6.
/// </summary>
public static Color Amethyst
{
get => colorChart[AmethystIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF2980B9.
/// </summary>
public static Color BelizeHole
{
get => colorChart[BelizeHoleIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF1ABC9C.
/// </summary>
public static Color Turquoise
{
get => colorChart[TurquoiseIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF27AE60.
/// </summary>
public static Color Nephritis
{
get => colorChart[NephritisIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFF1C40F.
/// </summary>
public static Color Sunflower
{
get => colorChart[SunflowerIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFE67E22.
/// </summary>
public static Color Carrot
{
get => colorChart[CarrotIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFECF0F1.
/// </summary>
public static Color Clouds
{
get => colorChart[CloudsIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF95A5A6.
/// </summary>
public static Color Concrete
{
get => colorChart[ConcreteIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF34495E.
/// </summary>
public static Color WetAsphalt
{
get => colorChart[WetAsphaltIndex, DefaultShadeIndex];
}
/// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex)
{
// Table is transposed compared to the reference chart
return colorChart[
MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1),
MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)];
}
}
}

136
src/Avalonia.Controls.ColorPicker/ColorPalettes/FluentColorPalette.cs

@ -0,0 +1,136 @@
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Implements the standard Windows 10 color palette.
/// </summary>
public class FluentColorPalette : IColorPalette
{
// Values were taken from the Settings App, Personalization > Colors which match with
// https://docs.microsoft.com/en-us/windows/uwp/whats-new/windows-docs-december-2017
//
// The default ordering and grouping of colors was undesirable so was modified.
// Colors were transposed: the colors in rows within the Settings app became columns here.
// This is because columns in an IColorPalette generally should contain different shades of
// the same color. In the settings app this concept is somewhat loosely reversed.
// The first 'column' ordering, after being transposed, was then reversed so 'red' colors
// were near to each other.
//
// This new ordering most closely follows the Windows standard while:
//
// 1. Keeping colors in a 'spectrum' order
// 2. Keeping like colors next to each both in rows and columns
// (which is unique for the windows palette).
// For example, similar red colors are next to each other in both
// rows within the same column and rows within the column next to it.
// This follows a 'snake-like' pattern as illustrated below.
// 3. A downside of this ordering is colors don't follow strict 'shades'
// as in other palettes.
//
// The colors will be displayed in the below pattern.
// This pattern follows a spectrum while keeping like-colors near to one
// another across both rows and columns.
//
// ┌Red───┐ ┌Blue──┐ ┌Gray──┐
// │ │ │ │ │ |
// │ │ │ │ │ |
// Yellow └Violet┘ └Green─┘ Brown
private static Color[,] colorChart = new Color[,]
{
{
// Ordering reversed for this section only
Color.FromArgb(255, 255, 67, 67), /* #FF4343 */
Color.FromArgb(255, 209, 52, 56), /* #D13438 */
Color.FromArgb(255, 239, 105, 80), /* #EF6950 */
Color.FromArgb(255, 218, 59, 1), /* #DA3B01 */
Color.FromArgb(255, 202, 80, 16), /* #CA5010 */
Color.FromArgb(255, 247, 99, 12), /* #F7630C */
Color.FromArgb(255, 255, 140, 0), /* #FF8C00 */
Color.FromArgb(255, 255, 185, 0), /* #FFB900 */
},
{
Color.FromArgb(255, 231, 72, 86), /* #E74856 */
Color.FromArgb(255, 232, 17, 35), /* #E81123 */
Color.FromArgb(255, 234, 0, 94), /* #EA005E */
Color.FromArgb(255, 195, 0, 82), /* #C30052 */
Color.FromArgb(255, 227, 0, 140), /* #E3008C */
Color.FromArgb(255, 191, 0, 119), /* #BF0077 */
Color.FromArgb(255, 194, 57, 179), /* #C239B3 */
Color.FromArgb(255, 154, 0, 137), /* #9A0089 */
},
{
Color.FromArgb(255, 0, 120, 215), /* #0078D7 */
Color.FromArgb(255, 0, 99, 177), /* #0063B1 */
Color.FromArgb(255, 142, 140, 216), /* #8E8CD8 */
Color.FromArgb(255, 107, 105, 214), /* #6B69D6 */
Color.FromArgb(255, 135, 100, 184), /* #8764B8 */
Color.FromArgb(255, 116, 77, 169), /* #744DA9 */
Color.FromArgb(255, 177, 70, 194), /* #B146C2 */
Color.FromArgb(255, 136, 23, 152), /* #881798 */
},
{
Color.FromArgb(255, 0, 153, 188), /* #0099BC */
Color.FromArgb(255, 45, 125, 154), /* #2D7D9A */
Color.FromArgb(255, 0, 183, 195), /* #00B7C3 */
Color.FromArgb(255, 3, 131, 135), /* #038387 */
Color.FromArgb(255, 0, 178, 148), /* #00B294 */
Color.FromArgb(255, 1, 133, 116), /* #018574 */
Color.FromArgb(255, 0, 204, 106), /* #00CC6A */
Color.FromArgb(255, 16, 137, 62), /* #10893E */
},
{
Color.FromArgb(255, 122, 117, 116), /* #7A7574 */
Color.FromArgb(255, 93, 90, 80), /* #5D5A58 */
Color.FromArgb(255, 104, 118, 138), /* #68768A */
Color.FromArgb(255, 81, 92, 107), /* #515C6B */
Color.FromArgb(255, 86, 124, 115), /* #567C73 */
Color.FromArgb(255, 72, 104, 96), /* #486860 */
Color.FromArgb(255, 73, 130, 5), /* #498205 */
Color.FromArgb(255, 16, 124, 16), /* #107C10 */
},
{
Color.FromArgb(255, 118, 118, 118), /* #767676 */
Color.FromArgb(255, 76, 74, 72), /* #4C4A48 */
Color.FromArgb(255, 105, 121, 126), /* #69797E */
Color.FromArgb(255, 74, 84, 89), /* #4A5459 */
Color.FromArgb(255, 100, 124, 100), /* #647C64 */
Color.FromArgb(255, 82, 94, 84), /* #525E54 */
Color.FromArgb(255, 132, 117, 69), /* #847545 */
Color.FromArgb(255, 126, 115, 95), /* #7E735F */
}
};
/// <summary>
/// Gets the total number of colors in this palette.
/// A color is not necessarily a single value and may be composed of several shades.
/// This has little meaning in this palette as colors are not strictly separated.
/// </summary>
/// <inheritdoc path="/remarks"/>
public int ColorCount
{
get => colorChart.GetLength(0);
}
/// <summary>
/// Gets the total number of shades for each color in this palette.
/// Shades are usually a variation of the color lightening or darkening it.
/// This has little meaning in this palette as colors are not strictly separated by shade.
/// </summary>
/// <inheritdoc path="/remarks"/>
public int ShadeCount
{
get => colorChart.GetLength(1);
}
/// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex)
{
return colorChart[
MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1),
MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)];
}
}
}

38
src/Avalonia.Controls.ColorPicker/ColorPalettes/IColorPalette.cs

@ -0,0 +1,38 @@
using Avalonia.Media;
namespace Avalonia.Controls
{
/// <summary>
/// Interface to define a color palette.
/// </summary>
public interface IColorPalette
{
/// <summary>
/// Gets the total number of colors in this palette.
/// A color is not necessarily a single value and may be composed of several shades.
/// </summary>
/// <remarks>
/// Represents total columns in a table.
/// </remarks>
int ColorCount { get; }
/// <summary>
/// Gets the total number of shades for each color in this palette.
/// Shades are usually a variation of the color lightening or darkening it.
/// </summary>
/// <remarks>
/// Represents total rows in a table.
/// </remarks>
int ShadeCount { get; }
/// <summary>
/// Gets a color in the palette by index.
/// </summary>
/// <param name="colorIndex">The index of the color in the palette.
/// The index must be between zero and <see cref="ColorCount"/>.</param>
/// <param name="shadeIndex">The index of the color shade in the palette.
/// The index must be between zero and <see cref="ShadeCount"/>.</param>
/// <returns>The color at the specified index or an exception.</returns>
Color GetColor(int colorIndex, int shadeIndex);
}
}

302
src/Avalonia.Controls.ColorPicker/ColorPalettes/SixteenColorPalette.cs

@ -0,0 +1,302 @@
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Implements the standard sixteen color palette from the HTML 4.01 specification.
/// </summary>
/// <remarks>
/// See https://en.wikipedia.org/wiki/Web_colors#HTML_color_names.
/// </remarks>
public class SixteenColorPalette : IColorPalette
{
// The 16 standard colors from HTML and early Windows computers
// https://en.wikipedia.org/wiki/List_of_software_palettes
// https://en.wikipedia.org/wiki/Web_colors#HTML_color_names
private static Color[,] colorChart = new Color[,]
{
{
Colors.White,
Colors.Silver
},
{
Colors.Gray,
Colors.Black
},
{
Colors.Red,
Colors.Maroon
},
{
Colors.Yellow,
Colors.Olive
},
{
Colors.Lime,
Colors.Green
},
{
Colors.Aqua,
Colors.Teal
},
{
Colors.Blue,
Colors.Navy
},
{
Colors.Fuchsia,
Colors.Purple
}
};
/// <summary>
/// Gets the index of the default shade of colors in this palette.
/// </summary>
public const int DefaultShadeIndex = 0;
/// <summary>
/// The index in the color palette of the 'White' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int WhiteIndex = 0;
/// <summary>
/// The index in the color palette of the 'Silver' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int SilverIndex = 1;
/// <summary>
/// The index in the color palette of the 'Gray' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int GrayIndex = 2;
/// <summary>
/// The index in the color palette of the 'Black' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BlackIndex = 3;
/// <summary>
/// The index in the color palette of the 'Red' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int RedIndex = 4;
/// <summary>
/// The index in the color palette of the 'Maroon' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int MaroonIndex = 5;
/// <summary>
/// The index in the color palette of the 'Yellow' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int YellowIndex = 6;
/// <summary>
/// The index in the color palette of the 'Olive' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int OliveIndex = 7;
/// <summary>
/// The index in the color palette of the 'Lime' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int LimeIndex = 8;
/// <summary>
/// The index in the color palette of the 'Green' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int GreenIndex = 9;
/// <summary>
/// The index in the color palette of the 'Aqua' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int AquaIndex = 10;
/// <summary>
/// The index in the color palette of the 'Teal' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int TealIndex = 11;
/// <summary>
/// The index in the color palette of the 'Blue' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int BlueIndex = 12;
/// <summary>
/// The index in the color palette of the 'Navy' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int NavyIndex = 13;
/// <summary>
/// The index in the color palette of the 'Fuchsia' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int FuchsiaIndex = 14;
/// <summary>
/// The index in the color palette of the 'Purple' color.
/// This index can correspond to multiple color shades.
/// </summary>
public const int PurpleIndex = 15;
/// <inheritdoc/>
public int ColorCount
{
get => colorChart.GetLength(0);
}
/// <inheritdoc/>
public int ShadeCount
{
get => colorChart.GetLength(1);
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFFFFFF.
/// </summary>
public static Color White
{
get => colorChart[WhiteIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFC0C0C0.
/// </summary>
public static Color Silver
{
get => colorChart[SilverIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF808080.
/// </summary>
public static Color Gray
{
get => colorChart[GrayIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF000000.
/// </summary>
public static Color Black
{
get => colorChart[BlackIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFF0000.
/// </summary>
public static Color Red
{
get => colorChart[RedIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF800000.
/// </summary>
public static Color Maroon
{
get => colorChart[MaroonIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFFFF00.
/// </summary>
public static Color Yellow
{
get => colorChart[YellowIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF808000.
/// </summary>
public static Color Olive
{
get => colorChart[OliveIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF00FF00.
/// </summary>
public static Color Lime
{
get => colorChart[LimeIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF008000.
/// </summary>
public static Color Green
{
get => colorChart[GreenIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF00FFFF.
/// </summary>
public static Color Aqua
{
get => colorChart[AquaIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF008080.
/// </summary>
public static Color Teal
{
get => colorChart[TealIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF0000FF.
/// </summary>
public static Color Blue
{
get => colorChart[BlueIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF000080.
/// </summary>
public static Color Navy
{
get => colorChart[NavyIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FFFF00FF.
/// </summary>
public static Color Fuchsia
{
get => colorChart[FuchsiaIndex, DefaultShadeIndex];
}
/// <summary>
/// Gets the palette defined color that has an ARGB value of #FF800080.
/// </summary>
public static Color Purple
{
get => colorChart[PurpleIndex, DefaultShadeIndex];
}
/// <inheritdoc/>
public Color GetColor(int colorIndex, int shadeIndex)
{
return colorChart[
MathUtilities.Clamp(colorIndex, 0, colorChart.GetLength(0) - 1),
MathUtilities.Clamp(shadeIndex, 0, colorChart.GetLength(1) - 1)];
}
}
}

19
src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs

@ -0,0 +1,19 @@
namespace Avalonia.Controls
{
/// <summary>
/// Presents a color for user editing using a spectrum, palette and component sliders within a drop down.
/// Editing is available when the drop down flyout is opened; otherwise, only the preview color is shown.
/// </summary>
public class ColorPicker : ColorView
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorPicker"/> class.
/// </summary>
public ColorPicker() : base()
{
// Completely ignore property changes here
// The ColorView in the control template is responsible to manage this
base.ignorePropertyChanged = true;
}
}
}

14
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.Properties.cs

@ -16,11 +16,11 @@ namespace Avalonia.Controls.Primitives
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Defines the <see cref="ShowAccentColors"/> property. /// Defines the <see cref="IsAccentColorsVisible"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> ShowAccentColorsProperty = public static readonly StyledProperty<bool> IsAccentColorsVisibleProperty =
AvaloniaProperty.Register<ColorPreviewer, bool>( AvaloniaProperty.Register<ColorPreviewer, bool>(
nameof(ShowAccentColors), nameof(IsAccentColorsVisible),
true); true);
/// <summary> /// <summary>
@ -38,13 +38,13 @@ namespace Avalonia.Controls.Primitives
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether accent colors are shown along /// Gets or sets a value indicating whether accent colors are visible along
/// with the preview color. /// with the preview color.
/// </summary> /// </summary>
public bool ShowAccentColors public bool IsAccentColorsVisible
{ {
get => GetValue(ShowAccentColorsProperty); get => GetValue(IsAccentColorsVisibleProperty);
set => SetValue(ShowAccentColorsProperty, value); set => SetValue(IsAccentColorsVisibleProperty, value);
} }
} }
} }

53
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs

@ -10,10 +10,10 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Presents a preview color with optional accent colors. /// Presents a preview color with optional accent colors.
/// </summary> /// </summary>
[TemplatePart(Name = nameof(AccentDec1Border), Type = typeof(Border))] [TemplatePart("PART_AccentDecrement1Border", typeof(Border))]
[TemplatePart(Name = nameof(AccentDec2Border), Type = typeof(Border))] [TemplatePart("PART_AccentDecrement2Border", typeof(Border))]
[TemplatePart(Name = nameof(AccentInc1Border), Type = typeof(Border))] [TemplatePart("PART_AccentIncrement1Border", typeof(Border))]
[TemplatePart(Name = nameof(AccentInc2Border), Type = typeof(Border))] [TemplatePart("PART_AccentIncrement2Border", typeof(Border))]
public partial class ColorPreviewer : TemplatedControl public partial class ColorPreviewer : TemplatedControl
{ {
/// <summary> /// <summary>
@ -24,10 +24,11 @@ namespace Avalonia.Controls.Primitives
private bool eventsConnected = false; private bool eventsConnected = false;
private Border? AccentDec1Border; // XAML template parts
private Border? AccentDec2Border; private Border? _accentDecrement1Border;
private Border? AccentInc1Border; private Border? _accentDecrement2Border;
private Border? AccentInc2Border; private Border? _accentIncrement1Border;
private Border? _accentIncrement2Border;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColorPreviewer"/> class. /// Initializes a new instance of the <see cref="ColorPreviewer"/> class.
@ -45,20 +46,20 @@ namespace Avalonia.Controls.Primitives
if (connected == true && eventsConnected == false) if (connected == true && eventsConnected == false)
{ {
// Add all events // Add all events
if (AccentDec1Border != null) { AccentDec1Border.PointerPressed += AccentBorder_PointerPressed; } if (_accentDecrement1Border != null) { _accentDecrement1Border.PointerPressed += AccentBorder_PointerPressed; }
if (AccentDec2Border != null) { AccentDec2Border.PointerPressed += AccentBorder_PointerPressed; } if (_accentDecrement2Border != null) { _accentDecrement2Border.PointerPressed += AccentBorder_PointerPressed; }
if (AccentInc1Border != null) { AccentInc1Border.PointerPressed += AccentBorder_PointerPressed; } if (_accentIncrement1Border != null) { _accentIncrement1Border.PointerPressed += AccentBorder_PointerPressed; }
if (AccentInc2Border != null) { AccentInc2Border.PointerPressed += AccentBorder_PointerPressed; } if (_accentIncrement2Border != null) { _accentIncrement2Border.PointerPressed += AccentBorder_PointerPressed; }
eventsConnected = true; eventsConnected = true;
} }
else if (connected == false && eventsConnected == true) else if (connected == false && eventsConnected == true)
{ {
// Remove all events // Remove all events
if (AccentDec1Border != null) { AccentDec1Border.PointerPressed -= AccentBorder_PointerPressed; } if (_accentDecrement1Border != null) { _accentDecrement1Border.PointerPressed -= AccentBorder_PointerPressed; }
if (AccentDec2Border != null) { AccentDec2Border.PointerPressed -= AccentBorder_PointerPressed; } if (_accentDecrement2Border != null) { _accentDecrement2Border.PointerPressed -= AccentBorder_PointerPressed; }
if (AccentInc1Border != null) { AccentInc1Border.PointerPressed -= AccentBorder_PointerPressed; } if (_accentIncrement1Border != null) { _accentIncrement1Border.PointerPressed -= AccentBorder_PointerPressed; }
if (AccentInc2Border != null) { AccentInc2Border.PointerPressed -= AccentBorder_PointerPressed; } if (_accentIncrement2Border != null) { _accentIncrement2Border.PointerPressed -= AccentBorder_PointerPressed; }
eventsConnected = false; eventsConnected = false;
} }
@ -70,10 +71,10 @@ namespace Avalonia.Controls.Primitives
// Remove any existing events present if the control was previously loaded then unloaded // Remove any existing events present if the control was previously loaded then unloaded
ConnectEvents(false); ConnectEvents(false);
AccentDec1Border = e.NameScope.Find<Border>(nameof(AccentDec1Border)); _accentDecrement1Border = e.NameScope.Find<Border>("PART_AccentDecrement1Border");
AccentDec2Border = e.NameScope.Find<Border>(nameof(AccentDec2Border)); _accentDecrement2Border = e.NameScope.Find<Border>("PART_AccentDecrement2Border");
AccentInc1Border = e.NameScope.Find<Border>(nameof(AccentInc1Border)); _accentIncrement1Border = e.NameScope.Find<Border>("PART_AccentIncrement1Border");
AccentInc2Border = e.NameScope.Find<Border>(nameof(AccentInc2Border)); _accentIncrement2Border = e.NameScope.Find<Border>("PART_AccentIncrement2Border");
// Must connect after controls are found // Must connect after controls are found
ConnectEvents(true); ConnectEvents(true);
@ -116,15 +117,15 @@ namespace Avalonia.Controls.Primitives
// Get the value component delta // Get the value component delta
try try
{ {
accentStep = int.Parse(border?.Tag?.ToString() ?? "", CultureInfo.InvariantCulture); accentStep = int.Parse(border?.Tag?.ToString() ?? "0", CultureInfo.InvariantCulture);
} }
catch { } catch { }
HsvColor newHsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep); if (accentStep != 0)
HsvColor oldHsvColor = HsvColor; {
// ColorChanged will be invoked in OnPropertyChanged if the value is different
HsvColor = newHsvColor; HsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep);
OnColorChanged(new ColorChangedEventArgs(oldHsvColor.ToRgb(), newHsvColor.ToRgb())); }
} }
} }
} }

20
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs

@ -49,12 +49,12 @@ namespace Avalonia.Controls.Primitives
true); true);
/// <summary> /// <summary>
/// Defines the <see cref="IsAutoUpdatingEnabled"/> property. /// Defines the <see cref="IsRoundingEnabled"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsAutoUpdatingEnabledProperty = public static readonly StyledProperty<bool> IsRoundingEnabledProperty =
AvaloniaProperty.Register<ColorSlider, bool>( AvaloniaProperty.Register<ColorSlider, bool>(
nameof(IsAutoUpdatingEnabled), nameof(IsRoundingEnabled),
true); false);
/// <summary> /// <summary>
/// Defines the <see cref="IsSaturationValueMaxForced"/> property. /// Defines the <see cref="IsSaturationValueMaxForced"/> property.
@ -120,16 +120,16 @@ namespace Avalonia.Controls.Primitives
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether automatic background and foreground updates will be /// Gets or sets a value indicating whether rounding of color component values is enabled.
/// calculated when the set color changes.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This can be disabled for performance reasons when working with multiple sliders. /// This is applicable for the HSV color model only. The <see cref="Media.HsvColor"/> struct uses double
/// values while the <see cref="Media.Color"/> struct uses byte. Only double types need rounding.
/// </remarks> /// </remarks>
public bool IsAutoUpdatingEnabled public bool IsRoundingEnabled
{ {
get => GetValue(IsAutoUpdatingEnabledProperty); get => GetValue(IsRoundingEnabledProperty);
set => SetValue(IsAutoUpdatingEnabledProperty, value); set => SetValue(IsRoundingEnabledProperty, value);
} }
/// <summary> /// <summary>

109
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@ -20,8 +20,16 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public event EventHandler<ColorChangedEventArgs>? ColorChanged; public event EventHandler<ColorChangedEventArgs>? ColorChanged;
private const double MaxHue = 359.99999999999999999; // 17 decimal places /// <summary>
private bool disableUpdates = false; /// Defines the maximum hue component value
/// (other components are always 0..100 or 0.255).
/// </summary>
/// <remarks>
/// This should match the default <see cref="ColorSpectrum.MaxHue"/> property.
/// </remarks>
private const double MaxHue = 359;
protected bool ignorePropertyChanged = false;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColorSlider"/> class. /// Initializes a new instance of the <see cref="ColorSlider"/> class.
@ -107,21 +115,41 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Rounds the component values of the given <see cref="HsvColor"/>.
/// This is useful for user-display and to ensure a color matches user selection exactly.
/// </summary>
/// <param name="hsvColor">The <see cref="HsvColor"/> to round component values for.</param>
/// <returns>A new <see cref="HsvColor"/> with rounded component values.</returns>
private HsvColor RoundComponentValues(HsvColor hsvColor)
{
return new HsvColor(
Math.Round(hsvColor.A, 2, MidpointRounding.AwayFromZero),
Math.Round(hsvColor.H, 0, MidpointRounding.AwayFromZero),
Math.Round(hsvColor.S, 2, MidpointRounding.AwayFromZero),
Math.Round(hsvColor.V, 2, MidpointRounding.AwayFromZero));
}
/// <summary> /// <summary>
/// Updates the slider property values by applying the current color. /// Updates the slider property values by applying the current color.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Warning: This will trigger property changed updates. /// Warning: This will trigger property changed updates.
/// Consider using <see cref="disableUpdates"/> externally. /// Consider using <see cref="ignorePropertyChanged"/> externally.
/// </remarks> /// </remarks>
private void SetColorToSliderValues() private void SetColorToSliderValues()
{ {
var hsvColor = HsvColor;
var rgbColor = Color;
var component = ColorComponent; var component = ColorComponent;
if (ColorModel == ColorModel.Hsva) if (ColorModel == ColorModel.Hsva)
{ {
var hsvColor = HsvColor;
if (IsRoundingEnabled)
{
hsvColor = RoundComponentValues(hsvColor);
}
// Note: Components converted into a usable range for the user // Note: Components converted into a usable range for the user
switch (component) switch (component)
{ {
@ -149,6 +177,8 @@ namespace Avalonia.Controls.Primitives
} }
else else
{ {
var rgbColor = Color;
switch (component) switch (component)
{ {
case ColorComponent.Alpha: case ColorComponent.Alpha:
@ -183,13 +213,12 @@ namespace Avalonia.Controls.Primitives
HsvColor hsvColor = new HsvColor(); HsvColor hsvColor = new HsvColor();
Color rgbColor = new Color(); Color rgbColor = new Color();
double sliderPercent = Value / (Maximum - Minimum); double sliderPercent = Value / (Maximum - Minimum);
var baseHsvColor = HsvColor;
var baseRgbColor = Color;
var component = ColorComponent; var component = ColorComponent;
if (ColorModel == ColorModel.Hsva) if (ColorModel == ColorModel.Hsva)
{ {
var baseHsvColor = HsvColor;
switch (component) switch (component)
{ {
case ColorComponent.Alpha: case ColorComponent.Alpha:
@ -214,10 +243,12 @@ namespace Avalonia.Controls.Primitives
} }
} }
return (hsvColor.ToRgb(), hsvColor); rgbColor = hsvColor.ToRgb();
} }
else else
{ {
var baseRgbColor = Color;
byte componentValue = Convert.ToByte(MathUtilities.Clamp(sliderPercent * 255, 0, 255)); byte componentValue = Convert.ToByte(MathUtilities.Clamp(sliderPercent * 255, 0, 255));
switch (component) switch (component)
@ -236,8 +267,15 @@ namespace Avalonia.Controls.Primitives
break; break;
} }
return (rgbColor, rgbColor.ToHsv()); hsvColor = rgbColor.ToHsv();
} }
if (IsRoundingEnabled)
{
hsvColor = RoundComponentValues(hsvColor);
}
return (rgbColor, hsvColor);
} }
/// <summary> /// <summary>
@ -306,7 +344,7 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
if (disableUpdates) if (ignorePropertyChanged)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
return; return;
@ -315,54 +353,59 @@ namespace Avalonia.Controls.Primitives
// Always keep the two color properties in sync // Always keep the two color properties in sync
if (change.Property == ColorProperty) if (change.Property == ColorProperty)
{ {
disableUpdates = true; ignorePropertyChanged = true;
HsvColor = Color.ToHsv(); HsvColor = Color.ToHsv();
if (IsAutoUpdatingEnabled) SetColorToSliderValues();
{ UpdateBackground();
SetColorToSliderValues();
UpdateBackground();
}
UpdatePseudoClasses(); UpdatePseudoClasses();
OnColorChanged(new ColorChangedEventArgs( OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<Color>(), change.GetOldValue<Color>(),
change.GetNewValue<Color>())); change.GetNewValue<Color>()));
disableUpdates = false; ignorePropertyChanged = false;
}
else if (change.Property == ColorModelProperty)
{
ignorePropertyChanged = true;
SetColorToSliderValues();
UpdateBackground();
UpdatePseudoClasses();
ignorePropertyChanged = false;
} }
else if (change.Property == HsvColorProperty) else if (change.Property == HsvColorProperty)
{ {
disableUpdates = true; ignorePropertyChanged = true;
Color = HsvColor.ToRgb(); Color = HsvColor.ToRgb();
if (IsAutoUpdatingEnabled) SetColorToSliderValues();
{ UpdateBackground();
SetColorToSliderValues();
UpdateBackground();
}
UpdatePseudoClasses(); UpdatePseudoClasses();
OnColorChanged(new ColorChangedEventArgs( OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<HsvColor>().ToRgb(), change.GetOldValue<HsvColor>().ToRgb(),
change.GetNewValue<HsvColor>().ToRgb())); change.GetNewValue<HsvColor>().ToRgb()));
disableUpdates = false; ignorePropertyChanged = false;
}
else if (change.Property == IsRoundingEnabledProperty)
{
SetColorToSliderValues();
} }
else if (change.Property == BoundsProperty) else if (change.Property == BoundsProperty)
{ {
if (IsAutoUpdatingEnabled) UpdateBackground();
{
UpdateBackground();
}
} }
else if (change.Property == ValueProperty || else if (change.Property == ValueProperty ||
change.Property == MinimumProperty || change.Property == MinimumProperty ||
change.Property == MaximumProperty) change.Property == MaximumProperty)
{ {
disableUpdates = true; ignorePropertyChanged = true;
Color oldColor = Color; Color oldColor = Color;
(var color, var hsvColor) = GetColorFromSliderValues(); (var color, var hsvColor) = GetColorFromSliderValues();
@ -381,7 +424,7 @@ namespace Avalonia.Controls.Primitives
UpdatePseudoClasses(); UpdatePseudoClasses();
OnColorChanged(new ColorChangedEventArgs(oldColor, Color)); OnColorChanged(new ColorChangedEventArgs(oldColor, Color));
disableUpdates = false; ignorePropertyChanged = false;
} }
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

2
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs

@ -97,7 +97,7 @@ namespace Avalonia.Controls.Primitives
/// Gets or sets the currently selected color in the RGB color model. /// Gets or sets the currently selected color in the RGB color model.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// For control authors use <see cref="HsvColor"/> instead to avoid loss /// For control authors, use <see cref="HsvColor"/> instead to avoid loss
/// of precision and color drifting. /// of precision and color drifting.
/// </remarks> /// </remarks>
public Color Color public Color Color

3
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@ -44,7 +44,6 @@ namespace Avalonia.Controls.Primitives
private bool _updatingColor = false; private bool _updatingColor = false;
private bool _updatingHsvColor = false; private bool _updatingHsvColor = false;
private bool _isPointerOver = false;
private bool _isPointerPressed = false; private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false; private bool _shouldShowLargeSelection = false;
private List<Hsv> _hsvValues = new List<Hsv>(); private List<Hsv> _hsvValues = new List<Hsv>();
@ -851,7 +850,6 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc cref="InputElement.PointerEntered"/> /// <inheritdoc cref="InputElement.PointerEntered"/>
private void InputTarget_PointerEntered(object? sender, PointerEventArgs args) private void InputTarget_PointerEntered(object? sender, PointerEventArgs args)
{ {
_isPointerOver = true;
UpdatePseudoClasses(); UpdatePseudoClasses();
args.Handled = true; args.Handled = true;
} }
@ -859,7 +857,6 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc cref="InputElement.PointerExited"/> /// <inheritdoc cref="InputElement.PointerExited"/>
private void InputTarget_PointerExited(object? sender, PointerEventArgs args) private void InputTarget_PointerExited(object? sender, PointerEventArgs args)
{ {
_isPointerOver = false;
UpdatePseudoClasses(); UpdatePseudoClasses();
args.Handled = true; args.Handled = true;
} }

495
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs

@ -0,0 +1,495 @@
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Media;
namespace Avalonia.Controls
{
/// <inheritdoc/>
public partial class ColorView
{
/// <summary>
/// Defines the <see cref="Color"/> property.
/// </summary>
public static readonly StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<ColorView, Color>(
nameof(Color),
Colors.White,
defaultBindingMode: BindingMode.TwoWay,
coerce: CoerceColor) ;
/// <summary>
/// Defines the <see cref="ColorModel"/> property.
/// </summary>
public static readonly StyledProperty<ColorModel> ColorModelProperty =
AvaloniaProperty.Register<ColorView, ColorModel>(
nameof(ColorModel),
ColorModel.Rgba);
/// <summary>
/// Defines the <see cref="ColorSpectrumComponents"/> property.
/// </summary>
public static readonly StyledProperty<ColorSpectrumComponents> ColorSpectrumComponentsProperty =
AvaloniaProperty.Register<ColorView, ColorSpectrumComponents>(
nameof(ColorSpectrumComponents),
ColorSpectrumComponents.HueSaturation);
/// <summary>
/// Defines the <see cref="ColorSpectrumShape"/> property.
/// </summary>
public static readonly StyledProperty<ColorSpectrumShape> ColorSpectrumShapeProperty =
AvaloniaProperty.Register<ColorView, ColorSpectrumShape>(
nameof(ColorSpectrumShape),
ColorSpectrumShape.Box);
/// <summary>
/// Defines the <see cref="HsvColor"/> property.
/// </summary>
public static readonly StyledProperty<HsvColor> HsvColorProperty =
AvaloniaProperty.Register<ColorView, HsvColor>(
nameof(HsvColor),
Colors.White.ToHsv(),
defaultBindingMode: BindingMode.TwoWay,
coerce: CoerceHsvColor);
/// <summary>
/// Defines the <see cref="IsAccentColorsVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAccentColorsVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsAccentColorsVisible),
true);
/// <summary>
/// Defines the <see cref="IsAlphaEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAlphaEnabledProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsAlphaEnabled),
true);
/// <summary>
/// Defines the <see cref="IsAlphaVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsAlphaVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsAlphaVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorComponentsVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorComponentsVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorComponentsVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorModelVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorModelVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorModelVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorPaletteVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorPaletteVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorPaletteVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorPreviewVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorPreviewVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorPreviewVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorSpectrumVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorSpectrumVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorSpectrumVisible),
true);
/// <summary>
/// Defines the <see cref="IsColorSpectrumSliderVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsColorSpectrumSliderVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsColorSpectrumSliderVisible),
true);
/// <summary>
/// Defines the <see cref="IsComponentSliderVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsComponentSliderVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsComponentSliderVisible),
true);
/// <summary>
/// Defines the <see cref="IsComponentTextInputVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsComponentTextInputVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsComponentTextInputVisible),
true);
/// <summary>
/// Defines the <see cref="IsHexInputVisible"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsHexInputVisibleProperty =
AvaloniaProperty.Register<ColorView, bool>(
nameof(IsHexInputVisible),
true);
/// <summary>
/// Defines the <see cref="MaxHue"/> property.
/// </summary>
public static readonly StyledProperty<int> MaxHueProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MaxHue),
359);
/// <summary>
/// Defines the <see cref="MaxSaturation"/> property.
/// </summary>
public static readonly StyledProperty<int> MaxSaturationProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MaxSaturation),
100);
/// <summary>
/// Defines the <see cref="MaxValue"/> property.
/// </summary>
public static readonly StyledProperty<int> MaxValueProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MaxValue),
100);
/// <summary>
/// Defines the <see cref="MinHue"/> property.
/// </summary>
public static readonly StyledProperty<int> MinHueProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MinHue),
0);
/// <summary>
/// Defines the <see cref="MinSaturation"/> property.
/// </summary>
public static readonly StyledProperty<int> MinSaturationProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MinSaturation),
0);
/// <summary>
/// Defines the <see cref="MinValue"/> property.
/// </summary>
public static readonly StyledProperty<int> MinValueProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(MinValue),
0);
/// <summary>
/// Defines the <see cref="PaletteColors"/> property.
/// </summary>
public static readonly StyledProperty<IEnumerable<Color>?> PaletteColorsProperty =
AvaloniaProperty.Register<ColorView, IEnumerable<Color>?>(
nameof(PaletteColors),
null);
/// <summary>
/// Defines the <see cref="PaletteColumnCount"/> property.
/// </summary>
public static readonly StyledProperty<int> PaletteColumnCountProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(PaletteColumnCount),
4);
/// <summary>
/// Defines the <see cref="Palette"/> property.
/// </summary>
public static readonly StyledProperty<IColorPalette?> PaletteProperty =
AvaloniaProperty.Register<ColorView, IColorPalette?>(
nameof(Palette),
null);
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
public static readonly StyledProperty<int> SelectedIndexProperty =
AvaloniaProperty.Register<ColorView, int>(
nameof(SelectedIndex),
(int)ColorViewTab.Spectrum);
/// <inheritdoc cref="ColorSpectrum.Color"/>
public Color Color
{
get => GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <inheritdoc cref="ColorSlider.ColorModel"/>
/// <remarks>
/// This property is only applicable to the components tab.
/// The spectrum tab must always be in HSV and the palette tab contains only pre-defined colors.
/// </remarks>
public ColorModel ColorModel
{
get => GetValue(ColorModelProperty);
set => SetValue(ColorModelProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.Components"/>
public ColorSpectrumComponents ColorSpectrumComponents
{
get => GetValue(ColorSpectrumComponentsProperty);
set => SetValue(ColorSpectrumComponentsProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.Shape"/>
public ColorSpectrumShape ColorSpectrumShape
{
get => GetValue(ColorSpectrumShapeProperty);
set => SetValue(ColorSpectrumShapeProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.HsvColor"/>
public HsvColor HsvColor
{
get => GetValue(HsvColorProperty);
set => SetValue(HsvColorProperty, value);
}
/// <inheritdoc cref="ColorPreviewer.IsAccentColorsVisible"/>
public bool IsAccentColorsVisible
{
get => GetValue(IsAccentColorsVisibleProperty);
set => SetValue(IsAccentColorsVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the alpha component is enabled.
/// When disabled (set to false) the alpha component will be fixed to maximum and
/// editing controls disabled.
/// </summary>
public bool IsAlphaEnabled
{
get => GetValue(IsAlphaEnabledProperty);
set => SetValue(IsAlphaEnabledProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the alpha component editing controls
/// (Slider(s) and TextBox) are visible. When hidden, the existing alpha component
/// value is maintained.
/// </summary>
/// <remarks>
/// Note that <see cref="IsComponentTextInputVisible"/> also controls the alpha
/// component TextBox visibility.
/// </remarks>
public bool IsAlphaVisible
{
get => GetValue(IsAlphaVisibleProperty);
set => SetValue(IsAlphaVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the color components tab/panel/page (subview) is visible.
/// </summary>
public bool IsColorComponentsVisible
{
get => GetValue(IsColorComponentsVisibleProperty);
set => SetValue(IsColorComponentsVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the active color model indicator/selector is visible.
/// </summary>
public bool IsColorModelVisible
{
get => GetValue(IsColorModelVisibleProperty);
set => SetValue(IsColorModelVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the color palette tab/panel/page (subview) is visible.
/// </summary>
public bool IsColorPaletteVisible
{
get => GetValue(IsColorPaletteVisibleProperty);
set => SetValue(IsColorPaletteVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the color preview is visible.
/// </summary>
/// <remarks>
/// Note that accent color visibility is controlled separately by
/// <see cref="IsAccentColorsVisible"/>.
/// </remarks>
public bool IsColorPreviewVisible
{
get => GetValue(IsColorPreviewVisibleProperty);
set => SetValue(IsColorPreviewVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the color spectrum tab/panel/page (subview) is visible.
/// </summary>
public bool IsColorSpectrumVisible
{
get => GetValue(IsColorSpectrumVisibleProperty);
set => SetValue(IsColorSpectrumVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the color spectrum's third component slider
/// is visible.
/// </summary>
public bool IsColorSpectrumSliderVisible
{
get => GetValue(IsColorSpectrumSliderVisibleProperty);
set => SetValue(IsColorSpectrumSliderVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether color component sliders are visible.
/// </summary>
/// <remarks>
/// All color components are controlled by this property but alpha can also be
/// controlled with <see cref="IsAlphaVisible"/>.
/// </remarks>
public bool IsComponentSliderVisible
{
get => GetValue(IsComponentSliderVisibleProperty);
set => SetValue(IsComponentSliderVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether color component text inputs are visible.
/// </summary>
/// <remarks>
/// All color components are controlled by this property but alpha can also be
/// controlled with <see cref="IsAlphaVisible"/>.
/// </remarks>
public bool IsComponentTextInputVisible
{
get => GetValue(IsComponentTextInputVisibleProperty);
set => SetValue(IsComponentTextInputVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the hexadecimal color value text input
/// is visible.
/// </summary>
public bool IsHexInputVisible
{
get => GetValue(IsHexInputVisibleProperty);
set => SetValue(IsHexInputVisibleProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MaxHue"/>
public int MaxHue
{
get => GetValue(MaxHueProperty);
set => SetValue(MaxHueProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MaxSaturation"/>
public int MaxSaturation
{
get => GetValue(MaxSaturationProperty);
set => SetValue(MaxSaturationProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MaxValue"/>
public int MaxValue
{
get => GetValue(MaxValueProperty);
set => SetValue(MaxValueProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MinHue"/>
public int MinHue
{
get => GetValue(MinHueProperty);
set => SetValue(MinHueProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MinSaturation"/>
public int MinSaturation
{
get => GetValue(MinSaturationProperty);
set => SetValue(MinSaturationProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.MinValue"/>
public int MinValue
{
get => GetValue(MinValueProperty);
set => SetValue(MinValueProperty, value);
}
/// <summary>
/// Gets or sets the collection of individual colors in the palette.
/// </summary>
/// <remarks>
/// This is not commonly set manually. Instead, it should be set automatically by
/// providing an <see cref="IColorPalette"/> to the <see cref="Palette"/> property.
/// <br/><br/>
/// Also note that this property is what should be bound in the control template.
/// <see cref="Palette"/> is too high-level to use on its own.
/// </remarks>
public IEnumerable<Color>? PaletteColors
{
get => GetValue(PaletteColorsProperty);
set => SetValue(PaletteColorsProperty, value);
}
/// <summary>
/// Gets or sets the number of colors in each row (section) of the color palette.
/// Within a standard palette, rows are shades and columns are colors.
/// </summary>
/// <remarks>
/// This is not commonly set manually. Instead, it should be set automatically by
/// providing an <see cref="IColorPalette"/> to the <see cref="Palette"/> property.
/// <br/><br/>
/// Also note that this property is what should be bound in the control template.
/// <see cref="Palette"/> is too high-level to use on its own.
/// </remarks>
public int PaletteColumnCount
{
get => GetValue(PaletteColumnCountProperty);
set => SetValue(PaletteColumnCountProperty, value);
}
/// <summary>
/// Gets or sets the color palette.
/// </summary>
/// <remarks>
/// This will automatically set both <see cref="PaletteColors"/> and
/// <see cref="PaletteColumnCount"/> overwriting any existing values.
/// </remarks>
public IColorPalette? Palette
{
get => GetValue(PaletteProperty);
set => SetValue(PaletteProperty, value);
}
/// <summary>
/// Gets or sets the index of the selected tab/panel/page (subview).
/// </summary>
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
}
}

379
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs

@ -0,0 +1,379 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using Avalonia.Controls.Converters;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// Presents a color for user editing using a spectrum, palette and component sliders.
/// </summary>
[TemplatePart("PART_HexTextBox", typeof(TextBox))]
[TemplatePart("PART_TabControl", typeof(TabControl))]
public partial class ColorView : TemplatedControl
{
/// <summary>
/// Event for when the selected color changes within the slider.
/// </summary>
public event EventHandler<ColorChangedEventArgs>? ColorChanged;
// XAML template parts
private TextBox? _hexTextBox;
private TabControl? _tabControl;
private ColorToHexConverter colorToHexConverter = new ColorToHexConverter();
protected bool ignorePropertyChanged = false;
/// <summary>
/// Initializes a new instance of the <see cref="ColorView"/> class.
/// </summary>
public ColorView() : base()
{
}
/// <summary>
/// Gets the value of the hex TextBox and sets it as the current <see cref="Color"/>.
/// If invalid, the TextBox hex text will revert back to the last valid color.
/// </summary>
private void GetColorFromHexTextBox()
{
if (_hexTextBox != null)
{
var convertedColor = colorToHexConverter.ConvertBack(_hexTextBox.Text, typeof(Color), null, CultureInfo.CurrentCulture);
if (convertedColor is Color color)
{
Color = color;
}
// Re-apply the hex value
// This ensure the hex color value is always valid and formatted correctly
SetColorToHexTextBox();
}
}
/// <summary>
/// Sets the current <see cref="Color"/> to the hex TextBox.
/// </summary>
private void SetColorToHexTextBox()
{
if (_hexTextBox != null)
{
_hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string;
}
}
/// <summary>
/// Validates the tab/panel/page selection taking into account the visibility of each item
/// as well as the current selection.
/// </summary>
/// <remarks>
/// Derived controls may re-implement this based on their default style / control template
/// and any specialized selection needs.
/// </remarks>
protected virtual void ValidateSelection()
{
if (_tabControl != null &&
_tabControl.Items != null)
{
// Determine the number of visible tab items
int numVisibleItems = 0;
foreach (var item in _tabControl.Items)
{
if (item is Control control &&
control.IsVisible)
{
numVisibleItems++;
}
}
// Verify the selection
if (numVisibleItems > 0)
{
object? selectedItem = null;
if (_tabControl.SelectedItem == null &&
_tabControl.ItemCount > 0)
{
// As a failsafe, forcefully select the first item
foreach (var item in _tabControl.Items)
{
selectedItem = item;
break;
}
}
else
{
selectedItem = _tabControl.SelectedItem;
}
if (selectedItem is Control selectedControl &&
selectedControl.IsVisible == false)
{
// Select the first visible item instead
foreach (var item in _tabControl.Items)
{
if (item is Control control &&
control.IsVisible)
{
selectedItem = item;
break;
}
}
}
_tabControl.SelectedItem = selectedItem;
_tabControl.IsVisible = true;
}
else
{
// Special case when all items are hidden
// If TabControl ever properly supports no selected item /
// all items hidden this can be removed
_tabControl.SelectedItem = null;
_tabControl.IsVisible = false;
}
// Hide the "tab strip" if there is only one tab
// This allows, for example, to view only the palette
/*
var itemsPresenter = _tabControl.FindDescendantOfType<ItemsPresenter>();
if (itemsPresenter != null)
{
if (numVisibleItems == 1)
{
itemsPresenter.IsVisible = false;
}
else
{
itemsPresenter.IsVisible = true;
}
}
*/
// Note that if externally the SelectedIndex is set to 4 or something
// outside the valid range, the TabControl will ignore it and replace it
// with a valid SelectedIndex. This however is not propagated back through
// the TwoWay binding in the control template so the SelectedIndex and
// SelectedIndex become out of sync.
//
// The work-around for this is done here where SelectedIndex is forcefully
// synchronized with whatever the TabControl property value is. This is
// possible since selection validation is already done by this method.
SelectedIndex = _tabControl.SelectedIndex;
}
return;
}
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_hexTextBox != null)
{
_hexTextBox.KeyDown -= HexTextBox_KeyDown;
_hexTextBox.LostFocus -= HexTextBox_LostFocus;
}
_hexTextBox = e.NameScope.Find<TextBox>("PART_HexTextBox");
_tabControl = e.NameScope.Find<TabControl>("PART_TabControl");
SetColorToHexTextBox();
if (_hexTextBox != null)
{
_hexTextBox.KeyDown += HexTextBox_KeyDown;
_hexTextBox.LostFocus += HexTextBox_LostFocus;
}
base.OnApplyTemplate(e);
ValidateSelection();
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (ignorePropertyChanged)
{
base.OnPropertyChanged(change);
return;
}
// Always keep the two color properties in sync
if (change.Property == ColorProperty)
{
ignorePropertyChanged = true;
HsvColor = Color.ToHsv();
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<Color>(),
change.GetNewValue<Color>()));
ignorePropertyChanged = false;
}
else if (change.Property == HsvColorProperty)
{
ignorePropertyChanged = true;
Color = HsvColor.ToRgb();
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<HsvColor>().ToRgb(),
change.GetNewValue<HsvColor>().ToRgb()));
ignorePropertyChanged = false;
}
else if (change.Property == PaletteProperty)
{
IColorPalette? palette = Palette;
// Any custom palette change must be automatically synced with the
// bound properties controlling the palette grid
if (palette != null)
{
PaletteColumnCount = palette.ColorCount;
List<Color> newPaletteColors = new List<Color>();
for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++)
{
for (int colorIndex = 0; colorIndex < palette.ColorCount; colorIndex++)
{
newPaletteColors.Add(palette.GetColor(colorIndex, shadeIndex));
}
}
PaletteColors = newPaletteColors;
}
}
else if (change.Property == IsAlphaEnabledProperty)
{
// Manually coerce the HsvColor value
// (Color will be coerced automatically if HsvColor changes)
HsvColor = OnCoerceHsvColor(HsvColor);
}
else if (change.Property == IsColorComponentsVisibleProperty ||
change.Property == IsColorPaletteVisibleProperty ||
change.Property == IsColorSpectrumVisibleProperty)
{
// When the property changed notification is received here the visibility
// of individual tab items has not yet been updated through the bindings.
// Therefore, the validation is delayed until after bindings update.
Dispatcher.UIThread.Post(() =>
{
ValidateSelection();
}, DispatcherPriority.Background);
}
else if (change.Property == SelectedIndexProperty)
{
// Again, it is necessary to wait for the SelectedIndex value to
// be applied to the TabControl through binding before validation occurs.
Dispatcher.UIThread.Post(() =>
{
ValidateSelection();
}, DispatcherPriority.Background);
}
base.OnPropertyChanged(change);
}
/// <summary>
/// Called before the <see cref="ColorChanged"/> event occurs.
/// </summary>
/// <param name="e">The <see cref="ColorChangedEventArgs"/> defining old/new colors.</param>
protected virtual void OnColorChanged(ColorChangedEventArgs e)
{
ColorChanged?.Invoke(this, e);
}
/// <summary>
/// Called when the <see cref="Color"/> property has to be coerced.
/// </summary>
/// <param name="value">The value to coerce.</param>
protected virtual Color OnCoerceColor(Color value)
{
if (IsAlphaEnabled == false)
{
return new Color(255, value.R, value.G, value.B);
}
return value;
}
/// <summary>
/// Called when the <see cref="HsvColor"/> property has to be coerced.
/// </summary>
/// <param name="value">The value to coerce.</param>
protected virtual HsvColor OnCoerceHsvColor(HsvColor value)
{
if (IsAlphaEnabled == false)
{
return new HsvColor(1.0, value.H, value.S, value.V);
}
return value;
}
/// <summary>
/// Coerces/validates the <see cref="Color"/> property value.
/// </summary>
/// <param name="instance">The <see cref="ColorView"/> instance.</param>
/// <param name="value">The value to coerce.</param>
/// <returns>The coerced/validated value.</returns>
private static Color CoerceColor(IAvaloniaObject instance, Color value)
{
if (instance is ColorView colorView)
{
return colorView.OnCoerceColor(value);
}
return value;
}
/// <summary>
/// Coerces/validates the <see cref="HsvColor"/> property value.
/// </summary>
/// <param name="instance">The <see cref="ColorView"/> instance.</param>
/// <param name="value">The value to coerce.</param>
/// <returns>The coerced/validated value.</returns>
private static HsvColor CoerceHsvColor(IAvaloniaObject instance, HsvColor value)
{
if (instance is ColorView colorView)
{
return colorView.OnCoerceHsvColor(value);
}
return value;
}
/// <summary>
/// Event handler for when a key is pressed within the Hex RGB value TextBox.
/// This is used to trigger re-evaluation of the color based on the TextBox value.
/// </summary>
private void HexTextBox_KeyDown(object? sender, Input.KeyEventArgs e)
{
if (e.Key == Input.Key.Enter)
{
GetColorFromHexTextBox();
}
}
/// <summary>
/// Event handler for when the Hex RGB value TextBox looses focus.
/// This is used to trigger re-evaluation of the color based on the TextBox value.
/// </summary>
private void HexTextBox_LostFocus(object? sender, Interactivity.RoutedEventArgs e)
{
GetColorFromHexTextBox();
}
}
}

26
src/Avalonia.Controls.ColorPicker/ColorView/ColorViewTab.cs

@ -0,0 +1,26 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines a specific tab/page (subview) within the <see cref="ColorView"/>.
/// </summary>
/// <remarks>
/// This is indexed to match the default control template ordering.
/// </remarks>
public enum ColorViewTab
{
/// <summary>
/// The color spectrum subview with a box/ring spectrum and sliders.
/// </summary>
Spectrum = 0,
/// <summary>
/// The color palette subview with a grid of selectable colors.
/// </summary>
Palette = 1,
/// <summary>
/// The components subview with sliders and numeric input boxes.
/// </summary>
Components = 2,
}
}

4
src/Avalonia.Controls.ColorPicker/Converters/AccentColorConverter.cs

@ -7,8 +7,10 @@ namespace Avalonia.Controls.Primitives.Converters
{ {
/// <summary> /// <summary>
/// Creates an accent color for a given base color value and step parameter. /// Creates an accent color for a given base color value and step parameter.
/// This is a highly-specialized converter for the color picker.
/// </summary> /// </summary>
/// <remarks>
/// This is a highly-specialized converter for the color picker.
/// </remarks>
public class AccentColorConverter : IValueConverter public class AccentColorConverter : IValueConverter
{ {
/// <summary> /// <summary>

2
src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs

@ -42,7 +42,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue; return AvaloniaProperty.UnsetValue;
} }
string hexColor = color.ToString(); string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol == false) if (includeSymbol == false)
{ {

87
src/Avalonia.Controls.ColorPicker/Converters/ContrastBrushConverter.cs

@ -0,0 +1,87 @@
using System;
using System.Globalization;
using Avalonia.Controls.Converters;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives.Converters
{
/// <summary>
/// Gets a <see cref="SolidColorBrush"/>, either black or white, depending on the luminance of the supplied color.
/// A default color supplied in the converter parameter may be returned if alpha is below the set threshold.
/// </summary>
/// <remarks>
/// This is a highly-specialized converter for the color picker.
/// </remarks>
public class ContrastBrushConverter : IValueConverter
{
private ToColorConverter toColorConverter = new ToColorConverter();
/// <summary>
/// Gets or sets the alpha channel threshold below which a default color is used instead of black/white.
/// </summary>
public byte AlphaThreshold { get; set; } = 128;
/// <inheritdoc/>
public object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
Color comparisonColor;
Color? defaultColor = null;
// Get the changing color to compare against
var convertedValue = toColorConverter.Convert(value, targetType, parameter, culture);
if (convertedValue is Color valueColor)
{
comparisonColor = valueColor;
}
else
{
// Invalid color value provided
return AvaloniaProperty.UnsetValue;
}
// Get the default color when transparency is high
var convertedParameter = toColorConverter.Convert(parameter, targetType, parameter, culture);
if (convertedParameter is Color parameterColor)
{
defaultColor = parameterColor;
}
if (comparisonColor.A < AlphaThreshold &&
defaultColor.HasValue)
{
// If the transparency is less than the threshold, just use the default brush
// This can commonly be something like the TextControlForeground brush
return new SolidColorBrush(defaultColor.Value);
}
else
{
// Chose a white/black brush based on contrast to the base color
if (ColorHelper.GetRelativeLuminance(comparisonColor) <= 0.5)
{
// Dark color, return light for contrast
return new SolidColorBrush(Colors.White);
}
else
{
// Bright color, return dark for contrast
return new SolidColorBrush(Colors.Black);
}
}
}
/// <inheritdoc/>
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
return AvaloniaProperty.UnsetValue;
}
}
}

4
src/Avalonia.Controls.ColorPicker/Converters/ThirdComponentConverter.cs

@ -7,8 +7,10 @@ namespace Avalonia.Controls.Primitives.Converters
/// <summary> /// <summary>
/// Gets the third <see cref="ColorComponent"/> corresponding with a given /// Gets the third <see cref="ColorComponent"/> corresponding with a given
/// <see cref="ColorSpectrumComponents"/> that represents the other two components. /// <see cref="ColorSpectrumComponents"/> that represents the other two components.
/// This is a highly-specialized converter for the color picker.
/// </summary> /// </summary>
/// <remarks>
/// This is a highly-specialized converter for the color picker.
/// </remarks>
public class ThirdComponentConverter : IValueConverter public class ThirdComponentConverter : IValueConverter
{ {
/// <inheritdoc/> /// <inheritdoc/>

50
src/Avalonia.Controls.ColorPicker/Converters/ValueConverterGroup.cs

@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Controls.Primitives.Converters
{
/// <summary>
/// Converter to chain together multiple converters.
/// </summary>
public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
/// <inheritdoc/>
/// <inheritdoc/>
public object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
object? curValue;
curValue = value;
for (int i = 0; i < Count; i++)
{
curValue = this[i].Convert(curValue, targetType, parameter, culture);
}
return curValue;
}
/// <inheritdoc/>
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
object? curValue;
curValue = value;
for (int i = (Count - 1); i >= 0; i--)
{
curValue = this[i].ConvertBack(curValue, targetType, parameter, culture);
}
return curValue;
}
}
}

4
src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs

@ -64,6 +64,10 @@ namespace Avalonia.Controls.Primitives
// It is also needlessly large as there are only ~140 known/named colors. // It is also needlessly large as there are only ~140 known/named colors.
// Therefore, rounding of the input color's component values is done to // Therefore, rounding of the input color's component values is done to
// reduce the color space into something more useful. // reduce the color space into something more useful.
//
// The rounding value of 5 is specially chosen.
// It is a factor of 255 and therefore evenly divisible which improves
// the quality of the calculations.
double rounding = 5; double rounding = 5;
var roundedColor = new Color( var roundedColor = new Color(
0xFF, 0xFF,

136
src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml

@ -1,84 +1,100 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters" xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources> <Styles.Resources>
<pc:AccentColorConverter x:Key="AccentColor" /> <pc:AccentColorConverter x:Key="AccentColorConverter" />
<converters:ToBrushConverter x:Key="ToBrush" /> <x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/> <x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
</Styles.Resources> </Styles.Resources>
<Style Selector="ColorPreviewer"> <Style Selector="ColorPreviewer">
<Setter Property="Height" Value="70" /> <Setter Property="Height" Value="70" />
<Setter Property="CornerRadius" Value="0" /> <Setter Property="CornerRadius" Value="0" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorPreviewer}">
<Grid ColumnDefinitions="Auto,*,Auto"> <Panel>
<!-- Left accent colors --> <!-- Preview color with accents to the left and right -->
<Grid Grid.Column="0" <Grid ColumnDefinitions="Auto,*,Auto"
Height="40" IsVisible="{TemplateBinding IsAccentColorsVisible}">
Width="80" <!-- Left accent colors -->
ColumnDefinitions="*,*" <Grid Grid.Column="0"
Margin="0,0,-10,0" Height="{StaticResource ColorPreviewerAccentSectionHeight}"
VerticalAlignment="Center" Width="{StaticResource ColorPreviewerAccentSectionWidth}"
IsVisible="{TemplateBinding ShowAccentColors}"> ColumnDefinitions="*,*"
<Border Grid.Column="0" Margin="0,0,-10,0"
Grid.ColumnSpan="2" VerticalAlignment="Center">
HorizontalAlignment="Stretch" <Border Grid.Column="0"
VerticalAlignment="Stretch" Grid.ColumnSpan="2"
Background="{StaticResource CheckeredBackgroundBrush}" /> HorizontalAlignment="Stretch"
<Border x:Name="AccentDec2Border" VerticalAlignment="Stretch"
Grid.Column="0" Background="{StaticResource ColorControlCheckeredBackgroundBrush}" />
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" <Border x:Name="PART_AccentDecrement2Border"
Tag="-2" Grid.Column="0"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}"
<Border x:Name="AccentDec1Border" Tag="-2"
Grid.Column="1" Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-2'}" />
Tag="-1" <Border x:Name="PART_AccentDecrement1Border"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-1'}" /> Grid.Column="1"
</Grid> Tag="-1"
<!-- Right accent colors --> Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-1'}" />
<Grid Grid.Column="2" </Grid>
Height="40" <!-- Right accent colors -->
Width="80" <Grid Grid.Column="2"
ColumnDefinitions="*,*" Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Margin="-10,0,0,0" Width="{StaticResource ColorPreviewerAccentSectionWidth}"
VerticalAlignment="Center" ColumnDefinitions="*,*"
IsVisible="{TemplateBinding ShowAccentColors}"> Margin="-10,0,0,0"
<Border Grid.Column="0" VerticalAlignment="Center">
Grid.ColumnSpan="2" <Border Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}" />
<Border x:Name="PART_AccentIncrement1Border"
Grid.Column="0"
Tag="1"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='1'}" />
<Border x:Name="PART_AccentIncrement2Border"
Grid.Column="1"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}"
Tag="2"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='2'}" />
</Grid>
<!-- Preview color: Must be last for drop shadow Z-index -->
<Border Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{StaticResource CheckeredBackgroundBrush}" /> BoxShadow="0 0 10 2 #BF000000"
<Border x:Name="AccentInc1Border" CornerRadius="{TemplateBinding CornerRadius}"
Grid.Column="0" Margin="10">
Tag="1" <Panel>
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='1'}" /> <Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
<Border x:Name="AccentInc2Border" CornerRadius="{TemplateBinding CornerRadius}" />
Grid.Column="1" <Border CornerRadius="{TemplateBinding CornerRadius}"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}" Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrushConverter}}"
Tag="2" HorizontalAlignment="Stretch"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='2'}" /> VerticalAlignment="Stretch" />
</Panel>
</Border>
</Grid> </Grid>
<!-- Must be last for drop shadow Z-index --> <!-- Only preview color -->
<Border Grid.Column="1" <Border CornerRadius="{TemplateBinding CornerRadius}"
BoxShadow="0 0 10 2 #BF000000" IsVisible="{TemplateBinding IsAccentColorsVisible, Converter={x:Static BoolConverters.Not}}"
CornerRadius="{TemplateBinding CornerRadius}" HorizontalAlignment="Stretch"
Margin="10"> VerticalAlignment="Stretch"
Margin="0,10,0,10">
<Panel> <Panel>
<Border Background="{StaticResource CheckeredBackgroundBrush}" <Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" /> CornerRadius="{TemplateBinding CornerRadius}" />
<Border x:Name="PreviewBorder" <Border CornerRadius="{TemplateBinding CornerRadius}"
CornerRadius="{TemplateBinding CornerRadius}" Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrushConverter}}"
Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrush}}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Panel> </Panel>
</Border> </Border>
</Grid> </Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

30
src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml

@ -1,13 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" />
</Styles.Resources>
<Style Selector="Thumb.ColorSliderThumbStyle"> <Style Selector="Thumb.ColorSliderThumbStyle">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Template"> <Setter Property="Template">
@ -27,21 +21,21 @@
<Setter Property="CornerRadius" Value="10" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" /> <Setter Property="Height" Value="20" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}" <Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}"> <Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{StaticResource CheckeredBackgroundBrush}" Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}" Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track" <Track Name="PART_Track"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@ -99,21 +93,21 @@
<Setter Property="CornerRadius" Value="10" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" /> <Setter Property="Width" Value="20" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}" <Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}"> <Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{StaticResource CheckeredBackgroundBrush}" Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}" Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track" <Track Name="PART_Track"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"

34
src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml

@ -1,18 +1,12 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters" xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources>
<converters:EnumValueEqualsConverter x:Key="EnumValueEquals" />
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" />
</Styles.Resources>
<Style Selector="ColorSpectrum"> <Style Selector="ColorSpectrum">
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSpectrum}">
<Panel x:Name="PART_LayoutRoot" <Panel x:Name="PART_LayoutRoot"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
@ -24,26 +18,26 @@
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle x:Name="PART_SpectrumOverlayRectangle" <Rectangle x:Name="PART_SpectrumOverlayRectangle"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Ellipse x:Name="PART_SpectrumEllipse" <Ellipse x:Name="PART_SpectrumEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
<Ellipse x:Name="PART_SpectrumOverlayEllipse" <Ellipse x:Name="PART_SpectrumOverlayEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
<Canvas x:Name="PART_InputTarget" <Canvas x:Name="PART_InputTarget"
Background="Transparent" Background="Transparent"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -72,14 +66,14 @@
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Ellipse x:Name="BorderEllipse" <Ellipse x:Name="BorderEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
</Panel> </Panel>
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>

16
src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml

@ -1,8 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters">
<Styles.Resources> <Styles.Resources>
<VisualBrush x:Key="CheckeredBackgroundBrush" <!-- Shared Resources -->
<VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
TileMode="Tile" TileMode="Tile"
Stretch="Uniform" Stretch="Uniform"
DestinationRect="0,0,8,8"> DestinationRect="0,0,8,8">
@ -18,6 +20,16 @@
</DrawingPresenter> </DrawingPresenter>
</VisualBrush.Visual> </VisualBrush.Visual>
</VisualBrush> </VisualBrush>
<!-- Shared Converters -->
<converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:ToBrushConverter x:Key="ToBrushConverter" />
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" />
</Styles.Resources> </Styles.Resources>
<!-- Primitives --> <!-- Primitives -->

91
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -0,0 +1,91 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
<Styles.Resources>
<!-- This must follow OverlayCornerRadius -->
<CornerRadius x:Key="TopOverlayCornerRadius">5,5,0,0</CornerRadius>
</Styles.Resources>
<Style Selector="ColorPicker">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Height" Value="32" />
<Setter Property="Width" Value="64" />
<Setter Property="MinWidth" Value="64" />
<Setter Property="Palette">
<controls:FluentColorPalette />
</Setter>
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorPicker}">
<DropDownButton CornerRadius="{TemplateBinding CornerRadius}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Padding="0,0,10,0"
UseLayoutRounding="False">
<DropDownButton.Styles>
<Style Selector="FlyoutPresenter.NoPadding">
<Setter Property="Padding" Value="0" />
</Style>
</DropDownButton.Styles>
<DropDownButton.Content>
<!-- Preview color -->
<Panel>
<Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="1,1,0,1" />
<Border Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrushConverter}}"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="1,1,0,1" />
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout FlyoutPresenterClasses="NoPadding">
<ColorView x:Name="FlyoutColorView"
Color="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorModel="{Binding ColorModel, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ColorSpectrumComponents="{TemplateBinding ColorSpectrumComponents}"
ColorSpectrumShape="{TemplateBinding ColorSpectrumShape}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
IsAlphaEnabled="{TemplateBinding IsAlphaEnabled}"
IsAlphaVisible="{TemplateBinding IsAlphaVisible}"
IsColorComponentsVisible="{TemplateBinding IsColorComponentsVisible}"
IsColorModelVisible="{TemplateBinding IsColorModelVisible}"
IsColorPaletteVisible="{TemplateBinding IsColorPaletteVisible}"
IsColorPreviewVisible="{TemplateBinding IsColorPreviewVisible}"
IsColorSpectrumVisible="{TemplateBinding IsColorSpectrumVisible}"
IsColorSpectrumSliderVisible="{TemplateBinding IsColorSpectrumSliderVisible}"
IsComponentSliderVisible="{TemplateBinding IsComponentSliderVisible}"
IsComponentTextInputVisible="{TemplateBinding IsComponentTextInputVisible}"
IsHexInputVisible="{TemplateBinding IsHexInputVisible}"
MaxHue="{TemplateBinding MaxHue}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MaxValue="{TemplateBinding MaxValue}"
MinHue="{TemplateBinding MinHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MinValue="{TemplateBinding MinValue}"
PaletteColors="{TemplateBinding PaletteColors}"
PaletteColumnCount="{TemplateBinding PaletteColumnCount}"
Palette="{TemplateBinding Palette}"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
</ControlTemplate>
</Setter>
</Style>
<!-- Adjust Background within Flyout -->
<!-- Note: This is implemented but there seems to be an issue and the selector can't match across the Flyout -->
<Style Selector="ColorPicker /template/ ColorView#FlyoutColorView /template/ Border#TabBackgroundBorder">
<Setter Property="CornerRadius" Value="{DynamicResource TopOverlayCornerRadius}" />
</Style>
</Styles>

136
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml

@ -1,84 +1,100 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:pc="using:Avalonia.Controls.Primitives.Converters" xmlns:pc="using:Avalonia.Controls.Primitives.Converters"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources> <Styles.Resources>
<pc:AccentColorConverter x:Key="AccentColor" /> <pc:AccentColorConverter x:Key="AccentColorConverter" />
<converters:ToBrushConverter x:Key="ToBrush" /> <x:Double x:Key="ColorPreviewerAccentSectionWidth">80</x:Double>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/> <x:Double x:Key="ColorPreviewerAccentSectionHeight">40</x:Double>
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
</Styles.Resources> </Styles.Resources>
<Style Selector="ColorPreviewer"> <Style Selector="ColorPreviewer">
<Setter Property="Height" Value="70" /> <Setter Property="Height" Value="70" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorPreviewer}">
<Grid ColumnDefinitions="Auto,*,Auto"> <Panel>
<!-- Left accent colors --> <!-- Preview color with accents to the left and right -->
<Grid Grid.Column="0" <Grid ColumnDefinitions="Auto,*,Auto"
Height="40" IsVisible="{TemplateBinding IsAccentColorsVisible}">
Width="80" <!-- Left accent colors -->
ColumnDefinitions="*,*" <Grid Grid.Column="0"
Margin="0,0,-10,0" Height="{StaticResource ColorPreviewerAccentSectionHeight}"
VerticalAlignment="Center" Width="{StaticResource ColorPreviewerAccentSectionWidth}"
IsVisible="{TemplateBinding ShowAccentColors}"> ColumnDefinitions="*,*"
<Border Grid.Column="0" Margin="0,0,-10,0"
Grid.ColumnSpan="2" VerticalAlignment="Center">
HorizontalAlignment="Stretch" <Border Grid.Column="0"
VerticalAlignment="Stretch" Grid.ColumnSpan="2"
Background="{StaticResource CheckeredBackgroundBrush}" /> HorizontalAlignment="Stretch"
<Border x:Name="AccentDec2Border" VerticalAlignment="Stretch"
Grid.Column="0" Background="{StaticResource ColorControlCheckeredBackgroundBrush}" />
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}" <Border x:Name="PART_AccentDecrement2Border"
Tag="-2" Grid.Column="0"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-2'}" /> CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource LeftCornerRadiusFilterConverter}}"
<Border x:Name="AccentDec1Border" Tag="-2"
Grid.Column="1" Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-2'}" />
Tag="-1" <Border x:Name="PART_AccentDecrement1Border"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='-1'}" /> Grid.Column="1"
</Grid> Tag="-1"
<!-- Right accent colors --> Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='-1'}" />
<Grid Grid.Column="2" </Grid>
Height="40" <!-- Right accent colors -->
Width="80" <Grid Grid.Column="2"
ColumnDefinitions="*,*" Height="{StaticResource ColorPreviewerAccentSectionHeight}"
Margin="-10,0,0,0" Width="{StaticResource ColorPreviewerAccentSectionWidth}"
VerticalAlignment="Center" ColumnDefinitions="*,*"
IsVisible="{TemplateBinding ShowAccentColors}"> Margin="-10,0,0,0"
<Border Grid.Column="0" VerticalAlignment="Center">
Grid.ColumnSpan="2" <Border Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}" />
<Border x:Name="PART_AccentIncrement1Border"
Grid.Column="0"
Tag="1"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='1'}" />
<Border x:Name="PART_AccentIncrement2Border"
Grid.Column="1"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}"
Tag="2"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColorConverter}, ConverterParameter='2'}" />
</Grid>
<!-- Preview color: Must be last for drop shadow Z-index -->
<Border Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{StaticResource CheckeredBackgroundBrush}" /> BoxShadow="0 0 10 2 #BF000000"
<Border x:Name="AccentInc1Border" CornerRadius="{TemplateBinding CornerRadius}"
Grid.Column="0" Margin="10">
Tag="1" <Panel>
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='1'}" /> <Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
<Border x:Name="AccentInc2Border" CornerRadius="{TemplateBinding CornerRadius}" />
Grid.Column="1" <Border CornerRadius="{TemplateBinding CornerRadius}"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource RightCornerRadiusFilterConverter}}" Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrushConverter}}"
Tag="2" HorizontalAlignment="Stretch"
Background="{TemplateBinding HsvColor, Converter={StaticResource AccentColor}, ConverterParameter='2'}" /> VerticalAlignment="Stretch" />
</Panel>
</Border>
</Grid> </Grid>
<!-- Must be last for drop shadow Z-index --> <!-- Only preview color -->
<Border Grid.Column="1" <Border CornerRadius="{TemplateBinding CornerRadius}"
BoxShadow="0 0 10 2 #BF000000" IsVisible="{TemplateBinding IsAccentColorsVisible, Converter={x:Static BoolConverters.Not}}"
CornerRadius="{TemplateBinding CornerRadius}" HorizontalAlignment="Stretch"
Margin="10"> VerticalAlignment="Stretch"
Margin="0,10,0,10">
<Panel> <Panel>
<Border Background="{StaticResource CheckeredBackgroundBrush}" <Border Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{TemplateBinding CornerRadius}" /> CornerRadius="{TemplateBinding CornerRadius}" />
<Border x:Name="PreviewBorder" <Border CornerRadius="{TemplateBinding CornerRadius}"
CornerRadius="{TemplateBinding CornerRadius}" Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrushConverter}}"
Background="{TemplateBinding HsvColor, Converter={StaticResource ToBrush}}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Panel> </Panel>
</Border> </Border>
</Grid> </Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

30
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@ -1,13 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" />
</Styles.Resources>
<Style Selector="Thumb.ColorSliderThumbStyle"> <Style Selector="Thumb.ColorSliderThumbStyle">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Template"> <Setter Property="Template">
@ -27,21 +21,21 @@
<Setter Property="CornerRadius" Value="10" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" /> <Setter Property="Height" Value="20" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}" <Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}"> <Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{StaticResource CheckeredBackgroundBrush}" Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}" Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track" <Track Name="PART_Track"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@ -99,21 +93,21 @@
<Setter Property="CornerRadius" Value="10" /> <Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" /> <Setter Property="Width" Value="20" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}" <Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}"> <Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{StaticResource CheckeredBackgroundBrush}" Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch" <Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}" Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Track Name="PART_Track" <Track Name="PART_Track"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"

34
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml

@ -1,18 +1,12 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters" xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True"> x:CompileBindings="True">
<Styles.Resources>
<converters:EnumValueEqualsConverter x:Key="EnumValueEquals" />
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadius" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadius" Corner="BottomRight" />
</Styles.Resources>
<Style Selector="ColorSpectrum"> <Style Selector="ColorSpectrum">
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate> <ControlTemplate TargetType="{x:Type ColorSpectrum}">
<Panel x:Name="PART_LayoutRoot" <Panel x:Name="PART_LayoutRoot"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
@ -24,26 +18,26 @@
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle x:Name="PART_SpectrumOverlayRectangle" <Rectangle x:Name="PART_SpectrumOverlayRectangle"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Ellipse x:Name="PART_SpectrumEllipse" <Ellipse x:Name="PART_SpectrumEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
<Ellipse x:Name="PART_SpectrumOverlayEllipse" <Ellipse x:Name="PART_SpectrumOverlayEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
<Canvas x:Name="PART_InputTarget" <Canvas x:Name="PART_InputTarget"
Background="Transparent" Background="Transparent"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -72,14 +66,14 @@
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Box'}" IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Box}}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadius}}" RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadius}}" /> RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Ellipse x:Name="BorderEllipse" <Ellipse x:Name="BorderEllipse"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumValueEquals}, ConverterParameter='Ring'}" /> IsVisible="{TemplateBinding Shape, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorSpectrumShape.Ring}}" />
</Panel> </Panel>
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>

650
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -0,0 +1,650 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.Controls"
xmlns:converters="using:Avalonia.Controls.Converters"
xmlns:primitives="using:Avalonia.Controls.Primitives"
xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
xmlns:globalization="clr-namespace:System.Globalization;assembly=mscorlib"
x:CompileBindings="True">
<Styles.Resources>
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<pc:ThirdComponentConverter x:Key="ThirdComponentConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
<x:Double x:Key="ColorViewComponentLabelWidth">30</x:Double>
<x:Double x:Key="ColorViewComponentTextInputWidth">80</x:Double>
<!-- Fluent UI System Icons : ic_fluent_inking_tool_20_regular.svg -->
<PathGeometry x:Key="ColorViewSpectrumIconGeometry">
M3 2C3.27614 2 3.5 2.22386 3.5 2.5V5.5C3.5 5.77614 3.72386 6 4 6H16C16.2761 6 16.5 5.77614
16.5 5.5V2.5C16.5 2.22386 16.7239 2 17 2C17.2761 2 17.5 2.22386 17.5 2.5V5.5C17.5 6.32843
16.8284 7 16 7H15.809L12.2236 14.1708C12.0615 14.4951 11.7914 14.7431 11.4695
14.8802C11.4905 15.0808 11.5 15.2891 11.5 15.5C11.5 16.0818 11.4278 16.6623 11.2268
17.1165C11.019 17.5862 10.6266 18 10 18C9.37343 18 8.98105 17.5862 8.77323 17.1165C8.57222
16.6623 8.5 16.0818 8.5 15.5C8.5 15.2891 8.50952 15.0808 8.53051 14.8802C8.20863 14.7431
7.93851 14.4951 7.77639 14.1708L4.19098 7H4C3.17157 7 2.5 6.32843 2.5 5.5V2.5C2.5 2.22386
2.72386 2 3 2ZM9.11803 14H10.882C11.0714 14 11.2445 13.893 11.3292 13.7236L14.691
7H5.30902L8.67082 13.7236C8.75552 13.893 8.92865 14 9.11803 14ZM9.52346 15C9.50787 15.1549
9.5 15.3225 9.5 15.5C9.5 16.0228 9.56841 16.4423 9.6877 16.7119C9.8002 16.9661 9.90782 17
10 17C10.0922 17 10.1998 16.9661 10.3123 16.7119C10.4316 16.4423 10.5 16.0228 10.5
15.5C10.5 15.3225 10.4921 15.1549 10.4765 15H9.52346Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_color_20_regular.svg -->
<PathGeometry x:Key="ColorViewPaletteIconGeometry">
M9.75003 6.5C10.1642 6.5 10.5 6.16421 10.5 5.75C10.5 5.33579 10.1642 5 9.75003 5C9.33582
5 9.00003 5.33579 9.00003 5.75C9.00003 6.16421 9.33582 6.5 9.75003 6.5ZM12.75 7.5C13.1642
7.5 13.5 7.16421 13.5 6.75C13.5 6.33579 13.1642 6 12.75 6C12.3358 6 12 6.33579 12 6.75C12
7.16421 12.3358 7.5 12.75 7.5ZM15.25 9C15.25 9.41421 14.9142 9.75 14.5 9.75C14.0858 9.75
13.75 9.41421 13.75 9C13.75 8.58579 14.0858 8.25 14.5 8.25C14.9142 8.25 15.25 8.58579
15.25 9ZM14.5 12.75C14.9142 12.75 15.25 12.4142 15.25 12C15.25 11.5858 14.9142 11.25 14.5
11.25C14.0858 11.25 13.75 11.5858 13.75 12C13.75 12.4142 14.0858 12.75 14.5 12.75ZM13.25
14C13.25 14.4142 12.9142 14.75 12.5 14.75C12.0858 14.75 11.75 14.4142 11.75 14C11.75
13.5858 12.0858 13.25 12.5 13.25C12.9142 13.25 13.25 13.5858 13.25 14ZM13.6972
2.99169C10.9426 1.57663 8.1432 1.7124 5.77007 3.16636C4.55909 3.9083 3.25331 5.46925
2.51605 7.05899C2.14542 7.85816 1.89915 8.70492 1.90238 9.49318C1.90566 10.2941 2.16983
11.0587 2.84039 11.6053C3.45058 12.1026 3.98165 12.353 4.49574 12.3784C5.01375 12.404
5.41804 12.1942 5.73429 12.0076C5.80382 11.9666 5.86891 11.927 5.93113 11.8892C6.17332
11.7421 6.37205 11.6214 6.62049 11.5426C6.90191 11.4534 7.2582 11.4205 7.77579
11.5787C7.96661 11.637 8.08161 11.7235 8.16212 11.8229C8.24792 11.9289 8.31662 12.0774
8.36788 12.2886C8.41955 12.5016 8.44767 12.7527 8.46868 13.0491C8.47651 13.1594 8.48379
13.2855 8.49142 13.4176C8.50252 13.6098 8.51437 13.8149 8.52974 14.0037C8.58435 14.6744
8.69971 15.4401 9.10362 16.1357C9.51764 16.8488 10.2047 17.439 11.307 17.8158C12.9093
18.3636 14.3731 17.9191 15.5126 17.0169C16.6391 16.125 17.4691 14.7761 17.8842
13.4272C19.1991 9.15377 17.6728 5.03394 13.6972 2.99169ZM6.29249 4.01905C8.35686 2.75426
10.7844 2.61959 13.2403 3.88119C16.7473 5.68275 18.1135 9.28161 16.9284 13.1332C16.5624
14.3227 15.8338 15.4871 14.8919 16.2329C13.963 16.9684 12.8486 17.286 11.6305
16.8696C10.7269 16.5607 10.2467 16.1129 9.96842 15.6336C9.68001 15.1369 9.57799 14.5556
9.52644 13.9225C9.51101 13.733 9.50132 13.5621 9.49147 13.3884C9.48399 13.2564 9.47642
13.1229 9.46618 12.9783C9.44424 12.669 9.41175 12.3499 9.33968 12.0529C9.26719 11.7541
9.14902 11.4527 8.93935 11.1937C8.72439 10.9282 8.43532 10.7346 8.06801 10.6223C7.36648
10.408 6.80266 10.4359 6.31839 10.5893C5.94331 10.7082 5.62016 10.9061 5.37179
11.0582C5.31992 11.0899 5.2713 11.1197 5.22616 11.1463C4.94094 11.3146 4.75357 11.39
4.54514 11.3796C4.33279 11.3691 4.00262 11.2625 3.47218 10.8301C3.0866 10.5158 2.90473
10.0668 2.90237 9.48908C2.89995 8.89865 3.08843 8.20165 3.42324 7.47971C4.09686 6.0272
5.28471 4.63649 6.29249 4.01905Z
</PathGeometry>
<!-- Fluent UI System Icons : ic_fluent_options_20_regular.svg -->
<PathGeometry x:Key="ColorViewComponentsIconGeometry">
M14.95 5C14.7184 3.85888 13.7095 3 12.5 3C11.2905 3 10.2816 3.85888 10.05 5H2.5C2.22386
5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H10.05C10.2816 7.14112 11.2905 8 12.5 8C13.7297
8 14.752 7.11217 14.961 5.94254C14.9575 5.96177 14.9539 5.98093 14.95 6H17.5C17.7761 6 18
5.77614 18 5.5C18 5.22386 17.7761 5 17.5 5H14.95ZM12.5 7C11.6716 7 11 6.32843 11 5.5C11
4.67157 11.6716 4 12.5 4C13.3284 4 14 4.67157 14 5.5C14 6.32843 13.3284 7 12.5 7ZM9.94999
14C9.71836 12.8589 8.70948 12 7.5 12C6.29052 12 5.28164 12.8589 5.05001 14H2.5C2.22386
14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H5.05001C5.28164 16.1411 6.29052 17 7.5
17C8.70948 17 9.71836 16.1411 9.94999 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239
17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5
13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z
</PathGeometry>
</Styles.Resources>
<Style Selector="ColorView">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Palette">
<controls:FluentColorPalette />
</Setter>
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorView}">
<Grid RowDefinitions="Auto,Auto">
<!-- Backgrounds -->
<!-- These are separated for Fluent v2 re-styling without having to re-template. -->
<Border x:Name="TabBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Height="48"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Border x:Name="ContentBackgroundBorder"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,48,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius, Converter={StaticResource BottomCornerRadiusFilterConverter}}"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0,1,0,0" />
<TabControl x:Name="PART_TabControl"
Grid.Row="0"
Height="338"
Width="350"
SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<TabControl.Resources>
<Thickness x:Key="TabItemMargin">0,0,0,0</Thickness>
</TabControl.Resources>
<TabControl.Styles>
<Style Selector="TabControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="0"
Rows="1" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Styles>
<!-- Spectrum Tab -->
<TabItem IsVisible="{TemplateBinding IsColorSpectrumVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewSpectrumIconGeometry}" />
</Border>
</TabItem.Header>
<Grid RowDefinitions="*"
Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"
MinWidth="32" />
</Grid.ColumnDefinitions>
<primitives:ColorSlider x:Name="ColorSpectrumThirdComponentSlider"
AutomationProperties.Name="Third Component"
Grid.Column="0"
IsAlphaMaxForced="True"
IsSaturationValueMaxForced="False"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="{Binding Components, ElementName=ColorSpectrum, Converter={StaticResource ThirdComponentConverter}}"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="0,0,12,0"
IsVisible="{TemplateBinding IsColorSpectrumSliderVisible}"/>
<primitives:ColorSpectrum x:Name="ColorSpectrum"
Grid.Column="1"
Components="{TemplateBinding ColorSpectrumComponents}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
MinHue="{TemplateBinding MinHue}"
MaxHue="{TemplateBinding MaxHue}"
MinSaturation="{TemplateBinding MinSaturation}"
MaxSaturation="{TemplateBinding MaxSaturation}"
MinValue="{TemplateBinding MinValue}"
MaxValue="{TemplateBinding MaxValue}"
Shape="{TemplateBinding ColorSpectrumShape}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<primitives:ColorSlider x:Name="ColorSpectrumAlphaSlider"
AutomationProperties.Name="Alpha Component"
Grid.Column="2"
Orientation="Vertical"
ColorModel="Hsva"
ColorComponent="Alpha"
HsvColor="{Binding HsvColor, ElementName=ColorSpectrum}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Margin="12,0,0,0"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
<!-- Palette Tab -->
<TabItem IsVisible="{TemplateBinding IsColorPaletteVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewPaletteIconGeometry}" />
</Border>
</TabItem.Header>
<ListBox Items="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid UseLayoutRounding="False">
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
<Rectangle x:Name="BorderRectangle"
IsHitTestVisible="False"
StrokeThickness="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ Rectangle#BorderRectangle">
<Setter Property="Stroke" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="^:pointerover /template/ Rectangle#BorderRectangle">
<Setter Property="Stroke" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="^:selected /template/ Rectangle#BorderRectangle"
x:DataType="Color">
<!-- The below line really should be:
'Value="{Binding Converter={StaticResource ContrastBrushConverter}, ConverterParameter={DynamicResource TextControlForeground}}"'
However, DynamicResource and Binding is not currently supported in Avalonia for converter parameters.
This means certain colors with transparency less than 50% may have a selection border that is difficult
to see over top of the default control/window background. Since palettes do not usually have transparency
it is considered better to disable this functionality rather than work around it with a multi-value
converter. This should be revisited if Avalonia supports the above code in the future. -->
<Setter Property="Stroke" Value="{Binding Converter={StaticResource ContrastBrushConverter}}" />
<Setter Property="Opacity" Value="1" />
</Style>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Color}">
<Border AutomationProperties.Name="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
ToolTip.Tip="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding $parent[ColorView].PaletteColumnCount}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</TabItem>
<!-- Components Tab -->
<TabItem IsVisible="{TemplateBinding IsColorComponentsVisible}">
<TabItem.Header>
<Border Height="{DynamicResource ColorViewTabStripHeight}">
<PathIcon Width="20"
Height="20"
Data="{DynamicResource ColorViewComponentsIconGeometry}" />
</Border>
</TabItem.Header>
<Grid ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,24,1*,1*,1*,1*,12"
Margin="12">
<!-- Top color model & Hex input -->
<Grid Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
ColumnDefinitions="1*,12,1*">
<!-- Content RGB/HSV names are hard-coded and considered universal -->
<!-- RadioButtons are styled to look like a 'SegmentedControl' or 'ButtonGroup' -->
<Grid ColumnDefinitions="1*,1*"
IsVisible="{TemplateBinding IsColorModelVisible}">
<Grid.Styles>
<Style Selector="RadioButton">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Padding" Value="{DynamicResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type RadioButton}">
<ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundPressed}" />
</Style>
<Style Selector="^:checked /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundChecked}" />
</Style>
<Style Selector="^:checked:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="^:checked:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
</Style>
</Style>
</Grid.Styles>
<RadioButton x:Name="RgbRadioButton"
Grid.Column="0"
Content="RGB"
CornerRadius="4,0,0,4"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Grid.Column="1"
Content="HSV"
CornerRadius="0,4,4,0"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"
Grid.Column="2"
IsVisible="{TemplateBinding IsHexInputVisible}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Height="32"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="#"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<!-- Color updated in code-behind -->
<TextBox x:Name="PART_HexTextBox"
Grid.Column="1"
AutomationProperties.Name="Hexadecimal Color"
Height="32"
MaxLength="8"
HorizontalAlignment="Stretch"
CornerRadius="0,4,4,0" />
</Grid>
</Grid>
<!-- Color component editing controls -->
<!-- Component 1 RGB:Red HSV:Hue -->
<Border Grid.Column="0"
Grid.Row="2"
Height="{Binding ElementName=Component1NumericUpDown, Path=Bounds.Height}"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="R"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}"/>
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="H"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component1NumericUpDown"
Grid.Column="1"
Grid.Row="2"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component1Slider}"
Maximum="{Binding Maximum, ElementName=Component1Slider}"
Value="{Binding Value, ElementName=Component1Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component1Slider"
Grid.Column="2"
Grid.Row="2"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component1"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 2 RGB:Green HSV:Saturation -->
<Border Grid.Column="0"
Grid.Row="3"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component2NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="G"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="S"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component2NumericUpDown"
Grid.Column="1"
Grid.Row="3"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component2Slider}"
Maximum="{Binding Maximum, ElementName=Component2Slider}"
Value="{Binding Value, ElementName=Component2Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component2Slider"
Grid.Column="2"
Grid.Row="3"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component2"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Component 3 RGB:Blue HSV:Value -->
<Border Grid.Column="0"
Grid.Row="4"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=Component3NumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentTextInputVisible}">
<Panel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="B"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=OneWay}" />
<TextBlock Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="V"
IsVisible="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=OneWay}" />
</Panel>
</Border>
<NumericUpDown x:Name="Component3NumericUpDown"
Grid.Column="1"
Grid.Row="4"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=Component3Slider}"
Maximum="{Binding Maximum, ElementName=Component3Slider}"
Value="{Binding Value, ElementName=Component3Slider}"
IsVisible="{TemplateBinding IsComponentTextInputVisible}" />
<primitives:ColorSlider x:Name="Component3Slider"
Grid.Column="2"
Grid.Row="4"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Component3"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsComponentSliderVisible}" />
<!-- Alpha Component -->
<Border Grid.Column="0"
Grid.Row="5"
Width="{DynamicResource ColorViewComponentLabelWidth}"
Height="{Binding ElementName=AlphaComponentNumericUpDown, Path=Bounds.Height}"
Background="{DynamicResource TextControlBackgroundDisabled}"
BorderBrush="{DynamicResource TextControlBorderBrush}"
BorderThickness="1,1,0,1"
CornerRadius="4,0,0,4"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<TextBlock x:Name="AlphaComponentTextBlock"
Foreground="{DynamicResource TextControlForegroundDisabled}"
FontWeight="SemiBold"
Text="A"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</Border.IsVisible>
</Border>
<NumericUpDown x:Name="AlphaComponentNumericUpDown"
Grid.Column="1"
Grid.Row="5"
AllowSpin="True"
ShowButtonSpinner="False"
Height="32"
Width="{DynamicResource ColorViewComponentTextInputWidth}"
CornerRadius="0,4,4,0"
Margin="0,0,12,0"
VerticalAlignment="Center"
NumberFormat="{StaticResource ColorViewComponentNumberFormat}"
Minimum="{Binding Minimum, ElementName=AlphaComponentSlider}"
Maximum="{Binding Maximum, ElementName=AlphaComponentSlider}"
Value="{Binding Value, ElementName=AlphaComponentSlider}"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<NumericUpDown.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentTextInputVisible" />
</MultiBinding>
</NumericUpDown.IsVisible>
</NumericUpDown>
<primitives:ColorSlider x:Name="AlphaComponentSlider"
Grid.Column="2"
Grid.Row="5"
Orientation="Horizontal"
IsRoundingEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="1"
ColorComponent="Alpha"
ColorModel="{TemplateBinding ColorModel, Mode=OneWay}"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsAlphaEnabled}">
<primitives:ColorSlider.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsAlphaVisible" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsComponentSliderVisible" />
</MultiBinding>
</primitives:ColorSlider.IsVisible>
</primitives:ColorSlider>
</Grid>
</TabItem>
</TabControl>
<!-- Previewer -->
<!-- Note that top/bottom margins have -5 to remove for drop shadow padding -->
<primitives:ColorPreviewer Grid.Row="1"
HsvColor="{Binding HsvColor, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
IsAccentColorsVisible="{TemplateBinding IsAccentColorsVisible}"
Margin="12,-5,12,7"
IsVisible="{TemplateBinding IsColorPreviewVisible}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
</Styles>

20
src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml

@ -1,8 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Avalonia.Controls.Converters">
<Styles.Resources> <Styles.Resources>
<VisualBrush x:Key="CheckeredBackgroundBrush" <!-- Shared Resources -->
<VisualBrush x:Key="ColorControlCheckeredBackgroundBrush"
TileMode="Tile" TileMode="Tile"
Stretch="Uniform" Stretch="Uniform"
DestinationRect="0,0,8,8"> DestinationRect="0,0,8,8">
@ -18,6 +20,16 @@
</DrawingPresenter> </DrawingPresenter>
</VisualBrush.Visual> </VisualBrush.Visual>
</VisualBrush> </VisualBrush>
<!-- Shared Converters -->
<converters:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<converters:ToBrushConverter x:Key="ToBrushConverter" />
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusToDoubleConverter x:Key="TopLeftCornerRadiusConverter" Corner="TopLeft" />
<converters:CornerRadiusToDoubleConverter x:Key="BottomRightCornerRadiusConverter" Corner="BottomRight" />
</Styles.Resources> </Styles.Resources>
<!-- Primitives --> <!-- Primitives -->
@ -25,4 +37,8 @@
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" /> <StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" /> <StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSpectrum.xaml" />
<!-- Controls -->
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml" />
</Styles> </Styles>

56
src/Avalonia.Controls/Converters/EnumToBoolConverter.cs

@ -0,0 +1,56 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters
{
/// <summary>
/// Converter to convert an enum value to bool by comparing to the given parameter.
/// Both value and parameter must be of the same enum type.
/// </summary>
/// <remarks>
/// This converter is useful to enable binding of radio buttons with a selected enum value.
/// </remarks>
public class EnumToBoolConverter : IValueConverter
{
/// <inheritdoc/>
public object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (value == null &&
parameter == null)
{
return true;
}
else if (value == null ||
parameter == null)
{
return false;
}
else
{
return value!.Equals(parameter);
}
}
/// <inheritdoc/>
public object? ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (value is bool boolValue &&
boolValue == true)
{
return parameter;
}
return BindingOperations.DoNothing;
}
}
}

54
src/Avalonia.Controls/Converters/EnumValueEqualsConverter.cs

@ -1,54 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters
{
/// <summary>
/// Converter that checks if an enum value is equal to the given parameter enum value.
/// </summary>
public class EnumValueEqualsConverter : IValueConverter
{
/// <inheritdoc/>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
// Note: Unlike string comparisons, null/empty is not supported
// Both 'value' and 'parameter' must exist and if both are missing they are not considered equal
if (value != null &&
parameter != null)
{
Type type = value.GetType();
if (type.IsEnum)
{
var valueStr = value?.ToString();
var paramStr = parameter?.ToString();
if (string.Equals(valueStr, paramStr, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
/*
// TODO: When .net Standard 2.0 is no longer supported the code can be changed to below
// This is a little more type safe
if (type.IsEnum &&
Enum.TryParse(type, value?.ToString(), true, out object? valueEnum) &&
Enum.TryParse(type, parameter?.ToString(), true, out object? paramEnum))
{
return valueEnum == paramEnum;
}
*/
}
return false;
}
/// <inheritdoc/>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
}

2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -291,7 +291,9 @@ namespace Avalonia.Controls.Primitives
// Existing code kinda expect to see a NameScope even if it's empty // Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null) if (nameScope == null)
{
nameScope = new NameScope(); nameScope = new NameScope();
}
var e = new TemplateAppliedEventArgs(nameScope); var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e); OnApplyTemplate(e);

Loading…
Cancel
Save