Skip to main content

dioxus_native/
dioxus_application.rs

1use blitz_shell::{BlitzApplication, BlitzShellProxy, View};
2use dioxus_core::{ScopeId, provide_context};
3use dioxus_history::{History, MemoryHistory};
4use std::rc::Rc;
5use std::sync::Arc;
6use winit::application::ApplicationHandler;
7use winit::event::{StartCause, WindowEvent};
8use winit::event_loop::ActiveEventLoop;
9use winit::window::WindowId;
10
11#[cfg(target_os = "macos")]
12use winit::platform::macos::ApplicationHandlerExtMacOS;
13
14use crate::DioxusNativeWindowRenderer;
15use crate::event_handlers::WindowEventHandlers;
16use crate::{BlitzShellEvent, DioxusDocument, WindowConfig, contexts::DioxusNativeDocument};
17
18/// Dioxus-native specific event type
19pub enum DioxusNativeEvent {
20    /// A hotreload event, basically telling us to update our templates.
21    #[cfg(all(feature = "hot-reload", debug_assertions))]
22    DevserverEvent(dioxus_devtools::DevserverMsg),
23
24    /// Create a new head element from the Link and Title elements
25    ///
26    /// todo(jon): these should probabkly be synchronous somehow
27    CreateHeadElement {
28        window: WindowId,
29        name: String,
30        attributes: Vec<(String, String)>,
31        contents: Option<String>,
32    },
33}
34
35pub struct DioxusNativeApplication {
36    pending_window: Option<WindowConfig<DioxusNativeWindowRenderer>>,
37    inner: BlitzApplication<DioxusNativeWindowRenderer>,
38    event_handlers: Rc<WindowEventHandlers>,
39}
40
41impl DioxusNativeApplication {
42    pub fn new(
43        proxy: BlitzShellProxy,
44        event_queue: std::sync::mpsc::Receiver<BlitzShellEvent>,
45        config: WindowConfig<DioxusNativeWindowRenderer>,
46    ) -> Self {
47        Self {
48            pending_window: Some(config),
49            inner: BlitzApplication::new(proxy, event_queue),
50            event_handlers: Rc::new(WindowEventHandlers::default()),
51        }
52    }
53
54    pub fn add_window(&mut self, window_config: WindowConfig<DioxusNativeWindowRenderer>) {
55        self.inner.add_window(window_config);
56    }
57
58    fn handle_dioxus_native_event(
59        &mut self,
60        event_loop: &dyn ActiveEventLoop,
61        event: &DioxusNativeEvent,
62    ) {
63        match event {
64            #[cfg(all(feature = "hot-reload", debug_assertions))]
65            DioxusNativeEvent::DevserverEvent(event) => match event {
66                dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
67                    for window in self.inner.windows.values_mut() {
68                        let doc = window.downcast_doc_mut::<DioxusDocument>();
69
70                        // Apply changes to vdom
71                        dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);
72
73                        // Reload changed assets
74                        for asset_path in &hotreload_message.assets {
75                            if let Some(url) = asset_path.to_str() {
76                                doc.inner.borrow_mut().reload_resource_by_href(url);
77                            }
78                        }
79
80                        window.poll();
81                    }
82                }
83                dioxus_devtools::DevserverMsg::Shutdown => event_loop.exit(),
84                dioxus_devtools::DevserverMsg::FullReloadStart => {}
85                dioxus_devtools::DevserverMsg::FullReloadFailed => {}
86                dioxus_devtools::DevserverMsg::FullReloadCommand => {}
87                _ => {}
88            },
89
90            DioxusNativeEvent::CreateHeadElement {
91                name,
92                attributes,
93                contents,
94                window,
95            } => {
96                if let Some(window) = self.inner.windows.get_mut(window) {
97                    let doc = window.downcast_doc_mut::<DioxusDocument>();
98                    doc.create_head_element(name, attributes, contents);
99                    window.poll();
100                }
101            }
102
103            // Suppress unused variable warning
104            #[cfg(not(all(feature = "hot-reload", debug_assertions)))]
105            #[allow(unreachable_patterns)]
106            _ => {
107                let _ = event_loop;
108                let _ = event;
109            }
110        }
111    }
112}
113
114impl ApplicationHandler for DioxusNativeApplication {
115    #[cfg(target_os = "macos")]
116    fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
117        self.inner.macos_handler()
118    }
119
120    fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
121        self.inner.resumed(event_loop);
122    }
123
124    fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
125        self.inner.suspended(event_loop);
126    }
127
128    fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
129        self.inner.destroy_surfaces(event_loop);
130    }
131
132    fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
133        self.inner.about_to_wait(event_loop);
134    }
135
136    fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
137        #[cfg(feature = "tracing")]
138        tracing::debug!("Injecting document provider into all windows");
139
140        if let Some(config) = self.pending_window.take() {
141            let mut window = View::init(config, event_loop, &self.inner.proxy);
142            let winit_window = Arc::clone(&window.window);
143            let renderer = window.renderer.clone();
144            let window_id = window.window_id();
145            let doc = window.downcast_doc_mut::<DioxusDocument>();
146
147            doc.vdom.in_scope(ScopeId::ROOT, || {
148                let shared: Rc<dyn dioxus_document::Document> = Rc::new(DioxusNativeDocument::new(
149                    self.inner.proxy.clone(),
150                    window_id,
151                ));
152                provide_context(shared);
153                provide_context(self.event_handlers.clone());
154            });
155
156            // Add shell provider
157            let shell_provider = doc.inner.borrow().shell_provider.clone();
158            doc.vdom
159                .in_scope(ScopeId::ROOT, move || provide_context(shell_provider));
160
161            // Add history
162            let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
163            doc.vdom
164                .in_scope(ScopeId::ROOT, move || provide_context(history_provider));
165
166            // Add renderer
167            doc.vdom
168                .in_scope(ScopeId::ROOT, move || provide_context(renderer));
169
170            // Add winit window
171            doc.vdom
172                .in_scope(ScopeId::ROOT, move || provide_context(winit_window));
173
174            // Queue rebuild
175            doc.initial_build();
176
177            // And then request redraw
178            window.request_redraw();
179
180            // todo(jon): we should actually mess with the pending windows instead of passing along the contexts
181            self.inner.windows.insert(window_id, window);
182        }
183
184        self.inner.can_create_surfaces(event_loop);
185    }
186
187    fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
188        self.inner.new_events(event_loop, cause);
189    }
190
191    fn window_event(
192        &mut self,
193        event_loop: &dyn ActiveEventLoop,
194        window_id: WindowId,
195        event: WindowEvent,
196    ) {
197        self.event_handlers
198            .apply_event(window_id, &event, event_loop);
199        self.inner.window_event(event_loop, window_id, event);
200    }
201
202    fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
203        while let Ok(event) = self.inner.event_queue.try_recv() {
204            match event {
205                BlitzShellEvent::Embedder(event) => {
206                    if let Some(event) = event.downcast_ref::<DioxusNativeEvent>() {
207                        self.handle_dioxus_native_event(event_loop, event);
208                    }
209                }
210                event => self.inner.handle_blitz_shell_event(event_loop, event),
211            }
212        }
213    }
214}