Browse Source

Merge branch 'master' into fixes/moreTextProcessingFixes

pull/10784/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
41a8d2d3cb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  2. 2
      src/Avalonia.Base/Media/Color.cs
  3. 2
      src/Avalonia.Base/Media/HsvColor.cs
  4. 46
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  5. 8
      src/Avalonia.Base/Platform/PixelFormat.cs
  6. 4
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  7. 2
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs
  8. 30
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  9. 10
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  10. 11
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  11. 26
      src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs
  12. 18
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  13. 35
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  14. 169
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  15. 146
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  16. 2
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  17. 5
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  18. 67
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  19. 3
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  20. 4
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  21. 67
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
  22. 3
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  23. 8
      src/Avalonia.Controls/Slider.cs
  24. 2
      tests/Avalonia.RenderTests/Media/BitmapTests.cs

35
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@ -28,6 +28,41 @@
<Grid Grid.Column="2"
Grid.Row="0"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid.Resources>
<x:Double x:Key="ColorSliderSize">24</x:Double>
<x:Double x:Key="ColorSliderTrackSize">18</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">12</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">9</CornerRadius>
<!-- Due to 'SystemControlForegroundBaseHighBrush' usage this only works in Fluent theme. -->
<!-- Otherwise it would be necessary to make custom light/dark resources. -->
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderThickness" Value="5" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" />
<Ellipse Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Fill="Transparent"
Stroke="{TemplateBinding Foreground}"
StrokeThickness="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</ControlTheme>
</Grid.Resources>
<ColorSpectrum x:Name="ColorSpectrum1"
Grid.Row="0"
Color="Red"

2
src/Avalonia.Base/Media/Color.cs

@ -309,7 +309,7 @@ namespace Avalonia.Media
if (input.Length == 3 || input.Length == 4)
{
var extendedLength = 2 * input.Length;
#if !BUILDTASK
Span<char> extended = stackalloc char[extendedLength];
#else

2
src/Avalonia.Base/Media/HsvColor.cs

@ -131,7 +131,7 @@ namespace Avalonia.Media
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>0 is a shade of gray (no color).</item>
/// <item>0 is fully white (or a shade of gray) and shows no color.</item>
/// <item>1 is the full color.</item>
/// </list>
/// </remarks>

46
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

@ -228,6 +228,44 @@ static unsafe class PixelFormatReader
public void Reset(IntPtr address) => _address = (Rgba64*)address;
}
public unsafe struct Rgb24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public Rgba8888Pixel ReadNext()
{
var addr = _address;
_address += 3;
return new Rgba8888Pixel
{
R = addr[0],
G = addr[1],
B = addr[2],
A = 255,
};
}
public void Reset(IntPtr address) => _address = (byte*)address;
}
public unsafe struct Bgr24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public Rgba8888Pixel ReadNext()
{
var addr = _address;
_address += 3;
return new Rgba8888Pixel
{
R = addr[2],
G = addr[1],
B = addr[0],
A = 255,
};
}
public void Reset(IntPtr address) => _address = (byte*)address;
}
public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
PixelFormat format)
@ -242,6 +280,10 @@ static unsafe class PixelFormatReader
Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Gray16)
Transcode<Gray16PixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Rgb24)
Transcode<Rgb24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Bgr24)
Transcode<Bgr24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Gray32Float)
Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Rgba64)
@ -258,7 +300,9 @@ static unsafe class PixelFormatReader
|| format == PixelFormats.Gray8
|| format == PixelFormats.Gray16
|| format == PixelFormats.Gray32Float
|| format == PixelFormats.Rgba64;
|| format == PixelFormats.Rgba64
|| format == PixelFormats.Bgr24
|| format == PixelFormats.Rgb24;
}
public static void Transcode<TReader>(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader

8
src/Avalonia.Base/Platform/PixelFormat.cs

@ -13,7 +13,9 @@ namespace Avalonia.Platform
Gray8,
Gray16,
Gray32Float,
Rgba64
Rgba64,
Rgb24,
Bgr24
}
public record struct PixelFormat
@ -35,6 +37,8 @@ namespace Avalonia.Platform
else if (FormatEnum == PixelFormatEnum.Rgb565
|| FormatEnum == PixelFormatEnum.Gray16)
return 16;
else if (FormatEnum is PixelFormatEnum.Bgr24 or PixelFormatEnum.Rgb24)
return 24;
else if (FormatEnum == PixelFormatEnum.Rgba64)
return 64;
@ -70,5 +74,7 @@ namespace Avalonia.Platform
public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
public static PixelFormat Rgb24 { get; } = new PixelFormat(PixelFormatEnum.Rgb24);
public static PixelFormat Bgr24 { get; } = new PixelFormat(PixelFormatEnum.Bgr24);
}
}

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

