Move Web backend to winit-web
This commit is contained in:
parent
2d4b9938f0
commit
e542a78deb
50 changed files with 259 additions and 273 deletions
103
winit-web/src/event_loop/mod.rs
Normal file
103
winit-web/src/event_loop/mod.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::error::{EventLoopError, NotSupportedError};
|
||||
use winit_core::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
|
||||
use crate::{
|
||||
backend, HasMonitorPermissionFuture, MonitorPermissionFuture, PollStrategy, WaitUntilStrategy,
|
||||
};
|
||||
|
||||
mod proxy;
|
||||
pub(crate) mod runner;
|
||||
mod state;
|
||||
mod window_target;
|
||||
|
||||
pub(crate) use window_target::ActiveEventLoop;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoop {
|
||||
elw: ActiveEventLoop,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PlatformSpecificEventLoopAttributes {}
|
||||
|
||||
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
|
||||
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
|
||||
// For better cross-platformness.
|
||||
return Err(EventLoopError::RecreationAttempt);
|
||||
}
|
||||
|
||||
Ok(EventLoop { elw: ActiveEventLoop::new() })
|
||||
}
|
||||
|
||||
fn allow_event_loop_recreation() {
|
||||
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
|
||||
let app = Box::new(app);
|
||||
|
||||
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
||||
// because this function will never return and all resources not cleaned up by the point we
|
||||
// `throw` will leak, making this actually `'static`.
|
||||
let app = unsafe {
|
||||
std::mem::transmute::<
|
||||
Box<dyn ApplicationHandler + '_>,
|
||||
Box<dyn ApplicationHandler + 'static>,
|
||||
>(app)
|
||||
};
|
||||
|
||||
self.elw.run(app, false);
|
||||
|
||||
// Throw an exception to break out of Rust execution and use unreachable to tell the
|
||||
// compiler this function won't return, giving it a return type of '!'
|
||||
backend::throw(
|
||||
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
|
||||
);
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
|
||||
self.elw.run(Box::new(app), true);
|
||||
}
|
||||
|
||||
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
|
||||
&self.elw
|
||||
}
|
||||
|
||||
pub fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.elw.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
pub fn poll_strategy(&self) -> PollStrategy {
|
||||
self.elw.poll_strategy()
|
||||
}
|
||||
|
||||
pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.elw.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
pub fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.elw.wait_until_strategy()
|
||||
}
|
||||
|
||||
pub fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
|
||||
self.elw.has_multiple_screens()
|
||||
}
|
||||
|
||||
pub fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
|
||||
MonitorPermissionFuture(self.elw.request_detailed_monitor_permission())
|
||||
}
|
||||
|
||||
pub fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture {
|
||||
HasMonitorPermissionFuture(
|
||||
self.elw.runner.monitor().has_detailed_monitor_permission_async(),
|
||||
)
|
||||
}
|
||||
}
|
||||
104
winit-web/src/event_loop/proxy.rs
Normal file
104
winit-web/src/event_loop/proxy.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use std::future;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
|
||||
use winit_core::event_loop::EventLoopProxyProvider;
|
||||
|
||||
use super::super::main_thread::MainThreadMarker;
|
||||
use crate::event_loop::runner::WeakShared;
|
||||
use crate::r#async::{AtomicWaker, Wrapper};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopProxy(Wrapper<WeakShared, Arc<State>, ()>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
awoken: AtomicBool,
|
||||
waker: AtomicWaker,
|
||||
closed: AtomicBool,
|
||||
}
|
||||
|
||||
impl EventLoopProxy {
|
||||
pub fn new(main_thread: MainThreadMarker, runner: WeakShared) -> Self {
|
||||
let state = Arc::new(State {
|
||||
awoken: AtomicBool::new(false),
|
||||
waker: AtomicWaker::new(),
|
||||
closed: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
Self(Wrapper::new(
|
||||
main_thread,
|
||||
runner,
|
||||
|runner, _| {
|
||||
let runner = runner.borrow();
|
||||
let runner = runner.as_ref().unwrap();
|
||||
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
runner.send_proxy_wake_up(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
move |runner| async move {
|
||||
while future::poll_fn(|cx| {
|
||||
if state.awoken.swap(false, Ordering::Relaxed) {
|
||||
Poll::Ready(true)
|
||||
} else {
|
||||
state.waker.register(cx.waker());
|
||||
|
||||
if state.awoken.swap(false, Ordering::Relaxed) {
|
||||
Poll::Ready(true)
|
||||
} else {
|
||||
if state.closed.load(Ordering::Relaxed) {
|
||||
return Poll::Ready(false);
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
{
|
||||
let runner = runner.borrow();
|
||||
let runner = runner.as_ref().unwrap();
|
||||
|
||||
if let Some(runner) = runner.upgrade() {
|
||||
runner.send_proxy_wake_up(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
state,
|
||||
|state, _| {
|
||||
state.awoken.store(true, Ordering::Relaxed);
|
||||
state.waker.wake();
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn take(&self) -> bool {
|
||||
debug_assert!(
|
||||
MainThreadMarker::new().is_some(),
|
||||
"this should only be called from the main thread"
|
||||
);
|
||||
|
||||
self.0.with_sender_data(|state| state.awoken.swap(false, Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventLoopProxy {
|
||||
fn drop(&mut self) {
|
||||
self.0.with_sender_data(|state| {
|
||||
state.closed.store(true, Ordering::Relaxed);
|
||||
state.waker.wake();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopProxyProvider for EventLoopProxy {
|
||||
fn wake_up(&self) {
|
||||
self.0.send(())
|
||||
}
|
||||
}
|
||||
888
winit-web/src/event_loop/runner.rs
Normal file
888
winit-web/src/event_loop/runner.rs
Normal file
|
|
@ -0,0 +1,888 @@
|
|||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, iter};
|
||||
|
||||
use dpi::PhysicalSize;
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Document, KeyboardEvent, Navigator, PageTransitionEvent, PointerEvent, WheelEvent};
|
||||
use web_time::{Duration, Instant};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::event::{
|
||||
DeviceEvent, DeviceId, ElementState, RawKeyEvent, StartCause, WindowEvent,
|
||||
};
|
||||
use winit_core::event_loop::{ControlFlow, DeviceEvents};
|
||||
use winit_core::window::WindowId;
|
||||
|
||||
use super::proxy::EventLoopProxy;
|
||||
use super::state::State;
|
||||
use crate::backend::{EventListenerHandle, SafeAreaHandle};
|
||||
use crate::event_loop::ActiveEventLoop;
|
||||
use crate::main_thread::MainThreadMarker;
|
||||
use crate::monitor::MonitorHandler;
|
||||
use crate::r#async::DispatchRunner;
|
||||
use crate::web_sys::event::mouse_button_to_id;
|
||||
use crate::window::Inner;
|
||||
use crate::{backend, event, EventLoop, PollStrategy, WaitUntilStrategy};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Shared(Rc<Execution>);
|
||||
|
||||
impl Clone for Shared {
|
||||
fn clone(&self) -> Self {
|
||||
Shared(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
|
||||
|
||||
struct Execution {
|
||||
main_thread: MainThreadMarker,
|
||||
event_loop_proxy: Arc<EventLoopProxy>,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
poll_strategy: Cell<PollStrategy>,
|
||||
wait_until_strategy: Cell<WaitUntilStrategy>,
|
||||
exit: Cell<bool>,
|
||||
runner: RefCell<RunnerEnum>,
|
||||
suspended: Cell<bool>,
|
||||
event_loop_recreation: Cell<bool>,
|
||||
events: RefCell<VecDeque<Event>>,
|
||||
id: Cell<usize>,
|
||||
window: web_sys::Window,
|
||||
navigator: Navigator,
|
||||
document: Document,
|
||||
#[allow(clippy::type_complexity)]
|
||||
all_canvases: RefCell<Vec<(WindowId, Weak<backend::Canvas>, DispatchRunner<Inner>)>>,
|
||||
redraw_pending: RefCell<HashSet<WindowId>>,
|
||||
destroy_pending: RefCell<VecDeque<WindowId>>,
|
||||
pub(crate) monitor: Rc<MonitorHandler>,
|
||||
safe_area: Rc<SafeAreaHandle>,
|
||||
page_transition_event_handle: RefCell<Option<backend::PageTransitionEventHandle>>,
|
||||
device_events: Cell<DeviceEvents>,
|
||||
on_mouse_move: OnEventHandle<PointerEvent>,
|
||||
on_wheel: OnEventHandle<WheelEvent>,
|
||||
on_mouse_press: OnEventHandle<PointerEvent>,
|
||||
on_mouse_release: OnEventHandle<PointerEvent>,
|
||||
on_key_press: OnEventHandle<KeyboardEvent>,
|
||||
on_key_release: OnEventHandle<KeyboardEvent>,
|
||||
on_visibility_change: OnEventHandle<web_sys::Event>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Execution {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Execution").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
enum RunnerEnum {
|
||||
/// The `EventLoop` is created but not being run.
|
||||
Pending,
|
||||
/// The `EventLoop` is running some async initialization and is waiting to be started.
|
||||
Initializing(Runner),
|
||||
/// The `EventLoop` is being run.
|
||||
Running(Runner),
|
||||
/// The `EventLoop` is exited after being started with `EventLoop::run_app`. Since
|
||||
/// `EventLoop::run_app` takes ownership of the `EventLoop`, we can be certain
|
||||
/// that this event loop will never be run again.
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
impl RunnerEnum {
|
||||
fn maybe_runner(&self) -> Option<&Runner> {
|
||||
match self {
|
||||
RunnerEnum::Running(runner) => Some(runner),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Runner {
|
||||
state: State,
|
||||
app: Box<dyn ApplicationHandler>,
|
||||
event_loop: ActiveEventLoop,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Runner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Runner")
|
||||
.field("state", &self.state)
|
||||
.field("app", &"<ApplicationHandler>")
|
||||
.field("event_loop", &self.event_loop)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn new(app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) -> Self {
|
||||
Runner { state: State::Init, app, event_loop }
|
||||
}
|
||||
|
||||
/// Returns the corresponding `StartCause` for the current `state`, or `None`
|
||||
/// when in `Exit` state.
|
||||
fn maybe_start_cause(&self) -> Option<StartCause> {
|
||||
Some(match self.state {
|
||||
State::Init => StartCause::Init,
|
||||
State::Poll { .. } => StartCause::Poll,
|
||||
State::Wait { start } => StartCause::WaitCancelled { start, requested_resume: None },
|
||||
State::WaitUntil { start, end, .. } => {
|
||||
StartCause::WaitCancelled { start, requested_resume: Some(end) }
|
||||
},
|
||||
State::Exit => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_single_event(&mut self, runner: &Shared, event: Event) {
|
||||
match event {
|
||||
Event::NewEvents(cause) => self.app.new_events(&self.event_loop, cause),
|
||||
Event::WindowEvent { window_id, event } => {
|
||||
self.app.window_event(&self.event_loop, window_id, event)
|
||||
},
|
||||
Event::ScaleChange { canvas, size, scale } => {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.handle_scale_change(
|
||||
runner,
|
||||
|window_id, event| {
|
||||
self.app.window_event(&self.event_loop, window_id, event);
|
||||
},
|
||||
size,
|
||||
scale,
|
||||
)
|
||||
}
|
||||
},
|
||||
Event::DeviceEvent { device_id, event } => {
|
||||
self.app.device_event(&self.event_loop, device_id, event)
|
||||
},
|
||||
Event::UserWakeUp => self.app.proxy_wake_up(&self.event_loop),
|
||||
Event::Suspended => self.app.suspended(&self.event_loop),
|
||||
Event::Resumed => self.app.resumed(&self.event_loop),
|
||||
Event::CreateSurfaces => self.app.can_create_surfaces(&self.event_loop),
|
||||
Event::AboutToWait => self.app.about_to_wait(&self.event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
pub fn new() -> Self {
|
||||
let main_thread = MainThreadMarker::new().expect("only callable from inside the `Window`");
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let window = web_sys::window().expect("only callable from inside the `Window`");
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let navigator = window.navigator();
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let document = window.document().expect("Failed to obtain document");
|
||||
|
||||
Shared(Rc::<Execution>::new_cyclic(|weak| {
|
||||
let proxy_spawner = EventLoopProxy::new(main_thread, WeakShared(weak.clone()));
|
||||
|
||||
let monitor = MonitorHandler::new(
|
||||
main_thread,
|
||||
window.clone(),
|
||||
&navigator,
|
||||
WeakShared(weak.clone()),
|
||||
);
|
||||
|
||||
let safe_area = SafeAreaHandle::new(&window, &document);
|
||||
|
||||
Execution {
|
||||
main_thread,
|
||||
event_loop_proxy: Arc::new(proxy_spawner),
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
poll_strategy: Cell::new(PollStrategy::default()),
|
||||
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
|
||||
exit: Cell::new(false),
|
||||
runner: RefCell::new(RunnerEnum::Pending),
|
||||
suspended: Cell::new(false),
|
||||
event_loop_recreation: Cell::new(false),
|
||||
events: RefCell::new(VecDeque::new()),
|
||||
window,
|
||||
navigator,
|
||||
document,
|
||||
id: Cell::new(0),
|
||||
all_canvases: RefCell::new(Vec::new()),
|
||||
redraw_pending: RefCell::new(HashSet::new()),
|
||||
destroy_pending: RefCell::new(VecDeque::new()),
|
||||
monitor: Rc::new(monitor),
|
||||
safe_area: Rc::new(safe_area),
|
||||
page_transition_event_handle: RefCell::new(None),
|
||||
device_events: Cell::default(),
|
||||
on_mouse_move: RefCell::new(None),
|
||||
on_wheel: RefCell::new(None),
|
||||
on_mouse_press: RefCell::new(None),
|
||||
on_mouse_release: RefCell::new(None),
|
||||
on_key_press: RefCell::new(None),
|
||||
on_key_release: RefCell::new(None),
|
||||
on_visibility_change: RefCell::new(None),
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn main_thread(&self) -> MainThreadMarker {
|
||||
self.0.main_thread
|
||||
}
|
||||
|
||||
pub fn window(&self) -> &web_sys::Window {
|
||||
&self.0.window
|
||||
}
|
||||
|
||||
pub fn navigator(&self) -> &Navigator {
|
||||
&self.0.navigator
|
||||
}
|
||||
|
||||
pub fn document(&self) -> &Document {
|
||||
&self.0.document
|
||||
}
|
||||
|
||||
pub fn add_canvas(
|
||||
&self,
|
||||
id: WindowId,
|
||||
canvas: Weak<backend::Canvas>,
|
||||
runner: DispatchRunner<Inner>,
|
||||
) {
|
||||
self.0.all_canvases.borrow_mut().push((id, canvas, runner));
|
||||
}
|
||||
|
||||
pub fn notify_destroy_window(&self, id: WindowId) {
|
||||
self.0.destroy_pending.borrow_mut().push_back(id);
|
||||
}
|
||||
|
||||
pub(crate) fn start(&self, app: Box<dyn ApplicationHandler>, event_loop: ActiveEventLoop) {
|
||||
let mut runner = self.0.runner.borrow_mut();
|
||||
assert!(matches!(*runner, RunnerEnum::Pending));
|
||||
if self.0.monitor.is_initializing() {
|
||||
*runner = RunnerEnum::Initializing(Runner::new(app, event_loop));
|
||||
} else {
|
||||
*runner = RunnerEnum::Running(Runner::new(app, event_loop));
|
||||
|
||||
drop(runner);
|
||||
|
||||
self.init();
|
||||
self.set_listener();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_delayed(&self) {
|
||||
let event_handler = match self.0.runner.replace(RunnerEnum::Pending) {
|
||||
RunnerEnum::Initializing(event_handler) => event_handler,
|
||||
// The event loop wasn't started yet.
|
||||
RunnerEnum::Pending => return,
|
||||
_ => unreachable!("event loop already started before waiting for initialization"),
|
||||
};
|
||||
*self.0.runner.borrow_mut() = RunnerEnum::Running(event_handler);
|
||||
|
||||
self.init();
|
||||
self.set_listener();
|
||||
}
|
||||
|
||||
// Set the event callback to use for the event loop runner
|
||||
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
||||
// over a RootActiveEventLoop reference
|
||||
fn set_listener(&self) {
|
||||
*self.0.page_transition_event_handle.borrow_mut() = Some(backend::on_page_transition(
|
||||
self.window().clone(),
|
||||
{
|
||||
let runner = self.clone();
|
||||
move |event: PageTransitionEvent| {
|
||||
if event.persisted() {
|
||||
runner.0.suspended.set(false);
|
||||
runner.send_event(Event::Resumed);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
let runner = self.clone();
|
||||
move |event: PageTransitionEvent| {
|
||||
runner.0.suspended.set(true);
|
||||
if event.persisted() {
|
||||
runner.send_event(Event::Suspended);
|
||||
} else {
|
||||
runner.handle_unload();
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
let runner = self.clone();
|
||||
let window = self.window().clone();
|
||||
let navigator = self.navigator().clone();
|
||||
*self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"pointermove",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
// chorded button event
|
||||
let device_id = event::mkdid(event.pointer_id());
|
||||
|
||||
if let Some(button) = backend::event::mouse_button(&event) {
|
||||
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
state,
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// pointer move event
|
||||
let mut delta = backend::event::MouseDelta::init(&navigator, &event);
|
||||
runner.send_events(backend::event::pointer_move_event(event).map(|event| {
|
||||
let delta = delta.delta(&event).to_physical(backend::scale_factor(&window));
|
||||
|
||||
Event::DeviceEvent {
|
||||
device_id,
|
||||
event: DeviceEvent::PointerMotion { delta: (delta.x, delta.y) },
|
||||
}
|
||||
}));
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
let window = self.window().clone();
|
||||
*self.0.on_wheel.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"wheel",
|
||||
Closure::new(move |event: WheelEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(delta) = backend::event::mouse_scroll_delta(&window, &event) {
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: None,
|
||||
event: DeviceEvent::MouseWheel { delta },
|
||||
});
|
||||
}
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_mouse_press.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"pointerdown",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: event::mkdid(event.pointer_id()),
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
state: ElementState::Pressed,
|
||||
},
|
||||
});
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_mouse_release.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"pointerup",
|
||||
Closure::new(move |event: PointerEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: event::mkdid(event.pointer_id()),
|
||||
event: DeviceEvent::Button {
|
||||
button: mouse_button_to_id(button).into(),
|
||||
state: ElementState::Released,
|
||||
},
|
||||
});
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_key_press.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"keydown",
|
||||
Closure::new(move |event: KeyboardEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: None,
|
||||
event: DeviceEvent::Key(RawKeyEvent {
|
||||
physical_key: backend::event::key_code(&event),
|
||||
state: ElementState::Pressed,
|
||||
}),
|
||||
});
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_key_release.borrow_mut() = Some(EventListenerHandle::new(
|
||||
self.window().clone(),
|
||||
"keyup",
|
||||
Closure::new(move |event: KeyboardEvent| {
|
||||
if !runner.device_events() {
|
||||
return;
|
||||
}
|
||||
|
||||
runner.send_event(Event::DeviceEvent {
|
||||
device_id: None,
|
||||
event: DeviceEvent::Key(RawKeyEvent {
|
||||
physical_key: backend::event::key_code(&event),
|
||||
state: ElementState::Released,
|
||||
}),
|
||||
});
|
||||
}),
|
||||
));
|
||||
let runner = self.clone();
|
||||
*self.0.on_visibility_change.borrow_mut() = Some(EventListenerHandle::new(
|
||||
// Safari <14 doesn't support the `visibilitychange` event on `Window`.
|
||||
self.document().clone(),
|
||||
"visibilitychange",
|
||||
Closure::new(move |_| {
|
||||
if !runner.0.suspended.get() {
|
||||
for (id, canvas, _) in &*runner.0.all_canvases.borrow() {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
let is_visible = backend::is_visible(runner.document());
|
||||
// only fire if:
|
||||
// - not visible and intersects
|
||||
// - not visible and we don't know if it intersects yet
|
||||
// - visible and intersects
|
||||
if let (false, Some(true) | None) | (true, Some(true)) =
|
||||
(is_visible, canvas.is_intersecting.get())
|
||||
{
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id: *id,
|
||||
event: WindowEvent::Occluded(!is_visible),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate a strictly increasing ID
|
||||
// This is used to differentiate windows when handling events
|
||||
pub fn generate_id(&self) -> usize {
|
||||
let id = self.0.id.get();
|
||||
self.0.id.set(id.checked_add(1).expect("exhausted `WindowId`"));
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self, id: WindowId) {
|
||||
self.0.redraw_pending.borrow_mut().insert(id);
|
||||
self.send_events([]);
|
||||
}
|
||||
|
||||
fn init(&self) {
|
||||
// NB: For consistency all platforms must call `can_create_surfaces` even though Web
|
||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||
self.run_until_cleared(
|
||||
[Event::NewEvents(StartCause::Init), Event::CreateSurfaces].into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue
|
||||
pub fn poll(&self) {
|
||||
let start_cause = Event::NewEvents(StartCause::Poll);
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Run the logic for waking from a WaitUntil, which involves clearing the queue
|
||||
// Generally there shouldn't be events built up when this is called
|
||||
pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) {
|
||||
let start_cause =
|
||||
Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume });
|
||||
self.run_until_cleared(iter::once(start_cause));
|
||||
}
|
||||
|
||||
// Add an event to the event loop runner, from the user or an event handler
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub(crate) fn send_event(&self, event: Event) {
|
||||
self.send_events(iter::once(event));
|
||||
}
|
||||
|
||||
// Add a user event to the event loop runner.
|
||||
//
|
||||
// This will schedule the event loop to wake up instead of waking it up immediately if its not
|
||||
// running.
|
||||
pub(crate) fn send_proxy_wake_up(&self, local: bool) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if local {
|
||||
// If the loop is not running and triggered locally, queue on next microtick.
|
||||
if let Ok(RunnerEnum::Running(_)) =
|
||||
self.0.runner.try_borrow().as_ref().map(Deref::deref)
|
||||
{
|
||||
self.window().queue_microtask(
|
||||
&Closure::once_into_js({
|
||||
let this = Rc::downgrade(&self.0);
|
||||
move || {
|
||||
if let Some(shared) = this.upgrade() {
|
||||
Shared(shared).send_event(Event::UserWakeUp)
|
||||
}
|
||||
}
|
||||
})
|
||||
.unchecked_into(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.send_event(Event::UserWakeUp);
|
||||
}
|
||||
|
||||
// Add a series of events to the event loop runner
|
||||
//
|
||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||
pub(crate) fn send_events(&self, events: impl IntoIterator<Item = Event>) {
|
||||
// If the event loop is closed, it should discard any new events
|
||||
if self.is_closed() {
|
||||
return;
|
||||
}
|
||||
// If we can run the event processing right now, or need to queue this and wait for later
|
||||
let mut process_immediately = true;
|
||||
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
|
||||
// If the runner is attached but not running, we always wake it up.
|
||||
Ok(RunnerEnum::Running(_)) => (),
|
||||
// The runner still hasn't been attached: queue this event and wait for it to be
|
||||
Ok(RunnerEnum::Pending | RunnerEnum::Initializing(_)) => {
|
||||
process_immediately = false;
|
||||
},
|
||||
// Some other code is mutating the runner, which most likely means
|
||||
// the event loop is running and busy. So we queue this event for
|
||||
// it to be processed later.
|
||||
Err(_) => {
|
||||
process_immediately = false;
|
||||
},
|
||||
// This is unreachable since `self.is_closed() == true`.
|
||||
Ok(RunnerEnum::Destroyed) => unreachable!(),
|
||||
}
|
||||
if !process_immediately {
|
||||
// Queue these events to look at later
|
||||
self.0.events.borrow_mut().extend(events);
|
||||
return;
|
||||
}
|
||||
// At this point, we know this is a fresh set of events
|
||||
// Now we determine why new events are incoming, and handle the events
|
||||
let start_cause = match (self.0.runner.borrow().maybe_runner())
|
||||
.unwrap_or_else(|| {
|
||||
unreachable!("The runner cannot process events when it is not attached")
|
||||
})
|
||||
.maybe_start_cause()
|
||||
{
|
||||
Some(c) => c,
|
||||
// If we're in the exit state, don't do event processing
|
||||
None => return,
|
||||
};
|
||||
// Take the start event, then the events provided to this function, and run an iteration of
|
||||
// the event loop
|
||||
let start_event = Event::NewEvents(start_cause);
|
||||
let events = iter::once(start_event).chain(events);
|
||||
self.run_until_cleared(events);
|
||||
}
|
||||
|
||||
// Process the destroy-pending windows. This should only be called from
|
||||
// `run_until_cleared`, somewhere between emitting `NewEvents` and `AboutToWait`.
|
||||
fn process_destroy_pending_windows(&self) {
|
||||
while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() {
|
||||
self.0.all_canvases.borrow_mut().retain(|&(item_id, ..)| item_id != id);
|
||||
self.handle_event(Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: winit_core::event::WindowEvent::Destroyed,
|
||||
});
|
||||
self.0.redraw_pending.borrow_mut().remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
// Given the set of new events, run the event loop until the main events and redraw events are
|
||||
// cleared
|
||||
//
|
||||
// This will also process any events that have been queued or that are queued during processing
|
||||
fn run_until_cleared(&self, events: impl Iterator<Item = Event>) {
|
||||
for event in events {
|
||||
self.handle_event(event);
|
||||
}
|
||||
self.process_destroy_pending_windows();
|
||||
|
||||
// Collect all of the redraw events to avoid double-locking the RefCell
|
||||
let redraw_events: Vec<WindowId> = self.0.redraw_pending.borrow_mut().drain().collect();
|
||||
for window_id in redraw_events {
|
||||
self.handle_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::RedrawRequested,
|
||||
});
|
||||
}
|
||||
|
||||
self.handle_event(Event::AboutToWait);
|
||||
|
||||
self.apply_control_flow();
|
||||
// If the event loop is closed, it has been closed this iteration and now the closing
|
||||
// event should be emitted
|
||||
if self.is_closed() {
|
||||
self.handle_loop_destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_unload(&self) {
|
||||
self.exit();
|
||||
self.apply_control_flow();
|
||||
// We don't call `handle_loop_destroyed` here because we don't need to
|
||||
// perform cleanup when the Web browser is going to destroy the page.
|
||||
//
|
||||
// We do want to run the application handler's `Drop` impl.
|
||||
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
||||
}
|
||||
|
||||
// handle_event takes in events and either queues them or applies a callback
|
||||
//
|
||||
// It should only ever be called from `run_until_cleared`.
|
||||
fn handle_event(&self, event: Event) {
|
||||
if self.is_closed() {
|
||||
self.exit();
|
||||
}
|
||||
match *self.0.runner.borrow_mut() {
|
||||
RunnerEnum::Running(ref mut runner) => {
|
||||
runner.handle_single_event(self, event);
|
||||
},
|
||||
// If an event is being handled without a runner somehow, add it to the event queue so
|
||||
// it will eventually be processed
|
||||
RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event),
|
||||
// If the Runner has been destroyed, there is nothing to do.
|
||||
RunnerEnum::Destroyed => return,
|
||||
// This function should never be called if we are still waiting for something.
|
||||
RunnerEnum::Initializing(_) => unreachable!(),
|
||||
}
|
||||
|
||||
let is_closed = self.exiting();
|
||||
|
||||
// Don't take events out of the queue if the loop is closed or the runner doesn't exist
|
||||
// If the runner doesn't exist and this method recurses, it will recurse infinitely
|
||||
if !is_closed && self.0.runner.borrow().maybe_runner().is_some() {
|
||||
// Pre-fetch window commands to avoid having to wait until the next event loop cycle
|
||||
// and potentially block other threads in the meantime.
|
||||
for (_, window, runner) in self.0.all_canvases.borrow().iter() {
|
||||
if let Some(window) = window.upgrade() {
|
||||
runner.run(self.main_thread());
|
||||
drop(window)
|
||||
}
|
||||
}
|
||||
|
||||
// Take an event out of the queue and handle it
|
||||
// Make sure not to let the borrow_mut live during the next handle_event
|
||||
let event = {
|
||||
let mut events = self.0.events.borrow_mut();
|
||||
|
||||
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
|
||||
events.extend(self.0.event_loop_proxy.take().then_some(Event::UserWakeUp));
|
||||
|
||||
events.pop_front()
|
||||
};
|
||||
if let Some(event) = event {
|
||||
self.handle_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the new ControlFlow that has been selected by the user
|
||||
// Start any necessary timeouts etc
|
||||
fn apply_control_flow(&self) {
|
||||
let new_state = if self.exiting() {
|
||||
State::Exit
|
||||
} else {
|
||||
match self.control_flow() {
|
||||
ControlFlow::Poll => {
|
||||
let cloned = self.clone();
|
||||
State::Poll {
|
||||
_request: backend::Schedule::new(
|
||||
self.poll_strategy(),
|
||||
self.window(),
|
||||
move || cloned.poll(),
|
||||
),
|
||||
}
|
||||
},
|
||||
ControlFlow::Wait => State::Wait { start: Instant::now() },
|
||||
ControlFlow::WaitUntil(end) => {
|
||||
let start = Instant::now();
|
||||
|
||||
let delay = if end <= start { Duration::from_millis(0) } else { end - start };
|
||||
|
||||
let cloned = self.clone();
|
||||
|
||||
State::WaitUntil {
|
||||
start,
|
||||
end,
|
||||
_timeout: backend::Schedule::new_with_duration(
|
||||
self.wait_until_strategy(),
|
||||
self.window(),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if let RunnerEnum::Running(ref mut runner) = *self.0.runner.borrow_mut() {
|
||||
runner.state = new_state;
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_loop_destroyed(&self) {
|
||||
let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut());
|
||||
*self.0.page_transition_event_handle.borrow_mut() = None;
|
||||
*self.0.on_mouse_move.borrow_mut() = None;
|
||||
*self.0.on_wheel.borrow_mut() = None;
|
||||
*self.0.on_mouse_press.borrow_mut() = None;
|
||||
*self.0.on_mouse_release.borrow_mut() = None;
|
||||
*self.0.on_key_press.borrow_mut() = None;
|
||||
*self.0.on_key_release.borrow_mut() = None;
|
||||
*self.0.on_visibility_change.borrow_mut() = None;
|
||||
// Dropping the `Runner` drops the event handler closure, which will in
|
||||
// turn drop all `Window`s moved into the closure.
|
||||
*self.0.runner.borrow_mut() = RunnerEnum::Destroyed;
|
||||
for (_, canvas, _) in all_canvases {
|
||||
// In case any remaining `Window`s are still not dropped, we will need
|
||||
// to explicitly remove the event handlers associated with their canvases.
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.remove_listeners();
|
||||
}
|
||||
}
|
||||
// At this point, the `self.0` `Rc` should only be strongly referenced
|
||||
// by the following:
|
||||
// * `self`, i.e. the item which triggered this event loop wakeup, which is usually a
|
||||
// `wasm-bindgen` `Closure`, which will be dropped after returning to the JS glue code.
|
||||
// * The `ActiveEventLoop` leaked inside `EventLoop::run_app` due to the JS exception thrown
|
||||
// at the end.
|
||||
// * For each undropped `Window`:
|
||||
// * The `register_redraw_request` closure.
|
||||
// * The `destroy_fn` closure.
|
||||
if self.0.event_loop_recreation.get() {
|
||||
EventLoop::allow_event_loop_recreation();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the event loop is currently closed
|
||||
fn is_closed(&self) -> bool {
|
||||
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
|
||||
Ok(RunnerEnum::Running(runner)) => runner.state.exiting(),
|
||||
// The event loop is not closed since it is not initialized.
|
||||
Ok(RunnerEnum::Pending) => false,
|
||||
// The event loop is closed since it has been destroyed.
|
||||
Ok(RunnerEnum::Destroyed) => true,
|
||||
// The event loop is not closed since its still waiting to be started.
|
||||
Ok(RunnerEnum::Initializing(_)) => false,
|
||||
// Some other code is mutating the runner, which most likely means
|
||||
// the event loop is running and busy.
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
||||
self.0.device_events.set(allowed)
|
||||
}
|
||||
|
||||
fn device_events(&self) -> bool {
|
||||
match self.0.device_events.get() {
|
||||
DeviceEvents::Always => true,
|
||||
DeviceEvents::WhenFocused => {
|
||||
self.0.all_canvases.borrow().iter().any(|(_, canvas, _)| {
|
||||
if let Some(canvas) = canvas.upgrade() {
|
||||
canvas.has_focus.get()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
},
|
||||
DeviceEvents::Never => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_loop_recreation(&self, allow: bool) {
|
||||
self.0.event_loop_recreation.set(allow)
|
||||
}
|
||||
|
||||
pub(crate) fn control_flow(&self) -> ControlFlow {
|
||||
self.0.control_flow.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.0.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&self) {
|
||||
self.0.exit.set(true)
|
||||
}
|
||||
|
||||
pub(crate) fn exiting(&self) -> bool {
|
||||
self.0.exit.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.0.poll_strategy.set(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_strategy(&self) -> PollStrategy {
|
||||
self.0.poll_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.0.wait_until_strategy.set(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.0.wait_until_strategy.get()
|
||||
}
|
||||
|
||||
pub(crate) fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
|
||||
&self.0.event_loop_proxy
|
||||
}
|
||||
|
||||
pub(crate) fn weak(&self) -> WeakShared {
|
||||
WeakShared(Rc::downgrade(&self.0))
|
||||
}
|
||||
|
||||
pub(crate) fn monitor(&self) -> &Rc<MonitorHandler> {
|
||||
&self.0.monitor
|
||||
}
|
||||
|
||||
pub(crate) fn safe_area(&self) -> &Rc<SafeAreaHandle> {
|
||||
&self.0.safe_area
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WeakShared(Weak<Execution>);
|
||||
|
||||
impl WeakShared {
|
||||
pub fn upgrade(&self) -> Option<Shared> {
|
||||
self.0.upgrade().map(Shared)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum Event {
|
||||
NewEvents(StartCause),
|
||||
WindowEvent { window_id: WindowId, event: WindowEvent },
|
||||
ScaleChange { canvas: Weak<backend::Canvas>, size: PhysicalSize<u32>, scale: f64 },
|
||||
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
|
||||
Suspended,
|
||||
CreateSurfaces,
|
||||
Resumed,
|
||||
AboutToWait,
|
||||
UserWakeUp,
|
||||
}
|
||||
18
winit-web/src/event_loop/state.rs
Normal file
18
winit-web/src/event_loop/state.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use web_time::Instant;
|
||||
|
||||
use super::backend;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum State {
|
||||
Init,
|
||||
WaitUntil { _timeout: backend::Schedule, start: Instant, end: Instant },
|
||||
Wait { start: Instant },
|
||||
Poll { _request: backend::Schedule },
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn exiting(&self) -> bool {
|
||||
matches!(self, State::Exit)
|
||||
}
|
||||
}
|
||||
574
winit-web/src/event_loop/window_target.rs
Normal file
574
winit-web/src/event_loop/window_target.rs
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
use std::cell::Cell;
|
||||
use std::clone::Clone;
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use web_sys::Element;
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::event::{ElementState, KeyEvent, TouchPhase, WindowEvent};
|
||||
use winit_core::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
|
||||
EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
|
||||
};
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
use winit_core::monitor::MonitorHandle as CoremMonitorHandle;
|
||||
use winit_core::window::{Theme, WindowId};
|
||||
|
||||
use super::super::lock;
|
||||
use super::super::monitor::MonitorPermissionFuture;
|
||||
use super::runner::Event;
|
||||
use super::{backend, runner};
|
||||
use crate::cursor::CustomCursor;
|
||||
use crate::event_loop::proxy::EventLoopProxy;
|
||||
use crate::window::Window;
|
||||
use crate::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct ModifiersShared(Rc<Cell<ModifiersState>>);
|
||||
|
||||
impl ModifiersShared {
|
||||
fn set(&self, new: ModifiersState) {
|
||||
self.0.set(new)
|
||||
}
|
||||
|
||||
fn get(&self) -> ModifiersState {
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ModifiersShared {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Rc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
pub(crate) runner: runner::Shared,
|
||||
modifiers: ModifiersShared,
|
||||
}
|
||||
|
||||
impl ActiveEventLoop {
|
||||
pub fn new() -> Self {
|
||||
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
|
||||
}
|
||||
|
||||
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
|
||||
self.runner.event_loop_recreation(event_loop_recreation);
|
||||
self.runner.start(app, self.clone());
|
||||
}
|
||||
|
||||
pub fn generate_id(&self) -> WindowId {
|
||||
WindowId::from_raw(self.runner.generate_id())
|
||||
}
|
||||
|
||||
pub fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
|
||||
CustomCursorFuture(CustomCursor::new_async(self, source))
|
||||
}
|
||||
|
||||
pub fn register(&self, canvas: &Rc<backend::Canvas>, window_id: WindowId) {
|
||||
let canvas_clone = canvas.clone();
|
||||
|
||||
canvas.on_touch_start();
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = canvas.has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
canvas.on_blur(move || {
|
||||
has_focus.set(false);
|
||||
|
||||
let clear_modifiers = (!modifiers.get().is_empty()).then(|| {
|
||||
modifiers.set(ModifiersState::empty());
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(clear_modifiers.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Focused(false),
|
||||
})));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = canvas.has_focus.clone();
|
||||
canvas.on_focus(move || {
|
||||
if !has_focus.replace(true) {
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Focused(true),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// It is possible that at this point the canvas has
|
||||
// been focused before the callback can be called.
|
||||
let focused = canvas
|
||||
.document()
|
||||
.active_element()
|
||||
.filter(|element| {
|
||||
let canvas: &Element = canvas.raw();
|
||||
element == canvas
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if focused {
|
||||
canvas.has_focus.set(true);
|
||||
self.runner
|
||||
.send_event(Event::WindowEvent { window_id, event: WindowEvent::Focused(true) })
|
||||
}
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
canvas.on_keyboard_press(
|
||||
move |physical_key, logical_key, text, location, repeat, active_modifiers| {
|
||||
let modifiers_changed = (modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(
|
||||
iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
physical_key,
|
||||
logical_key: logical_key.clone(),
|
||||
text: text.clone(),
|
||||
location,
|
||||
state: ElementState::Pressed,
|
||||
repeat,
|
||||
text_with_all_modifiers: text,
|
||||
key_without_modifiers: logical_key,
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
})
|
||||
.chain(modifiers_changed),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
canvas.on_keyboard_release(
|
||||
move |physical_key, logical_key, text, location, repeat, active_modifiers| {
|
||||
let modifiers_changed = (modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(
|
||||
iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id: None,
|
||||
event: KeyEvent {
|
||||
physical_key,
|
||||
logical_key: logical_key.clone(),
|
||||
text: text.clone(),
|
||||
location,
|
||||
state: ElementState::Released,
|
||||
repeat,
|
||||
text_with_all_modifiers: text,
|
||||
key_without_modifiers: logical_key,
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
})
|
||||
.chain(modifiers_changed),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let has_focus = canvas.has_focus.clone();
|
||||
canvas.on_pointer_leave({
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |active_modifiers, device_id, primary, position, kind| {
|
||||
let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(focus.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerLeft {
|
||||
device_id,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind,
|
||||
},
|
||||
})))
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on_pointer_enter({
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |active_modifiers, device_id, primary, position, kind| {
|
||||
let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(focus.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerEntered { device_id, primary, position, kind },
|
||||
})))
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on_pointer_move(
|
||||
{
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |device_id, events| {
|
||||
runner.send_events(events.flat_map(
|
||||
|(active_modifiers, primary, position, source)| {
|
||||
let modifiers = (has_focus.get()
|
||||
&& modifiers.get() != active_modifiers)
|
||||
.then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(
|
||||
active_modifiers.into(),
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
modifiers.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerMoved {
|
||||
device_id,
|
||||
primary,
|
||||
position,
|
||||
source,
|
||||
},
|
||||
}))
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
{
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |active_modifiers, device_id, primary, position, state, button| {
|
||||
let modifiers =
|
||||
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(modifiers.into_iter().chain([Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerButton {
|
||||
device_id,
|
||||
primary,
|
||||
state,
|
||||
position,
|
||||
button,
|
||||
},
|
||||
}]));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
canvas.on_pointer_press({
|
||||
let runner = self.runner.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |active_modifiers, device_id, primary, position, button| {
|
||||
let modifiers = (modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(modifiers.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerButton {
|
||||
device_id,
|
||||
primary,
|
||||
state: ElementState::Pressed,
|
||||
position,
|
||||
button,
|
||||
},
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on_pointer_release({
|
||||
let runner = self.runner.clone();
|
||||
let has_focus = has_focus.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
|
||||
move |active_modifiers, device_id, primary, position, button| {
|
||||
let modifiers =
|
||||
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(modifiers.into_iter().chain(iter::once(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::PointerButton {
|
||||
device_id,
|
||||
primary,
|
||||
state: ElementState::Released,
|
||||
position,
|
||||
button,
|
||||
},
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
let modifiers = self.modifiers.clone();
|
||||
canvas.on_mouse_wheel(move |delta, active_modifiers| {
|
||||
let modifiers_changed =
|
||||
(has_focus.get() && modifiers.get() != active_modifiers).then(|| {
|
||||
modifiers.set(active_modifiers);
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
|
||||
}
|
||||
});
|
||||
|
||||
runner.send_events(modifiers_changed.into_iter().chain(iter::once(
|
||||
Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::MouseWheel {
|
||||
device_id: None,
|
||||
delta,
|
||||
phase: TouchPhase::Moved,
|
||||
},
|
||||
},
|
||||
)));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_dark_mode(move |is_dark_mode| {
|
||||
let theme = if is_dark_mode { Theme::Dark } else { Theme::Light };
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::ThemeChanged(theme),
|
||||
});
|
||||
});
|
||||
|
||||
canvas.on_resize_scale(
|
||||
{
|
||||
let runner = self.runner.clone();
|
||||
let canvas = canvas_clone.clone();
|
||||
|
||||
move |size, scale| {
|
||||
runner.send_event(Event::ScaleChange {
|
||||
canvas: Rc::downgrade(&canvas),
|
||||
size,
|
||||
scale,
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
let runner = self.runner.clone();
|
||||
let canvas = canvas_clone.clone();
|
||||
|
||||
move |new_size| {
|
||||
canvas.set_current_size(new_size);
|
||||
if canvas.old_size() != new_size {
|
||||
canvas.set_old_size(new_size);
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::SurfaceResized(new_size),
|
||||
});
|
||||
canvas.request_animation_frame();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_intersection(move |is_intersecting| {
|
||||
// only fire if visible while skipping the first event if it's intersecting
|
||||
if backend::is_visible(runner.document())
|
||||
&& !(is_intersecting && canvas_clone.is_intersecting.get().is_none())
|
||||
{
|
||||
runner.send_event(Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::Occluded(!is_intersecting),
|
||||
});
|
||||
}
|
||||
|
||||
canvas_clone.is_intersecting.set(Some(is_intersecting));
|
||||
});
|
||||
|
||||
let runner = self.runner.clone();
|
||||
canvas.on_animation_frame(move || runner.request_redraw(window_id));
|
||||
|
||||
canvas.on_context_menu();
|
||||
}
|
||||
|
||||
pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.runner.set_poll_strategy(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_strategy(&self) -> PollStrategy {
|
||||
self.runner.poll_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.runner.set_wait_until_strategy(strategy)
|
||||
}
|
||||
|
||||
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.runner.wait_until_strategy()
|
||||
}
|
||||
|
||||
pub(crate) fn is_cursor_lock_raw(&self) -> bool {
|
||||
lock::is_cursor_lock_raw(self.runner.navigator(), self.runner.document())
|
||||
}
|
||||
|
||||
pub(crate) fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
|
||||
self.runner
|
||||
.monitor()
|
||||
.is_extended()
|
||||
.ok_or(NotSupportedError::new("has_multiple_screens is not supported"))
|
||||
}
|
||||
|
||||
pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
|
||||
self.runner.monitor().request_detailed_monitor_permission()
|
||||
}
|
||||
|
||||
pub(crate) fn has_detailed_monitor_permission(&self) -> bool {
|
||||
self.runner.monitor().has_detailed_monitor_permission()
|
||||
}
|
||||
|
||||
pub(crate) fn event_loop_proxy(&self) -> Arc<EventLoopProxy> {
|
||||
self.runner.event_loop_proxy().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl RootActiveEventLoop for ActiveEventLoop {
|
||||
fn create_proxy(&self) -> RootEventLoopProxy {
|
||||
let event_loop_proxy = self.event_loop_proxy();
|
||||
RootEventLoopProxy::new(event_loop_proxy)
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
&self,
|
||||
window_attributes: winit_core::window::WindowAttributes,
|
||||
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
|
||||
let window = Window::new(self, window_attributes)?;
|
||||
Ok(Box::new(window))
|
||||
}
|
||||
|
||||
fn create_custom_cursor(
|
||||
&self,
|
||||
source: CustomCursorSource,
|
||||
) -> Result<CoreCustomCursor, RequestError> {
|
||||
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, source))))
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoremMonitorHandle>> {
|
||||
Box::new(
|
||||
self.runner
|
||||
.monitor()
|
||||
.available_monitors()
|
||||
.into_iter()
|
||||
.map(|monitor| CoremMonitorHandle(Arc::new(monitor))),
|
||||
)
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<CoremMonitorHandle> {
|
||||
self.runner.monitor().primary_monitor().map(|monitor| CoremMonitorHandle(Arc::new(monitor)))
|
||||
}
|
||||
|
||||
fn listen_device_events(&self, allowed: DeviceEvents) {
|
||||
self.runner.listen_device_events(allowed)
|
||||
}
|
||||
|
||||
fn system_theme(&self) -> Option<Theme> {
|
||||
backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| {
|
||||
if is_dark_mode {
|
||||
Theme::Dark
|
||||
} else {
|
||||
Theme::Light
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.runner.set_control_flow(control_flow)
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> ControlFlow {
|
||||
self.runner.control_flow()
|
||||
}
|
||||
|
||||
fn exit(&self) {
|
||||
self.runner.exit()
|
||||
}
|
||||
|
||||
fn exiting(&self) -> bool {
|
||||
self.runner.exiting()
|
||||
}
|
||||
|
||||
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
|
||||
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
|
||||
}
|
||||
|
||||
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new());
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct OwnedDisplayHandle;
|
||||
|
||||
impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
|
||||
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
let raw = rwh_06::RawDisplayHandle::Web(rwh_06::WebDisplayHandle::new());
|
||||
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue