Skip to content

feat(ui): Phase 2 v3.3 — cross-platform showToast + setText (macOS, v0.5.408)#326

Merged
proggeramlug merged 1 commit into
mainfrom
phase-2-v3.3
Apr 30, 2026
Merged

feat(ui): Phase 2 v3.3 — cross-platform showToast + setText (macOS, v0.5.408)#326
proggeramlug merged 1 commit into
mainfrom
phase-2-v3.3

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

  • Adds the cross-platform runtime backbone for showToast / setText so non-harmonyos backends can light them up incrementally.
  • Wires the macOS implementation as the first cross-platform target — borderless NSPanel toast presenter + id → widget-handle registry calling into the existing widgets::text::set_text_str setStringValue: path.
  • Pre-existing v0.5.405 perry_arkts_show_toast / perry_arkts_set_text symbols were guarded behind feature = "ohos-napi". A non-harmonyos build of a TS program calling either failed to link with Undefined symbols; this PR fills the gap without touching the harmonyos drain-queue path.

Stacks on #322.

What landed

Runtime (always compiled)

New crates/perry-runtime/src/ui_text_registry.rs provides cross-platform stubs of perry_arkts_show_toast / perry_arkts_set_text / perry_arkts_register_text_id gated behind #[cfg(not(feature = "ohos-napi"))] so harmonyos keeps the canonical drain-queue impl in arkts_callbacks.rs (no symbol collision). Each stub forwards to a per-process AtomicPtr<()> handler slot registered via three new FFIs (js_register_show_toast_handler / _set_text_handler / _text_id_handler) — same pattern as the existing js_register_stdlib_pump.

Pending-call buffering: widget construction runs at module-init time (before app_run registers handlers), so Text("Count: 0", "counter") immediately fires perry_arkts_register_text_id with no handler available. Without buffering, the id → handle map would never populate and later setText("counter", ...) calls would silently no-op. Each pre-registration call is buffered into a static Mutex<Vec<...>>; the registration FFI drains on first registration, replaying every queued call against the fresh handler.

macOS UI (real implementation)

New crates/perry-ui-macos/src/widgets/toast.rs (~280 LOC):

  • Borderless NSPanel with rounded translucent dark background + centered NSTextField, anchored near the top of the active window.
  • Animates alpha 0→1 over 0.25s, holds for ~2s, fades out, closes.
  • Two NSTimer targets (PerryToastFadeOutTarget + PerryToastCleanupTarget) using the same define_class! pattern as the existing app::PerryTestExitTarget / PerryTimerTarget.
  • Multi-toast queueing via TOAST_QUEUE: VecDeque<String> + PRESENTING: Cell<bool>showToast("a"); showToast("b") produces sequential banners, not "b" overwriting "a".

New crates/perry-ui-macos/src/widgets/text_registry.rs (~80 LOC):

  • Mutex<Option<HashMap<String, i64>>> mapping user-supplied ids to widget handles.
  • register_text_id_handler populates; set_text_handler looks up + calls the existing widgets::text::set_text_str to update the NSTextField via setStringValue:.

Both registered in app::register_cross_platform_text_handlers called from the top of app_run.

Codegen change

crates/perry-codegen/src/lower_call/native.rs: new arm before PERRY_UI_TABLE dispatch detects Text(content, id). Pre-fix, the table's "args.len() == sig.args.len() + 1 ⇒ inline_style_arg" path absorbed the id as a would-be inline-style object literal, and apply_inline_style silently no-op'd because strings aren't object literals — so the id was dropped on the floor and setText("counter", ...) had nothing to look up. The new arm lowers the create call manually, then emits a perry_arkts_register_text_id(handle, id) follow-up so the platform UI crate gets the mapping. On harmonyos that follow-up is a runtime no-op (codegen-arkts emits @State text_<id> directly into the .ets — see the #[cfg(feature = "ohos-napi")] arm in ui_text_registry.rs).

Other platforms

iOS / GTK4 / Windows / Android / tvOS / visionOS / watchOS get the runtime symbols cross-platform via libperry_runtime.a — programs link cleanly but showToast / setText no-op visibly until each platform's UI crate registers its own handlers. Each is a focused follow-up issue (the macOS-side files in this PR are the template; each platform needs its own widgets/toast.rs + widgets/text_registry.rs plus a registration call from app_run).

