Explorar el Código

new dioxus_core api

Demonthos hace 3 años
padre
commit
628638b9af

+ 2 - 1
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-liveview = { path = "./packages/liveview", optional = true }
 
 
 dioxus-native-core = { path = "./packages/native-core", 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-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
 # dioxus-rsx = { path = "./packages/rsx", optional = true }
 # dioxus-rsx = { path = "./packages/rsx", optional = true }
@@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"]
 router = ["dioxus-router"]
 router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]
 liveview = ["dioxus-liveview"]
-native-core = ["dioxus-native-core"]
+native-core = ["dioxus-native-core", "dioxus-native-core-macro"]
 
 
 
 
 [workspace]
 [workspace]

+ 12 - 0
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"

+ 345 - 0
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<std::any::TypeId>{
+                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<std::any::TypeId>{
+                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<std::any::TypeId>{
+                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<Member>,
+}
+
+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<StateMember<'a>>,
+}
+
+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<Type>,
+    dep_ty: Option<Type>,
+}
+
+impl Parse for DepTypes {
+    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
+        let dep_ty = input.parse().ok();
+        let comma: Option<Token![,]> = input.parse().ok();
+        let ctx_ty = input.parse().ok();
+        Ok(Self {
+            ctx_ty: comma.and(ctx_ty),
+            dep_ty,
+        })
+    }
+}
+
+struct NodeDepTypes {
+    ctx_ty: Option<Type>,
+}
+
+impl Parse for NodeDepTypes {
+    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
+        let ctx_ty = input.parse().ok();
+        Ok(Self { ctx_ty })
+    }
+}
+
+impl From<NodeDepTypes> 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<Self> {
+        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<Type>,
+}
+
+impl<'a> StateMember<'a> {
+    fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option<StateMember<'a>> {
+        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::<NodeDepTypes>().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
+        })
+    }
+}

+ 2 - 0
packages/native-core/Cargo.toml

