implement macro and update tui

This commit is contained in:
Evan Almloff 2023-01-01 08:52:59 -06:00
parent 9e971fbe11
commit bc2925240e
11 changed files with 233 additions and 800 deletions

View file

@ -3,16 +3,9 @@ extern crate proc_macro;
mod sorted_slice;
use proc_macro::TokenStream;
use quote::{quote, ToTokens, __private::Span};
use quote::quote;
use sorted_slice::StrSlice;
use syn::parenthesized;
use syn::parse::ParseBuffer;
use syn::punctuated::Punctuated;
use syn::{
self,
parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
};
use syn::{self, parse_macro_input};
/// Sorts a slice of string literals at compile time.
#[proc_macro]
@ -22,112 +15,8 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
quote!([#(#strings, )*]).into()
}
#[derive(PartialEq, Debug, Clone)]
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)
)]
/// Derive's the state from any members that implement the Pass trait
#[proc_macro_derive(State, attributes(skip, skip_clone))]
pub fn state_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_derive_macro(&ast)
@ -145,376 +34,119 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
.collect(),
_ => unimplemented!(),
};
let strct = Struct::new(type_name.clone(), &fields);
match StateStruct::parse(&fields, &strct) {
Ok(state_strct) => {
let passes = state_strct.state_members.iter().map(|m| {
let unit = &m.mem.unit_type;
match m.dep_kind {
DependencyKind::Node => quote! {dioxus_native_core::AnyPass::Node(&#unit)},
DependencyKind::Child => quote! {dioxus_native_core::AnyPass::Upward(&#unit)},
DependencyKind::Parent => {
quote! {dioxus_native_core::AnyPass::Downward(&#unit)}
}
}
});
let member_types = state_strct.state_members.iter().map(|m| &m.mem.ty);
let impl_members = state_strct
.state_members
.iter()
.map(|m| m.impl_pass(state_strct.ty));
let gen = quote! {
#(#impl_members)*
impl State for #type_name {
const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self>>] = &[
#(#passes),*
];
const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*];
}
};
gen.into()
}
Err(e) => e.into_compile_error().into(),
}
}
struct Struct {
name: Ident,
members: Vec<Member>,
}
impl Struct {
fn new(name: Ident, fields: &[&Field]) -> Self {
let members = fields
let clone_or_default = fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let skip_clone = field
.attrs
.iter()
.enumerate()
.filter_map(|(i, f)| Member::parse(&name, f, i as u64))
.collect();
Self { name, members }
}
}
struct StateStruct<'a> {
state_members: Vec<StateMember<'a>>,
#[allow(unused)]
child_states: Vec<&'a Member>,
ty: &'a Ident,
}
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 state_members: Vec<_> = strct
.members
.iter()
.zip(fields.iter())
.filter_map(|(m, f)| match StateMember::parse(f, m, strct) {
Ok(m) => m,
Err(err) => {
parse_err = Err(err);
None
}
})
.collect();
parse_err?;
for i in 0..state_members.len() {
let deps: Vec<_> = state_members[i].dep_mems.iter().map(|m| m.id).collect();
for dep in deps {
state_members[dep as usize].dependant_mems.push(i as u64);
}
}
let child_states = strct
.members
.iter()
.zip(fields.iter())
.filter(|(_, f)| {
f.attrs.iter().any(|a| {
a.path
.get_ident()
.filter(|i| i.to_string().as_str() == "state")
.is_some()
})
})
.map(|(m, _)| m);
// members need to be sorted so that members are updated after the members they depend on
Ok(Self {
ty: &strct.name,
state_members,
child_states: child_states.collect(),
})
}
}
fn try_parenthesized(input: ParseStream) -> Result<ParseBuffer> {
let inside;
parenthesized!(inside in input);
Ok(inside)
}
struct Dependency {
ctx_ty: Option<Type>,
deps: Vec<Ident>,
}
impl Parse for Dependency {
fn parse(input: ParseStream) -> Result<Self> {
let deps: Option<Punctuated<Ident, Token![,]>> = {
try_parenthesized(input)
.ok()
.and_then(|inside| inside.parse_terminated(Ident::parse).ok())
};
let deps: Vec<_> = deps
.map(|deps| deps.into_iter().collect())
.or_else(|| {
input
.parse::<Ident>()
.ok()
.filter(|i: &Ident| format!("{}", i) != "NONE")
.map(|i| vec![i])
})
.unwrap_or_default();
let comma: Option<Token![,]> = input.parse().ok();
let ctx_ty = input.parse().ok();
Ok(Self {
ctx_ty: comma.and(ctx_ty),
deps,
})
}
}
/// The type of the member and the ident of the member
#[derive(PartialEq, Debug)]
struct Member {
id: u64,
ty: Type,
unit_type: Ident,
ident: Ident,
}
impl Member {
fn parse(parent: &Ident, field: &Field, id: u64) -> Option<Self> {
Some(Self {
id,
ty: field.ty.clone(),
unit_type: Ident::new(
("_Unit".to_string()
+ parent.to_token_stream().to_string().as_str()
+ field.ty.to_token_stream().to_string().as_str())
.as_str(),
Span::call_site(),
),
ident: field.ident.as_ref()?.clone(),
})
}
}
#[derive(Debug, Clone)]
struct StateMember<'a> {
mem: &'a Member,
// the kind of dependncies this state has
dep_kind: DependencyKind,
// the depenancy and if it is satified
dep_mems: Vec<&'a Member>,
// any members that depend on this member
dependant_mems: Vec<u64>,
// the context this state requires
ctx_ty: Option<Type>,
}
impl<'a> StateMember<'a> {
fn parse(
field: &Field,
mem: &'a Member,
parent: &'a Struct,
) -> Result<Option<StateMember<'a>>> {
let mut err = Ok(());
let member = field.attrs.iter().find_map(|a| {
let dep_kind = a
.path
.get_ident()
.and_then(|i| match i.to_string().as_str() {
"node_dep_state" => Some(DependencyKind::Node),
"child_dep_state" => Some(DependencyKind::Child),
"parent_dep_state" => Some(DependencyKind::Parent),
_ => None,
})?;
match a.parse_args::<Dependency>() {
Ok(dependency) => {
let dep_mems = dependency
.deps
.iter()
.filter_map(|name| {
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
Some(found)
} else {
err = Err(Error::new(
name.span(),
format!("{} not found in {}", name, parent.name),
));
None
}
})
.collect();
Some(Self {
mem,
dep_kind,
dep_mems,
dependant_mems: Vec::new(),
ctx_ty: dependency.ctx_ty,
})
}
Err(e) => {
err = Err(e);
None
}
}
});
err?;
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 impl_pass(&self, parent_type: &Ident) -> quote::__private::TokenStream {
let ident = &self.mem.ident;
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
if ctx_ty == &parse_quote!(()) {
quote! {&()}
} else {
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
quote! {ctx.get().expect(#msg)}
.any(|attr| attr.path.is_ident("skip_clone"));
if skip_clone {
quote! {
Default::default()
}
} else {
quote! {&()}
};
let ty = &self.mem.ty;
let unit_type = &self.mem.unit_type;
let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK));
let dep_idents = self.dep_mems.iter().map(|m| &m.ident);
let impl_specific = match self.dep_kind {
DependencyKind::Node => {
quote! {
impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx)
}
}
}
}
DependencyKind::Child => {
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: true,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
} else {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
};
quote!(
impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type{
fn pass<'a>(
&self,
node: &mut dioxus_native_core::node::Node<#parent_type>,
children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type>>,
ctx: &dioxus_native_core::SendAnyMap,
) -> dioxus_native_core::PassReturn {
let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx);
#update
}
}
)
}
DependencyKind::Parent => {
let update = if self.dep_mems.iter().any(|m| m.id == self.mem.id) {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: true,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
} else {
quote! {
if update {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: true,
}
} else {
dioxus_native_core::PassReturn{
progress: false,
mark_dirty: false,
}
}
}
};
quote!(
impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx);
#update
}
}
)
}
};
let pass_id = self.mem.id;
let depenancies = self.dep_mems.iter().map(|m| m.id);
let dependants = &self.dependant_mems;
let mask = self
.dep_mems
.iter()
.map(|m| 1u64 << m.id)
.fold(1 << self.mem.id, |a, b| a | b);
quote! {
#[derive(Clone, Copy)]
struct #unit_type;
#impl_specific
impl dioxus_native_core::Pass for #unit_type {
fn pass_id(&self) -> dioxus_native_core::PassId {
dioxus_native_core::PassId(#pass_id)
}
fn dependancies(&self) -> &'static [dioxus_native_core::PassId] {
&[#(dioxus_native_core::PassId(#depenancies)),*]
}
fn dependants(&self) -> &'static [dioxus_native_core::PassId] {
&[#(dioxus_native_core::PassId(#dependants)),*]
}
fn mask(&self) -> dioxus_native_core::MemberMask {
dioxus_native_core::MemberMask(#mask)
}
quote! {
self.#field_name.clone()
}
}
}
});
let non_clone_types = fields
.iter()
.filter(|field| {
field
.attrs
.iter()
.any(|attr| attr.path.is_ident("skip_clone"))
})
.map(|field| &field.ty);
let names = fields.iter().map(|field| field.ident.as_ref().unwrap());
let types = fields
.iter()
.filter(|field| !field.attrs.iter().any(|attr| attr.path.is_ident("skip")))
.map(|field| &field.ty);
let gen = quote! {
impl dioxus_native_core::State for #type_name {
fn create_passes() -> Box<[dioxus_native_core::TypeErasedPass<Self>]> {
Box::new([
#(
<#types as dioxus_native_core::Pass>::to_type_erased()
),*
])
}
fn clone_or_default(&self) -> Self {
Self {
#(
#names: #clone_or_default
),*
}
}
fn non_clone_members() -> Box<[std::any::TypeId]> {
Box::new([
#(
std::any::TypeId::of::<#non_clone_types>()
),*
])
}
}
};
gen.into()
}
/// Derive's the state from any elements that have a node_dep_state, child_dep_state, parent_dep_state, or state attribute.
#[proc_macro_derive(AnyMapLike)]
pub fn anymap_like_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_anymap_like_derive_macro(&ast)
}
fn impl_anymap_like_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
let type_name = &ast.ident;
let fields: Vec<_> = match &ast.data {
syn::Data::Struct(data) => match &data.fields {
syn::Fields::Named(e) => &e.named,
syn::Fields::Unnamed(_) => todo!("unnamed fields"),
syn::Fields::Unit => todo!("unit structs"),
}
.iter()
.collect(),
_ => unimplemented!(),
};
let names: Vec<_> = fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.collect();
let types: Vec<_> = fields.iter().map(|field| &field.ty).collect();
let gen = quote! {
impl dioxus_native_core::AnyMapLike for #type_name {
fn get<T: std::any::Any>(&self) -> Option<&T> {
#(
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<#types>() {
return unsafe { Some(&*(&self.#names as *const _ as *const T)) }
}
)*
None
}
fn get_mut<T: std::any::Any>(&mut self) -> Option<&mut T> {
#(
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<#types>() {
return unsafe { Some(&mut *(&mut self.#names as *mut _ as *mut T)) }
}
)*
None
}
}
};
gen.into()
}

