Skip to content

feat: implement unified IFormattable on Color, HslColor, and HsvColor - #20919

Merged
MrJul merged 7 commits into
AvaloniaUI:mainfrom
NathanDrake2406:feat/color-tostring-formats
Aug 19, 2026
Merged

feat: implement unified IFormattable on Color, HslColor, and HsvColor#20919
MrJul merged 7 commits into
AvaloniaUI:mainfrom
NathanDrake2406:feat/color-tostring-formats

Conversation

@NathanDrake2406

@NathanDrake2406 NathanDrake2406 commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements IFormattable on all three color types (Color, HslColor, HsvColor) with a unified set of format specifiers — any type can output any format via auto-conversion, following the DateTime analogy discussed in review.

Also fixes a pre-existing bug: HslColor.ToString() was outputting hsva( instead of hsla(.

Closes #18725

Design

Convention: uppercase = include alpha (rgba/hsla/hsva prefix), lowercase = exclude alpha (rgb/hsl/hsv prefix). % suffix = percent mode.

Format Output Example
null/"" Type-specific default Red, hsla(230, 1, 0.5, 1)
"X" XAML hex with alpha #FFFF0000
"x" Hex without alpha #FF0000
"H" HTML hex with alpha #FF0000FF
"R" CSS rgba absolute rgba(255, 0, 0, 1.00)
"r" CSS rgb absolute rgb(255, 0, 0)
"R%" CSS rgba percent rgba(100%, 0%, 0%, 100%)
"r%" CSS rgb percent rgb(100%, 0%, 0%)
"L" CSS hsla hsla(0, 100%, 50%, 1.00)
"l" CSS hsl hsl(0, 100%, 50%)
"L%" HSL all percent hsla(0%, 100%, 50%, 100%)
"l%" HSL all percent hsl(0%, 100%, 50%)
"V" CSS hsva hsva(0, 100%, 100%, 1.00)
"v" CSS hsv hsv(0, 100%, 100%)
"V%" HSV all percent hsva(0%, 100%, 100%, 100%)
"v%" HSV all percent hsv(0%, 100%, 100%)

"C" and "A" are reserved for future complex format strings (hsv:C1,C2,C3,A).

Architecture

Each type only implements its native format natively and delegates cross-model formats via conversion:

  • Color handles hex + RGB natively; delegates L/VToHsl()/ToHsv()
  • HslColor handles HSL natively; delegates hex/RGB → ToRgb(), HSV → ToHsv()
  • HsvColor handles HSV natively; delegates hex/RGB → ToRgb(), HSL → ToHsl()

Zero duplicated formatting logic. Every delegation terminates in one hop.

Breaking changes from prior iteration

  • "h" dropped (identical to "x")
  • "C"/"c" removed, reserved for future use
  • "P"/"p" replaced by "R%"/"r%"
  • Uppercase specifiers now use rgba()/hsla()/hsva() prefix (CSS convention)

Test plan

  • 116 unit tests covering all specifiers on all types
  • Cross-type delegation verified (e.g. HslColor.ToString("X") → hex via RGB)
  • Reserved specifiers (C, c, A, a) throw FormatException
  • Removed specifiers (h, P, p) throw FormatException
  • IFormatProvider ignored (culture-invariant verified with fr-FR)
  • Default ToString() unchanged for backwards compatibility
  • hsla() bug fix verified (was hsva())

@MrJul MrJul added the feature label Mar 17, 2026
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.0.999-cibuild0063532-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@robloo

robloo commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Nice! Couple questions:

  1. Where did these format codes come from? They do make sense but if there is any precident in the ecosystem we should try to take advantage of it.
  2. We need to support HslColor and HsvColor as well. This is where there could be issues with the format strings but we could establish "L/l" and "V/v" as the other formatting codes. Then again we can generalize all this using the "component" terminology. So having "C/c" for ALL THREE colors may make sense too.
  3. There are people who need to be able to add/remove the alpha channel. These formatting codes don't directly support that. (you do support both XAML and HTML formatting which is awesome though!)
    • Related you seem to arbitrarily include/exclude the alpha channel based on if it is opaque. That is a bad policy in general as it could break parsing downstream if devs are expecting stable/consistent formatting strings.
  4. Have you considered more advanced formatting strings like date/time where users can have full control? In light of the above maybe the simple format strings aren't best here and we should have complex strings for maximum control.
    • This means the ability to fully control which components are included and what the format of that component is (percent/absolute).

@NathanDrake2406
NathanDrake2406 force-pushed the feat/color-tostring-formats branch from bfef8c5 to 7adf64c Compare March 18, 2026 02:36
@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

Nice! Couple questions:

1. Where did these format codes come from? They do make sense but if there is any precident in the ecosystem we should try to take advantage of it.

2. We need to support `HslColor` and `HsvColor` as well. This is where there could be issues with the format strings but we could establish "L/l" and "V/v" as the other formatting codes. Then again we can generalize all this using the "component" terminology. So having "C/c" for ALL THREE colors may make sense too.

3. There are people who need to be able to add/remove the alpha channel. These formatting codes don't directly support that. (you do support both XAML and HTML formatting which is awesome though!)
   
   * Related you seem to arbitrarily include/exclude the alpha channel based on if it is opaque. That is a bad policy in general as it could break parsing downstream if devs are expecting stable/consistent formatting strings.

4. Have you considered more advanced formatting strings like date/time where users can have full control? In light of the above maybe the simple format strings aren't best here and we should have complex strings for maximum control.
   
   * This means the ability to fully control which components are included and what the format of that component is (percent/absolute).
  1. Unprecedented :)
  2. I'll put up a new issue for this since this PR is getting quite big
  3. Addressed! Thanks for the feedback, please review :)
  4. Will put up a new issue if you feel like it's needed

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.0.999-cibuild0063581-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.0.999-cibuild0063601-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@robloo

