|
@@ -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);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
- #[cfg(feature = "autofocus")]
|
|
|
- autofocus_node_recursive(self.doc, &mut self.node_to_autofocus, clone_id);
|
|
|
+ fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
- self.set_id_mapping(clone_id, id);
|
|
|
- self.state.stack.push(clone_id);
|
|
|
+ fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+ 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.maybe_push_parent_style_node(anchor_node_id);
|
|
|
- self.doc.insert_before(anchor_node_id, &new_nodes);
|
|
|
- self.doc.remove_node(anchor_node_id);
|
|
|
+ self.docm
|
|
|
+ .replace_placeholder_with_nodes(anchor_node_id, &new_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);
|
|
|
+ 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 insert_nodes_before(&mut self, id: ElementId, m: usize) {
|
|
|
- #[cfg(feature = "tracing")]
|
|
|
- tracing::info!("insert_nodes_before 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.doc.insert_before(anchor_node_id, &new_nodes);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
|
|
|
- self.maybe_push_parent_style_node(anchor_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
|
|
|
- );
|
|
|
-
|
|
|
- self.doc.snapshot_node(node_id);
|
|
|
-
|
|
|
- let mut queued_image = None;
|
|
|
- let mut queued_stylesheet = None;
|
|
|
-
|
|
|
- {
|
|
|
- let node = &mut self.doc.nodes[node_id];
|
|
|
+ // 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;
|
|
|
+ }
|
|
|
|
|
|
- let stylo_element_data = &mut *node.stylo_element_data.borrow_mut();
|
|
|
- if let Some(data) = stylo_element_data {
|
|
|
- data.hint |= RestyleHint::restyle_subtree();
|
|
|
+ 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 NodeData::Element(ref mut element) = node.data {
|
|
|
- if element.name.local == local_name!("input") && name == "checked" {
|
|
|
- set_input_checked_state(element, value);
|
|
|
+ let name = qual_name(local_name, ns);
|
|
|
+
|
|
|
+ // 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);
|
|
|
}
|
|
|
- // 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());
|
|
|
- }
|
|
|
+ AttributeValue::Int(value) => {
|
|
|
+ let value = value.to_string();
|
|
|
+ self.docm.set_attribute(node_id, name, &value);
|
|
|
}
|
|
|
-
|
|
|
- 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);
|
|
|
+ AttributeValue::Bool(value) => {
|
|
|
+ let value = value.to_string();
|
|
|
+ self.docm.set_attribute(node_id, name, &value);
|
|
|
}
|
|
|
- }
|
|
|
+ _ => {
|
|
|
+ // FIXME: support all attribute types
|
|
|
+ }
|
|
|
+ };
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if let Some(queued_image) = queued_image {
|
|
|
- crate::assets::fetch_image(self.doc, node_id, queued_image);
|
|
|
- }
|
|
|
+ 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();
|
|
|
|
|
|
- if let Some(queued_stylesheet) = queued_stylesheet {
|
|
|
- crate::assets::fetch_linked_stylesheet(self.doc, node_id, queued_stylesheet);
|
|
|
- }
|
|
|
- }
|
|
|
+ template_root_ids
|
|
|
+ });
|
|
|
|
|
|
- fn set_node_text(&mut self, value: &str, id: ElementId) {
|
|
|
- #[cfg(feature = "tracing")]
|
|
|
- tracing::info!("set_node_text id:{} value:{}", id.0, value);
|
|
|
+ let template_node_id = template_entry[index];
|
|
|
+ let clone_id = self.docm.deep_clone_node(template_node_id);
|
|
|
|
|
|
- let node_id = self.state.element_to_node_id(id);
|
|
|
- let node = self.doc.get_node_mut(node_id).unwrap();
|
|
|
-
|
|
|
- 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 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,
|
|
|
- );
|
|
|
-
|
|
|
- // 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,
|
|
|
- );
|
|
|
-
|
|
|
- // let node_id = self.state.element_to_node_id(id);
|
|
|
- // let mut node = self.rdom.get_mut(node_id).unwrap();
|
|
|
+ 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
|
|
|
+ let value = AttributeValue::Text(id.0.to_string());
|
|
|
+ self.set_attribute("data-dioxus-id", None, &value, id);
|
|
|
+
|
|
|
// 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);
|
|
|
-
|
|
|
- let node_id = self.state.element_to_node_id(id);
|
|
|
- self.doc.remove_node(node_id);
|
|
|
- }
|
|
|
-
|
|
|
- fn push_root(&mut self, id: ElementId) {
|
|
|
- #[cfg(feature = "tracing")]
|
|
|
- tracing::info!("push_root 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 child_ids: Vec<NodeId> = children
|
|
|
+ .iter()
|
|
|
+ .map(|child| create_template_node(docm, child))
|
|
|
+ .collect();
|
|
|
|
|
|
- let node_id = self.state.element_to_node_id(id);
|
|
|
- self.state.stack.push(node_id);
|
|
|
- }
|
|
|
-}
|
|
|
+ docm.append_children(node_id, &child_ids);
|
|
|
|
|
|
-/// 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;
|
|
|
+ node_id
|
|
|
}
|
|
|
- };
|
|
|
- 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(),
|
|
|
- }),
|
|
|
- _ => {}
|
|
|
+ TemplateNode::Text { text } => docm.create_text_node(text),
|
|
|
+ TemplateNode::Dynamic { .. } => docm.create_comment_node(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[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);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+fn map_template_attr(attr: &TemplateAttribute) -> Option<Attribute> {
|
|
|
+ let TemplateAttribute::Static {
|
|
|
+ name,
|
|
|
+ value,
|
|
|
+ namespace,
|
|
|
+ } = attr
|
|
|
+ else {
|
|
|
+ return None;
|
|
|
+ };
|
|
|
|
|
|
- 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 })
|
|
|
}
|