dioxus_native/
dioxus_application.rs

1use blitz_shell::{BlitzApplication, View};
2use dioxus_core::{provide_context, ScopeId};
3use dioxus_history::{History, MemoryHistory};
4use std::rc::Rc;
5use winit::application::ApplicationHandler;
6use winit::event::{StartCause, WindowEvent};
7use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
8use winit::window::WindowId;
9
10use crate::DioxusNativeWindowRenderer;
11use crate::{contexts::DioxusNativeDocument, BlitzShellEvent, DioxusDocument, WindowConfig};
12
13/// Dioxus-native specific event type
14pub enum DioxusNativeEvent {
15    /// A hotreload event, basically telling us to update our templates.
16    #[cfg(all(
17        feature = "hot-reload",
18        debug_assertions,
19        not(target_os = "android"),
20        not(target_os = "ios")
21    ))]
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    proxy: EventLoopProxy<BlitzShellEvent>,
39}
40
41impl DioxusNativeApplication {
42    pub fn new(
43        proxy: EventLoopProxy<BlitzShellEvent>,
44        config: WindowConfig<DioxusNativeWindowRenderer>,
45    ) -> Self {
46        Self {
47            pending_window: Some(config),
48            inner: BlitzApplication::new(proxy.clone()),
49            proxy,
50        }
51    }
52
53    pub fn add_window(&mut self, window_config: WindowConfig<DioxusNativeWindowRenderer>) {
54        self.inner.add_window(window_config);
55    }
56
57    fn handle_blitz_shell_event(
58        &mut self,
59        event_loop: &ActiveEventLoop,
60        event: &DioxusNativeEvent,
61    ) {
62        match event {
63            #[cfg(all(
64                feature = "hot-reload",
65                debug_assertions,
66                not(target_os = "android"),
67                not(target_os = "ios")
68            ))]
69            DioxusNativeEvent::DevserverEvent(event) => match event {
70                dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
71                    for window in self.inner.windows.values_mut() {
72                        let doc = window.downcast_doc_mut::<DioxusDocument>();
73
74                        // Apply changes to vdom
75                        dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);
76
77                        // Reload changed assets
78                        for asset_path in &hotreload_message.assets {
79                            if let Some(url) = asset_path.to_str() {
80                                doc.reload_resource_by_href(url);
81                            }
82                        }
83
84                        window.poll();
85                    }
86                }
87                dioxus_devtools::DevserverMsg::Shutdown => event_loop.exit(),
88                dioxus_devtools::DevserverMsg::FullReloadStart => {}
89                dioxus_devtools::DevserverMsg::FullReloadFailed => {}
90                dioxus_devtools::DevserverMsg::FullReloadCommand => {}
91                _ => {}
92            },
93
94            DioxusNativeEvent::CreateHeadElement {
95                name,
96                attributes,
97                contents,
98                window,
99            } => {
100                if let Some(window) = self.inner.windows.get_mut(window) {
101                    let doc = window.downcast_doc_mut::<DioxusDocument>();
102                    doc.create_head_element(name, attributes, contents);
103                    window.poll();
104                }
105            }
106
107            // Suppress unused variable warning
108            #[cfg(not(all(
109                feature = "hot-reload",
110                debug_assertions,
111                not(target_os = "android"),
112                not(target_os = "ios")
113            )))]
114            #[allow(unreachable_patterns)]
115            _ => {
116                let _ = event_loop;
117                let _ = event;
118            }
119        }
120    }
121}
122
123impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
124    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
125        #[cfg(feature = "tracing")]
126        tracing::debug!("Injecting document provider into all windows");
127
128        if let Some(config) = self.pending_window.take() {
129            let mut window = View::init(config, event_loop, &self.proxy);
130            let renderer = window.renderer.clone();
131            let window_id = window.window_id();
132            let doc = window.downcast_doc_mut::<DioxusDocument>();
133
134            doc.vdom.in_scope(ScopeId::ROOT, || {
135                let shared: Rc<dyn dioxus_document::Document> =
136                    Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id));
137                provide_context(shared);
138            });
139
140            // Add history
141            let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
142            doc.vdom
143                .in_scope(ScopeId::ROOT, move || provide_context(history_provider));
144
145            // Add renderer
146            doc.vdom
147                .in_scope(ScopeId::ROOT, move || provide_context(renderer));
148
149            // Queue rebuild
150            doc.initial_build();
151
152            // And then request redraw
153            window.request_redraw();
154
155            // todo(jon): we should actually mess with the pending windows instead of passing along the contexts
156            self.inner.windows.insert(window_id, window);
157        }
158
159        self.inner.resumed(event_loop);
160    }
161
162    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
163        self.inner.suspended(event_loop);
164    }
165
166    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
167        self.inner.new_events(event_loop, cause);
168    }
169
170    fn window_event(
171        &mut self,
172        event_loop: &ActiveEventLoop,
173        window_id: WindowId,
174        event: WindowEvent,
175    ) {
176        self.inner.window_event(event_loop, window_id, event);
177    }
178
179    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: BlitzShellEvent) {
180        match event {
181            BlitzShellEvent::Embedder(event) => {
182                if let Some(event) = event.downcast_ref::<DioxusNativeEvent>() {
183                    self.handle_blitz_shell_event(event_loop, event);
184                }
185            }
186            event => self.inner.user_event(event_loop, event),
187        }
188    }
189}