@ -1,6 +1,4 @@
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
namespace Avalonia.Controls
{
/// <summary>
/// Presents a color for user editing using a spectrum, palette and component sliders within a drop down.

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

@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives
if (accentStep != 0)
{
// ColorChanged will be invoked in OnPropertyChanged if the value is different
HsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep);
SetCurrentValue(HsvColorProperty, AccentColorConverter.GetAccent(hsvColor, accentStep));
}
}
}

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

@ -96,8 +96,22 @@ namespace Avalonia.Controls.Primitives
// independent pixels of controls.
var scale = LayoutHelper.GetLayoutScale(this);
var pixelWidth = Convert.ToInt32(Bounds.Width * scale);
var pixelHeight = Convert.ToInt32(Bounds.Height * scale);
int pixelWidth;
int pixelHeight;
if (base._track != null)
{
pixelWidth = Convert.ToInt32(base._track.Bounds.Width * scale);
pixelHeight = Convert.ToInt32(base._track.Bounds.Height * scale);
}
else
{
// As a fallback, attempt to calculate using the overall control size
// This shouldn't happen as a track is a required template part of a slider
// However, if it does, the spectrum will still be shown
pixelWidth = Convert.ToInt32(Bounds.Width * scale);
pixelHeight = Convert.ToInt32(Bounds.Height * scale);
}
if (pixelWidth != 0 && pixelHeight != 0)
{
@ -373,7 +387,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true;
// Always keep the two color properties in sync
HsvColor = Color.ToHsv();
SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToSliderValues();
UpdateBackground();
@ -403,7 +417,7 @@ namespace Avalonia.Controls.Primitives
ignorePropertyChanged = true;
// Always keep the two color properties in sync
Color = HsvColor.ToRgb();
SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToSliderValues();
UpdateBackground();
@ -440,13 +454,13 @@ namespace Avalonia.Controls.Primitives
if (ColorModel == ColorModel.Hsva)
{
HsvColor = hsvColor;
Color = hsvColor.ToRgb();
SetCurrentValue(HsvColorProperty, hsvColor);
SetCurrentValue(ColorProperty, hsvColor.ToRgb());
}
else
{
Color = color;
HsvColor = color.ToHsv();
SetCurrentValue(ColorProperty, color);
SetCurrentValue(HsvColorProperty, color.ToHsv());
}
UpdatePseudoClasses();

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

@ -96,10 +96,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="ThirdComponent"/> property.
/// </summary>
public static readonly StyledProperty<ColorComponent> ThirdComponentProperty =
AvaloniaProperty.Register<ColorSpectrum, ColorComponent>(
public static readonly DirectProperty<ColorSpectrum, ColorComponent> ThirdComponentProperty =
AvaloniaProperty.RegisterDirect<ColorSpectrum, ColorComponent>(
nameof(ThirdComponent),
ColorComponent.Component3); // Value
o => o.ThirdComponent);
/// <summary>
/// Gets or sets the currently selected color in the RGB color model.
@ -239,8 +239,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks>
public ColorComponent ThirdComponent
{
get => GetValue(ThirdComponentProperty);
protected set => SetValue(ThirdComponentProperty, value);
get => _thirdComponent;
private set => SetAndRaise(ThirdComponentProperty, ref _thirdComponent, value);
}
}
}

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

