Introduce Window::try_dispatch_event (#7313)

That is the same as Window::dispatch_event, but it reports an error
instead of panicking
This commit is contained in:
Olivier Goffart 2025-01-09 19:39:38 +01:00 committed by GitHub
parent 19bfe926d2
commit cfbcf0b1d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 137 additions and 102 deletions

View file

@ -222,7 +222,7 @@ loop {
if let Some(event) = check_for_touch_event(/*...*/) {
// convert the event from the driver into a `slint::platform::WindowEvent`
// and pass it to the window.
window.dispatch_event(event);
window.try_dispatch_event(event).unwrap();
}
// ... maybe some more application logic ...

View file

@ -154,11 +154,11 @@ impl slint::platform::Platform for EspBackend {
let is_pointer_release_event =
matches!(event, WindowEvent::PointerReleased { .. });
window.dispatch_event(event);
window.try_dispatch_event(event)?;
// removes hover state on widgets
if is_pointer_release_event {
window.dispatch_event(WindowEvent::PointerExited);
window.try_dispatch_event(WindowEvent::PointerExited)?;
}
}
}

View file

@ -283,11 +283,11 @@ impl<
let is_pointer_release_event =
matches!(event, WindowEvent::PointerReleased { .. });
window.dispatch_event(event);
window.try_dispatch_event(event)?;
// removes hover state on widgets
if is_pointer_release_event {
window.dispatch_event(WindowEvent::PointerExited);
window.try_dispatch_event(WindowEvent::PointerExited)?;
}
// Don't go to sleep after a touch event that forces a redraw
continue;

View file

@ -281,11 +281,11 @@ impl<
let is_pointer_release_event =
matches!(event, WindowEvent::PointerReleased { .. });
window.dispatch_event(event);
window.try_dispatch_event(event)?;
// removes hover state on widgets
if is_pointer_release_event {
window.dispatch_event(WindowEvent::PointerExited);
window.try_dispatch_event(WindowEvent::PointerExited)?;
}
// Don't go to sleep after a touch event that forces a redraw
continue;

View file

@ -373,11 +373,11 @@ impl slint::platform::Platform for StmBackend {
let is_pointer_release_event =
matches!(event, slint::platform::WindowEvent::PointerReleased { .. });
window.dispatch_event(event);
window.try_dispatch_event(event)?;
// removes hover state on widgets
if is_pointer_release_event {
window.dispatch_event(slint::platform::WindowEvent::PointerExited);
window.try_dispatch_event(slint::platform::WindowEvent::PointerExited)?;
}
}
}

View file

@ -308,8 +308,8 @@ impl slint::platform::Platform for Platform {
while let Some(key) = get_key_press() {
// EFI does not distinguish between pressed and released events.
let text = SharedString::from(key);
self.window.dispatch_event(WindowEvent::KeyPressed { text: text.clone() });
self.window.dispatch_event(WindowEvent::KeyReleased { text });
self.window.try_dispatch_event(WindowEvent::KeyPressed { text: text.clone() })?;
self.window.try_dispatch_event(WindowEvent::KeyReleased { text })?;
}
// mouse handle until no input
while let Some(mut mouse) =
@ -341,10 +341,11 @@ impl slint::platform::Platform for Platform {
mouse.relative_movement[1] = (info.resolution().1) as i32;
}
self.window.dispatch_event(WindowEvent::PointerMoved { position });
self.window.dispatch_event(WindowEvent::PointerExited {});
self.window.dispatch_event(WindowEvent::PointerPressed { position, button });
self.window.dispatch_event(WindowEvent::PointerReleased { position, button });
self.window.try_dispatch_event(WindowEvent::PointerMoved { position })?;
self.window.try_dispatch_event(WindowEvent::PointerExited {})?;
self.window.try_dispatch_event(WindowEvent::PointerPressed { position, button })?;
self.window
.try_dispatch_event(WindowEvent::PointerReleased { position, button })?;
is_mouse_move = true;
}

View file

