Skip to content

Commit

Permalink
Implements WindowEvent::CloseRequested for Slint (#1200)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Dec 24, 2024
1 parent d2d4684 commit b7f7ceb
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 88 deletions.
7 changes: 4 additions & 3 deletions gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ async fn run_launcher(
profiles.select(row, &win);

// Run the window.
win.exec().await.map_err(ProgramError::RunMainWindow)?;
win.show().map_err(ProgramError::ShowMainWindow)?;
win.wait().await;

// Update selected profile.
let profile = win.get_selected_profile();
Expand Down Expand Up @@ -384,8 +385,8 @@ enum ProgramError {
#[error("couldn't initialize graphics engine")]
InitGraphics(#[source] GraphicsError),

#[error("failed to run main window")]
RunMainWindow(#[source] slint::PlatformError),
#[error("couldn't show main window")]
ShowMainWindow(#[source] slint::PlatformError),

#[error("couldn't create VMM screen")]
CreateScreen(#[source] Box<dyn Error>),
Expand Down
2 changes: 0 additions & 2 deletions gui/src/rt/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use super::event::WindowEvent;
use super::task::TaskList;
use super::{Event, Hook, RuntimeWindow};
use std::cell::Cell;
Expand All @@ -16,7 +15,6 @@ pub struct Context<'a> {
pub tasks: &'a mut TaskList,
pub hooks: Option<&'a mut Vec<Box<dyn Hook>>>,
pub windows: &'a mut HashMap<WindowId, Weak<dyn RuntimeWindow>>,
pub on_close: &'a mut WindowEvent<()>,
}

impl<'a> Context<'a> {
Expand Down
53 changes: 0 additions & 53 deletions gui/src/rt/event.rs

This file was deleted.

24 changes: 7 additions & 17 deletions gui/src/rt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pub use self::hook::*;
pub use self::signal::*;
pub use self::window::*;

use self::context::Context;
use self::event::WindowEvent;
use self::task::TaskList;
use std::cell::Cell;
use std::collections::HashMap;
Expand All @@ -17,8 +17,8 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy}
use winit::window::{Window, WindowAttributes, WindowId};

mod context;
mod event;
mod hook;
mod signal;
mod task;
mod window;

Expand Down Expand Up @@ -53,7 +53,6 @@ pub fn run<T: 'static>(main: impl Future<Output = T> + 'static) -> Result<T, Run
main,
hooks: Vec::new(),
windows: HashMap::default(),
on_close: WindowEvent::default(),
exit,
};

Expand Down Expand Up @@ -96,12 +95,6 @@ pub fn create_window<T: RuntimeWindow + 'static>(
})
}

/// # Panics
/// If called from the other thread than main thread.
pub fn on_close(win: WindowId) -> impl Future<Output = ()> {
Context::with(move |cx| cx.on_close.wait(win))
}

/// # Panics
/// If called from outside `main` task that passed to [`run()`].
pub fn set_hook(hook: impl Hook + 'static) {
Expand All @@ -115,7 +108,6 @@ struct Runtime<T> {
main: u64,
hooks: Vec<Box<dyn Hook>>,
windows: HashMap<WindowId, Weak<dyn RuntimeWindow>>,
on_close: WindowEvent<()>,
exit: Rc<Cell<Option<Result<T, RuntimeError>>>>,
}

Expand All @@ -142,11 +134,11 @@ impl<T> Runtime<T> {
None
},
windows: &mut self.windows,
on_close: &mut self.on_close,
};

// Poll the task.
let r = cx.run(|| {
// TODO: Use RawWaker so we don't need to clone Arc here.
let waker = std::task::Waker::from(task.waker().clone());
let mut cx = std::task::Context::from_waker(&waker);

Expand Down Expand Up @@ -174,7 +166,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
tasks: &mut self.tasks,
hooks: None,
windows: &mut self.windows,
on_close: &mut self.on_close,
};

if let Err(e) = cx.run(|| {
Expand Down Expand Up @@ -216,7 +207,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
tasks: &mut self.tasks,
hooks: None,
windows: &mut self.windows,
on_close: &mut self.on_close,
};

if let Err(e) = cx.run(|| {
Expand Down Expand Up @@ -246,8 +236,7 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
dispatch!(w => { w.on_resized(v).map_err(RuntimeError::Resized) })
}
WindowEvent::CloseRequested => {
self.on_close.raise(id, ());
Ok(())
dispatch!(w => { w.on_close_requested().map_err(RuntimeError::CloseRequested) })
}
WindowEvent::Destroyed => {
// Run hook.
Expand Down Expand Up @@ -306,7 +295,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
tasks: &mut self.tasks,
hooks: None,
windows: &mut self.windows,
on_close: &mut self.on_close,
};

if let Err(e) = cx.run(|| {
Expand All @@ -329,7 +317,6 @@ impl<T> ApplicationHandler<Event> for Runtime<T> {
tasks: &mut self.tasks,
hooks: None,
windows: &mut self.windows,
on_close: &mut self.on_close,
};

if let Err(e) = cx.run(|| {
Expand Down Expand Up @@ -397,6 +384,9 @@ pub enum RuntimeError {
#[error("couldn't handle window resized")]
Resized(#[source] Box<dyn Error + Send + Sync>),

#[error("couldn't handle window close requested")]
CloseRequested(#[source] Box<dyn Error + Send + Sync>),

#[error("couldn't handle window destroyed")]
Destroyed(#[source] Box<dyn Error + Send + Sync>),

Expand Down
83 changes: 83 additions & 0 deletions gui/src/rt/signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::cell::{OnceCell, RefCell};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};

/// Contains a value that will be available in the future.
///
/// Once the value is set it will always available. There is no way to unset the value.
///
/// This type requires async executor that has a single waker per [`Future`].
#[derive(Default)]
pub struct Signal<T> {
value: OnceCell<T>,
wakers: RefCell<HashMap<*const (), Waker>>,
}

impl<T> Signal<T> {
pub fn set(&self, value: T) -> Result<(), T> {
self.value.set(value)?;

for (_, w) in self.wakers.borrow_mut().drain() {
w.wake();
}

Ok(())
}

pub fn wait(&self) -> WaitForSignal<T> {
WaitForSignal {
signal: self,
waker: None,
}
}
}

/// Implementation of [`Future`] to get the value from [`Signal`].
pub struct WaitForSignal<'a, T> {
signal: &'a Signal<T>,
waker: Option<Waker>,
}

impl<'a, T> Drop for WaitForSignal<'a, T> {
fn drop(&mut self) {
// We need a full Waker instead of its data so we don't accidentally remove a wrong waker if
// it already been freed somehow.
let w = match self.waker.take() {
Some(v) => v,
None => return,
};

// The waker may already removed from the list by Signal::set() when we are here.
self.signal.wakers.borrow_mut().remove(&w.data());
}
}

impl<'a, T> Future for WaitForSignal<'a, T> {
type Output = &'a T;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Check if value available.
if let Some(v) = self.signal.value.get() {
return Poll::Ready(v);
}

// Store waker. We requires async executor that has a single waker per future so we don't
// need to store the latest waker.
if self.waker.is_none() {
let w = cx.waker().clone();

assert!(self
.signal
.wakers
.borrow_mut()
.insert(w.data(), w.clone())
.is_none());

self.waker = Some(w);
}

Poll::Pending
}
}
1 change: 1 addition & 0 deletions gui/src/rt/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use winit::event::{DeviceId, ElementState, InnerSizeWriter, MouseButton};
/// The event loop will exit immediately if any method return an error.
pub trait RuntimeWindow {
fn on_resized(&self, new: PhysicalSize<u32>) -> Result<(), Box<dyn Error + Send + Sync>>;
fn on_close_requested(&self) -> Result<(), Box<dyn Error + Send + Sync>>;
fn on_focused(&self, gained: bool) -> Result<(), Box<dyn Error + Send + Sync>>;
fn on_cursor_moved(
&self,
Expand Down
2 changes: 1 addition & 1 deletion gui/src/ui/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod window;
/// The following are caveats of this back-end:
///
/// - [`slint::run_event_loop()`] and its related functions is not supported.
/// - [`slint::Window::show()`] and [`slint::Window::hide()`] can be called only once.
/// - [`slint::Window::show()`] can be called only once per window.
/// - [`slint::Window::hide()`] will not hide the window on Wayland. You need to drop it instead.
pub struct SlintBackend {}

Expand Down
18 changes: 14 additions & 4 deletions gui/src/ui/backend/window.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::rt::RuntimeWindow;
use crate::rt::{RuntimeWindow, Signal};
use i_slint_core::window::WindowAdapterInternal;
use i_slint_core::InternalToken;
use i_slint_renderer_skia::SkiaRenderer;
Expand All @@ -17,6 +17,7 @@ pub struct Window {
slint: slint::Window,
renderer: SkiaRenderer,
visible: Cell<Option<bool>>, // Wayland does not support this so we need to emulate it.
hidden: Signal<()>,
pointer: Cell<LogicalPosition>,
title: Cell<SharedString>,
minimum_size: Cell<Option<winit::dpi::PhysicalSize<u32>>>,
Expand All @@ -35,6 +36,7 @@ impl Window {
slint,
renderer,
visible: Cell::new(None),
hidden: Signal::default(),
pointer: Cell::default(),
title: Cell::default(),
minimum_size: Cell::default(),
Expand All @@ -46,6 +48,10 @@ impl Window {
pub fn id(&self) -> WindowId {
self.winit.id()
}

pub fn hidden(&self) -> &Signal<()> {
&self.hidden
}
}

impl RuntimeWindow for Window {
Expand All @@ -61,6 +67,11 @@ impl RuntimeWindow for Window {
Ok(())
}

fn on_close_requested(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
self.slint.dispatch_event(WindowEvent::CloseRequested);
Ok(())
}

fn on_focused(&self, gained: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
self.slint
.dispatch_event(WindowEvent::WindowActiveChanged(gained));
Expand Down Expand Up @@ -155,11 +166,10 @@ impl WindowAdapter for Window {

self.winit.set_visible(true);
self.visible.set(Some(true));
} else {
assert_eq!(self.visible.get(), Some(true));

} else if self.visible.get().is_some_and(|v| v) {
self.winit.set_visible(false);
self.visible.set(Some(false));
self.hidden.set(()).unwrap();
}

Ok(())
Expand Down
13 changes: 5 additions & 8 deletions gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ where
move || win.unwrap().hide().unwrap()
});

win.exec().await.unwrap();
win.show().unwrap();
win.wait().await;
}

/// Provides methods for [`ComponentHandle`] to work with our async runtime.
pub trait RuntimeExt: ComponentHandle {
async fn exec(&self) -> Result<(), slint::PlatformError>;
async fn wait(&self);
}

impl<T: ComponentHandle> RuntimeExt for T {
async fn exec(&self) -> Result<(), slint::PlatformError> {
async fn wait(&self) {
let win = WindowInner::from_pub(self.window()).window_adapter();
let win = win
.internal(InternalToken)
Expand All @@ -40,11 +41,7 @@ impl<T: ComponentHandle> RuntimeExt for T {
.downcast_ref::<Window>()
.unwrap();

self.show()?;
crate::rt::on_close(win.id()).await;
self.hide()?;

Ok(())
win.hidden().wait().await;
}
}

Expand Down

0 comments on commit b7f7ceb

Please sign in to comment.