@ -13,9 +13,9 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Reactive;
namespace Avalonia.Controls.Primitives
{
@ -48,6 +48,7 @@ namespace Avalonia.Controls.Primitives
private bool _isPointerPressed = false;
private bool _shouldShowLargeSelection = false;
private List<Hsv> _hsvValues = new List<Hsv>();
private ColorComponent _thirdComponent = ColorComponent.Component3; // HsvComponent.Value
private IDisposable? _layoutRootDisposable;
private IDisposable? _selectionEllipsePanelDisposable;
@ -403,7 +404,7 @@ namespace Avalonia.Controls.Primitives
_updatingHsvColor = true;
Hsv newHsv = (new Rgb(color)).ToHsv();
HsvColor = newHsv.ToHsvColor(color.A / 255.0);
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(color.A / 255.0));
_updatingHsvColor = false;
UpdateEllipse();
@ -534,7 +535,7 @@ namespace Avalonia.Controls.Primitives
_updatingColor = true;
Rgb newRgb = (new Hsv(hsvColor)).ToRgb();
Color = newRgb.ToColor(hsvColor.A);
SetCurrentValue(ColorProperty, newRgb.ToColor(hsvColor.A));
_updatingColor = false;
@ -608,8 +609,8 @@ namespace Avalonia.Controls.Primitives
Rgb newRgb = newHsv.ToRgb();
double alpha = HsvColor.A;
Color = newRgb.ToColor(alpha);
HsvColor = newHsv.ToHsvColor(alpha);
SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));
UpdateEllipse();
UpdatePseudoClasses();

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

@ -0,0 +1,26 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines the position of a color's alpha component relative to all other components.
/// </summary>
public enum AlphaComponentPosition
{
/// <summary>
/// The alpha component occurs before all other components.
/// </summary>
/// <remarks>
/// For example, this may indicate the #AARRGGBB or ARGB format which
/// is the default format for XAML itself and the Color struct.
/// </remarks>
Leading,
/// <summary>
/// The alpha component occurs after all other components.
/// </summary>
/// <remarks>
/// For example, this may indicate the #RRGGBBAA or RGBA format which
/// is the default format for CSS.
/// </remarks>
Trailing,
}
}

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

