Browse Source

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.
pull/20919/head
Nathan Nguyen 5 months ago
parent
commit
3d02651f68
  1. 55
      src/Avalonia.Base/Media/Color.cs
  2. 64
      src/Avalonia.Base/Media/HslColor.cs
  3. 64
      src/Avalonia.Base/Media/HsvColor.cs

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

@ -21,10 +21,7 @@ namespace Avalonia.Media
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
readonly struct Color : IEquatable<Color> readonly struct Color : IEquatable<Color>, IFormattable
#if !BUILDTASK
, IFormattable
#endif
{ {
private const double byteToDouble = 1.0 / 255; private const double byteToDouble = 1.0 / 255;
@ -464,7 +461,6 @@ namespace Avalonia.Media
} }
} }
#if !BUILDTASK
/// <summary> /// <summary>
/// Returns a formatted string representation of the color. /// Returns a formatted string representation of the color.
/// </summary> /// </summary>
@ -501,10 +497,10 @@ namespace Avalonia.Media
"x" => $"#{R:X2}{G:X2}{B:X2}", "x" => $"#{R:X2}{G:X2}{B:X2}",
"H" => $"#{R:X2}{G:X2}{B:X2}{A: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" => FormatRgbCss(),
"r" => string.Format(CultureInfo.InvariantCulture, "rgb({0}, {1}, {2})", R, G, B), "r" => FormatRgbCss(includeAlpha: false),
"R%" => FormatRgbaPercent(), "R%" => FormatRgbPercentCss(),
"r%" => FormatRgbPercent(), "r%" => FormatRgbPercentCss(includeAlpha: false),
"L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider), "L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider),
"V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider), "V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider),
@ -513,23 +509,48 @@ namespace Avalonia.Media
}; };
} }
private string FormatRgbaPercent() /// <summary>
/// Formats the color as a CSS rgb() or rgba() string using absolute RGB components and fractional alpha.
/// </summary>
private string FormatRgbCss(bool includeAlpha = true)
{ {
int rPct = (int)Math.Round(R * byteToDouble * 100.0); if (includeAlpha)
int gPct = (int)Math.Round(G * byteToDouble * 100.0); {
int bPct = (int)Math.Round(B * byteToDouble * 100.0); return string.Format(
int aPct = (int)Math.Round(A * byteToDouble * 100.0); CultureInfo.InvariantCulture,
return string.Format(CultureInfo.InvariantCulture, "rgba({0}%, {1}%, {2}%, {3}%)", rPct, gPct, bPct, aPct); "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() /// <summary>
/// Formats the color as a CSS rgb() or rgba() string with all components as percentages.
/// </summary>
private string FormatRgbPercentCss(bool includeAlpha = true)
{ {
int rPct = (int)Math.Round(R * byteToDouble * 100.0); int rPct = (int)Math.Round(R * byteToDouble * 100.0);
int gPct = (int)Math.Round(G * byteToDouble * 100.0); int gPct = (int)Math.Round(G * byteToDouble * 100.0);
int bPct = (int)Math.Round(B * 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); return string.Format(CultureInfo.InvariantCulture, "rgb({0}%, {1}%, {2}%)", rPct, gPct, bPct);
} }
#endif
/// <summary> /// <summary>
/// Returns the integer representation of the color. /// Returns the integer representation of the color.

64
src/Avalonia.Base/Media/HslColor.cs

@ -17,10 +17,7 @@ namespace Avalonia.Media
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
readonly struct HslColor : IEquatable<HslColor> readonly struct HslColor : IEquatable<HslColor>, IFormattable
#if !BUILDTASK
, IFormattable
#endif
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HslColor"/> struct. /// Initializes a new instance of the <see cref="HslColor"/> struct.
@ -180,7 +177,6 @@ namespace Avalonia.Media
return HslColor.ToHsv(H, S, L, A); return HslColor.ToHsv(H, S, L, A);
} }
#if !BUILDTASK
/// <summary> /// <summary>
/// Returns a formatted string representation of the HSL color. /// Returns a formatted string representation of the HSL color.
/// </summary> /// </summary>
@ -215,10 +211,10 @@ namespace Avalonia.Media
{ {
"X" or "x" or "H" or "R" or "r" or "R%" or "r%" => ToRgb().ToString(format, formatProvider), "X" or "x" or "H" or "R" or "r" or "R%" or "r%" => ToRgb().ToString(format, formatProvider),
"L" => FormatHsla(), "L" => FormatHslCss(),
"l" => FormatHsl(), "l" => FormatHslCss(includeAlpha: false),
"L%" => FormatHslaPercent(), "L%" => FormatHslPercentCss(),
"l%" => FormatHslPercent(), "l%" => FormatHslPercentCss(includeAlpha: false),
"V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider), "V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider),
@ -226,39 +222,53 @@ namespace Avalonia.Media
}; };
} }
private string FormatHsla() /// <summary>
/// Formats the color as a CSS hsl() or hsla() string using degrees, percent saturation/lightness,
/// and fractional alpha.
/// </summary>
private string FormatHslCss(bool includeAlpha = true)
{ {
int hDeg = (int)Math.Round(H); int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0); int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 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() if (includeAlpha)
{ {
int hDeg = (int)Math.Round(H); return string.Format(
int sPct = (int)Math.Round(S * 100.0); CultureInfo.InvariantCulture,
int lPct = (int)Math.Round(L * 100.0); "hsla({0}, {1}%, {2}%, {3:F2})",
hDeg,
sPct,
lPct,
A);
}
return string.Format(CultureInfo.InvariantCulture, "hsl({0}, {1}%, {2}%)", hDeg, sPct, lPct); return string.Format(CultureInfo.InvariantCulture, "hsl({0}, {1}%, {2}%)", hDeg, sPct, lPct);
} }
private string FormatHslaPercent() /// <summary>
/// Formats the color as a CSS hsl() or hsla() string with all components as percentages.
/// </summary>
private string FormatHslPercentCss(bool includeAlpha = true)
{ {
int hPct = (int)Math.Round(H / 360.0 * 100.0); int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0); int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 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() if (includeAlpha)
{ {
int hPct = (int)Math.Round(H / 360.0 * 100.0); int aPct = (int)Math.Round(A * 100.0);
int sPct = (int)Math.Round(S * 100.0); return string.Format(
int lPct = (int)Math.Round(L * 100.0); CultureInfo.InvariantCulture,
"hsla({0}%, {1}%, {2}%, {3}%)",
hPct,
sPct,
lPct,
aPct);
}
return string.Format(CultureInfo.InvariantCulture, "hsl({0}%, {1}%, {2}%)", hPct, sPct, lPct); return string.Format(CultureInfo.InvariantCulture, "hsl({0}%, {1}%, {2}%)", hPct, sPct, lPct);
} }
#endif
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()

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

@ -17,10 +17,7 @@ namespace Avalonia.Media
#if !BUILDTASK #if !BUILDTASK
public public
#endif #endif
readonly struct HsvColor : IEquatable<HsvColor> readonly struct HsvColor : IEquatable<HsvColor>, IFormattable
#if !BUILDTASK
, IFormattable
#endif
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct. /// Initializes a new instance of the <see cref="HsvColor"/> struct.
@ -210,7 +207,6 @@ namespace Avalonia.Media
return HsvColor.ToHsl(H, S, V, A); return HsvColor.ToHsl(H, S, V, A);
} }
#if !BUILDTASK
/// <summary> /// <summary>
/// Returns a formatted string representation of the HSV color. /// Returns a formatted string representation of the HSV color.
/// </summary> /// </summary>
@ -247,48 +243,62 @@ namespace Avalonia.Media
"L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider), "L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider),
"V" => FormatHsva(), "V" => FormatHsvCss(),
"v" => FormatHsv(), "v" => FormatHsvCss(includeAlpha: false),
"V%" => FormatHsvaPercent(), "V%" => FormatHsvPercentCss(),
"v%" => FormatHsvPercent(), "v%" => FormatHsvPercentCss(includeAlpha: false),
_ => throw new FormatException($"Format string '{format}' is not supported.") _ => throw new FormatException($"Format string '{format}' is not supported.")
}; };
} }
private string FormatHsva() /// <summary>
/// Formats the color as a CSS hsv() or hsva() string using degrees, percent saturation/value,
/// and fractional alpha.
/// </summary>
private string FormatHsvCss(bool includeAlpha = true)
{ {
int hDeg = (int)Math.Round(H); int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0); int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 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() if (includeAlpha)
{ {
int hDeg = (int)Math.Round(H); return string.Format(
int sPct = (int)Math.Round(S * 100.0); CultureInfo.InvariantCulture,
int vPct = (int)Math.Round(V * 100.0); "hsva({0}, {1}%, {2}%, {3:F2})",
hDeg,
sPct,
vPct,
A);
}
return string.Format(CultureInfo.InvariantCulture, "hsv({0}, {1}%, {2}%)", hDeg, sPct, vPct); return string.Format(CultureInfo.InvariantCulture, "hsv({0}, {1}%, {2}%)", hDeg, sPct, vPct);
} }
private string FormatHsvaPercent() /// <summary>
/// Formats the color as a CSS hsv() or hsva() string with all components as percentages.
/// </summary>
private string FormatHsvPercentCss(bool includeAlpha = true)
{ {
int hPct = (int)Math.Round(H / 360.0 * 100.0); int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0); int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 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() if (includeAlpha)
{ {
int hPct = (int)Math.Round(H / 360.0 * 100.0); int aPct = (int)Math.Round(A * 100.0);
int sPct = (int)Math.Round(S * 100.0); return string.Format(
int vPct = (int)Math.Round(V * 100.0); CultureInfo.InvariantCulture,
"hsva({0}%, {1}%, {2}%, {3}%)",
hPct,
sPct,
vPct,
aPct);
}
return string.Format(CultureInfo.InvariantCulture, "hsv({0}%, {1}%, {2}%)", hPct, sPct, vPct); return string.Format(CultureInfo.InvariantCulture, "hsv({0}%, {1}%, {2}%)", hPct, sPct, vPct);
} }
#endif
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()

Loading…
Cancel
Save