robloo commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

I'll put up a new issue for this since this PR is getting quite big

This is a small PR in code and it's all a related discussion. Sometimes its useful to keep all discussions in one place ;)

Addressed! Thanks for the feedback, please review :)

Awesome! This should help developers and maintain consistency. More below about specifier codes.


There is another deeper conversation here. In .NET itself DateOnly, DateTimeOffset, DateTime, and 3rd party library types all represent a position on a calendar system. The calendar system is actually arbitrary and if you want you can output it as persian, gregorian, japanese etc. The fundamental information all these types represent is the same. When formatting you can specify the system as well as the format on EACH type. All types essentially support the same formatting.

For colors we have Color, HslColor and HsvColor. Again these 3 types are representing the same fundamental information (wavelength of light). The more I think about this I think we should take the approach that .NET uses for date/times. That means instead of different formatting for each type all formatting is supported by each type. The necessary "model"/"system" conversion is done automatically just like date/time.

So if you want to output an HslColor as HTML hex you can with one format specifier. The same format specifier code as Color.

This relates to #20928 (comment) (again, it's all the same discussion so I'm keeping it here in one spot).

What do you think about this line of thought?


As a spec this means ALL color types would have the exact same formatting codes and be treated exactly the same.

  • "null/""" -> Keeps type-specific formatting for backwards compatibility
    • Color : Red or #40ff8844
    • HslColor : `hsla(230, 1.0, 0.5, 1.0)`` This corrects the legacy bug...
    • HsvColor : hsva(230, 1.0, 0.5, 1.0)
  • "X" -> #AARRGGBB regardless of color type
  • "x" ->#123456 regardless of color type
  • "H" -> #RRGGBBAA regardless of color type
  • "h" -> This is now the same as "x". With no alpha channel there is no difference.

Things now expand pretty quickly when you are trying to support formatting of 1) all color models, 2) absolute/percent and 3) with/without alpha. The number of permutations expand quite a bit and for this reason I think we should go to two-letter format codes (which differs from your latest changes in code). Going to multiple letters keeps things readable and avoids having to come up with new letters like "P/p" that we might need in the future.

The format specifier is as follows:

  • "R" -> rgba(255, 255, 255, 255)
  • "R%" -> rgba(100%, 100%, 100%, 100%)
  • "r" -> rgb(255, 255, 255) without alpha
  • "r%" -> rgb(100%, 100%, 100%) without alpha

This can be expanded the same for HslColor (using "L") and HsvColor (using "V"). In the end:

  • Color model is defined by "R/r" for RGB, "L/l" for HSL and "V/v" for HSV
  • Absolute vs percent units is defined by with/without a "%" symbol
  • Capital letter means include alpha, lower-case letter means exclude alpha channel

Now I need to talk about the complex formatting codes. While at first it doesn't seem like it should be done now it's relevant for two reasons:

  1. If you don't account for it now you risk making it impossible to do in the future without breaking changes (which won't usually be acceptable)
  2. You may actually find it more elegantly handles all the cases we are talking about. It can handle the explosion of permutations better.

A full complex format code would be something like {model}:{components} where C1, C2, C3 are the component symbols for all color models and A is for alpha. This would allow something like:

  • hsv:C1,C2,C3,A -> "hsva(230, 1.0, 0.5, 1.0)`"
  • hsv:C1%,C2%,C3%,A% -> "hsva(50%, 50%, 50%, 50%)"
  • rgb:C1,C2,C3 -> "rgb(255, 255, 255)"

The obvious downside here is the complexity of parsing this grammar. It does support all cases though. However, if we keep this in mind and that we should reserve "C" and "A" for future use like this I think we are OK. The current design is future-proof.


Finally we have a few new issues:

  1. All your prefixes are just "hsl" or "hsv". However, the existing code with output hsva or hsla if there is an alpha component. I checked and we should keep doing this as it aligns to CSS: https://www.w3schools.com/css/css_colors_hsl.asp
  2. The existing code does have a bug with HslColor and it will output an hsva prefix when it should be hsla. Because that has to be fixed we are looking at a breaking change no matter what.
  3. "x" and "h" are now exactly the same. Do we still need both? Probably not.
  4. We don't support the standard CSS format like hsla(9, 100%, 64%, 0.2) out of the box. This is a mix of percent and absolute... we probably should support this.

This is blazing a brand-new trail so is getting a lot of thought typical of what you find in the .NET core lib but not necessarily Avalonia itself for this type of thing. Hopefully I'm not scaring you with the depth here.

Pre-existing copy-paste bug: HslColor.ToString() was outputting
"hsva(" instead of "hsla(" for its default format.
All three color types now support all format specifiers via
auto-conversion, following the DateTime analogy where the same
format codes work regardless of source type.

Format specifiers:
  Hex:  X (#AARRGGBB), x (#RRGGBB), H (#RRGGBBAA)
  RGB:  R/r (absolute), R%/r% (percent)
  HSL:  L/l (CSS standard), L%/l% (all percent)
  HSV:  V/v (CSS standard), V%/v% (all percent)

Convention: uppercase = include alpha (rgba/hsla/hsva prefix),
lowercase = exclude alpha (rgb/hsl/hsv prefix).

Each type handles its native model natively and delegates
cross-model formats via ToRgb()/ToHsl()/ToHsv().

Breaking changes from prior PR iteration:
- "h" dropped (identical to "x" without alpha)
- "C"/"c" removed, reserved for future complex format strings
- "P"/"p" replaced by "R%"/"r%"
- "R" now outputs rgba() not rgb() (CSS convention)
- "L" now outputs hsla() not hsl() (CSS convention)
- "V" now outputs hsva() not hsv() (CSS convention)
@NathanDrake2406
NathanDrake2406 force-pushed the feat/color-tostring-formats branch from 744d0d0 to 0b0dd03 Compare March 18, 2026 18:10
@NathanDrake2406 NathanDrake2406 changed the title Implement IFormattable on Color for configurable ToString formats feat: implement unified IFormattable on Color, HslColor, and HsvColor Mar 18, 2026
@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@robloo

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.0.999-cibuild0063723-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@MrJul @robloo hey guys is this PR gonna die a slow death? :(

@robloo

robloo commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

@NathanDrake2406 I don't think this is doing to die at all:

  1. This is useful to have in the framework
  2. It's brand-new functionality so keeping the PR open for a bit allows other developers to find it and add their feedback. That may improve the implementation.
  3. It isn't critical for 12.0 which the team is entirely focusing on right now.

Separately, I have to go back and see what changes you made. Note that just saying "updated" or similar slows down the review. I spent a lot of time on my feedback and having to go back through to figure out what exactly you changed takes additional time I don't always have. I do want to understand the decisions you made around CSS formatting. It's probably the right decision to always default to CSS-compliant formats; but that does deviate from pure absolute/percent formats we originally discussed above.

Each type only implements its native format natively and delegates cross-model formats via conversion:
Zero duplicated formatting logic. Every delegation terminates in one hop.

I'm glad you kept this architecture. It aligns with the original design and simplifies the code quite a bit.

@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@NathanDrake2406 I don't think this is doing to die at all:

  1. This is useful to have in the framework

  2. It's brand-new functionality so keeping the PR open for a bit allows other developers to find it and add their feedback. That may improve the implementation.

  3. It isn't critical for 12.0 which the team is entirely focusing on right now.

Separately, I have to go back and see what changes you made. Note that just saying "updated" or similar slows down the review. I spent a lot of time on my feedback and having to go back through to figure out what exactly you changed takes additional time I don't always have. I do want to understand the decisions you made around CSS formatting. It's probably the right decision to always default to CSS-compliant formats; but that does deviate from pure absolute/percent formats we originally discussed above.

Each type only implements its native format natively and delegates cross-model formats via conversion:

Zero duplicated formatting logic. Every delegation terminates in one hop.

I'm glad you kept this architecture. It aligns with the original design and simplifies the code quite a bit.

I’ll summarise changes properly next time.

I deviated there because I wanted the common case to be immediately recognisable in the format most Avalonia devs would expect. Since this is a UI framework, CSS style output felt like the more natural default. The pure absolute/percent model was cleaner as a spec, but % still keeps the all-percent form available when anyone wants to use it

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.0.999-cibuild0064489-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul added the api-needs-review The PR adds new public APIs that should be reviewed. label Apr 8, 2026
@NathanDrake2406
NathanDrake2406 marked this pull request as draft April 14, 2026 08:10

@robloo robloo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NathanDrake2406

I've gone over all this again and looked at the code.

  1. I agree with your decision to make the default formats CSS compliant. That is most universal and will make the most sense rather than making up our own "Absolute" variants.
  2. Aside from the #if !BUILDTASK the code looks fully functional and to spec -- can be merged as-is.
  3. I did add some ideas for simplification and to make code more explicit for future developers. However, that can be done in a later PR.

@MrJul

I don't see any issues with the ideas and implementation here. What is needed to get this through the API review? On my end (as the author of the HSL/HSV color structs, color picker) I sign off on this PR.

Comment thread src/Avalonia.Base/Media/Color.cs Outdated
Comment on lines +516 to +531
private string FormatRgbaPercent()
{
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);
}

private string FormatRgbPercent()
{
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);
return string.Format(CultureInfo.InvariantCulture, "rgb({0}%, {1}%, {2}%)", rPct, gPct, bPct);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These and similar methods for HsvColor/HslColor can be simplified. As in other code just add an includeAlpha parameter and the internal method can switch between modes. IMO less methods is better.

/// <summary>
/// Formats the color as a string with all components a percentage.
/// </summary>
/// <param name="includeAlpha">Whether the alpha component will be included in the string.</param>
private string FormatRgbPercent(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);
    }
    else
    {
         return string.Format(CultureInfo.InvariantCulture, "rgb({0}%, {1}%, {2}%)", rPct, gPct, bPct);
    }
}

Comment thread src/Avalonia.Base/Media/Color.cs Outdated
Comment on lines +504 to +505
"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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add comments making it VERY clear these are CSS compliant formats.

Comment thread src/Avalonia.Base/Media/HslColor.cs Outdated
Comment on lines +229 to +243
private string FormatHsla()
{
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);
return string.Format(CultureInfo.InvariantCulture, "hsl({0}, {1}%, {2}%)", hDeg, sPct, lPct);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again these methods can be combined. However, I think we also need to rename them to make is COMPLETELY CLEAR that the resulting format is CSS compliant. This will prevent other developers from accidentally breaking this concept in the future.

As an example TryParseCssFormat already exists in Color.

string FormatHslCss(bool includeAlpha) makes sense to me.

Comment thread src/Avalonia.Base/Media/Color.cs Outdated
Comment on lines +25 to +27
#if !BUILDTASK
, IFormattable
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's really necessary to do this. IFormattable is part of System so is fully supported by the BuildTask. Even so it will just be ignored as the original .ToString() methods will be called instead.

@NathanDrake2406
NathanDrake2406 marked this pull request as ready for review May 8, 2026 08:12
Nathan Nguyen and others added 2 commits May 8, 2026 18:12
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.
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065276-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@NathanDrake2406

I've gone over all this again and looked at the code.

  1. I agree with your decision to make the default formats CSS compliant. That is most universal and will make the most sense rather than making up our own "Absolute" variants.
  2. Aside from the #if !BUILDTASK the code looks fully functional and to spec -- can be merged as-is.
  3. I did add some ideas for simplification and to make code more explicit for future developers. However, that can be done in a later PR.

@MrJul

I don't see any issues with the ideas and implementation here. What is needed to get this through the API review? On my end (as the author of the HSL/HSV color structs, color picker) I sign off on this PR.

I've incorporated your ideas. Just wanna say that I really appreciate your thoughtfulness and extremely strong feedback. It's so rare in OSS.
Gave me quite a bit of knowledge 😀

@robloo

robloo commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@MrJul @maxkatz6 Can we get eyes on this PR from the core team? I don't think it's that difficult to review and merge in at this point.

It does fix a bug with HslColor .ToString() as well which shouldn't be left out in the wild too long.

@MrJul

MrJul commented Aug 7, 2026

Copy link
Copy Markdown
Member

We've reviewed this, and the feature itself is perfectly fine.

However, there were slight concerns about the various format strings not being very readable. We're aware that they're always a bit arbitrary, even in .NET itself, especially when they're not composable (like here or in Guid.ToString()).

A proposal was to simply use the name directly as a format specifier, as they're quite short.
For example, rgba, rgba%, hex, hsv, hsl%, etc. This is easier to remember than arbitrary formats.
The counter-point is, of course, that .NET uses very short format specifiers.

Nothing was really decided, so I'm posting this for feedback.

@robloo

robloo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@MrJul

However, there were slight concerns about the various format strings not being very readable. We're aware that they're always a bit arbitrary, even in .NET itself, especially when they're not composable
...The counter-point is, of course, that .NET uses very short format specifiers.

Well, yes, they are somewhat arbitrary. That's why I wondered at first if there was prior art we could fall back to. There wasn't. So, as you alluded to, this was done in the spirit of the DateTime formatting in .NET. The intent is to have quick formats specifiers just like for dates.

And while they do seem arbitrary at first it does start to make sense when you look at it deeper:

  • "X" is very common for HEX formatting and also is the first letter in "XAML". So this makes sense as the XAML format specifier.
  • "H" is the first letter of "HTML"
  • "R" is the first letter of "RGB"
  • Now HSV and HSL share the same first and second letters, so the 3rd character where they diverge was chosen
    • "V" for HSV (same as the HSB color model as well)
    • "L" for HSL

A proposal was to simply use the name directly as a format specifier, as they're quite short.
For example, rgba, rgba%, hex, hsv, hsl%, etc. This is easier to remember than arbitrary formats.

I would consider these reserved for future composability (the complex formats). For example it may be needed in the future to support argb or even bgra in the future. And each component may need to be customized: a%rgb for example.

Then there is the generalized version of this idea above:

A full complex format code would be something like {model}:{components} where C1, C2, C3 are the component symbols for all color models and A is for alpha. This would allow something like:

  • hsv:C1,C2,C3,A -> "hsva(230, 1.0, 0.5, 1.0)`"
  • hsv:C1%,C2%,C3%,A% -> "hsva(50%, 50%, 50%, 50%)"
  • rgb:C1,C2,C3 -> "rgb(255, 255, 255)"

The obvious downside here is the complexity of parsing this grammar. It does support all cases though. However, if we keep this in mind and that we should reserve "C" and "A" for future use like this I think we are OK. The current design is future-proof.

So the arguments are:

  • It's better to follow existing framework conventions i.e. very short format specifiers just like DateTime
  • The short format specifiers are phase 1 of formatting. Complex composable formatting may very well come in as phase 2 in the years to come. These simple single-letter with optional % symbol format specifiers are designed not to collide with any future complex composable formats.

@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

@robloo I’ve invited you as a collaborator on my fork, this PR is yours if you want to take over.
Your review did a lot for me. It changed the way I think about software engineering and I learned a ton from it, so thank you again!
5 months ago, I was but a humble vibecoder. Now I’m working as an engineer - found a job!

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with the proposed formats, but please remove the #region blocks.

Comment thread tests/Avalonia.Base.UnitTests/Media/ColorTests.cs Outdated
@MrJul MrJul removed the api-needs-review The PR adds new public APIs that should be reviewed. label Aug 19, 2026

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Edit: I can't push to your branch, you probably haven't checked "allow maintainers to edit".

@MrJul MrJul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regions need to be removed, or the PR allowed to be edited by maintainers.

@NathanDrake2406

Copy link
Copy Markdown
Contributor Author

You should be able to edit now. Sorry I’ve been busy, will update the other PRs as well when I have time

@cla-avalonia

cla-avalonia commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

@nathan Nguyen,

Please read the following Contributor License Agreement (CLA). If you agree with the CLA, please reply with the following:

@cla-avalonia agree
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement ( “Agreement” ) is agreed to by the party signing below ( “You” ),
and conveys certain license rights to the AvaloniaUI OÜ ( “AvaloniaUI OÜ” ) for Your contributions to
AvaloniaUI OÜ open source projects. This Agreement is effective as of the latest signature date below.

1. Definitions.

“Code” means the computer software code, whether in human-readable or machine-executable form,
that is delivered by You to AvaloniaUI OÜ under this Agreement.

“Project” means any of the projects owned or managed by AvaloniaUI OÜ and offered under a license
approved by the Open Source Initiative (www.opensource.org).

“Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
Project, including but not limited to communication on electronic mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
discussing and improving that Project, but excluding communication that is conspicuously marked or
otherwise designated in writing by You as “Not a Submission.”

“Submission” means the Code and any other copyrightable material Submitted by You, including any
associated comments and documentation.

2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
Project. This Agreement covers any and all Submissions that You, now or in the future (except as
described in Section 4 below), Submit to any Project.

3. Originality of Work. You represent that each of Your Submissions is entirely Your
original work. Should You wish to Submit materials that are not Your original work,
You may Submit them separately to the Project if You (a) retain all copyright and
license information that was in the materials as you received them, (b) in the
description accompanying your Submission, include the phrase "Submission
containing materials of a third party:" followed by the names of the third party and any
licenses or other restrictions of which You are aware, and (c) follow any other
instructions in the Project's written guidelines concerning Submissions.

4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
Submission is made in the course of Your work for an employer or Your employer has intellectual
property rights in Your Submission by contract or applicable law, You must secure permission from Your
employer to make the Submission before signing this Agreement. In that case, the term “You” in this
Agreement will refer to You and the employer collectively. If You change employers in the future and
desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
and secure permission from the new employer before Submitting those Submissions.

5. Licenses.

a. Copyright License. You grant AvaloniaUI OÜ, and those who receive the Submission directly
or indirectly from AvaloniaUI OÜ, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable
license in the Submission to reproduce, prepare derivative works of, publicly display, publicly perform,
and distribute the Submission and such derivative works, and to sublicense any or all of the foregoing
rights to third parties.

b. Patent License. You grant AvaloniaUI OÜ, and those who receive the Submission directly or
indirectly from AvaloniaUI OÜ, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license
under Your patent claims that are necessarily infringed by the Submission or the combination of the
Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
import or otherwise dispose of the Submission alone or with the Project.

c. Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
granted by implication, exhaustion, estoppel or otherwise.

6. Representations and Warranties. You represent that You are legally entitled to grant the above
licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
have disclosed under Section 3 ). You represent that You have secured permission from Your employer to
make the Submission in cases where Your Submission is made in the course of Your work for Your
employer or Your employer has intellectual property rights in Your Submission by contract or applicable
law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
EXPRESSLY STATED IN SECTIONS 3, 4, AND 6 , THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.

7. Notice to AvaloniaUI OÜ. You agree to notify AvaloniaUI OÜ in writing of any facts or
circumstances of which You later become aware that would make Your representations in this
Agreement inaccurate in any respect.

8. Information about Submissions. You agree that contributions to Projects and information about
contributions may be maintained indefinitely and disclosed publicly, including Your name and other
information that You submit with Your Submission.

9. Governing Law/Jurisdiction. This Agreement is governed by the laws of the Republic of Estonia, and
the parties consent to exclusive jurisdiction and venue in the courts sitting in Talinn,
Estonia. The parties waive all defenses of lack of personal jurisdiction and forum non-conveniens.

10. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
supersedes any and all prior agreements, understandings or communications, written or oral, between
the parties relating to the subject matter hereof. This Agreement may be assigned by AvaloniaUI OÜ.

AvaloniaUI OÜ dedicates this Contribution License Agreement to the public domain according to the Creative Commons CC0 1.

3 out of 4 committers have signed the CLA.

Nathan Nguyen doesn't seem to be a GitHub user.
Please add the emails used in your commits to your Github account accordingly.

@MrJul

MrJul commented Aug 19, 2026

Copy link
Copy Markdown
Member

You should be able to edit now.

Thank you!

Sorry I’ve been busy, will update the other PRs as well when I have time

And no problem, your contributions are appreciated :)

@MrJul
MrJul enabled auto-merge August 19, 2026 15:44
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.2.999-cibuild0068525-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
MrJul added this pull request to the merge queue Aug 19, 2026
Merged via the queue into AvaloniaUI:main with commit 79162b5 Aug 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Color ToString formats

6 participants