Skip to main content

blitz_shell/
event.rs

1use blitz_traits::navigation::NavigationOptions;
2use blitz_traits::net::NetWaker;
3use futures_util::task::ArcWake;
4use std::sync::mpsc::{Receiver, Sender, channel};
5use std::{any::Any, sync::Arc};
6use winit::{event_loop::EventLoopProxy, window::WindowId};
7
8#[cfg(feature = "accessibility")]
9use accesskit_xplat::WindowEvent as AccessKitEvent;
10
11#[derive(Debug, Clone)]
12pub enum BlitzShellEvent {
13    Poll {
14        window_id: WindowId,
15    },
16
17    /// The renderer for this window has finished its async initialization. The
18    /// embedder should call `View::complete_resume` to transition the view into
19    /// an active state.
20    ResumeReady {
21        window_id: WindowId,
22    },
23
24    RequestRedraw {
25        doc_id: usize,
26    },
27
28    /// Close a window programmatically (e.g. a custom titlebar close button).
29    /// Handled identically to `WindowEvent::CloseRequested`.
30    CloseWindow {
31        window_id: WindowId,
32    },
33
34    /// An accessibility event from `accesskit`.
35    #[cfg(feature = "accessibility")]
36    Accessibility {
37        window_id: WindowId,
38        data: Arc<AccessKitEvent>,
39    },
40
41    /// An arbitary event from the Blitz embedder
42    Embedder(Arc<dyn Any + Send + Sync>),
43
44    /// Navigate to another URL (triggered by e.g. clicking a link)
45    Navigate(Box<NavigationOptions>),
46
47    /// Navigate to another URL (triggered by e.g. clicking a link)
48    NavigationLoad {
49        url: String,
50        contents: String,
51        retain_scroll_position: bool,
52        is_md: bool,
53    },
54
55    /// Delivered after the WASM resize-debounce window expires. Route to
56    /// `View::apply_pending_resize_if_settled`, which applies the pending
57    /// size iff motion has actually settled.
58    #[cfg(target_arch = "wasm32")]
59    ResizeSettleCheck {
60        window_id: WindowId,
61    },
62}
63impl BlitzShellEvent {
64    pub fn embedder_event<T: Any + Send + Sync>(value: T) -> Self {
65        let boxed = Arc::new(value) as Arc<dyn Any + Send + Sync>;
66        Self::Embedder(boxed)
67    }
68}
69
70#[derive(Clone)]
71pub struct BlitzShellProxy(Arc<BlitzShellProxyInner>);
72pub struct BlitzShellProxyInner {
73    winit_proxy: EventLoopProxy,
74    sender: Sender<BlitzShellEvent>,
75}
76
77impl BlitzShellProxy {
78    pub fn new(winit_proxy: EventLoopProxy) -> (Self, Receiver<BlitzShellEvent>) {
79        let (sender, receiver) = channel();
80        let proxy = Self(Arc::new(BlitzShellProxyInner {
81            winit_proxy,
82            sender,
83        }));
84        (proxy, receiver)
85    }
86
87    pub fn wake_up(&self) {
88        self.0.winit_proxy.wake_up();
89    }
90    pub fn send_event(&self, event: impl Into<BlitzShellEvent>) {
91        self.send_event_impl(event.into());
92    }
93    fn send_event_impl(&self, event: BlitzShellEvent) {
94        let _ = self.0.sender.send(event);
95        self.wake_up();
96    }
97}
98
99impl NetWaker for BlitzShellProxy {
100    fn wake(&self, client_id: usize) {
101        self.send_event_impl(BlitzShellEvent::RequestRedraw { doc_id: client_id })
102    }
103}
104
105/// Create a waker that will send a poll event to the event loop.
106///
107/// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView.
108///
109/// All other IO lives in the Tokio runtime,
110pub fn create_waker(proxy: &BlitzShellProxy, id: WindowId) -> std::task::Waker {
111    struct DomHandle {
112        proxy: BlitzShellProxy,
113        id: WindowId,
114    }
115    impl ArcWake for DomHandle {
116        fn wake_by_ref(arc_self: &Arc<Self>) {
117            let event = BlitzShellEvent::Poll {
118                window_id: arc_self.id,
119            };
120            arc_self.proxy.send_event(event)
121        }
122    }
123
124    let proxy = proxy.clone();
125    futures_util::task::waker(Arc::new(DomHandle { id, proxy }))
126}