Test plan

  • cargo build --release -p perry-runtime -p perry-stdlib -p perry-ui-macos -p perry clean (1m22s incremental).
  • cargo test --release -p perry-codegen-arkts 23/23 still pass (no codegen-arkts code touched).
  • cargo test --release -p perry-runtime --lib 173/0 baseline.
  • Smoke compile of Text(initial, id) + Button(label, () => { setText(id, ...); showToast(msg); }) on --target macos produces a 0.9 MB binary that exits 0 in PERRY_UI_TEST_MODE=1.
  • Visual emulator validation (parent will run).
  • Platform follow-ups: iOS / GTK4 / Windows / Android / tvOS / visionOS / watchOS each get their own showToast + setText (separate issues).

…0.5.408)

v0.5.405 shipped harmonyos-only `perry_arkts_show_toast` and
`perry_arkts_set_text` runtime symbols guarded behind
`feature = "ohos-napi"`. Every other backend (macOS / iOS / Linux GTK4 /
Windows / Android / tvOS / visionOS / watchOS) failed to link a TS
program calling `showToast` or `setText`.

This commit lights up macOS as the first cross-platform backend and
provides the runtime backbone for the rest:

1. New `crates/perry-runtime/src/ui_text_registry.rs` (always compiled)
   provides cross-platform `perry_arkts_show_toast` /
   `perry_arkts_set_text` / `perry_arkts_register_text_id` stubs gated
   behind `#[cfg(not(feature = "ohos-napi"))]` — harmonyos keeps the
   canonical drain-queue impl in `arkts_callbacks.rs`. Each stub
   forwards to a per-process `AtomicPtr<()>` handler slot registered
   by the platform UI crate via three new FFIs
   (`js_register_show_toast_handler` / `_set_text_handler` /
   `_text_id_handler`) — same pattern as `js_register_stdlib_pump`.

2. Pending-call buffering. Widget construction runs at module-init time
   (before `app_run` registers handlers), so Text("Count: 0", "counter")
   immediately fires `perry_arkts_register_text_id` with no handler
   available. Pre-fix, calls were silently dropped — id → handle map
   never populated, later `setText("counter", ...)` calls no-op'd.
   Post-fix: each pre-registration call is buffered into a static
   `Mutex<Vec<...>>`; the registration FFI drains on first registration,
   replaying every call against the fresh handler.

3. macOS implementation. New `crates/perry-ui-macos/src/widgets/toast.rs`
   presents a borderless NSPanel with rounded translucent dark
   background near the top of the active window — alpha 0→1 fade-in,
   ~2s hold, fade-out, close — driven by NSTimer targets using the same
   `define_class!` pattern as `app::PerryTestExitTarget`. Multi-toast
   queueing via `TOAST_QUEUE: VecDeque<String>` so back-to-back
   `showToast("a"); showToast("b")` produces sequential banners.

4. New `crates/perry-ui-macos/src/widgets/text_registry.rs` holds id →
   widget-handle map; `register_text_id_handler` populates,
   `set_text_handler` looks up + calls `widgets::text::set_text_str`
   to update the NSTextField via `setStringValue:`.

5. Codegen change in `crates/perry-codegen/src/lower_call/native.rs`:
   new arm before `PERRY_UI_TABLE` dispatch detects `Text(content, id)`.
   Pre-fix the table's "args.len() == sig.args.len()+1" path absorbed
   the id as a would-be inline-style object and silently dropped it.
   Post-fix the new arm lowers the create call manually, then emits
   `perry_arkts_register_text_id(handle, id)` so the platform UI crate
   gets the mapping. On harmonyos that's a runtime no-op (codegen-arkts
   emits @State text_<id> directly into the .ets).

iOS / GTK4 / Windows / Android / tvOS / visionOS / watchOS get the
runtime symbols cross-platform via libperry_runtime.a — programs link
cleanly but `showToast` / `setText` no-op visibly until each platform's
UI crate registers its own handlers (good follow-up issues).

Verified
--------
* cargo build --release -p perry-runtime -p perry-stdlib
  -p perry-ui-macos -p perry: clean (1m22s incremental).
* cargo test --release -p perry-codegen-arkts: 23/23 pass
  (no codegen-arkts code touched).
* cargo test --release -p perry-runtime --lib: 173/0 baseline.
* Smoke compile of Text(initial, id) + Button(label, () => {
    setText(id, ...); showToast(msg); }) on --target macos produces
  a 0.9 MB binary that exits 0 in PERRY_UI_TEST_MODE=1.

