blitz_shell/
application.rs1use 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::event_loop::ControlFlow;
10use winit::window::WindowId;
11
12#[cfg(target_os = "macos")]
13use winit::platform::macos::ApplicationHandlerExtMacOS;
14
15use crate::{View, WindowConfig};
16
17pub struct BlitzApplication<Rend: WindowRenderer> {
18 pub windows: HashMap<WindowId, View<Rend>>,
19 pub pending_windows: Vec<WindowConfig<Rend>>,
20 pub proxy: BlitzShellProxy,
21 pub event_queue: Receiver<BlitzShellEvent>,
22 #[cfg(feature = "debug-control")]
23 debug_controller: Option<blitz_script::DebugController>,
24}
25
26impl<Rend: WindowRenderer> BlitzApplication<Rend> {
27 pub fn new(proxy: BlitzShellProxy, event_queue: Receiver<BlitzShellEvent>) -> Self {
28 BlitzApplication {
29 windows: HashMap::new(),
30 pending_windows: Vec::new(),
31 proxy,
32 event_queue,
33 #[cfg(feature = "debug-control")]
34 debug_controller: None,
35 }
36 }
37
38 pub fn add_window(&mut self, window_config: WindowConfig<Rend>) {
39 self.pending_windows.push(window_config);
40 }
41
42 #[cfg(feature = "debug-control")]
43 pub fn set_debug_controller(&mut self, controller: blitz_script::DebugController) {
44 let proxy = self.proxy.clone();
50 controller.set_waker(move || proxy.wake_up());
51 self.debug_controller = Some(controller);
52 }
53
54 #[cfg(feature = "debug-control")]
55 fn service_debug_controller(&mut self, event_loop: &dyn ActiveEventLoop) {
56 let Some(controller) = self.debug_controller.as_mut() else {
57 return;
58 };
59 let Some(document) = self
60 .windows
61 .values_mut()
62 .find_map(|view| view.try_downcast_doc_mut::<blitz_script::ScriptDocument>())
63 else {
64 return;
67 };
68 controller.service_pending(document);
69 if controller.exit_requested() {
70 event_loop.exit();
71 }
72 }
73
74 fn window_mut_by_doc_id(&mut self, doc_id: usize) -> Option<&mut View<Rend>> {
75 self.windows.values_mut().find(|w| w.doc.id() == doc_id)
76 }
77
78 pub fn handle_blitz_shell_event(
79 &mut self,
80 event_loop: &dyn ActiveEventLoop,
81 event: BlitzShellEvent,
82 ) {
83 match event {
84 BlitzShellEvent::Poll { window_id } => {
85 if let Some(window) = self.windows.get(&window_id) {
89 window.request_poll();
90 };
91 }
92 BlitzShellEvent::CloseWindow { window_id } => {
93 let window = self.windows.remove(&window_id);
96 drop(window);
97 if self.windows.is_empty() {
98 event_loop.exit();
99 }
100 }
101 BlitzShellEvent::ResumeReady { window_id } => {
102 if let Some(window) = self.windows.get_mut(&window_id)
106 && window.waker.is_none()
107 {
108 let ok = window.complete_resume();
109 debug_assert!(ok, "ResumeReady received but renderer not ready");
110 }
111 }
112 BlitzShellEvent::RequestRedraw { doc_id } => {
113 if let Some(window) = self.window_mut_by_doc_id(doc_id) {
115 window.request_redraw();
116 }
117 }
118
119 #[cfg(feature = "accessibility")]
120 BlitzShellEvent::Accessibility { window_id, data } => {
121 if let Some(window) = self.windows.get_mut(&window_id) {
122 match &*data {
123 accesskit_xplat::WindowEvent::InitialTreeRequested => {
124 window.build_accessibility_tree();
125 }
126 accesskit_xplat::WindowEvent::AccessibilityDeactivated => {
127 }
129 accesskit_xplat::WindowEvent::ActionRequested(_req) => {
130 }
132 }
133 }
134 }
135 BlitzShellEvent::Embedder(_) => {
136 }
138 BlitzShellEvent::Navigate(_opts) => {
139 }
141 BlitzShellEvent::NavigationLoad { .. } => {
142 }
144 #[cfg(target_arch = "wasm32")]
145 BlitzShellEvent::ResizeSettleCheck { window_id } => {
146 if let Some(window) = self.windows.get_mut(&window_id) {
147 window.apply_pending_resize_if_settled();
148 }
149 }
150 }
151 }
152}
153
154impl<Rend: WindowRenderer> ApplicationHandler for BlitzApplication<Rend> {
155 fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
156 for view in self.windows.values_mut() {
158 view.resume();
159 #[cfg(not(target_arch = "wasm32"))]
160 {
161 let ok = view.complete_resume();
162 debug_assert!(ok, "native renderer did not resume synchronously");
163 }
164 }
165
166 for window_config in self.pending_windows.drain(..) {
171 let mut view = View::init(window_config, event_loop, &self.proxy);
172 view.resume();
173 #[cfg(not(target_arch = "wasm32"))]
174 {
175 let ok = view.complete_resume();
176 debug_assert!(ok, "native renderer did not resume synchronously");
177 }
178 self.windows.insert(view.window_id(), view);
179 }
180 }
181
182 fn destroy_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {
183 for view in self.windows.values_mut() {
184 view.suspend();
185 }
186 }
187
188 fn resumed(&mut self, _event_loop: &dyn ActiveEventLoop) {
189 }
191
192 fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {
193 }
195
196 fn window_event(
197 &mut self,
198 event_loop: &dyn ActiveEventLoop,
199 window_id: WindowId,
200 event: WindowEvent,
201 ) {
202 if matches!(event, WindowEvent::CloseRequested) {
204 let window = self.windows.remove(&window_id);
207 drop(window);
208 if self.windows.is_empty() {
209 event_loop.exit();
210 }
211 return;
212 }
213
214 if let Some(window) = self.windows.get_mut(&window_id) {
215 window.handle_winit_event(event);
216 window.request_poll();
220 }
221 }
222
223 fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
224 while let Ok(event) = self.event_queue.try_recv() {
225 self.handle_blitz_shell_event(event_loop, event);
226 }
227 #[cfg(feature = "debug-control")]
228 self.service_debug_controller(event_loop);
229 }
230
231 #[cfg(target_os = "macos")]
232 fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
233 Some(self)
234 }
235
236 fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
237 let _ = event_loop;
238 #[cfg(feature = "debug-control")]
239 self.service_debug_controller(event_loop);
240
241 for window in self.windows.values_mut() {
245 window.poll_if_requested();
246 }
247
248 #[cfg(target_os = "ios")]
249 for view in self.windows.values_mut() {
250 if view.ios_request_redraw.get() {
251 view.window.request_redraw();
252 }
253 }
254
255 let now = web_time::Instant::now();
275 let next_frame = self
276 .windows
277 .values()
278 .filter_map(|view| view.poll_animation_frame(now))
279 .min();
280 if let Some(deadline) = next_frame {
281 event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
282 } else {
283 event_loop.set_control_flow(ControlFlow::Wait);
284 }
285 }
286}
287
288#[cfg(target_os = "macos")]
289impl<Rend: WindowRenderer> ApplicationHandlerExtMacOS for BlitzApplication<Rend> {
290 fn standard_key_binding(
291 &mut self,
292 _event_loop: &dyn ActiveEventLoop,
293 window_id: WindowId,
294 action: &str,
295 ) {
296 if let Some(window) = self.windows.get_mut(&window_id) {
297 window.handle_apple_standard_keybinding(action);
298 window.request_poll();
299 }
300 }
301}