Skip to content

Implement issue #58: text styling + TextField config#110

Merged
jonathanpeppers merged 2 commits into
mainfrom
jonathanpeppers/issue-58-text-styling-input-config
Jun 6, 2026
Merged

Implement issue #58: text styling + TextField config#110
jonathanpeppers merged 2 commits into
mainfrom
jonathanpeppers/issue-58-text-styling-input-config

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Owner

Closes #58 (the bulk of it — see Deferred at the bottom for the remainder).

What ships

New Text properties

Color, FontStyle, FontFamily, Align, Overflow, SoftWrap, MaxLines, MinLines.

new Text("Italic serif red, centered")
{
    Color      = ColorKt.Color(0xC6, 0x28, 0x28, 0xFF),
    FontStyle  = FontStyle.Italic,
    FontFamily = FontFamily.Serif,
    Align      = TextAlign.Center,
    Modifier   = Modifier.Companion.FillMaxWidth(),
}

new Text("This long line clips with an ellipsis…")
{
    MaxLines = 1,
    Overflow = TextOverflow.Ellipsis,
    SoftWrap = false,
}

New TextField / OutlinedTextField slots

Enabled, ReadOnly, Label, Placeholder, LeadingIcon, TrailingIcon, Prefix, Suffix, SupportingText, IsError, SingleLine, MaxLines, MinLines.

new TextField(name)
{
    Label          = new Text("Your name"),
    Placeholder    = new Text("Type something…"),
    LeadingIcon    = new Text("👤"),
    TrailingIcon   = new Text("✎"),
    SupportingText = new Text("Powered by issue #58 slots"),
    SingleLine     = true,
}

Generator changes

ComposeBridgeGenerator and ComposeFacadeGenerator now recognize nullable primitives (bool?, int?, long?, float?, double?). At the bridge level these lower to (name ?? defaultLiteral) so a null clears the corresponding $default bit and Kotlin's default runs; at the facade level they surface as nullable auto-properties — parity with existing nullable Compose value types like Dp? / Sp?. Three tests cover the new behaviour (one bridge, one facade, one mixed).

New wrapper types

Type Lowering Why
FontStyle reference (slot L) @JvmInline value class but Compose declares fontStyle: nullable, which boxes
FontFamily reference (slot L) regular Kotlin class, just not bound yet
TextOverflow packed int (slot I) @JvmInline value class, declared non-nullable in Compose
TextAlign reference (slot L) — breaking change, was a value struct same boxing reason as FontStyle

FontStyle and TextAlign reach Compose's companion via the mangled inline-class accessors (getCenter-e0LSkKk()I, getNormal-_-LCdwA()I) and box through the synthesized box-impl(I)L<Type>; static. FontFamily's companion getters return concrete subtypes (SystemFontFamily / GenericFontFamily) so each call uses its exact descriptor. All names verified against ui-text-android 1.11.2.1 bytecode.

Sample

The Greeting tab in MainActivity.cs now demos the new styling — color + italic serif center-aligned text, monospace end-aligned text, ellipsis clipping with MaxLines = 1, two-line clamp with reserved height, plus a TextField and OutlinedTextField exercising the new slots.

Verification

  • dotnet test src/ComposeNet.SourceGenerators.Tests83/83 passing
  • dotnet build src/ComposeNet.Compose — clean (PublicAPI.Unshipped.txt updated)
  • dotnet build src/ComposeNet.Sample — clean

Deferred (blocked or out of scope)

  • KeyboardOptions / KeyboardActionsKeyboardOptions ctor is stripped from the binding (inline-class params: KeyboardType, ImeAction, KeyboardCapitalization); needs a [ComposeBridge(JvmName="<init>")] follow-up.
  • VisualTransformation, AnnotatedString, TextStyle, LinkAnnotation — all in androidx.compose.ui.text.input / androidx.compose.ui.text, still 0 exported types until the next Compose package release post dotnet/android-libraries#1440 (merged but not yet shipped).
  • BasicText / BasicTextField / LocalTextStyle / FocusRequester — separate facades; out of scope here.

Breaking change

TextAlign is no longer a record struct. Callers that previously used the Pack static, Equals/Deconstruct, or accessed Value directly will need to migrate; the TextAlign.Left/Right/Center/Justify/Start/End/Unspecified accessors are unchanged in name and still typed TextAlign.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Adds the missing Material 3 Text styling parameters (color, italic,
font family, alignment, overflow, max/min lines, soft wrap) and the
missing TextField configuration parameters (label, placeholder,
leading/trailing icons, prefix/suffix, supportingText, isError,
enabled, readOnly, singleLine, max/min lines).

Generator changes
-----------------

ComposeBridgeGenerator + ComposeFacadeGenerator now recognize
nullable primitives (`bool?`, `int?`, `long?`, `float?`, `double?`).
At the bridge level they lower to `(name ?? defaultLiteral)` so a
`null` clears the corresponding `$default` bit and Kotlin's default
runs; at the facade level they surface as nullable auto-properties
(parity with existing nullable Compose value types like `Dp?`/`Sp?`).

New types
---------

* `FontStyle` — `Java.Lang.Object` subclass exposing `Normal`/`Italic`
  via Kotlin's mangled inline-class accessor (`getNormal-_-LCdwA()I`)
  + synthesized `box-impl(int) -> FontStyle` static, then handed to
  the bridge as a boxed reference (slot `L`). Material 3 declares
  every `fontStyle:` slot as nullable, which forces the inline class
  to box at the JNI boundary.
* `TextOverflow` — value struct (slot `I`, packed). Compose declares
  `overflow:` as non-nullable so it travels as a raw `int`.
* `FontFamily` — `Java.Lang.Object` subclass exposing `Default`,
  `SansSerif`, `Serif`, `Monospace`, `Cursive`. Each companion getter
  has its own concrete return descriptor (`SystemFontFamily` for
  Default, `GenericFontFamily` for the rest) that we resolve
  individually.
* `TextAlign` — converted from value struct to `Java.Lang.Object`
  subclass, same shape as `FontStyle` (mangled
  `getCenter-e0LSkKk()I` + `box-impl`). Same nullable-in-source
  reason for the boxed lowering.

Bridge expansions
-----------------

`Text` gains: `color`, `fontStyle`, `fontFamily`, `align`, `overflow`,
`softWrap`, `maxLines`, `minLines`. `TextField` and `OutlinedTextField`
each gain: `enabled`, `readOnly`, `label`, `placeholder`,
`leadingIcon`, `trailingIcon`, `prefix`, `suffix`, `supportingText`,
`isError`, `singleLine`, `maxLines`, `minLines`. The matching
`[ComposeDefaults]` declarations already had every bit position
named; no `ComposeDefaults.cs` changes were needed.

Sample
------

`MainActivity.cs` (Greeting tab) now demos the new styling — color +
italic serif center-aligned text, monospace end-aligned text, ellipsis
clipping with `MaxLines = 1`, two-line clamp with reserved height,
plus a `TextField` and `OutlinedTextField` exercising the new slots
(label, placeholder, leading/trailing icons, prefix/suffix,
supportingText).

Deferred (blocked or out of scope)
----------------------------------

* `KeyboardOptions` / `KeyboardActions` — `KeyboardOptions` ctor is
  stripped from the binding (inline-class params: `KeyboardType`,
  `ImeAction`, `KeyboardCapitalization`); needs a
  `[ComposeBridge(JvmName="<init>")]` follow-up.
* `VisualTransformation`, `AnnotatedString`, `TextStyle`,
  `LinkAnnotation` — all in `androidx.compose.ui.text.input` /
  `androidx.compose.ui.text`, still 0 exported types until the next
  Compose package release post `dotnet/android-libraries#1440`.
* `BasicText` / `BasicTextField` / `LocalTextStyle` / `FocusRequester`
  — separate facades; out of scope.

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

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

Implements most of issue #58 by extending the ComposeNet facade to expose additional Text styling knobs and TextField/OutlinedTextField configuration slots, supported by source-generator updates that broaden what parameter shapes can be expressed as optional/defaultable Kotlin parameters.

Changes:

  • Extend ComposeBridgeGenerator/ComposeFacadeGenerator to recognize nullable primitives (bool?, int?, long?, float?, double?) as “optional” defaultable parameters.
  • Add new text-related wrapper types (FontFamily, FontStyle, TextOverflow) and migrate TextAlign to a boxed reference wrapper to match Compose’s nullable inline-class usage.
  • Update sample + generator tests + public API tracking to cover the new surface area.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/ComposeNet.SourceGenerators/ComposeValueTypes.cs Updates recognized packed value-type registry (adds TextOverflow, removes packed TextAlign).
src/ComposeNet.SourceGenerators/ComposeReferenceTypes.cs Adds new reference-wrapper types (FontFamily, FontStyle, TextAlign) to the facade classification registry.
src/ComposeNet.SourceGenerators/ComposeFacadeGenerator.cs Classifies nullable primitives as optional-value facade properties (doc comment needs a small update).
src/ComposeNet.SourceGenerators/ComposeBridgeGenerator.cs Lowers nullable primitives via ?? zero-literals and updates default-bit clearing (comment needs a small update).
src/ComposeNet.SourceGenerators.Tests/FacadeGeneratorTests.cs Adds coverage for nullable primitives and TextOverflow optional-value classification.
src/ComposeNet.SourceGenerators.Tests/BridgeGeneratorTests.cs Adds coverage for nullable primitive lowering + packed TextOverflow lowering.
src/ComposeNet.Sample/MainActivity.cs Demonstrates new Text styling and TextField slot properties (one comment needs a small wording correction).
src/ComposeNet.Compose/TextOverflow.cs Introduces packed TextOverflow value wrapper and constants.
src/ComposeNet.Compose/TextAlign.cs Migrates TextAlign from packed struct to boxed JNI-backed reference wrapper.
src/ComposeNet.Compose/PublicAPI.Unshipped.txt Updates tracked public API for new types/properties and the TextAlign shape change.
src/ComposeNet.Compose/FontStyle.cs Adds boxed JNI-backed FontStyle wrapper with companion constant resolution.
src/ComposeNet.Compose/FontFamily.cs Adds JNI-backed FontFamily wrapper with companion constant resolution.
src/ComposeNet.Compose/ComposeBridges.cs Extends bridge/facade signatures for Text, TextField, and OutlinedTextField to include new parameters/slots.

Comment thread src/ComposeNet.SourceGenerators/ComposeFacadeGenerator.cs Outdated
Comment thread src/ComposeNet.SourceGenerators/ComposeBridgeGenerator.cs Outdated
Comment thread src/ComposeNet.Sample/MainActivity.cs Outdated
- ComposeFacadeGenerator: doc comment for IsOptionalValueType now
  lists TextOverflow under packed @JvmInline value classes, and
  spells out which wrappers live in ComposeReferenceTypes
  (FontWeight/FontStyle/FontFamily/TextAlign/...).
- ComposeBridgeGenerator: NeedsKeepAlive comment swaps the stale
  TextAlign mention for TextOverflow now that TextAlign is a
  reference wrapper.
- MainActivity sample comment: clarifies that the issue #58 styling
  set spans three generator paths (nullable primitive, nullable
  reference wrapper, packed value class) instead of just one.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers jonathanpeppers merged commit 3c7a0b6 into main Jun 6, 2026
1 check passed
@jonathanpeppers jonathanpeppers deleted the jonathanpeppers/issue-58-text-styling-input-config branch June 6, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants