blitz_shell/
application.rs

1use crate::event::BlitzShellEvent;
2
3use blitz_dom::BaseDocument;
4use blitz_traits::{Document, DocumentRenderer};
5use std::collections::HashMap;
6use winit::application::ApplicationHandler;
7use winit::event::WindowEvent;
8use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
9use winit::window::WindowId;
10
11use crate::{View, WindowConfig};
12
13// TODO: make generic
14type D = BaseDocument;
15
16pub struct BlitzApplication<Doc: Document<Doc = D>, Rend: DocumentRenderer<Doc = D>> {
17    pub windows: HashMap<WindowId, View<Doc, Rend>>,
18    pending_windows: Vec<WindowConfig<Doc, Rend>>,
19    proxy: EventLoopProxy<BlitzShellEvent>,
20
21    #[cfg(all(feature = "menu", not(any(target_os = "android", target_os = "ios"))))]
22    menu_channel: muda::MenuEventReceiver,
23}
24
25impl<Doc: Document<Doc = D>, Rend: DocumentRenderer<Doc = D>> BlitzApplication<Doc, Rend> {
26    pub fn new(proxy: EventLoopProxy<BlitzShellEvent>) -> Self {
27        BlitzApplication {
28            windows: HashMap::new(),
29            pending_windows: Vec::new(),
30            proxy,
31
32            #[cfg(all(feature = "menu", not(any(target_os = "android", target_os = "ios"))))]
33            menu_channel: muda::MenuEvent::receiver().clone(),
34        }
35    }
36
37    pub fn add_window(&mut self, window_config: WindowConfig<Doc, Rend>) {
38        self.pending_windows.push(window_config);
39    }
40
41    fn window_mut_by_doc_id(&mut self, doc_id: usize) -> Option<&mut View<Doc, Rend>> {
42        self.windows.values_mut().find(|w| w.doc.id() == doc_id)
43    }
44}
45
46impl<Doc: Document<Doc = D>, Rend: DocumentRenderer<Doc = D>> ApplicationHandler<BlitzShellEvent>
47    for BlitzApplication<Doc, Rend>
48{
49    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
50        // Resume existing windows
51        for (_, view) in self.windows.iter_mut() {
52            view.resume();
53        }
54
55        // Initialise pending windows
56        for window_config in self.pending_windows.drain(..) {
57            let mut view = View::init(window_config, event_loop, &self.proxy);
58            view.resume();
59            if !view.renderer.is_active() {
60                continue;
61            }
62            self.windows.insert(view.window_id(), view);
63        }
64    }
65
66    fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
67        for (_, view) in self.windows.iter_mut() {
68            view.suspend();
69        }
70    }
71
72    fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: winit::event::StartCause) {
73        #[cfg(all(feature = "menu", not(any(target_os = "android", target_os = "ios"))))]
74        if let Ok(event) = self.menu_channel.try_recv() {
75            if event.id == muda::MenuId::new("dev.show_layout") {
76                for (_, view) in self.windows.iter_mut() {
77                    view.devtools.show_layout = !view.devtools.show_layout;
78                    view.request_redraw();
79                }
80            }
81        }
82    }
83
84    fn window_event(
85        &mut self,
86        event_loop: &ActiveEventLoop,
87        window_id: WindowId,
88        event: WindowEvent,
89    ) {
90        // Exit the app when window close is requested.
91        if matches!(event, WindowEvent::CloseRequested) {
92            // Drop window before exiting event loop
93            // See https://github.com/rust-windowing/winit/issues/4135
94            let window = self.windows.remove(&window_id);
95            drop(window);
96            if self.windows.is_empty() {
97                event_loop.exit();
98            }
99            return;
100        }
101
102        if let Some(window) = self.windows.get_mut(&window_id) {
103            window.handle_winit_event(event);
104        }
105
106        let _ = self.proxy.send_event(BlitzShellEvent::Poll { window_id });
107    }
108
109    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: BlitzShellEvent) {
110        match event {
111            BlitzShellEvent::Poll { window_id } => {
112                if let Some(window) = self.windows.get_mut(&window_id) {
113                    window.poll();
114                };
115            }
116
117            BlitzShellEvent::ResourceLoad { doc_id, data } => {
118                // TODO: Handle multiple documents per window
119                if let Some(window) = self.window_mut_by_doc_id(doc_id) {
120                    window.doc.as_mut().load_resource(data);
121                    window.request_redraw();
122                }
123            }
124
125            #[cfg(feature = "accessibility")]
126            BlitzShellEvent::Accessibility { window_id, data } => {
127                if let Some(window) = self.windows.get_mut(&window_id) {
128                    match &*data {
129                        accesskit_winit::WindowEvent::InitialTreeRequested => {
130                            window.build_accessibility_tree();
131                        }
132                        accesskit_winit::WindowEvent::AccessibilityDeactivated => {
133                            // TODO
134                        }
135                        accesskit_winit::WindowEvent::ActionRequested(_req) => {
136                            // TODO
137                        }
138                    }
139                }
140            }
141
142            BlitzShellEvent::Embedder(_) => {
143                // Do nothing. Should be handled by embedders (if required).
144            }
145            BlitzShellEvent::Navigate(_url) => {
146                // Do nothing. Should be handled by embedders (if required).
147            }
148        }
149    }
150}