Skip to content

feat(ui): widgetSetMaxWidth — capped, centered content column (#10167) - #10184

Draft
steinybot wants to merge 3 commits into
PerryTS:mainfrom
steinybot:worktree-widget-set-max-width
Draft

steinybot wants to merge 3 commits into
PerryTS:mainfrom
steinybot:worktree-widget-set-max-width

Conversation

@steinybot

Copy link
Copy Markdown
Contributor

Problem

There is no way to give a widget a max-width. widgetSetWidth pins an exact width and widgetMatchParentWidth fills the parent, so a responsive centered content column — grow with the parent up to a cap, then stop and center — cannot be built (#10167).

Solution

Add widgetSetMaxWidth(widget, maxWidth): the CSS max-width + margin: auto behaviour. Below the cap the widget fills the available width; at and above maxWidth it stops at the cap and centers, so the side gutters grow with the window.

This PR

Wires widgetSetMaxWidth end to end on every UI backend, plus the perry-solid maxWidth prop, docs, and a runnable doc-example.

Changes

  • Dispatch + declaration. One MethodRow in PERRY_UI_TABLE (widgetSetMaxWidthperry_ui_widget_set_max_width, [Widget, F64]) and the types/perry/ui/index.d.ts declaration. The LLVM backend lowers it generically; JS/WASM codegen resolve it through the existing ui_method_to_runtime fall-through, so no per-backend codegen change.
  • macOS / iOS / tvOS / visionOS. Three Auto Layout constraints on the view: width <= maxWidth (required), width == superview.width at priority 999 (grow to fill until the cap binds), centerX == superview.centerX. Idempotent.
  • GTK4. A MaxWidthBin widget subclass (GTK4 has no max-width property) whose measure/size_allocate cap the child and center it; the child is re-parented through the bin.
  • Windows / WinUI. A max_width field honoured at Win32 layout time (clamp + center); WinUI uses MaxWidth + HorizontalAlignment::Stretch.
  • Android. A PerryMaxWidthLayout (FrameLayout whose onMeasure clamps and centers); widgetSetMaxWidth re-parents the child through it via JNI.
  • watchOS. The SwiftUI host applies .frame(maxWidth:, alignment: .center) from a new frame_max_width introspection field.
  • web (JS + WASM). width: 100% + max-width + margin: auto.
  • ArkTS. .width('100%').constraintSize({ maxWidth }).alignSelf(ItemAlign.Center).
  • Ergonomics + docs. A maxWidth prop in the perry-solid renderer, a styling-page section, and docs/examples/ui/layout/max_width_centered.ts (run by the iOS simulator harness).

Verification

Every backend was pushed to a real run where the environment allows it. Writing each one and then running it caught three backends that recorded the cap but never enforced it (GTK4, Android, web) — each is fixed here.

Run-verified:

Backend How Result
macOS compiled, ran, pixel-measured a 900pt window column 320.0pt, gutters 290 / 290
iOS compiled --target ios-simulator, ran on a real iPhone 17 simulator launch + first frame clean, harness 1/1 pass
web (JS+WASM) compiled --target web, measured the DOM in a browser column 320px, gutters 290 / 290
GTK4 mechanism the MaxWidthBin measure/allocate reproduced in a standalone GTK4 program 900px → 320 centered; 200px → 200 fill

Verified by build only, with the reason a run was not possible in the authoring environment (a macOS host):

Backend Verified Why no run here
Windows / WinUI CI windows-build compiles both crates on windows-latest no Windows host
ArkTS emitted-source unit test (widget_set_max_width_emits_constraint_size_and_center) codegen backend; no HarmonyOS tooling exists to run against
tvOS / visionOS code byte-identical to the run-verified iOS path simulator runtimes are not installed and no CI job runs them

Not verifiable in this environment — the honest limits:

  • GTK4 integrationperry-ui-gtk4 is #![cfg(target_os = "linux")], so it does not compile on the macOS host, and no CI job builds it. The layout mechanism is pixel-proven standalone (above); the integration rests on that plus review.
  • watchOS — the Rust side compiles on the host; the Swift host change and the runtime need a watch simulator, which is not installed and has no CI job.
  • Android — the one backend with no verification path here: cargo check needs the Android NDK (a cc-rs build script), the Kotlin PerryMaxWidthLayout is compiled by no CI job, the CI cross-build is non-fatal, and there is no emulator. It rests on review and a real Android build.

steinybot and others added 2 commits September 13, 2026 15:47
…PerryTS#10167)

A responsive centered column could not be built. widgetSetWidth pins an exact
width and widgetMatchParentWidth fills the parent, so neither grows to a cap
then stops. widgetSetMaxWidth is the CSS max-width plus margin:auto behaviour.
It fills the parent below the cap, then stops and centers at or above it.

The method lowers through PERRY_UI_TABLE as perry_ui_widget_set_max_width.
Constraint backends get the real three-constraint layout. Android fills and
centers without the hard cap. watchOS is a no-op. Web maps to CSS max-width.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126quMdA2DoCLU5EokB54rp
…S#10167)

Running each backend showed three that recorded the cap but never enforced it.
GTK4 used size_request as a minimum and filled; Android forced MATCH_PARENT and
ignored the value; web set max-width but the block shrank to its content instead
of filling up to the cap. watchOS was a plain no-op.

GTK4 now wraps the child in a MaxWidthBin widget whose measure and allocate cap
and center it. Android wraps the child in a PerryMaxWidthLayout whose onMeasure
clamps and centers. web adds width:100% so the element fills up to the cap.
watchOS stores a frame_max_width the SwiftUI host reads for
.frame(maxWidth:, alignment: .center).

Adds a runnable doc-example, ui/layout/max_width_centered.ts, that the iOS
simulator harness runs. Verified: macOS pixel-measured, iOS run on a real
simulator, web measured in a browser, the GTK4 bin proven with a standalone
GTK4 program.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126quMdA2DoCLU5EokB54rp
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The fragment was named after the issue while the PR number was unknown; the
changelog.d convention keys the filename on the PR number.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126quMdA2DoCLU5EokB54rp
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.

1 participant