Stacks on #322.
@proggeramlug proggeramlug merged commit 48fa66c into main Apr 30, 2026
6 of 8 checks passed
proggeramlug pushed a commit that referenced this pull request Apr 30, 2026
Wire `showToast` and `setText` on the three UIKit platforms (iOS, tvOS,
visionOS) using the macOS NSPanel implementation in PR #326 as the
template. Each platform gets the same two new files:

- `widgets/toast.rs`: UIView overlay added to the key UIWindow, alpha-
  fade 0→1 in 0.25 s (UIView.animateWithDuration:), hold 2 s, fade out,
  removeFromSuperview. FIFO queue via thread_local VecDeque + Cell<bool>
  so back-to-back showToast calls render sequentially. Two NSTimer target
  classes drive the hold → fade-out → cleanup pipeline, using the same
  define_class! + scheduledTimerWithTimeInterval:target:selector: pattern
  as PerryPumpTarget / PerryTimerTarget. ObjC class names are platform-
  prefixed (PerryIOSToast*, PerryTVOSToast*, PerryVisionOSToast*) to
  avoid runtime namespace collision if both crates ever link together.

- `widgets/text_registry.rs`: Mutex<Option<HashMap<String, i64>>> mapping
  user-supplied Text ids to widget handles. register_text_id_handler
  populates; set_text_handler looks up + calls widgets::text::set_text_str
  which issues UILabel setText: — UIKit analogue of NSTextField
  setStringValue:. Direct port of the macOS file; no platform-specific
  code needed.

Each crate's mod.rs exposes the two new modules. Each crate's app_run
calls register_cross_platform_text_handlers (with the same extern block +
function body as macOS) before UIApplicationMain / register_view_controller,
so the three runtime AtomicPtr handler slots are populated before any TS
code fires showToast or setText.

No codegen changes: PR #326's cross-platform design routes all UIKit
targets through the existing perry_arkts_show_toast / perry_arkts_set_text
/ perry_arkts_register_text_id symbols in perry-runtime; the handler
registration in app_run is the only per-platform hook required.

Verified: cargo test --release -p perry-codegen-arkts 23/23 pass.

