Browse Source

Support configurable AlphaComponentPosition in ColorView

The default now matches CSS and differs from XAML/WinUI. The CSS trailing alpha component is in wider use for end-users now and it also matches the default slider ordering in the UI.
pull/10439/head
robloo 3 years ago
parent
commit
23332cd048
  1. 2
      src/Avalonia.Base/Media/Color.cs
  2. 26
      src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs
  3. 18
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  4. 18
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  5. 169
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  6. 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  7. 1
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

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

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
{

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

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Controls.Converters;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
@ -25,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.
@ -43,7 +41,7 @@ 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)
{
@ -63,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);
}
}
@ -197,7 +195,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (ignorePropertyChanged)
if (_ignorePropertyChanged)
{
base.OnPropertyChanged(change);
return;
@ -206,7 +204,7 @@ namespace Avalonia.Controls
// Always keep the two color properties in sync
if (change.Property == ColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
SetCurrentValue(HsvColorProperty, Color.ToHsv());
SetColorToHexTextBox();
@ -215,11 +213,11 @@ namespace Avalonia.Controls
change.GetOldValue<Color>(),
change.GetNewValue<Color>()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == HsvColorProperty)
{
ignorePropertyChanged = true;
_ignorePropertyChanged = true;
SetCurrentValue(ColorProperty, HsvColor.ToRgb());
SetColorToHexTextBox();
@ -228,7 +226,7 @@ namespace Avalonia.Controls
change.GetOldValue<HsvColor>().ToRgb(),
change.GetNewValue<HsvColor>().ToRgb()));
ignorePropertyChanged = false;
_ignorePropertyChanged = false;
}
else if (change.Property == PaletteProperty)
{

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);
}
}
}

1
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>

1
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>

Loading…
Cancel
Save