dioxus_native/
dioxus_application.rs

1use anyrender_vello::VelloWindowRenderer;
2use blitz_shell::BlitzApplication;
3use dioxus_core::{ScopeId, VirtualDom};
4use dioxus_history::{History, MemoryHistory};
5use std::{collections::HashSet, rc::Rc};
6use winit::application::ApplicationHandler;
7use winit::event::{StartCause, WindowEvent};
8use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
9use winit::window::WindowId;
10
11use crate::{
12    assets::DioxusNativeNetProvider, contexts::DioxusNativeDocument,
13    mutation_writer::MutationWriter, BlitzShellEvent, DioxusDocument, WindowConfig,
14};
15
16/// Dioxus-native specific event type
17pub enum DioxusNativeEvent {
18    /// A hotreload event, basically telling us to update our templates.
19    #[cfg(all(
20        feature = "hot-reload",
21        debug_assertions,
22        not(target_os = "android"),
23        not(target_os = "ios")
24    ))]
25    DevserverEvent(dioxus_devtools::DevserverMsg),
26
27    /// Create a new head element from the Link and Title elements
28    ///
29    /// todo(jon): these should probabkly be synchronous somehow
30    CreateHeadElement {
31        window: WindowId,
32        name: String,
33        attributes: Vec<(String, String)>,
34        contents: Option<String>,
35    },
36}
37
38pub struct DioxusNativeApplication {
39    pending_vdom: Option<VirtualDom>,
40    inner: BlitzApplication<VelloWindowRenderer>,
41    proxy: EventLoopProxy<BlitzShellEvent>,
42}
43
44impl DioxusNativeApplication {
45    pub fn new(proxy: EventLoopProxy<BlitzShellEvent>, vdom: VirtualDom) -> Self {
46        Self {
47            pending_vdom: Some(vdom),
48            inner: BlitzApplication::new(proxy.clone()),
49            proxy,
50        }
51    }
52
53    pub fn add_window(&mut self, window_config: WindowConfig<VelloWindowRenderer>) {
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                        dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);
74                        window.poll();
75                    }
76                }
77                dioxus_devtools::DevserverMsg::Shutdown => event_loop.exit(),
78                dioxus_devtools::DevserverMsg::FullReloadStart => {}
79                dioxus_devtools::DevserverMsg::FullReloadFailed => {}
80                dioxus_devtools::DevserverMsg::FullReloadCommand => {}
81                _ => {}
82            },
83
84            DioxusNativeEvent::CreateHeadElement {
85                name,
86                attributes,
87                contents,
88                window,
89            } => {
90                if let Some(window) = self.inner.windows.get_mut(window) {
91                    let doc = window.downcast_doc_mut::<DioxusDocument>();
92                    doc.create_head_element(name, attributes, contents);
93                    window.poll();
94                }
95            }
96
97            // Suppress unused variable warning
98            #[cfg(not(all(
99                feature = "hot-reload",
100                debug_assertions,
101                not(target_os = "android"),
102                not(target_os = "ios")
103            )))]
104            _ => {
105                let _ = event_loop;
106                let _ = event;
107            }
108        }
109    }
110}
111
112impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
113    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
114        tracing::debug!("Injecting document provider into all windows");
115        let vdom = self.pending_vdom.take().unwrap();
116
117        #[cfg(feature = "net")]
118        let net_provider = {
119            let proxy = self.proxy.clone();
120            let net_provider = DioxusNativeNetProvider::shared(proxy);
121            Some(net_provider)
122        };
123
124        #[cfg(not(feature = "net"))]
125        let net_provider = None;
126
127        // Create document + window from the baked virtualdom
128        let doc = DioxusDocument::new(vdom, net_provider);
129        let window = WindowConfig::new(Box::new(doc) as _);
130
131        // little hack since View::init is not public - fix this once alpha-2 is out
132        let old_windows = self.inner.windows.keys().copied().collect::<HashSet<_>>();
133        self.add_window(window);
134        self.inner.resumed(event_loop);
135        let new_windows = self.inner.windows.keys().cloned().collect::<HashSet<_>>();
136
137        // todo(jon): we should actually mess with the pending windows instead of passing along the contexts
138        for window_id in new_windows.difference(&old_windows) {
139            let window = self.inner.windows.get_mut(window_id).unwrap();
140            let doc = window.downcast_doc_mut::<DioxusDocument>();
141            doc.vdom.in_runtime(|| {
142                let shared: Rc<dyn dioxus_document::Document> =
143                    Rc::new(DioxusNativeDocument::new(self.proxy.clone(), *window_id));
144                ScopeId::ROOT.provide_context(shared);
145            });
146
147            // Add history
148            let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
149            doc.vdom
150                .in_runtime(|| ScopeId::ROOT.provide_context(history_provider));
151
152            // Queue rebuild
153            let mut writer = MutationWriter::new(&mut doc.inner, &mut doc.vdom_state);
154            doc.vdom.rebuild(&mut writer);
155            drop(writer);
156
157            // And then request redraw
158            window.request_redraw();
159        }
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}