https://claude.ai/code/session_01QvJUom4CxtYCxpyqcukt7y
proggeramlug added a commit that referenced this pull request Apr 30, 2026
…420) (#327)

UIKit ports of the macOS pattern. UIView overlay + alpha-fade for toast; id→handle map for setText. Stacked on #326.
proggeramlug pushed a commit that referenced this pull request Apr 30, 2026
Wires showToast + setText on Android (second cross-platform backend after
macOS, stacks on PR #326 which adds the runtime backbone + macOS impl).

`show_toast_handler` is a cross-platform handler entry point (registered
via `js_register_show_toast_handler`). Calls `PerryBridge.showToast(String)`
over JNI; the Kotlin side posts `Toast.makeText(ctx, msg, LENGTH_SHORT).show()`
to the main looper via `uiHandler.post`. Toast sequencing is handled natively
by the Android system — no per-process FIFO queue needed.

Mirrors `crates/perry-ui-macos/src/widgets/text_registry.rs` exactly:
`Mutex<Option<HashMap<String, i64>>>` id → handle registry.
`register_text_id_handler` populates; `set_text_handler` looks up the
handle and calls `widgets::text::set_text_str` (the existing JNI
`TextView.setText(CharSequence)` path).

Calls `register_cross_platform_text_handlers()` at the top of `app_run`,
mirroring the macOS `app_run` pattern (v0.5.408). Extern declarations for
`js_register_{show_toast,set_text,text_id}_handler` FFIs added at the bottom.

New `@JvmStatic fun showToast(msg: String)` posts Toast to UI thread.
`android.widget.Toast` is already available via the existing `import android.widget.*`.

`perry_arkts_show_toast` / `perry_arkts_set_text` / `perry_arkts_register_text_id`
are already emitted by `lower_call/native.rs` for all targets and the
cross-platform runtime stubs (v0.5.408) dispatch to the registered handlers.

- `cargo test --release -p perry-codegen-arkts` → 23/23 pass (baseline).
- `cargo build --release -p perry-ui-android --target aarch64-linux-android`
  fails at `libmimalloc-sys` C build (NDK absent on this host, same class as
  the documented ci-env gap). Type-level correctness confirmed by code review
  against the macOS reference implementation.

https://claude.ai/code/session_015FKE6bodWtedKrBJyVbPgA
proggeramlug added a commit that referenced this pull request Apr 30, 2026
proggeramlug pushed a commit that referenced this pull request Apr 30, 2026
New `crates/perry-ui-windows/src/widgets/toast.rs`: borderless
WS_EX_LAYERED popup near the bottom-center of the main app window.
Registers a one-time "PerryToastClass" via std::sync::Once +
RegisterClassExW; creates the HWND with WS_EX_TOPMOST | WS_EX_NOACTIVATE |
WS_EX_TOOLWINDOW; clips to rounded rect via SetWindowRgn +
CreateRoundRectRgn(r=14); drives a 50ms WM_TIMER animation (FadeIn 5 ticks
0→255, Hold 40 ticks, FadeOut 5 ticks 255→0) via SetLayeredWindowAttributes
LWA_ALPHA; draws dark bg + white Segoe UI 14pt text in WM_PAINT via
DrawTextW DT_CENTER|DT_VCENTER|DT_SINGLELINE. FIFO queue + PRESENTING gate
so sequential showToast calls render back-to-back.

New `crates/perry-ui-windows/src/widgets/text_registry.rs`: process-global
Mutex<HashMap<String, i64>> mapping text-id → widget handle; handlers
wired to perry-runtime's js_register_{show_toast,set_text,text_id}_handler
AtomicPtr slots via register_cross_platform_text_handlers() at the top of
app_run — mirrors macOS v0.5.408 shape verbatim.

`widgets/mod.rs`: pub mod text_registry + pub mod toast.
`app.rs`: register_cross_platform_text_handlers() call + extern "C"
declarations + fn body at bottom of file.

No codegen changes — lower_call/native.rs showToast/setText/Text(content,id)
routing already landed in v0.5.408 (PR #326).

Stacks on PR #326 (Phase 2 v3.3 macOS). cargo test -p perry-codegen-arkts
23/23. All 4 pre-existing Linux-host `cargo check` errors are in text.rs
(Windows-only GDI code), unchanged by this PR.

https://claude.ai/code/session_01M7ZU3gwSqD2RSz3NTemCZu
proggeramlug added a commit that referenced this pull request Apr 30, 2026
Borderless layered HWND popup with alpha-fade. id→handle setText. Stacked on #326.
proggeramlug pushed a commit that referenced this pull request Apr 30, 2026
New widgets/toast.rs: GtkRevealer-backed slide-down banner overlaid on
the main ApplicationWindow via a gtk4::Overlay wrapper inserted in
app_run. Messages are queued (FIFO) with a 2.5 s hold + 200 ms slide
transition; pump_queue() drains one at a time so toasts never stack.

New widgets/text_registry.rs: thread_local id→handle map. Text(content,
id) (2-arg form, intercepted in lower_call/native.rs before the dispatch
table) calls perry_ui_text_create_with_id which creates the GtkLabel and
registers it. setText(id, val) calls perry_ui_set_text which looks up the
handle and calls text::set_text_str.

Dispatch: showToast + setText added to PERRY_UI_TABLE in perry-dispatch.
Text(content, id) 2-arg arm in native.rs mirrors the Button / VStack
special-case pattern — intercepted before the 1-arg table entry fires.

Stubs in perry-ui-macos / perry-ui-windows / perry-ui-ios / perry-ui-tvos
so cross-platform user code links on every target today. macOS / iOS /
tvOS stubs delegate perry_ui_text_create_with_id → perry_ui_text_create
so the widget still renders; setText is a no-op until #326 lands.

Verified: perry-dispatch builds clean; perry-codegen builds clean (12
pre-existing warnings, none new); perry-runtime + perry-stdlib + perry
all build clean; cargo test -p perry-codegen-arkts 15/15 unaffected.
GTK4 crate itself errors only on the missing system gdk-pixbuf-2.0
header (not installed in this sandbox) — the CI ubuntu-latest runner has
gtk4 dev packages and will resolve cleanly.

https://claude.ai/code/session_01Q4Z1Hmg8kgWidwYzueJjvW
@proggeramlug proggeramlug deleted the phase-2-v3.3 branch May 10, 2026 06:50
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