dioxus_native/
dioxus_document.rs

1//! Integration between Dioxus and Blitz
2use blitz_dom::Attribute;
3use futures_util::{pin_mut, FutureExt};
4use std::ops::{Deref, DerefMut};
5use std::{any::Any, collections::HashMap, rc::Rc, sync::Arc};
6
7use blitz_dom::{
8    net::Resource, BaseDocument, Document, EventDriver, EventHandler, Node, DEFAULT_CSS,
9};
10use blitz_traits::{
11    events::UiEvent, net::NetProvider, ColorScheme, DomEvent, DomEventData, EventState, Viewport,
12};
13
14use dioxus_core::{ElementId, Event, VirtualDom};
15use dioxus_html::{set_event_converter, PlatformEventData};
16
17use crate::events::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
18use crate::mutation_writer::{DioxusState, MutationWriter};
19use crate::qual_name;
20use crate::NodeId;
21
22fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
23    Rc::new(PlatformEventData::new(Box::new(value)))
24}
25
26/// Get the value of the "dioxus-data-id" attribute parsed aa usize
27fn get_dioxus_id(node: &Node) -> Option<ElementId> {
28    node.element_data()?
29        .attrs
30        .iter()
31        .find(|attr| *attr.name.local == *"data-dioxus-id")
32        .and_then(|attr| attr.value.parse::<usize>().ok())
33        .map(ElementId)
34}
35
36pub struct DioxusDocument {
37    pub(crate) vdom: VirtualDom,
38    pub(crate) vdom_state: DioxusState,
39    pub(crate) inner: BaseDocument,
40
41    #[allow(unused)]
42    pub(crate) html_element_id: NodeId,
43    #[allow(unused)]
44    pub(crate) head_element_id: NodeId,
45    #[allow(unused)]
46    pub(crate) body_element_id: NodeId,
47    #[allow(unused)]
48    pub(crate) main_element_id: NodeId,
49}
50
51impl DioxusDocument {
52    pub fn new(vdom: VirtualDom, net_provider: Option<Arc<dyn NetProvider<Resource>>>) -> Self {
53        let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
54        let mut doc = BaseDocument::new(viewport);
55
56        // Set net provider
57        if let Some(net_provider) = net_provider {
58            doc.set_net_provider(net_provider);
59        }
60
61        // Create some minimal HTML to render the app into.
62
63        // Create the html element
64        let mut mutr = doc.mutate();
65        let html_element_id = mutr.create_element(qual_name("html", None), vec![]);
66        mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);
67
68        // Create the body element
69        let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
70        mutr.append_children(html_element_id, &[head_element_id]);
71
72        // Create the body element
73        let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
74        mutr.append_children(html_element_id, &[body_element_id]);
75
76        // Create another virtual element to hold the root <div id="main"></div> under the html element
77        let main_attr = blitz_dom::Attribute {
78            name: qual_name("id", None),
79            value: "main".to_string(),
80        };
81        let main_element_id = mutr.create_element(qual_name("main", None), vec![main_attr]);
82        mutr.append_children(body_element_id, &[main_element_id]);
83
84        drop(mutr);
85
86        // Include default and user-specified stylesheets
87        doc.add_user_agent_stylesheet(DEFAULT_CSS);
88
89        let vdom_state = DioxusState::create(main_element_id);
90        let mut doc = Self {
91            vdom,
92            vdom_state,
93            inner: doc,
94            html_element_id,
95            head_element_id,
96            body_element_id,
97            main_element_id,
98        };
99
100        doc.inner.set_base_url("dioxus://index.html");
101        //doc.initial_build();
102        doc.inner.print_tree();
103
104        doc
105    }
106
107    pub fn initial_build(&mut self) {
108        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
109        self.vdom.rebuild(&mut writer);
110    }
111
112    pub fn create_head_element(
113        &mut self,
114        name: &str,
115        attributes: &[(String, String)],
116        contents: &Option<String>,
117    ) {
118        let mut mutr = self.inner.mutate();
119
120        let attributes = attributes
121            .iter()
122            .map(|(name, value)| Attribute {
123                name: qual_name(name, None),
124                value: value.clone(),
125            })
126            .collect();
127
128        let new_elem_id = mutr.create_element(qual_name(name, None), attributes);
129        mutr.append_children(self.head_element_id, &[new_elem_id]);
130        if let Some(contents) = contents {
131            mutr.append_text_to_node(new_elem_id, contents).unwrap();
132        }
133
134        // TODO: set title (we may also wish to have this built-in to the document?)
135        // if name == "title" {
136        //     let title = mutr.doc.nodes[new_elem_id].text_content();
137        //
138        // }
139    }
140}
141
142// Implement DocumentLike and required traits for DioxusDocument
143impl Document for DioxusDocument {
144    fn id(&self) -> usize {
145        self.inner.id()
146    }
147
148    fn as_any_mut(&mut self) -> &mut dyn Any {
149        self
150    }
151
152    fn poll(&mut self, mut cx: std::task::Context) -> bool {
153        {
154            let fut = self.vdom.wait_for_work();
155            pin_mut!(fut);
156
157            match fut.poll_unpin(&mut cx) {
158                std::task::Poll::Ready(_) => {}
159                std::task::Poll::Pending => return false,
160            }
161        }
162
163        let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
164        self.vdom.render_immediate(&mut writer);
165
166        true
167    }
168
169    fn handle_event(&mut self, event: UiEvent) {
170        set_event_converter(Box::new(NativeConverter {}));
171        let handler = DioxusEventHandler {
172            vdom: &mut self.vdom,
173            vdom_state: &mut self.vdom_state,
174        };
175        let mut driver = EventDriver::new(self.inner.mutate(), handler);
176        driver.handle_ui_event(event);
177    }
178}
179
180impl Deref for DioxusDocument {
181    type Target = BaseDocument;
182    fn deref(&self) -> &BaseDocument {
183        &self.inner
184    }
185}
186impl DerefMut for DioxusDocument {
187    fn deref_mut(&mut self) -> &mut Self::Target {
188        &mut self.inner
189    }
190}
191impl From<DioxusDocument> for BaseDocument {
192    fn from(doc: DioxusDocument) -> BaseDocument {
193        doc.inner
194    }
195}
196
197pub struct DioxusEventHandler<'v> {
198    vdom: &'v mut VirtualDom,
199    #[allow(dead_code, reason = "WIP")]
200    vdom_state: &'v mut DioxusState,
201}
202
203impl EventHandler for DioxusEventHandler<'_> {
204    fn handle_event(
205        &mut self,
206        chain: &[usize],
207        event: &mut DomEvent,
208        mutr: &mut blitz_dom::DocumentMutator<'_>,
209        event_state: &mut EventState,
210    ) {
211        let event_data = match &event.data {
212            DomEventData::MouseMove(mevent)
213            | DomEventData::MouseDown(mevent)
214            | DomEventData::MouseUp(mevent)
215            | DomEventData::Click(mevent) => Some(wrap_event_data(NativeClickData(mevent.clone()))),
216
217            DomEventData::KeyDown(kevent)
218            | DomEventData::KeyUp(kevent)
219            | DomEventData::KeyPress(kevent) => {
220                Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))
221            }
222
223            DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {
224                value: data.value.clone(),
225                values: HashMap::new(),
226            })),
227
228            // TODO: Implement IME handling
229            DomEventData::Ime(_) => None,
230        };
231
232        let Some(event_data) = event_data else {
233            return;
234        };
235
236        for &node_id in chain {
237            // Get dioxus vdom id for node
238            let dioxus_id = mutr.doc.get_node(node_id).and_then(get_dioxus_id);
239            let Some(id) = dioxus_id else {
240                return;
241            };
242
243            // Handle event in vdom
244            let dx_event = Event::new(event_data.clone(), event.bubbles);
245            self.vdom
246                .runtime()
247                .handle_event(event.name(), dx_event.clone(), id);
248
249            // Update event state
250            if !dx_event.default_action_enabled() {
251                event_state.prevent_default();
252            }
253            if !dx_event.propagates() {
254                event_state.stop_propagation();
255                break;
256            }
257        }
258    }
259}