浏览代码

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
robloo 1 月之前
父节点
当前提交
6c33fe192a

+ 6 - 6
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>

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

+ 54 - 2
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,

+ 21 - 1
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()
         {