123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId};
- use dioxus_core::{Mutation, Mutations};
- use std::fmt::Debug;
- #[derive(Debug)]
- pub enum ElementProduced {
- /// The iterator produced an element by progressing to the next node in a depth first order.
- Progressed(RealNodeId),
- /// The iterator reached the end of the tree and looped back to the root
- Looped(RealNodeId),
- }
- impl ElementProduced {
- pub fn id(&self) -> RealNodeId {
- match self {
- ElementProduced::Progressed(id) => *id,
- ElementProduced::Looped(id) => *id,
- }
- }
- }
- #[derive(Debug)]
- enum NodePosition {
- AtNode,
- InChild(usize),
- }
- impl NodePosition {
- fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self {
- match self {
- Self::AtNode => Self::AtNode,
- Self::InChild(i) => Self::InChild(f(*i)),
- }
- }
- fn get_or_insert(&mut self, child_idx: usize) -> usize {
- match self {
- Self::AtNode => {
- *self = Self::InChild(child_idx);
- child_idx
- }
- Self::InChild(i) => *i,
- }
- }
- }
- /// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
- /// This iterator traverses the tree depth first.
- /// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
- /// The iterator loops around when it reaches the end or the beginning.
- pub struct PersistantElementIter {
- // stack of elements and fragments
- stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
- }
- impl Default for PersistantElementIter {
- fn default() -> Self {
- PersistantElementIter {
- stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)],
- }
- }
- }
- impl PersistantElementIter {
- pub fn new() -> Self {
- Self::default()
- }
- /// remove stale element refreneces
- /// returns true if the focused element is removed
- pub fn prune<S: State>(&mut self, mutations: &Mutations, rdom: &RealDom<S>) -> bool {
- let mut changed = false;
- let ids_removed: Vec<_> = mutations
- .edits
- .iter()
- .filter_map(|m| {
- // nodes within templates will never be removedns
- if let Mutation::Remove { id } = m {
- Some(rdom.element_to_node_id(*id))
- } else {
- None
- }
- })
- .collect();
- // if any element is removed in the chain, remove it and its children from the stack
- if let Some(r) = self
- .stack
- .iter()
- .position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
- {
- self.stack.truncate(r);
- changed = true;
- }
- // if a child is removed or inserted before or at the current element, update the child index
- for (el_id, child_idx) in self.stack.iter_mut() {
- if let NodePosition::InChild(child_idx) = child_idx {
- if let Some(children) = &rdom.tree.children_ids(*el_id) {
- for m in &mutations.edits {
- match m {
- Mutation::Remove { id } => {
- let id = rdom.element_to_node_id(*id);
- if children.iter().take(*child_idx + 1).any(|c| *c == id) {
- *child_idx -= 1;
- }
- }
- Mutation::InsertBefore { id, m } => {
- let id = rdom.element_to_node_id(*id);
- if children.iter().take(*child_idx + 1).any(|c| *c == id) {
- *child_idx += *m;
- }
- }
- Mutation::InsertAfter { id, m } => {
- let id = rdom.element_to_node_id(*id);
- if children.iter().take(*child_idx).any(|c| *c == id) {
- *child_idx += *m;
- }
- }
- _ => (),
- }
- }
- }
- }
- }
- changed
- }
- /// get the next element
- pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
- if self.stack.is_empty() {
- let id = NodeId(0);
- let new = (id, NodePosition::AtNode);
- self.stack.push(new);
- ElementProduced::Looped(id)
- } else {
- let (last, old_child_idx) = self.stack.last_mut().unwrap();
- let node = &rdom[*last];
- match &node.node_data.node_type {
- NodeType::Element { .. } => {
- let children = rdom.tree.children_ids(*last).unwrap();
- *old_child_idx = old_child_idx.map(|i| i + 1);
- // if we have children, go to the next child
- let child_idx = old_child_idx.get_or_insert(0);
- if child_idx >= children.len() {
- self.pop();
- self.next(rdom)
- } else {
- let id = children[child_idx];
- if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
- self.stack.push((id, NodePosition::AtNode));
- }
- ElementProduced::Progressed(id)
- }
- }
- NodeType::Text { .. } | NodeType::Placeholder { .. } => {
- // we are at a leaf, so we are done
- ElementProduced::Progressed(self.pop())
- }
- }
- }
- }
- /// get the previous element
- pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
- // recursively add the last child element to the stack
- fn push_back<S: State>(
- stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
- new_node: RealNodeId,
- rdom: &RealDom<S>,
- ) -> RealNodeId {
- match &rdom[new_node].node_data.node_type {
- NodeType::Element { .. } => {
- let children = rdom.tree.children_ids(new_node).unwrap();
- if children.is_empty() {
- new_node
- } else {
- stack.push((new_node, NodePosition::InChild(children.len() - 1)));
- push_back(stack, *children.last().unwrap(), rdom)
- }
- }
- _ => new_node,
- }
- }
- if self.stack.is_empty() {
- let new_node = NodeId(0);
- ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
- } else {
- let (last, old_child_idx) = self.stack.last_mut().unwrap();
- let node = &rdom[*last];
- match &node.node_data.node_type {
- NodeType::Element { .. } => {
- let children = rdom.tree.children_ids(*last).unwrap();
- // if we have children, go to the next child
- if let NodePosition::InChild(0) = old_child_idx {
- ElementProduced::Progressed(self.pop())
- } else {
- *old_child_idx = old_child_idx.map(|i| i - 1);
- if let NodePosition::InChild(child_idx) = old_child_idx {
- if *child_idx >= children.len() || children.is_empty() {
- self.pop();
- self.prev(rdom)
- } else {
- let new_node = children[*child_idx];
- ElementProduced::Progressed(push_back(
- &mut self.stack,
- new_node,
- rdom,
- ))
- }
- } else {
- self.pop();
- self.prev(rdom)
- }
- }
- }
- NodeType::Text { .. } | NodeType::Placeholder { .. } => {
- // we are at a leaf, so we are done
- ElementProduced::Progressed(self.pop())
- }
- }
- }
- }
- fn pop(&mut self) -> RealNodeId {
- self.stack.pop().unwrap().0
- }
- }
- #[derive(Default, Clone, Debug)]
- struct Empty {}
- impl State for Empty {
- const PASSES: &'static [crate::AnyPass<crate::node::Node<Self>>] = &[];
- const MASKS: &'static [crate::NodeMask] = &[];
- }
- #[test]
- #[allow(unused_variables)]
- fn traverse() {
- use dioxus::prelude::*;
- #[allow(non_snake_case)]
- fn Base(cx: Scope) -> Element {
- render!(
- div{
- div{
- "hello"
- p{
- "world"
- }
- "hello world"
- }
- }
- )
- }
- let mut vdom = VirtualDom::new(Base);
- let mutations = vdom.rebuild();
- let mut rdom: RealDom<Empty> = RealDom::new();
- let _to_update = rdom.apply_mutations(mutations);
- let mut iter = PersistantElementIter::new();
- let div_tag = "div".to_string();
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- let text1 = "hello".to_string();
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text1, .. }
- ));
- let p_tag = "p".to_string();
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: p_tag, .. }
- ));
- let text2 = "world".to_string();
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text2, .. }
- ));
- let text3 = "hello world".to_string();
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text3, .. }
- ));
- assert!(matches!(
- &rdom[iter.next(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text3, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text2, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: p_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text1, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- assert!(matches!(
- &rdom[iter.prev(&rdom).id()].node_data.node_type,
- NodeType::Text { text: text3, .. }
- ));
- }
- #[test]
- #[allow(unused_variables)]
- fn persist_removes() {
- use dioxus::prelude::*;
- #[allow(non_snake_case)]
- fn Base(cx: Scope) -> Element {
- let children = match cx.generation() % 2 {
- 0 => 3,
- 1 => 2,
- _ => unreachable!(),
- };
- render!(
- div{
- (0..children).map(|i|{
- rsx!{
- p{
- key: "{i}",
- "{i}"
- }
- }
- })
- }
- )
- }
- let mut vdom = VirtualDom::new(Base);
- let mut rdom: RealDom<Empty> = RealDom::new();
- let build = vdom.rebuild();
- let _to_update = rdom.apply_mutations(build);
- // this will end on the node that is removed
- let mut iter1 = PersistantElementIter::new();
- // this will end on the after node that is removed
- let mut iter2 = PersistantElementIter::new();
- // div
- iter1.next(&rdom).id();
- iter2.next(&rdom).id();
- // p
- iter1.next(&rdom).id();
- iter2.next(&rdom).id();
- // "1"
- iter1.next(&rdom).id();
- iter2.next(&rdom).id();
- // p
- iter1.next(&rdom).id();
- iter2.next(&rdom).id();
- // "2"
- iter1.next(&rdom).id();
- iter2.next(&rdom).id();
- // p
- iter2.next(&rdom).id();
- // "3"
- iter2.next(&rdom).id();
- vdom.mark_dirty(ScopeId(0));
- let update = vdom.render_immediate();
- iter1.prune(&update, &rdom);
- iter2.prune(&update, &rdom);
- let _to_update = rdom.apply_mutations(update);
- let p_tag = "1".to_string();
- let idx = iter1.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Element { tag: p_tag, .. }
- ));
- let text = "2".to_string();
- let idx = iter1.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Text { text, .. }
- ));
- let div_tag = "div".to_string();
- let idx = iter2.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Element { tag: div_tag, .. }
- ));
- }
- #[test]
- #[allow(unused_variables)]
- fn persist_instertions_before() {
- use dioxus::prelude::*;
- #[allow(non_snake_case)]
- fn Base(cx: Scope) -> Element {
- let children = match cx.generation() % 2 {
- 0 => 3,
- 1 => 2,
- _ => unreachable!(),
- };
- render!(
- div{
- (0..children).map(|i|{
- rsx!{
- p{
- key: "{i}",
- "{i}"
- }
- }
- })
- }
- )
- }
- let mut vdom = VirtualDom::new(Base);
- let mut rdom: RealDom<Empty> = RealDom::new();
- let build = vdom.rebuild();
- let _to_update = rdom.apply_mutations(build);
- let mut iter = PersistantElementIter::new();
- // div
- iter.next(&rdom).id();
- // p
- iter.next(&rdom).id();
- // "1"
- iter.next(&rdom).id();
- // p
- iter.next(&rdom).id();
- // "2"
- iter.next(&rdom).id();
- vdom.mark_dirty(ScopeId(0));
- let update = vdom.render_immediate();
- iter.prune(&update, &rdom);
- let _to_update = rdom.apply_mutations(update);
- let p_tag = "div".to_string();
- let idx = iter.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Element { tag: p_tag, .. }
- ));
- }
- #[test]
- #[allow(unused_variables)]
- fn persist_instertions_after() {
- use dioxus::prelude::*;
- #[allow(non_snake_case)]
- fn Base(cx: Scope) -> Element {
- let children = match cx.generation() % 2 {
- 0 => 3,
- 1 => 2,
- _ => unreachable!(),
- };
- render!(
- div{
- (0..children).map(|i|{
- rsx!{
- p{
- key: "{i}",
- "{i}"
- }
- }
- })
- }
- )
- }
- let mut vdom = VirtualDom::new(Base);
- let mut rdom: RealDom<Empty> = RealDom::new();
- let build = vdom.rebuild();
- let _to_update = rdom.apply_mutations(build);
- let mut iter = PersistantElementIter::new();
- // div
- iter.next(&rdom).id();
- // p
- iter.next(&rdom).id();
- // "hello"
- iter.next(&rdom).id();
- // p
- iter.next(&rdom).id();
- // "world"
- iter.next(&rdom).id();
- let update = vdom.rebuild();
- iter.prune(&update, &rdom);
- let _to_update = rdom.apply_mutations(update);
- let p_tag = "p".to_string();
- let idx = iter.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Element { tag: p_tag, .. }
- ));
- let text = "hello world".to_string();
- let idx = iter.next(&rdom).id();
- assert!(matches!(
- &rdom[idx].node_data.node_type,
- NodeType::Text { text, .. }
- ));
- }
|