@ -42,6 +42,14 @@ namespace Avalonia.Controls
nameof(ColorSpectrumShape),
ColorSpectrumShape.Box);
/// <summary>
/// Defines the <see cref="HexInputAlphaPosition"/> property.
/// </summary>
public static readonly StyledProperty<AlphaComponentPosition> HexInputAlphaPositionProperty =
AvaloniaProperty.Register<ColorView, AlphaComponentPosition>(
nameof(HexInputAlphaPosition),
AlphaComponentPosition.Trailing); // Match CSS (and default slider order) instead of XAML/WinUI
/// <summary>
/// Defines the <see cref="HsvColor"/> property.
/// </summary>
@ -260,6 +268,16 @@ namespace Avalonia.Controls
set => SetValue(ColorSpectrumShapeProperty, value);
}
/// <summary>
/// Gets or sets the position of the alpha component in the hexadecimal input box relative to
/// all other color components.
/// </summary>
public AlphaComponentPosition HexInputAlphaPosition
{
get => GetValue(HexInputAlphaPositionProperty);
set => SetValue(HexInputAlphaPositionProperty, value);
}
/// <inheritdoc cref="ColorSpectrum.HsvColor"/>
public HsvColor HsvColor
{

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

@ -1,14 +1,10 @@
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
{
@ -28,8 +24,7 @@ namespace Avalonia.Controls
private TextBox? _hexTextBox;
private TabControl? _tabControl;
private ColorToHexConverter colorToHexConverter = new ColorToHexConverter();
protected bool ignorePropertyChanged = false;
protected bool _ignorePropertyChanged = false;
/// <summary>
/// Initializes a new instance of the <see cref="ColorView"/> class.
@ -46,11 +41,11 @@ namespace Avalonia.Controls
{
if (_hexTextBox != null)
{
var convertedColor = colorToHexConverter.ConvertBack(_hexTextBox.Text, typeof(Color), null, CultureInfo.CurrentCulture);
var convertedColor = ColorToHexConverter.ParseHexString(_hexTextBox.Text ?? string.Empty, HexInputAlphaPosition);
if (convertedColor is Color color)
{
Color = color;
SetCurrentValue(ColorProperty, color);
}
// Re-apply the hex value
@ -66,7 +61,7 @@ namespace Avalonia.Controls
{
if (_hexTextBox != null)
{
_hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string;
_hexTextBox.Text = ColorToHexConverter.ToHexString(Color, HexInputAlphaPosition);
}
}
@ -167,7 +162,7 @@ namespace Avalonia.Controls
// 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;
SetCurrentValue(SelectedIndexProperty, _tabControl.SelectedIndex);
}
return;
@ -200,7 +195,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (ignorePropertyChanged)
if (_ignorePropertyChanged)
{
base.OnPropertyChanged(change);
return;
@ -209,29 +204,29 @@ namespace Avalonia.Controls
// Always keep the two color properties in sync
if (change.Property == ColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
HsvColor = Color.ToHsv();
SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<Color>(),
change.GetNewValue<Color>()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == HsvColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
Color = HsvColor.ToRgb();
SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToHexTextBox();
OnColorChanged(new ColorChangedEventArgs(
change.GetOldValue<HsvColor>().ToRgb(),
change.GetNewValue<HsvColor>().ToRgb()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == PaletteProperty)
{
@ -241,7 +236,7 @@ namespace Avalonia.Controls
// bound properties controlling the palette grid
if (palette != null)
{
PaletteColumnCount = palette.ColorCount;
SetCurrentValue(PaletteColumnCountProperty, palette.ColorCount);
List<Color> newPaletteColors = new List<Color>();
for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++)
@ -252,14 +247,14 @@ namespace Avalonia.Controls
}
}
PaletteColors = newPaletteColors;
SetCurrentValue(PaletteColorsProperty, newPaletteColors);
}
}
else if (change.Property == IsAlphaEnabledProperty)
{
// Manually coerce the HsvColor value
// (Color will be coerced automatically if HsvColor changes)
HsvColor = OnCoerceHsvColor(HsvColor);
SetCurrentValue(HsvColorProperty, OnCoerceHsvColor(HsvColor));
}
else if (change.Property == IsColorComponentsVisibleProperty ||
change.Property == IsColorPaletteVisibleProperty ||

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

@ -2,6 +2,7 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls.Converters
{
@ -10,6 +11,11 @@ namespace Avalonia.Controls.Converters
/// </summary>
public class ColorToHexConverter : IValueConverter
{
/// <summary>
/// Gets or sets the position of a color's alpha component relative to all other components.
/// </summary>
public AlphaComponentPosition AlphaPosition { get; set; } = AlphaComponentPosition.Leading;
/// <inheritdoc/>
public object? Convert(
object? value,
@ -42,16 +48,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol == false)
{
// TODO: When .net standard 2.0 is dropped, replace the below line
//hexColor = hexColor.Replace("#", string.Empty, StringComparison.Ordinal);
hexColor = hexColor.Replace("#", string.Empty);
}
return hexColor;
return ToHexString(color, AlphaPosition, includeSymbol);
}
/// <inheritdoc/>
@ -62,21 +59,159 @@ namespace Avalonia.Controls.Converters
CultureInfo culture)
{
string hexValue = value?.ToString() ?? string.Empty;
return ParseHexString(hexValue, AlphaPosition) ?? AvaloniaProperty.UnsetValue;
}
/// <summary>
/// Converts the given color to its hex color value string representation.
/// </summary>
/// <param name="color">The color to represent as a hex value string.</param>
/// <param name="alphaPosition">The output position of the alpha component.</param>
/// <param name="includeSymbol">Whether the hex symbol '#' will be added.</param>
/// <returns>The input color converted to its hex value string.</returns>
public static string ToHexString(
Color color,
AlphaComponentPosition alphaPosition,
bool includeSymbol = false)
{
uint intColor;
if (alphaPosition == AlphaComponentPosition.Trailing)
{
intColor = ((uint)color.R << 24) | ((uint)color.G << 16) | ((uint)color.B << 8) | (uint)color.A;
}
else
{
// Default is Leading alpha
intColor = ((uint)color.A << 24) | ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
}
if (Color.TryParse(hexValue, out Color color))
string hexColor = intColor.ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
if (includeSymbol)
{
hexColor = '#' + hexColor;
}
return hexColor;
}
/// <summary>
/// Parses a hex color value string into a new <see cref="Color"/>.
/// </summary>
/// <param name="hexColor">The hex color string to parse.</param>
/// <param name="alphaPosition">The input position of the alpha component.</param>
/// <returns>The parsed <see cref="Color"/>; otherwise, null.</returns>
public static Color? ParseHexString(
string hexColor,
AlphaComponentPosition alphaPosition)
{
hexColor = hexColor.Trim();
if (!hexColor.StartsWith("#", StringComparison.Ordinal))
{
hexColor = "#" + hexColor;
}
if (TryParseHexFormat(hexColor.AsSpan(), alphaPosition, out Color color))
{
return color;
}
else if (hexValue.StartsWith("#", StringComparison.Ordinal) == false &&
Color.TryParse("#" + hexValue, out Color color2))
return null;
}
/// <summary>
/// Parses the given span of characters representing a hex color value into a new <see cref="Color"/>.
/// </summary>
/// <remarks>
/// This is based on the Color.TryParseHexFormat() method.
/// It is copied because it needs to be extended to handle alpha position.
/// However, the alpha position enum is only available in the controls namespace with the ColorPicker control.
/// </remarks>
private static bool TryParseHexFormat(
ReadOnlySpan<char> s,
AlphaComponentPosition alphaPosition,
out Color color)
{
static bool TryParseCore(ReadOnlySpan<char> input, AlphaComponentPosition alphaPosition, ref Color color)
{
return color2;
var alphaComponent = 0u;
if (input.Length == 6)
{
if (alphaPosition == AlphaComponentPosition.Trailing)
{
alphaComponent = 0x000000FF;
}
else
{
alphaComponent = 0xFF000000;
}
}
else if (input.Length != 8)
{
return false;
}
if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
{
return false;
}
if (alphaComponent != 0)
{
if (alphaPosition == AlphaComponentPosition.Trailing)
{
parsed = (parsed << 8) | alphaComponent;
}
else
{
parsed = parsed | alphaComponent;
}
}
if (alphaPosition == AlphaComponentPosition.Trailing)
{
// #RRGGBBAA
color = new Color(
a: (byte)(parsed & 0xFF),
r: (byte)((parsed >> 24) & 0xFF),
g: (byte)((parsed >> 16) & 0xFF),
b: (byte)((parsed >> 8) & 0xFF));
}
else
{
// #AARRGGBB
color = new Color(
a: (byte)((parsed >> 24) & 0xFF),
r: (byte)((parsed >> 16) & 0xFF),
g: (byte)((parsed >> 8) & 0xFF),
b: (byte)(parsed & 0xFF));
}
return true;
}
else
color = default;
ReadOnlySpan<char> input = s.Slice(1);
// Handle shorthand cases like #FFF (RGB) or #FFFF (ARGB).
if (input.Length == 3 || input.Length == 4)
{
// Invalid hex color value provided
return AvaloniaProperty.UnsetValue;
var extendedLength = 2 * input.Length;
Span<char> extended = stackalloc char[extendedLength];
for (int i = 0; i < input.Length; i++)
{
extended[2 * i + 0] = input[i];
extended[2 * i + 1] = input[i];
}
return TryParseCore(extended, alphaPosition, ref color);
}
return TryParseCore(input, alphaPosition, ref color);
}
}
}

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

@ -1,6 +1,6 @@
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Utilities;
@ -11,8 +11,11 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public static class ColorHelper
{
private static readonly Dictionary<Color, string> cachedDisplayNames = new Dictionary<Color, string>();
private static readonly object cacheMutex = new object();
private static readonly Dictionary<HsvColor, string> _cachedDisplayNames = new Dictionary<HsvColor, string>();
private static readonly Dictionary<KnownColor, string> _cachedKnownColorNames = new Dictionary<KnownColor, string>();
private static readonly object _displayNameCacheMutex = new object();
private static readonly object _knownColorCacheMutex = new object();
private static readonly KnownColor[] _knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
/// <summary>
/// Gets the relative (perceptual) luminance/brightness of the given color.
@ -59,7 +62,36 @@ namespace Avalonia.Controls.Primitives
/// <returns>The approximate color display name.</returns>
public static string ToDisplayName(Color color)
{
// Without rounding, there are 16,777,216 possible RGB colors (without alpha).
var hsvColor = color.ToHsv();
// Handle extremes that are outside the below algorithm
if (color.A == 0x00)
{
return GetDisplayName(KnownColor.Transparent);
}
// HSV ----------------------------------------------------------------------
//
// There are far too many possible HSV colors to cache and search through
// for performance reasons. Therefore, the HSV color is rounded.
// Rounding is tolerable in this algorithm because it is perception based.
// Hue is the most important for user perception so is rounded the least.
// Then there is a lot of loss in rounding the saturation and value components
// which are not as closely related to perceived color.
//
// Hue : Round to nearest int (0..360)
// Saturation : Round to the nearest 1/10 (0..1)
// Value : Round to the nearest 1/10 (0..1)
// Alpha : Is ignored in this algorithm
//
// Rounding results in ~36_000 values to cache in the worse case.
//
// RGB ----------------------------------------------------------------------
//
// The original algorithm worked in RGB color space.
// If this code is every adjusted to work in RGB again note the following:
//
// Without rounding, there are 16_777_216 possible RGB colors (without alpha).
// This is too many to cache and search through for performance reasons.
// 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
@ -68,42 +100,67 @@ namespace Avalonia.Controls.Primitives
// 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;
var roundedColor = new Color(
0xFF,
Convert.ToByte(Math.Round(color.R / rounding) * rounding),
Convert.ToByte(Math.Round(color.G / rounding) * rounding),
Convert.ToByte(Math.Round(color.B / rounding) * rounding));
var roundedHsvColor = new HsvColor(
1.0,
Math.Round(hsvColor.H, 0),
Math.Round(hsvColor.S, 1),
Math.Round(hsvColor.V, 1));
// Attempt to use a previously cached display name
lock (cacheMutex)
lock (_displayNameCacheMutex)
{
if (cachedDisplayNames.TryGetValue(roundedColor, out var displayName))
if (_cachedDisplayNames.TryGetValue(roundedHsvColor, out var displayName))
{
return displayName;
}
}
// Build the KnownColor name cache if it doesn't already exist
lock (_knownColorCacheMutex)
{
if (_cachedKnownColorNames.Count == 0)
{
for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
{
KnownColor knownColor = _knownColors[i];
// Some known colors have the same numerical value. For example:
// - Aqua = 0xff00ffff
// - Cyan = 0xff00ffff
//
// This is not possible to represent in a dictionary which requires
// unique values. Therefore, only the first value is used.
if (!_cachedKnownColorNames.ContainsKey(knownColor))
{
_cachedKnownColorNames.Add(knownColor, GetDisplayName(knownColor));
}
}
}
}
// Find the closest known color by measuring 3D Euclidean distance (ignore alpha)
// This is done in HSV color space to most closely match user-perception
var closestKnownColor = KnownColor.None;
var closestKnownColorDistance = double.PositiveInfinity;
var knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
for (int i = 1; i < knownColors.Length; i++) // Skip 'None'
for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
{
KnownColor knownColor = _knownColors[i];
// Transparent is skipped since alpha is ignored making it equivalent to White
if (knownColors[i] != KnownColor.Transparent)
if (knownColor != KnownColor.Transparent)
{
Color knownColor = KnownColors.ToColor(knownColors[i]);
HsvColor knownHsvColor = KnownColors.ToColor(knownColor).ToHsv();
double distance = Math.Sqrt(
Math.Pow((double)(roundedColor.R - knownColor.R), 2.0) +
Math.Pow((double)(roundedColor.G - knownColor.G), 2.0) +
Math.Pow((double)(roundedColor.B - knownColor.B), 2.0));
Math.Pow((roundedHsvColor.H - knownHsvColor.H), 2.0) +
Math.Pow((roundedHsvColor.S - knownHsvColor.S), 2.0) +
Math.Pow((roundedHsvColor.V - knownHsvColor.V), 2.0));
if (distance < closestKnownColorDistance)
{
closestKnownColor = knownColors[i];
closestKnownColor = knownColor;
closestKnownColorDistance = distance;
}
}
@ -113,26 +170,19 @@ namespace Avalonia.Controls.Primitives
// Cache results for next time as well
if (closestKnownColor != KnownColor.None)
{
var sb = StringBuilderCache.Acquire();
string name = closestKnownColor.ToString();
string? displayName;
// Add spaces converting PascalCase to human-readable names
for (int i = 0; i < name.Length; i++)
lock (_knownColorCacheMutex)
{
if (i != 0 &&
char.IsUpper(name[i]))
if (!_cachedKnownColorNames.TryGetValue(closestKnownColor, out displayName))
{
sb.Append(' ');
displayName = GetDisplayName(closestKnownColor);
}
sb.Append(name[i]);
}
string displayName = StringBuilderCache.GetStringAndRelease(sb);
lock (cacheMutex)
lock (_displayNameCacheMutex)
{
cachedDisplayNames.Add(roundedColor, displayName);
_cachedDisplayNames.Add(roundedHsvColor, displayName);
}
return displayName;
@ -142,5 +192,35 @@ namespace Avalonia.Controls.Primitives
return string.Empty;
}
}
/// <summary>
/// Gets the human-readable display name for the given <see cref="KnownColor"/>.
/// </summary>
/// <remarks>
/// This currently uses the <see cref="KnownColor"/> enum value's C# name directly
/// which limits it to the EN language only. In the future this should be localized
/// to other cultures.
/// </remarks>
/// <param name="knownColor">The <see cref="KnownColor"/> to get the display name for.</param>
/// <returns>The human-readable display name for the given <see cref="KnownColor"/>.</returns>
private static string GetDisplayName(KnownColor knownColor)
{
var sb = StringBuilderCache.Acquire();
string name = knownColor.ToString();
// Add spaces converting PascalCase to human-readable names
for (int i = 0; i < name.Length; i++)
{
if (i != 0 &&
char.IsUpper(name[i]))
{
sb.Append(' ');
}
sb.Append(name[i]);
}
return StringBuilderCache.GetStringAndRelease(sb);
}
}
}

2
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@ -4,8 +4,6 @@
// Licensed to The Avalonia Project under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Layout;

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

@ -42,7 +42,8 @@
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout FlyoutPresenterClasses="nopadding">
<Flyout FlyoutPresenterClasses="nopadding"
Placement="Top">
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
@ -216,6 +217,7 @@
Content="RGB"
CornerRadius="4,0,0,4"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -223,6 +225,7 @@
Content="HSV"
CornerRadius="0,4,4,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

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

@ -1,13 +1,20 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
Therefore, there are not currently separate resources to control it. -->
<x:Double x:Key="ColorSliderSize">20</x:Double>
<x:Double x:Key="ColorSliderTrackSize">20</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
@ -25,27 +32,28 @@
<Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
Height="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalAlignment="Center"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
@ -82,7 +90,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
@ -97,26 +105,27 @@
<Style Selector="^:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
Width="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
@ -154,7 +163,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"

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

@ -8,7 +8,6 @@
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@ -465,6 +464,7 @@
Content="RGB"
CornerRadius="4,0,0,4"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -472,6 +472,7 @@
Content="HSV"
CornerRadius="0,4,4,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

4
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -42,7 +42,7 @@
</Panel>
</DropDownButton.Content>
<DropDownButton.Flyout>
<Flyout>
<Flyout Placement="Top">
<!-- The following is copy-pasted from the ColorView's control template.
It MUST always be kept in sync with the ColorView (which is master).
@ -216,6 +216,7 @@
Content="RGB"
CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -223,6 +224,7 @@
Content="HSV"
CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

67
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml

@ -1,13 +1,20 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
Therefore, there are not currently separate resources to control it. -->
<x:Double x:Key="ColorSliderSize">20</x:Double>
<x:Double x:Key="ColorSliderTrackSize">20</x:Double>
<CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
<CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
<ControlTheme x:Key="ColorSliderThumbTheme"
TargetType="Thumb">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeForegroundBrush}" />
<!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
@ -25,27 +32,28 @@
<Style Selector="^:horizontal">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Height" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
Height="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VerticalAlignment="Center"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
@ -82,7 +90,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"
@ -97,26 +105,27 @@
<Style Selector="^:vertical">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="Width" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
<Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type ColorSlider}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Margin="{TemplateBinding Padding}">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="{TemplateBinding Background}"
RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
<Track Name="PART_Track"
HorizontalAlignment="Stretch"
Width="{DynamicResource ColorSliderTrackSize}"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
@ -154,7 +163,7 @@
</RepeatButton>
</Track.IncreaseButton>
<Thumb Name="ColorSliderThumb"
Theme="{StaticResource ColorSliderThumbTheme}"
Theme="{DynamicResource ColorSliderThumbTheme}"
Margin="0"
Padding="0"
DataContext="{TemplateBinding Value}"

3
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -8,7 +8,6 @@
<pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
<converters:ColorToHexConverter x:Key="ColorToHexConverter" />
<converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
<globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
<x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@ -427,6 +426,7 @@
Content="RGB"
CornerRadius="0,0,0,0"
BorderThickness="1,1,0,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
<RadioButton x:Name="HsvRadioButton"
Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@ -434,6 +434,7 @@
Content="HSV"
CornerRadius="0,0,0,0"
BorderThickness="0,1,1,1"
Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
</Grid>
<Grid x:Name="HexInputGrid"

8
src/Avalonia.Controls/Slider.cs

@ -86,10 +86,10 @@ namespace Avalonia.Controls
TickBar.TicksProperty.AddOwner<Slider>();
// Slider required parts
private bool _isDragging;
private Track? _track;
private Button? _decreaseButton;
private Button? _increaseButton;
protected bool _isDragging;
protected Track? _track;
protected Button? _decreaseButton;
protected Button? _increaseButton;
private IDisposable? _decreaseButtonPressDispose;
private IDisposable? _decreaseButtonReleaseDispose;
private IDisposable? _increaseButtonSubscription;

2
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -143,6 +143,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
InlineData(PixelFormatEnum.Gray4),
InlineData(PixelFormatEnum.Gray8),
InlineData(PixelFormatEnum.Gray16),
InlineData(PixelFormatEnum.Rgb24),
InlineData(PixelFormatEnum.Bgr24),
InlineData(PixelFormatEnum.Gray32Float),
InlineData(PixelFormatEnum.Rgba64),
InlineData(PixelFormatEnum.Rgba64, AlphaFormat.Premul),

Loading…
Cancel
Save