Upgrade to Blitz 0.1.0-alpha.2 (#4227)

* Update to use latest Blitz net traits

* Pass url to flush_style_attribute

* Add system-fonts feature to dioxus-native

* Native: cleanup log output

* Make dioxus-native work with Blitz simplified Document and Renderer traits

* Native: upgrade to event refactor

* Upgrade to blitz 0.1.0-alpha.2

* Add typo exception

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Ensure root_id is set to <main> element

---------

Signed-off-by: Nico Burns <nico@nicoburns.com>
This commit is contained in:
Nico Burns authored and GitHub committed 2025-05-31 13:52:19 +02:00
1 parent e1bd06bda5
commit 51d7affc54
10 files changed
+654 -1222

No files matched your search

+7 -38
View File
@@ -1,24 +1,18 @@
use blitz_shell::BlitzShellNetCallback;
use std::sync::Arc;
use blitz_dom::{
net::{CssHandler, ImageHandler, Resource},
util::ImageType,
BaseDocument,
};
use blitz_dom::net::Resource;
use blitz_shell::BlitzShellEvent;
use blitz_traits::net::{NetCallback, NetProvider};
use winit::event_loop::EventLoopProxy;
use crate::NodeId;
pub struct DioxusNativeNetProvider {
callback: Arc<dyn NetCallback<Data = Resource> + 'static>,
inner_net_provider: Arc<dyn NetProvider<Data = Resource> + 'static>,
callback: Arc<dyn NetCallback<Resource> + 'static>,
inner_net_provider: Arc<dyn NetProvider<Resource> + 'static>,
}
impl DioxusNativeNetProvider {
pub fn shared(proxy: EventLoopProxy<BlitzShellEvent>) -> Arc<dyn NetProvider<Data = Resource>> {
Arc::new(Self::new(proxy)) as Arc<dyn NetProvider<Data = Resource>>
pub fn shared(proxy: EventLoopProxy<BlitzShellEvent>) -> Arc<dyn NetProvider<Resource>> {
Arc::new(Self::new(proxy)) as Arc<dyn NetProvider<Resource>>
}
pub fn new(proxy: EventLoopProxy<BlitzShellEvent>) -> Self {
@@ -31,14 +25,12 @@ impl DioxusNativeNetProvider {
}
}
impl NetProvider for DioxusNativeNetProvider {
type Data = Resource;
impl NetProvider<Resource> for DioxusNativeNetProvider {
fn fetch(
&self,
doc_id: usize,
request: blitz_traits::net::Request,
handler: blitz_traits::net::BoxedHandler<Self::Data>,
handler: blitz_traits::net::BoxedHandler<Resource>,
) {
if request.url.scheme() == "dioxus" {
match dioxus_asset_resolver::serve_asset_from_raw_path(request.url.path()) {
@@ -55,26 +47,3 @@ impl NetProvider for DioxusNativeNetProvider {
}
}
}
pub fn fetch_linked_stylesheet(doc: &BaseDocument, node_id: NodeId, queued_url: String) {
let url = doc.resolve_url(&queued_url);
doc.net_provider.fetch(
doc.id(),
blitz_traits::net::Request::get(url.clone()),
Box::new(CssHandler {
node: node_id,
source_url: url,
guard: doc.guard.clone(),
provider: doc.net_provider.clone(),
}),
);
}
pub fn fetch_image(doc: &BaseDocument, node_id: usize, queued_image: String) {
let src = doc.resolve_url(&queued_image);
doc.net_provider.fetch(
doc.id(),
blitz_traits::net::Request::get(src),
Box::new(ImageHandler::new(node_id, ImageType::Image)),
);
}
+36 -14
View File
@@ -1,4 +1,4 @@
use blitz_renderer_vello::BlitzVelloRenderer;
use anyrender_vello::VelloWindowRenderer;
use blitz_shell::BlitzApplication;
use dioxus_core::{ScopeId, VirtualDom};
use dioxus_history::{History, MemoryHistory};
@@ -10,13 +10,34 @@ use winit::window::WindowId;
use crate::{
assets::DioxusNativeNetProvider, contexts::DioxusNativeDocument,
mutation_writer::MutationWriter, BlitzShellEvent, DioxusDocument, DioxusNativeEvent,
WindowConfig,
mutation_writer::MutationWriter, BlitzShellEvent, DioxusDocument, WindowConfig,
};
/// Dioxus-native specific event type
pub enum DioxusNativeEvent {
/// A hotreload event, basically telling us to update our templates.
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
DevserverEvent(dioxus_devtools::DevserverMsg),
/// Create a new head element from the Link and Title elements
///
/// todo(jon): these should probabkly be synchronous somehow
CreateHeadElement {
window: WindowId,
name: String,
attributes: Vec<(String, String)>,
contents: Option<String>,
},
}
pub struct DioxusNativeApplication {
pending_vdom: Option<VirtualDom>,
inner: BlitzApplication<DioxusDocument, BlitzVelloRenderer>,
inner: BlitzApplication<VelloWindowRenderer>,
proxy: EventLoopProxy<BlitzShellEvent>,
}
@@ -29,7 +50,7 @@ impl DioxusNativeApplication {
}
}
pub fn add_window(&mut self, window_config: WindowConfig<DioxusDocument, BlitzVelloRenderer>) {
pub fn add_window(&mut self, window_config: WindowConfig<VelloWindowRenderer>) {
self.inner.add_window(window_config);
}
@@ -48,7 +69,8 @@ impl DioxusNativeApplication {
DioxusNativeEvent::DevserverEvent(event) => match event {
dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => {
for window in self.inner.windows.values_mut() {
dioxus_devtools::apply_changes(&window.doc.vdom, hotreload_message);
let doc = window.downcast_doc_mut::<DioxusDocument>();
dioxus_devtools::apply_changes(&doc.vdom, hotreload_message);
window.poll();
}
}
@@ -66,7 +88,8 @@ impl DioxusNativeApplication {
window,
} => {
if let Some(window) = self.inner.windows.get_mut(window) {
window.doc.create_head_element(name, attributes, contents);
let doc = window.downcast_doc_mut::<DioxusDocument>();
doc.create_head_element(name, attributes, contents);
window.poll();
}
}
@@ -103,7 +126,7 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
// Create document + window from the baked virtualdom
let doc = DioxusDocument::new(vdom, net_provider);
let window = WindowConfig::new(doc);
let window = WindowConfig::new(Box::new(doc) as _);
// little hack since View::init is not public - fix this once alpha-2 is out
let old_windows = self.inner.windows.keys().copied().collect::<HashSet<_>>();
@@ -114,7 +137,8 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
// todo(jon): we should actually mess with the pending windows instead of passing along the contexts
for window_id in new_windows.difference(&old_windows) {
let window = self.inner.windows.get_mut(window_id).unwrap();
window.doc.vdom.in_runtime(|| {
let doc = window.downcast_doc_mut::<DioxusDocument>();
doc.vdom.in_runtime(|| {
let shared: Rc<dyn dioxus_document::Document> =
Rc::new(DioxusNativeDocument::new(self.proxy.clone(), *window_id));
ScopeId::ROOT.provide_context(shared);
@@ -122,14 +146,12 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
// Add history
let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
window
.doc
.vdom
doc.vdom
.in_runtime(|| ScopeId::ROOT.provide_context(history_provider));
// Queue rebuild
let mut writer = MutationWriter::new(&mut window.doc.inner, &mut window.doc.vdom_state);
window.doc.vdom.rebuild(&mut writer);
let mut writer = MutationWriter::new(&mut doc.inner, &mut doc.vdom_state);
doc.vdom.rebuild(&mut writer);
drop(writer);
// And then request redraw
+194 -499
View File
@@ -1,29 +1,36 @@
//! Integration between Dioxus and Blitz
use blitz_dom::Attribute;
use futures_util::{pin_mut, FutureExt};
use std::ops::{Deref, DerefMut};
use std::{any::Any, collections::HashMap, rc::Rc, sync::Arc};
use blitz_dom::{
local_name, namespace_url,
net::Resource,
node::{Attribute, NodeSpecificData},
ns, Atom, BaseDocument, ElementNodeData, Node, NodeData, QualName, DEFAULT_CSS,
net::Resource, BaseDocument, Document, EventDriver, EventHandler, Node, DEFAULT_CSS,
};
use blitz_traits::{
events::UiEvent, net::NetProvider, ColorScheme, DomEvent, DomEventData, EventState, Viewport,
};
use blitz_traits::{net::NetProvider, ColorScheme, Document, DomEvent, DomEventData, Viewport};
use dioxus_core::{ElementId, Event, VirtualDom};
use dioxus_html::{set_event_converter, FormValue, PlatformEventData};
use futures_util::{pin_mut, FutureExt};
use dioxus_html::{set_event_converter, PlatformEventData};
use super::event_handler::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
use crate::events::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
use crate::mutation_writer::{DioxusState, MutationWriter};
use crate::qual_name;
use crate::NodeId;
pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
QualName {
prefix: None,
ns: namespace.map(Atom::from).unwrap_or(ns!(html)),
local: Atom::from(local_name),
}
fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
Rc::new(PlatformEventData::new(Box::new(value)))
}
/// Get the value of the "dioxus-data-id" attribute parsed aa usize
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 {
@@ -41,25 +48,106 @@ pub struct DioxusDocument {
pub(crate) main_element_id: NodeId,
}
// Implement DocumentLike and required traits for DioxusDocument
impl DioxusDocument {
pub fn new(vdom: VirtualDom, net_provider: Option<Arc<dyn NetProvider<Resource>>>) -> Self {
let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
let mut doc = BaseDocument::new(viewport);
impl AsRef<BaseDocument> for DioxusDocument {
fn as_ref(&self) -> &BaseDocument {
&self.inner
// Set net provider
if let Some(net_provider) = net_provider {
doc.set_net_provider(net_provider);
}
// Create some minimal HTML to render the app into.
// Create the html element
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]);
// Create the body element
let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
mutr.append_children(html_element_id, &[head_element_id]);
// Create the body element
let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
mutr.append_children(html_element_id, &[body_element_id]);
// Create another virtual element to hold the root <div id="main"></div> under the html element
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);
// Include default and user-specified stylesheets
doc.add_user_agent_stylesheet(DEFAULT_CSS);
let vdom_state = DioxusState::create(main_element_id);
let mut doc = Self {
vdom,
vdom_state,
inner: doc,
html_element_id,
head_element_id,
body_element_id,
main_element_id,
};
doc.inner.set_base_url("dioxus://index.html");
//doc.initial_build();
doc.inner.print_tree();
doc
}
}
impl AsMut<BaseDocument> for DioxusDocument {
fn as_mut(&mut self) -> &mut BaseDocument {
&mut self.inner
pub fn initial_build(&mut self) {
let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
self.vdom.rebuild(&mut writer);
}
}
impl From<DioxusDocument> for BaseDocument {
fn from(doc: DioxusDocument) -> BaseDocument {
doc.inner
pub fn create_head_element(
&mut self,
name: &str,
attributes: &[(String, String)],
contents: &Option<String>,
) {
let mut mutr = self.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 {
mutr.append_text_to_node(new_elem_id, contents).unwrap();
}
// TODO: set title (we may also wish to have this built-in to the document?)
// if name == "title" {
// let title = mutr.doc.nodes[new_elem_id].text_content();
//
// }
}
}
// Implement DocumentLike and required traits for DioxusDocument
impl Document for DioxusDocument {
type Doc = BaseDocument;
fn id(&self) -> usize {
self.inner.id()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn poll(&mut self, mut cx: std::task::Context) -> bool {
{
@@ -78,487 +166,94 @@ impl Document for DioxusDocument {
true
}
fn id(&self) -> usize {
self.inner.id()
}
fn handle_event(&mut self, event: &mut DomEvent) {
let chain = self.inner.node_chain(event.target);
fn handle_event(&mut self, event: UiEvent) {
set_event_converter(Box::new(NativeConverter {}));
let renderer_event = event.clone();
let mut prevent_default = false;
let mut stop_propagation = false;
match &event.data {
DomEventData::MouseMove(data)
| DomEventData::MouseDown(data)
| DomEventData::MouseUp(data) => {
let click_event_data = wrap_event_data(NativeClickData(data.clone()));
for node_id in chain.clone().into_iter() {
let node = &self.inner.tree()[node_id];
let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
if let Some(id) = dioxus_id {
let click_event = Event::new(click_event_data.clone(), true);
self.vdom
.runtime()
.handle_event(event.name(), click_event.clone(), id);
prevent_default |= !click_event.default_action_enabled();
stop_propagation |= !click_event.propagates();
}
if !event.bubbles || stop_propagation {
break;
}
}
}
DomEventData::Click(data) => {
let click_event_data = wrap_event_data(NativeClickData(data.clone()));
for node_id in chain.clone().into_iter() {
let node = &self.inner.tree()[node_id];
let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
let mut trigger_label = false;
if let Some(id) = dioxus_id {
// Trigger click event
let click_event = Event::new(click_event_data.clone(), true);
self.vdom
.runtime()
.handle_event("click", click_event.clone(), id);
prevent_default |= !click_event.default_action_enabled();
stop_propagation |= !click_event.propagates();
if !prevent_default {
let mut default_event =
DomEvent::new(node_id, renderer_event.data.clone());
self.inner.as_mut().handle_event(&mut default_event);
prevent_default = true;
let element = self.inner.tree()[node_id].element_data().unwrap();
trigger_label = element.name.local == *"label";
//TODO Check for other inputs which trigger input event on click here, eg radio
let triggers_input_event = element.name.local == local_name!("input")
&& element.attr(local_name!("type")) == Some("checkbox");
if triggers_input_event {
let form_data =
wrap_event_data(self.input_event_form_data(&chain, element));
let input_event = Event::new(form_data, true);
self.vdom.runtime().handle_event("input", input_event, id);
}
}
}
//Clicking labels triggers click, and possibly input event, of bound input
if trigger_label {
if let Some((dioxus_id, node_id)) = self.label_bound_input_element(node_id)
{
let click_event = Event::new(click_event_data.clone(), true);
self.vdom.runtime().handle_event(
"click",
click_event.clone(),
dioxus_id,
);
// Handle default DOM event
if click_event.default_action_enabled() {
let DomEventData::Click(event) = &renderer_event.data else {
unreachable!();
};
let input_click_data = self
.inner
.get_node(node_id)
.unwrap()
.synthetic_click_event(event.mods);
let mut default_event = DomEvent::new(node_id, input_click_data);
self.inner.as_mut().handle_event(&mut default_event);
prevent_default = true;
// TODO: Generated click events should bubble separately
// prevent_default |= !click_event.default_action_enabled();
//TODO Check for other inputs which trigger input event on click here, eg radio
let element_data = self
.inner
.get_node(node_id)
.unwrap()
.element_data()
.unwrap();
let triggers_input_event =
element_data.attr(local_name!("type")) == Some("checkbox");
let form_data = wrap_event_data(
self.input_event_form_data(&chain, element_data),
);
if triggers_input_event {
let input_event = Event::new(form_data, true);
self.vdom.runtime().handle_event(
"input",
input_event,
dioxus_id,
);
}
}
}
}
if !event.bubbles || stop_propagation {
break;
}
}
}
DomEventData::KeyPress(kevent) => {
let key_event_data = wrap_event_data(BlitzKeyboardData(kevent.clone()));
for node_id in chain.clone().into_iter() {
let node = &self.inner.tree()[node_id];
let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
println!("{} {:?}", node_id, dioxus_id);
if let Some(id) = dioxus_id {
if kevent.state.is_pressed() {
// Handle keydown event
let event = Event::new(key_event_data.clone(), true);
self.vdom
.runtime()
.handle_event("keydown", event.clone(), id);
prevent_default |= !event.default_action_enabled();
stop_propagation |= !event.propagates();
if !prevent_default && kevent.text.is_some() {
// Handle keypress event
let event = Event::new(key_event_data.clone(), true);
self.vdom
.runtime()
.handle_event("keypress", event.clone(), id);
prevent_default |= !event.default_action_enabled();
stop_propagation |= !event.propagates();
if !prevent_default {
// Handle default DOM event
let mut default_event =
DomEvent::new(node_id, renderer_event.data.clone());
self.inner.as_mut().handle_event(&mut default_event);
prevent_default = true;
// Handle input event
let element =
self.inner.tree()[node_id].element_data().unwrap();
let triggers_input_event = &element.name.local == "input"
&& matches!(
element.attr(local_name!("type")),
None | Some("text" | "password" | "email" | "search")
);
if triggers_input_event {
let form_data = wrap_event_data(dbg!(
self.input_event_form_data(&chain, element)
));
let input_event = Event::new(form_data, true);
self.vdom.runtime().handle_event("input", input_event, id);
}
}
}
} else {
// Handle keyup event
let event = Event::new(key_event_data.clone(), true);
self.vdom.runtime().handle_event("keyup", event.clone(), id);
prevent_default |= !event.default_action_enabled();
stop_propagation |= !event.propagates();
}
}
if !event.bubbles || stop_propagation {
break;
}
}
}
DomEventData::Hover => {}
// TODO: Implement IME and Hover events handling
DomEventData::Ime(_) => {}
}
if !event.cancelable || !prevent_default {
self.inner.as_mut().handle_event(event);
}
let handler = DioxusEventHandler {
vdom: &mut self.vdom,
vdom_state: &mut self.vdom_state,
};
let mut driver = EventDriver::new(self.inner.mutate(), handler);
driver.handle_ui_event(event);
}
}
fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
Rc::new(PlatformEventData::new(Box::new(value)))
impl Deref for DioxusDocument {
type Target = BaseDocument;
fn deref(&self) -> &BaseDocument {
&self.inner
}
}
impl DerefMut for DioxusDocument {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl From<DioxusDocument> for BaseDocument {
fn from(doc: DioxusDocument) -> BaseDocument {
doc.inner
}
}
impl DioxusDocument {
/// Generate the FormData from an input event
/// Currently only cares about input checkboxes
pub fn input_event_form_data(
&self,
parent_chain: &[usize],
element_node_data: &ElementNodeData,
) -> NativeFormData {
let parent_form = parent_chain.iter().find_map(|id| {
let node = self.inner.get_node(*id)?;
let element_data = node.element_data()?;
if element_data.name.local == local_name!("form") {
Some(node)
} else {
None
}
});
let values = if let Some(parent_form) = parent_form {
let mut values = HashMap::<String, FormValue>::new();
for form_input in self.input_descendents(parent_form).into_iter() {
// Match html behaviour here. To be included in values:
// - input must have a name
// - if its an input, we only include it if checked
// - if value is not specified, it defaults to 'on'
if let Some(name) = form_input.attr(local_name!("name")) {
if form_input.attr(local_name!("type")) == Some("checkbox")
&& form_input
.element_data()
.and_then(|data| data.checkbox_input_checked())
.unwrap_or(false)
{
let value = form_input
.attr(local_name!("value"))
.unwrap_or("on")
.to_string();
values.insert(name.to_string(), FormValue(vec![value]));
}
}
}
values
} else {
Default::default()
};
let value = match &element_node_data.node_specific_data {
NodeSpecificData::CheckboxInput(checked) => checked.to_string(),
NodeSpecificData::TextInput(input_data) => input_data.editor.text().to_string(),
_ => element_node_data
.attr(local_name!("value"))
.unwrap_or_default()
.to_string(),
};
pub struct DioxusEventHandler<'v> {
vdom: &'v mut VirtualDom,
#[allow(dead_code, reason = "WIP")]
vdom_state: &'v mut DioxusState,
}
NativeFormData { value, values }
}
/// Collect all the inputs which are descendents of a given node
fn input_descendents(&self, node: &Node) -> Vec<&Node> {
node.children
.iter()
.flat_map(|id| {
let mut res = Vec::<&Node>::new();
let Some(n) = self.inner.get_node(*id) else {
return res;
};
let Some(element_data) = n.element_data() else {
return res;
};
if element_data.name.local == local_name!("input") {
res.push(n);
}
res.extend(self.input_descendents(n).iter());
res
})
.collect()
}
pub fn new(
vdom: VirtualDom,
net_provider: Option<Arc<dyn NetProvider<Data = Resource>>>,
) -> Self {
let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
let mut doc = BaseDocument::new(viewport);
// Set net provider
if let Some(net_provider) = net_provider {
doc.set_net_provider(net_provider);
}
// Create a virtual "html" element to act as the root element, as we won't necessarily
// have a single root otherwise, while the rest of blitz requires that we do
let html_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
qual_name("html", None),
Vec::new(),
)));
let root_node_id = doc.root_node().id;
let html_element = doc.get_node_mut(html_element_id).unwrap();
html_element.parent = Some(root_node_id);
let root_node = doc.get_node_mut(root_node_id).unwrap();
root_node.children.push(html_element_id);
// Create the body element
let body_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
qual_name("body", None),
Vec::new(),
)));
let html_element = doc.get_node_mut(html_element_id).unwrap();
html_element.children.push(body_element_id);
let body_element = doc.get_node_mut(body_element_id).unwrap();
body_element.parent = Some(html_element_id);
// Create another virtual element to hold the root <div id="main"></div> under the html element
let main_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
qual_name("div", Some("id")),
vec![blitz_dom::node::Attribute {
name: qual_name("id", None),
value: "main".to_string(),
}],
)));
let body_element = doc.get_node_mut(body_element_id).unwrap();
body_element.children.push(main_element_id);
let main_element = doc.get_node_mut(main_element_id).unwrap();
main_element.parent = Some(body_element_id);
// Create the head element
let head_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
qual_name("head", None),
Vec::new(),
)));
let head_element = doc.get_node_mut(head_element_id).unwrap();
head_element.parent = Some(html_element_id);
let html_element = doc.get_node_mut(html_element_id).unwrap();
html_element.children.push(head_element_id);
// Include default and user-specified stylesheets
doc.add_user_agent_stylesheet(DEFAULT_CSS);
let state = DioxusState::create(&mut doc, main_element_id);
let mut doc = Self {
vdom,
vdom_state: state,
inner: doc,
html_element_id,
head_element_id,
body_element_id,
main_element_id,
};
doc.inner.set_base_url("dioxus://index.html");
// doc.initial_build();
// doc.inner.print_tree();
doc
}
pub fn initial_build(&mut self) {
// let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
// self.vdom.rebuild(&mut writer);
// dbg!(self.vdom.rebuild_to_vec());
// std::process::exit(0);
// dbg!(writer.state);
}
pub fn label_bound_input_element(&self, label_node_id: NodeId) -> Option<(ElementId, NodeId)> {
let bound_input_elements = self.inner.label_bound_input_elements(label_node_id);
// Filter down bound elements to those which have dioxus id
bound_input_elements.into_iter().find_map(|n| {
let target_element_data = n.element_data()?;
let node_id = n.id;
let dioxus_id = DioxusDocument::dioxus_id(target_element_data)?;
Some((dioxus_id, node_id))
})
}
fn dioxus_id(element_node_data: &ElementNodeData) -> Option<ElementId> {
Some(ElementId(
element_node_data
.attrs
.iter()
.find(|attr| *attr.name.local == *"data-dioxus-id")?
.value
.parse::<usize>()
.ok()?,
))
}
pub fn create_head_element(
impl EventHandler for DioxusEventHandler<'_> {
fn handle_event(
&mut self,
name: &str,
attributes: &[(String, String)],
contents: &Option<String>,
chain: &[usize],
event: &mut DomEvent,
mutr: &mut blitz_dom::DocumentMutator<'_>,
event_state: &mut EventState,
) {
let mut stylesheet = None;
let mut title = None;
if name == "link" {
let is_stylesheet = attributes
.iter()
.any(|(name, value)| name == "rel" && value == "stylesheet");
if is_stylesheet {
stylesheet = attributes
.iter()
.find(|(name, _value)| name == "href")
.map(|(_name, value)| value.clone());
let event_data = match &event.data {
DomEventData::MouseMove(mevent)
| DomEventData::MouseDown(mevent)
| DomEventData::MouseUp(mevent)
| DomEventData::Click(mevent) => Some(wrap_event_data(NativeClickData(mevent.clone()))),
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: HashMap::new(),
})),
// TODO: Implement IME handling
DomEventData::Ime(_) => None,
};
let Some(event_data) = event_data else {
return;
};
for &node_id in chain {
// Get dioxus vdom id for node
let dioxus_id = mutr.doc.get_node(node_id).and_then(get_dioxus_id);
let Some(id) = dioxus_id else {
return;
};
// Handle event in vdom
let dx_event = Event::new(event_data.clone(), event.bubbles);
self.vdom
.runtime()
.handle_event(event.name(), dx_event.clone(), id);
// Update event state
if !dx_event.default_action_enabled() {
event_state.prevent_default();
}
if !dx_event.propagates() {
event_state.stop_propagation();
break;
}
}
if name == "title" {
title = attributes
.iter()
.find(|(name, _value)| name == "text")
.and_then(|(_name, _value)| contents.clone());
}
let attributes = attributes
.iter()
.map(|(name, value)| Attribute {
name: qual_name(name, None),
value: value.clone(),
})
.collect();
let new_element = self
.inner
.create_node(NodeData::Element(ElementNodeData::new(
qual_name(name, None),
attributes,
)));
if let Some(contents) = contents {
let text_node = self.inner.create_text_node(contents);
self.inner
.get_node_mut(new_element)
.unwrap()
.children
.push(text_node);
}
self.inner
.get_node_mut(self.head_element_id)
.unwrap()
.children
.push(new_element);
if let Some(url) = stylesheet {
crate::assets::fetch_linked_stylesheet(&self.inner, new_element, url);
}
if let Some(_title) = title {
println!("todo: set title");
}
}
// pub fn apply_mutations(&mut self) {
// // Apply the mutations to the actual dom
// let mut writer = MutationWriter {
// doc: &mut self.inner,
// state: &mut self.vdom_state,
// };
// self.vdom.render_immediate(&mut writer);
// println!("APPLY MUTATIONS");
// self.inner.print_tree();
// }
}
-23
View File
@@ -1,23 +0,0 @@
use winit::window::WindowId;
/// Dioxus-native specific event type
pub enum DioxusNativeEvent {
/// A hotreload event, basically telling us to update our templates.
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
DevserverEvent(dioxus_devtools::DevserverMsg),
/// Create a new head element from the Link and Title elements
///
/// todo(jon): these should probabkly be synchronous somehow
CreateHeadElement {
window: WindowId,
name: String,
attributes: Vec<(String, String)>,
contents: Option<String>,
},
}
File renamed without changes.
+36 -5
View File
@@ -13,13 +13,12 @@ mod assets;
mod contexts;
mod dioxus_application;
mod dioxus_document;
mod event;
mod event_handler;
mod events;
mod mutation_writer;
pub use dioxus_application::DioxusNativeApplication;
use blitz_dom::{ns, Atom, QualName};
pub use dioxus_application::{DioxusNativeApplication, DioxusNativeEvent};
pub use dioxus_document::DioxusDocument;
pub use event::DioxusNativeEvent;
use blitz_shell::{create_default_event_loop, BlitzShellEvent, Config, WindowConfig};
use dioxus_core::{ComponentFunction, Element, VirtualDom};
@@ -68,7 +67,6 @@ pub fn launch_cfg_with_props<P: Clone + 'static, M: 'static>(
not(target_os = "ios")
))]
{
use crate::event::DioxusNativeEvent;
let proxy = event_loop.create_proxy();
dioxus_devtools::connect(move |event| {
let dxn_event = DioxusNativeEvent::DevserverEvent(event);
@@ -92,3 +90,36 @@ pub fn launch_cfg_with_props<P: Clone + 'static, M: 'static>(
// Run event loop
event_loop.run_app(&mut application).unwrap();
}
pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
QualName {
prefix: None,
ns: namespace.map(Atom::from).unwrap_or(ns!(html)),
local: Atom::from(local_name),
}
}
// Syntax sugar to make tracing calls less noisy in function below
macro_rules! trace {
($pattern:literal) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern);
}};
($pattern:literal, $item1:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1);
}};
($pattern:literal, $item1:expr, $item2:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2);
}};
($pattern:literal, $item1:expr, $item2:expr, $item3:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2);
}};
($pattern:literal, $item1:expr, $item2:expr, $item3:expr, $item4:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2, $item3, $item4);
}};
}
pub(crate) use trace;
+187 -458
View File
@@ -1,14 +1,10 @@
use crate::{dioxus_document::qual_name, NodeId};
use blitz_dom::{
local_name, namespace_url,
node::{Attribute, NodeSpecificData},
ns, BaseDocument, ElementNodeData, NodeData, QualName, RestyleHint,
};
//! Integration between Dioxus and Blitz
use crate::{qual_name, trace, NodeId};
use blitz_dom::{Attribute, BaseDocument, DocumentMutator};
use dioxus_core::{
AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,
};
use rustc_hash::FxHashMap;
use std::collections::HashSet;
/// The state of the Dioxus integration with the RealDom
#[derive(Debug)]
@@ -21,79 +17,13 @@ pub struct DioxusState {
node_id_mapping: Vec<Option<NodeId>>,
}
/// A writer for mutations that can be used with the RealDom.
pub struct MutationWriter<'a> {
/// The realdom associated with this writer
pub doc: &'a mut BaseDocument,
/// The state associated with this writer
pub state: &'a mut DioxusState,
pub style_nodes: HashSet<usize>,
/// The (latest) node which has been mounted in and had autofocus=true, if any
#[cfg(feature = "autofocus")]
pub node_to_autofocus: Option<usize>,
}
impl<'a> MutationWriter<'a> {
pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {
MutationWriter {
doc,
state,
style_nodes: HashSet::new(),
#[cfg(feature = "autofocus")]
node_to_autofocus: None,
}
}
fn is_style_node(&self, node_id: NodeId) -> bool {
self.doc
.get_node(node_id)
.unwrap()
.data
.is_element_with_tag_name(&local_name!("style"))
}
fn maybe_push_style_node(&mut self, node_id: impl Into<Option<NodeId>>) {
if let Some(node_id) = node_id.into() {
if self.is_style_node(node_id) {
self.style_nodes.insert(node_id);
}
}
}
#[track_caller]
fn maybe_push_parent_style_node(&mut self, node_id: NodeId) {
let parent_id = self.doc.get_node(node_id).unwrap().parent;
self.maybe_push_style_node(parent_id);
}
}
impl Drop for MutationWriter<'_> {
fn drop(&mut self) {
// Add/Update inline stylesheets (<style> elements)
for &id in &self.style_nodes {
self.doc.upsert_stylesheet_for_node(id);
}
#[cfg(feature = "autofocus")]
if let Some(node_id) = self.node_to_autofocus {
if self.doc.get_node(node_id).is_some() {
self.doc.set_focus_to(node_id);
}
}
}
}
impl DioxusState {
/// Initialize the DioxusState in the RealDom
pub fn create(_doc: &mut BaseDocument, mount_node: NodeId) -> Self {
pub fn create(root_id: usize) -> Self {
Self {
templates: FxHashMap::default(),
stack: vec![mount_node],
node_id_mapping: vec![Some(mount_node)],
stack: vec![root_id],
node_id_mapping: vec![Some(root_id)],
}
}
@@ -106,6 +36,33 @@ impl DioxusState {
pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {
self.node_id_mapping.get(element_id.0).copied().flatten()
}
pub(crate) fn anchor_and_nodes(&mut self, id: ElementId, m: usize) -> (usize, Vec<usize>) {
let anchor_node_id = self.element_to_node_id(id);
let new_nodes = self.m_stack_nodes(m);
(anchor_node_id, new_nodes)
}
pub(crate) fn m_stack_nodes(&mut self, m: usize) -> Vec<usize> {
self.stack.split_off(self.stack.len() - m)
}
}
/// A writer for mutations that can be used with the RealDom.
pub struct MutationWriter<'a> {
/// The realdom associated with this writer
pub docm: DocumentMutator<'a>,
/// The state associated with this writer
pub state: &'a mut DioxusState,
}
impl<'a> MutationWriter<'a> {
pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {
MutationWriter {
docm: doc.mutate(),
state,
}
}
}
impl MutationWriter<'_> {
@@ -122,454 +79,226 @@ impl MutationWriter<'_> {
self.state.node_id_mapping[element_id] = Some(node_id);
}
/// Find a child in the document by child index path
fn load_child(&self, path: &[u8]) -> NodeId {
let mut current = self
.doc
.get_node(*self.state.stack.last().unwrap())
.unwrap();
for i in path {
let new_id = current.children[*i as usize];
current = self.doc.get_node(new_id).unwrap();
}
current.id
/// Create a ElementId -> NodeId mapping and push the node to the stack
fn map_new_node(&mut self, node_id: NodeId, element_id: ElementId) {
self.set_id_mapping(node_id, element_id);
self.state.stack.push(node_id);
}
fn create_template_node(&mut self, node: &TemplateNode) -> NodeId {
match node {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => {
let name = qual_name(tag, *namespace);
let attrs = attrs
.iter()
.filter_map(|attr| match attr {
TemplateAttribute::Static {
name,
value,
namespace,
} => Some(Attribute {
name: qual_name(name, *namespace),
value: value.to_string(),
}),
TemplateAttribute::Dynamic { .. } => None,
})
.collect();
let mut data = ElementNodeData::new(name, attrs);
data.flush_style_attribute(self.doc.guard());
let child_ids: Vec<NodeId> = children
.iter()
.map(|child| self.create_template_node(child))
.collect();
let id = self.doc.create_node(NodeData::Element(data));
let node = self.doc.get_node(id).unwrap();
// Initialise style data
*node.stylo_element_data.borrow_mut() = Some(Default::default());
if let Some(src_attr) = node.attr(local_name!("src")) {
crate::assets::fetch_image(self.doc, id, src_attr.to_string());
}
let rel_attr = node.attr(local_name!("rel"));
let href_attr = node.attr(local_name!("href"));
if let (Some("stylesheet"), Some(href)) = (rel_attr, href_attr) {
crate::assets::fetch_linked_stylesheet(self.doc, id, href.to_string());
}
// If the node has an "id" attribute, store it in the ID map.
if let Some(id_attr) = node.attr(local_name!("id")) {
self.doc.nodes_to_id.insert(id_attr.to_string(), id);
}
for &child_id in &child_ids {
self.doc.get_node_mut(child_id).unwrap().parent = Some(id);
}
self.doc.get_node_mut(id).unwrap().children = child_ids;
id
}
TemplateNode::Text { text } => self.doc.create_text_node(text),
TemplateNode::Dynamic { .. } => self.doc.create_node(NodeData::Comment),
}
/// Find a child in the document by child index path
fn load_child(&self, path: &[u8]) -> NodeId {
let top_of_stack_node_id = *self.state.stack.last().unwrap();
self.docm.node_at_path(top_of_stack_node_id, path)
}
}
impl WriteMutations for MutationWriter<'_> {
fn append_children(&mut self, id: ElementId, m: usize) {
#[cfg(feature = "tracing")]
tracing::info!("append_children id:{} m:{}", id.0, m);
let children = self.state.stack.split_off(self.state.stack.len() - m);
let parent = self.state.element_to_node_id(id);
for child in children {
self.doc.get_node_mut(parent).unwrap().children.push(child);
self.doc.get_node_mut(child).unwrap().parent = Some(parent);
}
self.maybe_push_style_node(parent);
}
fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("assign_node_id path:{:?} id:{}", path, id.0);
trace!("assign_node_id path:{:?} id:{}", path, id.0);
// If there is an existing node already mapped to that ID and
// it has no parent, then drop it
// If there is an existing node already mapped to that ID and it has no parent, then drop it
// TODO: more automated GC/ref-counted semantics for node lifetimes
if let Some(node_id) = self.state.try_element_to_node_id(id) {
if let Some(node) = self.doc.get_node(node_id) {
if node.parent.is_none() {
self.doc.remove_and_drop_node(node_id);
}
}
self.docm.remove_node_if_unparented(node_id);
}
let node_id = self.load_child(path);
self.set_id_mapping(node_id, id);
// Map the node at specified path
self.set_id_mapping(self.load_child(path), id);
}
fn create_placeholder(&mut self, id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("create_placeholder id:{}", id.0);
let node_id = self.doc.create_node(NodeData::Comment);
self.set_id_mapping(node_id, id);
self.state.stack.push(node_id);
trace!("create_placeholder id:{}", id.0);
let node_id = self.docm.create_comment_node();
self.map_new_node(node_id, id);
}
fn create_text_node(&mut self, value: &str, id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("create_text_node id:{} text:{}", id.0, value);
let node_id = self.doc.create_text_node(value);
self.set_id_mapping(node_id, id);
self.state.stack.push(node_id);
trace!("create_text_node id:{} text:{}", id.0, value);
let node_id = self.docm.create_text_node(value);
self.map_new_node(node_id, id);
}
#[allow(clippy::map_entry)]
fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
if !self.state.templates.contains_key(&template) {
let template_root_ids: Vec<NodeId> = template
.roots
.iter()
.map(|root| self.create_template_node(root))
.collect();
self.state.templates.insert(template, template_root_ids);
}
let template_entry = &self.state.templates[&template];
let template_node_id = template_entry[index];
let clone_id = self.doc.deep_clone_node(template_node_id);
#[cfg(feature = "autofocus")]
autofocus_node_recursive(self.doc, &mut self.node_to_autofocus, clone_id);
self.set_id_mapping(clone_id, id);
self.state.stack.push(clone_id);
}
fn replace_node_with(&mut self, id: ElementId, m: usize) {
#[cfg(feature = "tracing")]
tracing::info!("replace_node_with id:{} m:{}", id.0, m);
let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
let anchor_node_id = self.state.element_to_node_id(id);
self.maybe_push_parent_style_node(anchor_node_id);
self.doc.insert_before(anchor_node_id, &new_nodes);
self.doc.remove_node(anchor_node_id);
}
fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
#[cfg(feature = "tracing")]
tracing::info!("replace_placeholder_with_nodes path:{:?} m:{}", path, m);
let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
let anchor_node_id = self.load_child(path);
self.maybe_push_parent_style_node(anchor_node_id);
self.doc.insert_before(anchor_node_id, &new_nodes);
self.doc.remove_node(anchor_node_id);
fn append_children(&mut self, id: ElementId, m: usize) {
trace!("append_children id:{} m:{}", id.0, m);
let (parent_id, child_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.append_children(parent_id, &child_node_ids);
}
fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
#[cfg(feature = "tracing")]
tracing::info!("insert_nodes_after id:{} m:{}", id.0, m);
let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
let anchor_node_id = self.state.element_to_node_id(id);
let next_sibling_id = self
.doc
.get_node(anchor_node_id)
.unwrap()
.forward(1)
.map(|node| node.id);
match next_sibling_id {
Some(anchor_node_id) => {
self.doc.insert_before(anchor_node_id, &new_nodes);
}
None => self.doc.append(anchor_node_id, &new_nodes),
}
self.maybe_push_parent_style_node(anchor_node_id);
trace!("insert_nodes_after id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.insert_nodes_after(anchor_node_id, &new_node_ids);
}
fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
#[cfg(feature = "tracing")]
tracing::info!("insert_nodes_before id:{} m:{}", id.0, m);
trace!("insert_nodes_before id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.insert_nodes_before(anchor_node_id, &new_node_ids);
}
let new_nodes = self.state.stack.split_off(self.state.stack.len() - m);
let anchor_node_id = self.state.element_to_node_id(id);
self.doc.insert_before(anchor_node_id, &new_nodes);
fn replace_node_with(&mut self, id: ElementId, m: usize) {
trace!("replace_node_with id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.replace_node_with(anchor_node_id, &new_node_ids);
}
self.maybe_push_parent_style_node(anchor_node_id);
fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
trace!("replace_placeholder_with_nodes path:{:?} m:{}", path, m);
// WARNING: DO NOT REORDER
// The order of the following two lines is very important as "m_stack_nodes" mutates
// the stack and then "load_child" reads from the top of the stack.
let new_node_ids = self.state.m_stack_nodes(m);
let anchor_node_id = self.load_child(path);
self.docm
.replace_placeholder_with_nodes(anchor_node_id, &new_node_ids);
}
fn remove_node(&mut self, id: ElementId) {
trace!("remove_node id:{}", id.0);
let node_id = self.state.element_to_node_id(id);
self.docm.remove_node(node_id);
}
fn push_root(&mut self, id: ElementId) {
trace!("push_root id:{}", id.0);
let node_id = self.state.element_to_node_id(id);
self.state.stack.push(node_id);
}
fn set_node_text(&mut self, value: &str, id: ElementId) {
trace!("set_node_text id:{} value:{}", id.0, value);
let node_id = self.state.element_to_node_id(id);
self.docm.set_node_text(node_id, value);
}
fn set_attribute(
&mut self,
name: &'static str,
local_name: &'static str,
ns: Option<&'static str>,
value: &AttributeValue,
id: ElementId,
) {
let node_id = self.state.element_to_node_id(id);
trace!("set_attribute node_id:{node_id} ns: {ns:?} name:{local_name}, value:{value:?}");
#[cfg(feature = "tracing")]
tracing::info!(
"set_attribute node_id:{} ns: {:?} name:{}, value:{:?}",
node_id,
ns,
name,
value
);
// Dioxus has overloaded the style namespace to accumulate style attributes without a `style` block
// TODO: accumulate style attributes into a single style element.
if ns == Some("style") {
return;
}
self.doc.snapshot_node(node_id);
let mut queued_image = None;
let mut queued_stylesheet = None;
{
let node = &mut self.doc.nodes[node_id];
let stylo_element_data = &mut *node.stylo_element_data.borrow_mut();
if let Some(data) = stylo_element_data {
data.hint |= RestyleHint::restyle_subtree();
}
if let NodeData::Element(ref mut element) = node.data {
if element.name.local == local_name!("input") && name == "checked" {
set_input_checked_state(element, value);
}
// FIXME: support other non-text attributes
else if let AttributeValue::Text(val) = value {
if name == "value" {
// Update text input value
if let Some(input_data) = element.text_input_data_mut() {
input_data.set_text(
&mut self.doc.font_ctx,
&mut self.doc.layout_ctx,
val,
);
}
}
// FIXME check namespace
let existing_attr = element
.attrs
.iter_mut()
.find(|attr| attr.name.local == *name);
if let Some(existing_attr) = existing_attr {
existing_attr.value.clear();
existing_attr.value.push_str(val);
} else {
// we have overloaded the style namespace to accumulate style attributes without a `style` block
if ns == Some("style") {
// todo: need to accumulate style attributes into a single style
//
// element.
} else {
element.attrs.push(Attribute {
name: qual_name(name, ns),
value: val.to_string(),
});
}
}
if name == "style" {
element.flush_style_attribute(&self.doc.guard);
}
if name == "src" && !val.is_empty() {
element.node_specific_data = NodeSpecificData::None;
queued_image = Some(val.to_string());
}
if element.name.local == local_name!("link")
&& name == "href"
&& !val.is_empty()
{
queued_stylesheet = Some(val.to_string());
}
}
if let AttributeValue::None = value {
// Update text input value
if name == "value" {
if let Some(input_data) = element.text_input_data_mut() {
input_data.set_text(
&mut self.doc.font_ctx,
&mut self.doc.layout_ctx,
"",
);
}
}
// FIXME: check namespace
element.attrs.retain(|attr| attr.name.local != *name);
}
fn is_falsy(val: &AttributeValue) -> bool {
match val {
AttributeValue::None => true,
AttributeValue::Text(val) => val == "false",
AttributeValue::Bool(val) => !val,
AttributeValue::Int(val) => *val == 0,
AttributeValue::Float(val) => *val == 0.0,
_ => false,
}
}
if let Some(queued_image) = queued_image {
crate::assets::fetch_image(self.doc, node_id, queued_image);
}
let name = qual_name(local_name, ns);
if let Some(queued_stylesheet) = queued_stylesheet {
crate::assets::fetch_linked_stylesheet(self.doc, node_id, queued_stylesheet);
// FIXME: more principled handling of special case attributes
if value == &AttributeValue::None || (local_name == "checked" && is_falsy(value)) {
self.docm.clear_attribute(node_id, name);
} else {
match value {
AttributeValue::Text(value) => self.docm.set_attribute(node_id, name, value),
AttributeValue::Float(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
AttributeValue::Int(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
AttributeValue::Bool(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
_ => {
// FIXME: support all attribute types
}
};
}
}
fn set_node_text(&mut self, value: &str, id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("set_node_text id:{} value:{}", id.0, value);
fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
// TODO: proper template node support
let template_entry = self.state.templates.entry(template).or_insert_with(|| {
let template_root_ids: Vec<NodeId> = template
.roots
.iter()
.map(|root| create_template_node(&mut self.docm, root))
.collect();
let node_id = self.state.element_to_node_id(id);
let node = self.doc.get_node_mut(node_id).unwrap();
template_root_ids
});
let text = match node.data {
NodeData::Text(ref mut text) => text,
// todo: otherwise this is basically element.textContent which is a bit different - need to parse as html
_ => return,
};
let template_node_id = template_entry[index];
let clone_id = self.docm.deep_clone_node(template_node_id);
let changed = text.content != value;
if changed {
text.content.clear();
text.content.push_str(value);
let parent = node.parent;
self.maybe_push_style_node(parent);
}
trace!("load_template template_node_id:{template_node_id} clone_id:{clone_id}");
self.map_new_node(clone_id, id);
}
fn create_event_listener(&mut self, _name: &'static str, _id: ElementId) {
// we're going to actually set the listener here as a placeholder - in JS this would also be a placeholder
fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
// We're going to actually set the listener here as a placeholder - in JS this would also be a placeholder
// we might actually just want to attach the attribute to the root element (delegation)
self.set_attribute(
_name,
None,
&AttributeValue::Text("<rust func>".into()),
_id,
);
let value = AttributeValue::Text("<rust func>".into());
self.set_attribute(name, None, &value, id);
// also set the data-dioxus-id attribute so we can find the element later
self.set_attribute(
"data-dioxus-id",
None,
&AttributeValue::Text(_id.0.to_string()),
_id,
);
// Also set the data-dioxus-id attribute so we can find the element later
let value = AttributeValue::Text(id.0.to_string());
self.set_attribute("data-dioxus-id", None, &value, id);
// let node_id = self.state.element_to_node_id(id);
// let mut node = self.rdom.get_mut(node_id).unwrap();
// node.add_event_listener(name);
}
fn remove_event_listener(&mut self, _name: &'static str, _id: ElementId) {
// let node_id = self.state.element_to_node_id(id);
// let mut node = self.rdom.get_mut(node_id).unwrap();
// node.remove_event_listener(name);
}
}
fn remove_node(&mut self, id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("remove_node id:{}", id.0);
fn create_template_node(docm: &mut DocumentMutator<'_>, node: &TemplateNode) -> NodeId {
match node {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => {
let name = qual_name(tag, *namespace);
let attrs = attrs.iter().filter_map(map_template_attr).collect();
let node_id = docm.create_element(name, attrs);
let node_id = self.state.element_to_node_id(id);
self.doc.remove_node(node_id);
}
let child_ids: Vec<NodeId> = children
.iter()
.map(|child| create_template_node(docm, child))
.collect();
fn push_root(&mut self, id: ElementId) {
#[cfg(feature = "tracing")]
tracing::info!("push_root id:{}", id.0,);
docm.append_children(node_id, &child_ids);
let node_id = self.state.element_to_node_id(id);
self.state.stack.push(node_id);
node_id
}
TemplateNode::Text { text } => docm.create_text_node(text),
TemplateNode::Dynamic { .. } => docm.create_comment_node(),
}
}
/// Set 'checked' state on an input based on given attributevalue
fn set_input_checked_state(element: &mut ElementNodeData, value: &AttributeValue) {
let checked: bool;
match value {
AttributeValue::Bool(checked_bool) => {
checked = *checked_bool;
}
AttributeValue::Text(val) => {
if let Ok(checked_bool) = val.parse() {
checked = checked_bool;
} else {
return;
};
}
_ => {
return;
}
fn map_template_attr(attr: &TemplateAttribute) -> Option<Attribute> {
let TemplateAttribute::Static {
name,
value,
namespace,
} = attr
else {
return None;
};
match element.node_specific_data {
NodeSpecificData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
// If we have just constructed the element, set the node attribute,
// and NodeSpecificData will be created from that later
// this simulates the checked attribute being set in html,
// and the element's checked property being set from that
NodeSpecificData::None => element.attrs.push(Attribute {
name: QualName {
prefix: None,
ns: ns!(html),
local: local_name!("checked"),
},
value: checked.to_string(),
}),
_ => {}
}
}
#[cfg(feature = "autofocus")]
fn autofocus_node_recursive(
doc: &BaseDocument,
node_to_autofocus: &mut Option<usize>,
node_id: usize,
) {
if let Some(node) = doc.get_node(node_id) {
if node.is_focussable() {
if let NodeData::Element(ref element) = node.data {
if let Some(value) = element.attr(local_name!("autofocus")) {
if value == "true" {
*node_to_autofocus = Some(node_id);
}
}
}
}
for child_node_id in &node.children {
autofocus_node_recursive(doc, node_to_autofocus, *child_node_id);
}
}
let name = qual_name(name, *namespace);
let value = value.to_string();
Some(Attribute { name, value })
}