diff --git a/Cargo.toml b/Cargo.toml index 95900a1d2..a1e16bada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } dioxus-native-core = { path = "./packages/native-core", optional = true } +dioxus-native-core-macro = { path = "./packages/native-core-macro", optional = true } # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true } # dioxus-rsx = { path = "./packages/rsx", optional = true } @@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] -native-core = ["dioxus-native-core"] +native-core = ["dioxus-native-core", "dioxus-native-core-macro"] [workspace] diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml new file mode 100644 index 000000000..d71b03246 --- /dev/null +++ b/packages/native-core-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-native-core-macro" +version = "0.2.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +syn = { version = "1.0.11", features = ["extra-traits"] } +quote = "1.0" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs new file mode 100644 index 000000000..cc99ee746 --- /dev/null +++ b/packages/native-core-macro/src/lib.rs @@ -0,0 +1,345 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Field, Ident, Token, Type, TypeTuple, +}; + +#[derive(PartialEq)] +enum DepKind { + NodeDepState, + ChildDepState, + ParentDepState, +} + +// macro that streams data from the State for any attributes that end with _ +#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] +pub fn state_macro_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_derive_macro(&ast) +} + +fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { + let type_name = &ast.ident; + let fields: Vec<_> = match &ast.data { + syn::Data::Struct(data) => match &data.fields { + syn::Fields::Named(e) => &e.named, + syn::Fields::Unnamed(_) => todo!("unnamed fields"), + syn::Fields::Unit => todo!("unit fields"), + } + .iter() + .collect(), + _ => unimplemented!(), + }; + let strct = Struct::parse(&fields); + let state_strct = StateStruct::parse(&fields, &strct); + + let node_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + let child_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + let parent_dep_state_fields = quote::__private::TokenStream::from_iter( + state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| { + let ty_id = &f.type_id(); + let reduce = &f.reduce_self(); + quote! { + else if ty == #ty_id { + #reduce + } + } + }), + ); + + let node_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| &f.mem.ty); + let child_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| &f.mem.ty); + let parent_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| &f.mem.ty); + + let type_name_str = type_name.to_string(); + + let gen = quote! { + impl State for #type_name{ + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::NodeDepState as _; + // println!("called update_node_dep_state with ty: {:?}", ty); + if false { + unreachable!(); + } + #node_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str) + } + } + + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::ParentDepState as _; + // println!("called update_parent_dep_state with ty: {:?}", ty); + if false { + unreachable!(); + } + #parent_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str) + } + } + + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::state::ChildDepState as _; + // println!("called update_child_dep_state with ty: {:?}", ty); + if false { + unreachable!() + } + #child_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str) + } + } + + fn child_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #child_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#child_types>()); + })* + dep_types + } + + fn parent_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #parent_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#parent_types>()); + })* + dep_types + } + + fn node_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #node_types::NODE_MASK.overlaps(mask) { + dep_types.push(std::any::TypeId::of::<#node_types>()); + })* + dep_types + } + } + }; + gen.into() +} + +struct Struct { + members: Vec, +} + +impl Struct { + fn parse(fields: &[&Field]) -> Self { + let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); + Self { members } + } +} + +struct StateStruct<'a> { + state_members: Vec>, +} + +impl<'a> StateStruct<'a> { + fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self { + let state_members = strct + .members + .iter() + .zip(fields.iter()) + .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) + .collect(); + + // todo: sort members + + Self { state_members } + } +} + +struct DepTypes { + ctx_ty: Option, + dep_ty: Option, +} + +impl Parse for DepTypes { + fn parse(input: ParseStream) -> Result { + let dep_ty = input.parse().ok(); + let comma: Option = input.parse().ok(); + let ctx_ty = input.parse().ok(); + Ok(Self { + ctx_ty: comma.and(ctx_ty), + dep_ty, + }) + } +} + +struct NodeDepTypes { + ctx_ty: Option, +} + +impl Parse for NodeDepTypes { + fn parse(input: ParseStream) -> Result { + let ctx_ty = input.parse().ok(); + Ok(Self { ctx_ty }) + } +} + +impl From for DepTypes { + fn from(node_dep_types: NodeDepTypes) -> Self { + Self { + ctx_ty: node_dep_types.ctx_ty, + dep_ty: None, + } + } +} + +struct Member { + ty: Type, + ident: Ident, +} + +impl Member { + fn parse(field: &Field) -> Option { + Some(Self { + ty: field.ty.clone(), + ident: field.ident.as_ref()?.clone(), + }) + } +} + +struct StateMember<'a> { + mem: &'a Member, + dep_kind: DepKind, + dep_mem: Option<&'a Member>, + ctx_ty: Option, +} + +impl<'a> StateMember<'a> { + fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { + field.attrs.iter().find_map(|a| { + let dep_kind = a + .path + .get_ident() + .map(|i| match i.to_string().as_str() { + "node_dep_state" => Some(DepKind::NodeDepState), + "child_dep_state" => Some(DepKind::ChildDepState), + "parent_dep_state" => Some(DepKind::ParentDepState), + _ => None, + }) + .flatten()?; + let deps: DepTypes = match dep_kind { + DepKind::NodeDepState => a.parse_args::().ok()?.into(), + _ => a.parse_args().ok()?, + }; + + Some(Self { + mem, + dep_kind, + dep_mem: deps + .dep_ty + .map(|ty| parent.members.iter().find(|m| m.ty == ty)) + .flatten(), + ctx_ty: deps.ctx_ty, + }) + }) + } + + fn reduce_self(&self) -> quote::__private::TokenStream { + let ident = &self.mem.ident; + let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { + if ctx_ty + == &Type::Tuple(TypeTuple { + paren_token: Paren { + span: quote::__private::Span::call_site(), + }, + elems: Punctuated::new(), + }) + { + quote! {&()} + } else { + let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; + quote! {ctx.get().expect(#msg)} + } + } else { + quote! {&()} + }; + let ty = &self.mem.ty; + let node_view = quote!(NodeView::new(node, #ty::NODE_MASK)); + if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(#node_view, #get_ctx)) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx)) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) + } + } + } else { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(#node_view, #get_ctx)) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx)) + } + } + } + } + + fn type_id(&self) -> quote::__private::TokenStream { + let ty = &self.mem.ty; + quote!({ + let type_id = std::any::TypeId::of::<#ty>(); + type_id + }) + } +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 83e403487..3e37d7e8a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,10 +10,12 @@ homepage = "https://dioxuslabs.com" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 26c629446..7692a5ae1 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,2 +1,4 @@ pub mod layout_attributes; pub mod real_dom; +pub mod state; +pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 239c90aea..1c25806c3 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,30 +1,34 @@ +use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ + any::TypeId, collections::VecDeque, ops::{Index, IndexMut}, }; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +use crate::state::{union_ordered_iter, AttributeMask, NodeMask, State}; + /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. /// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. #[derive(Debug)] -pub struct RealDom { +pub struct RealDom { root: usize, - nodes: Vec>>, + nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } -impl Default for RealDom { +impl Default for RealDom { fn default() -> Self { Self::new() } } -impl RealDom { - pub fn new() -> RealDom { +impl RealDom { + pub fn new() -> RealDom { RealDom { root: 0, nodes: { @@ -44,7 +48,7 @@ impl RealDom { } /// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state. - pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec { + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec<(usize, NodeMask)> { let mut nodes_updated = Vec::new(); for mutations in mutations_vec { for e in mutations.edits { @@ -66,7 +70,7 @@ impl RealDom { .collect(); for ns in drained { self.link_child(ns, target).unwrap(); - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); } } ReplaceWith { root, m } => { @@ -74,7 +78,7 @@ impl RealDom { let target = root.parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -82,7 +86,7 @@ impl RealDom { let target = self[root as usize].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -90,13 +94,13 @@ impl RealDom { let target = self[root as usize].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } Remove { root } => { if let Some(parent) = self[root as usize].parent { - nodes_updated.push(parent.0); + nodes_updated.push((parent.0, NodeMask::NONE)); } self.remove(root as usize).unwrap(); } @@ -162,7 +166,10 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push(root as usize); + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::NONE, false, false, true), + )); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -170,11 +177,19 @@ impl RealDom { _ => unreachable!(), } } - SetAttribute { root, .. } => { - nodes_updated.push(root as usize); + SetAttribute { root, field, .. } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false, false), + )); } - RemoveAttribute { root, .. } => { - nodes_updated.push(root as usize); + RemoveAttribute { + root, name: field, .. + } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false, false), + )); } PopRoot {} => { self.node_stack.pop(); @@ -190,89 +205,226 @@ impl RealDom { pub fn update_state( &mut self, vdom: &VirtualDom, - nodes_updated: Vec, - us_ctx: &mut US::Ctx, - ds_ctx: &mut DS::Ctx, + nodes_updated: Vec<(usize, NodeMask)>, + ctx: AnyMap, ) -> Option> { + #[derive(PartialEq, Clone, Debug)] + enum StatesToCheck { + All, + Some(Vec), + } + impl StatesToCheck { + fn union(&self, other: &Self) -> Self { + match (self, other) { + (Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter( + s.iter().copied(), + o.iter().copied(), + s.len() + o.len(), + )), + _ => Self::All, + } + } + } + + #[derive(Debug, Clone)] + struct NodeRef { + id: usize, + height: u16, + node_mask: NodeMask, + to_check: StatesToCheck, + } + impl NodeRef { + fn union_with(&mut self, other: &Self) { + self.node_mask = self.node_mask.union(&other.node_mask); + self.to_check = self.to_check.union(&other.to_check); + } + } + impl Eq for NodeRef {} + impl PartialEq for NodeRef { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.height == other.height + } + } + impl Ord for NodeRef { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } + } + impl PartialOrd for NodeRef { + fn partial_cmp(&self, other: &Self) -> Option { + // Sort nodes first by height, then if the height is the same id. + // The order of the id does not matter it just helps with binary search later + Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id))) + } + } + let mut to_rerender = FxHashSet::default(); - to_rerender.extend(nodes_updated.iter()); + to_rerender.extend(nodes_updated.iter().map(|(id, _)| id)); let mut nodes_updated: Vec<_> = nodes_updated .into_iter() - .map(|id| (id, self[id].height)) + .map(|(id, mask)| NodeRef { + id, + height: self[id].height, + node_mask: mask, + to_check: StatesToCheck::All, + }) .collect(); - // Sort nodes first by height, then if the height is the same id. - nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0))); - nodes_updated.dedup(); + nodes_updated.sort(); + + // Combine mutations that affect the same node. + let mut last_node: Option = None; + let mut new_nodes_updated = VecDeque::new(); + for current in nodes_updated.into_iter() { + if let Some(node) = &mut last_node { + if *node == current { + node.union_with(¤t); + } else { + new_nodes_updated.push_back(last_node.replace(current).unwrap()); + } + } else { + last_node = Some(current); + } + } + if let Some(node) = last_node { + new_nodes_updated.push_back(node); + } + let nodes_updated = new_nodes_updated; + + // update the state that only depends on nodes. The order does not matter. + for node_ref in &nodes_updated { + let mut changed = false; + let node = &mut self[node_ref.id]; + let ids = match &node_ref.to_check { + StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask), + StatesToCheck::Some(_) => unreachable!(), + }; + for ty in ids { + let node = &mut self[node_ref.id]; + let vnode = node.element(vdom); + changed |= node.state.update_node_dep_state(ty, vnode, &ctx); + } + if changed { + to_rerender.insert(node_ref.id); + } + } // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. - let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_bubble.pop_back() { - let node = &mut self[id as usize]; - let vnode = node.element(vdom); - let node_type = &node.node_type; - let up_state = &mut node.up_state; - let children = match node_type { - NodeType::Element { children, .. } => Some(children), - _ => None, + let mut to_bubble = nodes_updated.clone(); + while let Some(node_ref) = to_bubble.pop_back() { + let NodeRef { + id, + height, + node_mask, + to_check, + } = node_ref; + let (node, children) = self.get_node_children_mut(id).unwrap(); + let children_state: Vec<_> = children.iter().map(|c| &c.state).collect(); + let ids = match to_check { + StatesToCheck::All => node.state.child_dep_types(&node_mask), + StatesToCheck::Some(ids) => ids, }; - // todo: reduce cloning state - let old = up_state.clone(); - let mut new = up_state.clone(); - let parent = node.parent; - new.reduce( - children - .unwrap_or(&Vec::new()) - .clone() - .iter() - .map(|c| &self[*c].up_state), - vnode, - us_ctx, - ); - if new != old { - to_rerender.insert(id); - if let Some(p) = parent { - let i = to_bubble.partition_point(|(other_id, h)| { - *h < height - 1 || (*h == height - 1 && *other_id < p.0) - }); + let mut changed = Vec::new(); + for ty in ids { + let vnode = node.element(vdom); + if node + .state + .update_child_dep_state(ty, vnode, &children_state, &ctx) + { + changed.push(ty); + } + } + if let Some(parent_id) = node.parent { + if !changed.is_empty() { + to_rerender.insert(id); + let i = to_bubble.partition_point( + |NodeRef { + id: other_id, + height: h, + .. + }| { + *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0) + }, + ); // make sure the parent is not already queued - if i >= to_bubble.len() || to_bubble[i].0 != p.0 { - to_bubble.insert(i, (p.0, height - 1)); + if i < to_bubble.len() && to_bubble[i].id == parent_id.0 { + to_bubble[i].to_check = + to_bubble[i].to_check.union(&StatesToCheck::Some(changed)); + } else { + to_bubble.insert( + i, + NodeRef { + id: parent_id.0, + height: height - 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed), + }, + ); } } - let node = &mut self[id as usize]; - node.up_state = new; } } // push down state. To avoid calling reduce more times than nessisary start from the top and go down. - let mut to_push: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_push.pop_front() { - let node = &self[id as usize]; - // todo: reduce cloning state - let old = node.down_state.clone(); - let mut new = node.down_state.clone(); - let vnode = node.element(vdom); - new.reduce( - node.parent - .filter(|e| e.0 != 0) - .map(|e| &self[e].down_state), - vnode, - ds_ctx, - ); - if new != old { - to_rerender.insert(id); - let node = &mut self[id as usize]; + let mut to_push = nodes_updated.clone(); + while let Some(node_ref) = to_push.pop_front() { + let NodeRef { + id, + height, + node_mask, + to_check, + } = node_ref; + let node = &self[id]; + let ids = match to_check { + StatesToCheck::All => node.state.parent_dep_types(&node_mask), + StatesToCheck::Some(ids) => ids, + }; + let mut changed = Vec::new(); + let (node, parent) = self.get_node_parent_mut(id).unwrap(); + for ty in ids { + let vnode = node.element(vdom); + let parent = parent.as_deref(); + let state = &mut node.state; + if state.update_parent_dep_state( + ty, + vnode, + parent.filter(|n| n.id.0 != 0).map(|n| &n.state), + &ctx, + ) { + changed.push(ty); + } + } + + to_rerender.insert(id); + if !changed.is_empty() { + let node = &self[id]; if let NodeType::Element { children, .. } = &node.node_type { for c in children { - let i = to_push.partition_point(|(other_id, h)| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }); - if i >= to_push.len() || to_push[i].0 != c.0 { - to_push.insert(i, (c.0, height + 1)); + let i = to_push.partition_point( + |NodeRef { + id: other_id, + height: h, + .. + }| { + *h < height + 1 || (*h == height + 1 && *other_id < c.0) + }, + ); + if i < to_push.len() && to_push[i].id == c.0 { + to_push[i].to_check = to_push[i] + .to_check + .union(&StatesToCheck::Some(changed.clone())); + } else { + to_push.insert( + i, + NodeRef { + id: c.0, + height: height + 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed.clone()), + }, + ); } } } - node.down_state = new; } } @@ -300,12 +452,9 @@ impl RealDom { } // remove a node and it's children from the dom. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. - fn inner( - dom: &mut RealDom, - id: usize, - ) -> Option> { + fn inner(dom: &mut RealDom, id: usize) -> Option> { let mut node = dom.nodes[id as usize].take()?; if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { @@ -327,7 +476,7 @@ impl RealDom { Some(node) } - fn insert(&mut self, node: Node) { + fn insert(&mut self, node: Node) { let current_len = self.nodes.len(); let id = node.id.0; if current_len - 1 < node.id.0 { @@ -337,15 +486,65 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&Node> { + pub fn get(&self, id: usize) -> Option<&Node> { self.nodes.get(id)?.as_ref() } - pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { + pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { self.nodes.get_mut(id)?.as_mut() } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { + // this is safe because no node will have itself as a child + pub fn get_node_children_mut<'a>( + &'a mut self, + id: usize, + ) -> Option<(&'a mut Node, Vec<&'a mut Node>)> { + let ptr = self.nodes.as_mut_ptr(); + unsafe { + if id >= self.nodes.len() { + None + } else { + let node = &mut *ptr.add(id); + if let Some(node) = node.as_mut() { + let children = match &node.node_type { + NodeType::Element { children, .. } => children + .iter() + .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap()) + .collect(), + _ => Vec::new(), + }; + return Some((node, children)); + } else { + None + } + } + } + } + + // this is safe because no node will have itself as a parent + pub fn get_node_parent_mut<'a>( + &'a mut self, + id: usize, + ) -> Option<(&'a mut Node, Option<&'a mut Node>)> { + let ptr = self.nodes.as_mut_ptr(); + unsafe { + let node = &mut *ptr.add(id); + if id >= self.nodes.len() { + None + } else { + if let Some(node) = node.as_mut() { + let parent = node + .parent + .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap()); + return Some((node, parent)); + } else { + None + } + } + } + } + + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); @@ -416,12 +615,8 @@ impl RealDom { } /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { - fn inner( - dom: &RealDom, - id: ElementId, - f: &mut impl FnMut(&Node), - ) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { + fn inner(dom: &RealDom, id: ElementId, f: &mut impl FnMut(&Node)) { let node = &dom[id]; f(node); if let NodeType::Element { children, .. } = &node.node_type { @@ -438,12 +633,8 @@ impl RealDom { } /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { - fn inner( - dom: &mut RealDom, - id: ElementId, - f: &mut impl FnMut(&mut Node), - ) { + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { + fn inner(dom: &mut RealDom, id: ElementId, f: &mut impl FnMut(&mut Node)) { let node = &mut dom[id]; f(node); if let NodeType::Element { children, .. } = &mut node.node_type { @@ -461,28 +652,28 @@ impl RealDom { } } -impl Index for RealDom { - type Output = Node; +impl Index for RealDom { + type Output = Node; fn index(&self, idx: usize) -> &Self::Output { self.get(idx).expect("Node does not exist") } } -impl Index for RealDom { - type Output = Node; +impl Index for RealDom { + type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { &self[idx.0] } } -impl IndexMut for RealDom { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { self.get_mut(idx).expect("Node does not exist") } } -impl IndexMut for RealDom { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { &mut self[idx.0] } @@ -490,15 +681,13 @@ impl IndexMut for RealDom { +pub struct Node { /// The id of the node this node was created from. pub id: ElementId, /// The parent id of the node. pub parent: Option, - /// State of the node that is bubbled up to its parents. The state must depend only on the node and its children. - pub up_state: US, - /// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent. - pub down_state: DS, + /// State of the node. + pub state: S, /// Additional inforation specific to the node type pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. @@ -518,14 +707,13 @@ pub enum NodeType { Placeholder, } -impl Node { +impl Node { fn new(id: u64, node_type: NodeType) -> Self { Node { id: ElementId(id as usize), parent: None, node_type, - down_state: DS::default(), - up_state: US::default(), + state: S::default(), height: 0, } } @@ -551,39 +739,3 @@ impl Node { self.parent = Some(parent); } } - -/// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. -/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. -/// Called at most once per update. -pub trait PushedDownState: Default + PartialEq + Clone { - /// The context is passed to the [PushedDownState::reduce] when it is pushed down. - /// This is sometimes nessisary for lifetime purposes. - type Ctx; - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx); -} -impl PushedDownState for () { - type Ctx = (); - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {} -} - -/// This state is derived from children. For example a node's size could be derived from the size of children. -/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed. -/// Called at most once per update. -pub trait BubbledUpState: Default + PartialEq + Clone { - /// The context is passed to the [BubbledUpState::reduce] when it is bubbled up. - /// This is sometimes nessisary for lifetime purposes. - type Ctx; - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a; -} -impl BubbledUpState for () { - type Ctx = (); - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - } -} diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs new file mode 100644 index 000000000..1e7135580 --- /dev/null +++ b/packages/native-core/src/state.rs @@ -0,0 +1,311 @@ +use std::{any::TypeId, fmt::Debug}; + +use anymap::AnyMap; +use dioxus_core::{Attribute, ElementId, VElement, VNode, VText}; + +pub(crate) fn union_ordered_iter( + s_iter: impl Iterator, + o_iter: impl Iterator, + new_len_guess: usize, +) -> Vec { + let mut s_peekable = s_iter.peekable(); + let mut o_peekable = o_iter.peekable(); + let mut v = Vec::with_capacity(new_len_guess); + while let Some(s_i) = s_peekable.peek() { + loop { + if let Some(o_i) = o_peekable.peek() { + if o_i > s_i { + break; + } else if o_i < s_i { + v.push(o_peekable.next().unwrap()); + } + } else { + break; + } + } + v.push(s_peekable.next().unwrap()); + } + while let Some(o_i) = o_peekable.next() { + v.push(o_i); + } + for w in v.windows(2) { + debug_assert!(w[1] > w[0]); + } + v +} + +#[derive(Debug)] +pub struct NodeView<'a> { + inner: &'a VNode<'a>, + mask: NodeMask, +} +impl<'a> NodeView<'a> { + pub fn new(vnode: &'a VNode<'a>, view: NodeMask) -> Self { + Self { + inner: vnode, + mask: view, + } + } + + pub fn id(&self) -> ElementId { + self.inner.mounted_id() + } + + pub fn tag(&self) -> Option<&'a str> { + self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten() + } + + pub fn namespace(&self) -> Option<&'a str> { + self.mask + .namespace + .then(|| self.el().map(|el| el.namespace).flatten()) + .flatten() + } + + pub fn attributes(&self) -> impl Iterator> { + self.el() + .map(|el| el.attributes) + .unwrap_or_default() + .iter() + .filter(|a| self.mask.attritutes.contains_attribute(&a.name)) + } + + pub fn text(&self) -> Option<&str> { + self.mask + .text + .then(|| self.txt().map(|txt| txt.text)) + .flatten() + } + + fn el(&self) -> Option<&'a VElement<'a>> { + if let VNode::Element(el) = &self.inner { + Some(el) + } else { + None + } + } + + fn txt(&self) -> Option<&'a VText<'a>> { + if let VNode::Text(txt) = &self.inner { + Some(txt) + } else { + None + } + } +} + +#[derive(PartialEq, Clone, Debug)] +pub enum AttributeMask { + All, + Dynamic(Vec<&'static str>), + Static(&'static [&'static str]), +} + +impl AttributeMask { + pub const NONE: Self = Self::Static(&[]); + + fn contains_attribute(&self, attr: &'static str) -> bool { + match self { + AttributeMask::All => true, + AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(), + AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), + } + } + + pub fn single(new: &'static str) -> Self { + Self::Dynamic(vec![new]) + } + + pub fn verify(&self) { + match &self { + AttributeMask::Static(attrs) => debug_assert!( + attrs.windows(2).all(|w| w[0] < w[1]), + "attritutes must be increasing" + ), + AttributeMask::Dynamic(attrs) => debug_assert!( + attrs.windows(2).all(|w| w[0] < w[1]), + "attritutes must be increasing" + ), + _ => (), + } + } + + pub fn union(&self, other: &Self) -> Self { + let new = match (self, other) { + (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + (AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic( + union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()), + ), + _ => AttributeMask::All, + }; + new.verify(); + new + } + + fn overlaps(&self, other: &Self) -> bool { + fn overlaps_iter( + mut self_iter: impl Iterator, + mut other_iter: impl Iterator, + ) -> bool { + if let Some(mut other_attr) = other_iter.next() { + while let Some(self_attr) = self_iter.next() { + while other_attr < self_attr { + if let Some(attr) = other_iter.next() { + other_attr = attr; + } else { + return false; + } + } + if other_attr == self_attr { + return true; + } + } + } + false + } + match (self, other) { + (AttributeMask::All, AttributeMask::All) => true, + (AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(), + (AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(), + (AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(), + (AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(), + (AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => { + overlaps_iter(v1.iter().copied(), v2.iter().copied()) + } + (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => { + overlaps_iter(v.iter().copied(), s.iter().copied()) + } + (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => { + overlaps_iter(v.iter().copied(), s.iter().copied()) + } + (AttributeMask::Static(s1), AttributeMask::Static(s2)) => { + overlaps_iter(s1.iter().copied(), s2.iter().copied()) + } + } + } +} + +impl Default for AttributeMask { + fn default() -> Self { + AttributeMask::Static(&[]) + } +} + +#[derive(Default, PartialEq, Clone, Debug)] +pub struct NodeMask { + // must be sorted + attritutes: AttributeMask, + tag: bool, + namespace: bool, + text: bool, +} + +impl NodeMask { + pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false, false); + pub const ALL: Self = Self::new(AttributeMask::All, true, true, true); + + /// attritutes must be sorted! + pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool, text: bool) -> Self { + Self { + attritutes, + tag, + namespace, + text, + } + } + + pub fn overlaps(&self, other: &Self) -> bool { + (self.tag && other.tag) + || (self.namespace && other.namespace) + || self.attritutes.overlaps(&other.attritutes) + || (self.text && other.text) + } + + pub fn union(&self, other: &Self) -> Self { + Self { + attritutes: self.attritutes.union(&other.attritutes), + tag: self.tag | other.tag, + namespace: self.namespace | other.namespace, + text: self.text | other.text, + } + } +} + +/// This state is derived from children. For example a node's size could be derived from the size of children. +/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed. +/// Called at most once per update. +pub trait ChildDepState { + /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// This is sometimes nessisary for lifetime purposes. + type Ctx; + type DepState: ChildDepState; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a; +} + +/// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. +/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. +/// Called at most once per update. +pub trait ParentDepState { + /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// This is sometimes nessisary for lifetime purposes. + type Ctx; + type DepState: ParentDepState; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; +} + +/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. +/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. +/// Called at most once per update. +pub trait NodeDepState { + type Ctx; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; +} + +pub trait State: Default + Clone { + fn update_node_dep_state<'a>( + &'a mut self, + ty: TypeId, + node: &'a VNode<'a>, + ctx: &AnyMap, + ) -> bool; + /// This must be a valid resolution order. (no nodes updated before a state they rely on) + fn child_dep_types(&self, mask: &NodeMask) -> Vec; + + fn update_parent_dep_state<'a>( + &'a mut self, + ty: TypeId, + node: &'a VNode<'a>, + parent: Option<&Self>, + ctx: &AnyMap, + ) -> bool; + /// This must be a valid resolution order. (no nodes updated before a state they rely on) + fn parent_dep_types(&self, mask: &NodeMask) -> Vec; + + fn update_child_dep_state<'a>( + &'a mut self, + ty: TypeId, + node: &'a VNode<'a>, + children: &[&Self], + ctx: &AnyMap, + ) -> bool; + /// This must be a valid resolution order. (no nodes updated before a state they rely on) + fn node_dep_types(&self, mask: &NodeMask) -> Vec; +} diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index 6afb225d3..8f4e6c8f0 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -3,8 +3,13 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::state::State; +use dioxus_native_core_macro::State; use std::cell::Cell; +#[derive(State, Default, Clone)] +struct Empty {} + #[test] fn remove_node() { #[allow(non_snake_case)] @@ -20,7 +25,7 @@ fn remove_node() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let child_div = VElement { @@ -92,7 +97,7 @@ fn add_node() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index df054dcdc..750987a29 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -5,6 +5,11 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::state::State; +use dioxus_native_core_macro::State; + +#[derive(State, Default, Clone)] +struct Empty {} #[test] fn initial_build_simple() { @@ -21,7 +26,7 @@ fn initial_build_simple() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let root_div = VElement { @@ -60,7 +65,7 @@ fn initial_build_with_children() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let first_text = VText { diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs new file mode 100644 index 000000000..a0bbd4f27 --- /dev/null +++ b/packages/native-core/tests/parse.rs @@ -0,0 +1,65 @@ +use dioxus_native_core::state::*; +use dioxus_native_core_macro::*; + +#[derive(State, Default, Clone)] +struct Z { + // depends on just attributes and no context + #[node_dep_state()] + x: A, + // depends on attributes, the B component of children and i32 context + #[child_dep_state(B, i32)] + y: B, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(C, u8)] + z: C, +} + +// struct Z { +// x: A, +// y: B, +// z: C, +// } + +use dioxus_native_core::state::NodeDepState; + +#[derive(Default, Clone)] +struct A; +impl NodeDepState for A { + type Ctx = (); + fn reduce(&mut self, _: NodeView, _: &()) -> bool { + todo!() + } +} + +#[derive(Default, Clone)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce<'a>( + &mut self, + _: dioxus_native_core::state::NodeView, + _: impl Iterator, + _: &i32, + ) -> bool + where + Self::DepState: 'a, + { + todo!() + } +} + +#[derive(Default, Clone)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::state::NodeView, + _: Option<&Self::DepState>, + _: &u8, + ) -> bool { + todo!() + } +} diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs deleted file mode 100644 index ff0dee1d5..000000000 --- a/packages/native-core/tests/state.rs +++ /dev/null @@ -1,320 +0,0 @@ -use dioxus_core::VNode; -use dioxus_core::*; -use dioxus_core_macro::*; -use dioxus_html as dioxus_elements; -use dioxus_native_core::real_dom::*; - -#[derive(Debug, Clone, PartialEq, Default)] -struct CallCounter(u32); -impl BubbledUpState for CallCounter { - type Ctx = (); - - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - self.0 += 1; - } -} - -impl PushedDownState for CallCounter { - type Ctx = (); - - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { - self.0 += 1; - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -struct BubbledUpStateTester(String, Vec>); -impl BubbledUpState for BubbledUpStateTester { - type Ctx = u32; - - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - assert_eq!(*ctx, 42); - *self = BubbledUpStateTester( - vnode.mounted_id().to_string(), - children.map(|c| Box::new(c.clone())).collect(), - ); - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -struct PushedDownStateTester(String, Option>); -impl PushedDownState for PushedDownStateTester { - type Ctx = u32; - - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) { - assert_eq!(*ctx, 42); - *self = PushedDownStateTester( - vnode.mounted_id().to_string(), - parent.map(|c| Box::new(c.clone())), - ); - } -} - -#[test] -fn state_initial() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div { - p{} - h1{} - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - p{} - h1{} - } - }); - - let mut dom: RealDom = RealDom::new(); - - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42); - - let root_div = &dom[1]; - assert_eq!(root_div.up_state.0, "1"); - assert_eq!( - root_div.up_state.1, - vec![ - Box::new(BubbledUpStateTester("2".to_string(), Vec::new())), - Box::new(BubbledUpStateTester("3".to_string(), Vec::new())) - ] - ); - assert_eq!(root_div.down_state.0, "1"); - assert_eq!(root_div.down_state.1, None); - - let child_p = &dom[2]; - assert_eq!(child_p.up_state.0, "2"); - assert_eq!(child_p.up_state.1, Vec::new()); - assert_eq!(child_p.down_state.0, "2"); - assert_eq!( - child_p.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) - ); - - let child_h1 = &dom[3]; - assert_eq!(child_h1.up_state.0, "3"); - assert_eq!(child_h1.up_state.1, Vec::new()); - assert_eq!(child_h1.down_state.0, "3"); - assert_eq!( - child_h1.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) - ); -} - -#[test] -fn state_reduce_initally_called_minimally() { - #[derive(Debug, Clone, PartialEq, Default)] - struct CallCounter(u32); - impl BubbledUpState for CallCounter { - type Ctx = (); - - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - self.0 += 1; - } - } - - impl PushedDownState for CallCounter { - type Ctx = (); - - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { - self.0 += 1; - } - } - - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); - - let mut dom: RealDom = RealDom::new(); - - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - - dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, 1); - assert_eq!(n.down_state.0, 1); - }); -} - -#[test] -fn state_reduce_down_called_minimally_on_update() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - width: "100%", - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); - - let mut dom: RealDom = RealDom::new(); - - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - let nodes_updated = dom.apply_mutations(vec![Mutations { - edits: vec![DomEdit::SetAttribute { - root: 1, - field: "width", - value: "99%", - ns: Some("style"), - }], - dirty_scopes: fxhash::FxHashSet::default(), - refs: Vec::new(), - }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - - dom.traverse_depth_first(|n| { - assert_eq!(n.down_state.0, 2); - }); -} - -#[test] -fn state_reduce_up_called_minimally_on_update() { - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div { - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - }) - } - - let vdom = VirtualDom::new(Base); - - let mutations = vdom.create_vnodes(rsx! { - div { - width: "100%", - div{ - div{ - p{} - } - p{ - "hello" - } - div{ - h1{} - } - p{ - "world" - } - } - } - }); - - let mut dom: RealDom = RealDom::new(); - - let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - let nodes_updated = dom.apply_mutations(vec![Mutations { - edits: vec![DomEdit::SetAttribute { - root: 4, - field: "width", - value: "99%", - ns: Some("style"), - }], - dirty_scopes: fxhash::FxHashSet::default(), - refs: Vec::new(), - }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - - dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); - }); -} diff --git a/packages/native-core/tests/update_state.rs b/packages/native-core/tests/update_state.rs new file mode 100644 index 000000000..d4825fdd5 --- /dev/null +++ b/packages/native-core/tests/update_state.rs @@ -0,0 +1,402 @@ +use anymap::AnyMap; +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::real_dom::*; +use dioxus_native_core::state::{ + AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, +}; +use dioxus_native_core_macro::State; + +#[derive(Debug, Clone, Default, State)] +struct CallCounterState { + #[child_dep_state(ChildDepCallCounter)] + child_counter: ChildDepCallCounter, + #[parent_dep_state(ParentDepCallCounter)] + parent_counter: ParentDepCallCounter, + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} + +#[derive(Debug, Clone, Default)] +struct ChildDepCallCounter(u32); +impl ChildDepState for ChildDepCallCounter { + type Ctx = (); + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + self.0 += 1; + true + } +} + +#[derive(Debug, Clone, Default)] +struct ParentDepCallCounter(u32); +impl ParentDepState for ParentDepCallCounter { + type Ctx = (); + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _node: NodeView, + _parent: Option<&Self::DepState>, + _ctx: &Self::Ctx, + ) -> bool { + self.0 += 1; + true + } +} + +#[derive(Debug, Clone, Default)] +struct NodeDepCallCounter(u32); +impl NodeDepState for NodeDepCallCounter { + type Ctx = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct BubbledUpStateTester(Option, Vec>); +impl ChildDepState for BubbledUpStateTester { + type Ctx = u32; + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + assert_eq!(*ctx, 42); + *self = BubbledUpStateTester( + node.tag().map(|s| s.to_string()), + children.into_iter().map(|c| Box::new(c.clone())).collect(), + ); + true + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct PushedDownStateTester(Option, Option>); +impl ParentDepState for PushedDownStateTester { + type Ctx = u32; + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { + assert_eq!(*ctx, 42); + *self = PushedDownStateTester( + node.tag().map(|s| s.to_string()), + parent.map(|c| Box::new(c.clone())), + ); + true + } +} + +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(BubbledUpStateTester, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(PushedDownStateTester, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(u32)] + node: NodeStateTester, +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct NodeStateTester(Option, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); + fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { + assert_eq!(*ctx, 42); + *self = NodeStateTester( + node.tag().map(|s| s.to_string()), + node.attributes() + .map(|a| (a.name.to_string(), a.value.to_string())) + .collect(), + ); + true + } +} + +#[test] +fn state_initial() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + p{} + h1{} + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + p{ + color: "red" + } + h1{} + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let mut ctx = AnyMap::new(); + ctx.insert(42u32); + let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx); + + let root_div = &dom[1]; + assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); + assert_eq!( + root_div.state.bubbled.1, + vec![ + Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), + Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) + ] + ); + assert_eq!(root_div.state.pushed.0, Some("div".to_string())); + assert_eq!(root_div.state.pushed.1, None); + assert_eq!(root_div.state.node.0, Some("div".to_string())); + assert_eq!(root_div.state.node.1, vec![]); + + let child_p = &dom[2]; + assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); + assert_eq!(child_p.state.bubbled.1, Vec::new()); + assert_eq!(child_p.state.pushed.0, Some("p".to_string())); + assert_eq!( + child_p.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + None + ))) + ); + assert_eq!(child_p.state.node.0, Some("p".to_string())); + assert_eq!( + child_p.state.node.1, + vec![("color".to_string(), "red".to_string())] + ); + + let child_h1 = &dom[3]; + assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); + assert_eq!(child_h1.state.bubbled.1, Vec::new()); + assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); + assert_eq!( + child_h1.state.pushed.1, + Some(Box::new(PushedDownStateTester( + Some("div".to_string()), + None + ))) + ); + assert_eq!(child_h1.state.node.0, Some("h1".to_string())); + assert_eq!(child_h1.state.node.1, vec![]); +} + +#[test] +fn state_reduce_initally_called_minimally() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + dom.traverse_depth_first(|n| { + println!("{:#?}", n.state); + }); + + dom.traverse_depth_first(|n| { + assert_eq!(n.state.child_counter.0, 1); + assert_eq!(n.state.parent_counter.0, 1); + assert_eq!(n.state.node_counter.0, 1); + }); +} + +#[test] +fn state_reduce_down_called_minimally_on_update() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + let nodes_updated = dom.apply_mutations(vec![Mutations { + edits: vec![DomEdit::SetAttribute { + root: 1, + field: "width", + value: "99%", + ns: Some("style"), + }], + dirty_scopes: fxhash::FxHashSet::default(), + refs: Vec::new(), + }]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + dom.traverse_depth_first(|n| { + // println!("{:?}", n.state); + assert_eq!(n.state.parent_counter.0, 2); + }); + // panic!() +} + +#[test] +fn state_reduce_up_called_minimally_on_update() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + let nodes_updated = dom.apply_mutations(vec![Mutations { + edits: vec![DomEdit::SetAttribute { + root: 4, + field: "width", + value: "99%", + ns: Some("style"), + }], + dirty_scopes: fxhash::FxHashSet::default(), + refs: Vec::new(), + }]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + dom.traverse_depth_first(|n| { + assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); + }); +} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 6a65af607..43064bb63 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -25,3 +25,4 @@ futures = "0.3.19" stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" \ No newline at end of file diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 512fab8ba..c09eed50b 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,7 +5,6 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::real_dom::{Node, RealDom}; use std::{ any::Any, cell::RefCell, @@ -15,8 +14,7 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; -use crate::layout::StretchLayout; -use crate::style_attributes::StyleModifier; +use crate::{Dom, Node}; // a wrapper around the input state for easier access // todo: fix loop @@ -166,7 +164,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { let previous_mouse = self .mouse @@ -191,7 +189,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -215,8 +213,8 @@ impl InnerInputState { data: Arc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &Node, - dom: &RealDom, + node: &Node, + dom: &Dom, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { @@ -261,7 +259,7 @@ impl InnerInputState { // mousemove let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousemove") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -285,7 +283,7 @@ impl InnerInputState { // mouseenter let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseenter") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -309,7 +307,7 @@ impl InnerInputState { // mouseover let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseover") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -333,7 +331,7 @@ impl InnerInputState { // mousedown let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.clicked { @@ -353,7 +351,7 @@ impl InnerInputState { // mouseup let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseup") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released { @@ -373,7 +371,7 @@ impl InnerInputState { // click let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("click") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released && data.mouse_data.button == 0 { @@ -393,7 +391,7 @@ impl InnerInputState { // contextmenu let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("contextmenu") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if currently_contains && data.released && data.mouse_data.button == 2 { @@ -413,7 +411,7 @@ impl InnerInputState { // wheel let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("wheel") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); if let Some(w) = data.wheel_data { @@ -435,7 +433,7 @@ impl InnerInputState { // mouseleave let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseleave") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -459,7 +457,7 @@ impl InnerInputState { // mouseout let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseout") { - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let previously_contained = data .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -522,11 +520,7 @@ impl RinkInputHandler { ) } - pub fn get_events( - &self, - layout: &Stretch, - dom: &mut RealDom, - ) -> Vec { + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 4224fbdd5..5924a0fd1 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,83 +1,90 @@ +use std::cell::RefCell; +use std::rc::Rc; + use dioxus_core::*; use dioxus_native_core::layout_attributes::apply_layout_attributes; -use dioxus_native_core::real_dom::BubbledUpState; +use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView}; use stretch2::prelude::*; -/// the size #[derive(Clone, PartialEq, Default, Debug)] pub struct StretchLayout { pub style: Style, pub node: Option, } -impl BubbledUpState for StretchLayout { - type Ctx = Stretch; +impl ChildDepState for StretchLayout { + type Ctx = Rc>; + type DepState = Self; + // todo: update mask + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true); /// Setup the layout - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx) + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool where - I: Iterator, - Self: 'a, + Self::DepState: 'a, { - match vnode { - VNode::Text(t) => { - let char_len = t.text.chars().count(); + let mut stretch = ctx.borrow_mut(); + if let Some(text) = node.text() { + let char_len = text.chars().count(); - let style = Style { - size: Size { - // characters are 1 point tall - height: Dimension::Points(1.0), + let style = Style { + size: Size { + // characters are 1 point tall + height: Dimension::Points(1.0), - // text is as long as it is declared - width: Dimension::Points(char_len as f32), - }, - ..Default::default() - }; + // text is as long as it is declared + width: Dimension::Points(char_len as f32), + }, + ..Default::default() + }; - if let Some(n) = self.node { - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); + if let Some(n) = self.node { + if self.style != style { + stretch.set_style(n, style).unwrap(); } - - self.style = style; + } else { + self.node = Some(stretch.new_node(style, &[]).unwrap()); } - VNode::Element(el) => { - // gather up all the styles from the attribute list - let mut style = Style::default(); - for &Attribute { name, value, .. } in el.attributes { - apply_layout_attributes(name, value, &mut style); - } + self.style = style; + } else { + // gather up all the styles from the attribute list + let mut style = Style::default(); - // the root node fills the entire area - if el.id.get() == Some(ElementId(0)) { - apply_layout_attributes("width", "100%", &mut style); - apply_layout_attributes("height", "100%", &mut style); - } - - // Set all direct nodes as our children - let mut child_layout = vec![]; - for l in children { - child_layout.push(l.node.unwrap()); - } - - if let Some(n) = self.node { - if stretch.children(n).unwrap() != child_layout { - stretch.set_children(n, &child_layout).unwrap(); - } - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &child_layout).unwrap()); - } - - self.style = style; + for &Attribute { name, value, .. } in node.attributes() { + apply_layout_attributes(name, value, &mut style); } - _ => (), + + // the root node fills the entire area + if node.id() == ElementId(0) { + apply_layout_attributes("width", "100%", &mut style); + apply_layout_attributes("height", "100%", &mut style); + } + + // Set all direct nodes as our children + let mut child_layout = vec![]; + for l in children { + child_layout.push(l.node.unwrap()); + } + + if let Some(n) = self.node { + if stretch.children(n).unwrap() != child_layout { + stretch.set_children(n, &child_layout).unwrap(); + } + if self.style != style { + stretch.set_style(n, style).unwrap(); + } + } else { + self.node = Some(stretch.new_node(style, &child_layout).unwrap()); + } + + self.style = style; } + true } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 03e137111..872356efb 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use anymap::AnyMap; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, execute, @@ -6,12 +7,14 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; use layout::StretchLayout; +use std::cell::RefCell; +use std::rc::Rc; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; @@ -27,7 +30,18 @@ mod widget; pub use config::*; pub use hooks::*; -pub use render::*; + +type Dom = RealDom; +type Node = dioxus_native_core::real_dom::Node; + +#[derive(Debug, Clone, State, Default)] +struct NodeState { + #[child_dep_state(StretchLayout, RefCell)] + layout: StretchLayout, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(StyleModifier)] + style: StyleModifier, +} #[derive(Clone)] pub struct TuiContext { @@ -70,13 +84,13 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: Dom = RealDom::new(); let mutations = dom.rebuild(); let to_update = rdom.apply_mutations(vec![mutations]); - let mut stretch = Stretch::new(); - let _to_rerender = rdom - .update_state(&dom, to_update, &mut stretch, &mut ()) - .unwrap(); + let stretch = Rc::new(RefCell::new(Stretch::new())); + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); render_vdom( &mut dom, @@ -95,8 +109,8 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut rdom: RealDom, - mut stretch: Stretch, + mut rdom: Dom, + stretch: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() @@ -114,7 +128,7 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; loop { @@ -131,15 +145,11 @@ fn render_vdom( if !to_rerender.is_empty() || resized { resized = false; - fn resize( - dims: Rect, - stretch: &mut Stretch, - rdom: &RealDom, - ) { + fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) { let width = dims.width; let height = dims.height; let root_id = rdom.root_id(); - let root_node = rdom[root_id].up_state.node.unwrap(); + let root_node = rdom[root_id].state.layout.node.unwrap(); stretch .compute_layout( @@ -154,9 +164,9 @@ fn render_vdom( if let Some(terminal) = &mut terminal { terminal.draw(|frame| { // size is guaranteed to not change when rendering - resize(frame.size(), &mut stretch, &rdom); + resize(frame.size(), &mut stretch.borrow_mut(), &rdom); let root = &rdom[rdom.root_id()]; - render::render_vnode(frame, &stretch, &rdom, &root, cfg); + render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg); })?; } else { resize( @@ -166,7 +176,7 @@ fn render_vdom( width: 300, height: 300, }, - &mut stretch, + &mut stretch.borrow_mut(), &rdom, ); } @@ -207,7 +217,7 @@ fn render_vdom( { // resolve events before rendering - let evts = handler.get_events(&stretch, &mut rdom); + let evts = handler.get_events(&stretch.borrow(), &mut rdom); for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } @@ -215,11 +225,9 @@ fn render_vdom( // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); // update the style and layout - to_rerender.extend( - rdom.update_state(vdom, to_update, &mut stretch, &mut ()) - .unwrap() - .iter(), - ) + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap(); } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 94cd90a62..e3f03f0d8 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,8 +1,4 @@ -use crate::layout::StretchLayout; -use dioxus_native_core::{ - layout_attributes::UnitSystem, - real_dom::{Node, RealDom}, -}; +use dioxus_native_core::layout_attributes::UnitSystem; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -15,16 +11,16 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, StyleModifier, + Config, Dom, Node, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; -pub fn render_vnode( +pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, - rdom: &RealDom, - node: &Node, + rdom: &Dom, + node: &Node, cfg: Config, ) { use dioxus_native_core::real_dom::NodeType; @@ -33,7 +29,7 @@ pub fn render_vnode( return; } - let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); + let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap(); let Point { x, y } = location; let Size { width, height } = size; @@ -59,7 +55,7 @@ pub fn render_vnode( let label = Label { text, - style: node.down_state.style, + style: node.state.style.style, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -84,7 +80,7 @@ pub fn render_vnode( } } -impl RinkWidget for &Node { +impl RinkWidget for &Node { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -268,14 +264,14 @@ impl RinkWidget for &Node { for x in area.left()..area.right() { for y in area.top()..area.bottom() { let mut new_cell = RinkCell::default(); - if let Some(c) = self.down_state.style.bg { + if let Some(c) = self.state.style.style.bg { new_cell.bg = c; } buf.set(x, y, new_cell); } } - let borders = &self.down_state.modifier.borders; + let borders = &self.state.style.modifier.borders; let last_edge = &borders.left; let current_edge = &borders.top; @@ -292,7 +288,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -327,7 +323,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -362,7 +358,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -397,7 +393,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.down_state.style.fg); + let color = current_edge.color.or(self.state.style.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 710610104..b49707668 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -29,10 +29,10 @@ - [ ] pub aspect_ratio: Number, */ -use dioxus_core::{Attribute, VNode}; +use dioxus_core::Attribute; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, - real_dom::PushedDownState, + state::{AttributeMask, NodeMask, NodeView, ParentDepState}, }; use crate::style::{RinkColor, RinkStyle}; @@ -43,18 +43,22 @@ pub struct StyleModifier { pub modifier: TuiModifier, } -impl PushedDownState for StyleModifier { +impl ParentDepState for StyleModifier { type Ctx = (); + type DepState = Self; + // todo: seperate each attribute into it's own class + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false); - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) { + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { *self = StyleModifier::default(); if parent.is_some() { self.style.fg = None; } - if let VNode::Element(el) = vnode { - // handle text modifier elements - if el.namespace.is_none() { - match el.tag { + + // handle text modifier elements + if node.namespace().is_none() { + if let Some(tag) = node.tag() { + match tag { "b" => apply_style_attributes("font-weight", "bold", self), "strong" => apply_style_attributes("font-weight", "bold", self), "u" => apply_style_attributes("text-decoration", "underline", self), @@ -68,11 +72,11 @@ impl PushedDownState for StyleModifier { _ => (), } } + } - // gather up all the styles from the attribute list - for &Attribute { name, value, .. } in el.attributes { - apply_style_attributes(name, value, self); - } + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in node.attributes() { + apply_style_attributes(name, value, self); } // keep the text styling from the parent element @@ -81,6 +85,7 @@ impl PushedDownState for StyleModifier { new_style.bg = self.style.bg; self.style = new_style; } + true } }