@ -69,7 +69,7 @@ impl WindowAdapter for AndroidWindowAdapter {
fn update_window_properties(&self, properties: WindowProperties<'_>) {
let f = properties.is_fullscreen();
if self.fullscreen.replace(f) != f {
self.resize();
self.resize().unwrap();
}
}
@ -202,9 +202,7 @@ impl AndroidWindowAdapter {
}
}
match event {
PollEvent::Main(MainEvent::InputAvailable) => {
self.process_inputs().map_err(|e| PlatformError::Other(e.to_string()))?
}
PollEvent::Main(MainEvent::InputAvailable) => self.process_inputs()?,
PollEvent::Main(MainEvent::InitWindow { .. }) => {
if let Some(w) = self.app.native_window() {
let size = PhysicalSize { width: w.width() as u32, height: w.height() as u32 };
@ -214,7 +212,7 @@ impl AndroidWindowAdapter {
if (scale_factor - self.window.scale_factor()).abs() > f32::EPSILON {
self.window
.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
.try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
}
self.renderer.set_window_handle(
@ -223,7 +221,7 @@ impl AndroidWindowAdapter {
size,
None,
)?;
self.resize();
self.resize()?;
// Fixes a problem for old Android versions: the soft input always prompt out on startup.
#[cfg(feature = "native-activity")]
@ -234,26 +232,27 @@ impl AndroidWindowAdapter {
}
PollEvent::Main(
MainEvent::WindowResized { .. } | MainEvent::ContentRectChanged { .. },
) => self.resize(),
) => self.resize()?,
PollEvent::Main(MainEvent::RedrawNeeded { .. }) => {
self.pending_redraw.set(false);
self.do_render()?;
}
PollEvent::Main(MainEvent::GainedFocus) => {
self.window.dispatch_event(WindowEvent::WindowActiveChanged(true));
self.window.try_dispatch_event(WindowEvent::WindowActiveChanged(true))?;
}
PollEvent::Main(MainEvent::LostFocus) => {
self.window.dispatch_event(WindowEvent::WindowActiveChanged(true));
self.window.try_dispatch_event(WindowEvent::WindowActiveChanged(true))?;
}
PollEvent::Main(MainEvent::ConfigChanged { .. }) => {
let scale_factor =
self.app.config().density().map(|dpi| dpi as f32 / 160.0).unwrap_or(1.0);
if (scale_factor - self.window.scale_factor()).abs() > f32::EPSILON {
self.window.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
self.window.dispatch_event(WindowEvent::Resized {
self.window
.try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
self.window.try_dispatch_event(WindowEvent::Resized {
size: self.size().to_logical(scale_factor),
});
})?;
}
}
PollEvent::Main(MainEvent::Destroy) => {
@ -264,21 +263,22 @@ impl AndroidWindowAdapter {
Ok(ControlFlow::Continue(()))
}
fn process_inputs(&self) -> Result<(), android_activity::error::AppError> {
let mut iter = self.app.input_events_iter()?;
fn process_inputs(&self) -> Result<(), PlatformError> {
let mut iter =
self.app.input_events_iter().map_err(|e| PlatformError::Other(e.to_string()))?;
loop {
let mut result = Ok(());
let read_input = iter.next(|event| match event {
InputEvent::KeyEvent(key_event) => match map_key_event(key_event) {
Some(ev) => {
self.window.dispatch_event(ev);
result = self.window.try_dispatch_event(ev);
InputStatus::Handled
}
None => InputStatus::Unhandled,
},
InputEvent::MotionEvent(motion_event) => match motion_event.action() {
MotionAction::ButtonPress => {
self.window.dispatch_event(WindowEvent::PointerPressed {
result = self.window.try_dispatch_event(WindowEvent::PointerPressed {
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: button_for_event(motion_event, &self.last_pressed_state),
@ -298,14 +298,14 @@ impl AndroidWindowAdapter {
long_press_timeout,
);
self.long_press.replace(Some(LongPressDetection { position, _timer }));
self.window.dispatch_event(WindowEvent::PointerPressed {
result = self.window.try_dispatch_event(WindowEvent::PointerPressed {
position,
button: PointerEventButton::Left,
});
InputStatus::Handled
}
MotionAction::ButtonRelease => {
self.window.dispatch_event(WindowEvent::PointerReleased {
result = self.window.try_dispatch_event(WindowEvent::PointerReleased {
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: button_for_event(motion_event, &self.last_pressed_state),
@ -314,13 +314,18 @@ impl AndroidWindowAdapter {
}
MotionAction::Up => {
self.long_press.take();
self.window.dispatch_event(WindowEvent::PointerReleased {
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: PointerEventButton::Left,
});
// Also send exit to avoid remaining hover state
self.window.dispatch_event(WindowEvent::PointerExited);
result = self
.window
.try_dispatch_event(WindowEvent::PointerReleased {
position: position_for_event(motion_event, self.offset.get())
.to_logical(self.window.scale_factor()),
button: PointerEventButton::Left,
})
.and_then(|()| {
// Also send exit to avoid remaining hover state
self.window.try_dispatch_event(WindowEvent::PointerExited)
});
InputStatus::Handled
}
MotionAction::Move | MotionAction::HoverMove => {
@ -333,12 +338,13 @@ impl AndroidWindowAdapter {
}) {
*lp = None;
}
self.window.dispatch_event(WindowEvent::PointerMoved { position });
result =
self.window.try_dispatch_event(WindowEvent::PointerMoved { position });
InputStatus::Handled
}
MotionAction::Cancel | MotionAction::Outside => {
self.long_press.take();
self.window.dispatch_event(WindowEvent::PointerExited);
result = self.window.try_dispatch_event(WindowEvent::PointerExited);
InputStatus::Handled
}
MotionAction::Scroll => todo!(),
@ -382,14 +388,16 @@ impl AndroidWindowAdapter {
_ => InputStatus::Unhandled,
});
result?;
if !read_input {
return Ok(());
}
}
}
fn resize(&self) {
let Some(win) = self.app.native_window() else { return };
fn resize(&self) -> Result<(), PlatformError> {
let Some(win) = self.app.native_window() else { return Ok(()) };
let (offset, size) = if self.fullscreen.get() {
(
Default::default(),
@ -399,10 +407,11 @@ impl AndroidWindowAdapter {
self.java_helper.get_view_rect().unwrap_or_else(|e| print_jni_error(&self.app, e))
};
self.window.dispatch_event(WindowEvent::Resized {
self.window.try_dispatch_event(WindowEvent::Resized {
size: size.to_logical(self.window.scale_factor()),
});
})?;
self.offset.set(offset);
Ok(())
}
pub fn do_render(&self) -> Result<(), PlatformError> {

View file

@ -193,7 +193,7 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> {
.clamp(0., screen_size.height);
self.mouse_pos.set(Some(mouse_pos));
let event = WindowEvent::PointerMoved { position: mouse_pos };
window.dispatch_event(event);
window.try_dispatch_event(event).map_err(Self::Error::other)?;
}
input::event::PointerEvent::MotionAbsolute(abs_motion_event) => {
let mouse_pos = LogicalPosition {
@ -205,7 +205,7 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> {
};
self.mouse_pos.set(Some(mouse_pos));
let event = WindowEvent::PointerMoved { position: mouse_pos };
window.dispatch_event(event);
window.try_dispatch_event(event).map_err(Self::Error::other)?;
}
input::event::PointerEvent::Button(button_event) => {
// https://github.com/torvalds/linux/blob/0dd2a6fb1e34d6dcb96806bc6b111388ad324722/include/uapi/linux/input-event-codes.h#L355
@ -226,7 +226,7 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> {
WindowEvent::PointerReleased { position: mouse_pos, button }
}
};
window.dispatch_event(event);
window.try_dispatch_event(event).map_err(Self::Error::other)?;
}
input::event::PointerEvent::ScrollWheel(_) => todo!(),
input::event::PointerEvent::ScrollFinger(_) => todo!(),
@ -259,7 +259,7 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> {
}
_ => None,
} {
window.dispatch_event(event);
window.try_dispatch_event(event).map_err(Self::Error::other)?;
}
}
input::Event::Keyboard(input::event::KeyboardEvent::Key(key_event)) => {
@ -314,7 +314,7 @@ impl<'a> calloop::EventSource for LibInputHandler<'a> {
KeyState::Pressed => WindowEvent::KeyPressed { text },
KeyState::Released => WindowEvent::KeyReleased { text },
};
window.dispatch_event(event);
window.try_dispatch_event(event).map_err(Self::Error::other)?;
}
}
_ => {}

View file

@ -57,7 +57,7 @@ impl WindowAdapter for FullscreenWindowAdapter {
if let Some(scale_factor) =
std::env::var("SLINT_SCALE_FACTOR").ok().and_then(|sf| sf.parse().ok())
{
self.window.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
self.window.try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
}
}
Ok(())

View file

@ -340,16 +340,22 @@ impl winit::application::ApplicationHandler<SlintUserEvent> for EventLoopState {
window.window_state_event();
}
WindowEvent::CloseRequested => {
window.window().dispatch_event(corelib::platform::WindowEvent::CloseRequested);
self.loop_error = window
.window()
.try_dispatch_event(corelib::platform::WindowEvent::CloseRequested)
.err();
}
WindowEvent::Focused(have_focus) => {
let have_focus = have_focus || window.input_method_focused();
// We don't render popups as separate windows yet, so treat
// focus to be the same as being active.
if have_focus != runtime_window.active() {
window.window().dispatch_event(
corelib::platform::WindowEvent::WindowActiveChanged(have_focus),
);
self.loop_error = window
.window()
.try_dispatch_event(corelib::platform::WindowEvent::WindowActiveChanged(
have_focus,
))
.err();
}
}
@ -384,28 +390,31 @@ impl winit::application::ApplicationHandler<SlintUserEvent> for EventLoopState {
}
let text = i_slint_common::for_each_special_keys!(winit_key_to_char);
window.window().dispatch_event(match event.state {
winit::event::ElementState::Pressed if event.repeat => {
corelib::platform::WindowEvent::KeyPressRepeated { text }
}
winit::event::ElementState::Pressed => {
if is_synthetic {
// Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
// Don't forward these keys other than modifiers to the app
use winit::keyboard::{Key::Named, NamedKey as N};
if !matches!(
key_code,
Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
) {
return;
}
self.loop_error = window
.window()
.try_dispatch_event(match event.state {
winit::event::ElementState::Pressed if event.repeat => {
corelib::platform::WindowEvent::KeyPressRepeated { text }
}
corelib::platform::WindowEvent::KeyPressed { text }
}
winit::event::ElementState::Released => {
corelib::platform::WindowEvent::KeyReleased { text }
}
});
winit::event::ElementState::Pressed => {
if is_synthetic {
// Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
// Don't forward these keys other than modifiers to the app
use winit::keyboard::{Key::Named, NamedKey as N};
if !matches!(
key_code,
Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
) {
return;
}
}
corelib::platform::WindowEvent::KeyPressed { text }
}
winit::event::ElementState::Released => {
corelib::platform::WindowEvent::KeyReleased { text }
}
})
.err();
}
WindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
let event = KeyEvent {
@ -521,11 +530,12 @@ impl winit::application::ApplicationHandler<SlintUserEvent> for EventLoopState {
}
WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: _ } => {
if std::env::var("SLINT_SCALE_FACTOR").is_err() {
window.window().dispatch_event(
corelib::platform::WindowEvent::ScaleFactorChanged {
self.loop_error = window
.window()
.try_dispatch_event(corelib::platform::WindowEvent::ScaleFactorChanged {
scale_factor: scale_factor as f32,
},
);
})
.err();
// TODO: send a resize event or try to keep the logical size the same.
//window.resize_event(inner_size_writer.???)?;
}

View file

@ -347,7 +347,7 @@ impl WinitWindowAdapter {
.and_then(|x| x.parse::<f32>().ok())
.filter(|f| *f > 0.)
.unwrap_or_else(|| winit_window.scale_factor() as f32);
self_rc.window().dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
self_rc.window().try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
Ok(self_rc)
}
@ -553,9 +553,9 @@ impl WinitWindowAdapter {
let physical_size = physical_size_to_slint(&size);
self.size.set(physical_size);
let scale_factor = WindowInner::from_pub(self.window()).scale_factor();
self.window().dispatch_event(WindowEvent::Resized {
self.window().try_dispatch_event(WindowEvent::Resized {
size: physical_size.to_logical(scale_factor),
});
})?;
// Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser)
// with the width/height attribute (the size of the viewport/GL surface)
@ -925,9 +925,11 @@ impl WindowAdapter for WinitWindowAdapter {
}
if must_resize {
self.window().dispatch_event(WindowEvent::Resized {
size: i_slint_core::api::LogicalSize::new(width, height),
});
self.window()
.try_dispatch_event(WindowEvent::Resized {
size: i_slint_core::api::LogicalSize::new(width, height),
})
.unwrap();
}
let m = properties.is_fullscreen();

View file

@ -363,7 +363,7 @@ fn generate_shared_globals(
let apply_constant_scale_factor = if !compiler_config.const_scale_factor.approx_eq(&1.0) {
let factor = compiler_config.const_scale_factor as f32;
Some(
quote!(adapter.window().dispatch_event(slint::platform::WindowEvent::ScaleFactorChanged{ scale_factor: #factor });),
quote!(adapter.window().try_dispatch_event(slint::platform::WindowEvent::ScaleFactorChanged{ scale_factor: #factor })?;),
)
} else {
None

View file

@ -554,8 +554,24 @@ impl Window {
///
/// Any position fields in the event must be in the logical pixel coordinate system relative to
/// the top left corner of the window.
// TODO: Return a Result<(), PlatformError>
///
/// This function panics if there is an error processing the event.
/// Use [`Self::try_dispatch_event()`] to handle the error.
#[track_caller]
pub fn dispatch_event(&self, event: crate::platform::WindowEvent) {
self.try_dispatch_event(event).unwrap()
}
/// Dispatch a window event to the scene.
///
/// Use this when you're implementing your own backend and want to forward user input events.
///
/// Any position fields in the event must be in the logical pixel coordinate system relative to
/// the top left corner of the window.
pub fn try_dispatch_event(
&self,
event: crate::platform::WindowEvent,
) -> Result<(), PlatformError> {
match event {
crate::platform::WindowEvent::PointerPressed { position, button } => {
self.0.process_mouse_input(MouseEvent::Pressed {
@ -615,19 +631,16 @@ impl Window {
}
crate::platform::WindowEvent::Resized { size } => {
self.0.set_window_item_geometry(size.to_euclid());
self.0
.window_adapter()
.renderer()
.resize(size.to_physical(self.scale_factor()))
.unwrap()
self.0.window_adapter().renderer().resize(size.to_physical(self.scale_factor()))?;
}
crate::platform::WindowEvent::CloseRequested => {
if self.0.request_close() {
self.hide().unwrap();
self.hide()?;
}
}
crate::platform::WindowEvent::WindowActiveChanged(bool) => self.0.set_active(bool),
}
};
Ok(())
}
/// Returns true if there is an animation currently active on any property in the Window; false otherwise.

View file

@ -280,7 +280,7 @@ pub use crate::input::PointerEventButton;
/// A event that describes user input or windowing system events.
///
/// Slint backends typically receive events from the windowing system, translate them to this
/// enum and deliver them to the scene of items via [`slint::Window::dispatch_event()`](`crate::api::Window::dispatch_event()`).
/// enum and deliver them to the scene of items via [`slint::Window::try_dispatch_event()`](`crate::api::Window::try_dispatch_event()`).
///
/// The pointer variants describe events originating from an input device such as a mouse
/// or a contact point on a touch-enabled surface.

View file

@ -49,20 +49,20 @@ fn previous_focus_item(item: ItemRc) -> ItemRc {
///
/// - When receiving messages from the windowing system about state changes, such as the window being resized,
/// the user requested the window to be closed, input being received, etc. you need to create a
/// [`crate::platform::WindowEvent`](enum.WindowEvent.html) and send it to Slint via [`create::Window::dispatch_event()`](../struct.Window.html#method.dispatch_event).
/// [`WindowEvent`](crate::platform::WindowEvent) and send it to Slint via [`Window::try_dispatch_event()`].
///
/// - Slint sends requests to change visibility, position, size, etc. via functions such as [`Self::set_visible`],
/// [`Self::set_size`], [`Self::set_position`], or [`Self::update_window_properties()`]. Re-implement these functions
/// and delegate the requests to the windowing system.
///
/// If the implementation of this bi-directional message passing protocol is incomplete, the user may
/// experience unexpected behavior, or the intention of the developer calling functions on the [`crate::Window`](struct.Window.html)
/// experience unexpected behavior, or the intention of the developer calling functions on the [`Window`]
/// API may not be fulfilled.
///
/// Your implementation must hold a renderer, such as [`crate::software_renderer::SoftwareRenderer`].
/// Your implementation must hold a renderer, such as [`SoftwareRenderer`](crate::software_renderer::SoftwareRenderer).
/// In the [`Self::renderer()`] function, you must return a reference to it.
///
/// It is also required to hold a [`crate::Window`](struct.Window.html) and return a reference to it in your
/// It is also required to hold a [`Window`] and return a reference to it in your
/// implementation of [`Self::window()`].
///
/// See also [`MinimalSoftwareWindow`](crate::software_renderer::MinimalSoftwareWindow)