Improve documentation for the native core and native core macro crates (#528)

* improve documentation for the native core and native core macro crates

* fix spelling of depenency
This commit is contained in:
Demonthos 2022-09-06 19:25:10 -05:00 committed by GitHub
parent 286cfa43b7
commit 2d08532bad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 312 additions and 79 deletions

View file

@ -14,6 +14,7 @@ use syn::{
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
}; };
/// Sorts a slice of string literals at compile time.
#[proc_macro] #[proc_macro]
pub fn sorted_str_slice(input: TokenStream) -> TokenStream { pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
let slice: StrSlice = parse_macro_input!(input as StrSlice); let slice: StrSlice = parse_macro_input!(input as StrSlice);
@ -22,12 +23,107 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
enum DepKind { enum DependencyKind {
Node, Node,
Child, Child,
Parent, Parent,
} }
/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute.
///
/// # Declaring elements
/// Each of the attributes require specifying the members of the struct it depends on to allow the macro to find the optimal resultion order.
/// These dependencies should match the types declared in the trait the member implements.
///
/// # The node_dep_state attribute
/// The node_dep_state attribute declares a member that implements the NodeDepState trait.
/// ```rust, ignore
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependency implements ChildDepState<()>
/// #[node_dep_state()]
/// my_dependency_1: MyDependency,
/// // MyDependency2 implements ChildDepState<(MyDependency,)>
/// #[node_dep_state(my_dependency_1)]
/// my_dependency_2: MyDependency2,
/// }
/// // or
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependnancy implements NodeDepState<()>
/// #[node_dep_state()]
/// my_dependency_1: MyDependency,
/// // MyDependency2 implements NodeDepState<()>
/// #[node_dep_state()]
/// my_dependency_2: MyDependency2,
/// // MyDependency3 implements NodeDepState<(MyDependency, MyDependency2)> with Ctx = f32
/// #[node_dep_state((my_dependency_1, my_dependency_2), f32)]
/// my_dependency_3: MyDependency2,
/// }
/// ```
/// # The child_dep_state attribute
/// The child_dep_state attribute declares a member that implements the ChildDepState trait.
/// ```rust, ignore
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependnacy implements ChildDepState with DepState = Self
/// #[child_dep_state(my_dependency_1)]
/// my_dependency_1: MyDependency,
/// }
/// // or
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependnacy implements ChildDepState with DepState = Self
/// #[child_dep_state(my_dependency_1)]
/// my_dependency_1: MyDependency,
/// // MyDependnacy2 implements ChildDepState with DepState = MyDependency and Ctx = f32
/// #[child_dep_state(my_dependency_1, f32)]
/// my_dependency_2: MyDependency2,
/// }
/// ```
/// # The parent_dep_state attribute
/// The parent_dep_state attribute declares a member that implements the ParentDepState trait.
/// The parent_dep_state attribute can be called in the forms:
/// ```rust, ignore
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependnacy implements ParentDepState with DepState = Self
/// #[parent_dep_state(my_dependency_1)]
/// my_dependency_1: MyDependency,
/// }
/// // or
/// #[derive(State)]
/// struct MyStruct {
/// // MyDependnacy implements ParentDepState with DepState = Self
/// #[parent_dep_state(my_dependency_1)]
/// my_dependency_1: MyDependency,
/// // MyDependnacy2 implements ParentDepState with DepState = MyDependency and Ctx = f32
/// #[parent_dep_state(my_dependency_1, f32)]
/// my_dependency_2: MyDependency2,
/// }
/// ```
///
/// # Combining dependancies
/// The node_dep_state, parent_dep_state, and child_dep_state attributes can be combined to allow for more complex dependancies.
/// For example if we wanted to combine the font that is passed from the parent to the child and the layout of the size children to find the size of the current node we could do:
/// ```rust, ignore
/// #[derive(State)]
/// struct MyStruct {
/// // ChildrenSize implements ChildDepState with DepState = Size
/// #[child_dep_state(size)]
/// children_size: ChildrenSize,
/// // FontSize implements ParentDepState with DepState = Self
/// #[parent_dep_state(font_size)]
/// font_size: FontSize,
/// // Size implements NodeDepState<(ChildrenSize, FontSize)>
/// #[parent_dep_state((children_size, font_size))]
/// size: Size,
/// }
/// ```
///
/// # The state attribute
/// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components.
/// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state.
#[proc_macro_derive( #[proc_macro_derive(
State, State,
attributes(node_dep_state, child_dep_state, parent_dep_state, state) attributes(node_dep_state, child_dep_state, parent_dep_state, state)
@ -101,6 +197,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
} }
} }
#[derive(Clone, Copy)]
struct MembersDirty { struct MembersDirty {
#(#members: bool, )* #(#members: bool, )*
} }
@ -113,18 +210,27 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
fn any(&self) -> bool { fn any(&self) -> bool {
#(self.#members || )* false #(self.#members || )* false
} }
fn union(self, other: Self) -> Self {
Self {#(#members: self.#members || other.#members),*}
}
} }
let mut dirty_elements = fxhash::FxHashSet::default(); let mut dirty_elements = fxhash::FxHashSet::default();
// the states of any elements that are dirty // the states of any elements that are dirty
let mut states = fxhash::FxHashMap::default(); let mut states: fxhash::FxHashMap<dioxus_core::ElementId, MembersDirty> = fxhash::FxHashMap::default();
for (id, mask) in dirty { for (id, mask) in dirty {
let members_dirty = MembersDirty { let members_dirty = MembersDirty {
#(#members: #member_types::NODE_MASK.overlaps(mask),)* #(#members: #member_types::NODE_MASK.overlaps(mask),)*
}; };
if members_dirty.any(){ if members_dirty.any(){
states.insert(*id, members_dirty); if let Some(state) = states.get_mut(id){
*state = state.union(members_dirty);
}
else{
states.insert(*id, members_dirty);
}
} }
dirty_elements.insert(*id); dirty_elements.insert(*id);
} }
@ -178,6 +284,7 @@ struct StateStruct<'a> {
} }
impl<'a> StateStruct<'a> { impl<'a> StateStruct<'a> {
/// Parse the state structure, and find a resolution order that will allow us to update the state for each node in after the state(s) it depends on have been resolved.
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> { fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
let mut parse_err = Ok(()); let mut parse_err = Ok(());
let mut unordered_state_members: Vec<_> = strct let mut unordered_state_members: Vec<_> = strct
@ -195,15 +302,17 @@ impl<'a> StateStruct<'a> {
parse_err?; parse_err?;
let mut state_members = Vec::new(); let mut state_members = Vec::new();
// Keep adding members that have had all of there dependancies resolved until there are no more members left.
while !unordered_state_members.is_empty() { while !unordered_state_members.is_empty() {
let mut resolved = false; let mut resolved = false;
for i in 0..unordered_state_members.len() { for i in 0..unordered_state_members.len() {
let mem = &mut unordered_state_members[i]; let mem = &mut unordered_state_members[i];
// if this member has all of its dependancies resolved other than itself, resolve it next.
if mem.dep_mems.iter().all(|(dep, resolved)| { if mem.dep_mems.iter().all(|(dep, resolved)| {
*resolved || (*dep == mem.mem && mem.dep_kind != DepKind::Node) *resolved || (*dep == mem.mem && mem.dep_kind != DependencyKind::Node)
}) { }) {
let mem = unordered_state_members.remove(i); let mem = unordered_state_members.remove(i);
// mark any dependancy that depends on this member as resolved // mark any dependency that depends on this member as resolved
for member in unordered_state_members.iter_mut() { for member in unordered_state_members.iter_mut() {
for (dep, resolved) in &mut member.dep_mems { for (dep, resolved) in &mut member.dep_mems {
*resolved |= *dep == mem.mem; *resolved |= *dep == mem.mem;
@ -260,9 +369,9 @@ impl<'a> StateStruct<'a> {
for (dep, _) in &member.dep_mems { for (dep, _) in &member.dep_mems {
if *dep == mem { if *dep == mem {
match member.dep_kind { match member.dep_kind {
DepKind::Node => dependants.node.push(member.mem), DependencyKind::Node => dependants.node.push(member.mem),
DepKind::Child => dependants.child.push(member.mem), DependencyKind::Child => dependants.child.push(member.mem),
DepKind::Parent => dependants.parent.push(member.mem), DependencyKind::Parent => dependants.parent.push(member.mem),
} }
} }
} }
@ -270,6 +379,7 @@ impl<'a> StateStruct<'a> {
dependants dependants
} }
// Mark the states that depend on the current state as dirty
fn update_dependants(&self, mem: &Member) -> impl ToTokens { fn update_dependants(&self, mem: &Member) -> impl ToTokens {
let dep = self.get_depenadants(mem); let dep = self.get_depenadants(mem);
let update_child_dependants = if dep.child.is_empty() { let update_child_dependants = if dep.child.is_empty() {
@ -368,13 +478,14 @@ impl<'a> StateStruct<'a> {
} }
} }
// Generate code to resolve this state
fn resolve(&self, mem: &StateMember) -> impl ToTokens { fn resolve(&self, mem: &StateMember) -> impl ToTokens {
let reduce_member = mem.reduce_self(); let reduce_member = mem.reduce_self();
let update_dependant = self.update_dependants(mem.mem); let update_dependant = self.update_dependants(mem.mem);
let member = &mem.mem.ident; let member = &mem.mem.ident;
match mem.dep_kind { match mem.dep_kind {
DepKind::Parent => { DependencyKind::Parent => {
quote! { quote! {
// resolve parent dependant state // resolve parent dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>(); let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
@ -394,7 +505,7 @@ impl<'a> StateStruct<'a> {
} }
} }
} }
DepKind::Child => { DependencyKind::Child => {
quote! { quote! {
// resolve child dependant state // resolve child dependant state
let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>(); let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::<Vec<_>>();
@ -416,7 +527,7 @@ impl<'a> StateStruct<'a> {
} }
} }
} }
DepKind::Node => { DependencyKind::Node => {
quote! { quote! {
// resolve node dependant state // resolve node dependant state
let mut resolution_order = states.keys().copied().collect::<Vec<_>>(); let mut resolution_order = states.keys().copied().collect::<Vec<_>>();
@ -444,12 +555,12 @@ fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
Ok(inside) Ok(inside)
} }
struct Dependancy { struct Dependency {
ctx_ty: Option<Type>, ctx_ty: Option<Type>,
deps: Vec<Ident>, deps: Vec<Ident>,
} }
impl Parse for Dependancy { impl Parse for Dependency {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
let deps: Option<Punctuated<Ident, Token![,]>> = { let deps: Option<Punctuated<Ident, Token![,]>> = {
try_parenthesized(input) try_parenthesized(input)
@ -475,6 +586,7 @@ impl Parse for Dependancy {
} }
} }
/// The type of the member and the ident of the member
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
struct Member { struct Member {
ty: Type, ty: Type,
@ -493,9 +605,11 @@ impl Member {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct StateMember<'a> { struct StateMember<'a> {
mem: &'a Member, mem: &'a Member,
dep_kind: DepKind, // the kind of dependncies this state has
dep_kind: DependencyKind,
// the depenancy and if it is satified // the depenancy and if it is satified
dep_mems: Vec<(&'a Member, bool)>, dep_mems: Vec<(&'a Member, bool)>,
// the context this state requires
ctx_ty: Option<Type>, ctx_ty: Option<Type>,
} }
@ -511,14 +625,14 @@ impl<'a> StateMember<'a> {
.path .path
.get_ident() .get_ident()
.and_then(|i| match i.to_string().as_str() { .and_then(|i| match i.to_string().as_str() {
"node_dep_state" => Some(DepKind::Node), "node_dep_state" => Some(DependencyKind::Node),
"child_dep_state" => Some(DepKind::Child), "child_dep_state" => Some(DependencyKind::Child),
"parent_dep_state" => Some(DepKind::Parent), "parent_dep_state" => Some(DependencyKind::Parent),
_ => None, _ => None,
})?; })?;
match a.parse_args::<Dependancy>() { match a.parse_args::<Dependency>() {
Ok(dependancy) => { Ok(dependency) => {
let dep_mems = dependancy let dep_mems = dependency
.deps .deps
.iter() .iter()
.filter_map(|name| { .filter_map(|name| {
@ -537,7 +651,7 @@ impl<'a> StateMember<'a> {
mem, mem,
dep_kind, dep_kind,
dep_mems, dep_mems,
ctx_ty: dependancy.ctx_ty, ctx_ty: dependency.ctx_ty,
}) })
} }
Err(e) => { Err(e) => {
@ -550,6 +664,7 @@ impl<'a> StateMember<'a> {
Ok(member) Ok(member)
} }
/// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty.
fn reduce_self(&self) -> quote::__private::TokenStream { fn reduce_self(&self) -> quote::__private::TokenStream {
let ident = &self.mem.ident; let ident = &self.mem.ident;
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
@ -568,17 +683,17 @@ impl<'a> StateMember<'a> {
quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom)); quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom));
let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident); let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
match self.dep_kind { match self.dep_kind {
DepKind::Node => { DependencyKind::Node => {
quote!({ quote!({
current_state.#ident.reduce(#node_view, (#(&current_state.#dep_idents,)*), #get_ctx) current_state.#ident.reduce(#node_view, (#(&current_state.#dep_idents,)*), #get_ctx)
}) })
} }
DepKind::Child => { DependencyKind::Child => {
quote!({ quote!({
current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx) current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx)
}) })
} }
DepKind::Parent => { DependencyKind::Parent => {
quote!({ quote!({
current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx) current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx)
}) })

View file

@ -7,6 +7,7 @@ use syn::{
parse::{Parse, ParseStream, Result}, parse::{Parse, ParseStream, Result},
LitStr, Token, LitStr, Token,
}; };
pub struct StrSlice { pub struct StrSlice {
pub map: BTreeMap<String, LitStr>, pub map: BTreeMap<String, LitStr>,
} }

View file

@ -2,6 +2,7 @@ use dioxus_core::*;
use crate::state::union_ordered_iter; use crate::state::union_ordered_iter;
/// A view into a [VNode] with limited access.
#[derive(Debug)] #[derive(Debug)]
pub struct NodeView<'a> { pub struct NodeView<'a> {
inner: &'a VNode<'a>, inner: &'a VNode<'a>,
@ -9,6 +10,7 @@ pub struct NodeView<'a> {
} }
impl<'a> NodeView<'a> { impl<'a> NodeView<'a> {
/// Create a new NodeView from a VNode, and mask.
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self { pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
if let VNode::Component(sc) = vnode { if let VNode::Component(sc) = vnode {
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
@ -20,41 +22,53 @@ impl<'a> NodeView<'a> {
} }
} }
/// Get the id of the node
pub fn id(&self) -> ElementId { pub fn id(&self) -> ElementId {
self.inner.mounted_id() self.inner.mounted_id()
} }
/// Get the tag of the node if the tag is enabled in the mask
pub fn tag(&self) -> Option<&'a str> { 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 self.mask
.namespace .tag
.then(|| self.el().and_then(|el| el.namespace)) .then(|| self.try_element().map(|el| el.tag))
.flatten() .flatten()
} }
/// Get the tag of the node if the namespace is enabled in the mask
pub fn namespace(&self) -> Option<&'a str> {
self.mask
.namespace
.then(|| self.try_element().and_then(|el| el.namespace))
.flatten()
}
/// Get any attributes that are enabled in the mask
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> { pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
self.el() self.try_element()
.map(|el| el.attributes) .map(|el| el.attributes)
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.filter(|a| self.mask.attritutes.contains_attribute(a.name)) .filter(|a| self.mask.attritutes.contains_attribute(a.name))
} }
/// Get the text if it is enabled in the mask
pub fn text(&self) -> Option<&str> { pub fn text(&self) -> Option<&str> {
self.mask self.mask
.text .text
.then(|| self.txt().map(|txt| txt.text)) .then(|| self.try_text().map(|txt| txt.text))
.flatten() .flatten()
} }
/// Get the listeners if it is enabled in the mask
pub fn listeners(&self) -> &'a [Listener<'a>] { pub fn listeners(&self) -> &'a [Listener<'a>] {
self.el().map(|el| el.listeners).unwrap_or_default() self.try_element()
.map(|el| el.listeners)
.unwrap_or_default()
} }
fn el(&self) -> Option<&'a VElement<'a>> { /// Try to get the underlying element.
fn try_element(&self) -> Option<&'a VElement<'a>> {
if let VNode::Element(el) = &self.inner { if let VNode::Element(el) = &self.inner {
Some(el) Some(el)
} else { } else {
@ -62,7 +76,8 @@ impl<'a> NodeView<'a> {
} }
} }
fn txt(&self) -> Option<&'a VText<'a>> { /// Try to get the underlying text node.
fn try_text(&self) -> Option<&'a VText<'a>> {
if let VNode::Text(txt) = &self.inner { if let VNode::Text(txt) = &self.inner {
Some(txt) Some(txt)
} else { } else {
@ -71,14 +86,18 @@ impl<'a> NodeView<'a> {
} }
} }
/// A mask that contains a list of attributes that are visible.
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
pub enum AttributeMask { pub enum AttributeMask {
All, All,
/// A list of attribute names that are visible, this list must be sorted
Dynamic(Vec<&'static str>), Dynamic(Vec<&'static str>),
/// A list of attribute names that are visible, this list must be sorted
Static(&'static [&'static str]), Static(&'static [&'static str]),
} }
impl AttributeMask { impl AttributeMask {
/// A empty attribute mask
pub const NONE: Self = Self::Static(&[]); pub const NONE: Self = Self::Static(&[]);
fn contains_attribute(&self, attr: &'static str) -> bool { fn contains_attribute(&self, attr: &'static str) -> bool {
@ -89,10 +108,12 @@ impl AttributeMask {
} }
} }
/// Create a new dynamic attribute mask with a single attribute
pub fn single(new: &'static str) -> Self { pub fn single(new: &'static str) -> Self {
Self::Dynamic(vec![new]) Self::Dynamic(vec![new])
} }
/// Ensure the attribute list is sorted.
pub fn verify(&self) { pub fn verify(&self) {
match &self { match &self {
AttributeMask::Static(attrs) => debug_assert!( AttributeMask::Static(attrs) => debug_assert!(
@ -107,6 +128,7 @@ impl AttributeMask {
} }
} }
/// Combine two attribute masks
pub fn union(&self, other: &Self) -> Self { pub fn union(&self, other: &Self) -> Self {
let new = match (self, other) { let new = match (self, other) {
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic( (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
@ -127,6 +149,7 @@ impl AttributeMask {
new new
} }
/// Check if two attribute masks overlap
fn overlaps(&self, other: &Self) -> bool { fn overlaps(&self, other: &Self) -> bool {
fn overlaps_iter( fn overlaps_iter(
self_iter: impl Iterator<Item = &'static str>, self_iter: impl Iterator<Item = &'static str>,
@ -176,9 +199,9 @@ impl Default for AttributeMask {
} }
} }
/// A mask that limits what parts of a node a dependency can see.
#[derive(Default, PartialEq, Eq, Clone, Debug)] #[derive(Default, PartialEq, Eq, Clone, Debug)]
pub struct NodeMask { pub struct NodeMask {
// must be sorted
attritutes: AttributeMask, attritutes: AttributeMask,
tag: bool, tag: bool,
namespace: bool, namespace: bool,
@ -187,11 +210,14 @@ pub struct NodeMask {
} }
impl NodeMask { impl NodeMask {
/// A node mask with no parts visible.
pub const NONE: Self = Self::new(); pub const NONE: Self = Self::new();
/// A node mask with every part visible.
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All) pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
.with_text() .with_text()
.with_element(); .with_element();
/// Check if two masks overlap
pub fn overlaps(&self, other: &Self) -> bool { pub fn overlaps(&self, other: &Self) -> bool {
(self.tag && other.tag) (self.tag && other.tag)
|| (self.namespace && other.namespace) || (self.namespace && other.namespace)
@ -200,6 +226,7 @@ impl NodeMask {
|| (self.listeners && other.listeners) || (self.listeners && other.listeners)
} }
/// Combine two node masks
pub fn union(&self, other: &Self) -> Self { pub fn union(&self, other: &Self) -> Self {
Self { Self {
attritutes: self.attritutes.union(&other.attritutes), attritutes: self.attritutes.union(&other.attritutes),
@ -210,6 +237,7 @@ impl NodeMask {
} }
} }
/// Create a new node mask with the given attributes
pub const fn new_with_attrs(attritutes: AttributeMask) -> Self { pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
Self { Self {
attritutes, attritutes,
@ -220,29 +248,35 @@ impl NodeMask {
} }
} }
/// Create a empty node mask
pub const fn new() -> Self { pub const fn new() -> Self {
Self::new_with_attrs(AttributeMask::NONE) Self::new_with_attrs(AttributeMask::NONE)
} }
/// Allow the mask to view the tag
pub const fn with_tag(mut self) -> Self { pub const fn with_tag(mut self) -> Self {
self.tag = true; self.tag = true;
self self
} }
/// Allow the mask to view the namespace
pub const fn with_namespace(mut self) -> Self { pub const fn with_namespace(mut self) -> Self {
self.namespace = true; self.namespace = true;
self self
} }
/// Allow the mask to view the namespace and tag
pub const fn with_element(self) -> Self { pub const fn with_element(self) -> Self {
self.with_namespace().with_tag() self.with_namespace().with_tag()
} }
/// Allow the mask to view the text
pub const fn with_text(mut self) -> Self { pub const fn with_text(mut self) -> Self {
self.text = true; self.text = true;
self self
} }
/// Allow the mask to view the listeners
pub const fn with_listeners(mut self) -> Self { pub const fn with_listeners(mut self) -> Self {
self.listeners = true; self.listeners = true;
self self

View file

@ -45,7 +45,7 @@ impl<S: State> RealDom<S> {
} }
} }
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state. /// Updates the dom with some mutations and return a set of nodes that were updated. Pass the dirty nodes to update_state.
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(ElementId, NodeMask)> { pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(ElementId, NodeMask)> {
let mut nodes_updated = Vec::new(); let mut nodes_updated = Vec::new();
for mutations in mutations_vec { for mutations in mutations_vec {
@ -203,6 +203,7 @@ impl<S: State> RealDom<S> {
nodes_updated nodes_updated
} }
/// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
pub fn update_state( pub fn update_state(
&mut self, &mut self,
vdom: &VirtualDom, vdom: &VirtualDom,
@ -217,6 +218,7 @@ impl<S: State> RealDom<S> {
) )
} }
/// Link a child and parent together
fn link_child(&mut self, child_id: ElementId, parent_id: ElementId) -> Option<()> { fn link_child(&mut self, child_id: ElementId, parent_id: ElementId) -> Option<()> {
debug_assert_ne!(child_id, parent_id); debug_assert_ne!(child_id, parent_id);
let parent = &mut self[parent_id]; let parent = &mut self[parent_id];
@ -227,6 +229,7 @@ impl<S: State> RealDom<S> {
Some(()) Some(())
} }
/// Recursively increase the height of a node and its children
fn increase_height(&mut self, id: ElementId, amount: u16) { fn increase_height(&mut self, id: ElementId, amount: u16) {
let n = &mut self[id]; let n = &mut self[id];
n.height += amount; n.height += amount;
@ -262,16 +265,18 @@ impl<S: State> RealDom<S> {
Some(node) Some(node)
} }
/// Create a node
fn insert(&mut self, node: Node<S>) { 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 {
// self.nodes.reserve(1 + id - current_len);
self.nodes.extend((0..1 + id - current_len).map(|_| None)); self.nodes.extend((0..1 + id - current_len).map(|_| None));
} }
self.nodes[id] = Some(node); self.nodes[id] = Some(node);
} }
/// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up.
/// This can be useful to avoid creating duplicate events.
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> { 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();
@ -409,6 +414,7 @@ pub struct Node<S: State> {
pub height: u16, pub height: u16,
} }
/// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum NodeType { pub enum NodeType {
Text { Text {
@ -438,18 +444,21 @@ impl<S: State> Node<S> {
vdom.get_element(self.id).unwrap() vdom.get_element(self.id).unwrap()
} }
/// link a child node
fn add_child(&mut self, child: ElementId) { fn add_child(&mut self, child: ElementId) {
if let NodeType::Element { children, .. } = &mut self.node_type { if let NodeType::Element { children, .. } = &mut self.node_type {
children.push(child); children.push(child);
} }
} }
/// remove a child node
fn remove_child(&mut self, child: ElementId) { fn remove_child(&mut self, child: ElementId) {
if let NodeType::Element { children, .. } = &mut self.node_type { if let NodeType::Element { children, .. } = &mut self.node_type {
children.retain(|c| c != &child); children.retain(|c| c != &child);
} }
} }
/// link the parent node
fn set_parent(&mut self, parent: ElementId) { fn set_parent(&mut self, parent: ElementId) {
self.parent = Some(parent); self.parent = Some(parent);
} }

View file

@ -7,6 +7,7 @@ use fxhash::FxHashSet;
use crate::node_ref::{NodeMask, NodeView}; use crate::node_ref::{NodeMask, NodeView};
use crate::traversable::Traversable; use crate::traversable::Traversable;
/// Join two sorted iterators
pub(crate) fn union_ordered_iter<T: Ord + Debug>( pub(crate) fn union_ordered_iter<T: Ord + Debug>(
s_iter: impl Iterator<Item = T>, s_iter: impl Iterator<Item = T>,
o_iter: impl Iterator<Item = T>, o_iter: impl Iterator<Item = T>,
@ -44,13 +45,45 @@ pub(crate) fn union_ordered_iter<T: Ord + Debug>(
/// This state is derived from children. For example a node's size could be derived from the size of children. /// 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 [ChildDepState] is modified or a child is removed. /// Called when the current node's node properties are modified, a child's [ChildDepState] is modified or a child is removed.
/// Called at most once per update. /// Called at most once per update.
/// ```rust
/// #[derive(Clone, Copy, PartialEq, Default)]
/// struct Layout {
/// width: u32,
/// height: u32,
/// }
///
/// impl ChildDepState for Layout {
/// type Ctx = ();
/// // The layout depends on the layout of the children.
/// type DepState = Layout;
/// fn reduce<'a>(
/// &mut self,
/// _node: NodeView,
/// children: impl Iterator<Item = &'a Self::DepState>,
/// _ctx: &Self::Ctx,
/// ) -> bool
/// where
/// Self::DepState: 'a{
/// /// Children are layed out form left to right. The width of the parent is the sum of the widths and the max of the heights.
/// let new = children.copied().reduce(|c1, c2| Layout{
/// width: c1.width + c2.width,
/// height: c1.height.max(c2.height)
/// }).unwrap_or_default();
/// let changed = new != self.combined;
/// self = new;
/// changed
/// }
/// }
/// ```
pub trait ChildDepState { pub trait ChildDepState {
/// The context is passed to the [ChildDepState::reduce] when it is pushed down. /// The context is passed to the [ChildDepState::reduce] when it is resolved.
/// This is sometimes nessisary for lifetime purposes.
type Ctx; type Ctx;
/// This must be either a [ChildDepState], or [NodeDepState] /// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
/// This must be either a [ChildDepState], or [NodeDepState].
type DepState; type DepState;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the children, the state of the node, and some external context.
fn reduce<'a>( fn reduce<'a>(
&mut self, &mut self,
node: NodeView, node: NodeView,
@ -64,13 +97,55 @@ pub trait ChildDepState {
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children. /// 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 [ParentDepState] is modified. /// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified.
/// Called at most once per update. /// Called at most once per update.
/// ```rust
/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
/// use dioxus_native_core::state::*;
///
/// #[derive(Clone, Copy, PartialEq)]
/// struct FontSize(usize);
///
/// impl ParentDepState for FontSize {
/// type Ctx = ();
/// // The font size depends on the font size of the parent element.
/// type DepState = Self;
/// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
/// "font-size"
/// ]));
/// fn reduce<'a>(
/// &mut self,
/// node: NodeView,
/// parent: Option<&'a Self::DepState>,
/// ctx: &Self::Ctx,
/// ) -> bool{
/// let old = *self;
/// // If the font size was set on the parent, it is passed down to the current element
/// if let Some(parent) = parent{
/// *self = parent;
/// }
/// // If the current node overrides the font size, use that size insead.
/// for attr in node.attributes() {
/// match attr.name {
/// "font-size" => {
/// self.0 = attr.value.as_text().unwrap().parse().unwrap();
/// }
/// // font-size is the only attribute we specified in the mask, so it is the only one we can see
/// _ => unreachable!(),
/// }
/// }
/// old != *self
/// }
/// }
/// ```
pub trait ParentDepState { pub trait ParentDepState {
/// The context is passed to the [ParentDepState::reduce] when it is pushed down. /// The context is passed to the [ParentDepState::reduce] when it is resolved.
/// This is sometimes nessisary for lifetime purposes.
type Ctx; type Ctx;
/// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
/// This must be either a [ParentDepState] or [NodeDepState] /// This must be either a [ParentDepState] or [NodeDepState]
type DepState; type DepState;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context.
fn reduce<'a>( fn reduce<'a>(
&mut self, &mut self,
node: NodeView, node: NodeView,
@ -80,56 +155,51 @@ pub trait ParentDepState {
} }
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. /// 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 sibling's [NodeDepState] is modified. /// Called when the current node's node properties are modified or a one of its dependanices are modified.
/// Called at most once per update. /// Called at most once per update.
/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node. /// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
/// ```rust, ignore /// ```rust
/// impl<'a, 'b> NodeDepState<(&'a TextWrap, &'b ChildLayout)> for Layout { /// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
/// type Ctx = LayoutCache; /// use dioxus_native_core::state::*;
///
/// #[derive(Clone, Copy, PartialEq)]
/// struct TabIndex(usize);
///
/// impl NodeDepState for TabIndex {
/// type Ctx = ();
/// const NODE_MASK: NodeMask = /// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([ /// NodeMask::new_with_attrs(AttributeMask::Static(&[
/// "width", "height" /// "tabindex"
/// ]))) /// ]));
/// .with_text(); /// fn reduce(
/// fn reduce<'a>(
/// &mut self, /// &mut self,
/// node: NodeView, /// node: NodeView,
/// siblings: (&'a TextWrap, &'b ChildLayout), /// siblings: (),
/// ctx: &Self::Ctx, /// ctx: &(),
/// ) -> bool { /// ) -> bool {
/// let old = self.clone(); /// let old = self;
/// let (text_wrap, child_layout) = siblings;
/// if TextWrap::Wrap == text_wrap {
/// if let Some(text) = node.text() {
/// let lines = text_wrap.get_lines(text);
/// self.width = lines.max_by(|l| l.len());
/// self.height = lines.len();
/// return old != self;
/// }
/// }
/// let mut width = child_layout.width;
/// let mut height = child_layout.width;
/// for attr in node.attributes() { /// for attr in node.attributes() {
/// match attr.name { /// match attr.name {
/// "width" => { /// "tabindex" => {
/// width = attr.value.as_text().unwrap().parse().unwrap(); /// self.0 = attr.value.as_text().unwrap().parse().unwrap();
/// }
/// "height" => {
/// height = attr.value.as_text().unwrap().parse().unwrap();
/// } /// }
/// // tabindex is the only attribute we specified in the mask, so it is the only one we can see
/// _ => unreachable!(), /// _ => unreachable!(),
/// } /// }
/// } /// }
/// self.width = width; /// old != *self
/// self.height = height;
/// old != self
/// } /// }
/// } /// }
/// ``` /// ```
/// The generic argument (Depstate) must be a tuple containing any number of borrowed elments that are either a [ChildDepState], [ParentDepState] or [NodeDepState]. /// The generic argument (Depstate) must be a tuple containing any number of borrowed elements that are either a [ChildDepState], [ParentDepState] or [NodeDepState].
pub trait NodeDepState<DepState> { // Todo: once GATs land we can model multable dependencies better
pub trait NodeDepState<DepState = ()> {
/// The state passed to [NodeDepState::reduce] when it is resolved.
type Ctx; type Ctx;
/// The part of a node that this state cares about. This is used to determine if the state should be updated when a node is updated.
const NODE_MASK: NodeMask = NodeMask::NONE; const NODE_MASK: NodeMask = NodeMask::NONE;
/// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context.
fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool; fn reduce(&mut self, node: NodeView, siblings: DepState, ctx: &Self::Ctx) -> bool;
} }
@ -144,7 +214,6 @@ pub trait State: Default + Clone {
) -> FxHashSet<ElementId>; ) -> FxHashSet<ElementId>;
} }
// Todo: once GATs land we can model multable dependencies
impl ChildDepState for () { impl ChildDepState for () {
type Ctx = (); type Ctx = ();
type DepState = (); type DepState = ();

View file

@ -1,3 +1,4 @@
/// This is a interface for a tree with the ability to jump to a specific node
pub trait Traversable { pub trait Traversable {
type Id: Copy; type Id: Copy;
type Node; type Node;
@ -55,6 +56,7 @@ pub trait Traversable {
} }
} }
/// Maps one type of tree to another. Similar to [std::iter::Map].
pub struct Map< pub struct Map<
'a, 'a,
T: Traversable, T: Traversable,

View file

@ -5,7 +5,9 @@ use crate::{
use dioxus_core::{DomEdit, ElementId, Mutations}; use dioxus_core::{DomEdit, ElementId, Mutations};
pub enum ElementProduced { pub enum ElementProduced {
/// The iterator produced an element by progressing to the next node in a depth first order.
Progressed(ElementId), Progressed(ElementId),
/// The iterator reached the end of the tree and looped back to the root
Looped(ElementId), Looped(ElementId),
} }
impl ElementProduced { impl ElementProduced {
@ -42,8 +44,9 @@ impl NodePosition {
} }
} }
/// The focus system needs a iterator that can persist through changes in the [VirtualDom]. /// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
/// Iterate through it with [ElementIter::next] [ElementIter::prev], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]). /// This iterator traverses the tree depth first.
/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
/// The iterator loops around when it reaches the end or the beginning. /// The iterator loops around when it reaches the end or the beginning.
pub struct PersistantElementIter { pub struct PersistantElementIter {
// stack of elements and fragments // stack of elements and fragments