View file

@ -1,8 +1,10 @@
use std::hash::BuildHasherDefault;
pub use node_ref::NodeMask;
pub use passes::Pass;
pub use passes::AnyMapLike;
pub use passes::{Dependancy, Pass, TypeErasedPass};
use rustc_hash::FxHasher;
pub use state::State;
pub use tree::NodeId;
pub mod layout_attributes;

View file

@ -1,5 +1,4 @@
use anymap::AnyMap;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
use std::any::{Any, TypeId};
use std::collections::BTreeMap;
@ -22,7 +21,7 @@ pub trait Pass: Any {
type NodeDependencies: Dependancy;
/// This is a tuple of (T: Any, ..)
type Ctx: Dependancy;
const MASK: NodeMask;
const NODE_MASK: NodeMask;
fn pass<'a>(
&mut self,
@ -70,7 +69,7 @@ pub trait Pass: Any {
this_type_id: TypeId::of::<Self>(),
combined_dependancy_type_ids: Self::all_dependanices().into_iter().collect(),
dependants: FxHashSet::default(),
mask: Self::MASK,
mask: Self::NODE_MASK,
pass_direction: Self::pass_direction(),
pass: Box::new(
|node_id: NodeId, any_map: &mut Tree<Node<T>>, context: &SendAnyMap| {
@ -100,7 +99,7 @@ pub trait Pass: Any {
let context = Self::Ctx::borrow_elements_from(context)
.expect("tried to get a pass that does not exist");
myself.pass(
NodeView::new(&current_node.node_data, Self::MASK),
NodeView::new(&current_node.node_data, Self::NODE_MASK),
node,
parent,
children,
@ -350,16 +349,6 @@ pub struct DirtyNodeStates {
}
impl DirtyNodeStates {
pub fn new(starting_nodes: FxHashMap<NodeId, FxHashSet<TypeId>>) -> Self {
let this = Self::default();
for (node, nodes) in starting_nodes {
for pass_id in nodes {
this.insert(pass_id, node);
}
}
this
}
pub fn insert(&self, pass_id: TypeId, node_id: NodeId) {
if let Some(mut dirty) = self.dirty.get_mut(&node_id) {
dirty.insert(pass_id);

View file

@ -426,13 +426,12 @@ impl<S: State + Send> RealDom<S> {
node_data: node.node_data.clone(),
};
let new_id = self.create_node(new_node, false);
if let Some(passes) = S::non_clone_nodes() {
for pass_id in &*passes {
self.passes_updated
.entry(node_id)
.or_default()
.insert(*pass_id);
}
let passes = S::non_clone_members();
for pass_id in &*passes {
self.passes_updated
.entry(node_id)
.or_default()
.insert(*pass_id);
}
let self_ptr = self as *mut Self;

View file

@ -1,4 +1,3 @@
use crate::node_ref::{NodeMask, NodeView};
use crate::passes::{AnyMapLike, TypeErasedPass};
use std::any::TypeId;
use std::cmp::Ordering;
@ -38,174 +37,6 @@ pub(crate) fn union_ordered_iter<'a>(
v
}
/// 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
/// # use dioxus_native_core::node_ref::NodeView;
/// # use dioxus_native_core::state::ChildDepState;
/// #[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,)>,
/// _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.map(|(&c,)|c).reduce(|c1, c2| Layout{
/// width: c1.width + c2.width,
/// height: c1.height.max(c2.height)
/// }).unwrap_or_default();
/// let changed = new != *self;
/// *self = new;
/// changed
/// }
/// }
/// ```
pub trait ChildDepState {
/// The context is passed to the [ChildDepState::reduce] when it is resolved.
type Ctx;
/// 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.
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// 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,
children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a;
}
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
/// Called when the current node's node properties are modified or a parrent's [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,)>,
/// 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().unwrap() {
/// match attr.attribute.name.as_str() {
/// "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 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.
/// Depstate must be a tuple containing any number of borrowed elements that are either [ParentDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// 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,
parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
ctx: &Self::Ctx,
) -> bool;
}
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
/// Called when the current node's node properties are modified or a 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
/// 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 = ();
/// type DepState = ();
/// const NODE_MASK: NodeMask =
/// NodeMask::new_with_attrs(AttributeMask::Static(&[
/// "tabindex"
/// ]));
/// fn reduce(
/// &mut self,
/// node: NodeView,
/// siblings: (),
/// ctx: &(),
/// ) -> bool {
/// let old = self.clone();
/// for attr in node.attributes().unwrap() {
/// match attr.attribute.name.as_str() {
/// "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!(),
/// }
/// }
/// old != *self
/// }
/// }
/// ```
pub trait NodeDepState {
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
type DepState: ElementBorrowable;
/// 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<'a>(
&mut self,
node: NodeView,
node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
ctx: &Self::Ctx,
) -> bool;
}
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
pub trait State: Default + Clone + AnyMapLike + 'static {
#[doc(hidden)]
@ -217,74 +48,7 @@ pub trait State: Default + Clone + AnyMapLike + 'static {
}
#[doc(hidden)]
fn non_clone_nodes() -> Option<Box<[TypeId]>> {
None
fn non_clone_members() -> Box<[TypeId]> {
Box::new([])
}
}
impl ChildDepState for () {
type Ctx = ();
type DepState = ();
fn reduce<'a>(&mut self, _: NodeView, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
where
Self::DepState: 'a,
{
false
}
}
impl ParentDepState for () {
type Ctx = ();
type DepState = ();
fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
false
}
}
impl NodeDepState for () {
type DepState = ();
type Ctx = ();
fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
false
}
}
pub trait ElementBorrowable {
type ElementBorrowed<'a>
where
Self: 'a;
fn borrow_elements(&self) -> Self::ElementBorrowed<'_>;
}
macro_rules! impl_element_borrowable {
($($t:ident),*) => {
impl< $($t),* > ElementBorrowable for ($($t,)*) {
type ElementBorrowed<'a> = ($(&'a $t,)*) where Self: 'a;
#[allow(clippy::unused_unit, non_snake_case)]
fn borrow_elements<'a>(&'a self) -> Self::ElementBorrowed<'a> {
let ($($t,)*) = self;
($(&$t,)*)
}
}
};
}
impl_element_borrowable!();
impl_element_borrowable!(A);
impl_element_borrowable!(A, B);
impl_element_borrowable!(A, B, C);
impl_element_borrowable!(A, B, C, D);
impl_element_borrowable!(A, B, C, D, E);
impl_element_borrowable!(A, B, C, D, E, F);
impl_element_borrowable!(A, B, C, D, E, F, G);
impl_element_borrowable!(A, B, C, D, E, F, G, H);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_element_borrowable!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);

