use crate::NodeId;
use crate::events::{
BlitzKeyboardData, NativeConverter, NativeFocusData, NativeFormData, NativePointerData,
NativeScrollData, NativeWheelData, NodeHandle,
};
use crate::mutation_writer::{DioxusState, MutationWriter};
use crate::qual_name;
use blitz_dom::{
Attribute, BaseDocument, DEFAULT_CSS, DocGuard, DocGuardMut, Document, DocumentConfig,
EventDriver, EventHandler, Node,
};
use blitz_traits::events::{DomEvent, DomEventData, EventState, UiEvent};
use dioxus_core::{ElementId, Event, VirtualDom};
use dioxus_html::{PlatformEventData, set_event_converter};
use futures_util::task::noop_waker;
use std::cell::RefCell;
use std::future::Future;
use std::mem;
use std::pin::pin;
use std::sync::LazyLock;
use std::task::{Context as TaskContext, Waker};
use std::{any::Any, rc::Rc};
fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
Rc::new(PlatformEventData::new(Box::new(value)))
}
fn get_dioxus_id(node: &Node) -> Option<ElementId> {
node.element_data()?
.attrs
.iter()
.find(|attr| *attr.name.local == *"data-dioxus-id")
.and_then(|attr| attr.value.parse::<usize>().ok())
.map(ElementId)
}
pub struct DioxusDocument {
pub inner: Rc<RefCell<BaseDocument>>,
pub vdom: VirtualDom,
pub vdom_state: DioxusState,
#[allow(unused)]
pub(crate) html_element_id: NodeId,
#[allow(unused)]
pub(crate) head_element_id: NodeId,
#[allow(unused)]
pub(crate) body_element_id: NodeId,
#[allow(unused)]
pub(crate) main_element_id: NodeId,
}
impl DioxusDocument {
pub fn new(vdom: VirtualDom, mut config: DocumentConfig) -> Self {
set_event_converter(Box::new(NativeConverter {}));
config.base_url = Some(
config
.base_url
.unwrap_or_else(|| String::from("dioxus://index.html")),
);
let mut doc = BaseDocument::new(config);
doc.add_user_agent_stylesheet(DEFAULT_CSS);
let mut mutr = doc.mutate();
let html_element_id = mutr.create_element(qual_name("html", None), vec![]);
mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);
let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
mutr.append_children(html_element_id, &[head_element_id]);
let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
mutr.append_children(html_element_id, &[body_element_id]);
let main_attr = blitz_dom::Attribute {
name: qual_name("id", None),
value: "main".to_string(),
};
let main_element_id = mutr.create_element(qual_name("main", None), vec![main_attr]);
mutr.append_children(body_element_id, &[main_element_id]);
drop(mutr);
let vdom_state = DioxusState::create(main_element_id);
Self {
vdom,
vdom_state,
inner: Rc::new(RefCell::new(doc)),
html_element_id,
head_element_id,
body_element_id,
main_element_id,
}
}
pub fn initial_build(&mut self) {
let mut inner = self.inner.borrow_mut();
let mut writer = MutationWriter::new(&mut inner, &mut self.vdom_state);
self.vdom.rebuild(&mut writer);
drop(writer);
drop(inner);
self.flush_queued_mounted_events();
}
#[doc(hidden)]
pub fn create_head_element(
&mut self,
name: &str,
attributes: &[(String, String)],
contents: &Option<String>,
) {
let mut inner = self.inner.borrow_mut();
let mut mutr = inner.mutate();
let attributes = attributes
.iter()
.map(|(name, value)| Attribute {
name: qual_name(name, None),
value: value.clone(),
})
.collect();
let new_elem_id = mutr.create_element(qual_name(name, None), attributes);
mutr.append_children(self.head_element_id, &[new_elem_id]);
if let Some(contents) = contents {
let text_node_id = mutr.create_text_node(contents);
mutr.append_children(new_elem_id, &[text_node_id]);
}
}
pub(crate) fn flush_queued_mounted_events(&mut self) {
let mut queued_mounted_events = mem::take(&mut self.vdom_state.queued_mounted_events);
for element_id in queued_mounted_events.drain(..) {
let node_id = self.vdom_state.element_to_node_id(element_id);
if self.inner.borrow().get_node(node_id).is_some() {
let event = Event::new(
Rc::new(PlatformEventData::new(Box::new(NodeHandle {
doc: Rc::clone(&self.inner),
node_id,
}))) as Rc<dyn Any>,
false,
);
self.vdom
.runtime()
.handle_event("mounted", event, element_id);
}
}
self.vdom_state.queued_mounted_events = queued_mounted_events;
}
}
impl Document for DioxusDocument {
fn id(&self) -> usize {
self.inner.borrow().id()
}
fn inner(&self) -> DocGuard<'_> {
DocGuard::RefCell(self.inner.borrow())
}
fn inner_mut(&mut self) -> DocGuardMut<'_> {
DocGuardMut::RefCell(self.inner.borrow_mut())
}
fn poll(&mut self, cx: Option<TaskContext>) -> bool {
{
let fut = self.vdom.wait_for_work();
let mut pinned_fut = pin!(fut);
static NOOP_WAKER: LazyLock<Waker> = LazyLock::new(noop_waker);
let mut cx = cx.unwrap_or_else(|| TaskContext::from_waker(&NOOP_WAKER));
match pinned_fut.as_mut().poll(&mut cx) {
std::task::Poll::Ready(_) => {}
std::task::Poll::Pending => return false,
}
}
let mut inner = self.inner.borrow_mut();
let mut writer = MutationWriter::new(&mut inner, &mut self.vdom_state);
self.vdom.render_immediate(&mut writer);
drop(writer);
drop(inner);
self.flush_queued_mounted_events();
true
}
fn handle_ui_event(&mut self, event: UiEvent) {
let handler = DioxusEventHandler {
vdom: &mut self.vdom,
vdom_state: &mut self.vdom_state,
};
let mut driver = EventDriver::new(&mut self.inner, handler);
driver.handle_ui_event(event);
}
}
pub struct DioxusEventHandler<'v> {
vdom: &'v mut VirtualDom,
vdom_state: &'v mut DioxusState,
}
impl EventHandler for DioxusEventHandler<'_> {
fn handle_event(
&mut self,
chain: &[usize],
event: &mut DomEvent,
doc: &mut dyn Document,
event_state: &mut EventState,
) {
let event_kind_idx = event.data.discriminant() as usize;
let event_kind_count = self.vdom_state.event_handler_counts[event_kind_idx];
if event_kind_count == 0 {
return;
}
let event_data = match &event.data {
DomEventData::PointerMove(mevent)
| DomEventData::PointerDown(mevent)
| DomEventData::PointerUp(mevent)
| DomEventData::PointerLeave(mevent)
| DomEventData::PointerEnter(mevent)
| DomEventData::PointerOver(mevent)
| DomEventData::PointerOut(mevent)
| DomEventData::MouseMove(mevent)
| DomEventData::MouseDown(mevent)
| DomEventData::MouseUp(mevent)
| DomEventData::MouseLeave(mevent)
| DomEventData::MouseEnter(mevent)
| DomEventData::MouseOver(mevent)
| DomEventData::MouseOut(mevent)
| DomEventData::Click(mevent)
| DomEventData::ContextMenu(mevent)
| DomEventData::DoubleClick(mevent) => {
Some(wrap_event_data(NativePointerData(mevent.clone())))
}
DomEventData::Scroll(sevent) => Some(wrap_event_data(NativeScrollData(sevent.clone()))),
DomEventData::Wheel(wevent) => Some(wrap_event_data(NativeWheelData(wevent.clone()))),
DomEventData::Focus(_) => Some(wrap_event_data(NativeFocusData)),
DomEventData::Blur(_) => Some(wrap_event_data(NativeFocusData)),
DomEventData::FocusIn(_) => Some(wrap_event_data(NativeFocusData)),
DomEventData::FocusOut(_) => Some(wrap_event_data(NativeFocusData)),
DomEventData::KeyDown(kevent)
| DomEventData::KeyUp(kevent)
| DomEventData::KeyPress(kevent) => {
Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))
}
DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {
value: data.value.clone(),
values: vec![],
})),
DomEventData::Ime(_) => None,
DomEventData::AppleStandardKeybinding(_) => None,
};
let Some(event_data) = event_data else {
return;
};
for &node_id in chain {
let dioxus_id = doc.inner().get_node(node_id).and_then(get_dioxus_id);
let Some(id) = dioxus_id else {
continue;
};
let dx_event = Event::new(event_data.clone(), event.bubbles);
self.vdom
.runtime()
.handle_event(event.name(), dx_event.clone(), id);
if !dx_event.default_action_enabled() {
event_state.prevent_default();
}
if !dx_event.propagates() {
event_state.stop_propagation();
break;
}
}
}
}