From 48b7a0480f541d5ceaef0f62fe1e0678ee035858 Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Sat, 14 Dec 2024 00:54:35 +0700 Subject: [PATCH] Implements RuntimeExt::exec for UI (#1179) --- Cargo.lock | 1 + gui/Cargo.toml | 1 + gui/src/rt/context.rs | 38 +++++++++++++++++--------- gui/src/rt/event.rs | 53 ++++++++++++++++++++++++++++++++++++ gui/src/rt/mod.rs | 40 +++++++++++++++++++-------- gui/src/ui/backend/mod.rs | 3 +- gui/src/ui/backend/window.rs | 24 +++++++++++++++- gui/src/ui/mod.rs | 17 +++++++++++- 8 files changed, 150 insertions(+), 27 deletions(-) create mode 100644 gui/src/rt/event.rs diff --git a/Cargo.lock b/Cargo.lock index e582278cc..7b24b43c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1608,6 +1608,7 @@ dependencies = [ "gdbstub", "gdbstub_arch", "humansize", + "i-slint-core", "i-slint-renderer-skia", "libc", "metal 0.29.0", diff --git a/gui/Cargo.toml b/gui/Cargo.toml index d63d12617..74a77b4dd 100644 --- a/gui/Cargo.toml +++ b/gui/Cargo.toml @@ -16,6 +16,7 @@ futures = "0.3.31" gdbstub = "0.7.3" gdbstub_arch = "0.3.1" humansize = "2.1.3" +i-slint-core = "=1.8.0" i-slint-renderer-skia = { version = "=1.8.0", features = ["wayland", "x11"] } libc = "0.2.164" num_enum = "0.7.3" diff --git a/gui/src/rt/context.rs b/gui/src/rt/context.rs index c64fde3f1..a9f476122 100644 --- a/gui/src/rt/context.rs +++ b/gui/src/rt/context.rs @@ -1,37 +1,49 @@ +use super::event::WindowEvent; use std::cell::Cell; +use std::future::Future; use std::mem::transmute; -use std::ptr::null; +use std::ptr::null_mut; use winit::event_loop::ActiveEventLoop; +use winit::window::WindowId; /// Execution context of the runtime. pub struct RuntimeContext<'a> { - el: &'a ActiveEventLoop, + pub(super) el: &'a ActiveEventLoop, + pub(super) on_close: &'a mut WindowEvent<()>, } impl<'a> RuntimeContext<'a> { - pub(super) fn new(el: &'a ActiveEventLoop) -> Self { - Self { el } - } - /// # Panics - /// If called from the other thread than main thread. - pub fn with(f: impl FnOnce(&Self) -> R) -> R { - let cx = CONTEXT.get(); + /// - If called from the other thread than main thread. + /// - If this call has been nested. + pub fn with(f: impl FnOnce(&mut RuntimeContext) -> R) -> R { + // Take context to achieve exclusive access. + let cx = CONTEXT.replace(null_mut()); + assert!(!cx.is_null()); - unsafe { f(&*cx) } + + // Execute action then put context back. + let r = unsafe { f(&mut *cx) }; + + CONTEXT.set(cx); + r } pub fn event_loop(&self) -> &ActiveEventLoop { self.el } - pub(super) fn run(&self, f: impl FnOnce()) { + pub fn on_close(&mut self, win: WindowId) -> impl Future { + self.on_close.wait(win) + } + + pub(super) fn run(&mut self, f: impl FnOnce()) { assert!(CONTEXT.replace(unsafe { transmute(self) }).is_null()); f(); - CONTEXT.set(null()); + CONTEXT.set(null_mut()); } } thread_local! { - static CONTEXT: Cell<*const RuntimeContext<'static>> = Cell::new(null()); + static CONTEXT: Cell<*mut RuntimeContext<'static>> = Cell::new(null_mut()); } diff --git a/gui/src/rt/event.rs b/gui/src/rt/event.rs new file mode 100644 index 000000000..24bb88829 --- /dev/null +++ b/gui/src/rt/event.rs @@ -0,0 +1,53 @@ +use futures::channel::oneshot::{Receiver, Sender}; +use futures::FutureExt; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use winit::window::WindowId; + +/// List of one-shot channel waiting for a window event. +#[derive(Default)] +pub struct WindowEvent(Vec<(WindowId, Sender)>); + +impl WindowEvent { + pub fn wait(&mut self, win: WindowId) -> impl Future { + let (tx, rx) = futures::channel::oneshot::channel(); + + self.0.push((win, tx)); + + Wait(rx) + } + + pub fn raise(&mut self, win: WindowId, data: T) { + // TODO: https://github.com/rust-lang/rust/issues/43244 + let mut i = 0; + + while i < self.0.len() { + let s = if self.0[i].0 == win { + self.0.remove(i).1 + } else { + i += 1; + continue; + }; + + s.send(data.clone()).ok(); + } + } +} + +/// Result of [`WindowEvent::wait()`]. +struct Wait(Receiver); + +impl Future for Wait { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let r = match self.0.poll_unpin(cx) { + Poll::Ready(v) => v, + Poll::Pending => return Poll::Pending, + }; + + // The future only driven when the event loop is running so this should never panic. + Poll::Ready(r.unwrap()) + } +} diff --git a/gui/src/rt/mod.rs b/gui/src/rt/mod.rs index f9296ce60..95251877a 100644 --- a/gui/src/rt/mod.rs +++ b/gui/src/rt/mod.rs @@ -1,24 +1,26 @@ pub use self::context::*; +use self::event::WindowEvent; use futures::executor::LocalPool; use futures::task::LocalSpawnExt; use std::future::Future; use thiserror::Error; use winit::application::ApplicationHandler; use winit::error::EventLoopError; -use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::window::WindowId; mod context; +mod event; pub fn block_on(main: impl Future + 'static) -> Result<(), RuntimeError> { // Setup winit event loop. let mut el = EventLoop::::with_user_event(); let el = el.build().map_err(RuntimeError::CreateEventLoop)?; - let exe = LocalPool::new(); + let executor = LocalPool::new(); - exe.spawner() + executor + .spawner() .spawn_local(async move { main.await; RuntimeContext::with(|cx| cx.event_loop().exit()); @@ -26,27 +28,43 @@ pub fn block_on(main: impl Future + 'static) -> Result<(), RuntimeE .unwrap(); // Run event loop. - el.run_app(&mut AsyncExecutor(exe)) - .map_err(RuntimeError::RunEventLoop) + let mut rt = Runtime { + executor, + on_close: WindowEvent::default(), + }; + + el.run_app(&mut rt).map_err(RuntimeError::RunEventLoop) } /// Implementation of [`ApplicationHandler`] to drive [`Future`]. -struct AsyncExecutor(LocalPool); +struct Runtime { + executor: LocalPool, + on_close: WindowEvent<()>, +} -impl ApplicationHandler for AsyncExecutor { +impl ApplicationHandler for Runtime { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let cx = RuntimeContext::new(event_loop); + let mut cx = RuntimeContext { + el: event_loop, + on_close: &mut self.on_close, + }; - cx.run(|| self.0.run_until_stalled()); + cx.run(|| self.executor.run_until_stalled()); } fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, - event: WindowEvent, + event: winit::event::WindowEvent, ) { - todo!() + use winit::event::WindowEvent; + + match event { + WindowEvent::CloseRequested => self.on_close.raise(window_id, ()), + WindowEvent::RedrawRequested => todo!(), + _ => {} + } } } diff --git a/gui/src/ui/backend/mod.rs b/gui/src/ui/backend/mod.rs index 87402c092..91a3b1ecb 100644 --- a/gui/src/ui/backend/mod.rs +++ b/gui/src/ui/backend/mod.rs @@ -1,4 +1,5 @@ -use self::window::Window; +pub(super) use self::window::Window; + use crate::rt::RuntimeContext; use i_slint_renderer_skia::SkiaRenderer; use slint::platform::WindowAdapter; diff --git a/gui/src/ui/backend/window.rs b/gui/src/ui/backend/window.rs index ab6529f31..a5a2d79e4 100644 --- a/gui/src/ui/backend/window.rs +++ b/gui/src/ui/backend/window.rs @@ -1,7 +1,11 @@ +use i_slint_core::window::WindowAdapterInternal; +use i_slint_core::InternalToken; use i_slint_renderer_skia::SkiaRenderer; use slint::platform::{Renderer, WindowAdapter}; -use slint::PhysicalSize; +use slint::{PhysicalSize, PlatformError}; +use std::any::Any; use std::rc::Rc; +use winit::window::WindowId; /// Implementation of [`WindowAdapter`]. pub struct Window { @@ -22,6 +26,10 @@ impl Window { renderer, } } + + pub fn id(&self) -> WindowId { + self.winit.id() + } } impl WindowAdapter for Window { @@ -29,6 +37,10 @@ impl WindowAdapter for Window { &self.slint } + fn set_visible(&self, visible: bool) -> Result<(), PlatformError> { + todo!() + } + fn size(&self) -> PhysicalSize { let s = self.winit.inner_size(); @@ -38,4 +50,14 @@ impl WindowAdapter for Window { fn renderer(&self) -> &dyn Renderer { &self.renderer } + + fn internal(&self, _: InternalToken) -> Option<&dyn WindowAdapterInternal> { + Some(self) + } +} + +impl WindowAdapterInternal for Window { + fn as_any(&self) -> &dyn Any { + self + } } diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs index 149515afc..0bd68f66e 100644 --- a/gui/src/ui/mod.rs +++ b/gui/src/ui/mod.rs @@ -1,6 +1,9 @@ pub use self::backend::*; pub use self::profile::*; +use crate::rt::RuntimeContext; +use i_slint_core::window::WindowInner; +use i_slint_core::InternalToken; use slint::ComponentHandle; mod backend; @@ -13,7 +16,19 @@ pub trait RuntimeExt: ComponentHandle { impl RuntimeExt for T { async fn exec(&self) -> Result<(), slint::PlatformError> { - todo!() + let win = WindowInner::from_pub(self.window()).window_adapter(); + let win = win + .internal(InternalToken) + .unwrap() + .as_any() + .downcast_ref::() + .unwrap(); + + self.show()?; + RuntimeContext::with(|cx| cx.on_close(win.id())).await; + self.hide()?; + + Ok(()) } }