Browse Source

Update Brush.Parse() to Handle all Color Formats (#19526)

* Add unit test for all color format Brush parsing

* Support parsing all known color formats in Brush.Parse()

* Update property syntax

* Remove unnecessary usings

* Use an actual hex color

* Fix new brush test

* Switch order of brush parsing so known color optimizations are used

* Add more documentation comments to KnownColors

* Fix displayed text in BorderPage

InnerBorderEdge not InnerBorder
pull/19540/head
robloo 6 months ago
committed by GitHub
parent
commit
6c33fe192a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      samples/ControlCatalog/Pages/BorderPage.xaml
  2. 30
      src/Avalonia.Base/Media/Brush.cs
  3. 56
      src/Avalonia.Base/Media/KnownColors.cs
  4. 22
      tests/Avalonia.Base.UnitTests/Media/BrushTests.cs

12
samples/ControlCatalog/Pages/BorderPage.xaml

@ -20,26 +20,26 @@
<Border Background="{DynamicResource SystemAccentColorDark1}"
BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}"
BackgroundSizing="CenterBorder"
BorderThickness="8"
BorderThickness="8"
Padding="12">
<TextBlock>Background And CenterBorder</TextBlock>
</Border>
<Border Background="{DynamicResource SystemAccentColorDark1}"
BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}"
BackgroundSizing="InnerBorderEdge"
BorderThickness="8"
BorderThickness="8"
Padding="12">
<TextBlock>Background And InnerBorder</TextBlock>
<TextBlock>Background And InnerBorderEdge</TextBlock>
</Border>
<Border Background="{DynamicResource SystemAccentColorDark1}"
BorderBrush="{DynamicResource SemiTransparentSystemAccentBrush}"
BackgroundSizing="OuterBorderEdge"
BorderThickness="8"
BorderThickness="8"
Padding="12">
<TextBlock>Background And OuterBorderEdge</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="4"
BorderThickness="4"
CornerRadius="8"
Padding="16">
<TextBlock>Rounded Corners</TextBlock>
@ -56,6 +56,6 @@
<Image Source="/Assets/maple-leaf-888807_640.jpg" Stretch="UniformToFill" />
</Border>
<TextBlock Text="Border with Clipping" HorizontalAlignment="Center" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

30
src/Avalonia.Base/Media/Brush.cs

@ -1,9 +1,7 @@
using System;
using System.ComponentModel;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
using Avalonia.Reactive;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
@ -40,8 +38,8 @@ namespace Avalonia.Media
/// </summary>
public double Opacity
{
get { return GetValue(OpacityProperty); }
set { SetValue(OpacityProperty, value); }
get => GetValue(OpacityProperty);
set => SetValue(OpacityProperty, value);
}
/// <summary>
@ -49,8 +47,8 @@ namespace Avalonia.Media
/// </summary>
public ITransform? Transform
{
get { return GetValue(TransformProperty); }
set { SetValue(TransformProperty, value); }
get => GetValue(TransformProperty);
set => SetValue(TransformProperty, value);
}
/// <summary>
@ -73,28 +71,30 @@ namespace Avalonia.Media
if (s.Length > 0)
{
if (s[0] == '#')
{
return new ImmutableSolidColorBrush(Color.Parse(s));
}
// Attempt to get a cached known brush first
// This is a performance optimization for known colors
var brush = KnownColors.GetKnownBrush(s);
if (brush != null)
{
return brush;
}
if (Color.TryParse(s, out Color color))
{
return new ImmutableSolidColorBrush(color);
}
}
throw new FormatException($"Invalid brush string: '{s}'.");
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == TransformProperty)
_resource.ProcessPropertyChangeNotification(change);
RegisterForSerialization();
base.OnPropertyChanged(change);
}
@ -126,7 +126,7 @@ namespace Avalonia.Media
if(_resource.Release(c))
OnUnreferencedFromCompositor(c);
}
protected virtual void OnUnreferencedFromCompositor(Compositor c)
{
if (Transform is ICompositionRenderResource<ITransform> resource)
@ -139,7 +139,7 @@ namespace Avalonia.Media
{
ServerCompositionSimpleBrush.SerializeAllChanges(writer, Opacity, TransformOrigin, Transform.GetServer(c));
}
void ICompositorSerializable.SerializeChanges(Compositor c, BatchStreamWriter writer) => SerializeChanges(c, writer);
}
}

56
src/Avalonia.Base/Media/KnownColors.cs