@@ -10,10 +10,12 @@ homepage = "https://dioxuslabs.com"
 dioxus-core = { path = "../core", version = "^0.2.0" }
 dioxus-core = { path = "../core", version = "^0.2.0" }
 dioxus-html = { path = "../html", version = "^0.2.0" }
 dioxus-html = { path = "../html", version = "^0.2.0" }
 dioxus-core-macro = { path = "../core-macro", 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" }
 stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
 smallvec = "1.6"
 smallvec = "1.6"
 fxhash = "0.2"
 fxhash = "0.2"
+anymap = "0.12.1"
 
 
 [dev-dependencies]
 [dev-dependencies]
 rand = "0.8.5"
 rand = "0.8.5"

+ 2 - 0
packages/native-core/src/lib.rs

@@ -1,2 +1,4 @@
 pub mod layout_attributes;
 pub mod layout_attributes;
 pub mod real_dom;
 pub mod real_dom;
+pub mod state;
+pub use dioxus_native_core_macro;

+ 302 - 150
packages/native-core/src/real_dom.rs

@@ -1,30 +1,34 @@
+use anymap::AnyMap;
 use fxhash::{FxHashMap, FxHashSet};
 use fxhash::{FxHashMap, FxHashSet};
 use std::{
 use std::{
+    any::TypeId,
     collections::VecDeque,
     collections::VecDeque,
     ops::{Index, IndexMut},
     ops::{Index, IndexMut},
 };
 };
 
 
 use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
 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.
 /// 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.
 /// 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.
 /// 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)]
 #[derive(Debug)]
-pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> {
+pub struct RealDom<S: State> {
     root: usize,
     root: usize,
-    nodes: Vec<Option<Node<US, DS>>>,
+    nodes: Vec<Option<Node<S>>>,
     nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
     nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
     node_stack: smallvec::SmallVec<[usize; 10]>,
     node_stack: smallvec::SmallVec<[usize; 10]>,
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> Default for RealDom<US, DS> {
+impl<S: State> Default for RealDom<S> {
     fn default() -> Self {
     fn default() -> Self {
         Self::new()
         Self::new()
     }
     }
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
-    pub fn new() -> RealDom<US, DS> {
+impl<S: State> RealDom<S> {
+    pub fn new() -> RealDom<S> {
         RealDom {
         RealDom {
             root: 0,
             root: 0,
             nodes: {
             nodes: {
@@ -44,7 +48,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     }
     }
 
 
     /// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
     /// 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<Mutations>) -> Vec<usize> {
+    pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
         let mut nodes_updated = Vec::new();
         let mut nodes_updated = Vec::new();
         for mutations in mutations_vec {
         for mutations in mutations_vec {
             for e in mutations.edits {
             for e in mutations.edits {
@@ -66,7 +70,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                             .collect();
                             .collect();
                         for ns in drained {
                         for ns in drained {
                             self.link_child(ns, target).unwrap();
                             self.link_child(ns, target).unwrap();
-                            nodes_updated.push(ns);
+                            nodes_updated.push((ns, NodeMask::ALL));
                         }
                         }
                     }
                     }
                     ReplaceWith { root, m } => {
                     ReplaceWith { root, m } => {
@@ -74,7 +78,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                         let target = root.parent.unwrap().0;
                         let target = root.parent.unwrap().0;
                         let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
                         let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
                         for ns in drained {
                         for ns in drained {
-                            nodes_updated.push(ns);
+                            nodes_updated.push((ns, NodeMask::ALL));
                             self.link_child(ns, target).unwrap();
                             self.link_child(ns, target).unwrap();
                         }
                         }
                     }
                     }
@@ -82,7 +86,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                         let target = self[root as usize].parent.unwrap().0;
                         let target = self[root as usize].parent.unwrap().0;
                         let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
                         let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
                         for ns in drained {
                         for ns in drained {
-                            nodes_updated.push(ns);
+                            nodes_updated.push((ns, NodeMask::ALL));
                             self.link_child(ns, target).unwrap();
                             self.link_child(ns, target).unwrap();
                         }
                         }
                     }
                     }
@@ -90,13 +94,13 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                         let target = self[root as usize].parent.unwrap().0;
                         let target = self[root as usize].parent.unwrap().0;
                         let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
                         let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
                         for ns in drained {
                         for ns in drained {
-                            nodes_updated.push(ns);
+                            nodes_updated.push((ns, NodeMask::ALL));
                             self.link_child(ns, target).unwrap();
                             self.link_child(ns, target).unwrap();
                         }
                         }
                     }
                     }
                     Remove { root } => {
                     Remove { root } => {
                         if let Some(parent) = self[root as usize].parent {
                         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();
                         self.remove(root as usize).unwrap();
                     }
                     }
@@ -162,7 +166,10 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                         text: new_text,
                         text: new_text,
                     } => {
                     } => {
                         let target = &mut self[root as usize];
                         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 {
                         match &mut target.node_type {
                             NodeType::Text { text } => {
                             NodeType::Text { text } => {
                                 *text = new_text.to_string();
                                 *text = new_text.to_string();
@@ -170,11 +177,19 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
                             _ => unreachable!(),
                             _ => 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 {} => {
                     PopRoot {} => {
                         self.node_stack.pop();
                         self.node_stack.pop();
@@ -190,89 +205,226 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     pub fn update_state(
     pub fn update_state(
         &mut self,
         &mut self,
         vdom: &VirtualDom,
         vdom: &VirtualDom,
-        nodes_updated: Vec<usize>,
-        us_ctx: &mut US::Ctx,
-        ds_ctx: &mut DS::Ctx,
+        nodes_updated: Vec<(usize, NodeMask)>,
+        ctx: AnyMap,
     ) -> Option<FxHashSet<usize>> {
     ) -> Option<FxHashSet<usize>> {
+        #[derive(PartialEq, Clone, Debug)]
+        enum StatesToCheck {
+            All,
+            Some(Vec<TypeId>),
+        }
+        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<std::cmp::Ordering> {
+                // 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();
         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
         let mut nodes_updated: Vec<_> = nodes_updated
             .into_iter()
             .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();
             .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<NodeRef> = 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(&current);
+                } 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.
         // 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
                     // 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.
         // 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 {
                 if let NodeType::Element { children, .. } = &node.node_type {
                     for c in children {
                     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<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     }
     }
 
 
     // remove a node and it's children from the dom.
     // remove a node and it's children from the dom.
-    fn remove(&mut self, id: usize) -> Option<Node<US, DS>> {
+    fn remove(&mut self, id: usize) -> Option<Node<S>> {
         // We do not need to remove the node from the parent's children list for children.
         // We do not need to remove the node from the parent's children list for children.
-        fn inner<US: BubbledUpState, DS: PushedDownState>(
-            dom: &mut RealDom<US, DS>,
-            id: usize,
-        ) -> Option<Node<US, DS>> {
+        fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> {
             let mut node = dom.nodes[id as usize].take()?;
             let mut node = dom.nodes[id as usize].take()?;
             if let NodeType::Element { children, .. } = &mut node.node_type {
             if let NodeType::Element { children, .. } = &mut node.node_type {
                 for c in children {
                 for c in children {
@@ -327,7 +476,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
         Some(node)
         Some(node)
     }
     }
 
 
-    fn insert(&mut self, node: Node<US, DS>) {
+    fn insert(&mut self, node: Node<S>) {
         let current_len = self.nodes.len();
         let current_len = self.nodes.len();
         let id = node.id.0;
         let id = node.id.0;
         if current_len - 1 < node.id.0 {
         if current_len - 1 < node.id.0 {
@@ -337,15 +486,65 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
         self.nodes[id] = Some(node);
         self.nodes[id] = Some(node);
     }
     }
 
 
-    pub fn get(&self, id: usize) -> Option<&Node<US, DS>> {
+    pub fn get(&self, id: usize) -> Option<&Node<S>> {
         self.nodes.get(id)?.as_ref()
         self.nodes.get(id)?.as_ref()
     }
     }
 
 
-    pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<US, DS>> {
+    pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
         self.nodes.get_mut(id)?.as_mut()
         self.nodes.get_mut(id)?.as_mut()
     }
     }
 
 
-    pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<US, DS>> {
+    // 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<S>, Vec<&'a mut Node<S>>)> {
+        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<S>, Option<&'a mut Node<S>>)> {
+        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<S>> {
         if let Some(nodes) = self.nodes_listening.get(event) {
         if let Some(nodes) = self.nodes_listening.get(event) {
             let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
             let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
             listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
             listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
@@ -416,12 +615,8 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     }
     }
 
 
     /// Call a function for each node in the dom, depth first.
     /// Call a function for each node in the dom, depth first.
-    pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<US, DS>)) {
-        fn inner<US: BubbledUpState, DS: PushedDownState>(
-            dom: &RealDom<US, DS>,
-            id: ElementId,
-            f: &mut impl FnMut(&Node<US, DS>),
-        ) {
+    pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
+        fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
             let node = &dom[id];
             let node = &dom[id];
             f(node);
             f(node);
             if let NodeType::Element { children, .. } = &node.node_type {
             if let NodeType::Element { children, .. } = &node.node_type {
@@ -438,12 +633,8 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     }
     }
 
 
     /// Call a function for each node in the dom, depth first.
     /// 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<US, DS>)) {
-        fn inner<US: BubbledUpState, DS: PushedDownState>(
-            dom: &mut RealDom<US, DS>,
-            id: ElementId,
-            f: &mut impl FnMut(&mut Node<US, DS>),
-        ) {
+    pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
+        fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
             let node = &mut dom[id];
             let node = &mut dom[id];
             f(node);
             f(node);
             if let NodeType::Element { children, .. } = &mut node.node_type {
             if let NodeType::Element { children, .. } = &mut node.node_type {
@@ -461,28 +652,28 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
     }
     }
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
-    type Output = Node<US, DS>;
+impl<S: State> Index<usize> for RealDom<S> {
+    type Output = Node<S>;
 
 
     fn index(&self, idx: usize) -> &Self::Output {
     fn index(&self, idx: usize) -> &Self::Output {
         self.get(idx).expect("Node does not exist")
         self.get(idx).expect("Node does not exist")
     }
     }
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> {
-    type Output = Node<US, DS>;
+impl<S: State> Index<ElementId> for RealDom<S> {
+    type Output = Node<S>;
 
 
     fn index(&self, idx: ElementId) -> &Self::Output {
     fn index(&self, idx: ElementId) -> &Self::Output {
         &self[idx.0]
         &self[idx.0]
     }
     }
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for RealDom<US, DS> {
+impl<S: State> IndexMut<usize> for RealDom<S> {
     fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
     fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
         self.get_mut(idx).expect("Node does not exist")
         self.get_mut(idx).expect("Node does not exist")
     }
     }
 }
 }
-impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US, DS> {
+impl<S: State> IndexMut<ElementId> for RealDom<S> {
     fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
     fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
         &mut self[idx.0]
         &mut self[idx.0]
     }
     }
@@ -490,15 +681,13 @@ impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US
 
 
 /// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
 /// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
-pub struct Node<US: BubbledUpState, DS: PushedDownState> {
+pub struct Node<S: State> {
     /// The id of the node this node was created from.
     /// The id of the node this node was created from.
     pub id: ElementId,
     pub id: ElementId,
     /// The parent id of the node.
     /// The parent id of the node.
     pub parent: Option<ElementId>,
     pub parent: Option<ElementId>,
-    /// 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
     /// Additional inforation specific to the node type
     pub node_type: NodeType,
     pub node_type: NodeType,
     /// The number of parents before the root node. The root node has height 1.
     /// The number of parents before the root node. The root node has height 1.
@@ -518,14 +707,13 @@ pub enum NodeType {
     Placeholder,
     Placeholder,
 }
 }
 
 
-impl<US: BubbledUpState, DS: PushedDownState> Node<US, DS> {
+impl<S: State> Node<S> {
     fn new(id: u64, node_type: NodeType) -> Self {
     fn new(id: u64, node_type: NodeType) -> Self {
         Node {
         Node {
             id: ElementId(id as usize),
             id: ElementId(id as usize),
             parent: None,
             parent: None,
             node_type,
             node_type,
-            down_state: DS::default(),
-            up_state: US::default(),
+            state: S::default(),
             height: 0,
             height: 0,
         }
         }
     }
     }
@@ -551,39 +739,3 @@ impl<US: BubbledUpState, DS: PushedDownState> Node<US, DS> {
         self.parent = Some(parent);
         self.parent = Some(parent);
     }
     }
 }
 }
-
-/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) 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<Item = &'a Self>,
-        Self: 'a;
-}
-impl BubbledUpState for () {
-    type Ctx = ();
-    fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
-    where
-        I: Iterator<Item = &'a Self>,
-        Self: 'a,
-    {
-    }
-}

+ 311 - 0
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<T: Ord + Debug>(
+    s_iter: impl Iterator<Item = T>,
+    o_iter: impl Iterator<Item = T>,
+    new_len_guess: usize,
+) -> Vec<T> {
+    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<Item = &Attribute<'a>> {
+        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<Item = &'static str>,
+            mut other_iter: impl Iterator<Item = &'static str>,
+        ) -> 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<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a;
+}
+
+/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) 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<TypeId>;
+
+    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<TypeId>;
+
+    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<TypeId>;
+}

+ 7 - 2
packages/native-core/tests/change_nodes.rs

@@ -3,8 +3,13 @@ use dioxus_core::*;
 use dioxus_core_macro::*;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
 use dioxus_native_core::real_dom::RealDom;
 use dioxus_native_core::real_dom::RealDom;
+use dioxus_native_core::state::State;
+use dioxus_native_core_macro::State;
 use std::cell::Cell;
 use std::cell::Cell;
 
 
+#[derive(State, Default, Clone)]
+struct Empty {}
+
 #[test]
 #[test]
 fn remove_node() {
 fn remove_node() {
     #[allow(non_snake_case)]
     #[allow(non_snake_case)]
@@ -20,7 +25,7 @@ fn remove_node() {
         }
         }
     });
     });
 
 
-    let mut dom: RealDom<(), ()> = RealDom::new();
+    let mut dom: RealDom<Empty> = RealDom::new();
 
 
     let _to_update = dom.apply_mutations(vec![mutations]);
     let _to_update = dom.apply_mutations(vec![mutations]);
     let child_div = VElement {
     let child_div = VElement {
@@ -92,7 +97,7 @@ fn add_node() {
         div{}
         div{}
     });
     });
 
 
-    let mut dom: RealDom<(), ()> = RealDom::new();
+    let mut dom: RealDom<Empty> = RealDom::new();
 
 
     let _to_update = dom.apply_mutations(vec![mutations]);
     let _to_update = dom.apply_mutations(vec![mutations]);
 
 

+ 7 - 2
packages/native-core/tests/initial_build.rs

@@ -5,6 +5,11 @@ use dioxus_core::*;
 use dioxus_core_macro::*;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
 use dioxus_native_core::real_dom::RealDom;
 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]
 #[test]
 fn initial_build_simple() {
 fn initial_build_simple() {
@@ -21,7 +26,7 @@ fn initial_build_simple() {
         div{}
         div{}
     });
     });
 
 
-    let mut dom: RealDom<(), ()> = RealDom::new();
+    let mut dom: RealDom<Empty> = RealDom::new();
 
 
     let _to_update = dom.apply_mutations(vec![mutations]);
     let _to_update = dom.apply_mutations(vec![mutations]);
     let root_div = VElement {
     let root_div = VElement {
@@ -60,7 +65,7 @@ fn initial_build_with_children() {
         }
         }
     });
     });
 
 
-    let mut dom: RealDom<(), ()> = RealDom::new();
+    let mut dom: RealDom<Empty> = RealDom::new();
 
 
     let _to_update = dom.apply_mutations(vec![mutations]);
     let _to_update = dom.apply_mutations(vec![mutations]);
     let first_text = VText {
     let first_text = VText {

+ 65 - 0
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<Item = &'a Self::DepState>,
+        _: &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!()
+    }
+}

+ 0 - 320
packages/native-core/tests/state.rs

@@ -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<Item = &'a Self>,
-        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<Box<BubbledUpStateTester>>);
-impl BubbledUpState for BubbledUpStateTester {
-    type Ctx = u32;
-
-    fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
-    where
-        I: Iterator<Item = &'a Self>,
-        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<Box<PushedDownStateTester>>);
-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<BubbledUpStateTester, PushedDownStateTester> = 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<Item = &'a Self>,
-            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<CallCounter, CallCounter> = 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<CallCounter, CallCounter> = 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<CallCounter, CallCounter> = 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 });
-    });
-}

+ 402 - 0
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<Item = &'a Self::DepState>,
+        _: &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<String>, Vec<Box<BubbledUpStateTester>>);
+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<Item = &'a Self::DepState>,
+        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<String>, Option<Box<PushedDownStateTester>>);
+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<String>, 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<StateTester> = 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<CallCounterState> = 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<CallCounterState> = 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<CallCounterState> = 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 });
+    });
+}

+ 1 - 0
packages/tui/Cargo.toml

@@ -25,3 +25,4 @@ futures = "0.3.19"
 stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
 stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
 smallvec = "1.6"
 smallvec = "1.6"
 fxhash = "0.2"
 fxhash = "0.2"
+anymap = "0.12.1"

+ 16 - 22
packages/tui/src/hooks.rs

@@ -5,7 +5,6 @@ use dioxus_core::*;
 use fxhash::{FxHashMap, FxHashSet};
 use fxhash::{FxHashMap, FxHashSet};
 
 
 use dioxus_html::{on::*, KeyCode};
 use dioxus_html::{on::*, KeyCode};
-use dioxus_native_core::real_dom::{Node, RealDom};
 use std::{
 use std::{
     any::Any,
     any::Any,
     cell::RefCell,
     cell::RefCell,
@@ -15,8 +14,7 @@ use std::{
 };
 };
 use stretch2::{prelude::Layout, Stretch};
 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
 // a wrapper around the input state for easier access
 // todo: fix loop
 // todo: fix loop
@@ -166,7 +164,7 @@ impl InnerInputState {
         evts: &mut Vec<EventCore>,
         evts: &mut Vec<EventCore>,
         resolved_events: &mut Vec<UserEvent>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
         layout: &Stretch,
-        dom: &mut RealDom<StretchLayout, StyleModifier>,
+        dom: &mut Dom,
     ) {
     ) {
         let previous_mouse = self
         let previous_mouse = self
             .mouse
             .mouse
@@ -191,7 +189,7 @@ impl InnerInputState {
         previous_mouse: Option<(MouseData, Vec<u16>)>,
         previous_mouse: Option<(MouseData, Vec<u16>)>,
         resolved_events: &mut Vec<UserEvent>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
         layout: &Stretch,
-        dom: &mut RealDom<StretchLayout, StyleModifier>,
+        dom: &mut Dom,
     ) {
     ) {
         struct Data<'b> {
         struct Data<'b> {
             new_pos: (i32, i32),
             new_pos: (i32, i32),
@@ -215,8 +213,8 @@ impl InnerInputState {
             data: Arc<dyn Any + Send + Sync>,
             data: Arc<dyn Any + Send + Sync>,
             will_bubble: &mut FxHashSet<ElementId>,
             will_bubble: &mut FxHashSet<ElementId>,
             resolved_events: &mut Vec<UserEvent>,
             resolved_events: &mut Vec<UserEvent>,
-            node: &Node<StretchLayout, StyleModifier>,
-            dom: &RealDom<StretchLayout, StyleModifier>,
+            node: &Node,
+            dom: &Dom,
         ) {
         ) {
             // only trigger event if the event was not triggered already by a child
             // only trigger event if the event was not triggered already by a child
             if will_bubble.insert(node.id) {
             if will_bubble.insert(node.id) {
@@ -261,7 +259,7 @@ impl InnerInputState {
                 // mousemove
                 // mousemove
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mousemove") {
                 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
                     let previously_contained = data
                         .old_pos
                         .old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .filter(|pos| layout_contains_point(node_layout, *pos))
@@ -285,7 +283,7 @@ impl InnerInputState {
                 // mouseenter
                 // mouseenter
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseenter") {
                 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
                     let previously_contained = data
                         .old_pos
                         .old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .filter(|pos| layout_contains_point(node_layout, *pos))
@@ -309,7 +307,7 @@ impl InnerInputState {
                 // mouseover
                 // mouseover
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseover") {
                 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
                     let previously_contained = data
                         .old_pos
                         .old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .filter(|pos| layout_contains_point(node_layout, *pos))
@@ -333,7 +331,7 @@ impl InnerInputState {
                 // mousedown
                 // mousedown
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mousedown") {
                 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);
                     let currently_contains = layout_contains_point(node_layout, data.new_pos);
 
 
                     if currently_contains && data.clicked {
                     if currently_contains && data.clicked {
@@ -353,7 +351,7 @@ impl InnerInputState {
                 // mouseup
                 // mouseup
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseup") {
                 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);
                     let currently_contains = layout_contains_point(node_layout, data.new_pos);
 
 
                     if currently_contains && data.released {
                     if currently_contains && data.released {
@@ -373,7 +371,7 @@ impl InnerInputState {
                 // click
                 // click
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("click") {
                 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);
                     let currently_contains = layout_contains_point(node_layout, data.new_pos);
 
 
                     if currently_contains && data.released && data.mouse_data.button == 0 {
                     if currently_contains && data.released && data.mouse_data.button == 0 {
@@ -393,7 +391,7 @@ impl InnerInputState {
                 // contextmenu
                 // contextmenu
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("contextmenu") {
                 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);
                     let currently_contains = layout_contains_point(node_layout, data.new_pos);
 
 
                     if currently_contains && data.released && data.mouse_data.button == 2 {
                     if currently_contains && data.released && data.mouse_data.button == 2 {
@@ -413,7 +411,7 @@ impl InnerInputState {
                 // wheel
                 // wheel
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("wheel") {
                 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);
                     let currently_contains = layout_contains_point(node_layout, data.new_pos);
 
 
                     if let Some(w) = data.wheel_data {
                     if let Some(w) = data.wheel_data {
@@ -435,7 +433,7 @@ impl InnerInputState {
                 // mouseleave
                 // mouseleave
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseleave") {
                 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
                     let previously_contained = data
                         .old_pos
                         .old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .filter(|pos| layout_contains_point(node_layout, *pos))
@@ -459,7 +457,7 @@ impl InnerInputState {
                 // mouseout
                 // mouseout
                 let mut will_bubble = FxHashSet::default();
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseout") {
                 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
                     let previously_contained = data
                         .old_pos
                         .old_pos
                         .filter(|pos| layout_contains_point(node_layout, *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<StretchLayout, StyleModifier>,
-    ) -> Vec<UserEvent> {
+    pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
         let mut resolved_events = Vec::new();
         let mut resolved_events = Vec::new();
 
 
         (*self.state).borrow_mut().update(
         (*self.state).borrow_mut().update(

+ 61 - 54
packages/tui/src/layout.rs

@@ -1,83 +1,90 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
 use dioxus_core::*;
 use dioxus_core::*;
 use dioxus_native_core::layout_attributes::apply_layout_attributes;
 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::*;
 use stretch2::prelude::*;
 
 
-/// the size
 #[derive(Clone, PartialEq, Default, Debug)]
 #[derive(Clone, PartialEq, Default, Debug)]
 pub struct StretchLayout {
 pub struct StretchLayout {
     pub style: Style,
     pub style: Style,
     pub node: Option<Node>,
     pub node: Option<Node>,
 }
 }
 
 
-impl BubbledUpState for StretchLayout {
-    type Ctx = Stretch;
+impl ChildDepState for StretchLayout {
+    type Ctx = Rc<RefCell<Stretch>>;
+    type DepState = Self;
+    // todo: update mask
+    const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true);
 
 
     /// Setup the layout
     /// 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<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
     where
     where
-        I: Iterator<Item = &'a Self>,
-        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();
                 }
                 }
+            } else {
+                self.node = Some(stretch.new_node(style, &[]).unwrap());
+            }
+
+            self.style = style;
+        } else {
+            // gather up all the styles from the attribute list
+            let mut style = Style::default();
 
 
-                self.style = style;
+            for &Attribute { name, value, .. } in node.attributes() {
+                apply_layout_attributes(name, value, &mut style);
             }
             }
-            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);
-                }
+            // 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);
+            }
 
 
-                // 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());
+            }
 
 
-                // 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 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());
+                if self.style != style {
+                    stretch.set_style(n, style).unwrap();
                 }
                 }
-
-                self.style = style;
+            } else {
+                self.node = Some(stretch.new_node(style, &child_layout).unwrap());
             }
             }
-            _ => (),
+
+            self.style = style;
         }
         }
+        true
     }
     }
 }
 }

+ 33 - 25
packages/tui/src/lib.rs

@@ -1,4 +1,5 @@
 use anyhow::Result;
 use anyhow::Result;
+use anymap::AnyMap;
 use crossterm::{
 use crossterm::{
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
     execute,
     execute,
@@ -6,12 +7,14 @@ use crossterm::{
 };
 };
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::*;
 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::{
 use futures::{
     channel::mpsc::{UnboundedReceiver, UnboundedSender},
     channel::mpsc::{UnboundedReceiver, UnboundedSender},
     pin_mut, StreamExt,
     pin_mut, StreamExt,
 };
 };
 use layout::StretchLayout;
 use layout::StretchLayout;
+use std::cell::RefCell;
+use std::rc::Rc;
 use std::{io, time::Duration};
 use std::{io, time::Duration};
 use stretch2::{prelude::Size, Stretch};
 use stretch2::{prelude::Size, Stretch};
 use style_attributes::StyleModifier;
 use style_attributes::StyleModifier;
@@ -27,7 +30,18 @@ mod widget;
 
 
 pub use config::*;
 pub use config::*;
 pub use hooks::*;
 pub use hooks::*;
-pub use render::*;
+
+type Dom = RealDom<NodeState>;
+type Node = dioxus_native_core::real_dom::Node<NodeState>;
+
+#[derive(Debug, Clone, State, Default)]
+struct NodeState {
+    #[child_dep_state(StretchLayout, RefCell<Stretch>)]
+    layout: StretchLayout,
+    // depends on attributes, the C component of it's parent and a u8 context
+    #[parent_dep_state(StyleModifier)]
+    style: StyleModifier,
+}
 
 
 #[derive(Clone)]
 #[derive(Clone)]
 pub struct TuiContext {
 pub struct TuiContext {
@@ -70,13 +84,13 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
     cx.provide_root_context(state);
     cx.provide_root_context(state);
     cx.provide_root_context(TuiContext { tx: event_tx_clone });
     cx.provide_root_context(TuiContext { tx: event_tx_clone });
 
 
-    let mut rdom: RealDom<StretchLayout, StyleModifier> = RealDom::new();
+    let mut rdom: Dom = RealDom::new();
     let mutations = dom.rebuild();
     let mutations = dom.rebuild();
     let to_update = rdom.apply_mutations(vec![mutations]);
     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(
     render_vdom(
         &mut dom,
         &mut dom,
@@ -95,8 +109,8 @@ fn render_vdom(
     mut event_reciever: UnboundedReceiver<InputEvent>,
     mut event_reciever: UnboundedReceiver<InputEvent>,
     handler: RinkInputHandler,
     handler: RinkInputHandler,
     cfg: Config,
     cfg: Config,
-    mut rdom: RealDom<StretchLayout, StyleModifier>,
-    mut stretch: Stretch,
+    mut rdom: Dom,
+    stretch: Rc<RefCell<Stretch>>,
     mut register_event: impl FnMut(crossterm::event::Event),
     mut register_event: impl FnMut(crossterm::event::Event),
 ) -> Result<()> {
 ) -> Result<()> {
     tokio::runtime::Builder::new_current_thread()
     tokio::runtime::Builder::new_current_thread()
@@ -114,7 +128,7 @@ fn render_vdom(
                 terminal.clear().unwrap();
                 terminal.clear().unwrap();
             }
             }
 
 
-            let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
+            let to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
             let mut resized = true;
             let mut resized = true;
 
 
             loop {
             loop {
@@ -131,15 +145,11 @@ fn render_vdom(
 
 
                 if !to_rerender.is_empty() || resized {
                 if !to_rerender.is_empty() || resized {
                     resized = false;
                     resized = false;
-                    fn resize(
-                        dims: Rect,
-                        stretch: &mut Stretch,
-                        rdom: &RealDom<StretchLayout, StyleModifier>,
-                    ) {
+                    fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
                         let width = dims.width;
                         let width = dims.width;
                         let height = dims.height;
                         let height = dims.height;
                         let root_id = rdom.root_id();
                         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
                         stretch
                             .compute_layout(
                             .compute_layout(
@@ -154,9 +164,9 @@ fn render_vdom(
                     if let Some(terminal) = &mut terminal {
                     if let Some(terminal) = &mut terminal {
                         terminal.draw(|frame| {
                         terminal.draw(|frame| {
                             // size is guaranteed to not change when rendering
                             // 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()];
                             let root = &rdom[rdom.root_id()];
-                            render::render_vnode(frame, &stretch, &rdom, &root, cfg);
+                            render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg);
                         })?;
                         })?;
                     } else {
                     } else {
                         resize(
                         resize(
@@ -166,7 +176,7 @@ fn render_vdom(
                                 width: 300,
                                 width: 300,
                                 height: 300,
                                 height: 300,
                             },
                             },
-                            &mut stretch,
+                            &mut stretch.borrow_mut(),
                             &rdom,
                             &rdom,
                         );
                         );
                     }
                     }
@@ -207,7 +217,7 @@ fn render_vdom(
 
 
                 {
                 {
                     // resolve events before rendering
                     // 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 {
                     for e in evts {
                         vdom.handle_message(SchedulerMsg::Event(e));
                         vdom.handle_message(SchedulerMsg::Event(e));
                     }
                     }
@@ -215,11 +225,9 @@ fn render_vdom(
                     // updates the dom's nodes
                     // updates the dom's nodes
                     let to_update = rdom.apply_mutations(mutations);
                     let to_update = rdom.apply_mutations(mutations);
                     // update the style and layout
                     // 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();
                 }
                 }
             }
             }
 
 

+ 14 - 18
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 std::io::Stdout;
 use stretch2::{
 use stretch2::{
     geometry::Point,
     geometry::Point,
@@ -15,16 +11,16 @@ use crate::{
     style::{RinkColor, RinkStyle},
     style::{RinkColor, RinkStyle},
     style_attributes::{BorderEdge, BorderStyle},
     style_attributes::{BorderEdge, BorderStyle},
     widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
     widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
-    Config, StyleModifier,
+    Config, Dom, Node,
 };
 };
 
 
 const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
 const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
 
 
-pub fn render_vnode(
+pub(crate) fn render_vnode(
     frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
     frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
     layout: &Stretch,
     layout: &Stretch,
-    rdom: &RealDom<StretchLayout, StyleModifier>,
-    node: &Node<StretchLayout, StyleModifier>,
+    rdom: &Dom,
+    node: &Node,
     cfg: Config,
     cfg: Config,
 ) {
 ) {
     use dioxus_native_core::real_dom::NodeType;
     use dioxus_native_core::real_dom::NodeType;
@@ -33,7 +29,7 @@ pub fn render_vnode(
         return;
         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 Point { x, y } = location;
     let Size { width, height } = size;
     let Size { width, height } = size;
@@ -59,7 +55,7 @@ pub fn render_vnode(
 
 
             let label = Label {
             let label = Label {
                 text,
                 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);
             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<StretchLayout, StyleModifier> {
+impl RinkWidget for &Node {
     fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
     fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
         use tui::symbols::line::*;
         use tui::symbols::line::*;
 
 
@@ -268,14 +264,14 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
         for x in area.left()..area.right() {
         for x in area.left()..area.right() {
             for y in area.top()..area.bottom() {
             for y in area.top()..area.bottom() {
                 let mut new_cell = RinkCell::default();
                 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;
                     new_cell.bg = c;
                 }
                 }
                 buf.set(x, y, new_cell);
                 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 last_edge = &borders.left;
         let current_edge = &borders.top;
         let current_edge = &borders.top;
@@ -292,7 +288,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) 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();
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
             if let Some(c) = color {
                 new_cell.fg = c;
                 new_cell.fg = c;
@@ -327,7 +323,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) 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();
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
             if let Some(c) = color {
                 new_cell.fg = c;
                 new_cell.fg = c;
@@ -362,7 +358,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) 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();
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
             if let Some(c) = color {
                 new_cell.fg = c;
                 new_cell.fg = c;
@@ -397,7 +393,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) 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();
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
             if let Some(c) = color {
                 new_cell.fg = c;
                 new_cell.fg = c;

+ 17 - 12
packages/tui/src/style_attributes.rs

@@ -29,10 +29,10 @@
 - [ ] pub aspect_ratio: Number,
 - [ ] pub aspect_ratio: Number,
 */
 */
 
 
-use dioxus_core::{Attribute, VNode};
+use dioxus_core::Attribute;
 use dioxus_native_core::{
 use dioxus_native_core::{
     layout_attributes::{parse_value, UnitSystem},
     layout_attributes::{parse_value, UnitSystem},
-    real_dom::PushedDownState,
+    state::{AttributeMask, NodeMask, NodeView, ParentDepState},
 };
 };
 
 
 use crate::style::{RinkColor, RinkStyle};
 use crate::style::{RinkColor, RinkStyle};
@@ -43,18 +43,22 @@ pub struct StyleModifier {
     pub modifier: TuiModifier,
     pub modifier: TuiModifier,
 }
 }
 
 
-impl PushedDownState for StyleModifier {
+impl ParentDepState for StyleModifier {
     type Ctx = ();
     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();
         *self = StyleModifier::default();
         if parent.is_some() {
         if parent.is_some() {
             self.style.fg = None;
             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),
                     "b" => apply_style_attributes("font-weight", "bold", self),
                     "strong" => apply_style_attributes("font-weight", "bold", self),
                     "strong" => apply_style_attributes("font-weight", "bold", self),
                     "u" => apply_style_attributes("text-decoration", "underline", 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
         // keep the text styling from the parent element
@@ -81,6 +85,7 @@ impl PushedDownState for StyleModifier {
             new_style.bg = self.style.bg;
             new_style.bg = self.style.bg;
             self.style = new_style;
             self.style = new_style;
         }
         }
+        true
     }
     }
 }
 }