Skip to main content

blitz_shell/
application.rs

1use crate::event::{BlitzShellEvent, BlitzShellProxy};
2
3use anyrender::WindowRenderer;
4use std::collections::HashMap;
5use std::sync::mpsc::Receiver;
6use winit::application::ApplicationHandler;
7use winit::event::WindowEvent;
8use winit::event_loop::ActiveEventLoop;
9use winit::window::WindowId;
10
11#[cfg(target_os = "macos")]
12use winit::platform::macos::ApplicationHandlerExtMacOS;
13
14use crate::{View, WindowConfig};
15
16pub struct BlitzApplication<Rend: WindowRenderer> {
17    pub windows: HashMap<WindowId, View<Rend>>,
18    pub pending_windows: Vec<WindowConfig<Rend>>,
19    pub proxy: BlitzShellProxy,
20    pub event_queue: Receiver<BlitzShellEvent>,
21}
22
23impl<Rend: WindowRenderer> BlitzApplication<Rend> {
24    pub fn new(proxy: BlitzShellProxy, event_queue: Receiver<BlitzShellEvent>) -> Self {
25        BlitzApplication {
26            windows: HashMap::new(),
27            pending_windows: Vec::new(),
28            proxy,
29            event_queue,
30        }
31    }
32
33    pub fn add_window(&mut self, window_config: WindowConfig<Rend>) {
34        self.pending_windows.push(window_config);
35    }
36
37    fn window_mut_by_doc_id(&mut self, doc_id: usize) -> Option<&mut View<Rend>> {
38        self.windows.values_mut().find(|w| w.doc.id() == doc_id)
39    }
40
41    pub fn handle_blitz_shell_event(
42        &mut self,
43        _event_loop: &dyn ActiveEventLoop,
44        event: BlitzShellEvent,
45    ) {
46        match event {
47            BlitzShellEvent::Poll { window_id } => {
48                if let Some(window) = self.windows.get_mut(&window_id) {
49                    window.poll();
50                };
51            }
52            BlitzShellEvent::ResumeReady { window_id } => {
53                // The renderer fires `on_ready` after it has sent on the
54                // channel, so `complete_resume` should always succeed here.
55                // If a stale event survives a suspend, dropping it is safe.
56                if let Some(window) = self.windows.get_mut(&window_id) {
57                    let ok = window.complete_resume();
58                    debug_assert!(ok, "ResumeReady received but renderer not ready");
59                }
60            }
61            BlitzShellEvent::RequestRedraw { doc_id } => {
62                // TODO: Handle multiple documents per window
63                if let Some(window) = self.window_mut_by_doc_id(doc_id) {
64                    window.request_redraw();
65                }
66            }
67
68            #[cfg(feature = "accessibility")]
69            BlitzShellEvent::Accessibility { window_id, data } => {
70                if let Some(window) = self.windows.get_mut(&window_id) {
71                    match &*data {
72                        accesskit_xplat::WindowEvent::InitialTreeRequested => {
73                            window.build_accessibility_tree();
74                        }
75                        accesskit_xplat::WindowEvent::AccessibilityDeactivated => {
76                            // TODO
77                        }
78                        accesskit_xplat::WindowEvent::ActionRequested(_req) => {
79                            // TODO
80                        }
81                    }
82                }
83            }
84            BlitzShellEvent::Embedder(_) => {
85                // Do nothing. Should be handled by embedders (if required).
86            }
87            BlitzShellEvent::Navigate(_opts) => {
88                // Do nothing. Should be handled by embedders (if required).
89            }
90            BlitzShellEvent::NavigationLoad { .. } => {
91                // Do nothing. Should be handled by embedders (if required).
92            }
93            #[cfg(target_arch = "wasm32")]
94            BlitzShellEvent::ResizeSettleCheck { window_id } => {
95                if let Some(window) = self.windows.get_mut(&window_id) {
96                    window.apply_pending_resize_if_settled();
97                }
98            }
99        }
100    }
101}
102
103impl<Rend: WindowRenderer> ApplicationHandler for BlitzApplication<Rend> {
104    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
105        // Resume existing windows
106        for (_, view) in self.windows.iter_mut() {
107            view.resume();
108        }
109
110        // Initialise pending windows. The renderer's resume is non-blocking —
111        // on native it finishes inline, on wasm32 it spawns a future that will
112        // dispatch BlitzShellEvent::ResumeReady when init completes. Either way
113        // we insert the view immediately so the event handler can find it.
114        for window_config in self.pending_windows.drain(..) {
115            let mut view = View::init(window_config, event_loop, &self.proxy);
116            view.resume();
117            self.windows.insert(view.window_id(), view);
118        }
119    }
120
121    fn destroy_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {
122        for (_, view) in self.windows.iter_mut() {
123            view.suspend();
124        }
125    }
126
127    fn resumed(&mut self, _event_loop: &dyn ActiveEventLoop) {
128        // TODO
129    }
130
131    fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {
132        // TODO
133    }
134
135    fn window_event(
136        &mut self,
137        event_loop: &dyn ActiveEventLoop,
138        window_id: WindowId,
139        event: WindowEvent,
140    ) {
141        // Exit the app when window close is requested.
142        if matches!(event, WindowEvent::CloseRequested) {
143            // Drop window before exiting event loop
144            // See https://github.com/rust-windowing/winit/issues/4135
145            let window = self.windows.remove(&window_id);
146            drop(window);
147            if self.windows.is_empty() {
148                event_loop.exit();
149            }
150            return;
151        }
152
153        if let Some(window) = self.windows.get_mut(&window_id) {
154            window.handle_winit_event(event);
155        }
156        self.proxy.send_event(BlitzShellEvent::Poll { window_id });
157    }
158
159    fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
160        while let Ok(event) = self.event_queue.try_recv() {
161            self.handle_blitz_shell_event(event_loop, event);
162        }
163    }
164
165    #[cfg(target_os = "macos")]
166    fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
167        Some(self)
168    }
169
170    #[cfg(target_os = "ios")]
171    fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
172        for view in self.windows.values_mut() {
173            if view.ios_request_redraw.get() {
174                view.window.request_redraw();
175            }
176        }
177    }
178}
179
180#[cfg(target_os = "macos")]
181impl<Rend: WindowRenderer> ApplicationHandlerExtMacOS for BlitzApplication<Rend> {
182    fn standard_key_binding(
183        &mut self,
184        _event_loop: &dyn ActiveEventLoop,
185        window_id: WindowId,
186        action: &str,
187    ) {
188        if let Some(window) = self.windows.get_mut(&window_id) {
189            window.handle_apple_standard_keybinding(action);
190            self.proxy.send_event(BlitzShellEvent::Poll { window_id });
191        }
192    }
193}