@ -1,8 +1,7 @@
using System;
using System.Reflection;
using System.Collections.Generic;
using Avalonia.SourceGenerator;
using System.Diagnostics.CodeAnalysis;
using Avalonia.SourceGenerator;
namespace Avalonia.Media
{
@ -45,13 +44,47 @@ namespace Avalonia.Media
}
#if !BUILDTASK
/// <summary>
/// Attempts to resolve a color name string to a solid color brush.
/// </summary>
/// <remarks>
/// <para>
/// Returns a cached immutable brush if the name matches one of the predefined
/// <see cref="KnownColor"/> values. Repeated calls with the same color name will
/// return the same brush instance.
/// </para>
/// <para>
/// The lookup is case-sensitive and depends on the set of predefined known colors.
/// </para>
/// </remarks>
/// <param name="s">
/// The color name to look up (for example, <c>"Red"</c> or <c>"CornflowerBlue"</c>).
/// </param>
/// <returns>
/// An <see cref="ISolidColorBrush"/> corresponding to the specified name,
/// or <c>null</c> if the color name is not recognized.
/// </returns>
public static ISolidColorBrush? GetKnownBrush(string s)
{
var color = GetKnownColor(s);
return color != KnownColor.None ? color.ToBrush() : null;
}
#endif
/// <summary>
/// Attempts to resolve a color name string to a <see cref="KnownColor"/> value.
/// </summary>
/// <remarks>
/// The lookup is case-sensitive and depends on the set of predefined known colors.
/// </remarks>
/// <param name="s">
/// The color name to look up (for example, <c>Red</c> or <c>CornflowerBlue</c>).
/// </param>
/// <returns>
/// A <see cref="KnownColor"/> value if the name is recognized; otherwise <see cref="KnownColor.None"/>.
/// </returns>
public static KnownColor GetKnownColor(string s)
{
if (_knownColorNames.TryGetValue(s, out var color))
@ -76,6 +109,20 @@ namespace Avalonia.Media
}
#if !BUILDTASK
/// <summary>
/// Converts a <see cref="KnownColor"/> value to an immutable solid color brush.
/// </summary>
/// <remarks>
/// This method maintains an internal cache of brushes to avoid unnecessary allocations.
/// If the same <paramref name="color"/> is requested multiple times, the same immutable brush
/// instance is returned.
/// </remarks>
/// <param name="color">The <see cref="KnownColor"/> to convert.</param>
/// <returns>
/// An <see cref="IImmutableSolidColorBrush"/> instance representing the specified color.
/// Brushes created from known colors are cached and reused for efficiency.
/// </returns>
public static IImmutableSolidColorBrush ToBrush(this KnownColor color)
{
lock (_knownBrushes)
@ -89,9 +136,14 @@ namespace Avalonia.Media
return brush;
}
}
#endif
}
/// <summary>
/// Defines all known colors by name along with their 32-bit ARGB value.
/// </summary>
internal enum KnownColor : uint
{
None,

22
tests/Avalonia.Base.UnitTests/Media/BrushTests.cs

@ -1,6 +1,5 @@
using System;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Drawing;
using Xunit;
namespace Avalonia.Base.UnitTests.Media
@ -79,6 +78,27 @@ namespace Avalonia.Base.UnitTests.Media
Assert.Throws<FormatException>(() => Brush.Parse("#ff808g80"));
}
[Theory]
[InlineData("rgb(255, 128, 64)")]
[InlineData("rgba(255, 128, 64, 0.5)")]
[InlineData("hsl(120, 100%, 50%)")]
[InlineData("hsla(120, 100%, 50%, 0.5)")]
[InlineData("hsv(300, 100%, 25%)")]
[InlineData("hsva(300, 100%, 25%, 0.75)")]
[InlineData("#40ff8844")]
[InlineData("Green")]
public void Parse_Parses_All_Color_Format_Brushes(string input)
{
var brush = Brush.Parse(input);
Assert.IsAssignableFrom<ISolidColorBrush>(brush);
// The ColorTests already validate all color formats are parsed properly
// Since Brush.Parse() forwards to Color.Parse() we don't need to repeat this
// We can simply check if the parsed Brush's color matches what Color.Parse provides
var expected = Color.Parse(input);
Assert.Equal(expected, (brush as ISolidColorBrush)?.Color);
}
[Fact]
public void Changing_Opacity_Raises_Invalidated()
{

Loading…
Cancel
Save