diff --git a/packages/dioxus-tui/src/lib.rs b/packages/dioxus-tui/src/lib.rs index 99df66df9..ece97b673 100644 --- a/packages/dioxus-tui/src/lib.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -9,10 +9,8 @@ use std::{ use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::EventData; -use dioxus_native_core::{ - dioxus::{DioxusState, NodeImmutableDioxusExt}, - NodeId, RealDom, -}; +use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt}; +use dioxus_native_core::prelude::*; pub use rink::Config; use rink::{query::Query, render, Renderer, TuiContext}; diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index a5d6af001..6132d898d 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -300,19 +300,18 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { #defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty { #(#items)* - fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { + fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem { use dioxus_native_core::exports::shipyard::{IntoWorkloadSystem, Get, AddComponent}; use dioxus_native_core::tree::TreeRef; use dioxus_native_core::prelude::{NodeType, NodeView}; let node_mask = Self::NODE_MASK.build(); - (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::RunPassView #trait_generics| { + (move |data: #combined_dependencies_quote, run_view: dioxus_native_core::prelude::RunPassView #trait_generics| { let (#(#split_views,)*) = data; - let (tree, types, _, _, _) = &run_view; - let tree = tree.clone(); - let node_types = types.clone(); - dioxus_native_core::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { + let tree = run_view.tree.clone(); + let node_types = run_view.node_type.clone(); + dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err)); // get all of the states from the tree view // Safety: No node has itself as a parent or child. diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 938ed16c4..d288883b0 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -1,3 +1,5 @@ +//! Integration between Dioxus and the RealDom + use crate::tree::TreeMut; use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -8,14 +10,15 @@ use crate::{ ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode, }, - prelude::NodeImmutable, + prelude::*, real_dom::NodeTypeMut, - NodeId, NodeMut, RealDom, + NodeId, }; #[derive(Component)] struct ElementIdComponent(ElementId); +/// The state of the Dioxus integration with the RealDom pub struct DioxusState { templates: FxHashMap>, stack: Vec, @@ -23,6 +26,7 @@ pub struct DioxusState { } impl DioxusState { + /// Initialize the DioxusState in the RealDom pub fn create(rdom: &mut RealDom) -> Self { let root_id = rdom.root_id(); let mut root = rdom.get_mut(root_id).unwrap(); @@ -34,10 +38,12 @@ impl DioxusState { } } + /// Convert an ElementId to a NodeId pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId { self.try_element_to_node_id(element_id).unwrap() } + /// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom. pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option { self.node_id_mapping.get(element_id.0).copied().flatten() } @@ -51,7 +57,7 @@ impl DioxusState { self.node_id_mapping[element_id.0] = Some(node_id); } - pub fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { + fn load_child(&self, rdom: &RealDom, path: &[u8]) -> NodeId { let mut current = rdom.get(*self.stack.last().unwrap()).unwrap(); for i in path { let new_id = current.child_ids()[*i as usize]; @@ -170,7 +176,7 @@ impl DioxusState { let mut node_type_mut = node.node_type_mut(); if let NodeTypeMut::Element(element) = &mut node_type_mut { if let BorrowedAttributeValue::None = &value { - element.remove_attributes(&OwnedAttributeDiscription { + element.remove_attribute(&OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), }); @@ -266,7 +272,10 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { } } +/// A trait that extends the `NodeImmutable` trait with methods that are useful for dioxus. pub trait NodeImmutableDioxusExt: NodeImmutable { + /// Returns the id of the element that this node is mounted to. + /// Not all nodes are mounted to an element, only nodes with dynamic content that have been renderered will have an id. fn mounted_id(&self) -> Option { let id = self.get::(); id.map(|id| id.0) diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index 77926eb96..84fd61ac4 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -1,3 +1,5 @@ +//! Utility functions for applying layout attributes to taffy layout + /* - [ ] pub display: Display, ----> taffy doesnt support all display types - [x] pub position_type: PositionType, --> taffy doesnt support everything @@ -46,12 +48,14 @@ use taffy::{ style::{FlexDirection, PositionType}, }; +/// Default values for layout attributes #[derive(Default)] pub struct LayoutConfigeration { /// the default border widths to use pub border_widths: BorderWidths, } +/// Default border widths pub struct BorderWidths { /// the default border width to use for thin borders pub thin: f32, diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 955e1ba6a..b57cf57c9 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,10 +1,10 @@ +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + use std::any::Any; use std::hash::BuildHasherDefault; -pub use node_ref::NodeMask; -pub use passes::AnyMapLike; -pub use passes::{run_pass, Dependancy, PassDirection, RunPassView, State, TypeErasedPass}; -pub use real_dom::{NodeMut, NodeRef, RealDom}; +use node_ref::NodeMask; use rustc_hash::FxHasher; #[cfg(feature = "dioxus")] @@ -19,22 +19,29 @@ pub mod tree; pub mod utils; pub use shipyard::EntityId as NodeId; -// exported for the macro pub mod exports { + //! Important dependencies that are used by the rest of the library + //! Feel free to just add the dependencies in your own Crates.toml + // exported for the macro #[doc(hidden)] pub use rustc_hash::FxHashSet; pub use shipyard; } +/// A prelude of commonly used items pub mod prelude { pub use crate::node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeView, TextNode}; pub use crate::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; - pub use crate::passes::{AnyState, Dependancy, DependancyView, State}; + pub use crate::passes::{run_pass, PassDirection, RunPassView, TypeErasedState}; + pub use crate::passes::{Dependancy, DependancyView, State}; pub use crate::real_dom::{NodeImmutable, NodeMut, NodeRef, RealDom}; pub use crate::NodeId; pub use crate::SendAnyMap; } +/// A map that can be sent between threads pub type FxDashMap = dashmap::DashMap>; +/// A set that can be sent between threads pub type FxDashSet = dashmap::DashSet>; +/// A map of types that can be sent between threads pub type SendAnyMap = anymap::Map; diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 1e6331c86..a3362ada2 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,22 +1,36 @@ +//! Items related to Nodes in the RealDom + use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::Component; -use std::{any::Any, fmt::Debug}; +use std::{ + any::Any, + fmt::{Debug, Display}, +}; +/// A element node in the RealDom #[derive(Debug, Clone, Default)] pub struct ElementNode { + /// The [tag](https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) of the element pub tag: String, + /// The [namespace](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI) of the element pub namespace: Option, + /// The attributes of the element pub attributes: FxHashMap>, + /// The events the element is listening for pub listeners: FxHashSet, } +/// A text node in the RealDom #[derive(Debug, Clone, Default)] pub struct TextNode { + /// The text of the node pub text: String, + /// The events the node is listening for pub listeners: FxHashSet, } impl TextNode { + /// Create a new text node pub fn new(text: String) -> Self { Self { text, @@ -25,17 +39,26 @@ impl TextNode { } } -/// A type of node with data specific to the node type. The types are a subset of the [VNode] types. +/// A type of node with data specific to the node type. #[derive(Debug, Clone, Component)] pub enum NodeType { + /// A text node Text(TextNode), + /// An element node Element(ElementNode), + /// A placeholder node. This can be used as a cheaper placeholder for a node that will be created later Placeholder, } +/// A discription of an attribute on a DOM node, such as `id` or `href`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct OwnedAttributeDiscription { + /// The name of the attribute. pub name: String, + /// The namespace of the attribute used to identify what kind of attribute it is. + /// + /// For renderers that use HTML, this can be used to identify if the attribute is a style attribute. + /// Instead of parsing the style attribute every time a style is changed, you can set an attribute with the `style` namespace. pub namespace: Option, } @@ -50,16 +73,24 @@ pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> { pub value: &'a OwnedAttributeValue, } +/// The value of an attribute on a DOM node. This contains non-text values to allow users to skip parsing attribute values in some cases. #[derive(Clone)] pub enum OwnedAttributeValue { + /// A string value. This is the most common type of attribute. Text(String), + /// A floating point value. Float(f64), + /// An integer value. Int(i64), + /// A boolean value. Bool(bool), + /// A custom value specific to the renderer Custom(V), } +/// Something that can be converted from a borrowed [Any] value. pub trait FromAnyValue: Clone + 'static { + /// Convert from an [Any] value. fn from_any_value(value: &dyn Any) -> Self; } @@ -79,6 +110,18 @@ impl Debug for OwnedAttributeValue { } } +impl Display for OwnedAttributeValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Text(arg0) => f.write_str(arg0), + Self::Float(arg0) => f.write_str(&arg0.to_string()), + Self::Int(arg0) => f.write_str(&arg0.to_string()), + Self::Bool(arg0) => f.write_str(&arg0.to_string()), + Self::Custom(arg0) => f.write_str(&arg0.to_string()), + } + } +} + #[cfg(feature = "dioxus")] impl From> for OwnedAttributeValue { fn from(value: dioxus_core::BorrowedAttributeValue<'_>) -> Self { @@ -94,6 +137,7 @@ impl From> for OwnedAtt } impl OwnedAttributeValue { + /// Attempt to convert the attribute value to a string. pub fn as_text(&self) -> Option<&str> { match self { OwnedAttributeValue::Text(text) => Some(text), @@ -101,20 +145,25 @@ impl OwnedAttributeValue { } } + /// Attempt to convert the attribute value to a float. pub fn as_float(&self) -> Option { match self { OwnedAttributeValue::Float(float) => Some(*float), + OwnedAttributeValue::Int(int) => Some(*int as f64), _ => None, } } + /// Attempt to convert the attribute value to an integer. pub fn as_int(&self) -> Option { match self { + OwnedAttributeValue::Float(float) => Some(*float as i64), OwnedAttributeValue::Int(int) => Some(*int), _ => None, } } + /// Attempt to convert the attribute value to a boolean. pub fn as_bool(&self) -> Option { match self { OwnedAttributeValue::Bool(bool) => Some(*bool), @@ -122,6 +171,7 @@ impl OwnedAttributeValue { } } + /// Attempt to convert the attribute value to a custom value. pub fn as_custom(&self) -> Option<&V> { match self { OwnedAttributeValue::Custom(custom) => Some(custom), diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index e50374c0b..22ab5d8f1 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,3 +1,5 @@ +//! Utilities that provide limited access to nodes + use rustc_hash::FxHashSet; use crate::{ @@ -5,7 +7,7 @@ use crate::{ NodeId, }; -/// A view into a [VNode] with limited access. +/// A view into a [NodeType] with a mask that determines what is visible. #[derive(Debug)] pub struct NodeView<'a, V: FromAnyValue = ()> { id: NodeId, @@ -98,11 +100,14 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { /// A mask that contains a list of attributes that are visible. #[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMask { + /// All attributes are visible All, + /// Only the given attributes are visible Some(FxHashSet>), } impl AttributeMask { + /// Check if the given attribute is visible fn contains_attribute(&self, attr: &str) -> bool { match self { AttributeMask::All => true, @@ -203,9 +208,12 @@ impl NodeMask { } } +/// A builder for a mask that controls what attributes are visible. #[derive(PartialEq, Eq, Clone, Debug)] pub enum AttributeMaskBuilder<'a> { + /// All attributes are visible All, + /// Only the given attributes are visible Some(&'a [&'a str]), } diff --git a/packages/native-core/src/node_watcher.rs b/packages/native-core/src/node_watcher.rs index b9b79ebab..27a449ef9 100644 --- a/packages/native-core/src/node_watcher.rs +++ b/packages/native-core/src/node_watcher.rs @@ -1,4 +1,6 @@ -use crate::{node::FromAnyValue, NodeMut}; +//! Helpers for watching for changes in the DOM tree. + +use crate::{node::FromAnyValue, prelude::*}; /// A trait for watching for changes in the DOM tree. pub trait NodeWatcher { diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index ff55f65f2..849be75e5 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -1,7 +1,6 @@ -use anymap::AnyMap; use parking_lot::RwLock; use rustc_hash::{FxHashMap, FxHashSet}; -use shipyard::{Component, Unique, UniqueView, View, WorkloadSystem}; +use shipyard::{Borrow, BorrowInfo, Component, Unique, UniqueView, View, WorkloadSystem}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::marker::PhantomData; @@ -104,6 +103,7 @@ impl DirtyNodeStates { } } +/// A state that is automatically inserted in a node with dependencies. pub trait State: Any + Send + Sync { /// This is a tuple of (T: State, ..) of states read from the parent required to run this pass type ParentDependencies: Dependancy; @@ -139,24 +139,77 @@ pub trait State: Any + Send + Sync { dependants: FxHashSet, pass_direction: PassDirection, ) -> WorkloadSystem; + + /// Converts to a type erased version of the trait + fn to_type_erased() -> TypeErasedState + where + Self: Sized, + { + let node_mask = Self::NODE_MASK.build(); + TypeErasedState { + this_type_id: TypeId::of::(), + combined_dependancy_type_ids: all_dependanices::().iter().copied().collect(), + parent_dependant: !Self::ParentDependencies::type_ids().is_empty(), + child_dependant: !Self::ChildDependencies::type_ids().is_empty(), + dependants: FxHashSet::default(), + mask: node_mask, + pass_direction: pass_direction::(), + workload: Self::workload_system, + phantom: PhantomData, + } + } } -pub type RunPassView<'a, V = ()> = ( - TreeRefView<'a>, - View<'a, NodeType>, - UniqueView<'a, DirtyNodesResult>, - UniqueView<'a, DirtyNodeStates>, - UniqueView<'a, SendAnyMapWrapper>, -); +fn pass_direction>() -> PassDirection { + if S::ChildDependencies::type_ids() + .iter() + .any(|type_id| *type_id == TypeId::of::()) + { + PassDirection::ChildToParent + } else if S::ParentDependencies::type_ids() + .iter() + .any(|type_id| *type_id == TypeId::of::()) + { + PassDirection::ParentToChild + } else { + PassDirection::AnyOrder + } +} -pub fn run_pass( +fn all_dependanices>() -> Box<[TypeId]> { + let mut dependencies = S::ParentDependencies::type_ids().to_vec(); + dependencies.extend(S::ChildDependencies::type_ids().iter()); + dependencies.extend(S::NodeDependencies::type_ids().iter()); + dependencies.into_boxed_slice() +} + +#[doc(hidden)] +#[derive(Borrow, BorrowInfo)] +pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> { + pub tree: TreeRefView<'a>, + pub node_type: View<'a, NodeType>, + dirty_nodes_result: UniqueView<'a, DirtyNodesResult>, + node_states: UniqueView<'a, DirtyNodeStates>, + any_map: UniqueView<'a, SendAnyMapWrapper>, +} + +// This is used by the macro +/// Updates the given pass, marking any nodes that were changed +#[doc(hidden)] +pub fn run_pass( type_id: TypeId, dependants: FxHashSet, pass_direction: PassDirection, view: RunPassView, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool, ) { - let (tree, _, nodes_updated, dirty, ctx, ..) = view; + let RunPassView { + tree, + dirty_nodes_result: nodes_updated, + node_states: dirty, + any_map: ctx, + .. + } = view; let ctx = ctx.as_ref(); match pass_direction { PassDirection::ParentToChild => { @@ -199,64 +252,8 @@ pub fn run_pass( } } -pub trait AnyState: State { - fn to_type_erased() -> TypeErasedPass - where - Self: Sized, - { - let node_mask = Self::NODE_MASK.build(); - TypeErasedPass { - this_type_id: TypeId::of::(), - combined_dependancy_type_ids: Self::all_dependanices().iter().copied().collect(), - parent_dependant: !Self::parent_type_ids().is_empty(), - child_dependant: !Self::child_type_ids().is_empty(), - dependants: FxHashSet::default(), - mask: node_mask, - pass_direction: Self::pass_direction(), - workload: Self::workload_system, - phantom: PhantomData, - } - } - - fn parent_type_ids() -> Box<[TypeId]> { - Self::ParentDependencies::type_ids() - } - - fn child_type_ids() -> Box<[TypeId]> { - Self::ChildDependencies::type_ids() - } - - fn node_type_ids() -> Box<[TypeId]> { - Self::NodeDependencies::type_ids() - } - - fn all_dependanices() -> Box<[TypeId]> { - let mut dependencies = Self::parent_type_ids().to_vec(); - dependencies.extend(Self::child_type_ids().iter()); - dependencies.extend(Self::node_type_ids().iter()); - dependencies.into_boxed_slice() - } - - fn pass_direction() -> PassDirection { - if Self::child_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::()) - { - PassDirection::ChildToParent - } else if Self::parent_type_ids() - .iter() - .any(|type_id| *type_id == TypeId::of::()) - { - PassDirection::ParentToChild - } else { - PassDirection::AnyOrder - } - } -} - -impl> AnyState for S {} - -pub struct TypeErasedPass { +/// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`] +pub struct TypeErasedState { pub(crate) this_type_id: TypeId, pub(crate) parent_dependant: bool, pub(crate) child_dependant: bool, @@ -268,7 +265,7 @@ pub struct TypeErasedPass { phantom: PhantomData, } -impl TypeErasedPass { +impl TypeErasedState { pub(crate) fn create_workload(&self) -> WorkloadSystem { (self.workload)( self.this_type_id, @@ -278,32 +275,23 @@ impl TypeErasedPass { } } +/// The direction that a pass should be run in #[derive(Debug, Clone, Copy)] pub enum PassDirection { + /// The pass should be run from the root to the leaves ParentToChild, + /// The pass should be run from the leaves to the root ChildToParent, + /// The pass can be run in any order AnyOrder, } -pub trait AnyMapLike<'a> { - fn get(self) -> Option<&'a T>; -} - -impl<'a> AnyMapLike<'a> for &'a AnyMap { - fn get(self) -> Option<&'a T> { - self.get() - } -} - -impl<'a> AnyMapLike<'a> for &'a SendAnyMap { - fn get(self) -> Option<&'a T> { - todo!() - } -} - +/// A trait that is implemented for all the dependancies of a [`State`] pub trait Dependancy { + /// A tuple with all the elements of the dependancy as [`DependancyView`] type ElementBorrowed<'a>; + /// Returns a list of all the [`TypeId`]s of the elements in the dependancy fn type_ids() -> Box<[TypeId]> { Box::new([]) } @@ -323,6 +311,7 @@ macro_rules! impl_dependancy { // TODO: track what components are actually read to update subscriptions // making this a wrapper makes it possible to implement that optimization without a breaking change +/// A immutable view of a [`State`] pub struct DependancyView<'a, T> { inner: &'a T, } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index d721428ea..016633fc7 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,3 +1,5 @@ +//! A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. + use rustc_hash::{FxHashMap, FxHashSet}; use shipyard::error::GetStorage; use shipyard::track::Untracked; @@ -13,14 +15,15 @@ use crate::node::{ }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; -use crate::passes::{DirtyNodeStates, TypeErasedPass}; +use crate::passes::{DirtyNodeStates, TypeErasedState}; use crate::prelude::AttributeMaskBuilder; use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView}; use crate::NodeId; use crate::{FxDashSet, SendAnyMap}; +/// The context passes can receive when they are executed #[derive(Unique)] -pub struct SendAnyMapWrapper(SendAnyMap); +pub(crate) struct SendAnyMapWrapper(SendAnyMap); impl Deref for SendAnyMapWrapper { type Target = SendAnyMap; @@ -30,8 +33,9 @@ impl Deref for SendAnyMapWrapper { } } +/// The nodes that were changed when updating the state of the RealDom #[derive(Unique, Default)] -pub struct DirtyNodesResult(FxDashSet); +pub(crate) struct DirtyNodesResult(FxDashSet); impl Deref for DirtyNodesResult { type Target = FxDashSet; @@ -41,13 +45,15 @@ impl Deref for DirtyNodesResult { } } -pub struct NodesDirty { +/// The nodes that have been marked as dirty in the RealDom +pub(crate) struct NodesDirty { passes_updated: FxHashMap>, nodes_updated: FxHashMap, - pub(crate) passes: Box<[TypeErasedPass]>, + pub(crate) passes: Box<[TypeErasedState]>, } impl NodesDirty { + /// Mark a node as dirty fn mark_dirty(&mut self, node_id: NodeId, mask: NodeMask) { self.passes_updated.entry(node_id).or_default().extend( self.passes @@ -62,6 +68,7 @@ impl NodesDirty { } } + /// Mark a node as added or removed from the tree fn mark_parent_added_or_removed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); for pass in &*self.passes { @@ -71,6 +78,7 @@ impl NodesDirty { } } + /// Mark a node as having a child added or removed fn mark_child_changed(&mut self, node_id: NodeId) { let hm = self.passes_updated.entry(node_id).or_default(); for pass in &*self.passes { @@ -85,9 +93,13 @@ type NodeWatchers = Arc + Send + Sync>>>>; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. -/// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. +/// To get started: +/// 1) Implement [crate::passes::State] for each part of your state that you want to compute incrementally +/// 2) Create a RealDom [RealDom::new], passing in each state you created +/// 3) Update the state of the RealDom by adding and modifying nodes +/// 4) Call [RealDom::update_state] to update the state of incrementally computed values on each node /// -/// # Custom values +/// # Custom attribute values /// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients. pub struct RealDom { pub(crate) world: World, @@ -100,10 +112,11 @@ pub struct RealDom { } impl RealDom { - pub fn new(mut passes: Box<[TypeErasedPass]>) -> RealDom { + /// Create a new RealDom with the given states that will be inserted and updated when needed + pub fn new(mut tracked_states: Box<[TypeErasedState]>) -> RealDom { // resolve dependants for each pass - for i in 1..passes.len() { - let (before, after) = passes.split_at_mut(i); + for i in 1..tracked_states.len() { + let (before, after) = tracked_states.split_at_mut(i); let (current, before) = before.split_last_mut().unwrap(); for pass in before.iter_mut().chain(after.iter_mut()) { if current @@ -114,7 +127,7 @@ impl RealDom { } } } - let workload = construct_workload(&mut passes); + let workload = construct_workload(&mut tracked_states); let (workload, _) = workload.build().unwrap(); let mut world = World::new(); let root_node: NodeType = NodeType::Element(ElementNode { @@ -132,7 +145,10 @@ impl RealDom { let mut passes_updated = FxHashMap::default(); let mut nodes_updated = FxHashMap::default(); - passes_updated.insert(root_id, passes.iter().map(|x| x.this_type_id).collect()); + passes_updated.insert( + root_id, + tracked_states.iter().map(|x| x.this_type_id).collect(), + ); nodes_updated.insert(root_id, NodeMaskBuilder::ALL.build()); RealDom { @@ -141,7 +157,7 @@ impl RealDom { dirty_nodes: NodesDirty { passes_updated, nodes_updated, - passes, + passes: tracked_states, }, node_watchers: Default::default(), workload, @@ -150,14 +166,17 @@ impl RealDom { } } - pub fn tree_mut(&self) -> TreeMutView { - self.world.borrow().unwrap() - } - + /// Get a reference to the tree. pub fn tree_ref(&self) -> TreeRefView { self.world.borrow().unwrap() } + /// Get a mutable reference to the tree. + pub fn tree_mut(&self) -> TreeMutView { + self.world.borrow().unwrap() + } + + /// Create a new node of the given type in the dom and return a mutable reference to it. pub fn create_node(&mut self, node: NodeType) -> NodeMut<'_, V> { let id = self.world.add_entity(node); self.tree_mut().create_node(id); @@ -196,17 +215,20 @@ impl RealDom { self.root_id } + /// Get a reference to a node. pub fn get(&self, id: NodeId) -> Option> { self.tree_ref() .contains(id) .then_some(NodeRef { id, dom: self }) } + /// Get a mutable reference to a node. pub fn get_mut(&mut self, id: NodeId) -> Option> { let contains = self.tree_ref().contains(id); contains.then(|| NodeMut::new(id, self)) } + /// Borrow a component from the world without updating the dirty nodes. fn borrow_raw<'a, B: IntoBorrow>(&'a self) -> Result where B::Borrow: shipyard::Borrow<'a, View = B>, @@ -214,6 +236,7 @@ impl RealDom { self.world.borrow() } + /// Borrow a component from the world without updating the dirty nodes. fn borrow_node_type_mut(&self) -> Result>, GetStorage> { self.world.borrow() } @@ -250,6 +273,7 @@ impl RealDom { (dirty.0, nodes_updated) } + /// Traverses the dom in a depth first manner, calling the provided function on each node. pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root_id()]; let tree = self.tree_ref(); @@ -262,6 +286,7 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner, calling the provided function on each node. pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); @@ -277,6 +302,7 @@ impl RealDom { } } + /// Traverses the dom in a depth first manner mutably, calling the provided function on each node. pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { @@ -292,6 +318,7 @@ impl RealDom { } } + /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node. pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); @@ -308,6 +335,7 @@ impl RealDom { } } + /// Adds a [`NodeWatcher`] to the dom. Node watchers are called whenever a node is created or removed. pub fn add_node_watcher(&mut self, watcher: impl NodeWatcher + 'static + Send + Sync) { self.node_watchers.write().unwrap().push(Box::new(watcher)); } @@ -323,6 +351,7 @@ impl RealDom { } } +/// A reference to a tracked component in a node. pub struct ViewEntry<'a, V: Component + Send + Sync> { view: View<'a, V>, id: NodeId, @@ -342,6 +371,7 @@ impl<'a, V: Component + Send + Sync> Deref for ViewEntry<'a, V> { } } +/// A mutable reference to a tracked component in a node. pub struct ViewEntryMut<'a, V: Component + Send + Sync> { view: ViewMut<'a, V, Untracked>, id: NodeId, @@ -367,16 +397,21 @@ impl<'a, V: Component + Send + Sync> DerefMut for ViewEntr } } +/// A immutable view of a node pub trait NodeImmutable: Sized { + /// Get the real dom this node was created in fn real_dom(&self) -> &RealDom; + /// Get the id of the current node fn id(&self) -> NodeId; + /// Get the type of the current node #[inline] fn node_type(&self) -> ViewEntry> { self.get().unwrap() } + /// Get a component from the current node #[inline] fn get<'a, T: Component + Sync + Send>(&'a self) -> Option> { // self.real_dom().tree.get(self.id()) @@ -385,11 +420,13 @@ pub trait NodeImmutable: Sized { .then(|| ViewEntry::new(view, self.id())) } + /// Get the ids of the children of the current node #[inline] fn child_ids(&self) -> Vec { self.real_dom().tree_ref().children_ids(self.id()) } + /// Get the children of the current node #[inline] fn children(&self) -> Vec> { self.child_ids() @@ -401,11 +438,13 @@ pub trait NodeImmutable: Sized { .collect() } + /// Get the id of the parent of the current node #[inline] fn parent_id(&self) -> Option { self.real_dom().tree_ref().parent_id(self.id()) } + /// Get the parent of the current node #[inline] fn parent(&self) -> Option> { self.parent_id().map(|id| NodeRef { @@ -414,6 +453,7 @@ pub trait NodeImmutable: Sized { }) } + /// Get the node after the current node #[inline] fn next(&self) -> Option> { let parent = self.parent_id()?; @@ -429,6 +469,7 @@ pub trait NodeImmutable: Sized { } } + /// Get the node before the current node #[inline] fn prev(&self) -> Option> { let parent = self.parent_id()?; @@ -444,12 +485,14 @@ pub trait NodeImmutable: Sized { } } + /// Get the height of the current node in the tree (the number of nodes between the current node and the root) #[inline] fn height(&self) -> u16 { self.real_dom().tree_ref().height(self.id()).unwrap() } } +/// An immutable reference to a node in a RealDom pub struct NodeRef<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a RealDom, @@ -478,12 +521,14 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeRef<'a, V> { } } +/// A mutable refrence to a node in the RealDom that tracks what States need to be updated pub struct NodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, dom: &'a mut RealDom, } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + /// Create a new mutable refrence to a node in a RealDom pub fn new(id: NodeId, dom: &'a mut RealDom) -> Self { Self { id, dom } } @@ -502,16 +547,19 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeImmutable for NodeMut<'a, V> { } impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { + /// Get the real dom this node was created in mutably #[inline(always)] pub fn real_dom_mut(&mut self) -> &mut RealDom { self.dom } + /// Get the parent of this node mutably #[inline] pub fn parent_mut(&mut self) -> Option> { self.parent_id().map(|id| NodeMut { id, dom: self.dom }) } + /// Get a component from the current node mutably #[inline] pub fn get_mut + Sync + Send>( &mut self, @@ -527,6 +575,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { Some(ViewEntryMut::new(view_mut, self.id)) } + /// Insert a custom component into this node + /// + /// Note: Components that implement State and are added when the RealDom is created will automatically be created #[inline] pub fn insert(&mut self, value: T) { // mark the node state as dirty @@ -539,6 +590,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.world.add_component(self.id, value); } + /// Get the next node #[inline] pub fn next_mut(self) -> Option> { let parent = self.parent_id()?; @@ -551,6 +603,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Get the previous node #[inline] pub fn prev_mut(self) -> Option> { let parent = self.parent_id()?; @@ -563,6 +616,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Add the given node to the end of this nodes children #[inline] pub fn add_child(&mut self, child: NodeId) { self.dom.dirty_nodes.mark_child_changed(self.id); @@ -571,6 +625,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { NodeMut::new(child, self.dom).mark_moved(); } + /// Insert this node after the given node #[inline] pub fn insert_after(&mut self, old: NodeId) { let id = self.id(); @@ -583,6 +638,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + /// Insert this node before the given node #[inline] pub fn insert_before(&mut self, old: NodeId) { let id = self.id(); @@ -595,6 +651,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.mark_moved(); } + /// Remove this node from the RealDom #[inline] pub fn remove(&mut self) { let id = self.id(); @@ -629,8 +686,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree_mut().remove_single(id); } + /// Replace this node with a different node #[inline] - pub fn replace(&mut self, new: NodeId) { + pub fn replace(mut self, new: NodeId) { self.mark_removed(); if let Some(parent_id) = self.parent_id() { self.real_dom_mut() @@ -644,6 +702,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { self.dom.tree_mut().replace(id, new); } + /// Add an event listener #[inline] pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); @@ -673,6 +732,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Remove an event listener #[inline] pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); @@ -694,6 +754,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// mark that this node was removed for the incremental system fn mark_removed(&mut self) { let watchers = self.dom.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { @@ -701,6 +762,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// mark that this node was moved for the incremental system fn mark_moved(&mut self) { let watchers = self.dom.node_watchers.clone(); for watcher in &*watchers.read().unwrap() { @@ -708,6 +770,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Get a mutable reference to the type of the current node pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> { let id = self.id(); let RealDom { @@ -730,6 +793,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } + /// Set the type of the current node pub fn set_type(&mut self, new: NodeType) { { let mut view: ViewMut> = self.dom.borrow_node_type_mut().unwrap(); @@ -740,6 +804,8 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { .mark_dirty(self.id, NodeMaskBuilder::ALL.build()) } + /// Clone a node and it's children and returns the id of the new node. + /// This is more effecient than creating the node from scratch because it can pre-allocate the memory required. #[inline] pub fn clone_node(&mut self) -> NodeId { let new_node = self.node_type().clone(); @@ -757,12 +823,17 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { } } +/// A mutable refrence to the type of a node in the RealDom pub enum NodeTypeMut<'a, V: FromAnyValue + Send + Sync = ()> { + /// An element node Element(ElementNodeMut<'a, V>), + /// A text node Text(TextNodeMut<'a, V>), + /// A placeholder node Placeholder, } +/// A mutable refrence to a text node in the RealDom pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, text: ViewEntryMut<'a, NodeType>, @@ -770,6 +841,7 @@ pub struct TextNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { } impl TextNodeMut<'_, V> { + /// Get the underlying test of the node pub fn text(&self) -> &str { match &*self.text { NodeType::Text(text) => &text.text, @@ -777,6 +849,7 @@ impl TextNodeMut<'_, V> { } } + /// Get the underlying text mutably pub fn text_mut(&mut self) -> &mut String { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); @@ -804,6 +877,7 @@ impl DerefMut for TextNodeMut<'_, V> { } } +/// A mutable refrence to a text Element node in the RealDom pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { id: NodeId, element: ViewEntryMut<'a, NodeType>, @@ -811,6 +885,7 @@ pub struct ElementNodeMut<'a, V: FromAnyValue + Send + Sync = ()> { } impl ElementNodeMut<'_, V> { + /// Get the current element fn element(&self) -> &ElementNode { match &*self.element { NodeType::Element(element) => element, @@ -818,6 +893,7 @@ impl ElementNodeMut<'_, V> { } } + /// Get the current element mutably (does not mark anything as dirty) fn element_mut(&mut self) -> &mut ElementNode { match &mut *self.element { NodeType::Element(element) => element, @@ -825,30 +901,36 @@ impl ElementNodeMut<'_, V> { } } + /// Get the tag of the element pub fn tag(&self) -> &str { &self.element().tag } + /// Get a mutable reference to the tag of the element pub fn tag_mut(&mut self) -> &mut String { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_tag().build()); &mut self.element_mut().tag } + /// Get a reference to the namespace the element is in pub fn namespace(&self) -> Option<&str> { self.element().namespace.as_deref() } + /// Get a mutable reference to the namespace the element is in pub fn namespace_mut(&mut self) -> &mut Option { self.dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_namespace().build()); &mut self.element_mut().namespace } + /// Get a reference to all of the attributes currently set on the element pub fn attributes(&self) -> &FxHashMap> { &self.element().attributes } + /// Set an attribute in the element pub fn set_attribute( &mut self, name: OwnedAttributeDiscription, @@ -863,7 +945,8 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.insert(name, value) } - pub fn remove_attributes( + /// Remove an attribute from the element + pub fn remove_attribute( &mut self, name: &OwnedAttributeDiscription, ) -> Option> { @@ -876,6 +959,7 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.remove(name) } + /// Get an attribute of the element pub fn get_attribute_mut( &mut self, name: &OwnedAttributeDiscription, @@ -889,13 +973,18 @@ impl ElementNodeMut<'_, V> { self.element_mut().attributes.get_mut(name) } + /// Get the set of all events the element is listening to pub fn listeners(&self) -> &FxHashSet { &self.element().listeners } } -fn construct_workload(passes: &mut [TypeErasedPass]) -> Workload { +// Create a workload from all of the passes. This orders the passes so that each pass will only run at most once. +fn construct_workload( + passes: &mut [TypeErasedState], +) -> Workload { let mut workload = Workload::new("Main Workload"); + // Assign a unique index to keep track of each pass let mut unresloved_workloads = passes .iter_mut() .enumerate() @@ -922,6 +1011,7 @@ fn construct_workload(passes: &mut [TypeErasedPas .map(|workload| workload.after_all(dependancy_id.to_string())); } } + // Add all of the passes for (_, _, mut workload_system) in unresloved_workloads { workload = workload.with_system(workload_system.take().unwrap()); } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index ec5e02895..99ac07d16 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -1,7 +1,10 @@ +//! A tree of nodes intigated with shipyard + use crate::NodeId; use shipyard::{Component, EntitiesViewMut, Get, View, ViewMut}; use std::fmt::Debug; +/// A node in a tree. #[derive(PartialEq, Eq, Clone, Debug, Component)] pub struct Node { parent: Option, @@ -9,24 +12,38 @@ pub struct Node { height: u16, } +/// A view of a tree. pub type TreeRefView<'a> = View<'a, Node>; +/// A mutable view of a tree. pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>); +/// A immutable view of a tree. pub trait TreeRef { + /// The parent id of the node. fn parent_id(&self, id: NodeId) -> Option; + /// The children ids of the node. fn children_ids(&self, id: NodeId) -> Vec; + /// The height of the node. fn height(&self, id: NodeId) -> Option; + /// Returns true if the node exists. fn contains(&self, id: NodeId) -> bool; } +/// A mutable view of a tree. pub trait TreeMut: TreeRef { + /// Removes the node and all of its children. fn remove(&mut self, id: NodeId); + /// Removes the node and all of its children. fn remove_single(&mut self, id: NodeId); - fn set_height(&mut self, node: NodeId, height: u16); + /// Adds a new node to the tree. fn create_node(&mut self, id: NodeId); + /// Adds a child to the node. fn add_child(&mut self, parent: NodeId, new: NodeId); + /// Replaces the node with a new node. fn replace(&mut self, old_id: NodeId, new_id: NodeId); + /// Inserts a node before another node. fn insert_before(&mut self, old_id: NodeId, new_id: NodeId); + /// Inserts a node after another node. fn insert_after(&mut self, old_id: NodeId, new_id: NodeId); } @@ -79,18 +96,6 @@ impl<'a> TreeMut for TreeMutView<'a> { } } - fn set_height(&mut self, node: NodeId, height: u16) { - let children = { - let mut node_data_mut = &mut self.1; - let mut node = (&mut node_data_mut).get(node).unwrap(); - node.height = height; - node.children.clone() - }; - for child in children { - self.set_height(child, height + 1); - } - } - fn create_node(&mut self, id: NodeId) { let (entities, node_data_mut) = self; entities.add_component( @@ -113,7 +118,7 @@ impl<'a> TreeMut for TreeMutView<'a> { parent.children.push(new); height = parent.height + 1; } - self.set_height(new, height); + set_height(self, new, height); } fn replace(&mut self, old_id: NodeId, new_id: NodeId) { @@ -129,7 +134,7 @@ impl<'a> TreeMut for TreeMutView<'a> { } } let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); } } // remove the old node @@ -149,7 +154,7 @@ impl<'a> TreeMut for TreeMutView<'a> { .unwrap(); parent.children.insert(index, new_id); let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); } fn insert_after(&mut self, old_id: NodeId, new_id: NodeId) { @@ -165,7 +170,20 @@ impl<'a> TreeMut for TreeMutView<'a> { .unwrap(); parent.children.insert(index + 1, new_id); let height = parent.height + 1; - self.set_height(new_id, height); + set_height(self, new_id, height); + } +} + +/// Sets the height of a node and updates the height of all its children +fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) { + let children = { + let mut node_data_mut = &mut tree.1; + let mut node = (&mut node_data_mut).get(node).unwrap(); + node.height = height; + node.children.clone() + }; + for child in children { + set_height(tree, child, height + 1); } } diff --git a/packages/native-core/src/utils/cursor.rs b/packages/native-core/src/utils/cursor.rs index 18fbbb991..466b0e70c 100644 --- a/packages/native-core/src/utils/cursor.rs +++ b/packages/native-core/src/utils/cursor.rs @@ -1,3 +1,5 @@ +//! A cursor implementation that can be used to navigate and edit text. + use std::{cmp::Ordering, ops::Range}; use keyboard_types::{Code, Key, Modifiers}; diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 58e98b0fa..a78b5310b 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -1,3 +1,7 @@ +//! # Utilities for renders using the RealDOM +//! +//! This includes an iterator that can be used to iterate over the children of a node that persists changes in the struture of the DOM, and a cursor for text editing. + mod persistant_iterator; pub use persistant_iterator::*; pub mod cursor; diff --git a/packages/native-core/src/utils/persistant_iterator.rs b/packages/native-core/src/utils/persistant_iterator.rs index 155909373..732789aac 100644 --- a/packages/native-core/src/utils/persistant_iterator.rs +++ b/packages/native-core/src/utils/persistant_iterator.rs @@ -3,27 +3,53 @@ use smallvec::SmallVec; use crate::{ node::FromAnyValue, node_watcher::NodeWatcher, + prelude::{NodeMut, NodeRef}, real_dom::{NodeImmutable, RealDom}, - NodeId, NodeRef, + NodeId, }; use std::{ fmt::Debug, sync::{Arc, Mutex}, }; -#[derive(Debug)] -pub enum ElementProduced { - /// The iterator produced an element by progressing to the next node in a depth first order. - Progressed(NodeId), +/// The element produced by the iterator +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ElementProduced { + id: NodeId, + movement: IteratorMovement, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// The method by which the iterator produced an element +pub enum IteratorMovement { + /// The iterator produced an element by progressing to the next node + Progressed, /// The iterator reached the end of the tree and looped back to the root - Looped(NodeId), + Looped, } impl ElementProduced { + /// Get the id of the element produced pub fn id(&self) -> NodeId { - match self { - ElementProduced::Progressed(id) => *id, - ElementProduced::Looped(id) => *id, + self.id + } + + /// The movement the iterator made to produce the element + pub fn movement(&self) -> &IteratorMovement { + &self.movement + } + + fn looped(id: NodeId) -> Self { + Self { + id, + movement: IteratorMovement::Looped, + } + } + + fn progressed(id: NodeId) -> Self { + Self { + id, + movement: IteratorMovement::Progressed, } } } @@ -34,7 +60,7 @@ struct PersistantElementIterUpdater { } impl NodeWatcher for PersistantElementIterUpdater { - fn on_node_moved(&self, node: crate::NodeMut) { + fn on_node_moved(&self, node: NodeMut) { // if any element is moved, update its parents in the stack let mut stack = self.stack.lock().unwrap(); let moved = node.id(); @@ -52,7 +78,7 @@ impl NodeWatcher for PersistantElementIterUpda } } - fn on_node_removed(&self, node: crate::NodeMut) { + fn on_node_removed(&self, node: NodeMut) { // if any element is removed in the chain, remove it and its children from the stack let mut stack = self.stack.lock().unwrap(); let removed = node.id(); @@ -62,9 +88,9 @@ impl NodeWatcher for PersistantElementIterUpda } } -/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom]. +/// Focus systems need a iterator that can persist through changes in the [crate::prelude::RealDom] /// 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`]). +/// You can iterate through it with [PersistantElementIter::next] and [PersistantElementIter::prev]. /// The iterator loops around when it reaches the end or the beginning. pub struct PersistantElementIter { // stack of elements and fragments, the last element is the last element that was yielded @@ -72,6 +98,7 @@ pub struct PersistantElementIter { } impl PersistantElementIter { + /// Create a new iterator in the RealDom pub fn create(rdom: &mut RealDom) -> Self { let inner = Arc::new(Mutex::new(smallvec::smallvec![rdom.root_id()])); @@ -90,7 +117,7 @@ impl PersistantElementIter { let id = rdom.root_id(); let new = id; stack.push(new); - ElementProduced::Looped(id) + ElementProduced::looped(id) } else { let mut look_in_children = true; loop { @@ -100,7 +127,7 @@ impl PersistantElementIter { if let Some(first) = current.children().first() { let new = first.id(); stack.push(new); - return ElementProduced::Progressed(new); + return ElementProduced::progressed(new); } } stack.pop(); @@ -108,7 +135,7 @@ impl PersistantElementIter { // the next element exists, add it to the stack and return it let new = new.id(); stack.push(new); - return ElementProduced::Progressed(new); + return ElementProduced::progressed(new); } // otherwise, continue the loop and go to the parent } else { @@ -116,7 +143,7 @@ impl PersistantElementIter { let new = rdom.root_id(); stack.clear(); stack.push(new); - return ElementProduced::Looped(new); + return ElementProduced::looped(new); } look_in_children = false; } @@ -141,28 +168,28 @@ impl PersistantElementIter { if stack.is_empty() { let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } else if let Some(current) = stack.pop().and_then(|last| rdom.get(last)) { if let Some(new) = current.prev() { // the next element exists, add it to the stack and return it let new = push_back(&mut stack, new); - ElementProduced::Progressed(new) + ElementProduced::progressed(new) } // otherwise, yeild the parent else if let Some(parent) = stack.last() { // if there is a parent, return it - ElementProduced::Progressed(*parent) + ElementProduced::progressed(*parent) } else { // if there is no parent, loop back to the root let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } } else { // if there is no parent, loop back to the root let id = rdom.root_id(); let last = push_back(&mut stack, rdom.get(id).unwrap()); - ElementProduced::Looped(last) + ElementProduced::looped(last) } } } diff --git a/packages/rink/examples/widgets.rs b/packages/rink/examples/widgets.rs new file mode 100644 index 000000000..793057bc5 --- /dev/null +++ b/packages/rink/examples/widgets.rs @@ -0,0 +1,93 @@ +use dioxus::prelude::*; +use dioxus_html::FormData; +use dioxus_tui::prelude::*; +use dioxus_tui::Config; + +fn main() { + dioxus_tui::launch_cfg(app, Config::new()); +} + +fn app(cx: Scope) -> Element { + let bg_green = use_state(cx, || false); + + let color = if *bg_green.get() { "green" } else { "red" }; + cx.render(rsx! { + div{ + width: "100%", + background_color: "{color}", + flex_direction: "column", + align_items: "center", + justify_content: "center", + + Input{ + oninput: |data: FormData| if &data.value == "good"{ + bg_green.set(true); + } else{ + bg_green.set(false); + }, + r#type: "checkbox", + value: "good", + width: "50%", + height: "10%", + checked: "true", + } + Input{ + oninput: |data: FormData| if &data.value == "hello world"{ + bg_green.set(true); + } else{ + bg_green.set(false); + }, + width: "50%", + height: "10%", + maxlength: "11", + } + Input{ + oninput: |data: FormData| { + if (data.value.parse::().unwrap() - 40.0).abs() < 5.0 { + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "range", + width: "50%", + height: "10%", + min: "20", + max: "80", + } + Input{ + oninput: |data: FormData| { + if data.value == "10"{ + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "number", + width: "50%", + height: "10%", + maxlength: "4", + } + Input{ + oninput: |data: FormData| { + if data.value == "hello world"{ + bg_green.set(true); + } else{ + bg_green.set(false); + } + }, + r#type: "password", + width: "50%", + height: "10%", + maxlength: "11", + } + Input{ + onclick: |_: FormData| bg_green.set(true), + r#type: "button", + value: "green", + width: "50%", + height: "10%", + } + } + }) +} diff --git a/packages/rink/src/focus.rs b/packages/rink/src/focus.rs index b42e10b55..e26f79b3d 100644 --- a/packages/rink/src/focus.rs +++ b/packages/rink/src/focus.rs @@ -2,9 +2,9 @@ use crate::prevent_default::PreventDefault; use dioxus_native_core::{ node_ref::{AttributeMaskBuilder, NodeMaskBuilder}, + prelude::*, real_dom::NodeImmutable, - utils::{ElementProduced, PersistantElementIter}, - Dependancy, NodeId, RealDom, SendAnyMap, State, + utils::{IteratorMovement, PersistantElementIter}, }; use dioxus_native_core_macro::partial_derive_state; use once_cell::sync::Lazy; @@ -178,7 +178,7 @@ impl FocusState { self.focus_iter.prev(rdom) }; let new_id = new.id(); - if let ElementProduced::Looped(_) = new { + if let IteratorMovement::Looped = new.movement() { let mut closest_level = None; if forward { diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 73d101bd5..fe29b4120 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -1,8 +1,8 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; +use dioxus_native_core::prelude::*; use dioxus_native_core::real_dom::NodeImmutable; -use dioxus_native_core::{NodeId, NodeRef, RealDom}; use rustc_hash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; diff --git a/packages/rink/src/layout.rs b/packages/rink/src/layout.rs index 286c129f1..b7eff57a6 100644 --- a/packages/rink/src/layout.rs +++ b/packages/rink/src/layout.rs @@ -6,7 +6,7 @@ use dioxus_native_core::layout_attributes::{ }; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; -use dioxus_native_core::{Dependancy, SendAnyMap, State}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use taffy::prelude::*; diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 9e1a4dbf1..b8bcc1dac 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use dioxus_html::EventData; -use dioxus_native_core::{node_ref::NodeMaskBuilder, prelude::AnyState, real_dom::NodeImmutable}; +use dioxus_native_core::prelude::*; use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, SendAnyMap}; use focus::FocusState; use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; diff --git a/packages/rink/src/prevent_default.rs b/packages/rink/src/prevent_default.rs index cadd262f2..4ad5ad87f 100644 --- a/packages/rink/src/prevent_default.rs +++ b/packages/rink/src/prevent_default.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{node_ref::NodeView, Dependancy, SendAnyMap, State}; +use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; diff --git a/packages/rink/src/query.rs b/packages/rink/src/query.rs index f28c4978f..1cf113310 100644 --- a/packages/rink/src/query.rs +++ b/packages/rink/src/query.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; -use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; +use dioxus_native_core::prelude::*; use taffy::{ geometry::Point, prelude::{Layout, Size}, diff --git a/packages/rink/src/render.rs b/packages/rink/src/render.rs index 28471a17c..ef433976d 100644 --- a/packages/rink/src/render.rs +++ b/packages/rink/src/render.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{real_dom::NodeImmutable, NodeRef}; +use dioxus_native_core::prelude::*; use std::io::Stdout; use taffy::{ geometry::Point, @@ -26,8 +26,6 @@ pub(crate) fn render_vnode( cfg: Config, parent_location: Point, ) { - use dioxus_native_core::node::NodeType; - if let NodeType::Placeholder = &*node.node_type() { return; } diff --git a/packages/rink/src/style_attributes.rs b/packages/rink/src/style_attributes.rs index 77fda7d91..0c87e3f83 100644 --- a/packages/rink/src/style_attributes.rs +++ b/packages/rink/src/style_attributes.rs @@ -33,7 +33,7 @@ use dioxus_native_core::{ layout_attributes::parse_value, node::OwnedAttributeView, node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}, - Dependancy, SendAnyMap, State, + prelude::*, }; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component;