View file

@ -3,16 +3,13 @@ use crate::{node::PreventDefault, TuiDom};
use dioxus_native_core::{
tree::TreeView,
utils::{ElementProduced, PersistantElementIter},
NodeId,
NodeId, Pass,
};
use dioxus_native_core_macro::sorted_str_slice;
use std::{cmp::Ordering, num::NonZeroU16};
use dioxus_native_core::{
node_ref::{AttributeMask, NodeMask, NodeView},
state::NodeDepState,
};
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum FocusLevel {
@ -64,15 +61,33 @@ pub(crate) struct Focus {
pub level: FocusLevel,
}
impl NodeDepState for Focus {
type DepState = ();
impl Pass for Focus {
type Ctx = ();
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
type ParentDependencies = ();
type ChildDependencies = ();
type NodeDependencies = ();
fn pass<'a>(
&mut self,
node_view: NodeView,
node: <Self::NodeDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
parent: Option<
<Self::ParentDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
>,
children: Option<
impl Iterator<
Item = <Self::ChildDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<
'a,
>,
>,
>,
context: <Self::Ctx as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
) -> bool {
let new = Focus {
level: if let Some(a) = node
level: if let Some(a) = node_view
.attributes()
.and_then(|mut a| a.find(|a| a.attribute.name == "tabindex"))
{
@ -91,7 +106,7 @@ impl NodeDepState for Focus {
} else {
FocusLevel::Unfocusable
}
} else if node
} else if node_view
.listeners()
.and_then(|mut listeners| {
listeners

View file

@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node::OwnedAttributeView;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core::Pass;
use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*;
@ -40,29 +40,35 @@ pub(crate) struct TaffyLayout {
pub node: PossiblyUninitalized<Node>,
}
impl ChildDepState for TaffyLayout {
type Ctx = Arc<Mutex<Taffy>>;
type DepState = (Self,);
// use tag to force this to be called when a node is built
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS))
.with_text()
.with_tag();
impl Pass for TaffyLayout {
type Ctx = (Arc<Mutex<Taffy>>,);
type ChildDependencies = (Self,);
type ParentDependencies = ();
type NodeDependencies = ();
/// Setup the layout
fn reduce<'a>(
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)).with_text();
fn pass<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = (&'a Self,)>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
node_view: NodeView,
node: <Self::NodeDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
parent: Option<
<Self::ParentDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
>,
children: Option<
impl Iterator<
Item = <Self::ChildDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<
'a,
>,
>,
>,
(taffy,): <Self::Ctx as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
) -> bool {
let mut changed = false;
let mut taffy = ctx.lock().expect("poisoned taffy");
let mut taffy = taffy.lock().expect("poisoned taffy");
let mut style = Style::default();
if let Some(text) = node.text() {
if let Some(text) = node_view.text() {
let char_len = text.chars().count();
style = Style {
@ -85,7 +91,7 @@ impl ChildDepState for TaffyLayout {
}
} else {
// gather up all the styles from the attribute list
if let Some(attributes) = node.attributes() {
if let Some(attributes) = node_view.attributes() {
for OwnedAttributeView {
attribute, value, ..
} in attributes
@ -101,8 +107,10 @@ impl ChildDepState for TaffyLayout {
// Set all direct nodes as our children
let mut child_layout = vec![];
for (l,) in children {
child_layout.push(l.node.unwrap());
if let Some(children) = children {
for (l,) in children {
child_layout.push(l.node.unwrap());
}
}
fn scale_dimention(d: Dimension) -> Dimension {

View file

@ -113,10 +113,10 @@ pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props
{
let mut rdom = rdom.borrow_mut();
let mutations = dom.rebuild();
let (to_update, _) = rdom.apply_mutations(mutations);
rdom.apply_mutations(mutations);
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(to_update, any_map);
let _ = rdom.update_state(any_map);
}
render_vdom(
@ -271,11 +271,12 @@ fn render_vdom(
let mutations = vdom.render_immediate();
handler.prune(&mutations, &rdom);
// updates the dom's nodes
let (to_update, dirty) = rdom.apply_mutations(mutations);
rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(to_update, any_map);
let (new_to_rerender, dirty) = rdom.update_state(any_map);
to_rerender = new_to_rerender;
for (id, mask) in dirty {
if mask.overlaps(&NodeMask::new().with_text()) {
to_rerender.insert(id);

View file

@ -1,22 +1,20 @@
use crate::focus::Focus;
use crate::layout::TaffyLayout;
use crate::style_attributes::StyleModifier;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::{sorted_str_slice, State};
use dioxus_native_core::{real_dom::RealDom, Dependancy, Pass};
use dioxus_native_core_macro::{sorted_str_slice, AnyMapLike, State};
pub(crate) type TuiDom = RealDom<NodeState>;
pub(crate) type TuiNode = dioxus_native_core::node::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
#[derive(Debug, Clone, State, AnyMapLike, Default)]
pub(crate) struct NodeState {
#[child_dep_state(layout, Mutex<Stretch>)]
#[skip_clone]
pub layout: TaffyLayout,
#[parent_dep_state(style)]
pub style: StyleModifier,
#[node_dep_state()]
pub prevent_default: PreventDefault,
#[node_dep_state()]
pub focus: Focus,
#[skip]
pub focused: bool,
}
@ -45,8 +43,11 @@ impl Default for PreventDefault {
}
}
impl NodeDepState for PreventDefault {
type DepState = ();
impl Pass for PreventDefault {
type ParentDependencies = ();
type ChildDependencies = ();
type NodeDependencies = ();
type Ctx = ();
const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
@ -57,13 +58,17 @@ impl NodeDepState for PreventDefault {
)
.with_listeners();
fn reduce(
fn pass<'a>(
&mut self,
node: dioxus_native_core::node_ref::NodeView,
_sibling: (),
_ctx: &Self::Ctx,
node_view: dioxus_native_core::node_ref::NodeView,
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Option<
impl Iterator<Item = <Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
>,
context: <Self::Ctx as Dependancy>::ElementBorrowed<'a>,
) -> bool {
let new = match node.attributes().and_then(|mut attrs| {
let new = match node_view.attributes().and_then(|mut attrs| {
attrs
.find(|a| a.attribute.name == "dioxus-prevent-default")
.and_then(|a| a.value.as_text())

View file

@ -45,7 +45,7 @@ pub(crate) fn render_vnode(
let height = layout_to_screen_space(fy + height).round() as u16 - y;
match &node.node_data.node_type {
NodeType::Text { text } => {
NodeType::Text(text) => {
#[derive(Default)]
struct Label<'a> {
text: &'a str,

View file

@ -33,7 +33,7 @@ use dioxus_native_core::{
layout_attributes::parse_value,
node::OwnedAttributeView,
node_ref::{AttributeMask, NodeMask, NodeView},
state::ParentDepState,
Pass,
};
use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*;
@ -46,22 +46,40 @@ pub struct StyleModifier {
pub modifier: TuiModifier,
}
impl ParentDepState for StyleModifier {
impl Pass for StyleModifier {
type Ctx = ();
type DepState = (Self,);
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
// todo: seperate each attribute into it's own class
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element();
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _: &Self::Ctx) -> bool {
fn pass<'a>(
&mut self,
node_view: NodeView,
node: <Self::NodeDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
parent: Option<
<Self::ParentDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
>,
children: Option<
impl Iterator<
Item = <Self::ChildDependencies as dioxus_native_core::Dependancy>::ElementBorrowed<
'a,
>,
>,
>,
context: <Self::Ctx as dioxus_native_core::Dependancy>::ElementBorrowed<'a>,
) -> bool {
let mut new = StyleModifier::default();
if parent.is_some() {
new.core.fg = None;
}
// handle text modifier elements
if node.namespace().is_none() {
if let Some(tag) = node.tag() {
if node_view.namespace().is_none() {
if let Some(tag) = node_view.tag() {
match tag {
"b" => apply_style_attributes("font-weight", "bold", &mut new),
"strong" => apply_style_attributes("font-weight", "bold", &mut new),
@ -79,7 +97,7 @@ impl ParentDepState for StyleModifier {
}
// gather up all the styles from the attribute list
if let Some(attrs) = node.attributes() {
if let Some(attrs) = node_view.attributes() {
for OwnedAttributeView {
attribute, value, ..
} in attrs