new dioxus_core api
This commit is contained in:
18 files changed
+1603
-610
No files matched your search
+2
-1
@@ -29,6 +29,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
|
|||||||
dioxus-liveview = { path = "./packages/liveview", optional = true }
|
dioxus-liveview = { path = "./packages/liveview", optional = true }
|
||||||
|
|
||||||
dioxus-native-core = { path = "./packages/native-core", optional = true }
|
dioxus-native-core = { path = "./packages/native-core", optional = true }
|
||||||
|
dioxus-native-core-macro = { path = "./packages/native-core-macro", optional = true }
|
||||||
|
|
||||||
# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
|
# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
|
||||||
# dioxus-rsx = { path = "./packages/rsx", optional = true }
|
# dioxus-rsx = { path = "./packages/rsx", optional = true }
|
||||||
@@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"]
|
|||||||
router = ["dioxus-router"]
|
router = ["dioxus-router"]
|
||||||
tui = ["dioxus-tui"]
|
tui = ["dioxus-tui"]
|
||||||
liveview = ["dioxus-liveview"]
|
liveview = ["dioxus-liveview"]
|
||||||
native-core = ["dioxus-native-core"]
|
native-core = ["dioxus-native-core", "dioxus-native-core-macro"]
|
||||||
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "dioxus-native-core-macro"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "1.0.11", features = ["extra-traits"] }
|
||||||
|
quote = "1.0"
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
self,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
punctuated::Punctuated,
|
||||||
|
token::Paren,
|
||||||
|
Field, Ident, Token, Type, TypeTuple,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum DepKind {
|
||||||
|
NodeDepState,
|
||||||
|
ChildDepState,
|
||||||
|
ParentDepState,
|
||||||
|
}
|
||||||
|
|
||||||
|
// macro that streams data from the State for any attributes that end with _
|
||||||
|
#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))]
|
||||||
|
pub fn state_macro_derive(input: TokenStream) -> TokenStream {
|
||||||
|
let ast = syn::parse(input).unwrap();
|
||||||
|
impl_derive_macro(&ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
let type_name = &ast.ident;
|
||||||
|
let fields: Vec<_> = match &ast.data {
|
||||||
|
syn::Data::Struct(data) => match &data.fields {
|
||||||
|
syn::Fields::Named(e) => &e.named,
|
||||||
|
syn::Fields::Unnamed(_) => todo!("unnamed fields"),
|
||||||
|
syn::Fields::Unit => todo!("unit fields"),
|
||||||
|
}
|
||||||
|
.iter()
|
||||||
|
.collect(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
let strct = Struct::parse(&fields);
|
||||||
|
let state_strct = StateStruct::parse(&fields, &strct);
|
||||||
|
|
||||||
|
let node_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||||
|
state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::NodeDepState)
|
||||||
|
.map(|f| {
|
||||||
|
let ty_id = &f.type_id();
|
||||||
|
let reduce = &f.reduce_self();
|
||||||
|
quote! {
|
||||||
|
else if ty == #ty_id {
|
||||||
|
#reduce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let child_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||||
|
state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::ChildDepState)
|
||||||
|
.map(|f| {
|
||||||
|
let ty_id = &f.type_id();
|
||||||
|
let reduce = &f.reduce_self();
|
||||||
|
quote! {
|
||||||
|
else if ty == #ty_id {
|
||||||
|
#reduce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let parent_dep_state_fields = quote::__private::TokenStream::from_iter(
|
||||||
|
state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::ParentDepState)
|
||||||
|
.map(|f| {
|
||||||
|
let ty_id = &f.type_id();
|
||||||
|
let reduce = &f.reduce_self();
|
||||||
|
quote! {
|
||||||
|
else if ty == #ty_id {
|
||||||
|
#reduce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let node_types = state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::NodeDepState)
|
||||||
|
.map(|f| &f.mem.ty);
|
||||||
|
let child_types = state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::ChildDepState)
|
||||||
|
.map(|f| &f.mem.ty);
|
||||||
|
let parent_types = state_strct
|
||||||
|
.state_members
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.dep_kind == DepKind::ParentDepState)
|
||||||
|
.map(|f| &f.mem.ty);
|
||||||
|
|
||||||
|
let type_name_str = type_name.to_string();
|
||||||
|
|
||||||
|
let gen = quote! {
|
||||||
|
impl State for #type_name{
|
||||||
|
fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, ctx: &anymap::AnyMap) -> bool{
|
||||||
|
use dioxus_native_core::state::NodeDepState as _;
|
||||||
|
// println!("called update_node_dep_state with ty: {:?}", ty);
|
||||||
|
if false {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
#node_dep_state_fields
|
||||||
|
else{
|
||||||
|
panic!("{:?} not in {}", ty, #type_name_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{
|
||||||
|
use dioxus_native_core::state::ParentDepState as _;
|
||||||
|
// println!("called update_parent_dep_state with ty: {:?}", ty);
|
||||||
|
if false {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
#parent_dep_state_fields
|
||||||
|
else{
|
||||||
|
panic!("{:?} not in {}", ty, #type_name_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{
|
||||||
|
use dioxus_native_core::state::ChildDepState as _;
|
||||||
|
// println!("called update_child_dep_state with ty: {:?}", ty);
|
||||||
|
if false {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
#child_dep_state_fields
|
||||||
|
else{
|
||||||
|
panic!("{:?} not in {}", ty, #type_name_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||||
|
let mut dep_types = Vec::new();
|
||||||
|
#(if #child_types::NODE_MASK.overlaps(mask) {
|
||||||
|
dep_types.push(std::any::TypeId::of::<#child_types>());
|
||||||
|
})*
|
||||||
|
dep_types
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parent_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||||
|
let mut dep_types = Vec::new();
|
||||||
|
#(if #parent_types::NODE_MASK.overlaps(mask) {
|
||||||
|
dep_types.push(std::any::TypeId::of::<#parent_types>());
|
||||||
|
})*
|
||||||
|
dep_types
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_dep_types(&self, mask: &dioxus_native_core::state::NodeMask) -> Vec<std::any::TypeId>{
|
||||||
|
let mut dep_types = Vec::new();
|
||||||
|
#(if #node_types::NODE_MASK.overlaps(mask) {
|
||||||
|
dep_types.push(std::any::TypeId::of::<#node_types>());
|
||||||
|
})*
|
||||||
|
dep_types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Struct {
|
||||||
|
members: Vec<Member>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Struct {
|
||||||
|
fn parse(fields: &[&Field]) -> Self {
|
||||||
|
let members = fields.iter().filter_map(|f| Member::parse(f)).collect();
|
||||||
|
Self { members }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateStruct<'a> {
|
||||||
|
state_members: Vec<StateMember<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StateStruct<'a> {
|
||||||
|
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self {
|
||||||
|
let state_members = strct
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.zip(fields.iter())
|
||||||
|
.filter_map(|(m, f)| StateMember::parse(f, m, &strct))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// todo: sort members
|
||||||
|
|
||||||
|
Self { state_members }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DepTypes {
|
||||||
|
ctx_ty: Option<Type>,
|
||||||
|
dep_ty: Option<Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for DepTypes {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||||
|
let dep_ty = input.parse().ok();
|
||||||
|
let comma: Option<Token![,]> = input.parse().ok();
|
||||||
|
let ctx_ty = input.parse().ok();
|
||||||
|
Ok(Self {
|
||||||
|
ctx_ty: comma.and(ctx_ty),
|
||||||
|
dep_ty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NodeDepTypes {
|
||||||
|
ctx_ty: Option<Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for NodeDepTypes {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||||
|
let ctx_ty = input.parse().ok();
|
||||||
|
Ok(Self { ctx_ty })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NodeDepTypes> for DepTypes {
|
||||||
|
fn from(node_dep_types: NodeDepTypes) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx_ty: node_dep_types.ctx_ty,
|
||||||
|
dep_ty: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Member {
|
||||||
|
ty: Type,
|
||||||
|
ident: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Member {
|
||||||
|
fn parse(field: &Field) -> Option<Self> {
|
||||||
|
Some(Self {
|
||||||
|
ty: field.ty.clone(),
|
||||||
|
ident: field.ident.as_ref()?.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateMember<'a> {
|
||||||
|
mem: &'a Member,
|
||||||
|
dep_kind: DepKind,
|
||||||
|
dep_mem: Option<&'a Member>,
|
||||||
|
ctx_ty: Option<Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StateMember<'a> {
|
||||||
|
fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option<StateMember<'a>> {
|
||||||
|
field.attrs.iter().find_map(|a| {
|
||||||
|
let dep_kind = a
|
||||||
|
.path
|
||||||
|
.get_ident()
|
||||||
|
.map(|i| match i.to_string().as_str() {
|
||||||
|
"node_dep_state" => Some(DepKind::NodeDepState),
|
||||||
|
"child_dep_state" => Some(DepKind::ChildDepState),
|
||||||
|
"parent_dep_state" => Some(DepKind::ParentDepState),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()?;
|
||||||
|
let deps: DepTypes = match dep_kind {
|
||||||
|
DepKind::NodeDepState => a.parse_args::<NodeDepTypes>().ok()?.into(),
|
||||||
|
_ => a.parse_args().ok()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
mem,
|
||||||
|
dep_kind,
|
||||||
|
dep_mem: deps
|
||||||
|
.dep_ty
|
||||||
|
.map(|ty| parent.members.iter().find(|m| m.ty == ty))
|
||||||
|
.flatten(),
|
||||||
|
ctx_ty: deps.ctx_ty,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce_self(&self) -> quote::__private::TokenStream {
|
||||||
|
let ident = &self.mem.ident;
|
||||||
|
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||||
|
if ctx_ty
|
||||||
|
== &Type::Tuple(TypeTuple {
|
||||||
|
paren_token: Paren {
|
||||||
|
span: quote::__private::Span::call_site(),
|
||||||
|
},
|
||||||
|
elems: Punctuated::new(),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
quote! {&()}
|
||||||
|
} else {
|
||||||
|
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||||
|
quote! {ctx.get().expect(#msg)}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {&()}
|
||||||
|
};
|
||||||
|
let ty = &self.mem.ty;
|
||||||
|
let node_view = quote!(NodeView::new(node, #ty::NODE_MASK));
|
||||||
|
if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) {
|
||||||
|
match self.dep_kind {
|
||||||
|
DepKind::NodeDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, #get_ctx))
|
||||||
|
}
|
||||||
|
DepKind::ChildDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx))
|
||||||
|
}
|
||||||
|
DepKind::ParentDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.dep_kind {
|
||||||
|
DepKind::NodeDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, #get_ctx))
|
||||||
|
}
|
||||||
|
DepKind::ChildDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, &(), #get_ctx))
|
||||||
|
}
|
||||||
|
DepKind::ParentDepState => {
|
||||||
|
quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_id(&self) -> quote::__private::TokenStream {
|
||||||
|
let ty = &self.mem.ty;
|
||||||
|
quote!({
|
||||||
|
let type_id = std::any::TypeId::of::<#ty>();
|
||||||
|
type_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,12 @@ homepage = "https://dioxuslabs.com"
|
|||||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
||||||
|
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
|
||||||
|
|
||||||
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
fxhash = "0.2"
|
fxhash = "0.2"
|
||||||
|
anymap = "0.12.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
pub mod layout_attributes;
|
pub mod layout_attributes;
|
||||||
pub mod real_dom;
|
pub mod real_dom;
|
||||||
|
pub mod state;
|
||||||
|
pub use dioxus_native_core_macro;
|
||||||
@@ -1,30 +1,34 @@
|
|||||||
|
use anymap::AnyMap;
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use std::{
|
use std::{
|
||||||
|
any::TypeId,
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
ops::{Index, IndexMut},
|
ops::{Index, IndexMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
||||||
|
|
||||||
|
use crate::state::{union_ordered_iter, AttributeMask, NodeMask, State};
|
||||||
|
|
||||||
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
||||||
/// The render state passes from parent to children and or accumulates state from children to parents.
|
/// The render state passes from parent to children and or accumulates state from children to parents.
|
||||||
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
|
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> {
|
pub struct RealDom<S: State> {
|
||||||
root: usize,
|
root: usize,
|
||||||
nodes: Vec<Option<Node<US, DS>>>,
|
nodes: Vec<Option<Node<S>>>,
|
||||||
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
|
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
|
||||||
node_stack: smallvec::SmallVec<[usize; 10]>,
|
node_stack: smallvec::SmallVec<[usize; 10]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> Default for RealDom<US, DS> {
|
impl<S: State> Default for RealDom<S> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
impl<S: State> RealDom<S> {
|
||||||
pub fn new() -> RealDom<US, DS> {
|
pub fn new() -> RealDom<S> {
|
||||||
RealDom {
|
RealDom {
|
||||||
root: 0,
|
root: 0,
|
||||||
nodes: {
|
nodes: {
|
||||||
@@ -44,7 +48,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
|
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
|
||||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
|
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
|
||||||
let mut nodes_updated = Vec::new();
|
let mut nodes_updated = Vec::new();
|
||||||
for mutations in mutations_vec {
|
for mutations in mutations_vec {
|
||||||
for e in mutations.edits {
|
for e in mutations.edits {
|
||||||
@@ -66,7 +70,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
.collect();
|
.collect();
|
||||||
for ns in drained {
|
for ns in drained {
|
||||||
self.link_child(ns, target).unwrap();
|
self.link_child(ns, target).unwrap();
|
||||||
nodes_updated.push(ns);
|
nodes_updated.push((ns, NodeMask::ALL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReplaceWith { root, m } => {
|
ReplaceWith { root, m } => {
|
||||||
@@ -74,7 +78,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
let target = root.parent.unwrap().0;
|
let target = root.parent.unwrap().0;
|
||||||
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
|
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
|
||||||
for ns in drained {
|
for ns in drained {
|
||||||
nodes_updated.push(ns);
|
nodes_updated.push((ns, NodeMask::ALL));
|
||||||
self.link_child(ns, target).unwrap();
|
self.link_child(ns, target).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
let target = self[root as usize].parent.unwrap().0;
|
let target = self[root as usize].parent.unwrap().0;
|
||||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||||
for ns in drained {
|
for ns in drained {
|
||||||
nodes_updated.push(ns);
|
nodes_updated.push((ns, NodeMask::ALL));
|
||||||
self.link_child(ns, target).unwrap();
|
self.link_child(ns, target).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,13 +94,13 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
let target = self[root as usize].parent.unwrap().0;
|
let target = self[root as usize].parent.unwrap().0;
|
||||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||||
for ns in drained {
|
for ns in drained {
|
||||||
nodes_updated.push(ns);
|
nodes_updated.push((ns, NodeMask::ALL));
|
||||||
self.link_child(ns, target).unwrap();
|
self.link_child(ns, target).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Remove { root } => {
|
Remove { root } => {
|
||||||
if let Some(parent) = self[root as usize].parent {
|
if let Some(parent) = self[root as usize].parent {
|
||||||
nodes_updated.push(parent.0);
|
nodes_updated.push((parent.0, NodeMask::NONE));
|
||||||
}
|
}
|
||||||
self.remove(root as usize).unwrap();
|
self.remove(root as usize).unwrap();
|
||||||
}
|
}
|
||||||
@@ -162,7 +166,10 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
text: new_text,
|
text: new_text,
|
||||||
} => {
|
} => {
|
||||||
let target = &mut self[root as usize];
|
let target = &mut self[root as usize];
|
||||||
nodes_updated.push(root as usize);
|
nodes_updated.push((
|
||||||
|
root as usize,
|
||||||
|
NodeMask::new(AttributeMask::NONE, false, false, true),
|
||||||
|
));
|
||||||
match &mut target.node_type {
|
match &mut target.node_type {
|
||||||
NodeType::Text { text } => {
|
NodeType::Text { text } => {
|
||||||
*text = new_text.to_string();
|
*text = new_text.to_string();
|
||||||
@@ -170,11 +177,19 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetAttribute { root, .. } => {
|
SetAttribute { root, field, .. } => {
|
||||||
nodes_updated.push(root as usize);
|
nodes_updated.push((
|
||||||
|
root as usize,
|
||||||
|
NodeMask::new(AttributeMask::single(field), false, false, false),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
RemoveAttribute { root, .. } => {
|
RemoveAttribute {
|
||||||
nodes_updated.push(root as usize);
|
root, name: field, ..
|
||||||
|
} => {
|
||||||
|
nodes_updated.push((
|
||||||
|
root as usize,
|
||||||
|
NodeMask::new(AttributeMask::single(field), false, false, false),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
PopRoot {} => {
|
PopRoot {} => {
|
||||||
self.node_stack.pop();
|
self.node_stack.pop();
|
||||||
@@ -190,89 +205,226 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
pub fn update_state(
|
pub fn update_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
vdom: &VirtualDom,
|
vdom: &VirtualDom,
|
||||||
nodes_updated: Vec<usize>,
|
nodes_updated: Vec<(usize, NodeMask)>,
|
||||||
us_ctx: &mut US::Ctx,
|
ctx: AnyMap,
|
||||||
ds_ctx: &mut DS::Ctx,
|
|
||||||
) -> Option<FxHashSet<usize>> {
|
) -> Option<FxHashSet<usize>> {
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
enum StatesToCheck {
|
||||||
|
All,
|
||||||
|
Some(Vec<TypeId>),
|
||||||
|
}
|
||||||
|
impl StatesToCheck {
|
||||||
|
fn union(&self, other: &Self) -> Self {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter(
|
||||||
|
s.iter().copied(),
|
||||||
|
o.iter().copied(),
|
||||||
|
s.len() + o.len(),
|
||||||
|
)),
|
||||||
|
_ => Self::All,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct NodeRef {
|
||||||
|
id: usize,
|
||||||
|
height: u16,
|
||||||
|
node_mask: NodeMask,
|
||||||
|
to_check: StatesToCheck,
|
||||||
|
}
|
||||||
|
impl NodeRef {
|
||||||
|
fn union_with(&mut self, other: &Self) {
|
||||||
|
self.node_mask = self.node_mask.union(&other.node_mask);
|
||||||
|
self.to_check = self.to_check.union(&other.to_check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for NodeRef {}
|
||||||
|
impl PartialEq for NodeRef {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id && self.height == other.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Ord for NodeRef {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.partial_cmp(other).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialOrd for NodeRef {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
// Sort nodes first by height, then if the height is the same id.
|
||||||
|
// The order of the id does not matter it just helps with binary search later
|
||||||
|
Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut to_rerender = FxHashSet::default();
|
let mut to_rerender = FxHashSet::default();
|
||||||
to_rerender.extend(nodes_updated.iter());
|
to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
|
||||||
let mut nodes_updated: Vec<_> = nodes_updated
|
let mut nodes_updated: Vec<_> = nodes_updated
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|id| (id, self[id].height))
|
.map(|(id, mask)| NodeRef {
|
||||||
|
id,
|
||||||
|
height: self[id].height,
|
||||||
|
node_mask: mask,
|
||||||
|
to_check: StatesToCheck::All,
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// Sort nodes first by height, then if the height is the same id.
|
nodes_updated.sort();
|
||||||
nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0)));
|
|
||||||
nodes_updated.dedup();
|
// Combine mutations that affect the same node.
|
||||||
|
let mut last_node: Option<NodeRef> = None;
|
||||||
|
let mut new_nodes_updated = VecDeque::new();
|
||||||
|
for current in nodes_updated.into_iter() {
|
||||||
|
if let Some(node) = &mut last_node {
|
||||||
|
if *node == current {
|
||||||
|
node.union_with(¤t);
|
||||||
|
} else {
|
||||||
|
new_nodes_updated.push_back(last_node.replace(current).unwrap());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
last_node = Some(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(node) = last_node {
|
||||||
|
new_nodes_updated.push_back(node);
|
||||||
|
}
|
||||||
|
let nodes_updated = new_nodes_updated;
|
||||||
|
|
||||||
|
// update the state that only depends on nodes. The order does not matter.
|
||||||
|
for node_ref in &nodes_updated {
|
||||||
|
let mut changed = false;
|
||||||
|
let node = &mut self[node_ref.id];
|
||||||
|
let ids = match &node_ref.to_check {
|
||||||
|
StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask),
|
||||||
|
StatesToCheck::Some(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
for ty in ids {
|
||||||
|
let node = &mut self[node_ref.id];
|
||||||
|
let vnode = node.element(vdom);
|
||||||
|
changed |= node.state.update_node_dep_state(ty, vnode, &ctx);
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
to_rerender.insert(node_ref.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
||||||
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
|
let mut to_bubble = nodes_updated.clone();
|
||||||
while let Some((id, height)) = to_bubble.pop_back() {
|
while let Some(node_ref) = to_bubble.pop_back() {
|
||||||
let node = &mut self[id as usize];
|
let NodeRef {
|
||||||
let vnode = node.element(vdom);
|
id,
|
||||||
let node_type = &node.node_type;
|
height,
|
||||||
let up_state = &mut node.up_state;
|
node_mask,
|
||||||
let children = match node_type {
|
to_check,
|
||||||
NodeType::Element { children, .. } => Some(children),
|
} = node_ref;
|
||||||
_ => None,
|
let (node, children) = self.get_node_children_mut(id).unwrap();
|
||||||
|
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
|
||||||
|
let ids = match to_check {
|
||||||
|
StatesToCheck::All => node.state.child_dep_types(&node_mask),
|
||||||
|
StatesToCheck::Some(ids) => ids,
|
||||||
};
|
};
|
||||||
// todo: reduce cloning state
|
let mut changed = Vec::new();
|
||||||
let old = up_state.clone();
|
for ty in ids {
|
||||||
let mut new = up_state.clone();
|
let vnode = node.element(vdom);
|
||||||
let parent = node.parent;
|
if node
|
||||||
new.reduce(
|
.state
|
||||||
children
|
.update_child_dep_state(ty, vnode, &children_state, &ctx)
|
||||||
.unwrap_or(&Vec::new())
|
{
|
||||||
.clone()
|
changed.push(ty);
|
||||||
.iter()
|
}
|
||||||
.map(|c| &self[*c].up_state),
|
}
|
||||||
vnode,
|
if let Some(parent_id) = node.parent {
|
||||||
us_ctx,
|
if !changed.is_empty() {
|
||||||
);
|
to_rerender.insert(id);
|
||||||
if new != old {
|
let i = to_bubble.partition_point(
|
||||||
to_rerender.insert(id);
|
|NodeRef {
|
||||||
if let Some(p) = parent {
|
id: other_id,
|
||||||
let i = to_bubble.partition_point(|(other_id, h)| {
|
height: h,
|
||||||
*h < height - 1 || (*h == height - 1 && *other_id < p.0)
|
..
|
||||||
});
|
}| {
|
||||||
|
*h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
|
||||||
|
},
|
||||||
|
);
|
||||||
// make sure the parent is not already queued
|
// make sure the parent is not already queued
|
||||||
if i >= to_bubble.len() || to_bubble[i].0 != p.0 {
|
if i < to_bubble.len() && to_bubble[i].id == parent_id.0 {
|
||||||
to_bubble.insert(i, (p.0, height - 1));
|
to_bubble[i].to_check =
|
||||||
|
to_bubble[i].to_check.union(&StatesToCheck::Some(changed));
|
||||||
|
} else {
|
||||||
|
to_bubble.insert(
|
||||||
|
i,
|
||||||
|
NodeRef {
|
||||||
|
id: parent_id.0,
|
||||||
|
height: height - 1,
|
||||||
|
node_mask: NodeMask::NONE,
|
||||||
|
to_check: StatesToCheck::Some(changed),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let node = &mut self[id as usize];
|
|
||||||
node.up_state = new;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
||||||
let mut to_push: VecDeque<_> = nodes_updated.clone().into();
|
let mut to_push = nodes_updated.clone();
|
||||||
while let Some((id, height)) = to_push.pop_front() {
|
while let Some(node_ref) = to_push.pop_front() {
|
||||||
let node = &self[id as usize];
|
let NodeRef {
|
||||||
// todo: reduce cloning state
|
id,
|
||||||
let old = node.down_state.clone();
|
height,
|
||||||
let mut new = node.down_state.clone();
|
node_mask,
|
||||||
let vnode = node.element(vdom);
|
to_check,
|
||||||
new.reduce(
|
} = node_ref;
|
||||||
node.parent
|
let node = &self[id];
|
||||||
.filter(|e| e.0 != 0)
|
let ids = match to_check {
|
||||||
.map(|e| &self[e].down_state),
|
StatesToCheck::All => node.state.parent_dep_types(&node_mask),
|
||||||
vnode,
|
StatesToCheck::Some(ids) => ids,
|
||||||
ds_ctx,
|
};
|
||||||
);
|
let mut changed = Vec::new();
|
||||||
if new != old {
|
let (node, parent) = self.get_node_parent_mut(id).unwrap();
|
||||||
to_rerender.insert(id);
|
for ty in ids {
|
||||||
let node = &mut self[id as usize];
|
let vnode = node.element(vdom);
|
||||||
|
let parent = parent.as_deref();
|
||||||
|
let state = &mut node.state;
|
||||||
|
if state.update_parent_dep_state(
|
||||||
|
ty,
|
||||||
|
vnode,
|
||||||
|
parent.filter(|n| n.id.0 != 0).map(|n| &n.state),
|
||||||
|
&ctx,
|
||||||
|
) {
|
||||||
|
changed.push(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_rerender.insert(id);
|
||||||
|
if !changed.is_empty() {
|
||||||
|
let node = &self[id];
|
||||||
if let NodeType::Element { children, .. } = &node.node_type {
|
if let NodeType::Element { children, .. } = &node.node_type {
|
||||||
for c in children {
|
for c in children {
|
||||||
let i = to_push.partition_point(|(other_id, h)| {
|
let i = to_push.partition_point(
|
||||||
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
|
|NodeRef {
|
||||||
});
|
id: other_id,
|
||||||
if i >= to_push.len() || to_push[i].0 != c.0 {
|
height: h,
|
||||||
to_push.insert(i, (c.0, height + 1));
|
..
|
||||||
|
}| {
|
||||||
|
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if i < to_push.len() && to_push[i].id == c.0 {
|
||||||
|
to_push[i].to_check = to_push[i]
|
||||||
|
.to_check
|
||||||
|
.union(&StatesToCheck::Some(changed.clone()));
|
||||||
|
} else {
|
||||||
|
to_push.insert(
|
||||||
|
i,
|
||||||
|
NodeRef {
|
||||||
|
id: c.0,
|
||||||
|
height: height + 1,
|
||||||
|
node_mask: NodeMask::NONE,
|
||||||
|
to_check: StatesToCheck::Some(changed.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.down_state = new;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,12 +452,9 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove a node and it's children from the dom.
|
// remove a node and it's children from the dom.
|
||||||
fn remove(&mut self, id: usize) -> Option<Node<US, DS>> {
|
fn remove(&mut self, id: usize) -> Option<Node<S>> {
|
||||||
// We do not need to remove the node from the parent's children list for children.
|
// We do not need to remove the node from the parent's children list for children.
|
||||||
fn inner<US: BubbledUpState, DS: PushedDownState>(
|
fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> {
|
||||||
dom: &mut RealDom<US, DS>,
|
|
||||||
id: usize,
|
|
||||||
) -> Option<Node<US, DS>> {
|
|
||||||
let mut node = dom.nodes[id as usize].take()?;
|
let mut node = dom.nodes[id as usize].take()?;
|
||||||
if let NodeType::Element { children, .. } = &mut node.node_type {
|
if let NodeType::Element { children, .. } = &mut node.node_type {
|
||||||
for c in children {
|
for c in children {
|
||||||
@@ -327,7 +476,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
Some(node)
|
Some(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, node: Node<US, DS>) {
|
fn insert(&mut self, node: Node<S>) {
|
||||||
let current_len = self.nodes.len();
|
let current_len = self.nodes.len();
|
||||||
let id = node.id.0;
|
let id = node.id.0;
|
||||||
if current_len - 1 < node.id.0 {
|
if current_len - 1 < node.id.0 {
|
||||||
@@ -337,15 +486,65 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
self.nodes[id] = Some(node);
|
self.nodes[id] = Some(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: usize) -> Option<&Node<US, DS>> {
|
pub fn get(&self, id: usize) -> Option<&Node<S>> {
|
||||||
self.nodes.get(id)?.as_ref()
|
self.nodes.get(id)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<US, DS>> {
|
pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
|
||||||
self.nodes.get_mut(id)?.as_mut()
|
self.nodes.get_mut(id)?.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<US, DS>> {
|
// this is safe because no node will have itself as a child
|
||||||
|
pub fn get_node_children_mut<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
id: usize,
|
||||||
|
) -> Option<(&'a mut Node<S>, Vec<&'a mut Node<S>>)> {
|
||||||
|
let ptr = self.nodes.as_mut_ptr();
|
||||||
|
unsafe {
|
||||||
|
if id >= self.nodes.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let node = &mut *ptr.add(id);
|
||||||
|
if let Some(node) = node.as_mut() {
|
||||||
|
let children = match &node.node_type {
|
||||||
|
NodeType::Element { children, .. } => children
|
||||||
|
.iter()
|
||||||
|
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
|
||||||
|
.collect(),
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
return Some((node, children));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is safe because no node will have itself as a parent
|
||||||
|
pub fn get_node_parent_mut<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
id: usize,
|
||||||
|
) -> Option<(&'a mut Node<S>, Option<&'a mut Node<S>>)> {
|
||||||
|
let ptr = self.nodes.as_mut_ptr();
|
||||||
|
unsafe {
|
||||||
|
let node = &mut *ptr.add(id);
|
||||||
|
if id >= self.nodes.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
if let Some(node) = node.as_mut() {
|
||||||
|
let parent = node
|
||||||
|
.parent
|
||||||
|
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
|
||||||
|
return Some((node, parent));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
|
||||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||||
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||||
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
|
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
|
||||||
@@ -416,12 +615,8 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call a function for each node in the dom, depth first.
|
/// Call a function for each node in the dom, depth first.
|
||||||
pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<US, DS>)) {
|
pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
|
||||||
fn inner<US: BubbledUpState, DS: PushedDownState>(
|
fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
|
||||||
dom: &RealDom<US, DS>,
|
|
||||||
id: ElementId,
|
|
||||||
f: &mut impl FnMut(&Node<US, DS>),
|
|
||||||
) {
|
|
||||||
let node = &dom[id];
|
let node = &dom[id];
|
||||||
f(node);
|
f(node);
|
||||||
if let NodeType::Element { children, .. } = &node.node_type {
|
if let NodeType::Element { children, .. } = &node.node_type {
|
||||||
@@ -438,12 +633,8 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call a function for each node in the dom, depth first.
|
/// Call a function for each node in the dom, depth first.
|
||||||
pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<US, DS>)) {
|
pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
|
||||||
fn inner<US: BubbledUpState, DS: PushedDownState>(
|
fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
|
||||||
dom: &mut RealDom<US, DS>,
|
|
||||||
id: ElementId,
|
|
||||||
f: &mut impl FnMut(&mut Node<US, DS>),
|
|
||||||
) {
|
|
||||||
let node = &mut dom[id];
|
let node = &mut dom[id];
|
||||||
f(node);
|
f(node);
|
||||||
if let NodeType::Element { children, .. } = &mut node.node_type {
|
if let NodeType::Element { children, .. } = &mut node.node_type {
|
||||||
@@ -461,28 +652,28 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
|
impl<S: State> Index<usize> for RealDom<S> {
|
||||||
type Output = Node<US, DS>;
|
type Output = Node<S>;
|
||||||
|
|
||||||
fn index(&self, idx: usize) -> &Self::Output {
|
fn index(&self, idx: usize) -> &Self::Output {
|
||||||
self.get(idx).expect("Node does not exist")
|
self.get(idx).expect("Node does not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> {
|
impl<S: State> Index<ElementId> for RealDom<S> {
|
||||||
type Output = Node<US, DS>;
|
type Output = Node<S>;
|
||||||
|
|
||||||
fn index(&self, idx: ElementId) -> &Self::Output {
|
fn index(&self, idx: ElementId) -> &Self::Output {
|
||||||
&self[idx.0]
|
&self[idx.0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for RealDom<US, DS> {
|
impl<S: State> IndexMut<usize> for RealDom<S> {
|
||||||
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
|
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
|
||||||
self.get_mut(idx).expect("Node does not exist")
|
self.get_mut(idx).expect("Node does not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US, DS> {
|
impl<S: State> IndexMut<ElementId> for RealDom<S> {
|
||||||
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
|
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
|
||||||
&mut self[idx.0]
|
&mut self[idx.0]
|
||||||
}
|
}
|
||||||
@@ -490,15 +681,13 @@ impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US
|
|||||||
|
|
||||||
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
|
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Node<US: BubbledUpState, DS: PushedDownState> {
|
pub struct Node<S: State> {
|
||||||
/// The id of the node this node was created from.
|
/// The id of the node this node was created from.
|
||||||
pub id: ElementId,
|
pub id: ElementId,
|
||||||
/// The parent id of the node.
|
/// The parent id of the node.
|
||||||
pub parent: Option<ElementId>,
|
pub parent: Option<ElementId>,
|
||||||
/// State of the node that is bubbled up to its parents. The state must depend only on the node and its children.
|
/// State of the node.
|
||||||
pub up_state: US,
|
pub state: S,
|
||||||
/// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent.
|
|
||||||
pub down_state: DS,
|
|
||||||
/// Additional inforation specific to the node type
|
/// Additional inforation specific to the node type
|
||||||
pub node_type: NodeType,
|
pub node_type: NodeType,
|
||||||
/// The number of parents before the root node. The root node has height 1.
|
/// The number of parents before the root node. The root node has height 1.
|
||||||
@@ -518,14 +707,13 @@ pub enum NodeType {
|
|||||||
Placeholder,
|
Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> Node<US, DS> {
|
impl<S: State> Node<S> {
|
||||||
fn new(id: u64, node_type: NodeType) -> Self {
|
fn new(id: u64, node_type: NodeType) -> Self {
|
||||||
Node {
|
Node {
|
||||||
id: ElementId(id as usize),
|
id: ElementId(id as usize),
|
||||||
parent: None,
|
parent: None,
|
||||||
node_type,
|
node_type,
|
||||||
down_state: DS::default(),
|
state: S::default(),
|
||||||
up_state: US::default(),
|
|
||||||
height: 0,
|
height: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -551,39 +739,3 @@ impl<US: BubbledUpState, DS: PushedDownState> Node<US, DS> {
|
|||||||
self.parent = Some(parent);
|
self.parent = Some(parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
|
||||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
|
||||||
/// Called at most once per update.
|
|
||||||
pub trait PushedDownState: Default + PartialEq + Clone {
|
|
||||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
|
||||||
/// This is sometimes nessisary for lifetime purposes.
|
|
||||||
type Ctx;
|
|
||||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx);
|
|
||||||
}
|
|
||||||
impl PushedDownState for () {
|
|
||||||
type Ctx = ();
|
|
||||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
|
||||||
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
|
||||||
/// Called at most once per update.
|
|
||||||
pub trait BubbledUpState: Default + PartialEq + Clone {
|
|
||||||
/// The context is passed to the [BubbledUpState::reduce] when it is bubbled up.
|
|
||||||
/// This is sometimes nessisary for lifetime purposes.
|
|
||||||
type Ctx;
|
|
||||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a;
|
|
||||||
}
|
|
||||||
impl BubbledUpState for () {
|
|
||||||
type Ctx = ();
|
|
||||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
use std::{any::TypeId, fmt::Debug};
|
||||||
|
|
||||||
|
use anymap::AnyMap;
|
||||||
|
use dioxus_core::{Attribute, ElementId, VElement, VNode, VText};
|
||||||
|
|
||||||
|
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
|
||||||
|
s_iter: impl Iterator<Item = T>,
|
||||||
|
o_iter: impl Iterator<Item = T>,
|
||||||
|
new_len_guess: usize,
|
||||||
|
) -> Vec<T> {
|
||||||
|
let mut s_peekable = s_iter.peekable();
|
||||||
|
let mut o_peekable = o_iter.peekable();
|
||||||
|
let mut v = Vec::with_capacity(new_len_guess);
|
||||||
|
while let Some(s_i) = s_peekable.peek() {
|
||||||
|
loop {
|
||||||
|
if let Some(o_i) = o_peekable.peek() {
|
||||||
|
if o_i > s_i {
|
||||||
|
break;
|
||||||
|
} else if o_i < s_i {
|
||||||
|
v.push(o_peekable.next().unwrap());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.push(s_peekable.next().unwrap());
|
||||||
|
}
|
||||||
|
while let Some(o_i) = o_peekable.next() {
|
||||||
|
v.push(o_i);
|
||||||
|
}
|
||||||
|
for w in v.windows(2) {
|
||||||
|
debug_assert!(w[1] > w[0]);
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NodeView<'a> {
|
||||||
|
inner: &'a VNode<'a>,
|
||||||
|
mask: NodeMask,
|
||||||
|
}
|
||||||
|
impl<'a> NodeView<'a> {
|
||||||
|
pub fn new(vnode: &'a VNode<'a>, view: NodeMask) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: vnode,
|
||||||
|
mask: view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> ElementId {
|
||||||
|
self.inner.mounted_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tag(&self) -> Option<&'a str> {
|
||||||
|
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn namespace(&self) -> Option<&'a str> {
|
||||||
|
self.mask
|
||||||
|
.namespace
|
||||||
|
.then(|| self.el().map(|el| el.namespace).flatten())
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||||
|
self.el()
|
||||||
|
.map(|el| el.attributes)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.filter(|a| self.mask.attritutes.contains_attribute(&a.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self) -> Option<&str> {
|
||||||
|
self.mask
|
||||||
|
.text
|
||||||
|
.then(|| self.txt().map(|txt| txt.text))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn el(&self) -> Option<&'a VElement<'a>> {
|
||||||
|
if let VNode::Element(el) = &self.inner {
|
||||||
|
Some(el)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn txt(&self) -> Option<&'a VText<'a>> {
|
||||||
|
if let VNode::Text(txt) = &self.inner {
|
||||||
|
Some(txt)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub enum AttributeMask {
|
||||||
|
All,
|
||||||
|
Dynamic(Vec<&'static str>),
|
||||||
|
Static(&'static [&'static str]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributeMask {
|
||||||
|
pub const NONE: Self = Self::Static(&[]);
|
||||||
|
|
||||||
|
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||||
|
match self {
|
||||||
|
AttributeMask::All => true,
|
||||||
|
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
|
||||||
|
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn single(new: &'static str) -> Self {
|
||||||
|
Self::Dynamic(vec![new])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self) {
|
||||||
|
match &self {
|
||||||
|
AttributeMask::Static(attrs) => debug_assert!(
|
||||||
|
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||||
|
"attritutes must be increasing"
|
||||||
|
),
|
||||||
|
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||||
|
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||||
|
"attritutes must be increasing"
|
||||||
|
),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(&self, other: &Self) -> Self {
|
||||||
|
let new = match (self, other) {
|
||||||
|
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||||
|
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||||
|
),
|
||||||
|
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||||
|
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||||
|
),
|
||||||
|
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||||
|
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||||
|
),
|
||||||
|
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||||
|
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||||
|
),
|
||||||
|
_ => AttributeMask::All,
|
||||||
|
};
|
||||||
|
new.verify();
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlaps(&self, other: &Self) -> bool {
|
||||||
|
fn overlaps_iter(
|
||||||
|
mut self_iter: impl Iterator<Item = &'static str>,
|
||||||
|
mut other_iter: impl Iterator<Item = &'static str>,
|
||||||
|
) -> bool {
|
||||||
|
if let Some(mut other_attr) = other_iter.next() {
|
||||||
|
while let Some(self_attr) = self_iter.next() {
|
||||||
|
while other_attr < self_attr {
|
||||||
|
if let Some(attr) = other_iter.next() {
|
||||||
|
other_attr = attr;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if other_attr == self_attr {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
match (self, other) {
|
||||||
|
(AttributeMask::All, AttributeMask::All) => true,
|
||||||
|
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||||
|
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||||
|
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||||
|
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||||
|
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||||
|
overlaps_iter(v1.iter().copied(), v2.iter().copied())
|
||||||
|
}
|
||||||
|
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||||
|
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||||
|
}
|
||||||
|
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||||
|
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||||
|
}
|
||||||
|
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||||
|
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AttributeMask {
|
||||||
|
fn default() -> Self {
|
||||||
|
AttributeMask::Static(&[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone, Debug)]
|
||||||
|
pub struct NodeMask {
|
||||||
|
// must be sorted
|
||||||
|
attritutes: AttributeMask,
|
||||||
|
tag: bool,
|
||||||
|
namespace: bool,
|
||||||
|
text: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeMask {
|
||||||
|
pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false, false);
|
||||||
|
pub const ALL: Self = Self::new(AttributeMask::All, true, true, true);
|
||||||
|
|
||||||
|
/// attritutes must be sorted!
|
||||||
|
pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool, text: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
attritutes,
|
||||||
|
tag,
|
||||||
|
namespace,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlaps(&self, other: &Self) -> bool {
|
||||||
|
(self.tag && other.tag)
|
||||||
|
|| (self.namespace && other.namespace)
|
||||||
|
|| self.attritutes.overlaps(&other.attritutes)
|
||||||
|
|| (self.text && other.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(&self, other: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
attritutes: self.attritutes.union(&other.attritutes),
|
||||||
|
tag: self.tag | other.tag,
|
||||||
|
namespace: self.namespace | other.namespace,
|
||||||
|
text: self.text | other.text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||||
|
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
||||||
|
/// Called at most once per update.
|
||||||
|
pub trait ChildDepState {
|
||||||
|
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||||
|
/// This is sometimes nessisary for lifetime purposes.
|
||||||
|
type Ctx;
|
||||||
|
type DepState: ChildDepState;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||||
|
fn reduce<'a>(
|
||||||
|
&mut self,
|
||||||
|
node: NodeView,
|
||||||
|
children: impl Iterator<Item = &'a Self::DepState>,
|
||||||
|
ctx: &Self::Ctx,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self::DepState: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||||
|
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||||
|
/// Called at most once per update.
|
||||||
|
pub trait ParentDepState {
|
||||||
|
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||||
|
/// This is sometimes nessisary for lifetime purposes.
|
||||||
|
type Ctx;
|
||||||
|
type DepState: ParentDepState;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||||
|
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
|
||||||
|
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||||
|
/// Called at most once per update.
|
||||||
|
pub trait NodeDepState {
|
||||||
|
type Ctx;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||||
|
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait State: Default + Clone {
|
||||||
|
fn update_node_dep_state<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
ty: TypeId,
|
||||||
|
node: &'a VNode<'a>,
|
||||||
|
ctx: &AnyMap,
|
||||||
|
) -> bool;
|
||||||
|
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||||
|
fn child_dep_types(&self, mask: &NodeMask) -> Vec<TypeId>;
|
||||||
|
|
||||||
|
fn update_parent_dep_state<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
ty: TypeId,
|
||||||
|
node: &'a VNode<'a>,
|
||||||
|
parent: Option<&Self>,
|
||||||
|
ctx: &AnyMap,
|
||||||
|
) -> bool;
|
||||||
|
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||||
|
fn parent_dep_types(&self, mask: &NodeMask) -> Vec<TypeId>;
|
||||||
|
|
||||||
|
fn update_child_dep_state<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
ty: TypeId,
|
||||||
|
node: &'a VNode<'a>,
|
||||||
|
children: &[&Self],
|
||||||
|
ctx: &AnyMap,
|
||||||
|
) -> bool;
|
||||||
|
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||||
|
fn node_dep_types(&self, mask: &NodeMask) -> Vec<TypeId>;
|
||||||
|
}
|
||||||
@@ -3,8 +3,13 @@ use dioxus_core::*;
|
|||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use dioxus_native_core::real_dom::RealDom;
|
use dioxus_native_core::real_dom::RealDom;
|
||||||
|
use dioxus_native_core::state::State;
|
||||||
|
use dioxus_native_core_macro::State;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
#[derive(State, Default, Clone)]
|
||||||
|
struct Empty {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_node() {
|
fn remove_node() {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@@ -20,7 +25,7 @@ fn remove_node() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
let mut dom: RealDom<Empty> = RealDom::new();
|
||||||
|
|
||||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||||
let child_div = VElement {
|
let child_div = VElement {
|
||||||
@@ -92,7 +97,7 @@ fn add_node() {
|
|||||||
div{}
|
div{}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
let mut dom: RealDom<Empty> = RealDom::new();
|
||||||
|
|
||||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ use dioxus_core::*;
|
|||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use dioxus_native_core::real_dom::RealDom;
|
use dioxus_native_core::real_dom::RealDom;
|
||||||
|
use dioxus_native_core::state::State;
|
||||||
|
use dioxus_native_core_macro::State;
|
||||||
|
|
||||||
|
#[derive(State, Default, Clone)]
|
||||||
|
struct Empty {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn initial_build_simple() {
|
fn initial_build_simple() {
|
||||||
@@ -21,7 +26,7 @@ fn initial_build_simple() {
|
|||||||
div{}
|
div{}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
let mut dom: RealDom<Empty> = RealDom::new();
|
||||||
|
|
||||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||||
let root_div = VElement {
|
let root_div = VElement {
|
||||||
@@ -60,7 +65,7 @@ fn initial_build_with_children() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut dom: RealDom<(), ()> = RealDom::new();
|
let mut dom: RealDom<Empty> = RealDom::new();
|
||||||
|
|
||||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||||
let first_text = VText {
|
let first_text = VText {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use dioxus_native_core::state::*;
|
||||||
|
use dioxus_native_core_macro::*;
|
||||||
|
|
||||||
|
#[derive(State, Default, Clone)]
|
||||||
|
struct Z {
|
||||||
|
// depends on just attributes and no context
|
||||||
|
#[node_dep_state()]
|
||||||
|
x: A,
|
||||||
|
// depends on attributes, the B component of children and i32 context
|
||||||
|
#[child_dep_state(B, i32)]
|
||||||
|
y: B,
|
||||||
|
// depends on attributes, the C component of it's parent and a u8 context
|
||||||
|
#[parent_dep_state(C, u8)]
|
||||||
|
z: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct Z {
|
||||||
|
// x: A,
|
||||||
|
// y: B,
|
||||||
|
// z: C,
|
||||||
|
// }
|
||||||
|
|
||||||
|
use dioxus_native_core::state::NodeDepState;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct A;
|
||||||
|
impl NodeDepState for A {
|
||||||
|
type Ctx = ();
|
||||||
|
fn reduce(&mut self, _: NodeView, _: &()) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct B;
|
||||||
|
impl ChildDepState for B {
|
||||||
|
type Ctx = i32;
|
||||||
|
type DepState = Self;
|
||||||
|
fn reduce<'a>(
|
||||||
|
&mut self,
|
||||||
|
_: dioxus_native_core::state::NodeView,
|
||||||
|
_: impl Iterator<Item = &'a Self::DepState>,
|
||||||
|
_: &i32,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self::DepState: 'a,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct C;
|
||||||
|
impl ParentDepState for C {
|
||||||
|
type Ctx = u8;
|
||||||
|
type DepState = Self;
|
||||||
|
fn reduce(
|
||||||
|
&mut self,
|
||||||
|
_: dioxus_native_core::state::NodeView,
|
||||||
|
_: Option<&Self::DepState>,
|
||||||
|
_: &u8,
|
||||||
|
) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
use dioxus_core::VNode;
|
|
||||||
use dioxus_core::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use dioxus_native_core::real_dom::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
|
||||||
struct CallCounter(u32);
|
|
||||||
impl BubbledUpState for CallCounter {
|
|
||||||
type Ctx = ();
|
|
||||||
|
|
||||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
self.0 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PushedDownState for CallCounter {
|
|
||||||
type Ctx = ();
|
|
||||||
|
|
||||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
|
||||||
self.0 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
|
||||||
struct BubbledUpStateTester(String, Vec<Box<BubbledUpStateTester>>);
|
|
||||||
impl BubbledUpState for BubbledUpStateTester {
|
|
||||||
type Ctx = u32;
|
|
||||||
|
|
||||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
assert_eq!(*ctx, 42);
|
|
||||||
*self = BubbledUpStateTester(
|
|
||||||
vnode.mounted_id().to_string(),
|
|
||||||
children.map(|c| Box::new(c.clone())).collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
|
||||||
struct PushedDownStateTester(String, Option<Box<PushedDownStateTester>>);
|
|
||||||
impl PushedDownState for PushedDownStateTester {
|
|
||||||
type Ctx = u32;
|
|
||||||
|
|
||||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) {
|
|
||||||
assert_eq!(*ctx, 42);
|
|
||||||
*self = PushedDownStateTester(
|
|
||||||
vnode.mounted_id().to_string(),
|
|
||||||
parent.map(|c| Box::new(c.clone())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn state_initial() {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn Base(cx: Scope) -> Element {
|
|
||||||
rsx!(cx, div {
|
|
||||||
p{}
|
|
||||||
h1{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let vdom = VirtualDom::new(Base);
|
|
||||||
|
|
||||||
let mutations = vdom.create_vnodes(rsx! {
|
|
||||||
div {
|
|
||||||
p{}
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut dom: RealDom<BubbledUpStateTester, PushedDownStateTester> = RealDom::new();
|
|
||||||
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42);
|
|
||||||
|
|
||||||
let root_div = &dom[1];
|
|
||||||
assert_eq!(root_div.up_state.0, "1");
|
|
||||||
assert_eq!(
|
|
||||||
root_div.up_state.1,
|
|
||||||
vec![
|
|
||||||
Box::new(BubbledUpStateTester("2".to_string(), Vec::new())),
|
|
||||||
Box::new(BubbledUpStateTester("3".to_string(), Vec::new()))
|
|
||||||
]
|
|
||||||
);
|
|
||||||
assert_eq!(root_div.down_state.0, "1");
|
|
||||||
assert_eq!(root_div.down_state.1, None);
|
|
||||||
|
|
||||||
let child_p = &dom[2];
|
|
||||||
assert_eq!(child_p.up_state.0, "2");
|
|
||||||
assert_eq!(child_p.up_state.1, Vec::new());
|
|
||||||
assert_eq!(child_p.down_state.0, "2");
|
|
||||||
assert_eq!(
|
|
||||||
child_p.down_state.1,
|
|
||||||
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
|
||||||
);
|
|
||||||
|
|
||||||
let child_h1 = &dom[3];
|
|
||||||
assert_eq!(child_h1.up_state.0, "3");
|
|
||||||
assert_eq!(child_h1.up_state.1, Vec::new());
|
|
||||||
assert_eq!(child_h1.down_state.0, "3");
|
|
||||||
assert_eq!(
|
|
||||||
child_h1.down_state.1,
|
|
||||||
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn state_reduce_initally_called_minimally() {
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
|
||||||
struct CallCounter(u32);
|
|
||||||
impl BubbledUpState for CallCounter {
|
|
||||||
type Ctx = ();
|
|
||||||
|
|
||||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
self.0 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PushedDownState for CallCounter {
|
|
||||||
type Ctx = ();
|
|
||||||
|
|
||||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
|
||||||
self.0 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn Base(cx: Scope) -> Element {
|
|
||||||
rsx!(cx, div {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let vdom = VirtualDom::new(Base);
|
|
||||||
|
|
||||||
let mutations = vdom.create_vnodes(rsx! {
|
|
||||||
div {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
|
||||||
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
|
||||||
|
|
||||||
dom.traverse_depth_first(|n| {
|
|
||||||
assert_eq!(n.up_state.0, 1);
|
|
||||||
assert_eq!(n.down_state.0, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn state_reduce_down_called_minimally_on_update() {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn Base(cx: Scope) -> Element {
|
|
||||||
rsx!(cx, div {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let vdom = VirtualDom::new(Base);
|
|
||||||
|
|
||||||
let mutations = vdom.create_vnodes(rsx! {
|
|
||||||
div {
|
|
||||||
width: "100%",
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
|
||||||
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
|
||||||
edits: vec![DomEdit::SetAttribute {
|
|
||||||
root: 1,
|
|
||||||
field: "width",
|
|
||||||
value: "99%",
|
|
||||||
ns: Some("style"),
|
|
||||||
}],
|
|
||||||
dirty_scopes: fxhash::FxHashSet::default(),
|
|
||||||
refs: Vec::new(),
|
|
||||||
}]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
|
||||||
|
|
||||||
dom.traverse_depth_first(|n| {
|
|
||||||
assert_eq!(n.down_state.0, 2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn state_reduce_up_called_minimally_on_update() {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn Base(cx: Scope) -> Element {
|
|
||||||
rsx!(cx, div {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let vdom = VirtualDom::new(Base);
|
|
||||||
|
|
||||||
let mutations = vdom.create_vnodes(rsx! {
|
|
||||||
div {
|
|
||||||
width: "100%",
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
p{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"hello"
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
h1{}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut dom: RealDom<CallCounter, CallCounter> = RealDom::new();
|
|
||||||
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
|
||||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
|
||||||
edits: vec![DomEdit::SetAttribute {
|
|
||||||
root: 4,
|
|
||||||
field: "width",
|
|
||||||
value: "99%",
|
|
||||||
ns: Some("style"),
|
|
||||||
}],
|
|
||||||
dirty_scopes: fxhash::FxHashSet::default(),
|
|
||||||
refs: Vec::new(),
|
|
||||||
}]);
|
|
||||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
|
||||||
|
|
||||||
dom.traverse_depth_first(|n| {
|
|
||||||
assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
use anymap::AnyMap;
|
||||||
|
use dioxus_core::VNode;
|
||||||
|
use dioxus_core::*;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_native_core::real_dom::*;
|
||||||
|
use dioxus_native_core::state::{
|
||||||
|
AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State,
|
||||||
|
};
|
||||||
|
use dioxus_native_core_macro::State;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, State)]
|
||||||
|
struct CallCounterState {
|
||||||
|
#[child_dep_state(ChildDepCallCounter)]
|
||||||
|
child_counter: ChildDepCallCounter,
|
||||||
|
#[parent_dep_state(ParentDepCallCounter)]
|
||||||
|
parent_counter: ParentDepCallCounter,
|
||||||
|
#[node_dep_state()]
|
||||||
|
node_counter: NodeDepCallCounter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct ChildDepCallCounter(u32);
|
||||||
|
impl ChildDepState for ChildDepCallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
type DepState = Self;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||||
|
fn reduce<'a>(
|
||||||
|
&mut self,
|
||||||
|
_: NodeView,
|
||||||
|
_: impl Iterator<Item = &'a Self::DepState>,
|
||||||
|
_: &Self::Ctx,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self::DepState: 'a,
|
||||||
|
{
|
||||||
|
self.0 += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct ParentDepCallCounter(u32);
|
||||||
|
impl ParentDepState for ParentDepCallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
type DepState = Self;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||||
|
fn reduce(
|
||||||
|
&mut self,
|
||||||
|
_node: NodeView,
|
||||||
|
_parent: Option<&Self::DepState>,
|
||||||
|
_ctx: &Self::Ctx,
|
||||||
|
) -> bool {
|
||||||
|
self.0 += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
struct NodeDepCallCounter(u32);
|
||||||
|
impl NodeDepState for NodeDepCallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||||
|
fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool {
|
||||||
|
self.0 += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
|
||||||
|
impl ChildDepState for BubbledUpStateTester {
|
||||||
|
type Ctx = u32;
|
||||||
|
type DepState = Self;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false);
|
||||||
|
fn reduce<'a>(
|
||||||
|
&mut self,
|
||||||
|
node: NodeView,
|
||||||
|
children: impl Iterator<Item = &'a Self::DepState>,
|
||||||
|
ctx: &Self::Ctx,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self::DepState: 'a,
|
||||||
|
{
|
||||||
|
assert_eq!(*ctx, 42);
|
||||||
|
*self = BubbledUpStateTester(
|
||||||
|
node.tag().map(|s| s.to_string()),
|
||||||
|
children.into_iter().map(|c| Box::new(c.clone())).collect(),
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
|
||||||
|
impl ParentDepState for PushedDownStateTester {
|
||||||
|
type Ctx = u32;
|
||||||
|
type DepState = Self;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false);
|
||||||
|
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
|
||||||
|
assert_eq!(*ctx, 42);
|
||||||
|
*self = PushedDownStateTester(
|
||||||
|
node.tag().map(|s| s.to_string()),
|
||||||
|
parent.map(|c| Box::new(c.clone())),
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(State, Clone, Default, Debug)]
|
||||||
|
struct StateTester {
|
||||||
|
#[child_dep_state(BubbledUpStateTester, u32)]
|
||||||
|
bubbled: BubbledUpStateTester,
|
||||||
|
#[parent_dep_state(PushedDownStateTester, u32)]
|
||||||
|
pushed: PushedDownStateTester,
|
||||||
|
#[node_dep_state(u32)]
|
||||||
|
node: NodeStateTester,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||||
|
impl NodeDepState for NodeStateTester {
|
||||||
|
type Ctx = u32;
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false);
|
||||||
|
fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool {
|
||||||
|
assert_eq!(*ctx, 42);
|
||||||
|
*self = NodeStateTester(
|
||||||
|
node.tag().map(|s| s.to_string()),
|
||||||
|
node.attributes()
|
||||||
|
.map(|a| (a.name.to_string(), a.value.to_string()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_initial() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
p{}
|
||||||
|
h1{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
p{
|
||||||
|
color: "red"
|
||||||
|
}
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut dom: RealDom<StateTester> = RealDom::new();
|
||||||
|
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||||
|
let mut ctx = AnyMap::new();
|
||||||
|
ctx.insert(42u32);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
|
||||||
|
|
||||||
|
let root_div = &dom[1];
|
||||||
|
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
root_div.state.bubbled.1,
|
||||||
|
vec![
|
||||||
|
Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
|
||||||
|
Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||||
|
assert_eq!(root_div.state.pushed.1, None);
|
||||||
|
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||||
|
assert_eq!(root_div.state.node.1, vec![]);
|
||||||
|
|
||||||
|
let child_p = &dom[2];
|
||||||
|
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
|
||||||
|
assert_eq!(child_p.state.bubbled.1, Vec::new());
|
||||||
|
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
child_p.state.pushed.1,
|
||||||
|
Some(Box::new(PushedDownStateTester(
|
||||||
|
Some("div".to_string()),
|
||||||
|
None
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
child_p.state.node.1,
|
||||||
|
vec![("color".to_string(), "red".to_string())]
|
||||||
|
);
|
||||||
|
|
||||||
|
let child_h1 = &dom[3];
|
||||||
|
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
|
||||||
|
assert_eq!(child_h1.state.bubbled.1, Vec::new());
|
||||||
|
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
child_h1.state.pushed.1,
|
||||||
|
Some(Box::new(PushedDownStateTester(
|
||||||
|
Some("div".to_string()),
|
||||||
|
None
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||||
|
assert_eq!(child_h1.state.node.1, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_reduce_initally_called_minimally() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||||
|
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||||
|
|
||||||
|
dom.traverse_depth_first(|n| {
|
||||||
|
println!("{:#?}", n.state);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.traverse_depth_first(|n| {
|
||||||
|
assert_eq!(n.state.child_counter.0, 1);
|
||||||
|
assert_eq!(n.state.parent_counter.0, 1);
|
||||||
|
assert_eq!(n.state.node_counter.0, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_reduce_down_called_minimally_on_update() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||||
|
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||||
|
edits: vec![DomEdit::SetAttribute {
|
||||||
|
root: 1,
|
||||||
|
field: "width",
|
||||||
|
value: "99%",
|
||||||
|
ns: Some("style"),
|
||||||
|
}],
|
||||||
|
dirty_scopes: fxhash::FxHashSet::default(),
|
||||||
|
refs: Vec::new(),
|
||||||
|
}]);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||||
|
|
||||||
|
dom.traverse_depth_first(|n| {
|
||||||
|
// println!("{:?}", n.state);
|
||||||
|
assert_eq!(n.state.parent_counter.0, 2);
|
||||||
|
});
|
||||||
|
// panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_reduce_up_called_minimally_on_update() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||||
|
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||||
|
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||||
|
edits: vec![DomEdit::SetAttribute {
|
||||||
|
root: 4,
|
||||||
|
field: "width",
|
||||||
|
value: "99%",
|
||||||
|
ns: Some("style"),
|
||||||
|
}],
|
||||||
|
dirty_scopes: fxhash::FxHashSet::default(),
|
||||||
|
refs: Vec::new(),
|
||||||
|
}]);
|
||||||
|
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||||
|
|
||||||
|
dom.traverse_depth_first(|n| {
|
||||||
|
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -25,3 +25,4 @@ futures = "0.3.19"
|
|||||||
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
fxhash = "0.2"
|
fxhash = "0.2"
|
||||||
|
anymap = "0.12.1"
|
||||||
+16
-22
@@ -5,7 +5,6 @@ use dioxus_core::*;
|
|||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use dioxus_html::{on::*, KeyCode};
|
use dioxus_html::{on::*, KeyCode};
|
||||||
use dioxus_native_core::real_dom::{Node, RealDom};
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
@@ -15,8 +14,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use stretch2::{prelude::Layout, Stretch};
|
use stretch2::{prelude::Layout, Stretch};
|
||||||
|
|
||||||
use crate::layout::StretchLayout;
|
use crate::{Dom, Node};
|
||||||
use crate::style_attributes::StyleModifier;
|
|
||||||
|
|
||||||
// a wrapper around the input state for easier access
|
// a wrapper around the input state for easier access
|
||||||
// todo: fix loop
|
// todo: fix loop
|
||||||
@@ -166,7 +164,7 @@ impl InnerInputState {
|
|||||||
evts: &mut Vec<EventCore>,
|
evts: &mut Vec<EventCore>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
dom: &mut RealDom<StretchLayout, StyleModifier>,
|
dom: &mut Dom,
|
||||||
) {
|
) {
|
||||||
let previous_mouse = self
|
let previous_mouse = self
|
||||||
.mouse
|
.mouse
|
||||||
@@ -191,7 +189,7 @@ impl InnerInputState {
|
|||||||
previous_mouse: Option<(MouseData, Vec<u16>)>,
|
previous_mouse: Option<(MouseData, Vec<u16>)>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
dom: &mut RealDom<StretchLayout, StyleModifier>,
|
dom: &mut Dom,
|
||||||
) {
|
) {
|
||||||
struct Data<'b> {
|
struct Data<'b> {
|
||||||
new_pos: (i32, i32),
|
new_pos: (i32, i32),
|
||||||
@@ -215,8 +213,8 @@ impl InnerInputState {
|
|||||||
data: Arc<dyn Any + Send + Sync>,
|
data: Arc<dyn Any + Send + Sync>,
|
||||||
will_bubble: &mut FxHashSet<ElementId>,
|
will_bubble: &mut FxHashSet<ElementId>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
node: &Node<StretchLayout, StyleModifier>,
|
node: &Node,
|
||||||
dom: &RealDom<StretchLayout, StyleModifier>,
|
dom: &Dom,
|
||||||
) {
|
) {
|
||||||
// only trigger event if the event was not triggered already by a child
|
// only trigger event if the event was not triggered already by a child
|
||||||
if will_bubble.insert(node.id) {
|
if will_bubble.insert(node.id) {
|
||||||
@@ -261,7 +259,7 @@ impl InnerInputState {
|
|||||||
// mousemove
|
// mousemove
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mousemove") {
|
for node in dom.get_listening_sorted("mousemove") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
.old_pos
|
.old_pos
|
||||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||||
@@ -285,7 +283,7 @@ impl InnerInputState {
|
|||||||
// mouseenter
|
// mouseenter
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mouseenter") {
|
for node in dom.get_listening_sorted("mouseenter") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
.old_pos
|
.old_pos
|
||||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||||
@@ -309,7 +307,7 @@ impl InnerInputState {
|
|||||||
// mouseover
|
// mouseover
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mouseover") {
|
for node in dom.get_listening_sorted("mouseover") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
.old_pos
|
.old_pos
|
||||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||||
@@ -333,7 +331,7 @@ impl InnerInputState {
|
|||||||
// mousedown
|
// mousedown
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mousedown") {
|
for node in dom.get_listening_sorted("mousedown") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
|
||||||
if currently_contains && data.clicked {
|
if currently_contains && data.clicked {
|
||||||
@@ -353,7 +351,7 @@ impl InnerInputState {
|
|||||||
// mouseup
|
// mouseup
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mouseup") {
|
for node in dom.get_listening_sorted("mouseup") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
|
||||||
if currently_contains && data.released {
|
if currently_contains && data.released {
|
||||||
@@ -373,7 +371,7 @@ impl InnerInputState {
|
|||||||
// click
|
// click
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("click") {
|
for node in dom.get_listening_sorted("click") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
|
||||||
if currently_contains && data.released && data.mouse_data.button == 0 {
|
if currently_contains && data.released && data.mouse_data.button == 0 {
|
||||||
@@ -393,7 +391,7 @@ impl InnerInputState {
|
|||||||
// contextmenu
|
// contextmenu
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("contextmenu") {
|
for node in dom.get_listening_sorted("contextmenu") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
|
||||||
if currently_contains && data.released && data.mouse_data.button == 2 {
|
if currently_contains && data.released && data.mouse_data.button == 2 {
|
||||||
@@ -413,7 +411,7 @@ impl InnerInputState {
|
|||||||
// wheel
|
// wheel
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("wheel") {
|
for node in dom.get_listening_sorted("wheel") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
|
||||||
if let Some(w) = data.wheel_data {
|
if let Some(w) = data.wheel_data {
|
||||||
@@ -435,7 +433,7 @@ impl InnerInputState {
|
|||||||
// mouseleave
|
// mouseleave
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mouseleave") {
|
for node in dom.get_listening_sorted("mouseleave") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
.old_pos
|
.old_pos
|
||||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||||
@@ -459,7 +457,7 @@ impl InnerInputState {
|
|||||||
// mouseout
|
// mouseout
|
||||||
let mut will_bubble = FxHashSet::default();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in dom.get_listening_sorted("mouseout") {
|
for node in dom.get_listening_sorted("mouseout") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
.old_pos
|
.old_pos
|
||||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||||
@@ -522,11 +520,7 @@ impl RinkInputHandler {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_events(
|
pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
|
||||||
&self,
|
|
||||||
layout: &Stretch,
|
|
||||||
dom: &mut RealDom<StretchLayout, StyleModifier>,
|
|
||||||
) -> Vec<UserEvent> {
|
|
||||||
let mut resolved_events = Vec::new();
|
let mut resolved_events = Vec::new();
|
||||||
|
|
||||||
(*self.state).borrow_mut().update(
|
(*self.state).borrow_mut().update(
|
||||||
|
|||||||
+65
-58
@@ -1,83 +1,90 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
||||||
use dioxus_native_core::real_dom::BubbledUpState;
|
use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView};
|
||||||
use stretch2::prelude::*;
|
use stretch2::prelude::*;
|
||||||
|
|
||||||
/// the size
|
|
||||||
#[derive(Clone, PartialEq, Default, Debug)]
|
#[derive(Clone, PartialEq, Default, Debug)]
|
||||||
pub struct StretchLayout {
|
pub struct StretchLayout {
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
pub node: Option<Node>,
|
pub node: Option<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BubbledUpState for StretchLayout {
|
impl ChildDepState for StretchLayout {
|
||||||
type Ctx = Stretch;
|
type Ctx = Rc<RefCell<Stretch>>;
|
||||||
|
type DepState = Self;
|
||||||
|
// todo: update mask
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true);
|
||||||
|
|
||||||
/// Setup the layout
|
/// Setup the layout
|
||||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx)
|
fn reduce<'a>(
|
||||||
|
&mut self,
|
||||||
|
node: NodeView,
|
||||||
|
children: impl Iterator<Item = &'a Self::DepState>,
|
||||||
|
ctx: &Self::Ctx,
|
||||||
|
) -> bool
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a Self>,
|
Self::DepState: 'a,
|
||||||
Self: 'a,
|
|
||||||
{
|
{
|
||||||
match vnode {
|
let mut stretch = ctx.borrow_mut();
|
||||||
VNode::Text(t) => {
|
if let Some(text) = node.text() {
|
||||||
let char_len = t.text.chars().count();
|
let char_len = text.chars().count();
|
||||||
|
|
||||||
let style = Style {
|
let style = Style {
|
||||||
size: Size {
|
size: Size {
|
||||||
// characters are 1 point tall
|
// characters are 1 point tall
|
||||||
height: Dimension::Points(1.0),
|
height: Dimension::Points(1.0),
|
||||||
|
|
||||||
// text is as long as it is declared
|
// text is as long as it is declared
|
||||||
width: Dimension::Points(char_len as f32),
|
width: Dimension::Points(char_len as f32),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(n) = self.node {
|
if let Some(n) = self.node {
|
||||||
if self.style != style {
|
if self.style != style {
|
||||||
stretch.set_style(n, style).unwrap();
|
stretch.set_style(n, style).unwrap();
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.node = Some(stretch.new_node(style, &[]).unwrap());
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
self.style = style;
|
self.node = Some(stretch.new_node(style, &[]).unwrap());
|
||||||
}
|
}
|
||||||
VNode::Element(el) => {
|
|
||||||
// gather up all the styles from the attribute list
|
|
||||||
let mut style = Style::default();
|
|
||||||
|
|
||||||
for &Attribute { name, value, .. } in el.attributes {
|
self.style = style;
|
||||||
apply_layout_attributes(name, value, &mut style);
|
} else {
|
||||||
}
|
// gather up all the styles from the attribute list
|
||||||
|
let mut style = Style::default();
|
||||||
|
|
||||||
// the root node fills the entire area
|
for &Attribute { name, value, .. } in node.attributes() {
|
||||||
if el.id.get() == Some(ElementId(0)) {
|
apply_layout_attributes(name, value, &mut style);
|
||||||
apply_layout_attributes("width", "100%", &mut style);
|
|
||||||
apply_layout_attributes("height", "100%", &mut style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all direct nodes as our children
|
|
||||||
let mut child_layout = vec![];
|
|
||||||
for l in children {
|
|
||||||
child_layout.push(l.node.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(n) = self.node {
|
|
||||||
if stretch.children(n).unwrap() != child_layout {
|
|
||||||
stretch.set_children(n, &child_layout).unwrap();
|
|
||||||
}
|
|
||||||
if self.style != style {
|
|
||||||
stretch.set_style(n, style).unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.node = Some(stretch.new_node(style, &child_layout).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.style = style;
|
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
|
// the root node fills the entire area
|
||||||
|
if node.id() == ElementId(0) {
|
||||||
|
apply_layout_attributes("width", "100%", &mut style);
|
||||||
|
apply_layout_attributes("height", "100%", &mut style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all direct nodes as our children
|
||||||
|
let mut child_layout = vec![];
|
||||||
|
for l in children {
|
||||||
|
child_layout.push(l.node.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(n) = self.node {
|
||||||
|
if stretch.children(n).unwrap() != child_layout {
|
||||||
|
stretch.set_children(n, &child_layout).unwrap();
|
||||||
|
}
|
||||||
|
if self.style != style {
|
||||||
|
stretch.set_style(n, style).unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.node = Some(stretch.new_node(style, &child_layout).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.style = style;
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+33
-25
@@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use anymap::AnyMap;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
|
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
|
||||||
execute,
|
execute,
|
||||||
@@ -6,12 +7,14 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use dioxus_native_core::real_dom::RealDom;
|
use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*};
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
pin_mut, StreamExt,
|
pin_mut, StreamExt,
|
||||||
};
|
};
|
||||||
use layout::StretchLayout;
|
use layout::StretchLayout;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
use stretch2::{prelude::Size, Stretch};
|
use stretch2::{prelude::Size, Stretch};
|
||||||
use style_attributes::StyleModifier;
|
use style_attributes::StyleModifier;
|
||||||
@@ -27,7 +30,18 @@ mod widget;
|
|||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use hooks::*;
|
pub use hooks::*;
|
||||||
pub use render::*;
|
|
||||||
|
type Dom = RealDom<NodeState>;
|
||||||
|
type Node = dioxus_native_core::real_dom::Node<NodeState>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, State, Default)]
|
||||||
|
struct NodeState {
|
||||||
|
#[child_dep_state(StretchLayout, RefCell<Stretch>)]
|
||||||
|
layout: StretchLayout,
|
||||||
|
// depends on attributes, the C component of it's parent and a u8 context
|
||||||
|
#[parent_dep_state(StyleModifier)]
|
||||||
|
style: StyleModifier,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TuiContext {
|
pub struct TuiContext {
|
||||||
@@ -70,13 +84,13 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
|||||||
cx.provide_root_context(state);
|
cx.provide_root_context(state);
|
||||||
cx.provide_root_context(TuiContext { tx: event_tx_clone });
|
cx.provide_root_context(TuiContext { tx: event_tx_clone });
|
||||||
|
|
||||||
let mut rdom: RealDom<StretchLayout, StyleModifier> = RealDom::new();
|
let mut rdom: Dom = RealDom::new();
|
||||||
let mutations = dom.rebuild();
|
let mutations = dom.rebuild();
|
||||||
let to_update = rdom.apply_mutations(vec![mutations]);
|
let to_update = rdom.apply_mutations(vec![mutations]);
|
||||||
let mut stretch = Stretch::new();
|
let stretch = Rc::new(RefCell::new(Stretch::new()));
|
||||||
let _to_rerender = rdom
|
let mut any_map = AnyMap::new();
|
||||||
.update_state(&dom, to_update, &mut stretch, &mut ())
|
any_map.insert(stretch.clone());
|
||||||
.unwrap();
|
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
|
||||||
|
|
||||||
render_vdom(
|
render_vdom(
|
||||||
&mut dom,
|
&mut dom,
|
||||||
@@ -95,8 +109,8 @@ fn render_vdom(
|
|||||||
mut event_reciever: UnboundedReceiver<InputEvent>,
|
mut event_reciever: UnboundedReceiver<InputEvent>,
|
||||||
handler: RinkInputHandler,
|
handler: RinkInputHandler,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
mut rdom: RealDom<StretchLayout, StyleModifier>,
|
mut rdom: Dom,
|
||||||
mut stretch: Stretch,
|
stretch: Rc<RefCell<Stretch>>,
|
||||||
mut register_event: impl FnMut(crossterm::event::Event),
|
mut register_event: impl FnMut(crossterm::event::Event),
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
@@ -114,7 +128,7 @@ fn render_vdom(
|
|||||||
terminal.clear().unwrap();
|
terminal.clear().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
let to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
||||||
let mut resized = true;
|
let mut resized = true;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -131,15 +145,11 @@ fn render_vdom(
|
|||||||
|
|
||||||
if !to_rerender.is_empty() || resized {
|
if !to_rerender.is_empty() || resized {
|
||||||
resized = false;
|
resized = false;
|
||||||
fn resize(
|
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
|
||||||
dims: Rect,
|
|
||||||
stretch: &mut Stretch,
|
|
||||||
rdom: &RealDom<StretchLayout, StyleModifier>,
|
|
||||||
) {
|
|
||||||
let width = dims.width;
|
let width = dims.width;
|
||||||
let height = dims.height;
|
let height = dims.height;
|
||||||
let root_id = rdom.root_id();
|
let root_id = rdom.root_id();
|
||||||
let root_node = rdom[root_id].up_state.node.unwrap();
|
let root_node = rdom[root_id].state.layout.node.unwrap();
|
||||||
|
|
||||||
stretch
|
stretch
|
||||||
.compute_layout(
|
.compute_layout(
|
||||||
@@ -154,9 +164,9 @@ fn render_vdom(
|
|||||||
if let Some(terminal) = &mut terminal {
|
if let Some(terminal) = &mut terminal {
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
// size is guaranteed to not change when rendering
|
// size is guaranteed to not change when rendering
|
||||||
resize(frame.size(), &mut stretch, &rdom);
|
resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
|
||||||
let root = &rdom[rdom.root_id()];
|
let root = &rdom[rdom.root_id()];
|
||||||
render::render_vnode(frame, &stretch, &rdom, &root, cfg);
|
render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg);
|
||||||
})?;
|
})?;
|
||||||
} else {
|
} else {
|
||||||
resize(
|
resize(
|
||||||
@@ -166,7 +176,7 @@ fn render_vdom(
|
|||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
},
|
},
|
||||||
&mut stretch,
|
&mut stretch.borrow_mut(),
|
||||||
&rdom,
|
&rdom,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -207,7 +217,7 @@ fn render_vdom(
|
|||||||
|
|
||||||
{
|
{
|
||||||
// resolve events before rendering
|
// resolve events before rendering
|
||||||
let evts = handler.get_events(&stretch, &mut rdom);
|
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
|
||||||
for e in evts {
|
for e in evts {
|
||||||
vdom.handle_message(SchedulerMsg::Event(e));
|
vdom.handle_message(SchedulerMsg::Event(e));
|
||||||
}
|
}
|
||||||
@@ -215,11 +225,9 @@ fn render_vdom(
|
|||||||
// updates the dom's nodes
|
// updates the dom's nodes
|
||||||
let to_update = rdom.apply_mutations(mutations);
|
let to_update = rdom.apply_mutations(mutations);
|
||||||
// update the style and layout
|
// update the style and layout
|
||||||
to_rerender.extend(
|
let mut any_map = AnyMap::new();
|
||||||
rdom.update_state(vdom, to_update, &mut stretch, &mut ())
|
any_map.insert(stretch.clone());
|
||||||
.unwrap()
|
let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap();
|
||||||
.iter(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-18
@@ -1,8 +1,4 @@
|
|||||||
use crate::layout::StretchLayout;
|
use dioxus_native_core::layout_attributes::UnitSystem;
|
||||||
use dioxus_native_core::{
|
|
||||||
layout_attributes::UnitSystem,
|
|
||||||
real_dom::{Node, RealDom},
|
|
||||||
};
|
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
use stretch2::{
|
use stretch2::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
@@ -15,16 +11,16 @@ use crate::{
|
|||||||
style::{RinkColor, RinkStyle},
|
style::{RinkColor, RinkStyle},
|
||||||
style_attributes::{BorderEdge, BorderStyle},
|
style_attributes::{BorderEdge, BorderStyle},
|
||||||
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
|
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
|
||||||
Config, StyleModifier,
|
Config, Dom, Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||||
|
|
||||||
pub fn render_vnode(
|
pub(crate) fn render_vnode(
|
||||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
rdom: &RealDom<StretchLayout, StyleModifier>,
|
rdom: &Dom,
|
||||||
node: &Node<StretchLayout, StyleModifier>,
|
node: &Node,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
) {
|
) {
|
||||||
use dioxus_native_core::real_dom::NodeType;
|
use dioxus_native_core::real_dom::NodeType;
|
||||||
@@ -33,7 +29,7 @@ pub fn render_vnode(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||||
|
|
||||||
let Point { x, y } = location;
|
let Point { x, y } = location;
|
||||||
let Size { width, height } = size;
|
let Size { width, height } = size;
|
||||||
@@ -59,7 +55,7 @@ pub fn render_vnode(
|
|||||||
|
|
||||||
let label = Label {
|
let label = Label {
|
||||||
text,
|
text,
|
||||||
style: node.down_state.style,
|
style: node.state.style.style,
|
||||||
};
|
};
|
||||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||||
|
|
||||||
@@ -84,7 +80,7 @@ pub fn render_vnode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
impl RinkWidget for &Node {
|
||||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||||
use tui::symbols::line::*;
|
use tui::symbols::line::*;
|
||||||
|
|
||||||
@@ -268,14 +264,14 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
|||||||
for x in area.left()..area.right() {
|
for x in area.left()..area.right() {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
let mut new_cell = RinkCell::default();
|
let mut new_cell = RinkCell::default();
|
||||||
if let Some(c) = self.down_state.style.bg {
|
if let Some(c) = self.state.style.style.bg {
|
||||||
new_cell.bg = c;
|
new_cell.bg = c;
|
||||||
}
|
}
|
||||||
buf.set(x, y, new_cell);
|
buf.set(x, y, new_cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let borders = &self.down_state.modifier.borders;
|
let borders = &self.state.style.modifier.borders;
|
||||||
|
|
||||||
let last_edge = &borders.left;
|
let last_edge = &borders.left;
|
||||||
let current_edge = &borders.top;
|
let current_edge = &borders.top;
|
||||||
@@ -292,7 +288,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
|||||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||||
];
|
];
|
||||||
let color = current_edge.color.or(self.down_state.style.fg);
|
let color = current_edge.color.or(self.state.style.style.fg);
|
||||||
let mut new_cell = RinkCell::default();
|
let mut new_cell = RinkCell::default();
|
||||||
if let Some(c) = color {
|
if let Some(c) = color {
|
||||||
new_cell.fg = c;
|
new_cell.fg = c;
|
||||||
@@ -327,7 +323,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
|||||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||||
];
|
];
|
||||||
let color = current_edge.color.or(self.down_state.style.fg);
|
let color = current_edge.color.or(self.state.style.style.fg);
|
||||||
let mut new_cell = RinkCell::default();
|
let mut new_cell = RinkCell::default();
|
||||||
if let Some(c) = color {
|
if let Some(c) = color {
|
||||||
new_cell.fg = c;
|
new_cell.fg = c;
|
||||||
@@ -362,7 +358,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
|||||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||||
];
|
];
|
||||||
let color = current_edge.color.or(self.down_state.style.fg);
|
let color = current_edge.color.or(self.state.style.style.fg);
|
||||||
let mut new_cell = RinkCell::default();
|
let mut new_cell = RinkCell::default();
|
||||||
if let Some(c) = color {
|
if let Some(c) = color {
|
||||||
new_cell.fg = c;
|
new_cell.fg = c;
|
||||||
@@ -397,7 +393,7 @@ impl RinkWidget for &Node<StretchLayout, StyleModifier> {
|
|||||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||||
];
|
];
|
||||||
let color = current_edge.color.or(self.down_state.style.fg);
|
let color = current_edge.color.or(self.state.style.style.fg);
|
||||||
let mut new_cell = RinkCell::default();
|
let mut new_cell = RinkCell::default();
|
||||||
if let Some(c) = color {
|
if let Some(c) = color {
|
||||||
new_cell.fg = c;
|
new_cell.fg = c;
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
- [ ] pub aspect_ratio: Number,
|
- [ ] pub aspect_ratio: Number,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use dioxus_core::{Attribute, VNode};
|
use dioxus_core::Attribute;
|
||||||
use dioxus_native_core::{
|
use dioxus_native_core::{
|
||||||
layout_attributes::{parse_value, UnitSystem},
|
layout_attributes::{parse_value, UnitSystem},
|
||||||
real_dom::PushedDownState,
|
state::{AttributeMask, NodeMask, NodeView, ParentDepState},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::style::{RinkColor, RinkStyle};
|
use crate::style::{RinkColor, RinkStyle};
|
||||||
@@ -43,18 +43,22 @@ pub struct StyleModifier {
|
|||||||
pub modifier: TuiModifier,
|
pub modifier: TuiModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PushedDownState for StyleModifier {
|
impl ParentDepState for StyleModifier {
|
||||||
type Ctx = ();
|
type Ctx = ();
|
||||||
|
type DepState = Self;
|
||||||
|
// todo: seperate each attribute into it's own class
|
||||||
|
const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false);
|
||||||
|
|
||||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) {
|
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
|
||||||
*self = StyleModifier::default();
|
*self = StyleModifier::default();
|
||||||
if parent.is_some() {
|
if parent.is_some() {
|
||||||
self.style.fg = None;
|
self.style.fg = None;
|
||||||
}
|
}
|
||||||
if let VNode::Element(el) = vnode {
|
|
||||||
// handle text modifier elements
|
// handle text modifier elements
|
||||||
if el.namespace.is_none() {
|
if node.namespace().is_none() {
|
||||||
match el.tag {
|
if let Some(tag) = node.tag() {
|
||||||
|
match tag {
|
||||||
"b" => apply_style_attributes("font-weight", "bold", self),
|
"b" => apply_style_attributes("font-weight", "bold", self),
|
||||||
"strong" => apply_style_attributes("font-weight", "bold", self),
|
"strong" => apply_style_attributes("font-weight", "bold", self),
|
||||||
"u" => apply_style_attributes("text-decoration", "underline", self),
|
"u" => apply_style_attributes("text-decoration", "underline", self),
|
||||||
@@ -68,11 +72,11 @@ impl PushedDownState for StyleModifier {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// gather up all the styles from the attribute list
|
// gather up all the styles from the attribute list
|
||||||
for &Attribute { name, value, .. } in el.attributes {
|
for &Attribute { name, value, .. } in node.attributes() {
|
||||||
apply_style_attributes(name, value, self);
|
apply_style_attributes(name, value, self);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the text styling from the parent element
|
// keep the text styling from the parent element
|
||||||
@@ -81,6 +85,7 @@ impl PushedDownState for StyleModifier {
|
|||||||
new_style.bg = self.style.bg;
|
new_style.bg = self.style.bg;
|
||||||
self.style = new_style;
|
self.style = new_style;
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user