From 3d02651f68742ac00c24543d433139a19c8d0209 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Fri, 8 May 2026 18:11:53 +1000 Subject: [PATCH] refactor(media): clarify color CSS formatters Color formatting now exposes the same IFormattable implementation in the BuildTask compile path as in normal builds. Keeping the interface conditional made the public contract harder to reason about even though the formatting members are System-only APIs. Remove the BuildTask guard from the formatting members, combine alpha and no-alpha CSS helpers behind includeAlpha parameters, and rename the helpers so future changes preserve the CSS-compliant output contract. --- src/Avalonia.Base/Media/Color.cs | 55 +++++++++++++++++-------- src/Avalonia.Base/Media/HslColor.cs | 64 +++++++++++++++++------------ src/Avalonia.Base/Media/HsvColor.cs | 64 +++++++++++++++++------------ 3 files changed, 112 insertions(+), 71 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 5463df0bbb..624513f065 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -21,10 +21,7 @@ namespace Avalonia.Media #if !BUILDTASK public #endif - readonly struct Color : IEquatable -#if !BUILDTASK - , IFormattable -#endif + readonly struct Color : IEquatable, IFormattable { private const double byteToDouble = 1.0 / 255; @@ -464,7 +461,6 @@ namespace Avalonia.Media } } -#if !BUILDTASK /// /// Returns a formatted string representation of the color. /// @@ -501,10 +497,10 @@ namespace Avalonia.Media "x" => $"#{R:X2}{G:X2}{B:X2}", "H" => $"#{R:X2}{G:X2}{B:X2}{A:X2}", - "R" => string.Format(CultureInfo.InvariantCulture, "rgba({0}, {1}, {2}, {3:F2})", R, G, B, A * byteToDouble), - "r" => string.Format(CultureInfo.InvariantCulture, "rgb({0}, {1}, {2})", R, G, B), - "R%" => FormatRgbaPercent(), - "r%" => FormatRgbPercent(), + "R" => FormatRgbCss(), + "r" => FormatRgbCss(includeAlpha: false), + "R%" => FormatRgbPercentCss(), + "r%" => FormatRgbPercentCss(includeAlpha: false), "L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider), "V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider), @@ -513,23 +509,48 @@ namespace Avalonia.Media }; } - private string FormatRgbaPercent() + /// + /// Formats the color as a CSS rgb() or rgba() string using absolute RGB components and fractional alpha. + /// + private string FormatRgbCss(bool includeAlpha = true) { - int rPct = (int)Math.Round(R * byteToDouble * 100.0); - int gPct = (int)Math.Round(G * byteToDouble * 100.0); - int bPct = (int)Math.Round(B * byteToDouble * 100.0); - int aPct = (int)Math.Round(A * byteToDouble * 100.0); - return string.Format(CultureInfo.InvariantCulture, "rgba({0}%, {1}%, {2}%, {3}%)", rPct, gPct, bPct, aPct); + if (includeAlpha) + { + return string.Format( + CultureInfo.InvariantCulture, + "rgba({0}, {1}, {2}, {3:F2})", + R, + G, + B, + A * byteToDouble); + } + + return string.Format(CultureInfo.InvariantCulture, "rgb({0}, {1}, {2})", R, G, B); } - private string FormatRgbPercent() + /// + /// Formats the color as a CSS rgb() or rgba() string with all components as percentages. + /// + private string FormatRgbPercentCss(bool includeAlpha = true) { int rPct = (int)Math.Round(R * byteToDouble * 100.0); int gPct = (int)Math.Round(G * byteToDouble * 100.0); int bPct = (int)Math.Round(B * byteToDouble * 100.0); + + if (includeAlpha) + { + int aPct = (int)Math.Round(A * byteToDouble * 100.0); + return string.Format( + CultureInfo.InvariantCulture, + "rgba({0}%, {1}%, {2}%, {3}%)", + rPct, + gPct, + bPct, + aPct); + } + return string.Format(CultureInfo.InvariantCulture, "rgb({0}%, {1}%, {2}%)", rPct, gPct, bPct); } -#endif /// /// Returns the integer representation of the color. diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 0bae48417b..2a09575d69 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -17,10 +17,7 @@ namespace Avalonia.Media #if !BUILDTASK public #endif - readonly struct HslColor : IEquatable -#if !BUILDTASK - , IFormattable -#endif + readonly struct HslColor : IEquatable, IFormattable { /// /// Initializes a new instance of the struct. @@ -180,7 +177,6 @@ namespace Avalonia.Media return HslColor.ToHsv(H, S, L, A); } -#if !BUILDTASK /// /// Returns a formatted string representation of the HSL color. /// @@ -215,10 +211,10 @@ namespace Avalonia.Media { "X" or "x" or "H" or "R" or "r" or "R%" or "r%" => ToRgb().ToString(format, formatProvider), - "L" => FormatHsla(), - "l" => FormatHsl(), - "L%" => FormatHslaPercent(), - "l%" => FormatHslPercent(), + "L" => FormatHslCss(), + "l" => FormatHslCss(includeAlpha: false), + "L%" => FormatHslPercentCss(), + "l%" => FormatHslPercentCss(includeAlpha: false), "V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider), @@ -226,39 +222,53 @@ namespace Avalonia.Media }; } - private string FormatHsla() + /// + /// Formats the color as a CSS hsl() or hsla() string using degrees, percent saturation/lightness, + /// and fractional alpha. + /// + private string FormatHslCss(bool includeAlpha = true) { int hDeg = (int)Math.Round(H); int sPct = (int)Math.Round(S * 100.0); int lPct = (int)Math.Round(L * 100.0); - return string.Format(CultureInfo.InvariantCulture, "hsla({0}, {1}%, {2}%, {3:F2})", hDeg, sPct, lPct, A); - } - private string FormatHsl() - { - int hDeg = (int)Math.Round(H); - int sPct = (int)Math.Round(S * 100.0); - int lPct = (int)Math.Round(L * 100.0); + if (includeAlpha) + { + return string.Format( + CultureInfo.InvariantCulture, + "hsla({0}, {1}%, {2}%, {3:F2})", + hDeg, + sPct, + lPct, + A); + } + return string.Format(CultureInfo.InvariantCulture, "hsl({0}, {1}%, {2}%)", hDeg, sPct, lPct); } - private string FormatHslaPercent() + /// + /// Formats the color as a CSS hsl() or hsla() string with all components as percentages. + /// + private string FormatHslPercentCss(bool includeAlpha = true) { int hPct = (int)Math.Round(H / 360.0 * 100.0); int sPct = (int)Math.Round(S * 100.0); int lPct = (int)Math.Round(L * 100.0); - int aPct = (int)Math.Round(A * 100.0); - return string.Format(CultureInfo.InvariantCulture, "hsla({0}%, {1}%, {2}%, {3}%)", hPct, sPct, lPct, aPct); - } - private string FormatHslPercent() - { - int hPct = (int)Math.Round(H / 360.0 * 100.0); - int sPct = (int)Math.Round(S * 100.0); - int lPct = (int)Math.Round(L * 100.0); + if (includeAlpha) + { + int aPct = (int)Math.Round(A * 100.0); + return string.Format( + CultureInfo.InvariantCulture, + "hsla({0}%, {1}%, {2}%, {3}%)", + hPct, + sPct, + lPct, + aPct); + } + return string.Format(CultureInfo.InvariantCulture, "hsl({0}%, {1}%, {2}%)", hPct, sPct, lPct); } -#endif /// public override string ToString() diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 5f36dff9ef..7ff180c8c5 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -17,10 +17,7 @@ namespace Avalonia.Media #if !BUILDTASK public #endif - readonly struct HsvColor : IEquatable -#if !BUILDTASK - , IFormattable -#endif + readonly struct HsvColor : IEquatable, IFormattable { /// /// Initializes a new instance of the struct. @@ -210,7 +207,6 @@ namespace Avalonia.Media return HsvColor.ToHsl(H, S, V, A); } -#if !BUILDTASK /// /// Returns a formatted string representation of the HSV color. /// @@ -247,48 +243,62 @@ namespace Avalonia.Media "L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider), - "V" => FormatHsva(), - "v" => FormatHsv(), - "V%" => FormatHsvaPercent(), - "v%" => FormatHsvPercent(), + "V" => FormatHsvCss(), + "v" => FormatHsvCss(includeAlpha: false), + "V%" => FormatHsvPercentCss(), + "v%" => FormatHsvPercentCss(includeAlpha: false), _ => throw new FormatException($"Format string '{format}' is not supported.") }; } - private string FormatHsva() + /// + /// Formats the color as a CSS hsv() or hsva() string using degrees, percent saturation/value, + /// and fractional alpha. + /// + private string FormatHsvCss(bool includeAlpha = true) { int hDeg = (int)Math.Round(H); int sPct = (int)Math.Round(S * 100.0); int vPct = (int)Math.Round(V * 100.0); - return string.Format(CultureInfo.InvariantCulture, "hsva({0}, {1}%, {2}%, {3:F2})", hDeg, sPct, vPct, A); - } - private string FormatHsv() - { - int hDeg = (int)Math.Round(H); - int sPct = (int)Math.Round(S * 100.0); - int vPct = (int)Math.Round(V * 100.0); + if (includeAlpha) + { + return string.Format( + CultureInfo.InvariantCulture, + "hsva({0}, {1}%, {2}%, {3:F2})", + hDeg, + sPct, + vPct, + A); + } + return string.Format(CultureInfo.InvariantCulture, "hsv({0}, {1}%, {2}%)", hDeg, sPct, vPct); } - private string FormatHsvaPercent() + /// + /// Formats the color as a CSS hsv() or hsva() string with all components as percentages. + /// + private string FormatHsvPercentCss(bool includeAlpha = true) { int hPct = (int)Math.Round(H / 360.0 * 100.0); int sPct = (int)Math.Round(S * 100.0); int vPct = (int)Math.Round(V * 100.0); - int aPct = (int)Math.Round(A * 100.0); - return string.Format(CultureInfo.InvariantCulture, "hsva({0}%, {1}%, {2}%, {3}%)", hPct, sPct, vPct, aPct); - } - private string FormatHsvPercent() - { - int hPct = (int)Math.Round(H / 360.0 * 100.0); - int sPct = (int)Math.Round(S * 100.0); - int vPct = (int)Math.Round(V * 100.0); + if (includeAlpha) + { + int aPct = (int)Math.Round(A * 100.0); + return string.Format( + CultureInfo.InvariantCulture, + "hsva({0}%, {1}%, {2}%, {3}%)", + hPct, + sPct, + vPct, + aPct); + } + return string.Format(CultureInfo.InvariantCulture, "hsv({0}%, {1}%, {2}%)", hPct, sPct, vPct); } -#endif /// public override string ToString()