Skip to content

Commit

Permalink
refactor(ribir): 💡 the window creation APIs have been updated to use …
Browse files Browse the repository at this point in the history
…async
  • Loading branch information
M-Adoo committed Apr 26, 2024
1 parent 5dc9c0f commit 92bfc45
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 31 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
9 changes: 3 additions & 6 deletions core/src/context/app_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -294,7 +290,8 @@ impl AppCtx {
}

impl AppCtx {
pub fn wait_future<F: Future>(f: F) -> F::Output { block_on(f) }
#[cfg(not(target_family = "wasm"))]
pub fn wait_future<F: Future>(f: F) -> F::Output { futures::executor::block_on(f) }

#[inline]
pub fn spawn_local<Fut>(future: Fut) -> Result<(), SpawnError>
Expand Down
1 change: 1 addition & 0 deletions ribir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
45 changes: 31 additions & 14 deletions ribir/src/app.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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<Window>);
pub struct AppRunGuard(Option<Pin<Box<dyn Future<Output = std::rc::Rc<Window>>>>>);

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
Expand All @@ -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<Window> {
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<Window> {
pub async fn new_window(root: impl WidgetBuilder) -> std::rc::Rc<Window> {
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"))]
Expand Down Expand Up @@ -297,10 +296,10 @@ impl App {
}

impl AppRunGuard {
fn new(wnd: std::rc::Rc<Window>) -> Self {
fn new(wnd: Pin<Box<dyn Future<Output = std::rc::Rc<Window>>>>) -> 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.
Expand All @@ -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) {
Expand Down
17 changes: 8 additions & 9 deletions ribir/src/winit_shell_wnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(
pub(crate) async fn new_with_canvas<T>(
canvas: web_sys::HtmlCanvasElement, window_target: &EventLoopWindowTarget<T>,
) -> Self {
use winit::platform::web::WindowBuilderExtWebSys;
Expand All @@ -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<T>(window_target: &EventLoopWindowTarget<T>) -> Self {
pub(crate) async fn new<T>(window_target: &EventLoopWindowTarget<T>) -> Self {
const RIBIR_CANVAS: &str = "ribir_canvas";
const RIBIR_CANVAS_USED: &str = "ribir_canvas_used";

Expand Down Expand Up @@ -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<T>(window_target: &EventLoopWindowTarget<T>) -> Self {
pub(crate) async fn new<T>(window_target: &EventLoopWindowTarget<T>) -> 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 }
}
}

0 comments on commit 92bfc45

Please sign in to comment.