mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
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:
parent
286cfa43b7
commit
2d08532bad
7 changed files with 312 additions and 79 deletions
|
@ -14,6 +14,7 @@ use syn::{
|
|||
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
|
||||
};
|
||||
|
||||
/// Sorts a slice of string literals at compile time.
|
||||
#[proc_macro]
|
||||
pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
|
||||
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)]
|
||||
enum DepKind {
|
||||
enum DependencyKind {
|
||||
Node,
|
||||
Child,
|
||||
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(
|
||||
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 {
|
||||
#(#members: bool, )*
|
||||
}
|
||||
|
@ -113,18 +210,27 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
fn any(&self) -> bool {
|
||||
#(self.#members || )* false
|
||||
}
|
||||
|
||||
fn union(self, other: Self) -> Self {
|
||||
Self {#(#members: self.#members || other.#members),*}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dirty_elements = fxhash::FxHashSet::default();
|
||||
// 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 {
|
||||
let members_dirty = MembersDirty {
|
||||
#(#members: #member_types::NODE_MASK.overlaps(mask),)*
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
@ -178,6 +284,7 @@ struct 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> {
|
||||
let mut parse_err = Ok(());
|
||||
let mut unordered_state_members: Vec<_> = strct
|
||||
|
@ -195,15 +302,17 @@ impl<'a> StateStruct<'a> {
|
|||
parse_err?;
|
||||
|
||||
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() {
|
||||
let mut resolved = false;
|
||||
for i in 0..unordered_state_members.len() {
|
||||
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)| {
|
||||
*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);
|
||||
// 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 (dep, resolved) in &mut member.dep_mems {
|
||||
*resolved |= *dep == mem.mem;
|
||||
|
@ -260,9 +369,9 @@ impl<'a> StateStruct<'a> {
|
|||
for (dep, _) in &member.dep_mems {
|
||||
if *dep == mem {
|
||||
match member.dep_kind {
|
||||
DepKind::Node => dependants.node.push(member.mem),
|
||||
DepKind::Child => dependants.child.push(member.mem),
|
||||
DepKind::Parent => dependants.parent.push(member.mem),
|
||||
DependencyKind::Node => dependants.node.push(member.mem),
|
||||
DependencyKind::Child => dependants.child.push(member.mem),
|
||||
DependencyKind::Parent => dependants.parent.push(member.mem),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +379,7 @@ impl<'a> StateStruct<'a> {
|
|||
dependants
|
||||
}
|
||||
|
||||
// Mark the states that depend on the current state as dirty
|
||||
fn update_dependants(&self, mem: &Member) -> impl ToTokens {
|
||||
let dep = self.get_depenadants(mem);
|
||||
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 {
|
||||
let reduce_member = mem.reduce_self();
|
||||
let update_dependant = self.update_dependants(mem.mem);
|
||||
let member = &mem.mem.ident;
|
||||
|
||||
match mem.dep_kind {
|
||||
DepKind::Parent => {
|
||||
DependencyKind::Parent => {
|
||||
quote! {
|
||||
// resolve parent dependant state
|
||||
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! {
|
||||
// resolve child dependant state
|
||||
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! {
|
||||
// resolve node dependant state
|
||||
let mut resolution_order = states.keys().copied().collect::<Vec<_>>();
|
||||
|
@ -444,12 +555,12 @@ fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
|
|||
Ok(inside)
|
||||
}
|
||||
|
||||
struct Dependancy {
|
||||
struct Dependency {
|
||||
ctx_ty: Option<Type>,
|
||||
deps: Vec<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Dependancy {
|
||||
impl Parse for Dependency {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let deps: Option<Punctuated<Ident, Token![,]>> = {
|
||||
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)]
|
||||
struct Member {
|
||||
ty: Type,
|
||||
|
@ -493,9 +605,11 @@ impl Member {
|
|||
#[derive(Debug, Clone)]
|
||||
struct StateMember<'a> {
|
||||
mem: &'a Member,
|
||||
dep_kind: DepKind,
|
||||
// the kind of dependncies this state has
|
||||
dep_kind: DependencyKind,
|
||||
// the depenancy and if it is satified
|
||||
dep_mems: Vec<(&'a Member, bool)>,
|
||||
// the context this state requires
|
||||
ctx_ty: Option<Type>,
|
||||
}
|
||||
|
||||
|
@ -511,14 +625,14 @@ impl<'a> StateMember<'a> {
|
|||
.path
|
||||
.get_ident()
|
||||
.and_then(|i| match i.to_string().as_str() {
|
||||
"node_dep_state" => Some(DepKind::Node),
|
||||
"child_dep_state" => Some(DepKind::Child),
|
||||
"parent_dep_state" => Some(DepKind::Parent),
|
||||
"node_dep_state" => Some(DependencyKind::Node),
|
||||
"child_dep_state" => Some(DependencyKind::Child),
|
||||
"parent_dep_state" => Some(DependencyKind::Parent),
|
||||
_ => None,
|
||||
})?;
|
||||
match a.parse_args::<Dependancy>() {
|
||||
Ok(dependancy) => {
|
||||
let dep_mems = dependancy
|
||||
match a.parse_args::<Dependency>() {
|
||||
Ok(dependency) => {
|
||||
let dep_mems = dependency
|
||||
.deps
|
||||
.iter()
|
||||
.filter_map(|name| {
|
||||
|
@ -537,7 +651,7 @@ impl<'a> StateMember<'a> {
|
|||
mem,
|
||||
dep_kind,
|
||||
dep_mems,
|
||||
ctx_ty: dependancy.ctx_ty,
|
||||
ctx_ty: dependency.ctx_ty,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -550,6 +664,7 @@ impl<'a> StateMember<'a> {
|
|||
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 {
|
||||
let ident = &self.mem.ident;
|
||||
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));
|
||||
let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
|
||||
match self.dep_kind {
|
||||
DepKind::Node => {
|
||||
DependencyKind::Node => {
|
||||
quote!({
|
||||
current_state.#ident.reduce(#node_view, (#(¤t_state.#dep_idents,)*), #get_ctx)
|
||||
})
|
||||
}
|
||||
DepKind::Child => {
|
||||
DependencyKind::Child => {
|
||||
quote!({
|
||||
current_state.#ident.reduce(#node_view, children.iter().map(|c| (#(&c.#dep_idents)*)), #get_ctx)
|
||||
})
|
||||
}
|
||||
DepKind::Parent => {
|
||||
DependencyKind::Parent => {
|
||||
quote!({
|
||||
current_state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.#dep_idents)*)), #get_ctx)
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ use syn::{
|
|||
parse::{Parse, ParseStream, Result},
|
||||
LitStr, Token,
|
||||
};
|
||||
|
||||
pub struct StrSlice {
|
||||
pub map: BTreeMap<String, LitStr>,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use dioxus_core::*;
|
|||
|
||||
use crate::state::union_ordered_iter;
|
||||
|
||||
/// A view into a [VNode] with limited access.
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VNode<'a>,
|
||||
|
@ -9,6 +10,7 @@ pub struct 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 {
|
||||
if let VNode::Component(sc) = vnode {
|
||||
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 {
|
||||
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> {
|
||||
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
|
||||
}
|
||||
|
||||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then(|| self.el().and_then(|el| el.namespace))
|
||||
.tag
|
||||
.then(|| self.try_element().map(|el| el.tag))
|
||||
.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>> {
|
||||
self.el()
|
||||
self.try_element()
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.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> {
|
||||
self.mask
|
||||
.text
|
||||
.then(|| self.txt().map(|txt| txt.text))
|
||||
.then(|| self.try_text().map(|txt| txt.text))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Get the listeners if it is enabled in the mask
|
||||
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 {
|
||||
Some(el)
|
||||
} 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 {
|
||||
Some(txt)
|
||||
} 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)]
|
||||
pub enum AttributeMask {
|
||||
All,
|
||||
/// A list of attribute names that are visible, this list must be sorted
|
||||
Dynamic(Vec<&'static str>),
|
||||
/// A list of attribute names that are visible, this list must be sorted
|
||||
Static(&'static [&'static str]),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
/// A empty attribute mask
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
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 {
|
||||
Self::Dynamic(vec![new])
|
||||
}
|
||||
|
||||
/// Ensure the attribute list is sorted.
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
|
@ -107,6 +128,7 @@ impl AttributeMask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Combine two attribute masks
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
|
@ -127,6 +149,7 @@ impl AttributeMask {
|
|||
new
|
||||
}
|
||||
|
||||
/// Check if two attribute masks overlap
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter(
|
||||
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)]
|
||||
pub struct NodeMask {
|
||||
// must be sorted
|
||||
attritutes: AttributeMask,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
|
@ -187,11 +210,14 @@ pub struct NodeMask {
|
|||
}
|
||||
|
||||
impl NodeMask {
|
||||
/// A node mask with no parts visible.
|
||||
pub const NONE: Self = Self::new();
|
||||
/// A node mask with every part visible.
|
||||
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
|
||||
.with_text()
|
||||
.with_element();
|
||||
|
||||
/// Check if two masks overlap
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|| (self.namespace && other.namespace)
|
||||
|
@ -200,6 +226,7 @@ impl NodeMask {
|
|||
|| (self.listeners && other.listeners)
|
||||
}
|
||||
|
||||
/// Combine two node masks
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
attritutes,
|
||||
|
@ -220,29 +248,35 @@ impl NodeMask {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a empty node mask
|
||||
pub const fn new() -> Self {
|
||||
Self::new_with_attrs(AttributeMask::NONE)
|
||||
}
|
||||
|
||||
/// Allow the mask to view the tag
|
||||
pub const fn with_tag(mut self) -> Self {
|
||||
self.tag = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow the mask to view the namespace
|
||||
pub const fn with_namespace(mut self) -> Self {
|
||||
self.namespace = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow the mask to view the namespace and tag
|
||||
pub const fn with_element(self) -> Self {
|
||||
self.with_namespace().with_tag()
|
||||
}
|
||||
|
||||
/// Allow the mask to view the text
|
||||
pub const fn with_text(mut self) -> Self {
|
||||
self.text = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow the mask to view the listeners
|
||||
pub const fn with_listeners(mut self) -> Self {
|
||||
self.listeners = true;
|
||||
self
|
||||
|
|
|
@ -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)> {
|
||||
let mut nodes_updated = Vec::new();
|
||||
for mutations in mutations_vec {
|
||||
|
@ -203,6 +203,7 @@ impl<S: State> RealDom<S> {
|
|||
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(
|
||||
&mut self,
|
||||
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<()> {
|
||||
debug_assert_ne!(child_id, parent_id);
|
||||
let parent = &mut self[parent_id];
|
||||
|
@ -227,6 +229,7 @@ impl<S: State> RealDom<S> {
|
|||
Some(())
|
||||
}
|
||||
|
||||
/// Recursively increase the height of a node and its children
|
||||
fn increase_height(&mut self, id: ElementId, amount: u16) {
|
||||
let n = &mut self[id];
|
||||
n.height += amount;
|
||||
|
@ -262,16 +265,18 @@ impl<S: State> RealDom<S> {
|
|||
Some(node)
|
||||
}
|
||||
|
||||
/// Create a node
|
||||
fn insert(&mut self, node: Node<S>) {
|
||||
let current_len = self.nodes.len();
|
||||
let id = 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[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>> {
|
||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||
|
@ -409,6 +414,7 @@ pub struct Node<S: State> {
|
|||
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)]
|
||||
pub enum NodeType {
|
||||
Text {
|
||||
|
@ -438,18 +444,21 @@ impl<S: State> Node<S> {
|
|||
vdom.get_element(self.id).unwrap()
|
||||
}
|
||||
|
||||
/// link a child node
|
||||
fn add_child(&mut self, child: ElementId) {
|
||||
if let NodeType::Element { children, .. } = &mut self.node_type {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
/// remove a child node
|
||||
fn remove_child(&mut self, child: ElementId) {
|
||||
if let NodeType::Element { children, .. } = &mut self.node_type {
|
||||
children.retain(|c| c != &child);
|
||||
}
|
||||
}
|
||||
|
||||
/// link the parent node
|
||||
fn set_parent(&mut self, parent: ElementId) {
|
||||
self.parent = Some(parent);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use fxhash::FxHashSet;
|
|||
use crate::node_ref::{NodeMask, NodeView};
|
||||
use crate::traversable::Traversable;
|
||||
|
||||
/// Join two sorted iterators
|
||||
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
|
||||
s_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.
|
||||
/// 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.
|
||||
/// ```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 {
|
||||
/// The context is passed to the [ChildDepState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
/// The context is passed to the [ChildDepState::reduce] when it is resolved.
|
||||
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;
|
||||
/// 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;
|
||||
/// 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>(
|
||||
&mut self,
|
||||
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.
|
||||
/// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified.
|
||||
/// 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 {
|
||||
/// The context is passed to the [ParentDepState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
/// The context is passed to the [ParentDepState::reduce] when it is resolved.
|
||||
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]
|
||||
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;
|
||||
/// 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>(
|
||||
&mut self,
|
||||
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.
|
||||
/// 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.
|
||||
/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
|
||||
/// ```rust, ignore
|
||||
/// impl<'a, 'b> NodeDepState<(&'a TextWrap, &'b ChildLayout)> for Layout {
|
||||
/// type Ctx = LayoutCache;
|
||||
/// ```rust
|
||||
/// use dioxus_native_core::node_ref::{NodeMask, AttributeMask, NodeView};
|
||||
/// use dioxus_native_core::state::*;
|
||||
///
|
||||
/// #[derive(Clone, Copy, PartialEq)]
|
||||
/// struct TabIndex(usize);
|
||||
///
|
||||
/// impl NodeDepState for TabIndex {
|
||||
/// type Ctx = ();
|
||||
/// const NODE_MASK: NodeMask =
|
||||
/// NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([
|
||||
/// "width", "height"
|
||||
/// ])))
|
||||
/// .with_text();
|
||||
/// fn reduce<'a>(
|
||||
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
|
||||
/// "tabindex"
|
||||
/// ]));
|
||||
/// fn reduce(
|
||||
/// &mut self,
|
||||
/// node: NodeView,
|
||||
/// siblings: (&'a TextWrap, &'b ChildLayout),
|
||||
/// ctx: &Self::Ctx,
|
||||
/// siblings: (),
|
||||
/// ctx: &(),
|
||||
/// ) -> bool {
|
||||
/// let old = self.clone();
|
||||
/// 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;
|
||||
/// let old = self;
|
||||
/// for attr in node.attributes() {
|
||||
/// match attr.name {
|
||||
/// "width" => {
|
||||
/// width = attr.value.as_text().unwrap().parse().unwrap();
|
||||
/// }
|
||||
/// "height" => {
|
||||
/// height = attr.value.as_text().unwrap().parse().unwrap();
|
||||
/// "tabindex" => {
|
||||
/// self.0 = 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!(),
|
||||
/// }
|
||||
/// }
|
||||
/// self.width = width;
|
||||
/// self.height = height;
|
||||
/// old != self
|
||||
/// old != *self
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// The generic argument (Depstate) must be a tuple containing any number of borrowed elments that are either a [ChildDepState], [ParentDepState] or [NodeDepState].
|
||||
pub trait NodeDepState<DepState> {
|
||||
/// The generic argument (Depstate) must be a tuple containing any number of borrowed elements that are either a [ChildDepState], [ParentDepState] or [NodeDepState].
|
||||
// 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;
|
||||
/// 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;
|
||||
/// 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;
|
||||
}
|
||||
|
||||
|
@ -144,7 +214,6 @@ pub trait State: Default + Clone {
|
|||
) -> FxHashSet<ElementId>;
|
||||
}
|
||||
|
||||
// Todo: once GATs land we can model multable dependencies
|
||||
impl ChildDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// This is a interface for a tree with the ability to jump to a specific node
|
||||
pub trait Traversable {
|
||||
type Id: Copy;
|
||||
type Node;
|
||||
|
@ -55,6 +56,7 @@ pub trait Traversable {
|
|||
}
|
||||
}
|
||||
|
||||
/// Maps one type of tree to another. Similar to [std::iter::Map].
|
||||
pub struct Map<
|
||||
'a,
|
||||
T: Traversable,
|
||||
|
|
|
@ -5,7 +5,9 @@ use crate::{
|
|||
use dioxus_core::{DomEdit, ElementId, Mutations};
|
||||
|
||||
pub enum ElementProduced {
|
||||
/// The iterator produced an element by progressing to the next node in a depth first order.
|
||||
Progressed(ElementId),
|
||||
/// The iterator reached the end of the tree and looped back to the root
|
||||
Looped(ElementId),
|
||||
}
|
||||
impl ElementProduced {
|
||||
|
@ -42,8 +44,9 @@ impl NodePosition {
|
|||
}
|
||||
}
|
||||
|
||||
/// The focus system needs a iterator that can persist through changes in the [VirtualDom].
|
||||
/// Iterate through it with [ElementIter::next] [ElementIter::prev], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]).
|
||||
/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
|
||||
/// This iterator traverses the tree depth first.
|
||||
/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
|
||||
/// The iterator loops around when it reaches the end or the beginning.
|
||||
pub struct PersistantElementIter {
|
||||
// stack of elements and fragments
|
||||
|
|
Loading…
Reference in a new issue