mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 06:41:14 +00:00

Reduce the dependency of the GLRenderer to a new trait that exposes the EventLoopTarget and EventLoopProxy. Those are provided by either an winit::event_loop::EventLoop or, once the loop is started using the self-consuming run(), by the event loop target provided to the run_fn. This way renderers can be created from either within run or before. The initial event loop instance is kept in TLS. When starting the loop, it's taken out and instead the event loop target is placed into a scoped tls variable.
428 lines
18 KiB
Rust
428 lines
18 KiB
Rust
/* LICENSE BEGIN
|
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
|
|
|
SPDX-License-Identifier: GPL-3.0-only
|
|
This file is also available under commercial licensing terms.
|
|
Please contact info@sixtyfps.io for more information.
|
|
LICENSE END */
|
|
#![warn(missing_docs)]
|
|
/*!
|
|
This module contains the event loop implementation using winit, as well as the
|
|
[PlatformWindow] trait used by the generated code and the run-time to change
|
|
aspects of windows on the screen.
|
|
*/
|
|
use sixtyfps_corelib as corelib;
|
|
|
|
use corelib::graphics::Point;
|
|
use corelib::input::{KeyEvent, MouseEventType};
|
|
use corelib::window::*;
|
|
use std::cell::RefCell;
|
|
use std::convert::TryInto;
|
|
use std::rc::{Rc, Weak};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use winit::platform::run_return::EventLoopExtRunReturn;
|
|
|
|
struct NotRunningEventLoop {
|
|
instance: winit::event_loop::EventLoop<CustomEvent>,
|
|
event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
|
|
}
|
|
|
|
impl NotRunningEventLoop {
|
|
fn new() -> Self {
|
|
let instance = winit::event_loop::EventLoop::with_user_event();
|
|
let event_loop_proxy = instance.create_proxy();
|
|
Self { instance, event_loop_proxy }
|
|
}
|
|
}
|
|
|
|
struct RunningEventLoop<'a> {
|
|
event_loop_target: &'a winit::event_loop::EventLoopWindowTarget<CustomEvent>,
|
|
event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
|
|
}
|
|
|
|
pub(crate) trait EventLoopInterface {
|
|
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent>;
|
|
fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent>;
|
|
}
|
|
|
|
impl EventLoopInterface for NotRunningEventLoop {
|
|
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent> {
|
|
&*self.instance
|
|
}
|
|
|
|
fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent> {
|
|
&self.event_loop_proxy
|
|
}
|
|
}
|
|
|
|
impl<'a> EventLoopInterface for RunningEventLoop<'a> {
|
|
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent> {
|
|
self.event_loop_target
|
|
}
|
|
|
|
fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent> {
|
|
&self.event_loop_proxy
|
|
}
|
|
}
|
|
|
|
thread_local! {
|
|
static ALL_WINDOWS: RefCell<std::collections::HashMap<winit::window::WindowId, Weak<crate::graphics_window::GraphicsWindow>>> = RefCell::new(std::collections::HashMap::new());
|
|
static MAYBE_LOOP_INSTANCE: RefCell<Option<NotRunningEventLoop>> = RefCell::new(Some(NotRunningEventLoop::new()));
|
|
}
|
|
|
|
scoped_tls_hkt::scoped_thread_local!(static CURRENT_WINDOW_TARGET : for<'a> &'a RunningEventLoop<'a>);
|
|
|
|
pub(crate) fn with_window_target<T>(callback: impl FnOnce(&dyn EventLoopInterface) -> T) -> T {
|
|
if CURRENT_WINDOW_TARGET.is_set() {
|
|
CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target))
|
|
} else {
|
|
MAYBE_LOOP_INSTANCE.with(|loop_instance| {
|
|
if loop_instance.borrow().is_none() {
|
|
*loop_instance.borrow_mut() = Some(NotRunningEventLoop::new());
|
|
}
|
|
callback(loop_instance.borrow().as_ref().unwrap())
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn register_window(
|
|
id: winit::window::WindowId,
|
|
window: Rc<crate::graphics_window::GraphicsWindow>,
|
|
) {
|
|
ALL_WINDOWS.with(|windows| {
|
|
windows.borrow_mut().insert(id, Rc::downgrade(&window));
|
|
})
|
|
}
|
|
|
|
pub fn unregister_window(id: winit::window::WindowId) {
|
|
ALL_WINDOWS.with(|windows| {
|
|
windows.borrow_mut().remove(&id);
|
|
})
|
|
}
|
|
|
|
/// This enum captures run-time specific events that can be dispatched to the event loop in
|
|
/// addition to the winit events.
|
|
#[derive(Debug)]
|
|
pub enum CustomEvent {
|
|
/// Request for the event loop to wake up and poll. This is used on the web for example to
|
|
/// request an animation frame.
|
|
#[cfg(target_arch = "wasm32")]
|
|
WakeUpAndPoll,
|
|
}
|
|
|
|
/// Runs the event loop and renders the items in the provided `component` in its
|
|
/// own window.
|
|
#[allow(unused_mut)] // mut need changes for wasm
|
|
pub fn run() {
|
|
use winit::event::Event;
|
|
use winit::event_loop::{ControlFlow, EventLoopWindowTarget};
|
|
|
|
let not_running_loop_instance = MAYBE_LOOP_INSTANCE.with(|loop_instance| {
|
|
loop_instance.borrow_mut().take().unwrap_or_else(|| NotRunningEventLoop::new())
|
|
});
|
|
|
|
let event_loop_proxy = not_running_loop_instance.event_loop_proxy;
|
|
let mut winit_loop = not_running_loop_instance.instance;
|
|
|
|
// last seen cursor position, (physical coordinate)
|
|
let mut cursor_pos = Point::default();
|
|
let mut pressed = false;
|
|
let mut run_fn = move |event: Event<CustomEvent>,
|
|
event_loop_target: &EventLoopWindowTarget<CustomEvent>,
|
|
control_flow: &mut ControlFlow| {
|
|
let running_instance =
|
|
RunningEventLoop { event_loop_target, event_loop_proxy: event_loop_proxy.clone() };
|
|
CURRENT_WINDOW_TARGET.set(&running_instance, || {
|
|
*control_flow = ControlFlow::Wait;
|
|
|
|
match event {
|
|
winit::event::Event::WindowEvent {
|
|
event: winit::event::WindowEvent::CloseRequested,
|
|
..
|
|
} => *control_flow = winit::event_loop::ControlFlow::Exit,
|
|
winit::event::Event::RedrawRequested(id) => {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window.draw();
|
|
}
|
|
});
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
event: winit::event::WindowEvent::Resized(size),
|
|
window_id,
|
|
} => {
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window.refresh_window_scale_factor();
|
|
window.set_geometry(size.width as _, size.height as _);
|
|
}
|
|
});
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
event:
|
|
winit::event::WindowEvent::ScaleFactorChanged {
|
|
scale_factor,
|
|
new_inner_size: size,
|
|
},
|
|
window_id,
|
|
} => {
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window.set_geometry(size.width as f32, size.height as f32);
|
|
window.set_scale_factor(scale_factor as f32);
|
|
}
|
|
});
|
|
}
|
|
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::MouseInput { state, .. },
|
|
..
|
|
} => {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
let what = match state {
|
|
winit::event::ElementState::Pressed => {
|
|
pressed = true;
|
|
MouseEventType::MousePressed
|
|
}
|
|
winit::event::ElementState::Released => {
|
|
pressed = false;
|
|
MouseEventType::MouseReleased
|
|
}
|
|
};
|
|
window.clone().process_mouse_input(cursor_pos, what);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::Touch(touch),
|
|
..
|
|
} => {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
let cursor_pos =
|
|
euclid::point2(touch.location.x as _, touch.location.y as _);
|
|
let what = match touch.phase {
|
|
winit::event::TouchPhase::Started => {
|
|
pressed = true;
|
|
MouseEventType::MousePressed
|
|
}
|
|
winit::event::TouchPhase::Ended
|
|
| winit::event::TouchPhase::Cancelled => {
|
|
pressed = false;
|
|
MouseEventType::MouseReleased
|
|
}
|
|
winit::event::TouchPhase::Moved => MouseEventType::MouseMoved,
|
|
};
|
|
window.clone().process_mouse_input(cursor_pos, what);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
window_id,
|
|
event: winit::event::WindowEvent::CursorMoved { position, .. },
|
|
..
|
|
} => {
|
|
cursor_pos = euclid::point2(position.x as _, position.y as _);
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window
|
|
.clone()
|
|
.process_mouse_input(cursor_pos, MouseEventType::MouseMoved);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
// On the html canvas, we don't get the mouse move or release event when outside the canvas. So we have no choice but canceling the event
|
|
#[cfg(target_arch = "wasm32")]
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::CursorLeft { .. },
|
|
..
|
|
} => {
|
|
if pressed {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
pressed = false;
|
|
window
|
|
.clone()
|
|
.process_mouse_input(cursor_pos, MouseEventType::MouseExit);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::KeyboardInput { ref input, .. },
|
|
} => {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
if let Some(ref key_event) =
|
|
(input, window.current_keyboard_modifiers()).try_into().ok()
|
|
{
|
|
window
|
|
.self_weak
|
|
.get()
|
|
.unwrap()
|
|
.upgrade()
|
|
.unwrap()
|
|
.process_key_input(key_event);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::ReceivedCharacter(ch),
|
|
} => {
|
|
if !ch.is_control() {
|
|
corelib::animations::update_animations();
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
let modifiers = window.current_keyboard_modifiers();
|
|
|
|
if !modifiers.control() && !modifiers.alt() && !modifiers.logo() {
|
|
let key_event = KeyEvent::CharacterInput {
|
|
unicode_scalar: ch.into(),
|
|
modifiers,
|
|
};
|
|
window
|
|
.self_weak
|
|
.get()
|
|
.unwrap()
|
|
.upgrade()
|
|
.unwrap()
|
|
.process_key_input(&key_event);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::ModifiersChanged(state),
|
|
} => {
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window.set_current_keyboard_modifiers(state.into());
|
|
}
|
|
});
|
|
}
|
|
|
|
winit::event::Event::WindowEvent {
|
|
ref window_id,
|
|
event: winit::event::WindowEvent::Focused(have_focus),
|
|
} => {
|
|
ALL_WINDOWS.with(|windows| {
|
|
if let Some(Some(window)) =
|
|
windows.borrow().get(&window_id).map(|weakref| weakref.upgrade())
|
|
{
|
|
window
|
|
.self_weak
|
|
.get()
|
|
.unwrap()
|
|
.upgrade()
|
|
.unwrap()
|
|
.set_focus(have_focus);
|
|
// FIXME: remove this, it should be based on actual changes rather than this
|
|
window.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
|
|
if *control_flow != winit::event_loop::ControlFlow::Exit {
|
|
corelib::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
|
|
if !driver.has_active_animations() {
|
|
return;
|
|
}
|
|
*control_flow = ControlFlow::Poll;
|
|
ALL_WINDOWS.with(|windows| {
|
|
windows.borrow().values().for_each(|window| {
|
|
if let Some(window) = window.upgrade() {
|
|
window.request_redraw();
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
if corelib::timers::TimerList::maybe_activate_timers() {
|
|
ALL_WINDOWS.with(|windows| {
|
|
windows.borrow().values().for_each(|window| {
|
|
if let Some(window) = window.upgrade() {
|
|
window.request_redraw();
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
if *control_flow == winit::event_loop::ControlFlow::Wait {
|
|
if let Some(next_timer) = corelib::timers::TimerList::next_timeout() {
|
|
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_timer);
|
|
}
|
|
}
|
|
})
|
|
};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
winit_loop.run_return(run_fn);
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
// Since wasm does not have a run_return function that takes a non-static closure,
|
|
// we use this hack to work that around
|
|
scoped_tls_hkt::scoped_thread_local!(static mut RUN_FN_TLS: for <'a> &'a mut dyn FnMut(
|
|
Event<'_, CustomEvent>,
|
|
&EventLoopWindowTarget<CustomEvent>,
|
|
&mut ControlFlow,
|
|
));
|
|
RUN_FN_TLS.set(&mut run_fn, move || {
|
|
winit_loop.run(|e, t, cf| RUN_FN_TLS.with(|run_fn| run_fn(e, t, cf)))
|
|
});
|
|
}
|
|
}
|