Skip to content

feat(ui): BloomView live-render plumbing on every backend (#5519)#5538

Merged
proggeramlug merged 2 commits into
mainfrom
feat/bloomview-native-handle-5519
Jun 22, 2026
Merged

feat(ui): BloomView live-render plumbing on every backend (#5519)#5538
proggeramlug merged 2 commits into
mainfrom
feat/bloomview-native-handle-5519

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Perry-UI side of #5519. The engine side landed in Bloom-Engine/engine#71 (the platform-neutral bloom_attach_native); this makes a BloomView actually usable as a render surface on every backend, not just Windows.

Changes

  • Rename bloomViewGetHwndbloomViewGetNativeHandle, keeping bloomViewGetHwnd as a deprecated alias (both dispatch rows route to the same perry_ui_bloomview_get_hwnd runtime symbol — no FFI symbol churn). Platform-neutral now that the handle is an NSView*/UIView*/GtkWidget*/ANativeWindow*, not only an HWND. Touches ui_table.rs, api-manifest entries.rs, types/perry/ui/index.d.ts, and the regenerated docs/api/perry.d.ts + docs/src/api/reference.md.
  • Sizing: the non-Windows backends created the view but ignored the requested size, so the renderer's surface came up 0×0 and nothing drew. BloomView(w, h) now pins the size — macOS/iOS/visionOS/tvOS via Auto Layout (set_width/set_height + translatesAutoresizingMaskIntoConstraints = false), Android via LayoutParams; GTK4/Windows already did.
  • tvOS: promoted the 0-handle stub to a real UIView (new crates/perry-ui-tvos/src/widgets/bloomview.rs), mirroring iOS.
  • Android: switched the host widget android.view.Viewandroid.view.SurfaceView; bloomViewGetNativeHandle now returns the real ANativeWindow* (NDK ANativeWindow_fromSurface on the view's SurfaceHolder.getSurface()) instead of echoing the registry token. Returns 0 until the surface is laid out / surfaceCreated.
  • Input/focus: the host view is now focusable so a focused BloomView receives keyboard/pointer events for the attached engine to consume (macOS acceptsFirstResponder via a PerryBloomView subclass; iOS/tvOS/visionOS userInteractionEnabled; GTK set_focusable/set_can_target; Android setFocusable/setFocusableInTouchMode).
  • Demo: examples/bloomview_embed_demo.ts — a live Bloom scene inside a Perry UI window (BloomView + bloomViewGetNativeHandle + the engine's attachToNSView, driven from onFrame).

Verification

  • macOS end-to-end: the integrated binary (Perry UI + the Bloom engine) links and runs the attach/frame loop without crashing (a bogus handle or failed surface attach would crash). Codegen confirmed bloomViewGetNativeHandle lowers to perry_ui_bloomview_get_hwnd.
  • iOS cross-compiles; tvOS/visionOS host-build; perry-dispatch + perry-api-manifest tests pass (36); no api-docs drift; my changed files are clippy-clean.
  • Android/GTK/Windows are best-effort (no local cross-toolchain — NDK/gtk/MSVC), mirroring each backend's established widget patterns; the Android JNI matches the crate's existing jni 0.21 usage.

Follow-ups (not blocking)

Full keyboard/mouse event forwarding into the engine is the engine's job once attached (on Windows Bloom subclasses the host wndproc); this PR makes the views focusable/input-capable. Android's surface-ready callback (surfaceCreated) deferral and the real GTK4 GdkSurface bridge remain per the issue.

Closes the Perry-UI checklist of #5519.

Summary by CodeRabbit

Release Notes v0.5.1199

  • New Features

    • Added cross-platform bloomViewGetNativeHandle() for attaching Bloom rendering to the native BloomView surface, with bloomViewGetHwnd() retained as a deprecated alias.
    • Updated the BloomView embed demo for live integration.
  • Bug Fixes

    • Fixed BloomView render-surface sizing to avoid starting at 0×0.
    • Improved BloomView focus and input handling so hosted scenes can receive events.
  • Updates

    • tvOS now uses a real UIView host; iOS/macOS/visionOS apply sizing properly.
    • Android now uses a surface-based rendering approach and returns a native handle when ready.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b89a9ee1-02e1-4370-9f64-b75c3f54d7ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4d61bf7 and 553125f.

📒 Files selected for processing (3)
  • crates/perry-ui-android/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/widgets/bloomview.rs
  • types/perry/ui/index.d.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • types/perry/ui/index.d.ts
  • crates/perry-ui-tvos/src/widgets/bloomview.rs
  • crates/perry-ui-android/src/widgets/bloomview.rs

📝 Walkthrough

Walkthrough

Version bumped to 0.5.1199. bloomViewGetNativeHandle is introduced as the canonical API (replacing bloomViewGetHwnd, kept as a deprecated alias) in TypeScript types, the API manifest, and the dispatch table. All native backends are updated: Android switches to SurfaceView/ANativeWindow*, macOS introduces a PerryBloomView NSView subclass, tvOS is promoted from stubs to real UIView, and iOS/visionOS/GTK4 gain proper sizing and focus. An embed demo is added alongside regenerated docs.

Changes

BloomView Live-Render Plumbing

Layer / File(s) Summary
API contract: types, manifest, and dispatch
types/perry/ui/index.d.ts, crates/perry-api-manifest/src/entries.rs, crates/perry-dispatch/src/ui_table.rs
Adds bloomViewGetNativeHandle(view: Widget): number to TypeScript types, marks bloomViewGetHwnd deprecated, registers the new method in the API manifest, and inserts the dispatch-table entry routing it to the same runtime symbol as the old alias.
Android: SurfaceView host and ANativeWindow* handle
crates/perry-ui-android/src/widgets/bloomview.rs
Switches host from a generic View to SurfaceView, adds NDK ANativeWindow_fromSurface extern binding, enables touch/keyboard focus, and rewrites get_native_handle to validate Surface.isValid() before returning a real ANativeWindow* pointer with caching.
macOS: PerryBloomView NSView subclass and sizing
crates/perry-ui-macos/src/widgets/bloomview.rs
Defines a PerryBloomView subclass overriding acceptsFirstResponder, replaces plain NSView with this subclass in create, disables autoresizing-mask translation, and conditionally pins width/height when finite and ≥ 1.0.
iOS, tvOS, visionOS, and GTK4 backend updates
crates/perry-ui-ios/src/widgets/bloomview.rs, crates/perry-ui-tvos/src/widgets/bloomview.rs, crates/perry-ui-tvos/src/widgets/mod.rs, crates/perry-ui-tvos/src/ffi/media_extras.rs, crates/perry-ui-visionos/src/widgets/bloomview.rs, crates/perry-ui-gtk4/src/widgets/bloomview.rs
iOS and visionOS create functions gain Auto Layout setup and conditional sizing. tvOS adds a new bloomview.rs module with real create/get_native_handle implementations, wired through mod.rs and replacing zero-returning FFI stubs. GTK4 enables focus and pointer targeting on the DrawingArea.
Embed demo, generated API docs, and version bump
examples/bloomview_embed_demo.ts, docs/api/perry.d.ts, docs/src/api/reference.md, CHANGELOG.md, CLAUDE.md, Cargo.toml
Adds an embed demo that waits for a non-zero native handle then drives a Bloom render loop inside a Perry window. Updates generated perry.d.ts and reference.md with the new API. Bumps workspace version to 0.5.1199 across all version files and adds a changelog entry.

Sequence Diagram(s)

sequenceDiagram
  participant TS as TypeScript caller
  participant Perry as Perry dispatch (ui_table)
  participant Backend as Native backend (platform)
  participant Bloom as Bloom engine

  TS->>Perry: bloomViewCreate(width, height)
  Perry->>Backend: create SurfaceView / UIView / NSView / DrawingArea
  Backend-->>Perry: widget handle (i64)
  Perry-->>TS: handle

  loop each frame until surface ready
    TS->>Perry: bloomViewGetNativeHandle(handle)
    Perry->>Backend: validate widget, check surface ready
    Backend-->>Perry: 0 (not ready)
    Perry-->>TS: 0
  end

  TS->>Perry: bloomViewGetNativeHandle(handle)
  Perry->>Backend: ANativeWindow_fromSurface / NSView* / UIView* / GtkWidget*
  Backend-->>Perry: native pointer as i64
  Perry-->>TS: non-zero handle

  TS->>Bloom: attachToNSView(nativeHandle)
  loop render frames
    TS->>Bloom: clearBackground + draw + endFrame
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5518 — Directly related: introduces the original BloomView widget wiring and the bloomViewGetHwnd dispatch entry that this PR renames and extends.

Poem

🐇 Hop hop, the surface is ready at last,
From stubbed-out zeros to real handles fast!
SurfaceView blooms on Android's stage,
PerryBloomView turns a fresh macOS page.
tvOS joins in—no longer a ghost—
And the demo confirms: the rabbit loves this most! 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(ui): BloomView live-render plumbing on every backend (#5519)' clearly and concisely summarizes the main change: enabling BloomView as a live-render surface across all backends, not just Windows.
Description check ✅ Passed The PR description comprehensively covers all required sections: summary, detailed changes (covering API rename, sizing, tvOS, Android, input/focus, and demo), related issue reference (#5519), verification steps for multiple platforms, and acknowledgment of the contributor's adherence to repository guidelines.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bloomview-native-handle-5519

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-ui-android/src/widgets/bloomview.rs`:
- Around line 74-80: The get_native_handle function calls
ANativeWindow_fromSurface on every invocation which returns a window reference
with a +1 refcount, but the code never calls ANativeWindow_release to decrement
these references. This causes reference leaks on repeated invocations. Implement
proper reference management by either caching the ANativeWindow pointer and
reusing it instead of creating a new one each time, or by releasing the previous
reference before returning a new one. Additionally, add a cleanup path in the
BloomView destructor to release the final reference when the view is destroyed.

In `@crates/perry-ui-tvos/src/widgets/bloomview.rs`:
- Around line 16-33: The tvOS create() function in bloomview.rs is missing the
main thread safety guard that exists in the iOS implementation. Add a
MainThreadMarker::new() check at the beginning of the function that returns 0 if
the guard fails (when Some variant is not present), before any UIKit operations.
This ensures the function respects UIKit's requirement that all view creation
and manipulation must occur on the main thread.

In `@types/perry/ui/index.d.ts`:
- Around line 446-447: The JSDoc comment in the file contains literal
asterisk-slash sequences (like NSView*/UIView*/GtkWidget*/ANativeWindow*) that
prematurely close the block comment because */ is interpreted as the end of the
JSDoc block. To fix this, escape or separate these sequences to prevent early
comment termination. You can insert a space between the asterisk and slash
(e.g., NSView* /UIView* /) or use other escaping techniques supported by JSDoc
parsers to ensure the comment text containing these character combinations is
properly preserved within the block comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d191eef-d5fe-43ab-a65f-f892e4087d64

📥 Commits

Reviewing files that changed from the base of the PR and between 9247f8c and 9c1bf74.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • CHANGELOG.md
  • CLAUDE.md
  • Cargo.toml
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-dispatch/src/ui_table.rs
  • crates/perry-ui-android/src/widgets/bloomview.rs
  • crates/perry-ui-gtk4/src/widgets/bloomview.rs
  • crates/perry-ui-ios/src/widgets/bloomview.rs
  • crates/perry-ui-macos/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/ffi/media_extras.rs
  • crates/perry-ui-tvos/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/widgets/mod.rs
  • crates/perry-ui-visionos/src/widgets/bloomview.rs
  • docs/api/perry.d.ts
  • docs/src/api/reference.md
  • examples/bloomview_embed_demo.ts
  • types/perry/ui/index.d.ts

Comment thread crates/perry-ui-android/src/widgets/bloomview.rs Outdated
Comment thread crates/perry-ui-tvos/src/widgets/bloomview.rs
Comment thread types/perry/ui/index.d.ts Outdated
Perry-UI side of #5519 (the engine side landed in Bloom-Engine/engine#71 as
the platform-neutral bloom_attach_native). Makes a BloomView actually usable as
a render surface on every backend, not just Windows.

- Rename bloomViewGetHwnd -> bloomViewGetNativeHandle, keeping the old name as a
  deprecated alias (both dispatch rows route to the same
  perry_ui_bloomview_get_hwnd runtime symbol). Platform-neutral now that the
  handle is an NSView*/UIView*/GtkWidget*/ANativeWindow*, not only an HWND.
  Updated ui_table.rs, api-manifest entries.rs, types/perry/ui/index.d.ts, and
  regenerated docs/api/perry.d.ts + docs/src/api/reference.md.
- Sizing: the non-Windows backends ignored the requested size, so the renderer's
  surface came up 0x0. BloomView(w,h) now pins the size on macOS/iOS/visionOS/
  tvOS (Auto Layout) and Android (LayoutParams); GTK/Windows already did.
- tvOS: promoted the 0-handle stub to a real UIView (new widgets/bloomview.rs).
- Android: switched View -> SurfaceView; bloomViewGetNativeHandle now returns the
  real ANativeWindow* (ANativeWindow_fromSurface on the SurfaceHolder surface),
  0 until the surface is ready.
- Input/focus: the host view is now focusable so a focused BloomView receives
  key/pointer events for the attached engine (macOS acceptsFirstResponder via a
  PerryBloomView subclass; iOS/tvOS/visionOS userInteractionEnabled; GTK
  set_focusable; Android setFocusable).
- examples/bloomview_embed_demo.ts: a live Bloom scene inside a Perry UI window
  (BloomView + bloomViewGetNativeHandle + attachToNSView, driven by onFrame).

Verified on macOS: the integrated binary (Perry UI + Bloom engine) links and
runs the attach/frame loop without crashing; iOS cross-compiles; tvOS/visionOS
host-build; dispatch+manifest tests pass; no api-docs drift. Android/GTK/Windows
are best-effort (no local cross-toolchain), mirroring established per-backend
patterns.
@proggeramlug proggeramlug force-pushed the feat/bloomview-native-handle-5519 branch from 9c1bf74 to 4d61bf7 Compare June 22, 2026 05:13

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (1)
types/perry/ui/index.d.ts (1)

446-447: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix JSDoc text that breaks parsing at Line 446.

The */ sequences in NSView*/UIView*/... close the block comment early, which causes the parse errors reported at Lines 446-447.

Suggested minimal fix
- * is no longer Windows-only (it's an NSView*/UIView*/GtkWidget*/ANativeWindow*
+ * is no longer Windows-only (it's an `NSView*`, `UIView*`, `GtkWidget*`, or
+ * `ANativeWindow*`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@types/perry/ui/index.d.ts` around lines 446 - 447, The JSDoc block comment in
the typedef section is being prematurely closed by the `*/` sequences appearing
in the type names like NSView*/UIView*/GtkWidget*/ANativeWindow* on lines
446-447. To fix this, escape these character sequences by replacing the forward
slashes with HTML entities (such as `&`#47`;`) or by adding spaces between the
asterisk and forward slash (like `* /`) to prevent them from being interpreted
as comment closers. This will allow the JSDoc parser to correctly interpret the
full comment block without early termination.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@types/perry/ui/index.d.ts`:
- Around line 446-447: The JSDoc block comment in the typedef section is being
prematurely closed by the `*/` sequences appearing in the type names like
NSView*/UIView*/GtkWidget*/ANativeWindow* on lines 446-447. To fix this, escape
these character sequences by replacing the forward slashes with HTML entities
(such as `&`#47`;`) or by adding spaces between the asterisk and forward slash
(like `* /`) to prevent them from being interpreted as comment closers. This
will allow the JSDoc parser to correctly interpret the full comment block
without early termination.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 67257e11-4a39-4f8f-8f49-c70403fdfa0a

📥 Commits

Reviewing files that changed from the base of the PR and between 9c1bf74 and 4d61bf7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • CHANGELOG.md
  • CLAUDE.md
  • Cargo.toml
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-dispatch/src/ui_table.rs
  • crates/perry-ui-android/src/widgets/bloomview.rs
  • crates/perry-ui-gtk4/src/widgets/bloomview.rs
  • crates/perry-ui-ios/src/widgets/bloomview.rs
  • crates/perry-ui-macos/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/ffi/media_extras.rs
  • crates/perry-ui-tvos/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/widgets/mod.rs
  • crates/perry-ui-visionos/src/widgets/bloomview.rs
  • docs/api/perry.d.ts
  • docs/src/api/reference.md
  • examples/bloomview_embed_demo.ts
  • types/perry/ui/index.d.ts
✅ Files skipped from review due to trivial changes (6)
  • crates/perry-ui-tvos/src/widgets/mod.rs
  • CLAUDE.md
  • CHANGELOG.md
  • Cargo.toml
  • docs/api/perry.d.ts
  • docs/src/api/reference.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • crates/perry-ui-gtk4/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/widgets/bloomview.rs
  • crates/perry-ui-tvos/src/ffi/media_extras.rs
  • examples/bloomview_embed_demo.ts
  • crates/perry-ui-ios/src/widgets/bloomview.rs
  • crates/perry-ui-visionos/src/widgets/bloomview.rs
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-dispatch/src/ui_table.rs
  • crates/perry-ui-macos/src/widgets/bloomview.rs
  • crates/perry-ui-android/src/widgets/bloomview.rs

- android BloomView: cache the ANativeWindow* per handle instead of calling
  ANativeWindow_fromSurface (which returns a +1-refcount window) on every
  get_native_handle poll — that leaked a window reference per call. The first
  ready surface builds the window; later calls reuse the cached pointer.
- tvos BloomView create(): add the MainThreadMarker main-thread guard the iOS
  path has (UIKit view creation must be on the main thread; return 0 otherwise).
- types/perry/ui/index.d.ts: the deprecated bloomViewGetHwnd JSDoc had a raw
  `NSView*/UIView*/…` whose `*/` prematurely closed the block comment — backtick
  the type names so the sequence no longer terminates the comment.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — all three addressed in 553125f:

  1. Android ref leakget_native_handle was calling ANativeWindow_fromSurface (which returns a +1-refcount window) on every poll. Now the first ready surface builds the window and it's cached per handle; later calls return the same pointer, so polling every frame no longer leaks a reference.
  2. tvOS main-thread guard — added the MainThreadMarker::new() check the iOS path has; create() returns 0 off the main thread before any UIKit calls.
  3. JSDoc */ — the deprecated bloomViewGetHwnd comment had a raw NSView*/UIView*/… whose */ closed the block early; backticked the type names so it no longer terminates the comment.

Verified: tvOS + the Android crate both build on host; the macOS path is unchanged.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@proggeramlug: Thanks for the thorough summary — all three fixes look exactly right:

  1. Android ref leak — caching the ANativeWindow* per handle and skipping ANativeWindow_fromSurface on subsequent polls is the correct approach; the +1 refcount is now acquired exactly once per surface lifetime.
  2. tvOS main-thread guard — mirroring the iOS MainThreadMarker::new() early-return pattern keeps UIKit usage safe across the board.
  3. JSDoc */ — backticking the pointer-type names is a clean fix that won't interfere with any Markdown or JSDoc parser.

Appreciate the verification on both host builds. This looks good to go! 🐇✨

@proggeramlug proggeramlug merged commit bea1dc7 into main Jun 22, 2026
15 checks passed
@proggeramlug proggeramlug deleted the feat/bloomview-native-handle-5519 branch June 22, 2026 06:15
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