Skip to content

Wire RadioButton stroke color (and document corner-radius/thickness limits)#276

Open
jonathanpeppers wants to merge 1 commit into
mainfrom
jonathanpeppers/radiobutton-stroke-color
Open

Wire RadioButton stroke color (and document corner-radius/thickness limits)#276
jonathanpeppers wants to merge 1 commit into
mainfrom
jonathanpeppers/radiobutton-stroke-color

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Owner

Routes IButtonStroke.StrokeColor (XAML's BorderColor on Microsoft.Maui.Controls.RadioButton) through Compose Material 3's RadioButtonColors ring slots, and explicitly holds back CornerRadius / StrokeThickness with a rationale comment.

What's wired

  • ComposeBridges.RadioButton now accepts RadioButtonColors? colors = null and forwards to RadioButtonKt.RadioButton(colors: ...).
  • New composer.RadioButtonColors(...) extension factory on IComposer mirrors CheckboxColors / SwitchColors — calls the parameterless RadioButtonDefaults.colors(composer, _changed) and threads non-null overrides through RadioButtonColors.copy(...) (the only four-long entry point that survives the binder strip from @JvmInline value class Color).
  • RadioButtonColors added to the source-generator reference-type allow-list (ComposeReferenceTypes.cs) so [ComposeFacade] recognises it as an OptionalValue slot (auto-property + auto-mask). The generated RadioButton.Colors { get; set; } becomes a normal facade property.
  • RadioButtonHandler adds [nameof(IButtonStroke.StrokeColor)] = MapStrokeColor and applies the colour to both selectedColor and unselectedColor so the user's chosen colour is visible regardless of check state — matches stock MAUI's UpdateStrokeColor semantic, which tints the ring drawable in every check state.

What's held back (intentionally)

IButtonStroke.StrokeThickness and CornerRadius are documented in the handler's mapper dictionary as deliberately unimplemented. Rationale (paraphrased from the new TODO block in RadioButtonHandler.cs):

Material 3's RadioButton draws a fixed 20.dp circle whose ring thickness and shape are baked into the composable's internal Canvas (drawCircle with a hard-coded RadioStrokeWidth = 2.dp); the public surface (RadioButtonColors) only exposes the ring colour, not its geometry. Honouring these would require replacing the call to RadioButtonKt.RadioButton with a hand-rolled Canvas { drawCircle / drawArc } composable, which loses M3's ripple, state-layer, focus indicator, and accessibility chrome. A CornerRadius override is also semantically off — XAML callers reaching for it on a RadioButton are typically trying to make a control that is no longer recognisable as a radio button.

Coverage

RadioButtonHandler: 36/39 (92%) → 37/39 (95%). Missing-key list shrinks from {CornerRadius, StrokeColor, StrokeThickness} to {CornerRadius, StrokeThickness}.

Repo-wide property-mapper coverage stays at 884/1224 (72.2%) since the new StrokeColor slot replaces an already-covered key in the regenerated docs/maui-coverage.md.

Sample

TogglesPage.xaml gains a fourth <RadioButton BorderColor="#D32F2F"> (Dragonfruit) in the existing radio group, so the change is visually verifiable on device.

Verified

  • dotnet build src/Microsoft.AndroidX.Compose ✅ (0 errors)
  • dotnet test src/Microsoft.AndroidX.Compose.SourceGenerators.Tests ✅ (155/155 pass)
  • dotnet build src/Microsoft.AndroidX.Compose.Maui ✅ (0 errors)
  • dotnet build src/Microsoft.AndroidX.Compose.Maui.Sample ✅ (0 errors)
  • dotnet run scripts/maui-coverage.cs regenerates docs/maui-coverage.md cleanly.

…imits)

Routes `IButtonStroke.StrokeColor` (XAML's `BorderColor` on
`Microsoft.Maui.Controls.RadioButton`) through the Compose Material 3
`RadioButtonColors` ring slots, and explicitly holds back
`CornerRadius` / `StrokeThickness` with a rationale comment.

What's wired:
- `ComposeBridges.RadioButton` accepts `RadioButtonColors? colors = null`
  and forwards to `RadioButtonKt.RadioButton(colors: ...)`.
- New `composer.RadioButtonColors(...)` extension factory on
  `IComposer` mirrors `CheckboxColors` / `SwitchColors` — calls the
  parameterless `RadioButtonDefaults.colors(composer, _changed)` and
  threads non-null overrides through `RadioButtonColors.copy(...)`
  (the only four-long entry point that survives the binder strip).
- `RadioButtonColors` added to the source-generator reference-type
  allow-list so `[ComposeFacade]` recognises it as an OptionalValue
  slot (auto-property + auto-mask).
- `RadioButtonHandler` adds `[nameof(IButtonStroke.StrokeColor)] =
  MapStrokeColor` and applies the colour to both `selectedColor` and
  `unselectedColor` so the user's chosen colour is visible regardless
  of check state — matches stock MAUI's `UpdateStrokeColor` semantic.

What's held back (with rationale in the handler's mapper dictionary):
- `IButtonStroke.StrokeThickness` and `CornerRadius`. Material 3's
  `RadioButton` draws a fixed 20.dp circle whose ring thickness and
  shape are baked into the composable's internal Canvas (`drawCircle`
  with a hard-coded `RadioStrokeWidth = 2.dp`); the public surface
  (`RadioButtonColors`) only exposes the ring colour, not its
  geometry. Honouring these would require replacing
  `RadioButtonKt.RadioButton` with a hand-rolled
  `Canvas { drawCircle / drawArc }` composable, which loses Material
  3's ripple, state-layer, focus indicator, and accessibility chrome.
  XAML callers reaching for `CornerRadius` on a radio button are
  typically trying to make a control that is no longer recognisable
  as a radio button.

Coverage:
- `RadioButtonHandler`: 36/39 (92%) -> 37/39 (95%). Missing keys
  drop from {CornerRadius, StrokeColor, StrokeThickness} to
  {CornerRadius, StrokeThickness}.
- Repo-wide property-mapper coverage: 884/1224 (72.2%).

Sample:
- TogglesPage adds a fourth `<RadioButton BorderColor="#D32F2F">`
  (Dragonfruit) so the change is visible on device.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 15, 2026 23:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR wires IButtonStroke.StrokeColor (MAUI's BorderColor on RadioButton) through Compose Material 3's RadioButtonColors ring slots, following the same colors-factory-and-copy pattern already established for CheckboxColors and SwitchColors. It also explicitly documents why StrokeThickness and CornerRadius are intentionally held back (Material 3's RadioButton composable hardcodes its ring geometry).

Changes:

  • Adds ComposeExtensions.RadioButtonColors(...) factory on IComposer that calls RadioButtonDefaults.Instance.Colors(composer, 0) and applies overrides via RadioButtonColors.copy(...), plus registers RadioButtonColors in the source-generator's reference-type allow-list so [ComposeFacade] recognizes it as an OptionalValue slot.
  • Updates ComposeBridges.RadioButton to accept and forward RadioButtonColors? colors = null to the underlying RadioButtonKt.RadioButton call.
  • Adds MapStrokeColor in RadioButtonHandler to convert IButtonStroke.StrokeColor to a packed long? and apply it to both selectedColor and unselectedColor ring slots so the user-chosen color is visible in all check states.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Microsoft.AndroidX.Compose/ComposeExtensions.RadioButtonDefaults.cs New IComposer.RadioButtonColors(...) extension mirroring RadioButtonDefaults.colors(), using the theme-defaults-then-copy pattern from CheckboxColors/SwitchColors.
src/Microsoft.AndroidX.Compose/ComposeBridges.cs RadioButton bridge gains RadioButtonColors? colors = null parameter forwarded to the bound RadioButtonKt.RadioButton.
src/Microsoft.AndroidX.Compose.SourceGenerators/ComposeReferenceTypes.cs Adds RadioButtonColors to the recognized reference-type list for [ComposeFacade] auto-property generation.
src/Microsoft.AndroidX.Compose.Maui/Handlers/RadioButtonHandler.cs Adds MutableState<long?> _strokeColor, MapStrokeColor mapper, and BuildNode wiring that builds RadioButtonColors when stroke is set. Expands TODO comment for held-back properties.
src/Microsoft.AndroidX.Compose/PublicAPI.Unshipped.txt Registers new RadioButton.Colors property and ComposeExtensions.RadioButtonColors(...) method.
src/Microsoft.AndroidX.Compose.Maui.Sample/Pages/TogglesPage.xaml Adds a "Dragonfruit" RadioButton with BorderColor="#D32F2F" for on-device visual verification.
docs/maui-coverage.md Regenerated: RadioButtonHandler coverage moves from 36/39 (92%) to 37/39 (95%); StrokeColor marked covered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants