diff --git a/CHANGELOG.md b/CHANGELOG.md index eae2a6559..6a678e5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features -- **ribir**: Introduced `AppRunGuard` to allow app and window configuration prior to app startup. (#pr, @M-Adoo) +- **ribir**: Introduced `AppRunGuard` to allow app and window configuration prior to app startup. (#565, @M-Adoo) Previously, to configure the app and window before startup, `App::run` couldn't be used: ``` rust unsafe { @@ -51,7 +51,8 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Breaking -- **ribir**: `App::new_window` not accept window size as the second parameter. (#pr, @M-Adoo) +- **ribir**: `App::new_window` not accept window size as the second parameter. (#565, @M-Adoo) +- **ribir**: The window creation APIs have been updated to use asynchronous methods, improving compatibility with browsers. (#565, @M-Adoo) ## [0.3.0-alpha.4] - 2024-04-17 diff --git a/Cargo.toml b/Cargo.toml index b3381f466..91178df39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ priority-queue = "1.3.2" phf = "0.11.2" web-sys = { version = "0.3.69", features = ["HtmlCollection"] } web-time = "1.1.0" +wasm-bindgen-futures = "0.4.42" getrandom = { version = "0.2.12", features = ["js"] } [workspace.metadata.release] diff --git a/core/src/context/app_ctx.rs b/core/src/context/app_ctx.rs index 785e5a37b..42b72dd36 100644 --- a/core/src/context/app_ctx.rs +++ b/core/src/context/app_ctx.rs @@ -7,11 +7,7 @@ use std::{ }; pub use futures::task::SpawnError; -use futures::{ - executor::{block_on, LocalPool}, - task::LocalSpawnExt, - Future, -}; +use futures::{executor::LocalPool, task::LocalSpawnExt, Future}; use pin_project_lite::pin_project; use ribir_text::{font_db::FontDB, shaper::TextShaper, TextReorder, TypographyStore}; use rxrust::scheduler::NEW_TIMER_FN; @@ -294,7 +290,8 @@ impl AppCtx { } impl AppCtx { - pub fn wait_future(f: F) -> F::Output { block_on(f) } + #[cfg(not(target_family = "wasm"))] + pub fn wait_future(f: F) -> F::Output { futures::executor::block_on(f) } #[inline] pub fn spawn_local(future: Fut) -> Result<(), SpawnError> diff --git a/ribir/Cargo.toml b/ribir/Cargo.toml index f9676b68e..9fa56b7e5 100644 --- a/ribir/Cargo.toml +++ b/ribir/Cargo.toml @@ -27,6 +27,7 @@ arboard.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys.workspace = true +wasm-bindgen-futures.workspace = true [target.'cfg(target_os = "macos")'.dependencies] icrate = { workspace = true, features = [ diff --git a/ribir/src/app.rs b/ribir/src/app.rs index 60fda4c8b..83e99cbd7 100644 --- a/ribir/src/app.rs +++ b/ribir/src/app.rs @@ -1,4 +1,4 @@ -use std::convert::Infallible; +use std::{convert::Infallible, future::Future, pin::Pin}; use ribir_core::{prelude::*, timer::Timer, window::WindowId}; use winit::{ @@ -165,16 +165,16 @@ impl App { /// before it starts up. /// /// This will call `App::exec` when it's dropped. -pub struct AppRunGuard(std::rc::Rc); +pub struct AppRunGuard(Option>>>>); impl App { /// Start an application with the `root` widget, this will use the default /// theme to create an application and use the `root` widget to create a /// window, then run the application. #[track_caller] - pub fn run(root: impl WidgetBuilder) -> AppRunGuard { + pub fn run(root: impl WidgetBuilder + 'static) -> AppRunGuard { let wnd = Self::new_window(root); - AppRunGuard::new(wnd) + AppRunGuard::new(Box::pin(wnd)) } /// Get a event sender of the application event loop, you can use this to send @@ -184,26 +184,25 @@ impl App { /// Creating a new window using the `root` widget and the specified canvas. /// Note: This is exclusive to the web platform. #[cfg(target_family = "wasm")] - pub fn new_with_canvas( + pub async fn new_with_canvas( root: impl WidgetBuilder, canvas: web_sys::HtmlCanvasElement, ) -> std::rc::Rc { let app = unsafe { App::shared_mut() }; let event_loop = app.event_loop.as_ref().expect( " Event loop consumed. You can't create window after `App::exec` called in Web platform.", ); - let shell_wnd = WinitShellWnd::new_with_canvas(canvas, &event_loop); + let shell_wnd = WinitShellWnd::new_with_canvas(canvas, &event_loop).await; let wnd = AppCtx::new_window(Box::new(shell_wnd), root); wnd } /// create a new window with the `root` widget - #[track_caller] - pub fn new_window(root: impl WidgetBuilder) -> std::rc::Rc { + pub async fn new_window(root: impl WidgetBuilder) -> std::rc::Rc { let app = unsafe { App::shared_mut() }; let event_loop = app.event_loop.as_ref().expect( " Event loop consumed. You can't create window after `App::exec` called in Web platform.", ); - let shell_wnd = WinitShellWnd::new(event_loop); + let shell_wnd = WinitShellWnd::new(event_loop).await; let wnd = AppCtx::new_window(Box::new(shell_wnd), root); #[cfg(not(target_family = "wasm"))] @@ -297,10 +296,10 @@ impl App { } impl AppRunGuard { - fn new(wnd: std::rc::Rc) -> Self { + fn new(wnd: Pin>>>) -> Self { static ONCE: std::sync::Once = std::sync::Once::new(); assert!(!ONCE.is_completed(), "App::run can only be called once."); - Self(wnd) + Self(Some(wnd)) } /// Set the application theme, this will apply to whole application. @@ -312,15 +311,33 @@ impl AppRunGuard { } /// Config the current window with the `f` function. - pub fn on_window(&mut self, f: impl FnOnce(&Window)) -> &mut Self { - f(&self.0); + pub fn on_window(&mut self, f: impl FnOnce(&Window) + 'static) -> &mut Self { + let wnd = self.0.take().unwrap(); + self.0 = Some(Box::pin(async move { + let wnd = wnd.await; + f(&wnd); + wnd + })); self } } impl Drop for AppRunGuard { - fn drop(&mut self) { App::exec() } + fn drop(&mut self) { + let wnd = self.0.take().unwrap(); + #[cfg(target_family = "wasm")] + wasm_bindgen_futures::spawn_local(async move { + let _ = wnd.await; + App::exec(); + }); + #[cfg(not(target_family = "wasm"))] + { + AppCtx::wait_future(wnd); + App::exec(); + } + } } + impl EventSender { pub fn send(&self, e: AppEvent) { if let Err(err) = self.0.send_event(e) { diff --git a/ribir/src/winit_shell_wnd.rs b/ribir/src/winit_shell_wnd.rs index f569c55b6..91a8ebe84 100644 --- a/ribir/src/winit_shell_wnd.rs +++ b/ribir/src/winit_shell_wnd.rs @@ -179,7 +179,7 @@ pub(crate) fn new_id(id: winit::window::WindowId) -> WindowId { impl WinitShellWnd { #[cfg(target_family = "wasm")] - pub(crate) fn new_with_canvas( + pub(crate) async fn new_with_canvas( canvas: web_sys::HtmlCanvasElement, window_target: &EventLoopWindowTarget, ) -> Self { use winit::platform::web::WindowBuilderExtWebSys; @@ -188,11 +188,11 @@ impl WinitShellWnd { .build(window_target) .unwrap(); - Self::inner_wnd(wnd) + Self::inner_wnd(wnd).await } #[cfg(target_family = "wasm")] - pub(crate) fn new(window_target: &EventLoopWindowTarget) -> Self { + pub(crate) async fn new(window_target: &EventLoopWindowTarget) -> Self { const RIBIR_CANVAS: &str = "ribir_canvas"; const RIBIR_CANVAS_USED: &str = "ribir_canvas_used"; @@ -225,23 +225,22 @@ impl WinitShellWnd { let canvas = canvas.expect("No unused 'ribir_canvas' class element found."); - return Self::new_with_canvas(canvas, window_target); + return Self::new_with_canvas(canvas, window_target).await; } #[cfg(not(target_family = "wasm"))] - pub(crate) fn new(window_target: &EventLoopWindowTarget) -> Self { + pub(crate) async fn new(window_target: &EventLoopWindowTarget) -> Self { let wnd = winit::window::WindowBuilder::new() .build(window_target) .unwrap(); - Self::inner_wnd(wnd) + Self::inner_wnd(wnd).await } - fn inner_wnd(winit_wnd: winit::window::Window) -> Self { + async fn inner_wnd(winit_wnd: winit::window::Window) -> Self { let ptr = &winit_wnd as *const winit::window::Window; // Safety: a reference to winit_wnd is valid as long as the WinitShellWnd is // alive. - let backend = Backend::new(unsafe { &*ptr }); - let backend = AppCtx::wait_future(backend); + let backend = Backend::new(unsafe { &*ptr }).await; WinitShellWnd { backend, winit_wnd, cursor: CursorIcon::Default } } }