Skip to main content

blitz_shell/
event.rs

1use blitz_traits::handoff::{EventQueue, EventTarget};
2use blitz_traits::navigation::NavigationOptions;
3use blitz_traits::net::NetWaker;
4use futures_util::task::ArcWake;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::{any::Any, sync::Arc};
7use winit::{event_loop::EventLoopProxy, window::WindowId};
8
9#[cfg(feature = "accessibility")]
10use accesskit_xplat::WindowEvent as AccessKitEvent;
11
12#[derive(Debug, Clone)]
13pub enum BlitzShellEvent {
14    Poll {
15        window_id: WindowId,
16    },
17
18    /// The renderer for this window has finished its async initialization. The
19    /// embedder should call `View::complete_resume` to transition the view into
20    /// an active state.
21    ResumeReady {
22        window_id: WindowId,
23    },
24
25    RequestRedraw {
26        doc_id: usize,
27    },
28
29    /// Close a window programmatically (e.g. a custom titlebar close button).
30    /// Handled identically to `WindowEvent::CloseRequested`.
31    CloseWindow {
32        window_id: WindowId,
33    },
34
35    /// An accessibility event from `accesskit`.
36    #[cfg(feature = "accessibility")]
37    Accessibility {
38        window_id: WindowId,
39        data: Arc<AccessKitEvent>,
40    },
41
42    /// An arbitary event from the Blitz embedder
43    Embedder(Arc<dyn Any + Send + Sync>),
44
45    /// Navigate to another URL (triggered by e.g. clicking a link)
46    Navigate(Box<NavigationOptions>),
47
48    /// Navigate to another URL (triggered by e.g. clicking a link)
49    NavigationLoad {
50        url: String,
51        contents: String,
52        retain_scroll_position: bool,
53        is_md: bool,
54    },
55
56    /// Delivered after the WASM resize-debounce window expires. Route to
57    /// `View::apply_pending_resize_if_settled`, which applies the pending
58    /// size iff motion has actually settled.
59    #[cfg(target_arch = "wasm32")]
60    ResizeSettleCheck {
61        window_id: WindowId,
62    },
63}
64impl BlitzShellEvent {
65    pub fn embedder_event<T: Any + Send + Sync>(value: T) -> Self {
66        let boxed = Arc::new(value) as Arc<dyn Any + Send + Sync>;
67        Self::Embedder(boxed)
68    }
69}
70
71pub type BlitzShellEventQueue = EventQueue<BlitzShellEvent>;
72
73#[derive(Clone)]
74pub struct BlitzShellProxy(Arc<BlitzShellProxyInner>);
75pub struct BlitzShellProxyInner {
76    winit_proxy: EventLoopProxy,
77    target: EventTarget<BlitzShellEvent>,
78}
79
80impl BlitzShellProxy {
81    pub fn new(winit_proxy: EventLoopProxy) -> (Self, BlitzShellEventQueue) {
82        let wake_proxy = winit_proxy.clone();
83        let queue = EventQueue::new(move || wake_proxy.wake_up());
84        let proxy = Self(Arc::new(BlitzShellProxyInner {
85            winit_proxy,
86            target: queue.target(),
87        }));
88        (proxy, queue)
89    }
90
91    pub fn wake_up(&self) {
92        self.0.winit_proxy.wake_up();
93    }
94    pub fn send_event(&self, event: impl Into<BlitzShellEvent>) {
95        self.send_event_impl(event.into());
96    }
97    fn send_event_impl(&self, event: BlitzShellEvent) {
98        let _ = self.0.target.publish(Arc::new(event));
99    }
100}
101
102impl NetWaker for BlitzShellProxy {
103    fn wake(&self, client_id: usize) {
104        self.send_event_impl(BlitzShellEvent::RequestRedraw { doc_id: client_id })
105    }
106}
107
108/// Create a waker that asks the event loop to poll a window's document.
109///
110/// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView.
111///
112/// Queue publication wakes the event loop through nagoya.
113///
114/// The request is a flag the window owns rather than a queued event, because
115/// wanting a poll is an edge and not a message: two wakes before the loop comes
116/// round mean the same thing as one. Queueing them meant an allocation and a
117/// wake syscall each, and a poll each on the far side.
118pub fn create_waker(proxy: &BlitzShellProxy, poll_requested: Arc<AtomicBool>) -> std::task::Waker {
119    struct DomHandle {
120        proxy: BlitzShellProxy,
121        poll_requested: Arc<AtomicBool>,
122    }
123    impl ArcWake for DomHandle {
124        fn wake_by_ref(arc_self: &Arc<Self>) {
125            arc_self.poll_requested.store(true, Ordering::Release);
126            arc_self.proxy.wake_up();
127        }
128    }
129
130    let proxy = proxy.clone();
131    futures_util::task::waker(Arc::new(DomHandle {
132        poll_requested,
133        proxy,
134    }))
135}