blitz_shell/
application.rs

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