From 3b06059834b18619401c4dced3aa12ce937fd294 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Apr 2022 11:39:38 -0500 Subject: [PATCH] fix rendering --- benches/tui_update.rs | 74 +++- packages/native-core-macro/src/lib.rs | 39 +- .../src/lib.rs~Stashed changes | 368 ++++++++++++++++++ packages/native-core/src/layout_attributes.rs | 2 + packages/native-core/src/real_dom.rs | 5 +- packages/native-core/src/state.rs | 11 +- packages/native-core/tests/parse.rs | 6 - packages/tui/src/layout.rs | 30 +- packages/tui/src/lib.rs | 5 +- packages/tui/src/render.rs | 1 + packages/tui/src/style_attributes.rs | 33 +- 11 files changed, 509 insertions(+), 65 deletions(-) create mode 100644 packages/native-core-macro/src/lib.rs~Stashed changes diff --git a/benches/tui_update.rs b/benches/tui_update.rs index cbc8ca467..5a3a70b66 100644 --- a/benches/tui_update.rs +++ b/benches/tui_update.rs @@ -10,50 +10,64 @@ fn tui_update(c: &mut Criterion) { let mut group = c.benchmark_group("Update boxes"); // We can also use loops to define multiple benchmarks, even over multiple dimensions. - for size in 1..=6 { - let parameter_string = format!("{}", 5 * size); + for size in 1..=8u32 { + let parameter_string = format!("{}", (3 * size).pow(2)); group.bench_with_input( BenchmarkId::new("size", parameter_string), &size, |b, size| { b.iter(|| match size { 1 => dioxus::tui::launch_cfg( - app5, + app3, Config { headless: true, ..Default::default() }, ), 2 => dioxus::tui::launch_cfg( - app10, + app6, Config { headless: true, ..Default::default() }, ), 3 => dioxus::tui::launch_cfg( - app15, + app9, Config { headless: true, ..Default::default() }, ), 4 => dioxus::tui::launch_cfg( - app20, + app12, Config { headless: true, ..Default::default() }, ), 5 => dioxus::tui::launch_cfg( - app25, + app15, Config { headless: true, ..Default::default() }, ), 6 => dioxus::tui::launch_cfg( - app30, + app18, + Config { + headless: true, + ..Default::default() + }, + ), + 7 => dioxus::tui::launch_cfg( + app21, + Config { + headless: true, + ..Default::default() + }, + ), + 8 => dioxus::tui::launch_cfg( + app24, Config { headless: true, ..Default::default() @@ -157,25 +171,49 @@ fn Grid(cx: Scope) -> Element { }) } -fn app5(cx: Scope) -> Element { +fn app3(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: 5, + size: 3, } } }) } -fn app10(cx: Scope) -> Element { +fn app6(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: 10, + size: 6, + } + } + }) +} + +fn app9(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 9, + } + } + }) +} + +fn app12(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 12, } } }) @@ -193,37 +231,37 @@ fn app15(cx: Scope) -> Element { }) } -fn app20(cx: Scope) -> Element { +fn app18(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: 20, + size: 18, } } }) } -fn app25(cx: Scope) -> Element { +fn app21(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: 25, + size: 21, } } }) } -fn app30(cx: Scope) -> Element { +fn app24(cx: Scope) -> Element { cx.render(rsx! { div{ width: "100%", height: "100%", Grid{ - size: 30, + size: 24, } } }) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index cc99ee746..084349831 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -4,12 +4,35 @@ use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{ self, - parse::{Parse, ParseStream}, + parse::{Parse, ParseStream, Result}, punctuated::Punctuated, token::Paren, - Field, Ident, Token, Type, TypeTuple, + Field, Ident, LitStr, Token, Type, TypeTuple, }; +// struct StrSlice { +// init: LitStr, +// } + +// impl Parse for StrSlice { +// fn parse(input: ParseStream) -> Result { +// input.parse::()?; +// let str: LitStr = input.parse()?; +// let name: Ident = input.parse()?; +// input.parse::()?; +// input.parse::()?; +// Ok(LazyStatic { +// visibility, +// name, +// ty, +// init, +// }) +// } +// } + +// #[proc_macro] +// pub fn sorted_str_slice(input: TokenStream) -> TokenStream {} + #[derive(PartialEq)] enum DepKind { NodeDepState, @@ -105,7 +128,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { 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{ + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, ctx: &anymap::AnyMap) -> bool{ use dioxus_native_core::state::NodeDepState as _; // println!("called update_node_dep_state with ty: {:?}", ty); if false { @@ -117,7 +140,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - 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{ + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, 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 { @@ -129,7 +152,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - 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{ + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ use dioxus_native_core::state::ChildDepState as _; // println!("called update_child_dep_state with ty: {:?}", ty); if false { @@ -205,7 +228,7 @@ struct DepTypes { } impl Parse for DepTypes { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> Result { let dep_ty = input.parse().ok(); let comma: Option = input.parse().ok(); let ctx_ty = input.parse().ok(); @@ -221,7 +244,7 @@ struct NodeDepTypes { } impl Parse for NodeDepTypes { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> Result { let ctx_ty = input.parse().ok(); Ok(Self { ctx_ty }) } @@ -307,7 +330,7 @@ impl<'a> StateMember<'a> { quote! {&()} }; let ty = &self.mem.ty; - let node_view = quote!(NodeView::new(node, #ty::NODE_MASK)); + let node_view = quote!(NodeView::new(node, #ty::NODE_MASK, vdom)); if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { match self.dep_kind { DepKind::NodeDepState => { diff --git a/packages/native-core-macro/src/lib.rs~Stashed changes b/packages/native-core-macro/src/lib.rs~Stashed changes new file mode 100644 index 000000000..084349831 --- /dev/null +++ b/packages/native-core-macro/src/lib.rs~Stashed changes @@ -0,0 +1,368 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, + parse::{Parse, ParseStream, Result}, + punctuated::Punctuated, + token::Paren, + Field, Ident, LitStr, Token, Type, TypeTuple, +}; + +// struct StrSlice { +// init: LitStr, +// } + +// impl Parse for StrSlice { +// fn parse(input: ParseStream) -> Result { +// input.parse::()?; +// let str: LitStr = input.parse()?; +// let name: Ident = input.parse()?; +// input.parse::()?; +// input.parse::()?; +// Ok(LazyStatic { +// visibility, +// name, +// ty, +// init, +// }) +// } +// } + +// #[proc_macro] +// pub fn sorted_str_slice(input: TokenStream) -> TokenStream {} + +#[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>, vdom: &'a dioxus_core::VirtualDom, 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>, vdom: &'a dioxus_core::VirtualDom, 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>, vdom: &'a dioxus_core::VirtualDom, 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{ + 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{ + 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{ + 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, +} + +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>, +} + +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, + dep_ty: Option, +} + +impl Parse for DepTypes { + fn parse(input: ParseStream) -> Result { + let dep_ty = input.parse().ok(); + let comma: Option = input.parse().ok(); + let ctx_ty = input.parse().ok(); + Ok(Self { + ctx_ty: comma.and(ctx_ty), + dep_ty, + }) + } +} + +struct NodeDepTypes { + ctx_ty: Option, +} + +impl Parse for NodeDepTypes { + fn parse(input: ParseStream) -> Result { + let ctx_ty = input.parse().ok(); + Ok(Self { ctx_ty }) + } +} + +impl From 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 { + 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, +} + +impl<'a> StateMember<'a> { + fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { + 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::().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, vdom)); + 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 + }) + } +} diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index 45461a3f9..b675183a7 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -29,6 +29,8 @@ - [ ] pub aspect_ratio: Number, */ +// align-content,align-items,align-self,animation,animation-delay,animation-direction,animation-duration,animation-fill-mode,animation-iteration-count,animation-name,animation-play-state,animation-timing-function,backface-visibility,border,border-bottom,border-bottom-color,border-bottom-left-radius,border-bottom-right-radius,border-bottom-style,border-bottom-width,border-collapse,border-color,border-image,border-image-outset,border-image-repeat,border-image-slice,border-image-source,border-image-width,border-left,border-left-color,border-left-style,border-left-width,border-radius,border-right,border-right-color,border-right-style,border-right-width,border-spacing,border-style,border-top,border-top-color,border-top-left-radius,border-top-right-radius,border-top-style,border-top-width,border-width,bottom,box-shadow,box-sizing,caption-side,clear,clip,column-count,column-fill,column-gap,column-rule,column-rule-color,column-rule-style,column-rule-width,column-span,column-width,columns,content,counter-increment,counter-reset,cursor,direction,ltr,rtl,display,empty-cells,flex,flex-basis,flex-direction,flex-flow,flex-grow,flex-shrink,flex-wrap,float,height,justify-content,flex-start,flex-end,center,space-between,space-around,space-evenly,left,letter-spacing,line-height,list-style,list-style-image,list-style-position,list-style-type,margin,margin-bottom,margin-left,margin-right,margin-top,max-height,max-width,min-height,min-width,opacity,order,outline,outline-color,outline-offset,outline-style,outline-width,overflow,overflow-x,overflow-y,padding,padding-bottom,padding-left,padding-right,padding-top,page-break-after,page-break-before,page-break-inside,perspective,perspective-origin,position,static,relative,fixed,absolute,sticky,pointer-events,quotes,resize,right,tab-size,table-layout,top,transform,transform-origin,transform-style,transition,transition-delay,transition-duration,transition-property,transition-timing-function,vertical-align,visibility,white-space,width,word-break,word-spacing,word-wrap,z-index + use stretch2::{prelude::*, style::PositionType}; /// applies the entire html namespace defined in dioxus-html diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 1c25806c3..8c2a05e9f 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -301,7 +301,7 @@ impl RealDom { 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); + changed |= node.state.update_node_dep_state(ty, vnode, vdom, &ctx); } if changed { to_rerender.insert(node_ref.id); @@ -328,7 +328,7 @@ impl RealDom { let vnode = node.element(vdom); if node .state - .update_child_dep_state(ty, vnode, &children_state, &ctx) + .update_child_dep_state(ty, vnode, vdom, &children_state, &ctx) { changed.push(ty); } @@ -387,6 +387,7 @@ impl RealDom { if state.update_parent_dep_state( ty, vnode, + vdom, parent.filter(|n| n.id.0 != 0).map(|n| &n.state), &ctx, ) { diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 1e7135580..13397837a 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,7 +1,7 @@ use std::{any::TypeId, fmt::Debug}; use anymap::AnyMap; -use dioxus_core::{Attribute, ElementId, VElement, VNode, VText}; +use dioxus_core::{Attribute, ElementId, VElement, VNode, VText, VirtualDom}; pub(crate) fn union_ordered_iter( s_iter: impl Iterator, @@ -40,7 +40,11 @@ pub struct NodeView<'a> { mask: NodeMask, } impl<'a> NodeView<'a> { - pub fn new(vnode: &'a VNode<'a>, view: NodeMask) -> Self { + pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self { + if let VNode::Component(sc) = vnode { + let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); + vnode = scope.root_node(); + } Self { inner: vnode, mask: view, @@ -284,6 +288,7 @@ pub trait State: Default + Clone { &'a mut self, ty: TypeId, node: &'a VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -293,6 +298,7 @@ pub trait State: Default + Clone { &'a mut self, ty: TypeId, node: &'a VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, parent: Option<&Self>, ctx: &AnyMap, ) -> bool; @@ -303,6 +309,7 @@ pub trait State: Default + Clone { &'a mut self, ty: TypeId, node: &'a VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, children: &[&Self], ctx: &AnyMap, ) -> bool; diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index a0bbd4f27..a035faf4b 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -14,12 +14,6 @@ struct Z { z: C, } -// struct Z { -// x: A, -// y: B, -// z: C, -// } - use dioxus_native_core::state::NodeDepState; #[derive(Default, Clone)] diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 5924a0fd1..f2a26c65e 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -28,11 +28,13 @@ impl ChildDepState for StretchLayout { where Self::DepState: 'a, { + let mut changed = false; let mut stretch = ctx.borrow_mut(); + let mut style = Style::default(); if let Some(text) = node.text() { let char_len = text.chars().count(); - let style = Style { + style = Style { size: Size { // characters are 1 point tall height: Dimension::Points(1.0), @@ -42,7 +44,6 @@ impl ChildDepState for StretchLayout { }, ..Default::default() }; - if let Some(n) = self.node { if self.style != style { stretch.set_style(n, style).unwrap(); @@ -50,12 +51,8 @@ impl ChildDepState for StretchLayout { } else { self.node = Some(stretch.new_node(style, &[]).unwrap()); } - - self.style = style; } else { // gather up all the styles from the attribute list - let mut style = Style::default(); - for &Attribute { name, value, .. } in node.attributes() { apply_layout_attributes(name, value, &mut style); } @@ -73,18 +70,27 @@ impl ChildDepState for StretchLayout { } 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.node = Some(stretch.new_node(style, &[]).unwrap()); } - + if let Some(n) = self.node { + if self.style != style { + stretch.set_style(n, style).unwrap(); + } + if stretch.children(n).unwrap() != child_layout { + stretch.set_children(n, &child_layout).unwrap(); + } + } else { + self.node = Some(stretch.new_node(style, &[]).unwrap()); + } + } + if self.style != style { + changed = true; self.style = style; } - true + changed } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 872356efb..2c1844eb5 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -148,8 +148,7 @@ fn render_vdom( fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) { let width = dims.width; let height = dims.height; - let root_id = rdom.root_id(); - let root_node = rdom[root_id].state.layout.node.unwrap(); + let root_node = rdom[0].state.layout.node.unwrap(); stretch .compute_layout( @@ -165,7 +164,7 @@ fn render_vdom( terminal.draw(|frame| { // size is guaranteed to not change when rendering resize(frame.size(), &mut stretch.borrow_mut(), &rdom); - let root = &rdom[rdom.root_id()]; + let root = &rdom[0]; render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg); })?; } else { diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index e3f03f0d8..4a69b7901 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -30,6 +30,7 @@ pub(crate) fn render_vnode( } let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap(); + // println!("rendering {node:?} {location:?} {size:?}"); let Point { x, y } = location; let Size { width, height } = size; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index b49707668..b676d3409 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -50,22 +50,22 @@ impl ParentDepState for StyleModifier { const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { - *self = StyleModifier::default(); + let mut new = StyleModifier::default(); if parent.is_some() { - self.style.fg = None; + new.style.fg = None; } // handle text modifier elements if node.namespace().is_none() { if let Some(tag) = node.tag() { match tag { - "b" => apply_style_attributes("font-weight", "bold", self), - "strong" => apply_style_attributes("font-weight", "bold", self), - "u" => apply_style_attributes("text-decoration", "underline", self), - "ins" => apply_style_attributes("text-decoration", "underline", self), - "del" => apply_style_attributes("text-decoration", "line-through", self), - "i" => apply_style_attributes("font-style", "italic", self), - "em" => apply_style_attributes("font-style", "italic", self), + "b" => apply_style_attributes("font-weight", "bold", &mut new), + "strong" => apply_style_attributes("font-weight", "bold", &mut new), + "u" => apply_style_attributes("text-decoration", "underline", &mut new), + "ins" => apply_style_attributes("text-decoration", "underline", &mut new), + "del" => apply_style_attributes("text-decoration", "line-through", &mut new), + "i" => apply_style_attributes("font-style", "italic", &mut new), + "em" => apply_style_attributes("font-style", "italic", &mut new), "mark" => { apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self) } @@ -76,16 +76,21 @@ impl ParentDepState for StyleModifier { // gather up all the styles from the attribute list for &Attribute { name, value, .. } in node.attributes() { - apply_style_attributes(name, value, self); + apply_style_attributes(name, value, &mut new); } // keep the text styling from the parent element if let Some(parent) = parent { - let mut new_style = self.style.merge(parent.style); - new_style.bg = self.style.bg; - self.style = new_style; + let mut new_style = new.style.merge(parent.style); + new_style.bg = new.style.bg; + new.style = new_style; + } + if &mut new != self { + *self = new; + true + } else { + false } - true } }