Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 76 additions & 76 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4935,6 +4935,9 @@ pub static API_MANIFEST: &[ApiEntry] = &[
method("perry/ui", "lazyvstackSetScrollEndCallback", false, None),
method("perry/ui", "Table", false, None),
method("perry/ui", "Canvas", false, None),
// Issue #2395 — BloomView (embed an external GPU renderer / Bloom engine)
method("perry/ui", "BloomView", false, None),
method("perry/ui", "bloomViewGetHwnd", false, None),
method("perry/ui", "CameraView", false, None),
method("perry/ui", "cameraStart", false, None),
method("perry/ui", "cameraStop", false, None),
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-codegen-wasm/src/wasm_runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -2830,6 +2830,17 @@ function perry_ui_canvas_create(width, height) {
el._ctx = el.getContext("2d");
return uiAlloc(el);
}
// BloomView (issue #2395) — a <canvas> render-surface host for an external GPU
// renderer. The web has no HWND; bloomViewGetHwnd returns the handle id (the
// surrogate the renderer keys off of) or 0 if unknown.
function perry_ui_bloomview_create(width, height) {
const el = document.createElement("canvas");
el.width = width || 300; el.height = height || 150;
return uiAlloc(el);
}
function perry_ui_bloomview_get_hwnd(h) {
return uiGet(h) ? h : 0;
}
function perry_ui_lazyvstack_create(count, renderClosure) {
const el = document.createElement("div");
el.style.display = "flex"; el.style.flexDirection = "column"; el.style.overflow = "auto"; el.style.flex = "1 1 0%";
Expand Down Expand Up @@ -4632,6 +4643,7 @@ const __perryUiDispatch = {
perry_ui_toggle_create, perry_ui_toggle_set_state, perry_ui_slider_create, perry_ui_scrollview_create, perry_ui_spacer_create,
perry_ui_divider_create, perry_ui_progressview_create, perry_ui_image_create, perry_ui_picker_create,
perry_ui_form_create, perry_ui_section_create, perry_ui_navigationstack_create, perry_ui_canvas_create,
perry_ui_bloomview_create, perry_ui_bloomview_get_hwnd,
perry_ui_lazyvstack_create, perry_ui_lazyvstack_update, perry_ui_table_create,
perry_ui_table_set_column_header, perry_ui_table_set_column_width,
perry_ui_table_update_row_count, perry_ui_table_set_on_row_select, perry_ui_table_get_selected_row,
Expand Down
16 changes: 16 additions & 0 deletions crates/perry-dispatch/src/ui_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,22 @@ pub const PERRY_UI_TABLE: &[MethodRow] = &[
args: &[ArgKind::F64, ArgKind::F64],
ret: ReturnKind::Widget,
},
// ---- BloomView (issue #2395) ----
// A render-surface host: `BloomView(width, height)` reserves a child window
// the Bloom engine draws into. `bloomViewGetHwnd(view)` returns the raw HWND
// (as a JS number) so user TS can call `attachToHwnd` on the Bloom package.
MethodRow {
method: "BloomView",
runtime: "perry_ui_bloomview_create",
args: &[ArgKind::F64, ArgKind::F64],
ret: ReturnKind::Widget,
},
MethodRow {
method: "bloomViewGetHwnd",
runtime: "perry_ui_bloomview_get_hwnd",
args: &[ArgKind::Widget],
ret: ReturnKind::I64AsF64,
},
// ---- Drag & drop (issue #4773) ----
// Widget-level setters that attach drag/drop behavior to an existing
// widget handle. `widgetOnDrop` registers a drop destination; the
Expand Down
16 changes: 16 additions & 0 deletions crates/perry-ui-android/src/ffi/issue_553.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ pub extern "C" fn perry_ui_webview_create(
widgets::webview::create(url_ptr as *const u8, width, height, ephemeral)
})
}

/// Create a BloomView render-surface host (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(width: f64, height: f64) -> i64 {
catch_panic("perry_ui_bloomview_create", || {
widgets::bloomview::create(width, height)
})
}

/// Return the BloomView's native handle token (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(handle: i64) -> i64 {
catch_panic("perry_ui_bloomview_get_hwnd", || {
widgets::bloomview::get_native_handle(handle)
})
}
#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(handle: i64, ua_ptr: i64) {
catch_panic_void("perry_ui_webview_set_user_agent", || {
Expand Down
57 changes: 57 additions & 0 deletions crates/perry-ui-android/src/widgets/bloomview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! BloomView — a native render-surface host widget (issue #2395).
//!
//! Reserves a bare `android.view.View` in the Perry UI view tree for an external
//! GPU renderer (e.g. the Bloom engine) to draw into. Perry UI only owns the
//! view; user TypeScript drives the renderer. Mirrors the Windows implementation
//! conceptually — Android has no HWND, so `bloomViewGetHwnd` echoes the registry
//! handle as a stable token (a real `SurfaceView`/`TextureView` + JNI surface
//! bridge is the follow-up for live embedding).

use crate::jni_bridge;
use jni::objects::JValue;

/// Create a BloomView host. Returns the widget handle, or 0 on JNI failure.
pub fn create(_width: f64, _height: f64) -> i64 {
let mut env = jni_bridge::get_env();
let _ = env.push_local_frame(8);

let activity = super::get_activity(&mut env);
let view = match env.new_object(
"android/view/View",
"(Landroid/content/Context;)V",
&[JValue::Object(&activity)],
Comment on lines +14 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect Android widget implementations for dimension/layout handling.
# Expectation: Either BloomView applies width/height here, or another Android layout path clearly applies them for registered widgets.

rg -n -C3 'setMinimumWidth|setMinimumHeight|setLayoutParams|LayoutParams|fn create\(.*width|_width|_height' crates/perry-ui-android/src/widgets

Repository: PerryTS/perry

Length of output: 50369


🏁 Script executed:

cat -n crates/perry-ui-android/src/widgets/bloomview.rs

Repository: PerryTS/perry

Length of output: 2237


Apply the requested BloomView size on Android.

BloomView(width, height) receives dimensions, but Android drops both values before registering a plain View. Unless another Android layout layer applies these dimensions, this can reserve a zero/default-sized host while GTK/web peers size their render surfaces.

Possible localized fix direction
-pub fn create(_width: f64, _height: f64) -> i64 {
+pub fn create(width: f64, height: f64) -> i64 {
     let mut env = jni_bridge::get_env();
     let _ = env.push_local_frame(8);

     let activity = super::get_activity(&mut env);
     let view = match env.new_object(
         "android/view/View",
         "(Landroid/content/Context;)V",
         &[JValue::Object(&activity)],
     ) {
         Ok(v) => v,
         Err(_) => {
             unsafe {
                 let _ = env.pop_local_frame(&jni::objects::JObject::null());
             }
             return 0;
         }
     };
+
+    if width > 0.0 {
+        let _ = env.call_method(
+            &view,
+            "setMinimumWidth",
+            "(I)V",
+            &[JValue::Int(width.min(i32::MAX as f64) as i32)],
+        );
+    }
+    if height > 0.0 {
+        let _ = env.call_method(
+            &view,
+            "setMinimumHeight",
+            "(I)V",
+            &[JValue::Int(height.min(i32::MAX as f64) as i32)],
+        );
+    }
🤖 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 `@crates/perry-ui-android/src/widgets/bloomview.rs` around lines 14 - 22, The
`create` function receives `_width` and `_height` parameters that are currently
unused (indicated by the underscore prefix), but these dimensions are not being
applied to the Android View being created via `env.new_object`. Remove the
underscore prefixes from the width and height parameters to indicate they will
be used, then modify the View construction to apply these dimensions when
creating the View object so that the Android host respects the intended size
rather than defaulting to zero or a default size.

) {
Ok(v) => v,
Err(_) => {
unsafe {
let _ = env.pop_local_frame(&jni::objects::JObject::null());
}
return 0;
}
};

let global_ref = match env.new_global_ref(&view) {
Ok(g) => g,
Err(_) => {
unsafe {
let _ = env.pop_local_frame(&jni::objects::JObject::null());
}
return 0;
}
};
unsafe {
let _ = env.pop_local_frame(&jni::objects::JObject::null());
}

super::register_widget(global_ref)
}

/// Android has no HWND; echo the registry handle as a stable token for the
/// caller — but validate it first (return 0 for an unknown/stale handle), so
/// downstream renderers never treat a bogus handle as attachable.
pub fn get_native_handle(handle: i64) -> i64 {
match super::get_widget(handle) {
Some(_) => handle,
None => 0,
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions crates/perry-ui-android/src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod attributed_text;
pub mod bloomview;
pub mod bottom_nav;
pub mod button;
pub mod calendar;
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-ui-gtk4/src/ffi/stubs_webview_attrtext_screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ pub extern "C" fn perry_ui_webview_create(
) -> i64 {
widgets::webview::create(url_ptr as *const u8, width, height, ephemeral)
}

/// Create a BloomView render-surface host (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(width: f64, height: f64) -> i64 {
widgets::bloomview::create(width, height)
}

/// Return the BloomView's native `GtkWidget*` as an integer (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(handle: i64) -> i64 {
widgets::bloomview::get_native_handle(handle)
}
#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(handle: i64, ua_ptr: i64) {
widgets::webview::set_user_agent(handle, ua_ptr as *const u8)
Expand Down
41 changes: 41 additions & 0 deletions crates/perry-ui-gtk4/src/widgets/bloomview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! BloomView — a native render-surface host widget (issue #2395).
//!
//! Reserves a `GtkDrawingArea` in the Perry UI view tree for an external GPU
//! renderer (e.g. the Bloom engine) to draw into. Perry UI only owns the widget
//! and exposes its `GtkWidget*` via `bloomViewGetHwnd`; user TypeScript hands
//! that to the renderer, which targets the widget's surface (the issue's MVP
//! used GTK4 + Vulkan dmabuf). Mirrors the Windows implementation, with the
//! HWND replaced by the raw `GtkWidget*`.

use gtk4::prelude::*;

/// Create a BloomView host. Reserves the requested size, or expands to fill if
/// none is given. Returns the widget handle.
pub fn create(width: f64, height: f64) -> i64 {
crate::app::ensure_gtk_init();
let area = gtk4::DrawingArea::new();
// Only honor finite, sensibly-bounded sizes — NaN/inf or sub-pixel
// fractions would produce a bogus GTK size request. Otherwise expand.
if width.is_finite() && height.is_finite() && width >= 1.0 && height >= 1.0 {
let w = (width as i32).clamp(1, 16384);
let h = (height as i32).clamp(1, 16384);
area.set_size_request(w, h);
} else {
area.set_hexpand(true);
area.set_vexpand(true);
}
super::register_widget(area.upcast())
}

/// Return the raw `GtkWidget*` for a BloomView handle as an integer, for handing
/// to an external GPU renderer. Returns 0 if the handle is unknown.
pub fn get_native_handle(handle: i64) -> i64 {
use gtk4::glib::translate::ToGlibPtr;
match super::get_widget(handle) {
Some(w) => {
let ptr: *mut gtk4::ffi::GtkWidget = w.to_glib_none().0;
ptr as i64
}
None => 0,
}
}
1 change: 1 addition & 0 deletions crates/perry-ui-gtk4/src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod attributed_text;
pub mod bloomview;
pub mod bottom_nav;
pub mod button;
pub mod calendar;
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-ui-ios/src/ffi/widgets_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ pub extern "C" fn perry_ui_webview_create(
) -> i64 {
widgets::webview::create(url_ptr as *const u8, width, height, ephemeral)
}

/// Create a BloomView render-surface host (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(width: f64, height: f64) -> i64 {
widgets::bloomview::create(width, height)
}

/// Return the BloomView's native view pointer as an integer (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(handle: i64) -> i64 {
widgets::bloomview::get_native_handle(handle)
}
#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(handle: i64, ua_ptr: i64) {
widgets::webview::set_user_agent(handle, ua_ptr as *const u8)
Expand Down
38 changes: 38 additions & 0 deletions crates/perry-ui-ios/src/widgets/bloomview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! BloomView — a native render-surface host widget (issue #2395).
//!
//! Reserves a bare `UIView` in the Perry UI view tree for an external GPU
//! renderer (e.g. the Bloom engine) to draw into. Perry UI only owns the view
//! and exposes its pointer via `bloomViewGetHwnd`; user TypeScript hands that
//! pointer to the renderer, which builds its (Metal) surface on it. Mirrors the
//! Windows implementation, with the HWND replaced by the raw `UIView*`.

use objc2::msg_send;
use objc2::rc::Retained;
use objc2::runtime::AnyClass;
use objc2_foundation::MainThreadMarker;
use objc2_ui_kit::UIView;

/// Create a BloomView host. `width`/`height` are advisory — the layout engine
/// sizes the view. Returns the widget handle (0 if called off the main thread).
pub fn create(width: f64, height: f64) -> i64 {
let _ = (width, height);
// UIKit views must be created on the main thread; don't panic across the
// FFI boundary if that contract is violated — return an invalid handle.
let Some(_mtm) = MainThreadMarker::new() else {
return 0;
};
unsafe {
let view: Retained<UIView> = msg_send![AnyClass::get(c"UIView").unwrap(), new];
super::register_widget(view)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

/// Return the raw `UIView*` for a BloomView handle as an integer, for handing
/// to an external GPU renderer. Returns 0 if the handle is unknown. The
/// registry retains the view; the returned pointer is non-owning.
pub fn get_native_handle(handle: i64) -> i64 {
match super::get_widget(handle) {
Some(view) => Retained::as_ptr(&view) as i64,
None => 0,
}
}
1 change: 1 addition & 0 deletions crates/perry-ui-ios/src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod alert;
pub mod attributed_text;
pub mod bloomview;
pub mod bottom_nav;
pub mod button;
pub mod calendar;
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-ui-macos/src/lib_ffi/interactivity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,18 @@ pub extern "C" fn perry_ui_webview_create(
) -> i64 {
widgets::webview::create(url_ptr as *const u8, width, height, ephemeral)
}

/// Create a BloomView render-surface host (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(width: f64, height: f64) -> i64 {
widgets::bloomview::create(width, height)
}

/// Return the BloomView's native view pointer as an integer (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(handle: i64) -> i64 {
widgets::bloomview::get_native_handle(handle)
}
#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(handle: i64, ua_ptr: i64) {
widgets::webview::set_user_agent(handle, ua_ptr as *const u8)
Expand Down
34 changes: 34 additions & 0 deletions crates/perry-ui-macos/src/widgets/bloomview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! BloomView — a native render-surface host widget (issue #2395).
//!
//! Reserves a bare `NSView` in the Perry UI view tree for an external GPU
//! renderer (e.g. the Bloom engine) to draw into. Perry UI only owns the view
//! and exposes its pointer via `bloomViewGetHwnd`; user TypeScript hands that
//! pointer to the renderer, which builds its (Metal) surface on it. Mirrors the
//! Windows implementation (`perry-ui-windows`), with the HWND replaced by the
//! raw `NSView*`.

use objc2_app_kit::NSView;
use objc2_foundation::MainThreadMarker;

/// Create a BloomView host. `width`/`height` are advisory — the layout engine
/// sizes the view. Returns the widget handle.
pub fn create(width: f64, height: f64) -> i64 {
let _ = (width, height);
// Public C ABI entry — don't panic across the FFI boundary if called off
// the main thread; return an invalid (0) handle instead.
let Some(mtm) = MainThreadMarker::new() else {
return 0;
};
let view = NSView::new(mtm);
super::register_widget(view)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// Return the raw `NSView*` for a BloomView handle as an integer, for handing
/// to an external GPU renderer. Returns 0 if the handle is unknown. The
/// registry retains the view; the returned pointer is non-owning.
pub fn get_native_handle(handle: i64) -> i64 {
match super::get_widget(handle) {
Some(view) => objc2::rc::Retained::as_ptr(&view) as i64,
None => 0,
}
}
1 change: 1 addition & 0 deletions crates/perry-ui-macos/src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod alert;
pub mod attributed_text;
pub mod bloomview;
pub mod bottom_nav;
pub mod button;
pub mod calendar;
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-ui-tvos/src/ffi/media_extras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ pub extern "C" fn perry_ui_webview_create(
) -> i64 {
0
}

// BloomView (issue #2395) — stub on this platform, matching the WebView shape
// above; returns a 0 handle so apps that import BloomView still link and run.
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(_width: f64, _height: f64) -> i64 {
0
}
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(_handle: i64) -> i64 {
0
}

#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(_handle: i64, _ua_ptr: i64) {}
#[no_mangle]
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-ui-visionos/src/ffi_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ pub extern "C" fn perry_ui_webview_create(
) -> i64 {
widgets::webview::create(url_ptr as *const u8, width, height, ephemeral)
}

/// Create a BloomView render-surface host (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_create(width: f64, height: f64) -> i64 {
widgets::bloomview::create(width, height)
}

/// Return the BloomView's native view pointer as an integer (issue #2395).
#[no_mangle]
pub extern "C" fn perry_ui_bloomview_get_hwnd(handle: i64) -> i64 {
widgets::bloomview::get_native_handle(handle)
}
#[no_mangle]
pub extern "C" fn perry_ui_webview_set_user_agent(handle: i64, ua_ptr: i64) {
widgets::webview::set_user_agent(handle, ua_ptr as *const u8)
Expand Down
Loading
Loading