1use 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
26fn 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 if let Some(net_provider) = net_provider {
58 doc.set_net_provider(net_provider);
59 }
60
61 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 let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
70 mutr.append_children(html_element_id, &[head_element_id]);
71
72 let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
74 mutr.append_children(html_element_id, &[body_element_id]);
75
76 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 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.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 }
140}
141
142impl 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 DomEventData::Ime(_) => None,
230 };
231
232 let Some(event_data) = event_data else {
233 return;
234 };
235
236 for &node_id in chain {
237 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 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 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}