From 499971e9b3a9cab21e0aec1864177d1840456de6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 18 Mar 2022 10:43:43 -0500 Subject: [PATCH 01/33] wip lazy layout --- packages/native-core/Cargo.toml | 17 + packages/native-core/src/lib.rs | 517 ++++++++++++++ packages/tui/Cargo.toml | 3 +- packages/tui/src/attributes.rs | 968 -------------------------- packages/tui/src/hooks.rs | 29 +- packages/tui/src/layout.rs | 174 ++--- packages/tui/src/layout_attributes.rs | 643 +++++++++++++++++ packages/tui/src/lib.rs | 87 ++- packages/tui/src/render.rs | 78 +-- packages/tui/src/style.rs | 88 +-- packages/tui/src/style_attributes.rs | 582 ++++++++++++++++ packages/tui/src/utils.rs | 83 +++ packages/tui/src/widget.rs | 8 +- packages/tui/tests/relayout.rs | 63 ++ 14 files changed, 2117 insertions(+), 1223 deletions(-) create mode 100644 packages/native-core/Cargo.toml create mode 100644 packages/native-core/src/lib.rs delete mode 100644 packages/tui/src/attributes.rs create mode 100644 packages/tui/src/layout_attributes.rs create mode 100644 packages/tui/src/style_attributes.rs create mode 100644 packages/tui/src/utils.rs create mode 100644 packages/tui/tests/relayout.rs diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml new file mode 100644 index 000000000..05ed52a06 --- /dev/null +++ b/packages/native-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dioxus-native-core" +version = "0.2.0" +edition = "2021" +license = "MIT/Apache-2.0" +authors = ["@dementhos"] +description = "TUI-based renderer for Dioxus" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" + +[dependencies] +dioxus-core = { path = "../core", version = "^0.2.0" } +dioxus-html = { path = "../html", version = "^0.2.0" } +dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } + +stretch2 = "0.4.1" +smallvec = "1.6" diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs new file mode 100644 index 000000000..a1eb805a2 --- /dev/null +++ b/packages/native-core/src/lib.rs @@ -0,0 +1,517 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; + +/// A tree that can sync with dioxus mutations backed by a hashmap. +/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. +/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. +#[derive(Debug)] + +pub struct Tree { + pub root: usize, + pub nodes: Vec>>, + pub listeners: HashMap>, +} + +impl Tree { + pub fn new() -> Tree { + Tree { + root: 0, + nodes: { + let mut v = Vec::new(); + v.push(Some(TreeNode::new( + 0, + TreeNodeType::Element { + tag: "Root".to_string(), + namespace: Some("Root"), + children: Vec::new(), + }, + ))); + v + }, + listeners: HashMap::new(), + } + } + + /// Updates the tree, up and down state and return a set of nodes that were updated + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec { + let mut nodes_updated = Vec::new(); + for mutations in mutations_vec { + let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new(); + for e in mutations.edits { + use dioxus_core::DomEdit::*; + match e { + PushRoot { root } => node_stack.push(root as usize), + AppendChildren { many } => { + let target = if node_stack.len() >= many as usize + 1 { + *node_stack + .get(node_stack.len() - (many as usize + 1)) + .unwrap() + } else { + 0 + }; + for ns in node_stack.drain(node_stack.len() - many as usize..).rev() { + self.link_child(ns, target).unwrap(); + nodes_updated.push(ns); + } + } + ReplaceWith { root, m } => { + let root = self.remove(root as usize).unwrap(); + let target = root.parent.unwrap().0; + for ns in node_stack.drain(0..m as usize) { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + InsertAfter { root, n } => { + let target = self.get(root as usize).parent.unwrap().0; + for ns in node_stack.drain(0..n as usize) { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + InsertBefore { root, n } => { + let target = self.get(root as usize).parent.unwrap().0; + for ns in node_stack.drain(0..n as usize) { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + Remove { root } => { + if let Some(parent) = self.get(root as usize).parent { + nodes_updated.push(parent.0); + } + self.remove(root as usize).unwrap(); + } + CreateTextNode { root, text } => { + let n = TreeNode::new( + root, + TreeNodeType::Text { + text: text.to_string(), + }, + ); + self.insert(n); + node_stack.push(root as usize) + } + CreateElement { root, tag } => { + let n = TreeNode::new( + root, + TreeNodeType::Element { + tag: tag.to_string(), + namespace: None, + children: Vec::new(), + }, + ); + self.insert(n); + node_stack.push(root as usize) + } + CreateElementNs { root, tag, ns } => { + let n = TreeNode::new( + root, + TreeNodeType::Element { + tag: tag.to_string(), + namespace: Some(ns), + children: Vec::new(), + }, + ); + self.insert(n); + node_stack.push(root as usize) + } + CreatePlaceholder { root } => { + let n = TreeNode::new(root, TreeNodeType::Placeholder); + self.insert(n); + node_stack.push(root as usize) + } + + NewEventListener { + event_name, + scope: _, + root, + } => { + if let Some(v) = self.listeners.get_mut(&(root as usize)) { + v.insert(event_name); + } else { + let mut hs = HashSet::new(); + hs.insert(event_name); + self.listeners.insert(root as usize, hs); + } + } + RemoveEventListener { root, event } => { + let v = self.listeners.get_mut(&(root as usize)).unwrap(); + v.remove(event); + } + SetText { + root, + text: new_text, + } => { + let target = self.get_mut(root as usize); + nodes_updated.push(root as usize); + match &mut target.node_type { + TreeNodeType::Text { text } => { + *text = new_text.to_string(); + } + _ => unreachable!(), + } + } + SetAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + RemoveAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + } + } + } + + nodes_updated + } + + pub fn update_state( + &mut self, + vdom: &VirtualDom, + nodes_updated: Vec, + us_ctx: &mut US::Ctx, + ds_ctx: &mut DS::Ctx, + ) -> Option> { + let mut to_rerender = HashSet::new(); + let mut nodes_updated: Vec<_> = nodes_updated + .into_iter() + .map(|id| (id, self.get(id).height)) + .collect(); + nodes_updated.dedup(); + nodes_updated.sort_by_key(|(_, h)| *h); + + // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. + // todo: this is called multable times per element? + let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); + while let Some((id, height)) = to_bubble.pop_back() { + let node = self.get_mut(id as usize); + let vnode = node.element(vdom); + let node_type = &node.node_type; + let up_state = &mut node.up_state; + let children = match node_type { + TreeNodeType::Element { children, .. } => Some(children), + _ => None, + }; + // todo: reduce cloning state + let old = up_state.clone(); + let mut new = up_state.clone(); + let parent = node.parent.clone(); + new.reduce( + children + .unwrap_or(&Vec::new()) + .clone() + .iter() + .map(|c| &self.get(c.0).up_state), + vnode, + us_ctx, + ); + if new != old { + to_rerender.insert(id); + if let Some(p) = parent { + let i = to_bubble.partition_point(|(_, h)| *h < height - 1); + // println!("{i}"); + // println!("{to_bubble:?}"); + // make sure the parent is not already queued + if to_bubble.len() == 0 || to_bubble.get(i).unwrap().0 != p.0 { + to_bubble.insert(i, (p.0, height - 1)); + } + } + let node = self.get_mut(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. + let mut to_push: VecDeque<_> = nodes_updated.clone().into(); + while let Some((id, height)) = to_push.pop_front() { + let node = self.get_mut(id as usize); + // todo: reduce cloning state + let old = node.down_state.clone(); + let mut new = node.down_state.clone(); + let vnode = node.element(vdom); + new.reduce( + node.parent.map(|e| &self.get(e.0).down_state), + vnode, + ds_ctx, + ); + if new != old { + to_rerender.insert(id); + let node = self.get_mut(id as usize); + match &node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + let i = to_bubble.partition_point(|(_, h)| *h < height + 1); + to_bubble.insert(i, (c.0, height + 1)); + } + } + _ => (), + }; + node.down_state = new; + } + } + + Some(to_rerender) + } + + fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { + debug_assert_ne!(child_id, parent_id); + let parent = self.get_mut(parent_id); + parent.add_child(ElementId(child_id)); + let parent_height = parent.height + 1; + self.get_mut(child_id).set_parent(ElementId(parent_id)); + self.increase_height(child_id, parent_height); + Some(()) + } + + fn increase_height(&mut self, id: usize, amount: u16) { + let n = self.get_mut(id); + n.height += amount; + match &n.node_type { + TreeNodeType::Element { children, .. } => { + for c in children.clone() { + self.increase_height(c.0, amount); + } + } + _ => (), + } + } + + fn remove(&mut self, id: usize) -> Option> { + let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap(); + match &mut node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + self.remove(c.0).unwrap(); + } + } + _ => (), + } + Some(node) + } + + fn insert(&mut self, node: TreeNode) { + let current_len = self.nodes.len(); + let id = node.id.0; + if current_len - 1 < node.id.0 { + // self.nodes.reserve(1 + id - current_len); + self.nodes.extend((0..1 + id - current_len).map(|_| None)); + } + self.nodes[id] = Some(node); + } + + pub fn get(&self, id: usize) -> &TreeNode { + self.nodes.get(id).unwrap().as_ref().unwrap() + } + + fn get_mut(&mut self, id: usize) -> &mut TreeNode { + self.nodes.get_mut(id).unwrap().as_mut().unwrap() + } +} + +/// The node is stored client side and stores render data +#[derive(Debug)] +pub struct TreeNode { + pub id: ElementId, + pub parent: Option, + pub up_state: US, + pub down_state: DS, + pub node_type: TreeNodeType, + pub height: u16, +} + +#[derive(Debug)] +pub enum TreeNodeType { + Text { + text: String, + }, + Element { + tag: String, + namespace: Option<&'static str>, + children: Vec, + }, + Placeholder, +} + +impl TreeNode { + fn new(id: u64, node_type: TreeNodeType) -> Self { + TreeNode { + id: ElementId(id as usize), + parent: None, + node_type, + down_state: DS::default(), + up_state: US::default(), + height: 0, + } + } + + fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> { + vdom.get_element(self.id).unwrap() + } + + fn add_child(&mut self, child: ElementId) { + match &mut self.node_type { + TreeNodeType::Element { children, .. } => { + children.push(child); + } + _ => (), + } + } + + fn set_parent(&mut self, parent: ElementId) { + self.parent = Some(parent); + } +} + +/// This state that is passed down to children. For example text properties (`` `` ``) 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 { + 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 non-flexbox div'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 { + type Ctx; + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a; +} +impl BubbledUpState for () { + type Ctx = (); + fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + } +} + +// /// The nodes that need to be updated after updating a state. +// pub struct Update { +// children: bool, +// parent: bool, +// } + +// /// This state is derived from children and parents. +// /// Called when the current node's node properties are modified or a parent or child's [State] is modified. +// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this. +// pub trait State: Default + PartialEq + Clone { +// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update +// where +// I: Iterator, +// Self: 'a; +// } +// impl State for () { +// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update +// where +// I: Iterator, +// Self: 'a, +// { +// Update { +// children: false, +// parent: false, +// } +// } +// } + +#[test] +fn test_insert() { + use dioxus_core::*; + use dioxus_core_macro::*; + use dioxus_html as dioxus_elements; + + #[derive(Debug, Default, PartialEq, Clone)] + struct Rect { + x: u16, + y: u16, + width: u16, + height: u16, + } + + impl BubbledUpState for Rect { + type Ctx = (); + + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + match vnode { + VNode::Text(t) => { + *self = Rect { + x: 0, + y: 0, + width: t.text.len().try_into().unwrap(), + height: 1, + }; + return; + } + _ => (), + } + self.width = 2; + self.height = 2; + for c in children { + println!("\t{c:?}"); + self.width = self.width.max(c.width); + self.height += c.height; + } + } + } + + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + let vdom = VirtualDom::new(Base); + let node_1 = rsx! { + div{ + div{ + "hello" + "hello world" + } + } + }; + let node_2 = rsx! { + div{ + div{ + "hello" + "hello world" + } + } + }; + let mutations = vdom.diff_lazynodes(node_1, node_2); + + let mut tree: Tree = Tree { + root: 0, + nodes: { + let mut v = Vec::new(); + v.push(Some(TreeNode::new( + 0, + TreeNodeType::Element { + tag: "Root".to_string(), + namespace: Some("Root"), + children: Vec::new(), + }, + ))); + v + }, + listeners: HashMap::new(), + }; + println!("{:?}", mutations); + let to_update = tree.apply_mutations(vec![mutations.0]); + let to_rerender = tree + .update_state(&vdom, to_update, &mut (), &mut ()) + .unwrap(); + println!("{to_rerender:?}"); + panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", "")); +} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 398da95c7..fcbfa053b 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -15,6 +15,7 @@ license = "MIT/Apache-2.0" [dependencies] dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } +dioxus-native-core = { path = "../native-core", version = "^0.2.0" } tui = "0.17.0" crossterm = "0.23.0" @@ -22,4 +23,4 @@ anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" stretch2 = "0.4.1" - +smallvec = "1.6" diff --git a/packages/tui/src/attributes.rs b/packages/tui/src/attributes.rs deleted file mode 100644 index 7533652f1..000000000 --- a/packages/tui/src/attributes.rs +++ /dev/null @@ -1,968 +0,0 @@ -/* -- [ ] pub display: Display, -- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything -- [ ] pub direction: Direction, - -- [x] pub flex_direction: FlexDirection, -- [x] pub flex_wrap: FlexWrap, -- [x] pub flex_grow: f32, -- [x] pub flex_shrink: f32, -- [x] pub flex_basis: Dimension, - -- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow - -- [x] pub align_items: AlignItems, -- [x] pub align_self: AlignSelf, -- [x] pub align_content: AlignContent, - -- [x] pub margin: Rect, -- [x] pub padding: Rect, - -- [x] pub justify_content: JustifyContent, -- [ ] pub position: Rect, -- [ ] pub border: Rect, - -- [ ] pub size: Size, ----> ??? seems to only be relevant for input? -- [ ] pub min_size: Size, -- [ ] pub max_size: Size, - -- [ ] pub aspect_ratio: Number, -*/ - -use stretch2::{prelude::*, style::PositionType, style::Style}; - -use crate::style::{RinkColor, RinkStyle}; - -pub struct StyleModifer { - pub style: Style, - pub tui_style: RinkStyle, - pub tui_modifier: TuiModifier, -} - -#[derive(Default)] -pub struct TuiModifier { - pub borders: Borders, -} - -#[derive(Default)] -pub struct Borders { - pub top: BorderEdge, - pub right: BorderEdge, - pub bottom: BorderEdge, - pub left: BorderEdge, -} - -impl Borders { - fn slice(&mut self) -> [&mut BorderEdge; 4] { - [ - &mut self.top, - &mut self.right, - &mut self.bottom, - &mut self.left, - ] - } -} - -pub struct BorderEdge { - pub color: Option, - pub style: BorderStyle, - pub width: UnitSystem, - pub radius: UnitSystem, -} - -impl Default for BorderEdge { - fn default() -> Self { - Self { - color: None, - style: BorderStyle::NONE, - width: UnitSystem::Point(0.0), - radius: UnitSystem::Point(0.0), - } - } -} - -#[derive(Clone, Copy)] -pub enum BorderStyle { - DOTTED, - DASHED, - SOLID, - DOUBLE, - GROOVE, - RIDGE, - INSET, - OUTSET, - HIDDEN, - NONE, -} - -impl BorderStyle { - pub fn symbol_set(&self) -> Option { - use tui::symbols::line::*; - const DASHED: Set = Set { - horizontal: "╌", - vertical: "╎", - ..NORMAL - }; - const DOTTED: Set = Set { - horizontal: "┈", - vertical: "┊", - ..NORMAL - }; - match self { - BorderStyle::DOTTED => Some(DOTTED), - BorderStyle::DASHED => Some(DASHED), - BorderStyle::SOLID => Some(NORMAL), - BorderStyle::DOUBLE => Some(DOUBLE), - BorderStyle::GROOVE => Some(NORMAL), - BorderStyle::RIDGE => Some(NORMAL), - BorderStyle::INSET => Some(NORMAL), - BorderStyle::OUTSET => Some(NORMAL), - BorderStyle::HIDDEN => None, - BorderStyle::NONE => None, - } - } -} - -/// applies the entire html namespace defined in dioxus-html -pub fn apply_attributes( - // - name: &str, - value: &str, - style: &mut StyleModifer, -) { - match name { - "align-content" - | "align-items" - | "align-self" => apply_align(name, value, style), - - "animation" - | "animation-delay" - | "animation-direction" - | "animation-duration" - | "animation-fill-mode" - | "animation-iteration-count" - | "animation-name" - | "animation-play-state" - | "animation-timing-function" => apply_animation(name, value, style), - - "backface-visibility" => {} - - "background" - | "background-attachment" - | "background-clip" - | "background-color" - | "background-image" - | "background-origin" - | "background-position" - | "background-repeat" - | "background-size" => apply_background(name, value, style), - - "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" => apply_border(name, value, style), - - "bottom" => {} - "box-shadow" => {} - "box-sizing" => {} - "caption-side" => {} - "clear" => {} - "clip" => {} - - "color" => { - if let Ok(c) = value.parse() { - style.tui_style.fg.replace(c); - } - } - - "column-count" - | "column-fill" - | "column-gap" - | "column-rule" - | "column-rule-color" - | "column-rule-style" - | "column-rule-width" - | "column-span" - // add column-width - | "column-width" => apply_column(name, value, style), - - "columns" => {} - - "content" => {} - "counter-increment" => {} - "counter-reset" => {} - - "cursor" => {} - "direction" => { - match value { - "ltr" => style.style.direction = Direction::LTR, - "rtl" => style.style.direction = Direction::RTL, - _ => {} - } - } - - "display" => apply_display(name, value, style), - - "empty-cells" => {} - - "flex" - | "flex-basis" - | "flex-direction" - | "flex-flow" - | "flex-grow" - | "flex-shrink" - | "flex-wrap" => apply_flex(name, value, style), - - "float" => {} - - "font" - | "font-family" - | "font-size" - | "font-size-adjust" - | "font-stretch" - | "font-style" - | "font-variant" - | "font-weight" => apply_font(name, value, style), - - "height" => { - if let Some(v) = parse_value(value){ - style.style.size.height = match v { - UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), - UnitSystem::Point(v)=> Dimension::Points(v), - }; - } - } - "justify-content" => { - use JustifyContent::*; - style.style.justify_content = match value { - "flex-start" => FlexStart, - "flex-end" => FlexEnd, - "center" => Center, - "space-between" => SpaceBetween, - "space-around" => SpaceAround, - "space-evenly" => SpaceEvenly, - _ => FlexStart, - }; - } - "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" => apply_margin(name, value, style), - - "max-height" => {} - "max-width" => {} - "min-height" => {} - "min-width" => {} - - "opacity" => {} - "order" => {} - "outline" => {} - - "outline-color" - | "outline-offset" - | "outline-style" - | "outline-width" => {} - - "overflow" - | "overflow-x" - | "overflow-y" => apply_overflow(name, value, style), - - "padding" - | "padding-bottom" - | "padding-left" - | "padding-right" - | "padding-top" => apply_padding(name, value, style), - - "page-break-after" - | "page-break-before" - | "page-break-inside" => {} - - "perspective" - | "perspective-origin" => {} - - "position" => { - match value { - "static" => {} - "relative" => style.style.position_type = PositionType::Relative, - "fixed" => {} - "absolute" => style.style.position_type = PositionType::Absolute, - "sticky" => {} - _ => {} - } - - } - - "pointer-events" => {} - - "quotes" => {} - "resize" => {} - "right" => {} - "tab-size" => {} - "table-layout" => {} - - "text-align" - | "text-align-last" - | "text-decoration" - | "text-decoration-color" - | "text-decoration-line" - | "text-decoration-style" - | "text-indent" - | "text-justify" - | "text-overflow" - | "text-shadow" - | "text-transform" => apply_text(name, value, style), - - "top" => {} - - "transform" - | "transform-origin" - | "transform-style" => apply_transform(name, value, style), - - "transition" - | "transition-delay" - | "transition-duration" - | "transition-property" - | "transition-timing-function" => apply_transition(name, value, style), - - "vertical-align" => {} - "visibility" => {} - "white-space" => {} - "width" => { - if let Some(v) = parse_value(value){ - style.style.size.width = match v { - UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), - UnitSystem::Point(v)=> Dimension::Points(v), - }; - } - } - "word-break" => {} - "word-spacing" => {} - "word-wrap" => {} - "z-index" => {} - _ => {} - } -} - -#[derive(Clone, Copy)] -pub enum UnitSystem { - Percent(f32), - Point(f32), -} - -impl Into for UnitSystem { - fn into(self) -> Dimension { - match self { - Self::Percent(v) => Dimension::Percent(v), - Self::Point(v) => Dimension::Points(v), - } - } -} - -fn parse_value(value: &str) -> Option { - if value.ends_with("px") { - if let Ok(px) = value.trim_end_matches("px").parse::() { - Some(UnitSystem::Point(px)) - } else { - None - } - } else if value.ends_with('%') { - if let Ok(pct) = value.trim_end_matches('%').parse::() { - Some(UnitSystem::Percent(pct)) - } else { - None - } - } else { - None - } -} - -fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) { - match name { - // todo: add more overflow support to stretch2 - "overflow" | "overflow-x" | "overflow-y" => { - style.style.overflow = match value { - "auto" => Overflow::Visible, - "hidden" => Overflow::Hidden, - "scroll" => Overflow::Scroll, - "visible" => Overflow::Visible, - _ => Overflow::Visible, - }; - } - _ => {} - } -} - -fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) { - style.style.display = match value { - "flex" => Display::Flex, - "block" => Display::None, - _ => Display::Flex, - } - - // TODO: there are way more variants - // stretch needs to be updated to handle them - // - // "block" => Display::Block, - // "inline" => Display::Inline, - // "inline-block" => Display::InlineBlock, - // "inline-table" => Display::InlineTable, - // "list-item" => Display::ListItem, - // "run-in" => Display::RunIn, - // "table" => Display::Table, - // "table-caption" => Display::TableCaption, - // "table-cell" => Display::TableCell, - // "table-column" => Display::TableColumn, - // "table-column-group" => Display::TableColumnGroup, - // "table-footer-group" => Display::TableFooterGroup, - // "table-header-group" => Display::TableHeaderGroup, - // "table-row" => Display::TableRow, - // "table-row-group" => Display::TableRowGroup, - // "none" => Display::None, - // _ => Display::Inline, -} - -fn apply_background(name: &str, value: &str, style: &mut StyleModifer) { - match name { - "background-color" => { - if let Ok(c) = value.parse() { - style.tui_style.bg.replace(c); - } - } - "background" => {} - "background-attachment" => {} - "background-clip" => {} - "background-image" => {} - "background-origin" => {} - "background-position" => {} - "background-repeat" => {} - "background-size" => {} - _ => {} - } -} - -fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { - fn parse_border_style(v: &str) -> BorderStyle { - match v { - "dotted" => BorderStyle::DOTTED, - "dashed" => BorderStyle::DASHED, - "solid" => BorderStyle::SOLID, - "double" => BorderStyle::DOUBLE, - "groove" => BorderStyle::GROOVE, - "ridge" => BorderStyle::RIDGE, - "inset" => BorderStyle::INSET, - "outset" => BorderStyle::OUTSET, - "none" => BorderStyle::NONE, - "hidden" => BorderStyle::HIDDEN, - _ => todo!(), - } - } - match name { - "border" => {} - "border-bottom" => {} - "border-bottom-color" => { - if let Ok(c) = value.parse() { - style.tui_modifier.borders.bottom.color = Some(c); - } - } - "border-bottom-left-radius" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.left.radius = v; - } - } - "border-bottom-right-radius" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.right.radius = v; - } - } - "border-bottom-style" => { - if style.style.border.bottom == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.bottom = v; - } - style.tui_modifier.borders.bottom.style = parse_border_style(value) - } - "border-bottom-width" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.bottom.width = v; - style.style.border.bottom = v.into(); - } - } - "border-collapse" => {} - "border-color" => { - let values: Vec<_> = value.split(' ').collect(); - if values.len() == 1 { - if let Ok(c) = values[0].parse() { - style - .tui_modifier - .borders - .slice() - .iter_mut() - .for_each(|b| b.color = Some(c)); - } - } else { - for (v, b) in values - .into_iter() - .zip(style.tui_modifier.borders.slice().iter_mut()) - { - if let Ok(c) = v.parse() { - b.color = Some(c); - } - } - } - } - "border-image" => {} - "border-image-outset" => {} - "border-image-repeat" => {} - "border-image-slice" => {} - "border-image-source" => {} - "border-image-width" => {} - "border-left" => {} - "border-left-color" => { - if let Ok(c) = value.parse() { - style.tui_modifier.borders.left.color = Some(c); - } - } - "border-left-style" => { - if style.style.border.start == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.start = v; - } - style.tui_modifier.borders.left.style = parse_border_style(value) - } - "border-left-width" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.left.width = v; - style.style.border.start = v.into(); - } - } - "border-radius" => { - let values: Vec<_> = value.split(' ').collect(); - if values.len() == 1 { - if let Some(r) = parse_value(values[0]) { - style - .tui_modifier - .borders - .slice() - .iter_mut() - .for_each(|b| b.radius = r); - } - } else { - for (v, b) in values - .into_iter() - .zip(style.tui_modifier.borders.slice().iter_mut()) - { - if let Some(r) = parse_value(v) { - b.radius = r; - } - } - } - } - "border-right" => {} - "border-right-color" => { - if let Ok(c) = value.parse() { - style.tui_modifier.borders.right.color = Some(c); - } - } - "border-right-style" => { - let v = Dimension::Points(1.0); - style.style.border.end = v; - style.tui_modifier.borders.right.style = parse_border_style(value) - } - "border-right-width" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.right.width = v; - } - } - "border-spacing" => {} - "border-style" => { - let values: Vec<_> = value.split(' ').collect(); - if style.style.border.top == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.top = v; - } - if style.style.border.bottom == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.bottom = v; - } - if style.style.border.start == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.start = v; - } - if style.style.border.end == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.end = v; - } - if values.len() == 1 { - let border_style = parse_border_style(values[0]); - style - .tui_modifier - .borders - .slice() - .iter_mut() - .for_each(|b| b.style = border_style); - } else { - for (v, b) in values - .into_iter() - .zip(style.tui_modifier.borders.slice().iter_mut()) - { - b.style = parse_border_style(v); - } - } - } - "border-top" => {} - "border-top-color" => { - if let Ok(c) = value.parse() { - style.tui_modifier.borders.top.color = Some(c); - } - } - "border-top-left-radius" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.left.radius = v; - } - } - "border-top-right-radius" => { - if let Some(v) = parse_value(value) { - style.tui_modifier.borders.right.radius = v; - } - } - "border-top-style" => { - if style.style.border.top == Dimension::default() { - let v = Dimension::Points(1.0); - style.style.border.top = v; - } - style.tui_modifier.borders.top.style = parse_border_style(value) - } - "border-top-width" => { - if let Some(v) = parse_value(value) { - style.style.border.top = v.into(); - style.tui_modifier.borders.top.width = v; - } - } - "border-width" => { - let values: Vec<_> = value.split(' ').collect(); - if values.len() == 1 { - if let Some(w) = parse_value(values[0]) { - style.style.border.top = w.into(); - style.style.border.bottom = w.into(); - style.style.border.start = w.into(); - style.style.border.end = w.into(); - style - .tui_modifier - .borders - .slice() - .iter_mut() - .for_each(|b| b.width = w); - } - } else { - let border_widths = [ - &mut style.style.border.top, - &mut style.style.border.bottom, - &mut style.style.border.start, - &mut style.style.border.end, - ]; - for ((v, b), width) in values - .into_iter() - .zip(style.tui_modifier.borders.slice().iter_mut()) - .zip(border_widths) - { - if let Some(w) = parse_value(v) { - *width = w.into(); - b.width = w; - } - } - } - } - _ => (), - } -} - -fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) { - match name { - "animation" => {} - "animation-delay" => {} - "animation-direction =>{}" => {} - "animation-duration" => {} - "animation-fill-mode" => {} - "animation-itera =>{}tion-count" => {} - "animation-name" => {} - "animation-play-state" => {} - "animation-timing-function" => {} - _ => {} - } -} - -fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) { - match name { - "column-count" => {} - "column-fill" => {} - "column-gap" => {} - "column-rule" => {} - "column-rule-color" => {} - "column-rule-style" => {} - "column-rule-width" => {} - "column-span" => {} - "column-width" => {} - _ => {} - } -} - -fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) { - // - [x] pub flex_direction: FlexDirection, - // - [x] pub flex_wrap: FlexWrap, - // - [x] pub flex_grow: f32, - // - [x] pub flex_shrink: f32, - // - [x] pub flex_basis: Dimension, - - match name { - "flex" => {} - "flex-direction" => { - use FlexDirection::*; - style.style.flex_direction = match value { - "row" => Row, - "row-reverse" => RowReverse, - "column" => Column, - "column-reverse" => ColumnReverse, - _ => Row, - }; - } - "flex-basis" => { - if let Some(v) = parse_value(value) { - style.style.flex_basis = match v { - UnitSystem::Percent(v) => Dimension::Percent(v / 100.0), - UnitSystem::Point(v) => Dimension::Points(v), - }; - } - } - "flex-flow" => {} - "flex-grow" => { - if let Ok(val) = value.parse::() { - style.style.flex_grow = val; - } - } - "flex-shrink" => { - if let Ok(px) = value.parse::() { - style.style.flex_shrink = px; - } - } - "flex-wrap" => { - use FlexWrap::*; - style.style.flex_wrap = match value { - "nowrap" => NoWrap, - "wrap" => Wrap, - "wrap-reverse" => WrapReverse, - _ => NoWrap, - }; - } - _ => {} - } -} - -fn apply_font(name: &str, value: &str, style: &mut StyleModifer) { - use tui::style::Modifier; - match name { - "font" => (), - "font-family" => (), - "font-size" => (), - "font-size-adjust" => (), - "font-stretch" => (), - "font-style" => match value { - "italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC), - "oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC), - _ => (), - }, - "font-variant" => todo!(), - "font-weight" => match value { - "bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD), - "normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD), - _ => (), - }, - _ => (), - } -} - -fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) { - match parse_value(value) { - Some(UnitSystem::Percent(v)) => match name { - "padding" => { - let v = Dimension::Percent(v / 100.0); - style.style.padding.top = v; - style.style.padding.bottom = v; - style.style.padding.start = v; - style.style.padding.end = v; - } - "padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0), - "padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0), - "padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0), - "padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0), - _ => {} - }, - Some(UnitSystem::Point(v)) => match name { - "padding" => { - style.style.padding.top = Dimension::Points(v); - style.style.padding.bottom = Dimension::Points(v); - style.style.padding.start = Dimension::Points(v); - style.style.padding.end = Dimension::Points(v); - } - "padding-bottom" => style.style.padding.bottom = Dimension::Points(v), - "padding-left" => style.style.padding.start = Dimension::Points(v), - "padding-right" => style.style.padding.end = Dimension::Points(v), - "padding-top" => style.style.padding.top = Dimension::Points(v), - _ => {} - }, - None => {} - } -} - -fn apply_text(name: &str, value: &str, style: &mut StyleModifer) { - use tui::style::Modifier; - - match name { - "text-align" => todo!(), - "text-align-last" => todo!(), - "text-decoration" | "text-decoration-line" => { - for v in value.split(' ') { - match v { - "line-through" => { - style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT) - } - "underline" => { - style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED) - } - _ => (), - } - } - } - "text-decoration-color" => todo!(), - "text-decoration-style" => todo!(), - "text-indent" => todo!(), - "text-justify" => todo!(), - "text-overflow" => todo!(), - "text-shadow" => todo!(), - "text-transform" => todo!(), - _ => todo!(), - } -} - -fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) { - todo!() -} - -fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) { - todo!() -} - -fn apply_align(name: &str, value: &str, style: &mut StyleModifer) { - match name { - "align-items" => { - use AlignItems::*; - style.style.align_items = match value { - "flex-start" => FlexStart, - "flex-end" => FlexEnd, - "center" => Center, - "baseline" => Baseline, - "stretch" => Stretch, - _ => FlexStart, - }; - } - "align-content" => { - use AlignContent::*; - style.style.align_content = match value { - "flex-start" => FlexStart, - "flex-end" => FlexEnd, - "center" => Center, - "space-between" => SpaceBetween, - "space-around" => SpaceAround, - _ => FlexStart, - }; - } - "align-self" => { - use AlignSelf::*; - style.style.align_self = match value { - "auto" => Auto, - "flex-start" => FlexStart, - "flex-end" => FlexEnd, - "center" => Center, - "baseline" => Baseline, - "stretch" => Stretch, - _ => Auto, - }; - } - _ => {} - } -} - -pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) { - // -} - -pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) { - match parse_value(value) { - Some(UnitSystem::Percent(v)) => match name { - "margin" => { - let v = Dimension::Percent(v / 100.0); - style.style.margin.top = v; - style.style.margin.bottom = v; - style.style.margin.start = v; - style.style.margin.end = v; - } - "margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0), - "margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0), - "margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0), - "margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0), - _ => {} - }, - Some(UnitSystem::Point(v)) => match name { - "margin" => { - style.style.margin.top = Dimension::Points(v); - style.style.margin.bottom = Dimension::Points(v); - style.style.margin.start = Dimension::Points(v); - style.style.margin.end = Dimension::Points(v); - } - "margin-top" => style.style.margin.top = Dimension::Points(v), - "margin-bottom" => style.style.margin.bottom = Dimension::Points(v), - "margin-left" => style.style.margin.start = Dimension::Points(v), - "margin-right" => style.style.margin.end = Dimension::Points(v), - _ => {} - }, - None => {} - } -} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 2224edb16..876d7105f 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,18 +4,19 @@ use crossterm::event::{ use dioxus_core::*; use dioxus_html::{on::*, KeyCode}; +use dioxus_native_core::{Tree, TreeNodeType}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, cell::RefCell, - collections::{HashMap, HashSet}, + collections::HashSet, rc::Rc, sync::Arc, time::{Duration, Instant}, }; use stretch2::{prelude::Layout, Stretch}; -use crate::TuiNode; +use crate::{style_attributes::StyleModifier, RinkLayout}; // a wrapper around the input state for easier access // todo: fix loop @@ -166,7 +167,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - layouts: &mut HashMap>, + layouts: &mut Tree, node: &'a VNode<'a>, ) { struct Data<'b> { @@ -190,7 +191,7 @@ impl InnerInputState { dom: &'c VirtualDom, resolved_events: &mut Vec, layout: &Stretch, - layouts: &HashMap>, + layouts: &Tree, node: &'c VNode<'c>, data: &'d Data<'d>, ) -> HashSet<&'static str> { @@ -225,9 +226,9 @@ impl InnerInputState { } let id = node.try_mounted_id().unwrap(); - let node = layouts.get(&id).unwrap(); + let node = layouts.get(id.0); - let node_layout = layout.layout(node.layout).unwrap(); + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -235,18 +236,18 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - match node.node { - VNode::Element(el) => { + match &node.node_type { + TreeNodeType::Element { children, .. } => { let mut events = HashSet::new(); if previously_contained || currently_contains { - for c in el.children { + for c in children { events = events .union(&get_mouse_events( dom, resolved_events, layout, layouts, - c, + dom.get_element(*c).unwrap(), data, )) .copied() @@ -260,7 +261,7 @@ impl InnerInputState { scope_id: None, priority: EventPriority::Medium, name, - element: Some(el.id.get().unwrap()), + element: Some(node.id), data: Arc::new(clone_mouse_data(data.mouse_data)), }) } @@ -287,7 +288,7 @@ impl InnerInputState { scope_id: None, priority: EventPriority::Medium, name: "wheel", - element: Some(el.id.get().unwrap()), + element: Some(node.id), data: Arc::new(clone_wheel_data(w)), }) } @@ -298,7 +299,7 @@ impl InnerInputState { } events } - VNode::Text(_) => HashSet::new(), + TreeNodeType::Text { .. } => HashSet::new(), _ => todo!(), } } @@ -392,7 +393,7 @@ impl RinkInputHandler { &self, dom: &'a VirtualDom, layout: &Stretch, - layouts: &mut HashMap>, + layouts: &mut Tree, node: &'a VNode<'a>, ) -> Vec { // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 0d615d1a5..77247ce30 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,11 +1,8 @@ use dioxus_core::*; -use std::collections::HashMap; +use dioxus_native_core::BubbledUpState; +use stretch2::prelude::*; -use crate::{ - attributes::{apply_attributes, StyleModifer}, - style::RinkStyle, - TuiModifier, TuiNode, -}; +use crate::layout_attributes::apply_layout_attributes; /* The layout system uses the lineheight as one point. @@ -13,119 +10,84 @@ The layout system uses the lineheight as one point. stretch uses fractional points, so we can rasterize if we need too, but not with characters this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger */ -pub fn collect_layout<'a>( - layout: &mut stretch2::Stretch, - nodes: &mut HashMap>, - vdom: &'a VirtualDom, - node: &'a VNode<'a>, -) { - use stretch2::prelude::*; +#[derive(Clone, PartialEq, Default, Debug)] +pub struct RinkLayout { + pub style: Style, + pub node: Option, +} - match node { - VNode::Text(t) => { - let id = t.id.get().unwrap(); - let char_len = t.text.chars().count(); +impl BubbledUpState for RinkLayout { + type Ctx = Stretch; - let style = Style { - size: Size { - // characters are 1 point tall - height: Dimension::Points(1.0), + // Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + match vnode { + VNode::Text(t) => { + let char_len = t.text.chars().count(); - // text is as long as it is declared - width: Dimension::Points(char_len as f32), - }, - ..Default::default() - }; + let style = Style { + size: Size { + // characters are 1 point tall + height: Dimension::Points(1.0), - nodes.insert( - id, - TuiNode { - node, - block_style: RinkStyle::default(), - tui_modifier: TuiModifier::default(), - layout: layout.new_node(style, &[]).unwrap(), - }, - ); - } - VNode::Element(el) => { - // gather up all the styles from the attribute list - let mut modifier = StyleModifer { - style: Style::default(), - tui_style: RinkStyle::default(), - tui_modifier: TuiModifier::default(), - }; + // text is as long as it is declared + width: Dimension::Points(char_len as f32), + }, + ..Default::default() + }; - // handle text modifier elements - if el.namespace.is_none() { - match el.tag { - "b" => apply_attributes("font-weight", "bold", &mut modifier), - "strong" => apply_attributes("font-weight", "bold", &mut modifier), - "u" => apply_attributes("text-decoration", "underline", &mut modifier), - "ins" => apply_attributes("text-decoration", "underline", &mut modifier), - "del" => apply_attributes("text-decoration", "line-through", &mut modifier), - "i" => apply_attributes("font-style", "italic", &mut modifier), - "em" => apply_attributes("font-style", "italic", &mut modifier), - "mark" => apply_attributes( - "background-color", - "rgba(241, 231, 64, 50%)", - &mut modifier, - ), - _ => (), + if let Some(n) = self.node { + if self.style != style { + panic!("new style: {style:?}"); + stretch.set_style(n, style).unwrap(); + } + } else { + self.node = Some(stretch.new_node(style, &[]).unwrap()); } + + self.style = style; } + VNode::Element(el) => { + // gather up all the styles from the attribute list + let mut style = Style::default(); + + for &Attribute { name, value, .. } in el.attributes { + apply_layout_attributes(name, value, &mut style); + } - for &Attribute { name, value, .. } in el.attributes { - apply_attributes(name, value, &mut modifier); - } + // todo: move + if el.id.get() == Some(ElementId(0)) { + apply_layout_attributes("width", "100%", &mut style); + apply_layout_attributes("height", "100%", &mut style); + } - // Layout the children - for child in el.children { - collect_layout(layout, nodes, vdom, child); - } + // Set all direct nodes as our children + let mut child_layout = vec![]; + for l in children { + child_layout.push(l.node.unwrap()); + } + child_layout.reverse(); - // Set all direct nodes as our children - let mut child_layout = vec![]; - for el in el.children { - let ite = ElementIdIterator::new(vdom, el); - for node in ite { - match node { - VNode::Element(_) | VNode::Text(_) => { - // - child_layout.push(nodes[&node.mounted_id()].layout) - } - VNode::Placeholder(_) => {} - VNode::Fragment(_) => todo!(), - VNode::Component(_) => todo!(), + if let Some(n) = self.node { + if &stretch.children(n).unwrap() != &child_layout { + panic!("new children: {child_layout:?}"); + stretch.set_children(n, &child_layout).unwrap(); + } + if self.style != style { + panic!("new style: {style:?}"); + stretch.set_style(n, style).unwrap(); } - - // child_layout.push(nodes[&node.mounted_id()].layout) + } else { + self.node = Some(stretch.new_node(style, &child_layout).unwrap()); } - } - nodes.insert( - node.mounted_id(), - TuiNode { - node, - block_style: modifier.tui_style, - tui_modifier: modifier.tui_modifier, - layout: layout.new_node(modifier.style, &child_layout).unwrap(), - }, - ); - } - VNode::Fragment(el) => { - // - for child in el.children { - collect_layout(layout, nodes, vdom, child); + self.style = style; } - } - VNode::Component(sc) => { - // - let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); - let root = scope.root_node(); - collect_layout(layout, nodes, vdom, root); - } - VNode::Placeholder(_) => { - // + _ => (), } - }; + } } diff --git a/packages/tui/src/layout_attributes.rs b/packages/tui/src/layout_attributes.rs new file mode 100644 index 000000000..4c38b1409 --- /dev/null +++ b/packages/tui/src/layout_attributes.rs @@ -0,0 +1,643 @@ +/* +- [ ] pub display: Display, +- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything +- [ ] pub direction: Direction, + +- [x] pub flex_direction: FlexDirection, +- [x] pub flex_wrap: FlexWrap, +- [x] pub flex_grow: f32, +- [x] pub flex_shrink: f32, +- [x] pub flex_basis: Dimension, + +- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow + +- [x] pub align_items: AlignItems, +- [x] pub align_self: AlignSelf, +- [x] pub align_content: AlignContent, + +- [x] pub margin: Rect, +- [x] pub padding: Rect, + +- [x] pub justify_content: JustifyContent, +- [ ] pub position: Rect, +- [x] pub border: Rect, + +- [ ] pub size: Size, ----> ??? seems to only be relevant for input? +- [ ] pub min_size: Size, +- [ ] pub max_size: Size, + +- [ ] pub aspect_ratio: Number, +*/ + +use stretch2::{prelude::*, style::PositionType, style::Style}; + +/// applies the entire html namespace defined in dioxus-html +pub fn apply_layout_attributes( + // + name: &str, + value: &str, + style: &mut Style, +) { + match name { + "align-content" + | "align-items" + | "align-self" => apply_align(name, value, style), + + "animation" + | "animation-delay" + | "animation-direction" + | "animation-duration" + | "animation-fill-mode" + | "animation-iteration-count" + | "animation-name" + | "animation-play-state" + | "animation-timing-function" => apply_animation(name, value, style), + + "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" => apply_border(name, value, style), + + "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" + // add column-width + | "column-width" => apply_column(name, value, style), + + "columns" => {} + + "content" => {} + "counter-increment" => {} + "counter-reset" => {} + + "cursor" => {} + "direction" => { + match value { + "ltr" => style.direction = Direction::LTR, + "rtl" => style.direction = Direction::RTL, + _ => {} + } + } + + "display" => apply_display(name, value, style), + + "empty-cells" => {} + + "flex" + | "flex-basis" + | "flex-direction" + | "flex-flow" + | "flex-grow" + | "flex-shrink" + | "flex-wrap" => apply_flex(name, value, style), + + "float" => {} + + "height" => { + if let Some(v) = parse_value(value){ + style.size.height = match v { + UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), + UnitSystem::Point(v)=> Dimension::Points(v), + }; + } + } + "justify-content" => { + use JustifyContent::*; + style.justify_content = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "space-between" => SpaceBetween, + "space-around" => SpaceAround, + "space-evenly" => SpaceEvenly, + _ => FlexStart, + }; + } + "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" => apply_margin(name, value, style), + + "max-height" => {} + "max-width" => {} + "min-height" => {} + "min-width" => {} + + "opacity" => {} + "order" => {} + "outline" => {} + + "outline-color" + | "outline-offset" + | "outline-style" + | "outline-width" => {} + + "overflow" + | "overflow-x" + | "overflow-y" => apply_overflow(name, value, style), + + "padding" + | "padding-bottom" + | "padding-left" + | "padding-right" + | "padding-top" => apply_padding(name, value, style), + + "page-break-after" + | "page-break-before" + | "page-break-inside" => {} + + "perspective" + | "perspective-origin" => {} + + "position" => { + match value { + "static" => {} + "relative" => style.position_type = PositionType::Relative, + "fixed" => {} + "absolute" => style.position_type = PositionType::Absolute, + "sticky" => {} + _ => {} + } + + } + + "pointer-events" => {} + + "quotes" => {} + "resize" => {} + "right" => {} + "tab-size" => {} + "table-layout" => {} + + "top" => {} + + "transform" + | "transform-origin" + | "transform-style" => apply_transform(name, value, style), + + "transition" + | "transition-delay" + | "transition-duration" + | "transition-property" + | "transition-timing-function" => apply_transition(name, value, style), + + "vertical-align" => {} + "visibility" => {} + "white-space" => {} + "width" => { + if let Some(v) = parse_value(value){ + style.size.width = match v { + UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), + UnitSystem::Point(v)=> Dimension::Points(v), + }; + } + } + "word-break" => {} + "word-spacing" => {} + "word-wrap" => {} + "z-index" => {} + _ => {} + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum UnitSystem { + Percent(f32), + Point(f32), +} + +impl Into for UnitSystem { + fn into(self) -> Dimension { + match self { + Self::Percent(v) => Dimension::Percent(v), + Self::Point(v) => Dimension::Points(v), + } + } +} + +pub fn parse_value(value: &str) -> Option { + if value.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + Some(UnitSystem::Point(px)) + } else { + None + } + } else if value.ends_with('%') { + if let Ok(pct) = value.trim_end_matches('%').parse::() { + Some(UnitSystem::Percent(pct)) + } else { + None + } + } else { + None + } +} + +fn apply_overflow(name: &str, value: &str, style: &mut Style) { + match name { + // todo: add more overflow support to stretch2 + "overflow" | "overflow-x" | "overflow-y" => { + style.overflow = match value { + "auto" => Overflow::Visible, + "hidden" => Overflow::Hidden, + "scroll" => Overflow::Scroll, + "visible" => Overflow::Visible, + _ => Overflow::Visible, + }; + } + _ => {} + } +} + +fn apply_display(_name: &str, value: &str, style: &mut Style) { + style.display = match value { + "flex" => Display::Flex, + "block" => Display::None, + _ => Display::Flex, + } + + // TODO: there are way more variants + // stretch needs to be updated to handle them + // + // "block" => Display::Block, + // "inline" => Display::Inline, + // "inline-block" => Display::InlineBlock, + // "inline-table" => Display::InlineTable, + // "list-item" => Display::ListItem, + // "run-in" => Display::RunIn, + // "table" => Display::Table, + // "table-caption" => Display::TableCaption, + // "table-cell" => Display::TableCell, + // "table-column" => Display::TableColumn, + // "table-column-group" => Display::TableColumnGroup, + // "table-footer-group" => Display::TableFooterGroup, + // "table-header-group" => Display::TableHeaderGroup, + // "table-row" => Display::TableRow, + // "table-row-group" => Display::TableRowGroup, + // "none" => Display::None, + // _ => Display::Inline, +} + +fn apply_border(name: &str, value: &str, style: &mut Style) { + match name { + "border" => {} + "border-bottom" => {} + "border-bottom-color" => {} + "border-bottom-left-radius" => {} + "border-bottom-right-radius" => {} + "border-bottom-style" => { + if style.border.bottom == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.bottom = v; + } + } + "border-bottom-width" => { + if let Some(v) = parse_value(value) { + style.border.bottom = v.into(); + } + } + "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" => { + if style.border.start == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.start = v; + } + } + "border-left-width" => { + if let Some(v) = parse_value(value) { + style.border.start = v.into(); + } + } + "border-radius" => {} + "border-right" => {} + "border-right-color" => {} + "border-right-style" => { + let v = Dimension::Points(1.0); + style.border.end = v; + } + "border-right-width" => { + if let Some(v) = parse_value(value) { + style.border.end = v.into(); + } + } + "border-spacing" => {} + "border-style" => { + if style.border.top == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.top = v; + } + if style.border.bottom == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.bottom = v; + } + if style.border.start == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.start = v; + } + if style.border.end == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.end = v; + } + } + "border-top" => {} + "border-top-color" => {} + "border-top-left-radius" => {} + "border-top-right-radius" => {} + "border-top-style" => { + if style.border.top == Dimension::default() { + let v = Dimension::Points(1.0); + style.border.top = v; + } + } + "border-top-width" => { + if let Some(v) = parse_value(value) { + style.border.top = v.into(); + } + } + "border-width" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Some(w) = parse_value(values[0]) { + style.border.top = w.into(); + style.border.bottom = w.into(); + style.border.start = w.into(); + style.border.end = w.into(); + } + } else { + let border_widths = [ + &mut style.border.top, + &mut style.border.bottom, + &mut style.border.start, + &mut style.border.end, + ]; + for (v, width) in values.into_iter().zip(border_widths) { + if let Some(w) = parse_value(v) { + *width = w.into(); + } + } + } + } + _ => (), + } +} + +fn apply_animation(name: &str, _value: &str, _style: &mut Style) { + match name { + "animation" => {} + "animation-delay" => {} + "animation-direction =>{}" => {} + "animation-duration" => {} + "animation-fill-mode" => {} + "animation-itera =>{}tion-count" => {} + "animation-name" => {} + "animation-play-state" => {} + "animation-timing-function" => {} + _ => {} + } +} + +fn apply_column(name: &str, _value: &str, _style: &mut Style) { + match name { + "column-count" => {} + "column-fill" => {} + "column-gap" => {} + "column-rule" => {} + "column-rule-color" => {} + "column-rule-style" => {} + "column-rule-width" => {} + "column-span" => {} + "column-width" => {} + _ => {} + } +} + +fn apply_flex(name: &str, value: &str, style: &mut Style) { + // - [x] pub flex_direction: FlexDirection, + // - [x] pub flex_wrap: FlexWrap, + // - [x] pub flex_grow: f32, + // - [x] pub flex_shrink: f32, + // - [x] pub flex_basis: Dimension, + + match name { + "flex" => {} + "flex-direction" => { + use FlexDirection::*; + style.flex_direction = match value { + "row" => Row, + "row-reverse" => RowReverse, + "column" => Column, + "column-reverse" => ColumnReverse, + _ => Row, + }; + } + "flex-basis" => { + if let Some(v) = parse_value(value) { + style.flex_basis = match v { + UnitSystem::Percent(v) => Dimension::Percent(v / 100.0), + UnitSystem::Point(v) => Dimension::Points(v), + }; + } + } + "flex-flow" => {} + "flex-grow" => { + if let Ok(val) = value.parse::() { + style.flex_grow = val; + } + } + "flex-shrink" => { + if let Ok(px) = value.parse::() { + style.flex_shrink = px; + } + } + "flex-wrap" => { + use FlexWrap::*; + style.flex_wrap = match value { + "nowrap" => NoWrap, + "wrap" => Wrap, + "wrap-reverse" => WrapReverse, + _ => NoWrap, + }; + } + _ => {} + } +} + +fn apply_padding(name: &str, value: &str, style: &mut Style) { + match parse_value(value) { + Some(UnitSystem::Percent(v)) => match name { + "padding" => { + let v = Dimension::Percent(v / 100.0); + style.padding.top = v; + style.padding.bottom = v; + style.padding.start = v; + style.padding.end = v; + } + "padding-bottom" => style.padding.bottom = Dimension::Percent(v / 100.0), + "padding-left" => style.padding.start = Dimension::Percent(v / 100.0), + "padding-right" => style.padding.end = Dimension::Percent(v / 100.0), + "padding-top" => style.padding.top = Dimension::Percent(v / 100.0), + _ => {} + }, + Some(UnitSystem::Point(v)) => match name { + "padding" => { + style.padding.top = Dimension::Points(v); + style.padding.bottom = Dimension::Points(v); + style.padding.start = Dimension::Points(v); + style.padding.end = Dimension::Points(v); + } + "padding-bottom" => style.padding.bottom = Dimension::Points(v), + "padding-left" => style.padding.start = Dimension::Points(v), + "padding-right" => style.padding.end = Dimension::Points(v), + "padding-top" => style.padding.top = Dimension::Points(v), + _ => {} + }, + None => {} + } +} + +fn apply_transform(_name: &str, _value: &str, _style: &mut Style) { + todo!() +} + +fn apply_transition(_name: &str, _value: &str, _style: &mut Style) { + todo!() +} + +fn apply_align(name: &str, value: &str, style: &mut Style) { + match name { + "align-items" => { + use AlignItems::*; + style.align_items = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "baseline" => Baseline, + "stretch" => Stretch, + _ => FlexStart, + }; + } + "align-content" => { + use AlignContent::*; + style.align_content = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "space-between" => SpaceBetween, + "space-around" => SpaceAround, + _ => FlexStart, + }; + } + "align-self" => { + use AlignSelf::*; + style.align_self = match value { + "auto" => Auto, + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "baseline" => Baseline, + "stretch" => Stretch, + _ => Auto, + }; + } + _ => {} + } +} + +pub fn apply_size(_name: &str, _value: &str, _style: &mut Style) { + // +} + +pub fn apply_margin(name: &str, value: &str, style: &mut Style) { + match parse_value(value) { + Some(UnitSystem::Percent(v)) => match name { + "margin" => { + let v = Dimension::Percent(v / 100.0); + style.margin.top = v; + style.margin.bottom = v; + style.margin.start = v; + style.margin.end = v; + } + "margin-top" => style.margin.top = Dimension::Percent(v / 100.0), + "margin-bottom" => style.margin.bottom = Dimension::Percent(v / 100.0), + "margin-left" => style.margin.start = Dimension::Percent(v / 100.0), + "margin-right" => style.margin.end = Dimension::Percent(v / 100.0), + _ => {} + }, + Some(UnitSystem::Point(v)) => match name { + "margin" => { + style.margin.top = Dimension::Points(v); + style.margin.bottom = Dimension::Points(v); + style.margin.start = Dimension::Points(v); + style.margin.end = Dimension::Points(v); + } + "margin-top" => style.margin.top = Dimension::Points(v), + "margin-bottom" => style.margin.bottom = Dimension::Points(v), + "margin-left" => style.margin.start = Dimension::Points(v), + "margin-right" => style.margin.end = Dimension::Points(v), + _ => {} + }, + None => {} + } +} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f90967802..95c6d8b56 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,28 +6,32 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; +use dioxus_native_core::Tree; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; use std::{ - collections::HashMap, io, time::{Duration, Instant}, }; -use stretch2::{prelude::Size, Stretch}; -use style::RinkStyle; +use stretch2::{ + prelude::{Layout, Size}, + Stretch, +}; +use style_attributes::StyleModifier; use tui::{backend::CrosstermBackend, Terminal}; -mod attributes; mod config; mod hooks; mod layout; +mod layout_attributes; mod render; mod style; +mod style_attributes; mod widget; -pub use attributes::*; pub use config::*; pub use hooks::*; pub use layout::*; +pub use layout_attributes::*; pub use render::*; pub fn launch(app: Component<()>) { @@ -44,16 +48,15 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); - dom.rebuild(); + let mut tree: Tree = Tree::new(); + let mutations = dom.rebuild(); + let to_update = tree.apply_mutations(vec![mutations]); + let mut stretch = Stretch::new(); + let _to_rerender = tree + .update_state(&dom, to_update, &mut stretch, &mut ()) + .unwrap(); - render_vdom(&mut dom, tx, handler, cfg).unwrap(); -} - -pub struct TuiNode<'a> { - pub layout: stretch2::node::Node, - pub block_style: RinkStyle, - pub tui_modifier: TuiModifier, - pub node: &'a VNode<'a>, + render_vdom(&mut dom, tx, handler, cfg, tree, stretch).unwrap(); } pub fn render_vdom( @@ -61,6 +64,8 @@ pub fn render_vdom( ctx: UnboundedSender, handler: RinkInputHandler, cfg: Config, + mut tree: Tree, + mut stretch: Stretch, ) -> Result<()> { // Setup input handling let (tx, mut rx) = unbounded(); @@ -102,11 +107,11 @@ pub fn render_vdom( loop { /* - -> collect all the nodes with their layout - -> solve their layout + -> collect all the nodes -> resolve events - -> render the nodes in the right place with tui/crosstream - -> while rendering, apply styling + -> render the nodes in the right place with tui/crossterm + -> rendering + -> lazily update the layout and style based on nodes changed use simd to compare lines for diffing? @@ -114,45 +119,47 @@ pub fn render_vdom( todo: reuse the layout and node objects. our work_with_deadline method can tell us which nodes are dirty. */ - let mut layout = Stretch::new(); - let mut nodes = HashMap::new(); - let root_node = vdom.base_scope().root_node(); - layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); /* Compute the layout given the terminal size */ - let node_id = root_node.try_mounted_id().unwrap(); - let root_layout = nodes[&node_id].layout; let mut events = Vec::new(); terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); + println!("{dims:?}"); let width = dims.width; let height = dims.height; - layout + let root_id = tree.root; + let root_node = tree.get(root_id).up_state.node.unwrap(); + stretch .compute_layout( - root_layout, + root_node, Size { - width: stretch2::prelude::Number::Defined(width as f32), - height: stretch2::prelude::Number::Defined(height as f32), + width: stretch2::prelude::Number::Defined((width - 1) as f32), + height: stretch2::prelude::Number::Defined((height - 1) as f32), }, ) .unwrap(); // resolve events before rendering - events = handler.get_events(vdom, &layout, &mut nodes, root_node); - render::render_vnode( - frame, - &layout, - &mut nodes, + events = handler.get_events( vdom, - root_node, - &RinkStyle::default(), - cfg, + &stretch, + &mut tree, + vdom.base_scope().root_node(), ); - assert!(nodes.is_empty()); + for n in &tree.nodes { + if let Some(node) = n { + let Layout { location, size, .. } = + stretch.layout(node.up_state.node.unwrap()).unwrap(); + println!("{node:#?}"); + println!("\t{location:?}: {size:?}"); + } + } + let root = tree.get(tree.root); + // render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; for e in events { @@ -191,7 +198,11 @@ pub fn render_vdom( } } - vdom.work_with_deadline(|| false); + let mutations = vdom.work_with_deadline(|| false); + let to_update = tree.apply_mutations(mutations); + let _to_rerender = tree + .update_state(&vdom, to_update, &mut stretch, &mut ()) + .unwrap(); } disable_raw_mode()?; diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index dc866830e..23d70f878 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,5 +1,5 @@ -use dioxus_core::*; -use std::{collections::HashMap, io::Stdout}; +use dioxus_native_core::{Tree, TreeNode}; +use std::io::Stdout; use stretch2::{ geometry::Point, prelude::{Layout, Size}, @@ -9,8 +9,9 @@ use tui::{backend::CrosstermBackend, layout::Rect}; use crate::{ style::{RinkColor, RinkStyle}, + style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - BorderEdge, BorderStyle, Config, TuiNode, UnitSystem, + Config, RinkLayout, StyleModifier, UnitSystem, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; @@ -18,43 +19,22 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode<'a>( frame: &mut tui::Frame>, layout: &Stretch, - layouts: &mut HashMap>, - vdom: &'a VirtualDom, - node: &'a VNode<'a>, - // this holds the accumulated syle state for styled text rendering - style: &RinkStyle, + tree: &Tree, + node: &TreeNode, cfg: Config, ) { - match node { - VNode::Fragment(f) => { - for child in f.children { - render_vnode(frame, layout, layouts, vdom, child, style, cfg); - } - return; - } - - VNode::Component(vcomp) => { - let idx = vcomp.scope.get().unwrap(); - let new_node = vdom.get_scope(idx).unwrap().root_node(); - render_vnode(frame, layout, layouts, vdom, new_node, style, cfg); - return; - } - - VNode::Placeholder(_) => return, - - VNode::Element(_) | VNode::Text(_) => {} + match &node.node_type { + dioxus_native_core::TreeNodeType::Placeholder => return, + _ => (), } - let id = node.try_mounted_id().unwrap(); - let mut node = layouts.remove(&id).unwrap(); - - let Layout { location, size, .. } = layout.layout(node.layout).unwrap(); + let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); let Point { x, y } = location; let Size { width, height } = size; - match node.node { - VNode::Text(t) => { + match &node.node_type { + dioxus_native_core::TreeNodeType::Text { text } => { #[derive(Default)] struct Label<'a> { text: &'a str, @@ -65,6 +45,7 @@ pub fn render_vnode<'a>( fn render(self, area: Rect, mut buf: RinkBuffer) { for (i, c) in self.text.char_indices() { let mut new_cell = RinkCell::default(); + // println!("{:?}", self.style); new_cell.set_style(self.style); new_cell.symbol = c.to_string(); buf.set(area.left() + i as u16, area.top(), &new_cell); @@ -73,8 +54,8 @@ pub fn render_vnode<'a>( } let label = Label { - text: t.text, - style: *style, + text: &text, + style: node.down_state.style, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -83,30 +64,23 @@ pub fn render_vnode<'a>( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - VNode::Element(el) => { + dioxus_native_core::TreeNodeType::Element { children, .. } => { let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); - let mut new_style = node.block_style.merge(*style); - node.block_style = new_style; - // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { frame.render_widget(WidgetWithContext::new(node, cfg), area); } - // do not pass background color to children - new_style.bg = None; - for el in el.children { - render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg); + for c in children { + render_vnode(frame, layout, tree, tree.get(c.0), cfg); } } - VNode::Fragment(_) => todo!(), - VNode::Component(_) => todo!(), - VNode::Placeholder(_) => todo!(), + dioxus_native_core::TreeNodeType::Placeholder => unreachable!(), } } -impl<'a> RinkWidget for TuiNode<'a> { +impl RinkWidget for &TreeNode { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -290,14 +264,14 @@ impl<'a> RinkWidget for TuiNode<'a> { for x in area.left()..area.right() { for y in area.top()..area.bottom() { let mut new_cell = RinkCell::default(); - if let Some(c) = self.block_style.bg { + if let Some(c) = self.down_state.style.bg { new_cell.bg = c; } buf.set(x, y, &new_cell); } } - let borders = self.tui_modifier.borders; + let borders = &self.down_state.modifier.borders; let last_edge = &borders.left; let current_edge = &borders.top; @@ -314,7 +288,7 @@ impl<'a> RinkWidget for TuiNode<'a> { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.block_style.fg); + let color = current_edge.color.or(self.down_state.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -349,7 +323,7 @@ impl<'a> RinkWidget for TuiNode<'a> { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.block_style.fg); + let color = current_edge.color.or(self.down_state.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -384,7 +358,7 @@ impl<'a> RinkWidget for TuiNode<'a> { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.block_style.fg); + let color = current_edge.color.or(self.down_state.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -419,7 +393,7 @@ impl<'a> RinkWidget for TuiNode<'a> { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.block_style.fg); + let color = current_edge.color.or(self.down_state.style.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style.rs b/packages/tui/src/style.rs index f4e061fee..823a94725 100644 --- a/packages/tui/src/style.rs +++ b/packages/tui/src/style.rs @@ -4,17 +4,17 @@ use tui::style::{Color, Modifier, Style}; use crate::RenderingMode; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct RinkColor { pub color: Color, - pub alpha: f32, + pub alpha: u8, } impl Default for RinkColor { fn default() -> Self { Self { color: Color::Black, - alpha: 0.0, + alpha: 0, } } } @@ -23,22 +23,17 @@ impl RinkColor { pub fn blend(self, other: Color) -> Color { if self.color == Color::Reset { Color::Reset - } else if self.alpha == 0.0 { + } else if self.alpha == 0 { other } else { - let [sr, sg, sb] = to_rgb(self.color); - let [or, og, ob] = to_rgb(other); - let (sr, sg, sb, sa) = ( - sr as f32 / 255.0, - sg as f32 / 255.0, - sb as f32 / 255.0, - self.alpha, - ); - let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0); + let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16); + let [or, og, ob] = to_rgb(other).map(|e| e as u16); + let sa: u16 = self.alpha as u16; + let rsa = 255 - sa; Color::Rgb( - (255.0 * (sr * sa + or * (1.0 - sa))) as u8, - (255.0 * (sg * sa + og * (1.0 - sa))) as u8, - (255.0 * (sb * sa + ob * (1.0 - sa))) as u8, + ((sr * sa + or * rsa) / 255) as u8, + ((sg * sa + og * rsa) / 255) as u8, + ((sb * sa + ob * rsa) / 255) as u8, ) } } @@ -151,75 +146,75 @@ impl FromStr for RinkColor { match color { "red" => Ok(RinkColor { color: Color::Red, - alpha: 1.0, + alpha: 255, }), "black" => Ok(RinkColor { color: Color::Black, - alpha: 1.0, + alpha: 255, }), "green" => Ok(RinkColor { color: Color::Green, - alpha: 1.0, + alpha: 255, }), "yellow" => Ok(RinkColor { color: Color::Yellow, - alpha: 1.0, + alpha: 255, }), "blue" => Ok(RinkColor { color: Color::Blue, - alpha: 1.0, + alpha: 255, }), "magenta" => Ok(RinkColor { color: Color::Magenta, - alpha: 1.0, + alpha: 255, }), "cyan" => Ok(RinkColor { color: Color::Cyan, - alpha: 1.0, + alpha: 255, }), "gray" => Ok(RinkColor { color: Color::Gray, - alpha: 1.0, + alpha: 255, }), "darkgray" => Ok(RinkColor { color: Color::DarkGray, - alpha: 1.0, + alpha: 255, }), // light red does not exist "orangered" => Ok(RinkColor { color: Color::LightRed, - alpha: 1.0, + alpha: 255, }), "lightgreen" => Ok(RinkColor { color: Color::LightGreen, - alpha: 1.0, + alpha: 255, }), "lightyellow" => Ok(RinkColor { color: Color::LightYellow, - alpha: 1.0, + alpha: 255, }), "lightblue" => Ok(RinkColor { color: Color::LightBlue, - alpha: 1.0, + alpha: 255, }), // light magenta does not exist "orchid" => Ok(RinkColor { color: Color::LightMagenta, - alpha: 1.0, + alpha: 255, }), "lightcyan" => Ok(RinkColor { color: Color::LightCyan, - alpha: 1.0, + alpha: 255, }), "white" => Ok(RinkColor { color: Color::White, - alpha: 1.0, + alpha: 255, }), _ => { if color.len() == 7 && color.starts_with('#') { parse_hex(color).map(|c| RinkColor { color: c, - alpha: 1.0, + alpha: 255, }) } else if let Some(stripped) = color.strip_prefix("rgb(") { let color_values = stripped.trim_end_matches(')'); @@ -234,7 +229,7 @@ impl FromStr for RinkColor { } else { parse_rgb(color_values).map(|c| RinkColor { color: c, - alpha: 1.0, + alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("rgba(") { @@ -243,14 +238,17 @@ impl FromStr for RinkColor { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { - parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a }) + parse_rgb(rgb_values).map(|c| RinkColor { + color: c, + alpha: (a * 255.0) as u8, + }) } else { Err(ParseColorError) } } else { parse_rgb(color_values).map(|c| RinkColor { color: c, - alpha: 1.0, + alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("hsl(") { @@ -259,14 +257,17 @@ impl FromStr for RinkColor { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { - parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a }) + parse_hsl(rgb_values).map(|c| RinkColor { + color: c, + alpha: (a * 255.0) as u8, + }) } else { Err(ParseColorError) } } else { parse_hsl(color_values).map(|c| RinkColor { color: c, - alpha: 1.0, + alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("hsla(") { @@ -275,14 +276,17 @@ impl FromStr for RinkColor { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { - parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a }) + parse_hsl(rgb_values).map(|c| RinkColor { + color: c, + alpha: (a * 255.0) as u8, + }) } else { Err(ParseColorError) } } else { parse_hsl(color_values).map(|c| RinkColor { color: c, - alpha: 1.0, + alpha: 255, }) } } else { @@ -393,7 +397,7 @@ fn rgb_to_ansi() { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Debug)] pub struct RinkStyle { pub fg: Option, pub bg: Option, @@ -406,7 +410,7 @@ impl Default for RinkStyle { Self { fg: Some(RinkColor { color: Color::White, - alpha: 1.0, + alpha: 255, }), bg: None, add_modifier: Modifier::empty(), diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs new file mode 100644 index 000000000..d365af75d --- /dev/null +++ b/packages/tui/src/style_attributes.rs @@ -0,0 +1,582 @@ +/* +- [ ] pub display: Display, +- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything +- [ ] pub direction: Direction, + +- [x] pub flex_direction: FlexDirection, +- [x] pub flex_wrap: FlexWrap, +- [x] pub flex_grow: f32, +- [x] pub flex_shrink: f32, +- [x] pub flex_basis: Dimension, + +- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow + +- [x] pub align_items: AlignItems, +- [x] pub align_self: AlignSelf, +- [x] pub align_content: AlignContent, + +- [x] pub margin: Rect, +- [x] pub padding: Rect, + +- [x] pub justify_content: JustifyContent, +- [ ] pub position: Rect, +- [x] pub border: Rect, + +- [ ] pub size: Size, ----> ??? seems to only be relevant for input? +- [ ] pub min_size: Size, +- [ ] pub max_size: Size, + +- [ ] pub aspect_ratio: Number, +*/ + +use dioxus_core::{Attribute, VNode}; +use dioxus_native_core::PushedDownState; + +use crate::{ + parse_value, + style::{RinkColor, RinkStyle}, + UnitSystem, +}; + +#[derive(Default, Clone, PartialEq, Debug)] +pub struct StyleModifier { + pub style: RinkStyle, + pub modifier: TuiModifier, +} + +impl PushedDownState for StyleModifier { + type Ctx = (); + + fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) { + *self = StyleModifier::default(); + self.style.fg = None; + match vnode { + VNode::Element(el) => { + // handle text modifier elements + if el.namespace.is_none() { + match el.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), + "mark" => apply_style_attributes( + "background-color", + "rgba(241, 231, 64, 50%)", + self, + ), + _ => (), + } + } + + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in el.attributes { + apply_style_attributes(name, value, self); + } + } + _ => (), + } + + // 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; + } + } +} + +#[derive(Default, Clone, PartialEq, Debug)] +pub struct TuiModifier { + pub borders: Borders, +} + +#[derive(Default, Clone, PartialEq, Debug)] +pub struct Borders { + pub top: BorderEdge, + pub right: BorderEdge, + pub bottom: BorderEdge, + pub left: BorderEdge, +} + +impl Borders { + fn slice(&mut self) -> [&mut BorderEdge; 4] { + [ + &mut self.top, + &mut self.right, + &mut self.bottom, + &mut self.left, + ] + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct BorderEdge { + pub color: Option, + pub style: BorderStyle, + pub width: UnitSystem, + pub radius: UnitSystem, +} + +impl Default for BorderEdge { + fn default() -> Self { + Self { + color: None, + style: BorderStyle::NONE, + width: UnitSystem::Point(0.0), + radius: UnitSystem::Point(0.0), + } + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum BorderStyle { + DOTTED, + DASHED, + SOLID, + DOUBLE, + GROOVE, + RIDGE, + INSET, + OUTSET, + HIDDEN, + NONE, +} + +impl BorderStyle { + pub fn symbol_set(&self) -> Option { + use tui::symbols::line::*; + const DASHED: Set = Set { + horizontal: "╌", + vertical: "╎", + ..NORMAL + }; + const DOTTED: Set = Set { + horizontal: "┈", + vertical: "┊", + ..NORMAL + }; + match self { + BorderStyle::DOTTED => Some(DOTTED), + BorderStyle::DASHED => Some(DASHED), + BorderStyle::SOLID => Some(NORMAL), + BorderStyle::DOUBLE => Some(DOUBLE), + BorderStyle::GROOVE => Some(NORMAL), + BorderStyle::RIDGE => Some(NORMAL), + BorderStyle::INSET => Some(NORMAL), + BorderStyle::OUTSET => Some(NORMAL), + BorderStyle::HIDDEN => None, + BorderStyle::NONE => None, + } + } +} + +/// applies the entire html namespace defined in dioxus-html +pub fn apply_style_attributes( + // + name: &str, + value: &str, + style: &mut StyleModifier, +) { + match name { + "animation" + | "animation-delay" + | "animation-direction" + | "animation-duration" + | "animation-fill-mode" + | "animation-iteration-count" + | "animation-name" + | "animation-play-state" + | "animation-timing-function" => apply_animation(name, value, style), + + "backface-visibility" => {} + + "background" + | "background-attachment" + | "background-clip" + | "background-color" + | "background-image" + | "background-origin" + | "background-position" + | "background-repeat" + | "background-size" => apply_background(name, value, style), + + "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" => apply_border(name, value, style), + + "bottom" => {} + "box-shadow" => {} + "box-sizing" => {} + "caption-side" => {} + "clear" => {} + "clip" => {} + + "color" => { + if let Ok(c) = value.parse() { + style.style.fg.replace(c); + } + } + + "columns" => {} + + "content" => {} + "counter-increment" => {} + "counter-reset" => {} + + "cursor" => {} + + "empty-cells" => {} + + "float" => {} + + "font" | "font-family" | "font-size" | "font-size-adjust" | "font-stretch" + | "font-style" | "font-variant" | "font-weight" => apply_font(name, value, style), + + "letter-spacing" => {} + "line-height" => {} + + "list-style" | "list-style-image" | "list-style-position" | "list-style-type" => {} + + "opacity" => {} + "order" => {} + "outline" => {} + + "outline-color" | "outline-offset" | "outline-style" | "outline-width" => {} + + "page-break-after" | "page-break-before" | "page-break-inside" => {} + + "perspective" | "perspective-origin" => {} + + "pointer-events" => {} + + "quotes" => {} + "resize" => {} + "tab-size" => {} + "table-layout" => {} + + "text-align" + | "text-align-last" + | "text-decoration" + | "text-decoration-color" + | "text-decoration-line" + | "text-decoration-style" + | "text-indent" + | "text-justify" + | "text-overflow" + | "text-shadow" + | "text-transform" => apply_text(name, value, style), + + "transition" + | "transition-delay" + | "transition-duration" + | "transition-property" + | "transition-timing-function" => apply_transition(name, value, style), + + "visibility" => {} + "white-space" => {} + _ => {} + } +} + +fn apply_background(name: &str, value: &str, style: &mut StyleModifier) { + match name { + "background-color" => { + if let Ok(c) = value.parse() { + style.style.bg.replace(c); + } + } + "background" => {} + "background-attachment" => {} + "background-clip" => {} + "background-image" => {} + "background-origin" => {} + "background-position" => {} + "background-repeat" => {} + "background-size" => {} + _ => {} + } +} + +fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { + fn parse_border_style(v: &str) -> BorderStyle { + match v { + "dotted" => BorderStyle::DOTTED, + "dashed" => BorderStyle::DASHED, + "solid" => BorderStyle::SOLID, + "double" => BorderStyle::DOUBLE, + "groove" => BorderStyle::GROOVE, + "ridge" => BorderStyle::RIDGE, + "inset" => BorderStyle::INSET, + "outset" => BorderStyle::OUTSET, + "none" => BorderStyle::NONE, + "hidden" => BorderStyle::HIDDEN, + _ => todo!(), + } + } + match name { + "border" => {} + "border-bottom" => {} + "border-bottom-color" => { + if let Ok(c) = value.parse() { + style.modifier.borders.bottom.color = Some(c); + } + } + "border-bottom-left-radius" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.left.radius = v; + } + } + "border-bottom-right-radius" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.right.radius = v; + } + } + "border-bottom-style" => style.modifier.borders.bottom.style = parse_border_style(value), + "border-bottom-width" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.bottom.width = v; + } + } + "border-collapse" => {} + "border-color" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Ok(c) = values[0].parse() { + style + .modifier + .borders + .slice() + .iter_mut() + .for_each(|b| b.color = Some(c)); + } + } else { + for (v, b) in values + .into_iter() + .zip(style.modifier.borders.slice().iter_mut()) + { + if let Ok(c) = v.parse() { + b.color = Some(c); + } + } + } + } + "border-image" => {} + "border-image-outset" => {} + "border-image-repeat" => {} + "border-image-slice" => {} + "border-image-source" => {} + "border-image-width" => {} + "border-left" => {} + "border-left-color" => { + if let Ok(c) = value.parse() { + style.modifier.borders.left.color = Some(c); + } + } + "border-left-style" => style.modifier.borders.left.style = parse_border_style(value), + "border-left-width" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.left.width = v; + } + } + "border-radius" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Some(r) = parse_value(values[0]) { + style + .modifier + .borders + .slice() + .iter_mut() + .for_each(|b| b.radius = r); + } + } else { + for (v, b) in values + .into_iter() + .zip(style.modifier.borders.slice().iter_mut()) + { + if let Some(r) = parse_value(v) { + b.radius = r; + } + } + } + } + "border-right" => {} + "border-right-color" => { + if let Ok(c) = value.parse() { + style.modifier.borders.right.color = Some(c); + } + } + "border-right-style" => style.modifier.borders.right.style = parse_border_style(value), + "border-right-width" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.right.width = v; + } + } + "border-spacing" => {} + "border-style" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + let border_style = parse_border_style(values[0]); + style + .modifier + .borders + .slice() + .iter_mut() + .for_each(|b| b.style = border_style); + } else { + for (v, b) in values + .into_iter() + .zip(style.modifier.borders.slice().iter_mut()) + { + b.style = parse_border_style(v); + } + } + } + "border-top" => {} + "border-top-color" => { + if let Ok(c) = value.parse() { + style.modifier.borders.top.color = Some(c); + } + } + "border-top-left-radius" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.left.radius = v; + } + } + "border-top-right-radius" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.right.radius = v; + } + } + "border-top-style" => style.modifier.borders.top.style = parse_border_style(value), + "border-top-width" => { + if let Some(v) = parse_value(value) { + style.modifier.borders.top.width = v; + } + } + "border-width" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Some(w) = parse_value(values[0]) { + style + .modifier + .borders + .slice() + .iter_mut() + .for_each(|b| b.width = w); + } + } else { + for (v, width) in values + .into_iter() + .zip(style.modifier.borders.slice().iter_mut()) + { + if let Some(w) = parse_value(v) { + width.width = w.into(); + } + } + } + } + _ => (), + } +} + +fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) { + match name { + "animation" => {} + "animation-delay" => {} + "animation-direction =>{}" => {} + "animation-duration" => {} + "animation-fill-mode" => {} + "animation-itera =>{}tion-count" => {} + "animation-name" => {} + "animation-play-state" => {} + "animation-timing-function" => {} + _ => {} + } +} + +fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { + use tui::style::Modifier; + match name { + "font" => (), + "font-family" => (), + "font-size" => (), + "font-size-adjust" => (), + "font-stretch" => (), + "font-style" => match value { + "italic" => style.style = style.style.add_modifier(Modifier::ITALIC), + "oblique" => style.style = style.style.add_modifier(Modifier::ITALIC), + _ => (), + }, + "font-variant" => todo!(), + "font-weight" => match value { + "bold" => style.style = style.style.add_modifier(Modifier::BOLD), + "normal" => style.style = style.style.remove_modifier(Modifier::BOLD), + _ => (), + }, + _ => (), + } +} + +fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { + use tui::style::Modifier; + + match name { + "text-align" => todo!(), + "text-align-last" => todo!(), + "text-decoration" | "text-decoration-line" => { + for v in value.split(' ') { + match v { + "line-through" => style.style = style.style.add_modifier(Modifier::CROSSED_OUT), + "underline" => style.style = style.style.add_modifier(Modifier::UNDERLINED), + _ => (), + } + } + } + "text-decoration-color" => todo!(), + "text-decoration-style" => todo!(), + "text-indent" => todo!(), + "text-justify" => todo!(), + "text-overflow" => todo!(), + "text-shadow" => todo!(), + "text-transform" => todo!(), + _ => todo!(), + } +} + +fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) { + todo!() +} diff --git a/packages/tui/src/utils.rs b/packages/tui/src/utils.rs new file mode 100644 index 000000000..f89e3180c --- /dev/null +++ b/packages/tui/src/utils.rs @@ -0,0 +1,83 @@ +// use dioxus_core::{Element, ElementId, Mutations, VNode, VirtualDom, DomEdit}; + +// /// The focus system needs a iterator that can persist through changes in the [VirtualDom]. Iterate through it with [ElementIter::next], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]). +// pub(crate) struct ElementIter { +// // stack of elements and fragments +// stack: smallvec::SmallVec<[(ElementId, usize); 5]>, +// } + +// impl ElementIter { +// pub(crate) fn new(initial: ElementId) -> Self { +// ElementIter { +// stack: smallvec::smallvec![(initial, 0)], +// } +// } +// /// remove stale element refreneces +// pub(crate) fn update(&mut self, mutations: &Mutations, vdom: &VirtualDom) { +// let ids_removed: Vec<_> = mutations.edits.iter().filter_map(|e| if let DomEdit::Remove{root: }).collect(); +// for node in self.stack { + +// match node.0 { +// VNode::Fragment(f) => { + +// } + +// VNode::Element(_) => {} + +// _ => unreachable!(), +// } +// } +// } +// pub(crate) fn next<'a>(&mut self, vdom: &'a VirtualDom) -> Option<&'a VNode<'a>> { +// let last = self.stack.last()?.0; +// let node = vdom.get_element(last)?; +// match node { +// VNode::Fragment(f) => { +// let mut last_mut = self.stack.last_mut()?; +// if last_mut.1 + 1 >= f.children.len() { +// self.stack.pop(); +// self.next(vdom) +// } else { +// last_mut.1 += 1; +// let new_node = &f.children[last_mut.1]; +// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { +// self.stack.push((new_node.mounted_id(), 0)); +// } +// self.next(vdom) +// } +// } + +// VNode::Component(vcomp) => { +// let idx = vcomp.scope.get().unwrap(); +// let new_node = vdom.get_scope(idx).unwrap().root_node(); +// *self.stack.last_mut()? = (new_node.mounted_id(), 0); +// self.next(vdom) +// } + +// VNode::Placeholder(_) | VNode::Text(_) => { +// self.stack.pop(); +// self.next(vdom) +// } + +// VNode::Element(e) => { +// let mut last_mut = self.stack.last_mut()?; +// if last_mut.1 + 1 >= e.children.len() { +// self.stack.pop(); +// self.next(vdom); +// } else { +// last_mut.1 += 1; +// let new_node = &e.children[last_mut.1]; +// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { +// self.stack.push((new_node.mounted_id(), 0)); +// } +// self.next(vdom); +// } +// Some(node) +// } +// } +// } + +// pub(crate) fn peak(&self) -> Option<&ElementId> { +// self.stack.last().map(|(e, c)| e) +// } +// } diff --git a/packages/tui/src/widget.rs b/packages/tui/src/widget.rs index 6d69ec75e..1b476a412 100644 --- a/packages/tui/src/widget.rs +++ b/packages/tui/src/widget.rs @@ -21,6 +21,10 @@ impl<'a> RinkBuffer<'a> { } pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) { + let area = self.buf.area(); + if x < area.x || x > area.width || y < area.y || y > area.height { + panic!("({x}, {y}) is not in {area:?}"); + } let mut cell = self.buf.get_mut(x, y); cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg)); if new.symbol.is_empty() { @@ -71,11 +75,11 @@ impl Default for RinkCell { symbol: "".to_string(), fg: RinkColor { color: Color::Rgb(0, 0, 0), - alpha: 0.0, + alpha: 0, }, bg: RinkColor { color: Color::Rgb(0, 0, 0), - alpha: 0.0, + alpha: 0, }, modifier: Modifier::empty(), } diff --git a/packages/tui/tests/relayout.rs b/packages/tui/tests/relayout.rs new file mode 100644 index 000000000..83b2a46da --- /dev/null +++ b/packages/tui/tests/relayout.rs @@ -0,0 +1,63 @@ +use stretch2 as stretch; + +#[test] +fn relayout() { + let mut stretch = stretch::Stretch::new(); + let node1 = stretch + .new_node( + stretch::style::Style { + position: stretch::geometry::Point { + x: stretch::style::Dimension::Points(10f32), + y: stretch::style::Dimension::Points(10f32), + }, + size: stretch::geometry::Size { + width: stretch::style::Dimension::Points(10f32), + height: stretch::style::Dimension::Points(10f32), + }, + ..Default::default() + }, + &[], + ) + .unwrap(); + let node0 = stretch + .new_node( + stretch::style::Style { + size: stretch::geometry::Size { + width: stretch::style::Dimension::Percent(1f32), + height: stretch::style::Dimension::Percent(1f32), + }, + ..Default::default() + }, + &[node1], + ) + .unwrap(); + let node = stretch + .new_node( + stretch::style::Style { + size: stretch::geometry::Size { + width: stretch::style::Dimension::Points(100f32), + height: stretch::style::Dimension::Points(100f32), + }, + ..Default::default() + }, + &[node0], + ) + .unwrap(); + for _ in 0..10 { + stretch + .compute_layout(node, stretch::geometry::Size::undefined()) + .unwrap(); + assert_eq!(stretch.layout(node).unwrap().size.width, 100f32); + assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); + assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); + assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); + assert_eq!(stretch.layout(node1).unwrap().size.width, 10f32); + assert_eq!(stretch.layout(node1).unwrap().size.height, 10f32); + assert_eq!(stretch.layout(node1).unwrap().location.x, 0f32); + assert_eq!(stretch.layout(node1).unwrap().location.y, 0f32); + assert_eq!(stretch.layout(node0).unwrap().size.width, 100f32); + assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); + assert_eq!(stretch.layout(node0).unwrap().location.x, 0f32); + assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); + } +} From 26d92b6e51d34f1c898896e74be8883cf3310c9d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 23 Mar 2022 14:18:17 -0500 Subject: [PATCH 02/33] rebase master --- Cargo.toml | 4 + examples/tui_color_test.rs | 56 +- examples/tui_readme.rs | 17 +- examples/tui_text.rs | 2 +- packages/core/src/events.rs | 2 +- .../src/layout_attributes.rs | 2 +- packages/native-core/src/lib.rs | 37 +- packages/tui/src/hooks.rs | 512 +++++++++++------- packages/tui/src/layout.rs | 5 +- packages/tui/src/lib.rs | 116 ++-- packages/tui/src/render.rs | 18 +- packages/tui/src/style_attributes.rs | 11 +- packages/tui/src/widget.rs | 4 +- packages/tui/tests/relayout.rs | 61 ++- 14 files changed, 529 insertions(+), 318 deletions(-) rename packages/{tui => native-core}/src/layout_attributes.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index e5ec20306..5d6f01506 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "packages/fermi", "packages/tui", "packages/liveview", + "packages/native-core", ] [dev-dependencies] @@ -88,3 +89,6 @@ harness = false [[bench]] name = "jsframework" harness = false + +[profile.release] +debug = true \ No newline at end of file diff --git a/examples/tui_color_test.rs b/examples/tui_color_test.rs index 358fae1f6..b110ecef0 100644 --- a/examples/tui_color_test.rs +++ b/examples/tui_color_test.rs @@ -4,11 +4,54 @@ fn main() { dioxus::tui::launch_cfg( app, dioxus::tui::Config { - rendering_mode: dioxus::tui::RenderingMode::Ansi, + rendering_mode: dioxus::tui::RenderingMode::Rgb, }, ); } +#[derive(Props, PartialEq)] +struct BoxProps { + x: i32, + y: i32, + hue: f32, + alpha: f32, +} +fn Box(cx: Scope) -> Element { + let painted = use_state(&cx, || true); + + // use_future(&cx, (), move |_| { + // let count = count.to_owned(); + // let update = cx.schedule_update(); + // async move { + // loop { + // count.with_mut(|i| *i += 1); + // tokio::time::sleep(std::time::Duration::from_millis(800)).await; + // update(); + // } + // } + // }); + + let x = cx.props.x; + let y = cx.props.y; + let hue = cx.props.hue; + let current_painted = painted.get(); + let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 }; + + cx.render(rsx! { + div { + left: "{x}px", + top: "{y}px", + width: "100%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + align_items: "center", + onkeydown: |_| painted.with_mut(|i| *i = !*i), + onmouseenter: |_| painted.with_mut(|i| *i = !*i), + p{" "} + } + }) +} + fn app(cx: Scope) -> Element { let steps = 50; cx.render(rsx! { @@ -28,12 +71,11 @@ fn app(cx: Scope) -> Element { { let alpha = y as f32*100.0/steps as f32; cx.render(rsx! { - div { - left: "{x}px", - top: "{y}px", - width: "10%", - height: "100%", - background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + Box{ + x: x, + y: y, + alpha: alpha, + hue: hue, } }) } diff --git a/examples/tui_readme.rs b/examples/tui_readme.rs index 3e15cf7e3..21aa6c996 100644 --- a/examples/tui_readme.rs +++ b/examples/tui_readme.rs @@ -5,15 +5,26 @@ fn main() { } fn app(cx: Scope) -> Element { + let alpha = use_state(&cx, || 100); + cx.render(rsx! { div { + onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), + width: "100%", height: "10px", background_color: "red", - justify_content: "center", - align_items: "center", + // justify_content: "center", + // align_items: "center", - "Hello world!" + p{ + color: "rgba(0, 255, 0, {alpha}%)", + "Hello world!" + } + p{ + "{alpha}" + } + // p{"Hi"} } }) } diff --git a/examples/tui_text.rs b/examples/tui_text.rs index b8f04d99a..95f9864a9 100644 --- a/examples/tui_text.rs +++ b/examples/tui_text.rs @@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element { onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), p { - background_color: "black", + // background_color: "black", flex_direction: "column", justify_content: "center", align_items: "center", diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 1a9899e4a..22889a96b 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -49,7 +49,7 @@ impl BubbleState { /// } /// )).unwrap(); /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct UserEvent { /// The originator of the event trigger if available pub scope_id: Option, diff --git a/packages/tui/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs similarity index 99% rename from packages/tui/src/layout_attributes.rs rename to packages/native-core/src/layout_attributes.rs index 4c38b1409..afe7d177c 100644 --- a/packages/tui/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -29,7 +29,7 @@ - [ ] pub aspect_ratio: Number, */ -use stretch2::{prelude::*, style::PositionType, style::Style}; +use stretch2::{prelude::*, style::PositionType}; /// applies the entire html namespace defined in dioxus-html pub fn apply_layout_attributes( diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index a1eb805a2..88dde28fc 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +pub mod layout_attributes; /// A tree that can sync with dioxus mutations backed by a hashmap. /// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. @@ -10,7 +11,7 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; pub struct Tree { pub root: usize, pub nodes: Vec>>, - pub listeners: HashMap>, + pub nodes_listening: HashMap<&'static str, HashSet>, } impl Tree { @@ -29,7 +30,7 @@ impl Tree { ))); v }, - listeners: HashMap::new(), + nodes_listening: HashMap::new(), } } @@ -128,17 +129,17 @@ impl Tree { scope: _, root, } => { - if let Some(v) = self.listeners.get_mut(&(root as usize)) { - v.insert(event_name); + if let Some(v) = self.nodes_listening.get_mut(event_name) { + v.insert(root as usize); } else { let mut hs = HashSet::new(); - hs.insert(event_name); - self.listeners.insert(root as usize, hs); + hs.insert(root as usize); + self.nodes_listening.insert(event_name, hs); } } RemoveEventListener { root, event } => { - let v = self.listeners.get_mut(&(root as usize)).unwrap(); - v.remove(event); + let v = self.nodes_listening.get_mut(event).unwrap(); + v.remove(&(root as usize)); } SetText { root, @@ -210,10 +211,8 @@ impl Tree { to_rerender.insert(id); if let Some(p) = parent { let i = to_bubble.partition_point(|(_, h)| *h < height - 1); - // println!("{i}"); - // println!("{to_bubble:?}"); // make sure the parent is not already queued - if to_bubble.len() == 0 || to_bubble.get(i).unwrap().0 != p.0 { + if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 { to_bubble.insert(i, (p.0, height - 1)); } } @@ -307,10 +306,20 @@ impl Tree { fn get_mut(&mut self, id: usize) -> &mut TreeNode { self.nodes.get_mut(id).unwrap().as_mut().unwrap() } + + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { + if let Some(nodes) = self.nodes_listening.get(event) { + let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect(); + listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); + listening + } else { + Vec::new() + } + } } /// The node is stored client side and stores render data -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TreeNode { pub id: ElementId, pub parent: Option, @@ -320,7 +329,7 @@ pub struct TreeNode { pub height: u16, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TreeNodeType { Text { text: String, @@ -505,7 +514,7 @@ fn test_insert() { ))); v }, - listeners: HashMap::new(), + nodes_listening: HashMap::new(), }; println!("{:?}", mutations); let to_update = tree.apply_mutations(vec![mutations.0]); diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 876d7105f..d65123d0c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,12 +4,12 @@ use crossterm::event::{ use dioxus_core::*; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::{Tree, TreeNodeType}; +use dioxus_native_core::{Tree, TreeNode}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, cell::RefCell, - collections::HashSet, + collections::{HashMap, HashSet}, rc::Rc, sync::Arc, time::{Duration, Instant}, @@ -163,12 +163,35 @@ impl InnerInputState { fn update<'a>( &mut self, - dom: &'a VirtualDom, evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - layouts: &mut Tree, - node: &'a VNode<'a>, + tree: &mut Tree, + ) { + let previous_mouse = self + .mouse + .as_ref() + .map(|m| (clone_mouse_data(&m.0), m.1.clone())); + + self.wheel = None; + + for e in evts.iter_mut() { + self.apply_event(e); + } + + self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); + + // for s in &self.subscribers { + // s(); + // } + } + + fn resolve_mouse_events( + &self, + previous_mouse: Option<(MouseData, Vec)>, + resolved_events: &mut Vec, + layout: &Stretch, + tree: &mut Tree, ) { struct Data<'b> { new_pos: (i32, i32), @@ -187,136 +210,31 @@ impl InnerInputState { && layout.location.y as i32 + layout.size.height as i32 >= point.1 } - fn get_mouse_events<'c, 'd>( - dom: &'c VirtualDom, + fn try_create_event( + name: &'static str, + data: Arc, + will_bubble: &mut HashSet, resolved_events: &mut Vec, - layout: &Stretch, - layouts: &Tree, - node: &'c VNode<'c>, - data: &'d Data<'d>, - ) -> HashSet<&'static str> { - match node { - VNode::Fragment(f) => { - let mut union = HashSet::new(); - for child in f.children { - union = union - .union(&get_mouse_events( - dom, - resolved_events, - layout, - layouts, - child, - data, - )) - .copied() - .collect(); - } - return union; + node: &TreeNode, + tree: &Tree, + ) { + // only trigger event if the event was not triggered already by a child + if will_bubble.insert(node.id) { + let mut parent = node.parent; + while let Some(parent_id) = parent { + will_bubble.insert(parent_id); + parent = tree.get(parent_id.0).parent; } - - VNode::Component(vcomp) => { - let idx = vcomp.scope.get().unwrap(); - let new_node = dom.get_scope(idx).unwrap().root_node(); - return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data); - } - - VNode::Placeholder(_) => return HashSet::new(), - - VNode::Element(_) | VNode::Text(_) => {} - } - - let id = node.try_mounted_id().unwrap(); - let node = layouts.get(id.0); - - let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); - - let previously_contained = data - .old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) - .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); - - match &node.node_type { - TreeNodeType::Element { children, .. } => { - let mut events = HashSet::new(); - if previously_contained || currently_contains { - for c in children { - events = events - .union(&get_mouse_events( - dom, - resolved_events, - layout, - layouts, - dom.get_element(*c).unwrap(), - data, - )) - .copied() - .collect(); - } - } - let mut try_create_event = |name| { - // only trigger event if the event was not triggered already by a child - if events.insert(name) { - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name, - element: Some(node.id), - data: Arc::new(clone_mouse_data(data.mouse_data)), - }) - } - }; - if currently_contains { - if !previously_contained { - try_create_event("mouseenter"); - try_create_event("mouseover"); - } - if data.clicked { - try_create_event("mousedown"); - } - if data.released { - try_create_event("mouseup"); - match data.mouse_data.button { - 0 => try_create_event("click"), - 2 => try_create_event("contextmenu"), - _ => (), - } - } - if let Some(w) = data.wheel_data { - if data.wheel_delta != 0.0 { - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: "wheel", - element: Some(node.id), - data: Arc::new(clone_wheel_data(w)), - }) - } - } - } else if previously_contained { - try_create_event("mouseleave"); - try_create_event("mouseout"); - } - events - } - TreeNodeType::Text { .. } => HashSet::new(), - _ => todo!(), + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name, + element: Some(node.id), + data: data, + }) } } - let previous_mouse = self - .mouse - .as_ref() - .map(|m| (clone_mouse_data(&m.0), m.1.clone())); - // println!("{previous_mouse:?}"); - - self.wheel = None; - - for e in evts.iter_mut() { - self.apply_event(e); - } - - // resolve hover events if let Some(mouse) = &self.mouse { let new_pos = (mouse.0.screen_x, mouse.0.screen_y); let old_pos = previous_mouse @@ -338,12 +256,245 @@ impl InnerInputState { mouse_data, wheel_data, }; - get_mouse_events(dom, resolved_events, layout, layouts, node, &data); - } - // for s in &self.subscribers { - // s(); - // } + { + // mousemove + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mousemove") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let previously_contained = data + .old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if previously_contained { + try_create_event( + "mousemove", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // mouseenter + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mouseenter") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let previously_contained = data + .old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if !previously_contained { + try_create_event( + "mouseenter", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // mouseover + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mouseover") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let previously_contained = data + .old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if !previously_contained { + try_create_event( + "mouseover", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // mousedown + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mousedown") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if data.clicked { + try_create_event( + "mousedown", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // mouseup + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mouseup") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if data.released { + try_create_event( + "mouseup", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // click + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("click") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if data.released && data.mouse_data.button == 0 { + try_create_event( + "click", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // contextmenu + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("contextmenu") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if data.released && data.mouse_data.button == 2 { + try_create_event( + "contextmenu", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + + { + // wheel + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("wheel") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if currently_contains { + if let Some(w) = data.wheel_data { + if data.wheel_delta != 0.0 { + try_create_event( + "wheel", + Arc::new(clone_wheel_data(w)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } + } + + { + // mouseleave + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mouseleave") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let previously_contained = data + .old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if !currently_contains && previously_contained { + try_create_event( + "mouseleave", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + + { + // mouseout + let mut will_bubble = HashSet::new(); + for node in tree.get_listening_sorted("mouseout") { + let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); + let previously_contained = data + .old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, data.new_pos); + + if !currently_contains && previously_contained { + try_create_event( + "mouseout", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); + } + } + } + } } // fn subscribe(&mut self, f: Rc) { @@ -364,7 +515,7 @@ impl RinkInputHandler { cx: &ScopeState, ) -> (Self, Rc>) { let queued_events = Rc::new(RefCell::new(Vec::new())); - let queued_events2 = Rc::>>::downgrade(&queued_events); + let queued_events2 = Rc::downgrade(&queued_events); cx.push_future(async move { while let Some(evt) = receiver.next().await { @@ -391,68 +542,63 @@ impl RinkInputHandler { pub fn get_events<'a>( &self, - dom: &'a VirtualDom, layout: &Stretch, - layouts: &mut Tree, - node: &'a VNode<'a>, + tree: &mut Tree, ) -> Vec { - // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus - fn inner( - queue: &[(&'static str, Arc)], - resolved: &mut Vec, - node: &VNode, - ) { - match node { - VNode::Fragment(frag) => { - for c in frag.children { - inner(queue, resolved, c); - } - } - VNode::Element(el) => { - for l in el.listeners { - for (name, data) in queue.iter() { - if *name == l.event { - if let Some(id) = el.id.get() { - resolved.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: *name, - element: Some(id), - data: data.clone(), - }); - } - } - } - } - for c in el.children { - inner(queue, resolved, c); - } - } - _ => (), - } - } - let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( - dom, &mut (*self.queued_events).borrow_mut(), &mut resolved_events, layout, - layouts, - node, + tree, ); - let events: Vec<_> = self + let events = self .queued_events .replace(Vec::new()) .into_iter() // these events were added in the update stage - .filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0)) - .map(|e| (e.0, e.1.into_any())) - .collect(); + .filter(|e| { + ![ + "mouseenter", + "mouseover", + "mouseleave", + "mouseout", + "mousedown", + "mouseup", + "mousemove", + "drag", + "wheel", + "click", + "contextmenu", + ] + .contains(&e.0) + }) + .map(|evt| (evt.0, evt.1.into_any())); - inner(&events, &mut resolved_events, node); + // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus + let mut hm: HashMap<&'static str, Vec>> = HashMap::new(); + for (event, data) in events { + if let Some(v) = hm.get_mut(event) { + v.push(data); + } else { + hm.insert(event, vec![data]); + } + } + for (event, datas) in hm { + for node in tree.get_listening_sorted(event) { + for data in &datas { + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: event, + element: Some(node.id), + data: data.clone(), + }); + } + } + } resolved_events } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 77247ce30..ced248dc9 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -2,7 +2,7 @@ use dioxus_core::*; use dioxus_native_core::BubbledUpState; use stretch2::prelude::*; -use crate::layout_attributes::apply_layout_attributes; +use dioxus_native_core::layout_attributes::apply_layout_attributes; /* The layout system uses the lineheight as one point. @@ -42,7 +42,6 @@ impl BubbledUpState for RinkLayout { if let Some(n) = self.node { if self.style != style { - panic!("new style: {style:?}"); stretch.set_style(n, style).unwrap(); } } else { @@ -74,11 +73,9 @@ impl BubbledUpState for RinkLayout { if let Some(n) = self.node { if &stretch.children(n).unwrap() != &child_layout { - panic!("new children: {child_layout:?}"); stretch.set_children(n, &child_layout).unwrap(); } if self.style != style { - panic!("new style: {style:?}"); stretch.set_style(n, style).unwrap(); } } else { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 95c6d8b56..3893ab423 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,3 +1,6 @@ +// notes: +// mouse events binary search was broken for absolutely positioned elements + use anyhow::Result; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, @@ -6,23 +9,19 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; +use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData}; use dioxus_native_core::Tree; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; -use std::{ - io, - time::{Duration, Instant}, -}; -use stretch2::{ - prelude::{Layout, Size}, - Stretch, -}; +use std::collections::HashSet; +use std::{io, time::Duration}; +use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; +use tokio::time::Instant; use tui::{backend::CrosstermBackend, Terminal}; mod config; mod hooks; mod layout; -mod layout_attributes; mod render; mod style; mod style_attributes; @@ -31,7 +30,6 @@ mod widget; pub use config::*; pub use hooks::*; pub use layout::*; -pub use layout_attributes::*; pub use render::*; pub fn launch(app: Component<()>) { @@ -104,6 +102,12 @@ pub fn render_vdom( let mut terminal = Terminal::new(backend).unwrap(); terminal.clear().unwrap(); + let mut to_rerender: HashSet = tree + .nodes + .iter() + .filter_map(|n| n.as_ref()) + .map(|n| n.id.0) + .collect(); loop { /* @@ -115,54 +119,51 @@ pub fn render_vdom( use simd to compare lines for diffing? - - todo: reuse the layout and node objects. - our work_with_deadline method can tell us which nodes are dirty. + todo: lazy re-rendering */ - /* - Compute the layout given the terminal size - */ - let mut events = Vec::new(); + if !to_rerender.is_empty() { + terminal.draw(|frame| { + // size is guaranteed to not change when rendering + let dims = frame.size(); + // println!("{dims:?}"); + let width = dims.width; + let height = dims.height; + let root_id = tree.root; + let root_node = tree.get(root_id).up_state.node.unwrap(); + stretch + .compute_layout( + root_node, + Size { + width: stretch2::prelude::Number::Defined((width - 1) as f32), + height: stretch2::prelude::Number::Defined((height - 1) as f32), + }, + ) + .unwrap(); + let root = tree.get(tree.root); + render::render_vnode(frame, &stretch, &tree, &root, cfg); + })?; + } - terminal.draw(|frame| { - // size is guaranteed to not change when rendering - let dims = frame.size(); - println!("{dims:?}"); - let width = dims.width; - let height = dims.height; - let root_id = tree.root; - let root_node = tree.get(root_id).up_state.node.unwrap(); - stretch - .compute_layout( - root_node, - Size { - width: stretch2::prelude::Number::Defined((width - 1) as f32), - height: stretch2::prelude::Number::Defined((height - 1) as f32), - }, - ) - .unwrap(); - - // resolve events before rendering - events = handler.get_events( - vdom, - &stretch, - &mut tree, - vdom.base_scope().root_node(), - ); - for n in &tree.nodes { - if let Some(node) = n { - let Layout { location, size, .. } = - stretch.layout(node.up_state.node.unwrap()).unwrap(); - println!("{node:#?}"); - println!("\t{location:?}: {size:?}"); - } - } - let root = tree.get(tree.root); - // render::render_vnode(frame, &stretch, &tree, &root, cfg); - })?; - - for e in events { + // resolve events before rendering + // todo: events do not trigger update? + for e in handler.get_events(&stretch, &mut tree) { + let tname = if e.data.is::() { + "PointerData" + } else if e.data.is::() { + "WheelData" + } else if e.data.is::() { + "MouseData" + } else if e.data.is::() { + "KeyboardData" + } else if e.data.is::() { + "TouchData" + } else if e.data.is::<(u16, u16)>() { + "(u16, u16)" + } else { + panic!() + }; + // println!("{tname}: {e:?}"); vdom.handle_message(SchedulerMsg::Event(e)); } @@ -199,8 +200,10 @@ pub fn render_vdom( } let mutations = vdom.work_with_deadline(|| false); + // updates the tree's nodes let to_update = tree.apply_mutations(mutations); - let _to_rerender = tree + // update the style and layout + to_rerender = tree .update_state(&vdom, to_update, &mut stretch, &mut ()) .unwrap(); } @@ -220,7 +223,6 @@ pub fn render_vdom( enum InputEvent { UserInput(TermEvent), Tick, - #[allow(dead_code)] Close, } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 23d70f878..66ee1fd91 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{Tree, TreeNode}; +use dioxus_native_core::{layout_attributes::UnitSystem, Tree, TreeNode}; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -11,7 +11,7 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, RinkLayout, StyleModifier, UnitSystem, + Config, RinkLayout, StyleModifier, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; @@ -48,7 +48,7 @@ pub fn render_vnode<'a>( // println!("{:?}", self.style); new_cell.set_style(self.style); new_cell.symbol = c.to_string(); - buf.set(area.left() + i as u16, area.top(), &new_cell); + buf.set(area.left() + i as u16, area.top(), new_cell); } } } @@ -150,7 +150,7 @@ impl RinkWidget for &TreeNode { buf.set( (current[0] + pos[0] as i32) as u16, (current[1] + pos[1] as i32) as u16, - &new_cell, + new_cell, ); } @@ -267,7 +267,7 @@ impl RinkWidget for &TreeNode { if let Some(c) = self.down_state.style.bg { new_cell.bg = c; } - buf.set(x, y, &new_cell); + buf.set(x, y, new_cell); } } @@ -295,7 +295,7 @@ impl RinkWidget for &TreeNode { } for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) { new_cell.symbol = symbols.horizontal.to_string(); - buf.set(x, area.top(), &new_cell); + buf.set(x, area.top(), new_cell.clone()); } draw_arc( [area.right() - radius[0] - 1, area.top() + radius[1]], @@ -330,7 +330,7 @@ impl RinkWidget for &TreeNode { } for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) { new_cell.symbol = symbols.vertical.to_string(); - buf.set(area.right() - 1, y, &new_cell); + buf.set(area.right() - 1, y, new_cell.clone()); } draw_arc( [area.right() - radius[0] - 1, area.bottom() - radius[1] - 1], @@ -365,7 +365,7 @@ impl RinkWidget for &TreeNode { } for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) { new_cell.symbol = symbols.horizontal.to_string(); - buf.set(x, area.bottom() - 1, &new_cell); + buf.set(x, area.bottom() - 1, new_cell.clone()); } draw_arc( [area.left() + radius[0], area.bottom() - radius[1] - 1], @@ -400,7 +400,7 @@ impl RinkWidget for &TreeNode { } for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) { new_cell.symbol = symbols.vertical.to_string(); - buf.set(area.left(), y, &new_cell); + buf.set(area.left(), y, new_cell.clone()); } draw_arc( [area.left() + radius[0], area.top() + radius[1]], diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index d365af75d..907a066de 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -30,14 +30,13 @@ */ use dioxus_core::{Attribute, VNode}; -use dioxus_native_core::PushedDownState; - -use crate::{ - parse_value, - style::{RinkColor, RinkStyle}, - UnitSystem, +use dioxus_native_core::{ + layout_attributes::{parse_value, UnitSystem}, + PushedDownState, }; +use crate::style::{RinkColor, RinkStyle}; + #[derive(Default, Clone, PartialEq, Debug)] pub struct StyleModifier { pub style: RinkStyle, diff --git a/packages/tui/src/widget.rs b/packages/tui/src/widget.rs index 1b476a412..b0c96d607 100644 --- a/packages/tui/src/widget.rs +++ b/packages/tui/src/widget.rs @@ -20,7 +20,7 @@ impl<'a> RinkBuffer<'a> { Self { buf, cfg } } - pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) { + pub fn set(&mut self, x: u16, y: u16, new: RinkCell) { let area = self.buf.area(); if x < area.x || x > area.width || y < area.y || y > area.height { panic!("({x}, {y}) is not in {area:?}"); @@ -34,7 +34,7 @@ impl<'a> RinkBuffer<'a> { } } else { cell.modifier = new.modifier; - cell.symbol = new.symbol.clone(); + cell.symbol = new.symbol; cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg)); } } diff --git a/packages/tui/tests/relayout.rs b/packages/tui/tests/relayout.rs index 83b2a46da..2ed73901a 100644 --- a/packages/tui/tests/relayout.rs +++ b/packages/tui/tests/relayout.rs @@ -1,3 +1,4 @@ +use stretch::style::Dimension; use stretch2 as stretch; #[test] @@ -6,14 +7,7 @@ fn relayout() { let node1 = stretch .new_node( stretch::style::Style { - position: stretch::geometry::Point { - x: stretch::style::Dimension::Points(10f32), - y: stretch::style::Dimension::Points(10f32), - }, - size: stretch::geometry::Size { - width: stretch::style::Dimension::Points(10f32), - height: stretch::style::Dimension::Points(10f32), - }, + size: stretch::geometry::Size { width: Dimension::Points(8f32), height: Dimension::Points(80f32) }, ..Default::default() }, &[], @@ -22,10 +16,9 @@ fn relayout() { let node0 = stretch .new_node( stretch::style::Style { - size: stretch::geometry::Size { - width: stretch::style::Dimension::Percent(1f32), - height: stretch::style::Dimension::Percent(1f32), - }, + align_self: stretch::prelude::AlignSelf::Center, + size: stretch::geometry::Size { width: Dimension::Auto, height: Dimension::Auto }, + // size: stretch::geometry::Size { width: Dimension::Percent(1.0), height: Dimension::Percent(1.0) }, ..Default::default() }, &[node1], @@ -34,30 +27,38 @@ fn relayout() { let node = stretch .new_node( stretch::style::Style { - size: stretch::geometry::Size { - width: stretch::style::Dimension::Points(100f32), - height: stretch::style::Dimension::Points(100f32), - }, + size: stretch::geometry::Size { width: Dimension::Percent(1f32), height: Dimension::Percent(1f32) }, ..Default::default() }, &[node0], ) .unwrap(); - for _ in 0..10 { + println!("0:"); + stretch + .compute_layout( + node, + stretch::geometry::Size { + width: stretch::prelude::Number::Defined(100f32), + height: stretch::prelude::Number::Defined(100f32), + }, + ) + .unwrap(); + let initial = stretch.layout(node).unwrap().location; + let initial0 = stretch.layout(node0).unwrap().location; + let initial1 = stretch.layout(node1).unwrap().location; + for i in 1..10 { + println!("\n\n{i}:"); stretch - .compute_layout(node, stretch::geometry::Size::undefined()) + .compute_layout( + node, + stretch::geometry::Size { + width: stretch::prelude::Number::Defined(100f32), + height: stretch::prelude::Number::Defined(100f32), + }, + ) .unwrap(); - assert_eq!(stretch.layout(node).unwrap().size.width, 100f32); - assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); - assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); - assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); - assert_eq!(stretch.layout(node1).unwrap().size.width, 10f32); - assert_eq!(stretch.layout(node1).unwrap().size.height, 10f32); - assert_eq!(stretch.layout(node1).unwrap().location.x, 0f32); - assert_eq!(stretch.layout(node1).unwrap().location.y, 0f32); - assert_eq!(stretch.layout(node0).unwrap().size.width, 100f32); - assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); - assert_eq!(stretch.layout(node0).unwrap().location.x, 0f32); - assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); + assert_eq!(stretch.layout(node).unwrap().location, initial); + assert_eq!(stretch.layout(node0).unwrap().location, initial0); + assert_eq!(stretch.layout(node1).unwrap().location, initial1); } } From 6adfa8805c308bee1addb776225393f3413827e1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 23 Mar 2022 14:33:06 -0500 Subject: [PATCH 03/33] moved stretch layout to native-core --- packages/{tui => native-core}/src/layout.rs | 8 ++--- packages/native-core/src/lib.rs | 1 + packages/tui/src/hooks.rs | 14 ++++---- packages/tui/src/lib.rs | 38 ++++++++++----------- packages/tui/src/render.rs | 10 +++--- 5 files changed, 35 insertions(+), 36 deletions(-) rename packages/{tui => native-core}/src/layout.rs (94%) diff --git a/packages/tui/src/layout.rs b/packages/native-core/src/layout.rs similarity index 94% rename from packages/tui/src/layout.rs rename to packages/native-core/src/layout.rs index ced248dc9..9e6dd90ea 100644 --- a/packages/tui/src/layout.rs +++ b/packages/native-core/src/layout.rs @@ -1,8 +1,8 @@ +use crate::BubbledUpState; use dioxus_core::*; -use dioxus_native_core::BubbledUpState; use stretch2::prelude::*; -use dioxus_native_core::layout_attributes::apply_layout_attributes; +use crate::layout_attributes::apply_layout_attributes; /* The layout system uses the lineheight as one point. @@ -11,12 +11,12 @@ stretch uses fractional points, so we can rasterize if we need too, but not with this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger */ #[derive(Clone, PartialEq, Default, Debug)] -pub struct RinkLayout { +pub struct StretchLayout { pub style: Style, pub node: Option, } -impl BubbledUpState for RinkLayout { +impl BubbledUpState for StretchLayout { type Ctx = Stretch; // Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 88dde28fc..67e888500 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +pub mod layout; pub mod layout_attributes; /// A tree that can sync with dioxus mutations backed by a hashmap. diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index d65123d0c..2184dcd06 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,7 +4,7 @@ use crossterm::event::{ use dioxus_core::*; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::{Tree, TreeNode}; +use dioxus_native_core::{layout::StretchLayout, Tree, TreeNode}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, @@ -16,7 +16,7 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; -use crate::{style_attributes::StyleModifier, RinkLayout}; +use crate::style_attributes::StyleModifier; // a wrapper around the input state for easier access // todo: fix loop @@ -166,7 +166,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut Tree, ) { let previous_mouse = self .mouse @@ -191,7 +191,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut Tree, ) { struct Data<'b> { new_pos: (i32, i32), @@ -215,8 +215,8 @@ impl InnerInputState { data: Arc, will_bubble: &mut HashSet, resolved_events: &mut Vec, - node: &TreeNode, - tree: &Tree, + node: &TreeNode, + tree: &Tree, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { @@ -543,7 +543,7 @@ impl RinkInputHandler { pub fn get_events<'a>( &self, layout: &Stretch, - tree: &mut Tree, + tree: &mut Tree, ) -> Vec { let mut resolved_events = Vec::new(); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 3893ab423..f3828aac1 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -10,7 +10,7 @@ use crossterm::{ use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData}; -use dioxus_native_core::Tree; +use dioxus_native_core::{layout::StretchLayout, Tree}; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; use std::collections::HashSet; use std::{io, time::Duration}; @@ -21,7 +21,6 @@ use tui::{backend::CrosstermBackend, Terminal}; mod config; mod hooks; -mod layout; mod render; mod style; mod style_attributes; @@ -29,7 +28,6 @@ mod widget; pub use config::*; pub use hooks::*; -pub use layout::*; pub use render::*; pub fn launch(app: Component<()>) { @@ -46,7 +44,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); - let mut tree: Tree = Tree::new(); + let mut tree: Tree = Tree::new(); let mutations = dom.rebuild(); let to_update = tree.apply_mutations(vec![mutations]); let mut stretch = Stretch::new(); @@ -62,7 +60,7 @@ pub fn render_vdom( ctx: UnboundedSender, handler: RinkInputHandler, cfg: Config, - mut tree: Tree, + mut tree: Tree, mut stretch: Stretch, ) -> Result<()> { // Setup input handling @@ -148,21 +146,21 @@ pub fn render_vdom( // resolve events before rendering // todo: events do not trigger update? for e in handler.get_events(&stretch, &mut tree) { - let tname = if e.data.is::() { - "PointerData" - } else if e.data.is::() { - "WheelData" - } else if e.data.is::() { - "MouseData" - } else if e.data.is::() { - "KeyboardData" - } else if e.data.is::() { - "TouchData" - } else if e.data.is::<(u16, u16)>() { - "(u16, u16)" - } else { - panic!() - }; + // let tname = if e.data.is::() { + // "PointerData" + // } else if e.data.is::() { + // "WheelData" + // } else if e.data.is::() { + // "MouseData" + // } else if e.data.is::() { + // "KeyboardData" + // } else if e.data.is::() { + // "TouchData" + // } else if e.data.is::<(u16, u16)>() { + // "(u16, u16)" + // } else { + // panic!() + // }; // println!("{tname}: {e:?}"); vdom.handle_message(SchedulerMsg::Event(e)); } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 66ee1fd91..7be17ff82 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::{layout_attributes::UnitSystem, Tree, TreeNode}; +use dioxus_native_core::{layout::StretchLayout, layout_attributes::UnitSystem, Tree, TreeNode}; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -11,7 +11,7 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, RinkLayout, StyleModifier, + Config, StyleModifier, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; @@ -19,8 +19,8 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode<'a>( frame: &mut tui::Frame>, layout: &Stretch, - tree: &Tree, - node: &TreeNode, + tree: &Tree, + node: &TreeNode, cfg: Config, ) { match &node.node_type { @@ -80,7 +80,7 @@ pub fn render_vnode<'a>( } } -impl RinkWidget for &TreeNode { +impl RinkWidget for &TreeNode { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; From 7f4e25775781b04df3371dec5c71dfef3e657aea Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Mar 2022 20:10:15 -0500 Subject: [PATCH 04/33] bugfixes, testing and refactoring --- .vscode/launch.json | 2541 +++++++++++++++++++ examples/tui_color_test.rs | 56 +- examples/tui_readme.rs | 17 +- examples/tui_stress_test.rs | 88 + examples/tui_text.rs | 2 +- packages/core/src/diff.rs | 1 + packages/native-core/Cargo.toml | 9 +- packages/native-core/src/client_tree.rs | 580 +++++ packages/native-core/src/layout.rs | 5 +- packages/native-core/src/lib.rs | 526 +--- packages/native-core/tests/change_nodes.rs | 153 ++ packages/native-core/tests/initial_build.rs | 124 + packages/native-core/tests/state.rs | 320 +++ packages/tui/Cargo.toml | 4 +- packages/tui/src/hooks.rs | 41 +- packages/tui/src/lib.rs | 44 +- packages/tui/src/render.rs | 20 +- packages/tui/src/style_attributes.rs | 6 +- packages/tui/src/utils.rs | 83 - packages/tui/tests/margin.rs | 7 + 20 files changed, 3888 insertions(+), 739 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 examples/tui_stress_test.rs create mode 100644 packages/native-core/src/client_tree.rs create mode 100644 packages/native-core/tests/change_nodes.rs create mode 100644 packages/native-core/tests/initial_build.rs create mode 100644 packages/native-core/tests/state.rs delete mode 100644 packages/tui/src/utils.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..fd829da72 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,2541 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-core'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-core" + ], + "filter": { + "name": "dioxus-core", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-rsx'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-rsx" + ], + "filter": { + "name": "dioxus-rsx", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-html'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-html" + ], + "filter": { + "name": "dioxus-html", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-hooks'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-hooks" + ], + "filter": { + "name": "dioxus-hooks", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-web'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-web" + ], + "filter": { + "name": "dioxus-web", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hydrate'", + "cargo": { + "args": [ + "build", + "--example=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hydrate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'hydrate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-interpreter-js'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-interpreter-js" + ], + "filter": { + "name": "dioxus-interpreter-js", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-ssr'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-ssr" + ], + "filter": { + "name": "dioxus-ssr", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'renders'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=renders", + "--package=dioxus-ssr" + ], + "filter": { + "name": "renders", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-desktop'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-desktop" + ], + "filter": { + "name": "dioxus-desktop", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-mobile'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-mobile" + ], + "filter": { + "name": "dioxus-mobile", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=fermi" + ], + "filter": { + "name": "fermi", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-tui'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-tui" + ], + "filter": { + "name": "dioxus-tui", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'margin'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=margin", + "--package=dioxus-tui" + ], + "filter": { + "name": "margin", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-native-core'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-native-core" + ], + "filter": { + "name": "dioxus-native-core", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus" + ], + "filter": { + "name": "dioxus", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'all_css'", + "cargo": { + "args": [ + "build", + "--example=all_css", + "--package=dioxus" + ], + "filter": { + "name": "all_css", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'all_css'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=all_css", + "--package=dioxus" + ], + "filter": { + "name": "all_css", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'borrowed'", + "cargo": { + "args": [ + "build", + "--example=borrowed", + "--package=dioxus" + ], + "filter": { + "name": "borrowed", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'borrowed'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=borrowed", + "--package=dioxus" + ], + "filter": { + "name": "borrowed", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'calculator'", + "cargo": { + "args": [ + "build", + "--example=calculator", + "--package=dioxus" + ], + "filter": { + "name": "calculator", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'calculator'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=calculator", + "--package=dioxus" + ], + "filter": { + "name": "calculator", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'crm'", + "cargo": { + "args": [ + "build", + "--example=crm", + "--package=dioxus" + ], + "filter": { + "name": "crm", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'crm'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=crm", + "--package=dioxus" + ], + "filter": { + "name": "crm", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'custom_assets'", + "cargo": { + "args": [ + "build", + "--example=custom_assets", + "--package=dioxus" + ], + "filter": { + "name": "custom_assets", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'custom_assets'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=custom_assets", + "--package=dioxus" + ], + "filter": { + "name": "custom_assets", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'disabled'", + "cargo": { + "args": [ + "build", + "--example=disabled", + "--package=dioxus" + ], + "filter": { + "name": "disabled", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'disabled'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=disabled", + "--package=dioxus" + ], + "filter": { + "name": "disabled", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'dog_app'", + "cargo": { + "args": [ + "build", + "--example=dog_app", + "--package=dioxus" + ], + "filter": { + "name": "dog_app", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'dog_app'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=dog_app", + "--package=dioxus" + ], + "filter": { + "name": "dog_app", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'error_handle'", + "cargo": { + "args": [ + "build", + "--example=error_handle", + "--package=dioxus" + ], + "filter": { + "name": "error_handle", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'error_handle'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=error_handle", + "--package=dioxus" + ], + "filter": { + "name": "error_handle", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'fermi'", + "cargo": { + "args": [ + "build", + "--example=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'filedragdrop'", + "cargo": { + "args": [ + "build", + "--example=filedragdrop", + "--package=dioxus" + ], + "filter": { + "name": "filedragdrop", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'filedragdrop'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=filedragdrop", + "--package=dioxus" + ], + "filter": { + "name": "filedragdrop", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'file_explorer'", + "cargo": { + "args": [ + "build", + "--example=file_explorer", + "--package=dioxus" + ], + "filter": { + "name": "file_explorer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'file_explorer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=file_explorer", + "--package=dioxus" + ], + "filter": { + "name": "file_explorer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'flat_router'", + "cargo": { + "args": [ + "build", + "--example=flat_router", + "--package=dioxus" + ], + "filter": { + "name": "flat_router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'flat_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=flat_router", + "--package=dioxus" + ], + "filter": { + "name": "flat_router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'form'", + "cargo": { + "args": [ + "build", + "--example=form", + "--package=dioxus" + ], + "filter": { + "name": "form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'form'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=form", + "--package=dioxus" + ], + "filter": { + "name": "form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'framework_benchmark'", + "cargo": { + "args": [ + "build", + "--example=framework_benchmark", + "--package=dioxus" + ], + "filter": { + "name": "framework_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'framework_benchmark'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=framework_benchmark", + "--package=dioxus" + ], + "filter": { + "name": "framework_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'heavy_compute'", + "cargo": { + "args": [ + "build", + "--example=heavy_compute", + "--package=dioxus" + ], + "filter": { + "name": "heavy_compute", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'heavy_compute'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=heavy_compute", + "--package=dioxus" + ], + "filter": { + "name": "heavy_compute", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hello_world'", + "cargo": { + "args": [ + "build", + "--example=hello_world", + "--package=dioxus" + ], + "filter": { + "name": "hello_world", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hello_world'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hello_world", + "--package=dioxus" + ], + "filter": { + "name": "hello_world", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hydration'", + "cargo": { + "args": [ + "build", + "--example=hydration", + "--package=dioxus" + ], + "filter": { + "name": "hydration", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hydration'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hydration", + "--package=dioxus" + ], + "filter": { + "name": "hydration", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'inputs'", + "cargo": { + "args": [ + "build", + "--example=inputs", + "--package=dioxus" + ], + "filter": { + "name": "inputs", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'inputs'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=inputs", + "--package=dioxus" + ], + "filter": { + "name": "inputs", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'link'", + "cargo": { + "args": [ + "build", + "--example=link", + "--package=dioxus" + ], + "filter": { + "name": "link", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'link'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=link", + "--package=dioxus" + ], + "filter": { + "name": "link", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'login_form'", + "cargo": { + "args": [ + "build", + "--example=login_form", + "--package=dioxus" + ], + "filter": { + "name": "login_form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'login_form'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=login_form", + "--package=dioxus" + ], + "filter": { + "name": "login_form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'nested_listeners'", + "cargo": { + "args": [ + "build", + "--example=nested_listeners", + "--package=dioxus" + ], + "filter": { + "name": "nested_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'nested_listeners'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=nested_listeners", + "--package=dioxus" + ], + "filter": { + "name": "nested_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'optional_props'", + "cargo": { + "args": [ + "build", + "--example=optional_props", + "--package=dioxus" + ], + "filter": { + "name": "optional_props", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'optional_props'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=optional_props", + "--package=dioxus" + ], + "filter": { + "name": "optional_props", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'pattern_model'", + "cargo": { + "args": [ + "build", + "--example=pattern_model", + "--package=dioxus" + ], + "filter": { + "name": "pattern_model", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'pattern_model'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=pattern_model", + "--package=dioxus" + ], + "filter": { + "name": "pattern_model", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'pattern_reducer'", + "cargo": { + "args": [ + "build", + "--example=pattern_reducer", + "--package=dioxus" + ], + "filter": { + "name": "pattern_reducer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'pattern_reducer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=pattern_reducer", + "--package=dioxus" + ], + "filter": { + "name": "pattern_reducer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'readme'", + "cargo": { + "args": [ + "build", + "--example=readme", + "--package=dioxus" + ], + "filter": { + "name": "readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'readme'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=readme", + "--package=dioxus" + ], + "filter": { + "name": "readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'router'", + "cargo": { + "args": [ + "build", + "--example=router", + "--package=dioxus" + ], + "filter": { + "name": "router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=router", + "--package=dioxus" + ], + "filter": { + "name": "router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'rsx_compile_fail'", + "cargo": { + "args": [ + "build", + "--example=rsx_compile_fail", + "--package=dioxus" + ], + "filter": { + "name": "rsx_compile_fail", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'rsx_compile_fail'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=rsx_compile_fail", + "--package=dioxus" + ], + "filter": { + "name": "rsx_compile_fail", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'rsx_usage'", + "cargo": { + "args": [ + "build", + "--example=rsx_usage", + "--package=dioxus" + ], + "filter": { + "name": "rsx_usage", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'rsx_usage'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=rsx_usage", + "--package=dioxus" + ], + "filter": { + "name": "rsx_usage", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'ssr'", + "cargo": { + "args": [ + "build", + "--example=ssr", + "--package=dioxus" + ], + "filter": { + "name": "ssr", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'ssr'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=ssr", + "--package=dioxus" + ], + "filter": { + "name": "ssr", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'suspense'", + "cargo": { + "args": [ + "build", + "--example=suspense", + "--package=dioxus" + ], + "filter": { + "name": "suspense", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'suspense'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=suspense", + "--package=dioxus" + ], + "filter": { + "name": "suspense", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'svg'", + "cargo": { + "args": [ + "build", + "--example=svg", + "--package=dioxus" + ], + "filter": { + "name": "svg", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'svg'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=svg", + "--package=dioxus" + ], + "filter": { + "name": "svg", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tailwind'", + "cargo": { + "args": [ + "build", + "--example=tailwind", + "--package=dioxus" + ], + "filter": { + "name": "tailwind", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tailwind'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tailwind", + "--package=dioxus" + ], + "filter": { + "name": "tailwind", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tasks'", + "cargo": { + "args": [ + "build", + "--example=tasks", + "--package=dioxus" + ], + "filter": { + "name": "tasks", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tasks'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tasks", + "--package=dioxus" + ], + "filter": { + "name": "tasks", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'textarea'", + "cargo": { + "args": [ + "build", + "--example=textarea", + "--package=dioxus" + ], + "filter": { + "name": "textarea", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'textarea'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=textarea", + "--package=dioxus" + ], + "filter": { + "name": "textarea", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'todomvc'", + "cargo": { + "args": [ + "build", + "--example=todomvc", + "--package=dioxus" + ], + "filter": { + "name": "todomvc", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'todomvc'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=todomvc", + "--package=dioxus" + ], + "filter": { + "name": "todomvc", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_border'", + "cargo": { + "args": [ + "build", + "--example=tui_border", + "--package=dioxus" + ], + "filter": { + "name": "tui_border", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_border'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_border", + "--package=dioxus" + ], + "filter": { + "name": "tui_border", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_color_test'", + "cargo": { + "args": [ + "build", + "--example=tui_color_test", + "--package=dioxus" + ], + "filter": { + "name": "tui_color_test", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_color_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_color_test", + "--package=dioxus" + ], + "filter": { + "name": "tui_color_test", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_components'", + "cargo": { + "args": [ + "build", + "--example=tui_components", + "--package=dioxus" + ], + "filter": { + "name": "tui_components", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_components'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_components", + "--package=dioxus" + ], + "filter": { + "name": "tui_components", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_frame'", + "cargo": { + "args": [ + "build", + "--example=tui_frame", + "--package=dioxus" + ], + "filter": { + "name": "tui_frame", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_frame'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_frame", + "--package=dioxus" + ], + "filter": { + "name": "tui_frame", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_hover'", + "cargo": { + "args": [ + "build", + "--example=tui_hover", + "--package=dioxus" + ], + "filter": { + "name": "tui_hover", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_hover'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_hover", + "--package=dioxus" + ], + "filter": { + "name": "tui_hover", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_keys'", + "cargo": { + "args": [ + "build", + "--example=tui_keys", + "--package=dioxus" + ], + "filter": { + "name": "tui_keys", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_keys'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_keys", + "--package=dioxus" + ], + "filter": { + "name": "tui_keys", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_list'", + "cargo": { + "args": [ + "build", + "--example=tui_list", + "--package=dioxus" + ], + "filter": { + "name": "tui_list", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_list'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_list", + "--package=dioxus" + ], + "filter": { + "name": "tui_list", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_margin'", + "cargo": { + "args": [ + "build", + "--example=tui_margin", + "--package=dioxus" + ], + "filter": { + "name": "tui_margin", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_margin'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_margin", + "--package=dioxus" + ], + "filter": { + "name": "tui_margin", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_quadrants'", + "cargo": { + "args": [ + "build", + "--example=tui_quadrants", + "--package=dioxus" + ], + "filter": { + "name": "tui_quadrants", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_quadrants'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_quadrants", + "--package=dioxus" + ], + "filter": { + "name": "tui_quadrants", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_readme'", + "cargo": { + "args": [ + "build", + "--example=tui_readme", + "--package=dioxus" + ], + "filter": { + "name": "tui_readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_readme'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_readme", + "--package=dioxus" + ], + "filter": { + "name": "tui_readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_task'", + "cargo": { + "args": [ + "build", + "--example=tui_task", + "--package=dioxus" + ], + "filter": { + "name": "tui_task", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_task'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_task", + "--package=dioxus" + ], + "filter": { + "name": "tui_task", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_text'", + "cargo": { + "args": [ + "build", + "--example=tui_text", + "--package=dioxus" + ], + "filter": { + "name": "tui_text", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_text'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_text", + "--package=dioxus" + ], + "filter": { + "name": "tui_text", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'window_event'", + "cargo": { + "args": [ + "build", + "--example=window_event", + "--package=dioxus" + ], + "filter": { + "name": "window_event", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'window_event'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=window_event", + "--package=dioxus" + ], + "filter": { + "name": "window_event", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'xss_safety'", + "cargo": { + "args": [ + "build", + "--example=xss_safety", + "--package=dioxus" + ], + "filter": { + "name": "xss_safety", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'xss_safety'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=xss_safety", + "--package=dioxus" + ], + "filter": { + "name": "xss_safety", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'borrowedstate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=borrowedstate", + "--package=dioxus" + ], + "filter": { + "name": "borrowedstate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'create_dom'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=create_dom", + "--package=dioxus" + ], + "filter": { + "name": "create_dom", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'diffing'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=diffing", + "--package=dioxus" + ], + "filter": { + "name": "diffing", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'earlyabort'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=earlyabort", + "--package=dioxus" + ], + "filter": { + "name": "earlyabort", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'lifecycle'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=lifecycle", + "--package=dioxus" + ], + "filter": { + "name": "lifecycle", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'miri_stress'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=miri_stress", + "--package=dioxus" + ], + "filter": { + "name": "miri_stress", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'passthru'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=passthru", + "--package=dioxus" + ], + "filter": { + "name": "passthru", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'scheduler'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=scheduler", + "--package=dioxus" + ], + "filter": { + "name": "scheduler", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'sharedstate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=sharedstate", + "--package=dioxus" + ], + "filter": { + "name": "sharedstate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'task'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=task", + "--package=dioxus" + ], + "filter": { + "name": "task", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'test_logging'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=test_logging", + "--package=dioxus" + ], + "filter": { + "name": "test_logging", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'vdom_rebuild'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=vdom_rebuild", + "--package=dioxus" + ], + "filter": { + "name": "vdom_rebuild", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'create'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=create", + "--package=dioxus" + ], + "filter": { + "name": "create", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'jsframework'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=jsframework", + "--package=dioxus" + ], + "filter": { + "name": "jsframework", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-router" + ], + "filter": { + "name": "dioxus-router", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'simple'", + "cargo": { + "args": [ + "build", + "--example=simple", + "--package=dioxus-router" + ], + "filter": { + "name": "simple", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'simple'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=simple", + "--package=dioxus-router" + ], + "filter": { + "name": "simple", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'ssr_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=ssr_router", + "--package=dioxus-router" + ], + "filter": { + "name": "ssr_router", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'web_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=web_router", + "--package=dioxus-router" + ], + "filter": { + "name": "web_router", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/examples/tui_color_test.rs b/examples/tui_color_test.rs index b110ecef0..358fae1f6 100644 --- a/examples/tui_color_test.rs +++ b/examples/tui_color_test.rs @@ -4,54 +4,11 @@ fn main() { dioxus::tui::launch_cfg( app, dioxus::tui::Config { - rendering_mode: dioxus::tui::RenderingMode::Rgb, + rendering_mode: dioxus::tui::RenderingMode::Ansi, }, ); } -#[derive(Props, PartialEq)] -struct BoxProps { - x: i32, - y: i32, - hue: f32, - alpha: f32, -} -fn Box(cx: Scope) -> Element { - let painted = use_state(&cx, || true); - - // use_future(&cx, (), move |_| { - // let count = count.to_owned(); - // let update = cx.schedule_update(); - // async move { - // loop { - // count.with_mut(|i| *i += 1); - // tokio::time::sleep(std::time::Duration::from_millis(800)).await; - // update(); - // } - // } - // }); - - let x = cx.props.x; - let y = cx.props.y; - let hue = cx.props.hue; - let current_painted = painted.get(); - let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 }; - - cx.render(rsx! { - div { - left: "{x}px", - top: "{y}px", - width: "100%", - height: "100%", - background_color: "hsl({hue}, 100%, 50%, {alpha}%)", - align_items: "center", - onkeydown: |_| painted.with_mut(|i| *i = !*i), - onmouseenter: |_| painted.with_mut(|i| *i = !*i), - p{" "} - } - }) -} - fn app(cx: Scope) -> Element { let steps = 50; cx.render(rsx! { @@ -71,11 +28,12 @@ fn app(cx: Scope) -> Element { { let alpha = y as f32*100.0/steps as f32; cx.render(rsx! { - Box{ - x: x, - y: y, - alpha: alpha, - hue: hue, + div { + left: "{x}px", + top: "{y}px", + width: "10%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", } }) } diff --git a/examples/tui_readme.rs b/examples/tui_readme.rs index 21aa6c996..3e15cf7e3 100644 --- a/examples/tui_readme.rs +++ b/examples/tui_readme.rs @@ -5,26 +5,15 @@ fn main() { } fn app(cx: Scope) -> Element { - let alpha = use_state(&cx, || 100); - cx.render(rsx! { div { - onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), - width: "100%", height: "10px", background_color: "red", - // justify_content: "center", - // align_items: "center", + justify_content: "center", + align_items: "center", - p{ - color: "rgba(0, 255, 0, {alpha}%)", - "Hello world!" - } - p{ - "{alpha}" - } - // p{"Hi"} + "Hello world!" } }) } diff --git a/examples/tui_stress_test.rs b/examples/tui_stress_test.rs new file mode 100644 index 000000000..72e72273d --- /dev/null +++ b/examples/tui_stress_test.rs @@ -0,0 +1,88 @@ +use dioxus::prelude::*; + +fn main() { + dioxus::tui::launch_cfg( + app, + dioxus::tui::Config { + rendering_mode: dioxus::tui::RenderingMode::Rgb, + }, + ); +} + +#[derive(Props, PartialEq)] +struct BoxProps { + x: i32, + y: i32, + hue: f32, + alpha: f32, +} +#[allow(non_snake_case)] +fn Box(cx: Scope) -> Element { + let count = use_state(&cx, || 0); + + use_future(&cx, (), move |_| { + let count = count.to_owned(); + let update = cx.schedule_update(); + async move { + loop { + count.with_mut(|i| *i += 1); + tokio::time::sleep(std::time::Duration::from_millis(800)).await; + update(); + } + } + }); + + let x = cx.props.x * 2; + let y = cx.props.y * 2; + let hue = cx.props.hue; + let count = count.get(); + let alpha = cx.props.alpha + (count % 100) as f32; + + cx.render(rsx! { + div { + left: "{x}%", + top: "{y}%", + width: "100%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + align_items: "center", + p{"{count}"} + } + }) +} + +fn app(cx: Scope) -> Element { + let steps = 50; + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "column", + (0..=steps).map(|x| + { + let hue = x as f32*360.0/steps as f32; + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "row", + (0..=steps).map(|y| + { + let alpha = y as f32*100.0/steps as f32; + cx.render(rsx! { + Box{ + x: x, + y: y, + alpha: alpha, + hue: hue, + } + }) + } + ) + } + }) + } + ) + } + }) +} diff --git a/examples/tui_text.rs b/examples/tui_text.rs index 95f9864a9..b8f04d99a 100644 --- a/examples/tui_text.rs +++ b/examples/tui_text.rs @@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element { onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), p { - // background_color: "black", + background_color: "black", flex_direction: "column", justify_content: "center", align_items: "center", diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 5682772be..a0be7fd3c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -423,6 +423,7 @@ impl<'b> DiffState<'b> { match (old.children.len(), new.children.len()) { (0, 0) => {} (0, _) => { + self.mutations.push_root(root); let created = self.create_children(new.children); self.mutations.append_children(created as u32); } diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 05ed52a06..c3816dac7 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -3,8 +3,6 @@ name = "dioxus-native-core" version = "0.2.0" edition = "2021" license = "MIT/Apache-2.0" -authors = ["@dementhos"] -description = "TUI-based renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" @@ -13,5 +11,10 @@ dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } -stretch2 = "0.4.1" +# stretch2 = "0.4.1" +stretch2 = { git = "https://github.com/Demonthos/stretch.git" } smallvec = "1.6" +fxhash = "0.2" + +[dev-dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/client_tree.rs b/packages/native-core/src/client_tree.rs new file mode 100644 index 000000000..6b4017e8f --- /dev/null +++ b/packages/native-core/src/client_tree.rs @@ -0,0 +1,580 @@ +use fxhash::{FxHashMap, FxHashSet}; +use std::{ + collections::VecDeque, + ops::{Index, IndexMut}, +}; + +use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; + +/// A tree that can sync with dioxus mutations backed by a hashmap. +/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. +/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. +#[derive(Debug)] +pub struct ClientTree { + root: usize, + nodes: Vec>>, + nodes_listening: FxHashMap<&'static str, FxHashSet>, + node_stack: smallvec::SmallVec<[usize; 10]>, +} + +impl ClientTree { + pub fn new() -> ClientTree { + ClientTree { + root: 0, + nodes: { + let mut v = Vec::new(); + v.push(Some(TreeNode::new( + 0, + TreeNodeType::Element { + tag: "Root".to_string(), + namespace: Some("Root"), + children: Vec::new(), + }, + ))); + v + }, + nodes_listening: FxHashMap::default(), + node_stack: smallvec::SmallVec::new(), + } + } + + /// Updates the tree, 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) -> Vec { + let mut nodes_updated = Vec::new(); + for mutations in mutations_vec { + for e in mutations.edits { + use dioxus_core::DomEdit::*; + match e { + PushRoot { root } => self.node_stack.push(root as usize), + AppendChildren { many } => { + let target = if self.node_stack.len() >= many as usize + 1 { + *self + .node_stack + .get(self.node_stack.len() - (many as usize + 1)) + .unwrap() + } else { + 0 + }; + let drained: Vec<_> = self + .node_stack + .drain(self.node_stack.len() - many as usize..) + .collect(); + for ns in drained { + self.link_child(ns, target).unwrap(); + nodes_updated.push(ns); + } + } + ReplaceWith { root, m } => { + let root = self.remove(root as usize).unwrap(); + let target = root.parent.unwrap().0; + let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect(); + for ns in drained { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + InsertAfter { root, n } => { + let target = self[root as usize].parent.unwrap().0; + let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); + for ns in drained { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + InsertBefore { root, n } => { + let target = self[root as usize].parent.unwrap().0; + let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); + for ns in drained { + nodes_updated.push(ns); + self.link_child(ns, target).unwrap(); + } + } + Remove { root } => { + if let Some(parent) = self[root as usize].parent { + nodes_updated.push(parent.0); + } + self.remove(root as usize).unwrap(); + } + CreateTextNode { root, text } => { + let n = TreeNode::new( + root, + TreeNodeType::Text { + text: text.to_string(), + }, + ); + self.insert(n); + self.node_stack.push(root as usize) + } + CreateElement { root, tag } => { + let n = TreeNode::new( + root, + TreeNodeType::Element { + tag: tag.to_string(), + namespace: None, + children: Vec::new(), + }, + ); + self.insert(n); + self.node_stack.push(root as usize) + } + CreateElementNs { root, tag, ns } => { + let n = TreeNode::new( + root, + TreeNodeType::Element { + tag: tag.to_string(), + namespace: Some(ns), + children: Vec::new(), + }, + ); + self.insert(n); + self.node_stack.push(root as usize) + } + CreatePlaceholder { root } => { + let n = TreeNode::new(root, TreeNodeType::Placeholder); + self.insert(n); + self.node_stack.push(root as usize) + } + + NewEventListener { + event_name, + scope: _, + root, + } => { + if let Some(v) = self.nodes_listening.get_mut(event_name) { + v.insert(root as usize); + } else { + let mut hs = FxHashSet::default(); + hs.insert(root as usize); + self.nodes_listening.insert(event_name, hs); + } + } + RemoveEventListener { root, event } => { + let v = self.nodes_listening.get_mut(event).unwrap(); + v.remove(&(root as usize)); + } + SetText { + root, + text: new_text, + } => { + let target = &mut self[root as usize]; + nodes_updated.push(root as usize); + match &mut target.node_type { + TreeNodeType::Text { text } => { + *text = new_text.to_string(); + } + _ => unreachable!(), + } + } + SetAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + RemoveAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + } + } + } + + nodes_updated + } + + /// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom. + pub fn update_state( + &mut self, + vdom: &VirtualDom, + nodes_updated: Vec, + us_ctx: &mut US::Ctx, + ds_ctx: &mut DS::Ctx, + ) -> Option> { + let mut to_rerender = FxHashSet::default(); + let mut nodes_updated: Vec<_> = nodes_updated + .into_iter() + .map(|id| (id, self[id].height)) + .collect(); + // Sort nodes first by height, then if the height is the same id. + nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0))); + nodes_updated.dedup(); + + // 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(); + while let Some((id, height)) = to_bubble.pop_back() { + let node = &mut self[id as usize]; + let vnode = node.element(vdom); + let node_type = &node.node_type; + let up_state = &mut node.up_state; + let children = match node_type { + TreeNodeType::Element { children, .. } => Some(children), + _ => None, + }; + // todo: reduce cloning state + let old = up_state.clone(); + let mut new = up_state.clone(); + let parent = node.parent.clone(); + new.reduce( + children + .unwrap_or(&Vec::new()) + .clone() + .iter() + .map(|c| &self[*c].up_state), + vnode, + us_ctx, + ); + if new != old { + to_rerender.insert(id); + if let Some(p) = parent { + let i = to_bubble.partition_point(|(other_id, h)| { + *h < height - 1 || (*h == height - 1 && *other_id < p.0) + }); + // make sure the parent is not already queued + if i >= to_bubble.len() || to_bubble[i].0 != p.0 { + to_bubble.insert(i, (p.0, height - 1)); + } + } + 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. + let mut to_push: VecDeque<_> = nodes_updated.clone().into(); + while let Some((id, height)) = to_push.pop_front() { + let node = &self[id as usize]; + // todo: reduce cloning state + let old = node.down_state.clone(); + let mut new = node.down_state.clone(); + let vnode = node.element(vdom); + new.reduce( + node.parent + .filter(|e| e.0 != 0) + .map(|e| &self[e].down_state), + vnode, + ds_ctx, + ); + if new != old { + to_rerender.insert(id); + let node = &mut self[id as usize]; + match &node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + let i = to_push.partition_point(|(other_id, h)| { + *h < height + 1 || (*h == height + 1 && *other_id < c.0) + }); + if i >= to_push.len() || to_push[i].0 != c.0 { + to_push.insert(i, (c.0, height + 1)); + } + } + } + _ => (), + }; + node.down_state = new; + } + } + + Some(to_rerender) + } + + fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { + debug_assert_ne!(child_id, parent_id); + let parent = &mut self[parent_id]; + parent.add_child(ElementId(child_id)); + let parent_height = parent.height + 1; + self[child_id].set_parent(ElementId(parent_id)); + self.increase_height(child_id, parent_height); + Some(()) + } + + fn increase_height(&mut self, id: usize, amount: u16) { + let n = &mut self[id]; + n.height += amount; + match &n.node_type { + TreeNodeType::Element { children, .. } => { + for c in children.clone() { + self.increase_height(c.0, amount); + } + } + _ => (), + } + } + + fn remove(&mut self, id: usize) -> Option> { + // We do not need to remove the node from the parent's children list for children. + fn inner( + tree: &mut ClientTree, + id: usize, + ) -> Option> { + let mut node = tree.nodes[id as usize].take()?; + match &mut node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(tree, c.0)?; + } + } + _ => (), + } + Some(node) + } + let mut node = self.nodes[id as usize].take()?; + if let Some(parent) = node.parent { + let parent = &mut self[parent]; + parent.remove_child(ElementId(id)); + } + match &mut node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(self, c.0)?; + } + } + _ => (), + } + Some(node) + } + + fn insert(&mut self, node: TreeNode) { + let current_len = self.nodes.len(); + let id = node.id.0; + if current_len - 1 < node.id.0 { + // self.nodes.reserve(1 + id - current_len); + self.nodes.extend((0..1 + id - current_len).map(|_| None)); + } + self.nodes[id] = Some(node); + } + + pub fn get(&self, id: usize) -> Option<&TreeNode> { + self.nodes.get(id)?.as_ref() + } + + pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode> { + self.nodes.get_mut(id)?.as_mut() + } + + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { + if let Some(nodes) = self.nodes_listening.get(event) { + let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); + listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); + listening + } else { + Vec::new() + } + } + + /// Check if the tree contains a node and its children. + pub fn contains_node(&self, node: &VNode) -> bool { + match node { + VNode::Component(_) => { + todo!() + } + VNode::Element(e) => { + if let Some(id) = e.id.get() { + let tree_node = &self[id]; + match &tree_node.node_type { + TreeNodeType::Element { + tag, + namespace, + children, + } => { + tag == e.tag + && namespace == &e.namespace + && children.iter().copied().collect::>() + == e.children + .iter() + .map(|c| c.mounted_id()) + .collect::>() + && e.children.iter().all(|c| { + self.contains_node(c) + && self[c.mounted_id()].parent == e.id.get() + }) + } + _ => false, + } + } else { + true + } + } + VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)), + VNode::Placeholder(_) => true, + VNode::Text(t) => { + if let Some(id) = t.id.get() { + let tree_node = &self[id]; + match &tree_node.node_type { + TreeNodeType::Text { text } => t.text == text, + _ => false, + } + } else { + true + } + } + } + } + + /// Return the number of nodes in the tree. + pub fn size(&self) -> usize { + // The tree has a root node, ignore it. + self.nodes.iter().filter(|n| n.is_some()).count() - 1 + } + + /// Returns the id of the root node. + pub fn root_id(&self) -> usize { + self.root + } + + /// Call a function for each node in the tree, depth first. + pub fn traverse(&self, mut f: impl FnMut(&TreeNode)) { + fn inner( + tree: &ClientTree, + id: ElementId, + f: &mut impl FnMut(&TreeNode), + ) { + let node = &tree[id]; + f(node); + match &node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(tree, *c, f); + } + } + _ => (), + } + } + match &self[self.root].node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(self, *c, &mut f); + } + } + _ => (), + } + } +} + +impl Index for ClientTree { + type Output = TreeNode; + + fn index(&self, idx: usize) -> &Self::Output { + self.get(idx).expect("Node does not exist") + } +} + +impl Index for ClientTree { + type Output = TreeNode; + + fn index(&self, idx: ElementId) -> &Self::Output { + &self[idx.0] + } +} + +impl IndexMut for ClientTree { + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + self.get_mut(idx).expect("Node does not exist") + } +} +impl IndexMut for ClientTree { + fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { + &mut self[idx.0] + } +} + +/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`TreeNode::element`]. +#[derive(Debug, Clone)] +pub struct TreeNode { + /// The id of the node this node was created from. + pub id: ElementId, + /// The parent id of the node. + pub parent: Option, + /// State of the node that is bubbled up to its parents. The state must depend only on the node and its children. + pub up_state: US, + /// 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 + pub node_type: TreeNodeType, + /// The number of parents before the root node. The root node has height 1. + pub height: u16, +} + +#[derive(Debug, Clone)] +pub enum TreeNodeType { + Text { + text: String, + }, + Element { + tag: String, + namespace: Option<&'static str>, + children: Vec, + }, + Placeholder, +} + +impl TreeNode { + fn new(id: u64, node_type: TreeNodeType) -> Self { + TreeNode { + id: ElementId(id as usize), + parent: None, + node_type, + down_state: DS::default(), + up_state: US::default(), + height: 0, + } + } + + /// Returns a reference to the element that this node refrences. + pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> { + vdom.get_element(self.id).unwrap() + } + + fn add_child(&mut self, child: ElementId) { + match &mut self.node_type { + TreeNodeType::Element { children, .. } => { + children.push(child); + } + _ => (), + } + } + + fn remove_child(&mut self, child: ElementId) { + match &mut self.node_type { + TreeNodeType::Element { children, .. } => { + children.retain(|c| c != &child); + } + _ => (), + } + } + + fn set_parent(&mut self, parent: ElementId) { + self.parent = Some(parent); + } +} + +/// This state that is passed down to children. For example text properties (`` `` ``) 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 non-flexbox div'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, + Self: 'a; +} +impl BubbledUpState for () { + type Ctx = (); + fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + } +} diff --git a/packages/native-core/src/layout.rs b/packages/native-core/src/layout.rs index 9e6dd90ea..5bfdb238b 100644 --- a/packages/native-core/src/layout.rs +++ b/packages/native-core/src/layout.rs @@ -1,4 +1,4 @@ -use crate::BubbledUpState; +use crate::client_tree::BubbledUpState; use dioxus_core::*; use stretch2::prelude::*; @@ -58,7 +58,7 @@ impl BubbledUpState for StretchLayout { apply_layout_attributes(name, value, &mut style); } - // todo: move + // the root node fills the entire area if el.id.get() == Some(ElementId(0)) { apply_layout_attributes("width", "100%", &mut style); apply_layout_attributes("height", "100%", &mut style); @@ -69,7 +69,6 @@ impl BubbledUpState for StretchLayout { for l in children { child_layout.push(l.node.unwrap()); } - child_layout.reverse(); if let Some(n) = self.node { if &stretch.children(n).unwrap() != &child_layout { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 67e888500..1dc8ad1c7 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,527 +1,3 @@ -use std::collections::{HashMap, HashSet, VecDeque}; - -use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +pub mod client_tree; pub mod layout; pub mod layout_attributes; - -/// A tree that can sync with dioxus mutations backed by a hashmap. -/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. -/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. -#[derive(Debug)] - -pub struct Tree { - pub root: usize, - pub nodes: Vec>>, - pub nodes_listening: HashMap<&'static str, HashSet>, -} - -impl Tree { - pub fn new() -> Tree { - Tree { - root: 0, - nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( - 0, - TreeNodeType::Element { - tag: "Root".to_string(), - namespace: Some("Root"), - children: Vec::new(), - }, - ))); - v - }, - nodes_listening: HashMap::new(), - } - } - - /// Updates the tree, up and down state and return a set of nodes that were updated - pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec { - let mut nodes_updated = Vec::new(); - for mutations in mutations_vec { - let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new(); - for e in mutations.edits { - use dioxus_core::DomEdit::*; - match e { - PushRoot { root } => node_stack.push(root as usize), - AppendChildren { many } => { - let target = if node_stack.len() >= many as usize + 1 { - *node_stack - .get(node_stack.len() - (many as usize + 1)) - .unwrap() - } else { - 0 - }; - for ns in node_stack.drain(node_stack.len() - many as usize..).rev() { - self.link_child(ns, target).unwrap(); - nodes_updated.push(ns); - } - } - ReplaceWith { root, m } => { - let root = self.remove(root as usize).unwrap(); - let target = root.parent.unwrap().0; - for ns in node_stack.drain(0..m as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - InsertAfter { root, n } => { - let target = self.get(root as usize).parent.unwrap().0; - for ns in node_stack.drain(0..n as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - InsertBefore { root, n } => { - let target = self.get(root as usize).parent.unwrap().0; - for ns in node_stack.drain(0..n as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - Remove { root } => { - if let Some(parent) = self.get(root as usize).parent { - nodes_updated.push(parent.0); - } - self.remove(root as usize).unwrap(); - } - CreateTextNode { root, text } => { - let n = TreeNode::new( - root, - TreeNodeType::Text { - text: text.to_string(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreateElement { root, tag } => { - let n = TreeNode::new( - root, - TreeNodeType::Element { - tag: tag.to_string(), - namespace: None, - children: Vec::new(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreateElementNs { root, tag, ns } => { - let n = TreeNode::new( - root, - TreeNodeType::Element { - tag: tag.to_string(), - namespace: Some(ns), - children: Vec::new(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreatePlaceholder { root } => { - let n = TreeNode::new(root, TreeNodeType::Placeholder); - self.insert(n); - node_stack.push(root as usize) - } - - NewEventListener { - event_name, - scope: _, - root, - } => { - if let Some(v) = self.nodes_listening.get_mut(event_name) { - v.insert(root as usize); - } else { - let mut hs = HashSet::new(); - hs.insert(root as usize); - self.nodes_listening.insert(event_name, hs); - } - } - RemoveEventListener { root, event } => { - let v = self.nodes_listening.get_mut(event).unwrap(); - v.remove(&(root as usize)); - } - SetText { - root, - text: new_text, - } => { - let target = self.get_mut(root as usize); - nodes_updated.push(root as usize); - match &mut target.node_type { - TreeNodeType::Text { text } => { - *text = new_text.to_string(); - } - _ => unreachable!(), - } - } - SetAttribute { root, .. } => { - nodes_updated.push(root as usize); - } - RemoveAttribute { root, .. } => { - nodes_updated.push(root as usize); - } - } - } - } - - nodes_updated - } - - pub fn update_state( - &mut self, - vdom: &VirtualDom, - nodes_updated: Vec, - us_ctx: &mut US::Ctx, - ds_ctx: &mut DS::Ctx, - ) -> Option> { - let mut to_rerender = HashSet::new(); - let mut nodes_updated: Vec<_> = nodes_updated - .into_iter() - .map(|id| (id, self.get(id).height)) - .collect(); - nodes_updated.dedup(); - nodes_updated.sort_by_key(|(_, h)| *h); - - // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. - // todo: this is called multable times per element? - let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_bubble.pop_back() { - let node = self.get_mut(id as usize); - let vnode = node.element(vdom); - let node_type = &node.node_type; - let up_state = &mut node.up_state; - let children = match node_type { - TreeNodeType::Element { children, .. } => Some(children), - _ => None, - }; - // todo: reduce cloning state - let old = up_state.clone(); - let mut new = up_state.clone(); - let parent = node.parent.clone(); - new.reduce( - children - .unwrap_or(&Vec::new()) - .clone() - .iter() - .map(|c| &self.get(c.0).up_state), - vnode, - us_ctx, - ); - if new != old { - to_rerender.insert(id); - if let Some(p) = parent { - let i = to_bubble.partition_point(|(_, h)| *h < height - 1); - // make sure the parent is not already queued - if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 { - to_bubble.insert(i, (p.0, height - 1)); - } - } - let node = self.get_mut(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. - let mut to_push: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_push.pop_front() { - let node = self.get_mut(id as usize); - // todo: reduce cloning state - let old = node.down_state.clone(); - let mut new = node.down_state.clone(); - let vnode = node.element(vdom); - new.reduce( - node.parent.map(|e| &self.get(e.0).down_state), - vnode, - ds_ctx, - ); - if new != old { - to_rerender.insert(id); - let node = self.get_mut(id as usize); - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - let i = to_bubble.partition_point(|(_, h)| *h < height + 1); - to_bubble.insert(i, (c.0, height + 1)); - } - } - _ => (), - }; - node.down_state = new; - } - } - - Some(to_rerender) - } - - fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { - debug_assert_ne!(child_id, parent_id); - let parent = self.get_mut(parent_id); - parent.add_child(ElementId(child_id)); - let parent_height = parent.height + 1; - self.get_mut(child_id).set_parent(ElementId(parent_id)); - self.increase_height(child_id, parent_height); - Some(()) - } - - fn increase_height(&mut self, id: usize, amount: u16) { - let n = self.get_mut(id); - n.height += amount; - match &n.node_type { - TreeNodeType::Element { children, .. } => { - for c in children.clone() { - self.increase_height(c.0, amount); - } - } - _ => (), - } - } - - fn remove(&mut self, id: usize) -> Option> { - let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap(); - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - self.remove(c.0).unwrap(); - } - } - _ => (), - } - Some(node) - } - - fn insert(&mut self, node: TreeNode) { - let current_len = self.nodes.len(); - let id = node.id.0; - if current_len - 1 < node.id.0 { - // self.nodes.reserve(1 + id - current_len); - self.nodes.extend((0..1 + id - current_len).map(|_| None)); - } - self.nodes[id] = Some(node); - } - - pub fn get(&self, id: usize) -> &TreeNode { - self.nodes.get(id).unwrap().as_ref().unwrap() - } - - fn get_mut(&mut self, id: usize) -> &mut TreeNode { - self.nodes.get_mut(id).unwrap().as_mut().unwrap() - } - - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { - if let Some(nodes) = self.nodes_listening.get(event) { - let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect(); - listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); - listening - } else { - Vec::new() - } - } -} - -/// The node is stored client side and stores render data -#[derive(Debug, Clone)] -pub struct TreeNode { - pub id: ElementId, - pub parent: Option, - pub up_state: US, - pub down_state: DS, - pub node_type: TreeNodeType, - pub height: u16, -} - -#[derive(Debug, Clone)] -pub enum TreeNodeType { - Text { - text: String, - }, - Element { - tag: String, - namespace: Option<&'static str>, - children: Vec, - }, - Placeholder, -} - -impl TreeNode { - fn new(id: u64, node_type: TreeNodeType) -> Self { - TreeNode { - id: ElementId(id as usize), - parent: None, - node_type, - down_state: DS::default(), - up_state: US::default(), - height: 0, - } - } - - fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> { - vdom.get_element(self.id).unwrap() - } - - fn add_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.push(child); - } - _ => (), - } - } - - fn set_parent(&mut self, parent: ElementId) { - self.parent = Some(parent); - } -} - -/// This state that is passed down to children. For example text properties (`` `` ``) 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 { - 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 non-flexbox div'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 { - type Ctx; - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a; -} -impl BubbledUpState for () { - type Ctx = (); - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - } -} - -// /// The nodes that need to be updated after updating a state. -// pub struct Update { -// children: bool, -// parent: bool, -// } - -// /// This state is derived from children and parents. -// /// Called when the current node's node properties are modified or a parent or child's [State] is modified. -// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this. -// pub trait State: Default + PartialEq + Clone { -// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update -// where -// I: Iterator, -// Self: 'a; -// } -// impl State for () { -// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update -// where -// I: Iterator, -// Self: 'a, -// { -// Update { -// children: false, -// parent: false, -// } -// } -// } - -#[test] -fn test_insert() { - use dioxus_core::*; - use dioxus_core_macro::*; - use dioxus_html as dioxus_elements; - - #[derive(Debug, Default, PartialEq, Clone)] - struct Rect { - x: u16, - y: u16, - width: u16, - height: u16, - } - - impl BubbledUpState for Rect { - type Ctx = (); - - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - match vnode { - VNode::Text(t) => { - *self = Rect { - x: 0, - y: 0, - width: t.text.len().try_into().unwrap(), - height: 1, - }; - return; - } - _ => (), - } - self.width = 2; - self.height = 2; - for c in children { - println!("\t{c:?}"); - self.width = self.width.max(c.width); - self.height += c.height; - } - } - } - - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div {}) - } - - let vdom = VirtualDom::new(Base); - let node_1 = rsx! { - div{ - div{ - "hello" - "hello world" - } - } - }; - let node_2 = rsx! { - div{ - div{ - "hello" - "hello world" - } - } - }; - let mutations = vdom.diff_lazynodes(node_1, node_2); - - let mut tree: Tree = Tree { - root: 0, - nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( - 0, - TreeNodeType::Element { - tag: "Root".to_string(), - namespace: Some("Root"), - children: Vec::new(), - }, - ))); - v - }, - nodes_listening: HashMap::new(), - }; - println!("{:?}", mutations); - let to_update = tree.apply_mutations(vec![mutations.0]); - let to_rerender = tree - .update_state(&vdom, to_update, &mut (), &mut ()) - .unwrap(); - println!("{to_rerender:?}"); - panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", "")); -} diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs new file mode 100644 index 000000000..bb15fd47d --- /dev/null +++ b/packages/native-core/tests/change_nodes.rs @@ -0,0 +1,153 @@ +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::client_tree::ClientTree; +use std::cell::Cell; + +#[test] +fn tree_remove_node() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div{ + div{} + } + }); + + let mut tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.apply_mutations(vec![mutations]); + let child_div = VElement { + id: Cell::new(Some(ElementId(2))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(1))), + listeners: &[], + attributes: &[], + children: &[], + }; + let child_div_el = VNode::Element(&child_div); + let root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[child_div_el], + }; + + assert_eq!(tree.size(), 2); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); + + let vdom = VirtualDom::new(Base); + let mutations = vdom.diff_lazynodes( + rsx! { + div{ + div{} + } + }, + rsx! { + div{} + }, + ); + tree.apply_mutations(vec![mutations.1]); + + let new_root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[], + }; + + assert_eq!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(tree[1].height, 1); +} + +#[test] +fn tree_add_node() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div{} + }); + + let mut tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.apply_mutations(vec![mutations]); + + let root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[], + }; + + assert_eq!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + + let vdom = VirtualDom::new(Base); + let mutations = vdom.diff_lazynodes( + rsx! { + div{} + }, + rsx! { + div{ + p{} + } + }, + ); + tree.apply_mutations(vec![mutations.1]); + + let child_div = VElement { + id: Cell::new(Some(ElementId(2))), + key: None, + tag: "p", + namespace: None, + parent: Cell::new(Some(ElementId(1))), + listeners: &[], + attributes: &[], + children: &[], + }; + let child_div_el = VNode::Element(&child_div); + let new_root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[child_div_el], + }; + + assert_eq!(tree.size(), 2); + assert!(&tree.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); +} diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs new file mode 100644 index 000000000..74bc809c7 --- /dev/null +++ b/packages/native-core/tests/initial_build.rs @@ -0,0 +1,124 @@ +use std::cell::Cell; + +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::client_tree::ClientTree; + +#[test] +fn tree_initial_build_simple() { + use std::cell::Cell; + + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div{} + }); + + let mut tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.apply_mutations(vec![mutations]); + let root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[], + }; + assert_eq!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); +} + +#[test] +fn tree_initial_build_with_children() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div{ + div{ + "hello" + p{ + "world" + } + "hello world" + } + } + }); + + let mut tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.apply_mutations(vec![mutations]); + let first_text = VText { + id: Cell::new(Some(ElementId(3))), + text: "hello", + is_static: true, + }; + let first_text_node = VNode::Text(&first_text); + let child_text = VText { + id: Cell::new(Some(ElementId(5))), + text: "world", + is_static: true, + }; + let child_text_node = VNode::Text(&child_text); + let child_p_el = VElement { + id: Cell::new(Some(ElementId(4))), + key: None, + tag: "p", + namespace: None, + parent: Cell::new(Some(ElementId(2))), + listeners: &[], + attributes: &[], + children: &[child_text_node], + }; + let child_p_node = VNode::Element(&child_p_el); + let second_text = VText { + id: Cell::new(Some(ElementId(6))), + text: "hello world", + is_static: true, + }; + let second_text_node = VNode::Text(&second_text); + let child_div_el = VElement { + id: Cell::new(Some(ElementId(2))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(1))), + listeners: &[], + attributes: &[], + children: &[first_text_node, child_p_node, second_text_node], + }; + let child_div_node = VNode::Element(&child_div_el); + let root_div = VElement { + id: Cell::new(Some(ElementId(1))), + key: None, + tag: "div", + namespace: None, + parent: Cell::new(Some(ElementId(0))), + listeners: &[], + attributes: &[], + children: &[child_div_node], + }; + assert_eq!(tree.size(), 6); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); + assert_eq!(tree[3].height, 3); + assert_eq!(tree[4].height, 3); + assert_eq!(tree[5].height, 4); + assert_eq!(tree[6].height, 3); +} diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs new file mode 100644 index 000000000..e738dc70d --- /dev/null +++ b/packages/native-core/tests/state.rs @@ -0,0 +1,320 @@ +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::client_tree::*; + +#[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, + 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>); +impl BubbledUpState for BubbledUpStateTester { + type Ctx = u32; + + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) + where + I: Iterator, + 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>); +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 tree_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 tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42); + + let root_div = &tree[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 = &tree[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 = &tree[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 tree_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, + 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 tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.up_state.0, 1); + assert_eq!(n.down_state.0, 1); + }); +} + +#[test] +fn tree_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 tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let nodes_updated = tree.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 = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.down_state.0, 2); + }); +} + +#[test] +fn tree_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 tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let nodes_updated = tree.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 = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); + }); +} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index fcbfa053b..92c915e76 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -22,5 +22,7 @@ crossterm = "0.23.0" anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" -stretch2 = "0.4.1" +# stretch2 = "0.4.1" +stretch2 = { git = "https://github.com/Demonthos/stretch.git" } smallvec = "1.6" +fxhash = "0.2" diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 2184dcd06..866d1fc85 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -2,14 +2,17 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; use dioxus_core::*; +use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::{layout::StretchLayout, Tree, TreeNode}; +use dioxus_native_core::{ + client_tree::{ClientTree, TreeNode}, + layout::StretchLayout, +}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, cell::RefCell, - collections::{HashMap, HashSet}, rc::Rc, sync::Arc, time::{Duration, Instant}, @@ -166,7 +169,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) { let previous_mouse = self .mouse @@ -191,7 +194,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) { struct Data<'b> { new_pos: (i32, i32), @@ -213,17 +216,17 @@ impl InnerInputState { fn try_create_event( name: &'static str, data: Arc, - will_bubble: &mut HashSet, + will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &TreeNode, - tree: &Tree, + tree: &ClientTree, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { let mut parent = node.parent; while let Some(parent_id) = parent { will_bubble.insert(parent_id); - parent = tree.get(parent_id.0).parent; + parent = tree[parent_id.0].parent; } resolved_events.push(UserEvent { scope_id: None, @@ -259,7 +262,7 @@ impl InnerInputState { { // mousemove - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mousemove") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -285,7 +288,7 @@ impl InnerInputState { { // mouseenter - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseenter") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -311,7 +314,7 @@ impl InnerInputState { { // mouseover - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseover") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -337,7 +340,7 @@ impl InnerInputState { { // mousedown - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -359,7 +362,7 @@ impl InnerInputState { { // mouseup - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseup") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -381,7 +384,7 @@ impl InnerInputState { { // click - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("click") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -403,7 +406,7 @@ impl InnerInputState { { // contextmenu - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("contextmenu") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -425,7 +428,7 @@ impl InnerInputState { { // wheel - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("wheel") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -449,7 +452,7 @@ impl InnerInputState { { // mouseleave - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseleave") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -473,7 +476,7 @@ impl InnerInputState { { // mouseout - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseout") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -543,7 +546,7 @@ impl RinkInputHandler { pub fn get_events<'a>( &self, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) -> Vec { let mut resolved_events = Vec::new(); @@ -578,7 +581,7 @@ impl RinkInputHandler { .map(|evt| (evt.0, evt.1.into_any())); // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus - let mut hm: HashMap<&'static str, Vec>> = HashMap::new(); + let mut hm: FxHashMap<&'static str, Vec>> = FxHashMap::default(); for (event, data) in events { if let Some(v) = hm.get_mut(event) { v.push(data); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f3828aac1..0fda78071 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -9,10 +9,8 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData}; -use dioxus_native_core::{layout::StretchLayout, Tree}; +use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout}; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; -use std::collections::HashSet; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; @@ -44,7 +42,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); - let mut tree: Tree = Tree::new(); + let mut tree: ClientTree = ClientTree::new(); let mutations = dom.rebuild(); let to_update = tree.apply_mutations(vec![mutations]); let mut stretch = Stretch::new(); @@ -60,7 +58,7 @@ pub fn render_vdom( ctx: UnboundedSender, handler: RinkInputHandler, cfg: Config, - mut tree: Tree, + mut tree: ClientTree, mut stretch: Stretch, ) -> Result<()> { // Setup input handling @@ -100,12 +98,8 @@ pub fn render_vdom( let mut terminal = Terminal::new(backend).unwrap(); terminal.clear().unwrap(); - let mut to_rerender: HashSet = tree - .nodes - .iter() - .filter_map(|n| n.as_ref()) - .map(|n| n.id.0) - .collect(); + let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let mut redraw = true; loop { /* @@ -120,15 +114,16 @@ pub fn render_vdom( todo: lazy re-rendering */ - if !to_rerender.is_empty() { + if !to_rerender.is_empty() || redraw { + redraw = false; terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); // println!("{dims:?}"); let width = dims.width; let height = dims.height; - let root_id = tree.root; - let root_node = tree.get(root_id).up_state.node.unwrap(); + let root_id = tree.root_id(); + let root_node = tree[root_id].up_state.node.unwrap(); stretch .compute_layout( root_node, @@ -138,7 +133,7 @@ pub fn render_vdom( }, ) .unwrap(); - let root = tree.get(tree.root); + let root = &tree[tree.root_id()]; render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; } @@ -146,22 +141,6 @@ pub fn render_vdom( // resolve events before rendering // todo: events do not trigger update? for e in handler.get_events(&stretch, &mut tree) { - // let tname = if e.data.is::() { - // "PointerData" - // } else if e.data.is::() { - // "WheelData" - // } else if e.data.is::() { - // "MouseData" - // } else if e.data.is::() { - // "KeyboardData" - // } else if e.data.is::() { - // "TouchData" - // } else if e.data.is::<(u16, u16)>() { - // "(u16, u16)" - // } else { - // panic!() - // }; - // println!("{tname}: {e:?}"); vdom.handle_message(SchedulerMsg::Event(e)); } @@ -184,7 +163,8 @@ pub fn render_vdom( break; } } - TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {} + TermEvent::Resize(_, _) => redraw = true, + TermEvent::Mouse(_) => {} }, InputEvent::Tick => {} // tick InputEvent::Close => break, diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 7be17ff82..fe21ba8ae 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,8 @@ -use dioxus_native_core::{layout::StretchLayout, layout_attributes::UnitSystem, Tree, TreeNode}; +use dioxus_native_core::{ + client_tree::{ClientTree, TreeNode}, + layout::StretchLayout, + layout_attributes::UnitSystem, +}; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -19,12 +23,14 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode<'a>( frame: &mut tui::Frame>, layout: &Stretch, - tree: &Tree, + tree: &ClientTree, node: &TreeNode, cfg: Config, ) { + use dioxus_native_core::client_tree::TreeNodeType; + match &node.node_type { - dioxus_native_core::TreeNodeType::Placeholder => return, + TreeNodeType::Placeholder => return, _ => (), } @@ -34,7 +40,7 @@ pub fn render_vnode<'a>( let Size { width, height } = size; match &node.node_type { - dioxus_native_core::TreeNodeType::Text { text } => { + TreeNodeType::Text { text } => { #[derive(Default)] struct Label<'a> { text: &'a str, @@ -64,7 +70,7 @@ pub fn render_vnode<'a>( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - dioxus_native_core::TreeNodeType::Element { children, .. } => { + TreeNodeType::Element { children, .. } => { let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -73,10 +79,10 @@ pub fn render_vnode<'a>( } for c in children { - render_vnode(frame, layout, tree, tree.get(c.0), cfg); + render_vnode(frame, layout, tree, &tree[c.0], cfg); } } - dioxus_native_core::TreeNodeType::Placeholder => unreachable!(), + TreeNodeType::Placeholder => unreachable!(), } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 907a066de..90cebe47d 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -31,8 +31,8 @@ use dioxus_core::{Attribute, VNode}; use dioxus_native_core::{ + client_tree::PushedDownState, layout_attributes::{parse_value, UnitSystem}, - PushedDownState, }; use crate::style::{RinkColor, RinkStyle}; @@ -48,7 +48,9 @@ impl PushedDownState for StyleModifier { fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) { *self = StyleModifier::default(); - self.style.fg = None; + if parent.is_some() { + self.style.fg = None; + } match vnode { VNode::Element(el) => { // handle text modifier elements diff --git a/packages/tui/src/utils.rs b/packages/tui/src/utils.rs deleted file mode 100644 index f89e3180c..000000000 --- a/packages/tui/src/utils.rs +++ /dev/null @@ -1,83 +0,0 @@ -// use dioxus_core::{Element, ElementId, Mutations, VNode, VirtualDom, DomEdit}; - -// /// The focus system needs a iterator that can persist through changes in the [VirtualDom]. Iterate through it with [ElementIter::next], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]). -// pub(crate) struct ElementIter { -// // stack of elements and fragments -// stack: smallvec::SmallVec<[(ElementId, usize); 5]>, -// } - -// impl ElementIter { -// pub(crate) fn new(initial: ElementId) -> Self { -// ElementIter { -// stack: smallvec::smallvec![(initial, 0)], -// } -// } -// /// remove stale element refreneces -// pub(crate) fn update(&mut self, mutations: &Mutations, vdom: &VirtualDom) { -// let ids_removed: Vec<_> = mutations.edits.iter().filter_map(|e| if let DomEdit::Remove{root: }).collect(); -// for node in self.stack { - -// match node.0 { -// VNode::Fragment(f) => { - -// } - -// VNode::Element(_) => {} - -// _ => unreachable!(), -// } -// } -// } -// pub(crate) fn next<'a>(&mut self, vdom: &'a VirtualDom) -> Option<&'a VNode<'a>> { -// let last = self.stack.last()?.0; -// let node = vdom.get_element(last)?; -// match node { -// VNode::Fragment(f) => { -// let mut last_mut = self.stack.last_mut()?; -// if last_mut.1 + 1 >= f.children.len() { -// self.stack.pop(); -// self.next(vdom) -// } else { -// last_mut.1 += 1; -// let new_node = &f.children[last_mut.1]; -// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { -// self.stack.push((new_node.mounted_id(), 0)); -// } -// self.next(vdom) -// } -// } - -// VNode::Component(vcomp) => { -// let idx = vcomp.scope.get().unwrap(); -// let new_node = vdom.get_scope(idx).unwrap().root_node(); -// *self.stack.last_mut()? = (new_node.mounted_id(), 0); -// self.next(vdom) -// } - -// VNode::Placeholder(_) | VNode::Text(_) => { -// self.stack.pop(); -// self.next(vdom) -// } - -// VNode::Element(e) => { -// let mut last_mut = self.stack.last_mut()?; -// if last_mut.1 + 1 >= e.children.len() { -// self.stack.pop(); -// self.next(vdom); -// } else { -// last_mut.1 += 1; -// let new_node = &e.children[last_mut.1]; -// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { -// self.stack.push((new_node.mounted_id(), 0)); -// } -// self.next(vdom); -// } -// Some(node) -// } -// } -// } - -// pub(crate) fn peak(&self) -> Option<&ElementId> { -// self.stack.last().map(|(e, c)| e) -// } -// } diff --git a/packages/tui/tests/margin.rs b/packages/tui/tests/margin.rs index 53e8978e5..44f909d69 100644 --- a/packages/tui/tests/margin.rs +++ b/packages/tui/tests/margin.rs @@ -90,4 +90,11 @@ fn margin_and_flex_row2() { assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); + + dbg!(stretch.layout(node0)).unwrap(); + + // assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32); + // assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); + // assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32); + // assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); } From 05d04165ba2bff520d53f26751e5ca329e03a85b Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:25:01 -0500 Subject: [PATCH 05/33] Delete launch.json --- .vscode/launch.json | 2541 ------------------------------------------- 1 file changed, 2541 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index fd829da72..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,2541 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-core'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-core" - ], - "filter": { - "name": "dioxus-core", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-rsx'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-rsx" - ], - "filter": { - "name": "dioxus-rsx", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-html'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-html" - ], - "filter": { - "name": "dioxus-html", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-hooks'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-hooks" - ], - "filter": { - "name": "dioxus-hooks", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-web'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-web" - ], - "filter": { - "name": "dioxus-web", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'hydrate'", - "cargo": { - "args": [ - "build", - "--example=hydrate", - "--package=dioxus-web" - ], - "filter": { - "name": "hydrate", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'hydrate'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=hydrate", - "--package=dioxus-web" - ], - "filter": { - "name": "hydrate", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'hydrate'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=hydrate", - "--package=dioxus-web" - ], - "filter": { - "name": "hydrate", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-interpreter-js'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-interpreter-js" - ], - "filter": { - "name": "dioxus-interpreter-js", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-ssr'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-ssr" - ], - "filter": { - "name": "dioxus-ssr", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'renders'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=renders", - "--package=dioxus-ssr" - ], - "filter": { - "name": "renders", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-desktop'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-desktop" - ], - "filter": { - "name": "dioxus-desktop", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-mobile'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-mobile" - ], - "filter": { - "name": "dioxus-mobile", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'fermi'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=fermi" - ], - "filter": { - "name": "fermi", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-tui'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-tui" - ], - "filter": { - "name": "dioxus-tui", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'margin'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=margin", - "--package=dioxus-tui" - ], - "filter": { - "name": "margin", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-native-core'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-native-core" - ], - "filter": { - "name": "dioxus-native-core", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus" - ], - "filter": { - "name": "dioxus", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'all_css'", - "cargo": { - "args": [ - "build", - "--example=all_css", - "--package=dioxus" - ], - "filter": { - "name": "all_css", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'all_css'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=all_css", - "--package=dioxus" - ], - "filter": { - "name": "all_css", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'borrowed'", - "cargo": { - "args": [ - "build", - "--example=borrowed", - "--package=dioxus" - ], - "filter": { - "name": "borrowed", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'borrowed'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=borrowed", - "--package=dioxus" - ], - "filter": { - "name": "borrowed", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'calculator'", - "cargo": { - "args": [ - "build", - "--example=calculator", - "--package=dioxus" - ], - "filter": { - "name": "calculator", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'calculator'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=calculator", - "--package=dioxus" - ], - "filter": { - "name": "calculator", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'crm'", - "cargo": { - "args": [ - "build", - "--example=crm", - "--package=dioxus" - ], - "filter": { - "name": "crm", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'crm'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=crm", - "--package=dioxus" - ], - "filter": { - "name": "crm", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'custom_assets'", - "cargo": { - "args": [ - "build", - "--example=custom_assets", - "--package=dioxus" - ], - "filter": { - "name": "custom_assets", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'custom_assets'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=custom_assets", - "--package=dioxus" - ], - "filter": { - "name": "custom_assets", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'disabled'", - "cargo": { - "args": [ - "build", - "--example=disabled", - "--package=dioxus" - ], - "filter": { - "name": "disabled", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'disabled'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=disabled", - "--package=dioxus" - ], - "filter": { - "name": "disabled", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'dog_app'", - "cargo": { - "args": [ - "build", - "--example=dog_app", - "--package=dioxus" - ], - "filter": { - "name": "dog_app", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'dog_app'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=dog_app", - "--package=dioxus" - ], - "filter": { - "name": "dog_app", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'error_handle'", - "cargo": { - "args": [ - "build", - "--example=error_handle", - "--package=dioxus" - ], - "filter": { - "name": "error_handle", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'error_handle'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=error_handle", - "--package=dioxus" - ], - "filter": { - "name": "error_handle", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'fermi'", - "cargo": { - "args": [ - "build", - "--example=fermi", - "--package=dioxus" - ], - "filter": { - "name": "fermi", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'fermi'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=fermi", - "--package=dioxus" - ], - "filter": { - "name": "fermi", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'filedragdrop'", - "cargo": { - "args": [ - "build", - "--example=filedragdrop", - "--package=dioxus" - ], - "filter": { - "name": "filedragdrop", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'filedragdrop'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=filedragdrop", - "--package=dioxus" - ], - "filter": { - "name": "filedragdrop", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'file_explorer'", - "cargo": { - "args": [ - "build", - "--example=file_explorer", - "--package=dioxus" - ], - "filter": { - "name": "file_explorer", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'file_explorer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=file_explorer", - "--package=dioxus" - ], - "filter": { - "name": "file_explorer", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'flat_router'", - "cargo": { - "args": [ - "build", - "--example=flat_router", - "--package=dioxus" - ], - "filter": { - "name": "flat_router", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'flat_router'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=flat_router", - "--package=dioxus" - ], - "filter": { - "name": "flat_router", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'form'", - "cargo": { - "args": [ - "build", - "--example=form", - "--package=dioxus" - ], - "filter": { - "name": "form", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'form'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=form", - "--package=dioxus" - ], - "filter": { - "name": "form", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'framework_benchmark'", - "cargo": { - "args": [ - "build", - "--example=framework_benchmark", - "--package=dioxus" - ], - "filter": { - "name": "framework_benchmark", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'framework_benchmark'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=framework_benchmark", - "--package=dioxus" - ], - "filter": { - "name": "framework_benchmark", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'heavy_compute'", - "cargo": { - "args": [ - "build", - "--example=heavy_compute", - "--package=dioxus" - ], - "filter": { - "name": "heavy_compute", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'heavy_compute'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=heavy_compute", - "--package=dioxus" - ], - "filter": { - "name": "heavy_compute", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'hello_world'", - "cargo": { - "args": [ - "build", - "--example=hello_world", - "--package=dioxus" - ], - "filter": { - "name": "hello_world", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'hello_world'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=hello_world", - "--package=dioxus" - ], - "filter": { - "name": "hello_world", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'hydration'", - "cargo": { - "args": [ - "build", - "--example=hydration", - "--package=dioxus" - ], - "filter": { - "name": "hydration", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'hydration'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=hydration", - "--package=dioxus" - ], - "filter": { - "name": "hydration", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'inputs'", - "cargo": { - "args": [ - "build", - "--example=inputs", - "--package=dioxus" - ], - "filter": { - "name": "inputs", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'inputs'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=inputs", - "--package=dioxus" - ], - "filter": { - "name": "inputs", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'link'", - "cargo": { - "args": [ - "build", - "--example=link", - "--package=dioxus" - ], - "filter": { - "name": "link", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'link'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=link", - "--package=dioxus" - ], - "filter": { - "name": "link", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'login_form'", - "cargo": { - "args": [ - "build", - "--example=login_form", - "--package=dioxus" - ], - "filter": { - "name": "login_form", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'login_form'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=login_form", - "--package=dioxus" - ], - "filter": { - "name": "login_form", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'nested_listeners'", - "cargo": { - "args": [ - "build", - "--example=nested_listeners", - "--package=dioxus" - ], - "filter": { - "name": "nested_listeners", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'nested_listeners'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=nested_listeners", - "--package=dioxus" - ], - "filter": { - "name": "nested_listeners", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'optional_props'", - "cargo": { - "args": [ - "build", - "--example=optional_props", - "--package=dioxus" - ], - "filter": { - "name": "optional_props", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'optional_props'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=optional_props", - "--package=dioxus" - ], - "filter": { - "name": "optional_props", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'pattern_model'", - "cargo": { - "args": [ - "build", - "--example=pattern_model", - "--package=dioxus" - ], - "filter": { - "name": "pattern_model", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'pattern_model'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=pattern_model", - "--package=dioxus" - ], - "filter": { - "name": "pattern_model", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'pattern_reducer'", - "cargo": { - "args": [ - "build", - "--example=pattern_reducer", - "--package=dioxus" - ], - "filter": { - "name": "pattern_reducer", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'pattern_reducer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=pattern_reducer", - "--package=dioxus" - ], - "filter": { - "name": "pattern_reducer", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'readme'", - "cargo": { - "args": [ - "build", - "--example=readme", - "--package=dioxus" - ], - "filter": { - "name": "readme", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'readme'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=readme", - "--package=dioxus" - ], - "filter": { - "name": "readme", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'router'", - "cargo": { - "args": [ - "build", - "--example=router", - "--package=dioxus" - ], - "filter": { - "name": "router", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'router'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=router", - "--package=dioxus" - ], - "filter": { - "name": "router", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'rsx_compile_fail'", - "cargo": { - "args": [ - "build", - "--example=rsx_compile_fail", - "--package=dioxus" - ], - "filter": { - "name": "rsx_compile_fail", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'rsx_compile_fail'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=rsx_compile_fail", - "--package=dioxus" - ], - "filter": { - "name": "rsx_compile_fail", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'rsx_usage'", - "cargo": { - "args": [ - "build", - "--example=rsx_usage", - "--package=dioxus" - ], - "filter": { - "name": "rsx_usage", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'rsx_usage'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=rsx_usage", - "--package=dioxus" - ], - "filter": { - "name": "rsx_usage", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'ssr'", - "cargo": { - "args": [ - "build", - "--example=ssr", - "--package=dioxus" - ], - "filter": { - "name": "ssr", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'ssr'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=ssr", - "--package=dioxus" - ], - "filter": { - "name": "ssr", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'suspense'", - "cargo": { - "args": [ - "build", - "--example=suspense", - "--package=dioxus" - ], - "filter": { - "name": "suspense", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'suspense'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=suspense", - "--package=dioxus" - ], - "filter": { - "name": "suspense", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'svg'", - "cargo": { - "args": [ - "build", - "--example=svg", - "--package=dioxus" - ], - "filter": { - "name": "svg", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'svg'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=svg", - "--package=dioxus" - ], - "filter": { - "name": "svg", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tailwind'", - "cargo": { - "args": [ - "build", - "--example=tailwind", - "--package=dioxus" - ], - "filter": { - "name": "tailwind", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tailwind'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tailwind", - "--package=dioxus" - ], - "filter": { - "name": "tailwind", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tasks'", - "cargo": { - "args": [ - "build", - "--example=tasks", - "--package=dioxus" - ], - "filter": { - "name": "tasks", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tasks'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tasks", - "--package=dioxus" - ], - "filter": { - "name": "tasks", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'textarea'", - "cargo": { - "args": [ - "build", - "--example=textarea", - "--package=dioxus" - ], - "filter": { - "name": "textarea", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'textarea'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=textarea", - "--package=dioxus" - ], - "filter": { - "name": "textarea", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'todomvc'", - "cargo": { - "args": [ - "build", - "--example=todomvc", - "--package=dioxus" - ], - "filter": { - "name": "todomvc", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'todomvc'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=todomvc", - "--package=dioxus" - ], - "filter": { - "name": "todomvc", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_border'", - "cargo": { - "args": [ - "build", - "--example=tui_border", - "--package=dioxus" - ], - "filter": { - "name": "tui_border", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_border'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_border", - "--package=dioxus" - ], - "filter": { - "name": "tui_border", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_color_test'", - "cargo": { - "args": [ - "build", - "--example=tui_color_test", - "--package=dioxus" - ], - "filter": { - "name": "tui_color_test", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_color_test'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_color_test", - "--package=dioxus" - ], - "filter": { - "name": "tui_color_test", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_components'", - "cargo": { - "args": [ - "build", - "--example=tui_components", - "--package=dioxus" - ], - "filter": { - "name": "tui_components", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_components'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_components", - "--package=dioxus" - ], - "filter": { - "name": "tui_components", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_frame'", - "cargo": { - "args": [ - "build", - "--example=tui_frame", - "--package=dioxus" - ], - "filter": { - "name": "tui_frame", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_frame'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_frame", - "--package=dioxus" - ], - "filter": { - "name": "tui_frame", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_hover'", - "cargo": { - "args": [ - "build", - "--example=tui_hover", - "--package=dioxus" - ], - "filter": { - "name": "tui_hover", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_hover'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_hover", - "--package=dioxus" - ], - "filter": { - "name": "tui_hover", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_keys'", - "cargo": { - "args": [ - "build", - "--example=tui_keys", - "--package=dioxus" - ], - "filter": { - "name": "tui_keys", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_keys'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_keys", - "--package=dioxus" - ], - "filter": { - "name": "tui_keys", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_list'", - "cargo": { - "args": [ - "build", - "--example=tui_list", - "--package=dioxus" - ], - "filter": { - "name": "tui_list", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_list'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_list", - "--package=dioxus" - ], - "filter": { - "name": "tui_list", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_margin'", - "cargo": { - "args": [ - "build", - "--example=tui_margin", - "--package=dioxus" - ], - "filter": { - "name": "tui_margin", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_margin'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_margin", - "--package=dioxus" - ], - "filter": { - "name": "tui_margin", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_quadrants'", - "cargo": { - "args": [ - "build", - "--example=tui_quadrants", - "--package=dioxus" - ], - "filter": { - "name": "tui_quadrants", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_quadrants'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_quadrants", - "--package=dioxus" - ], - "filter": { - "name": "tui_quadrants", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_readme'", - "cargo": { - "args": [ - "build", - "--example=tui_readme", - "--package=dioxus" - ], - "filter": { - "name": "tui_readme", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_readme'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_readme", - "--package=dioxus" - ], - "filter": { - "name": "tui_readme", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_task'", - "cargo": { - "args": [ - "build", - "--example=tui_task", - "--package=dioxus" - ], - "filter": { - "name": "tui_task", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_task'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_task", - "--package=dioxus" - ], - "filter": { - "name": "tui_task", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'tui_text'", - "cargo": { - "args": [ - "build", - "--example=tui_text", - "--package=dioxus" - ], - "filter": { - "name": "tui_text", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'tui_text'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=tui_text", - "--package=dioxus" - ], - "filter": { - "name": "tui_text", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'window_event'", - "cargo": { - "args": [ - "build", - "--example=window_event", - "--package=dioxus" - ], - "filter": { - "name": "window_event", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'window_event'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=window_event", - "--package=dioxus" - ], - "filter": { - "name": "window_event", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'xss_safety'", - "cargo": { - "args": [ - "build", - "--example=xss_safety", - "--package=dioxus" - ], - "filter": { - "name": "xss_safety", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'xss_safety'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=xss_safety", - "--package=dioxus" - ], - "filter": { - "name": "xss_safety", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'borrowedstate'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=borrowedstate", - "--package=dioxus" - ], - "filter": { - "name": "borrowedstate", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'create_dom'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=create_dom", - "--package=dioxus" - ], - "filter": { - "name": "create_dom", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'diffing'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=diffing", - "--package=dioxus" - ], - "filter": { - "name": "diffing", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'earlyabort'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=earlyabort", - "--package=dioxus" - ], - "filter": { - "name": "earlyabort", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'fermi'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=fermi", - "--package=dioxus" - ], - "filter": { - "name": "fermi", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'lifecycle'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=lifecycle", - "--package=dioxus" - ], - "filter": { - "name": "lifecycle", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'miri_stress'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=miri_stress", - "--package=dioxus" - ], - "filter": { - "name": "miri_stress", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'passthru'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=passthru", - "--package=dioxus" - ], - "filter": { - "name": "passthru", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'scheduler'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=scheduler", - "--package=dioxus" - ], - "filter": { - "name": "scheduler", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'sharedstate'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=sharedstate", - "--package=dioxus" - ], - "filter": { - "name": "sharedstate", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'task'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=task", - "--package=dioxus" - ], - "filter": { - "name": "task", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'test_logging'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=test_logging", - "--package=dioxus" - ], - "filter": { - "name": "test_logging", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'vdom_rebuild'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=vdom_rebuild", - "--package=dioxus" - ], - "filter": { - "name": "vdom_rebuild", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug benchmark 'create'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bench=create", - "--package=dioxus" - ], - "filter": { - "name": "create", - "kind": "bench" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug benchmark 'jsframework'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bench=jsframework", - "--package=dioxus" - ], - "filter": { - "name": "jsframework", - "kind": "bench" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'dioxus-router'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=dioxus-router" - ], - "filter": { - "name": "dioxus-router", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug example 'simple'", - "cargo": { - "args": [ - "build", - "--example=simple", - "--package=dioxus-router" - ], - "filter": { - "name": "simple", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'simple'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=simple", - "--package=dioxus-router" - ], - "filter": { - "name": "simple", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'ssr_router'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=ssr_router", - "--package=dioxus-router" - ], - "filter": { - "name": "ssr_router", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug integration test 'web_router'", - "cargo": { - "args": [ - "test", - "--no-run", - "--test=web_router", - "--package=dioxus-router" - ], - "filter": { - "name": "web_router", - "kind": "test" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file From 5b25500c0b76d8cfedeb3a3d2fbeaffd52432b39 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 30 Mar 2022 20:45:41 -0500 Subject: [PATCH 06/33] finishing touches and benchmarks --- Cargo.toml | 8 +- benches/tui_update.rs | 196 ++++++++++++++++++ examples/tui_stress_test.rs | 88 -------- packages/native-core/src/layout_attributes.rs | 15 +- packages/native-core/src/lib.rs | 3 +- .../src/{client_tree.rs => real_dom.rs} | 32 +-- packages/native-core/tests/change_nodes.rs | 6 +- packages/native-core/tests/initial_build.rs | 6 +- packages/native-core/tests/state.rs | 16 +- packages/tui/src/hooks.rs | 14 +- packages/{native-core => tui}/src/layout.rs | 14 +- packages/tui/src/lib.rs | 100 ++++----- packages/tui/src/render.rs | 9 +- packages/tui/src/style.rs | 2 +- packages/tui/src/style_attributes.rs | 2 +- 15 files changed, 305 insertions(+), 206 deletions(-) create mode 100644 benches/tui_update.rs delete mode 100644 examples/tui_stress_test.rs rename packages/native-core/src/{client_tree.rs => real_dom.rs} (95%) rename packages/{native-core => tui}/src/layout.rs (84%) diff --git a/Cargo.toml b/Cargo.toml index 5d6f01506..95900a1d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } +dioxus-native-core = { path = "./packages/native-core", optional = true } + # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true } # dioxus-rsx = { path = "./packages/rsx", optional = true } # macro = ["dioxus-core-macro", "dioxus-rsx"] @@ -45,6 +47,7 @@ ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] +native-core = ["dioxus-native-core"] [workspace] @@ -90,5 +93,6 @@ harness = false name = "jsframework" harness = false -[profile.release] -debug = true \ No newline at end of file +[[bench]] +name = "tui_update" +harness = false \ No newline at end of file diff --git a/benches/tui_update.rs b/benches/tui_update.rs new file mode 100644 index 000000000..6c77c9132 --- /dev/null +++ b/benches/tui_update.rs @@ -0,0 +1,196 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use dioxus::prelude::*; +use dioxus_tui::TuiContext; + +criterion_group!(mbenches, update_boxes); +criterion_main!(mbenches); + +/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. +fn update_boxes(c: &mut Criterion) { + let mut group = c.benchmark_group("Update boxes"); + // We can override the configuration on a per-group level + group.sample_size(10); + + // We can also use loops to define multiple benchmarks, even over multiple dimensions. + for size in 1..=6 { + let parameter_string = format!("{}", 5 * size); + group.bench_with_input( + BenchmarkId::new("size", parameter_string), + &size, + |b, size| { + b.iter(|| match size { + 1 => dioxus::tui::launch(app5), + 2 => dioxus::tui::launch(app10), + 3 => dioxus::tui::launch(app15), + 4 => dioxus::tui::launch(app20), + 5 => dioxus::tui::launch(app25), + 6 => dioxus::tui::launch(app30), + _ => (), + }) + }, + ); + } +} + +#[derive(Props, PartialEq)] +struct BoxProps { + x: usize, + y: usize, + hue: f32, + alpha: f32, +} +#[allow(non_snake_case)] +fn Box(cx: Scope) -> Element { + let count = use_state(&cx, || 0); + + let x = cx.props.x * 2; + let y = cx.props.y * 2; + let hue = cx.props.hue; + let display_hue = cx.props.hue as u32 / 10; + let count = count.get(); + let alpha = cx.props.alpha + (count % 100) as f32; + + cx.render(rsx! { + div { + left: "{x}%", + top: "{y}%", + width: "100%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + align_items: "center", + p{"{display_hue:03}"} + } + }) +} + +#[derive(Props, PartialEq)] +struct GridProps { + size: usize, +} +#[allow(non_snake_case)] +fn Grid(cx: Scope) -> Element { + let size = cx.props.size; + let count = use_state(&cx, || 0); + let counts = use_ref(&cx, || vec![0; size * size]); + + let ctx: TuiContext = cx.consume_context().unwrap(); + if *count.get() + 1 >= (size * size) { + ctx.quit(); + } else { + counts.with_mut(|c| { + let i = *count.current(); + c[i] += 1; + c[i] = c[i] % 360; + }); + count.with_mut(|i| { + *i += 1; + *i = *i % (size * size); + }); + } + + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "column", + (0..size).map(|x| + { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "row", + (0..size).map(|y| + { + let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; + let key = format!("{}-{}", x, y); + cx.render(rsx! { + Box{ + x: x, + y: y, + alpha: 100.0, + hue: alpha, + key: "{key}", + } + }) + } + ) + } + }) + } + ) + } + }) +} + +fn app5(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 5, + } + } + }) +} + +fn app10(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 10, + } + } + }) +} + +fn app15(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 15, + } + } + }) +} + +fn app20(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 20, + } + } + }) +} + +fn app25(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 25, + } + } + }) +} + +fn app30(cx: Scope) -> Element { + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + Grid{ + size: 30, + } + } + }) +} diff --git a/examples/tui_stress_test.rs b/examples/tui_stress_test.rs deleted file mode 100644 index 72e72273d..000000000 --- a/examples/tui_stress_test.rs +++ /dev/null @@ -1,88 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus::tui::launch_cfg( - app, - dioxus::tui::Config { - rendering_mode: dioxus::tui::RenderingMode::Rgb, - }, - ); -} - -#[derive(Props, PartialEq)] -struct BoxProps { - x: i32, - y: i32, - hue: f32, - alpha: f32, -} -#[allow(non_snake_case)] -fn Box(cx: Scope) -> Element { - let count = use_state(&cx, || 0); - - use_future(&cx, (), move |_| { - let count = count.to_owned(); - let update = cx.schedule_update(); - async move { - loop { - count.with_mut(|i| *i += 1); - tokio::time::sleep(std::time::Duration::from_millis(800)).await; - update(); - } - } - }); - - let x = cx.props.x * 2; - let y = cx.props.y * 2; - let hue = cx.props.hue; - let count = count.get(); - let alpha = cx.props.alpha + (count % 100) as f32; - - cx.render(rsx! { - div { - left: "{x}%", - top: "{y}%", - width: "100%", - height: "100%", - background_color: "hsl({hue}, 100%, 50%, {alpha}%)", - align_items: "center", - p{"{count}"} - } - }) -} - -fn app(cx: Scope) -> Element { - let steps = 50; - cx.render(rsx! { - div{ - width: "100%", - height: "100%", - flex_direction: "column", - (0..=steps).map(|x| - { - let hue = x as f32*360.0/steps as f32; - cx.render(rsx! { - div{ - width: "100%", - height: "100%", - flex_direction: "row", - (0..=steps).map(|y| - { - let alpha = y as f32*100.0/steps as f32; - cx.render(rsx! { - Box{ - x: x, - y: y, - alpha: alpha, - hue: hue, - } - }) - } - ) - } - }) - } - ) - } - }) -} diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index afe7d177c..9518451fc 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -32,12 +32,7 @@ use stretch2::{prelude::*, style::PositionType}; /// applies the entire html namespace defined in dioxus-html -pub fn apply_layout_attributes( - // - name: &str, - value: &str, - style: &mut Style, -) { +pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) { match name { "align-content" | "align-items" @@ -253,6 +248,7 @@ pub fn apply_layout_attributes( } } +/// a relative or absolute size #[derive(Clone, Copy, PartialEq, Debug)] pub enum UnitSystem { Percent(f32), @@ -268,6 +264,7 @@ impl Into for UnitSystem { } } +/// parse relative or absolute value pub fn parse_value(value: &str) -> Option { if value.ends_with("px") { if let Ok(px) = value.trim_end_matches("px").parse::() { @@ -605,11 +602,7 @@ fn apply_align(name: &str, value: &str, style: &mut Style) { } } -pub fn apply_size(_name: &str, _value: &str, _style: &mut Style) { - // -} - -pub fn apply_margin(name: &str, value: &str, style: &mut Style) { +fn apply_margin(name: &str, value: &str, style: &mut Style) { match parse_value(value) { Some(UnitSystem::Percent(v)) => match name { "margin" => { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 1dc8ad1c7..26c629446 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,3 +1,2 @@ -pub mod client_tree; -pub mod layout; pub mod layout_attributes; +pub mod real_dom; diff --git a/packages/native-core/src/client_tree.rs b/packages/native-core/src/real_dom.rs similarity index 95% rename from packages/native-core/src/client_tree.rs rename to packages/native-core/src/real_dom.rs index 6b4017e8f..1d697ff4a 100644 --- a/packages/native-core/src/client_tree.rs +++ b/packages/native-core/src/real_dom.rs @@ -6,20 +6,20 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -/// A tree that can sync with dioxus mutations backed by a hashmap. -/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. -/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. +/// A tree that can sync with the VirtualDom mutations intended for use in lazy renderers. +/// The render state passes from parent to children and or accumulates state from children to parents. +/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] to update the tree and [Tree::update_state] to update the state of the nodes. #[derive(Debug)] -pub struct ClientTree { +pub struct RealDom { root: usize, nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } -impl ClientTree { - pub fn new() -> ClientTree { - ClientTree { +impl RealDom { + pub fn new() -> RealDom { + RealDom { root: 0, nodes: { let mut v = Vec::new(); @@ -187,6 +187,7 @@ impl ClientTree { ds_ctx: &mut DS::Ctx, ) -> Option> { let mut to_rerender = FxHashSet::default(); + to_rerender.extend(nodes_updated.iter()); let mut nodes_updated: Vec<_> = nodes_updated .into_iter() .map(|id| (id, self[id].height)) @@ -296,10 +297,11 @@ impl ClientTree { } } + // remove a node and it's children from the tree. fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. fn inner( - tree: &mut ClientTree, + tree: &mut RealDom, id: usize, ) -> Option> { let mut node = tree.nodes[id as usize].take()?; @@ -418,9 +420,9 @@ impl ClientTree { } /// Call a function for each node in the tree, depth first. - pub fn traverse(&self, mut f: impl FnMut(&TreeNode)) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode)) { fn inner( - tree: &ClientTree, + tree: &RealDom, id: ElementId, f: &mut impl FnMut(&TreeNode), ) { @@ -446,7 +448,7 @@ impl ClientTree { } } -impl Index for ClientTree { +impl Index for RealDom { type Output = TreeNode; fn index(&self, idx: usize) -> &Self::Output { @@ -454,7 +456,7 @@ impl Index for ClientTree Index for ClientTree { +impl Index for RealDom { type Output = TreeNode; fn index(&self, idx: ElementId) -> &Self::Output { @@ -462,12 +464,12 @@ impl Index for ClientTree IndexMut for ClientTree { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { self.get_mut(idx).expect("Node does not exist") } } -impl IndexMut for ClientTree { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { &mut self[idx.0] } @@ -557,7 +559,7 @@ impl PushedDownState for () { fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {} } -/// This state is derived from children. For example a non-flexbox div's size could be derived from the size of children. +/// 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 { diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index bb15fd47d..387481462 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -2,7 +2,7 @@ use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; -use dioxus_native_core::client_tree::ClientTree; +use dioxus_native_core::real_dom::RealDom; use std::cell::Cell; #[test] @@ -20,7 +20,7 @@ fn tree_remove_node() { } }); - let mut tree: ClientTree<(), ()> = ClientTree::new(); + let mut tree: RealDom<(), ()> = RealDom::new(); let _to_update = tree.apply_mutations(vec![mutations]); let child_div = VElement { @@ -92,7 +92,7 @@ fn tree_add_node() { div{} }); - let mut tree: ClientTree<(), ()> = ClientTree::new(); + let mut tree: RealDom<(), ()> = RealDom::new(); let _to_update = tree.apply_mutations(vec![mutations]); diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index 74bc809c7..d17ea05d4 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -4,7 +4,7 @@ use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; -use dioxus_native_core::client_tree::ClientTree; +use dioxus_native_core::real_dom::RealDom; #[test] fn tree_initial_build_simple() { @@ -21,7 +21,7 @@ fn tree_initial_build_simple() { div{} }); - let mut tree: ClientTree<(), ()> = ClientTree::new(); + let mut tree: RealDom<(), ()> = RealDom::new(); let _to_update = tree.apply_mutations(vec![mutations]); let root_div = VElement { @@ -60,7 +60,7 @@ fn tree_initial_build_with_children() { } }); - let mut tree: ClientTree<(), ()> = ClientTree::new(); + let mut tree: RealDom<(), ()> = RealDom::new(); let _to_update = tree.apply_mutations(vec![mutations]); let first_text = VText { diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index e738dc70d..a31110f18 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/state.rs @@ -2,7 +2,7 @@ use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; -use dioxus_native_core::client_tree::*; +use dioxus_native_core::real_dom::*; #[derive(Debug, Clone, PartialEq, Default)] struct CallCounter(u32); @@ -77,7 +77,7 @@ fn tree_state_initial() { } }); - let mut tree: ClientTree = ClientTree::new(); + let mut tree: RealDom = RealDom::new(); let nodes_updated = tree.apply_mutations(vec![mutations]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42); @@ -178,12 +178,12 @@ fn tree_state_reduce_initally_called_minimally() { } }); - let mut tree: ClientTree = ClientTree::new(); + let mut tree: RealDom = RealDom::new(); let nodes_updated = tree.apply_mutations(vec![mutations]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse(|n| { + tree.traverse_depth_first(|n| { assert_eq!(n.up_state.0, 1); assert_eq!(n.down_state.0, 1); }); @@ -233,7 +233,7 @@ fn tree_state_reduce_down_called_minimally_on_update() { } }); - let mut tree: ClientTree = ClientTree::new(); + let mut tree: RealDom = RealDom::new(); let nodes_updated = tree.apply_mutations(vec![mutations]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); @@ -249,7 +249,7 @@ fn tree_state_reduce_down_called_minimally_on_update() { }]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse(|n| { + tree.traverse_depth_first(|n| { assert_eq!(n.down_state.0, 2); }); } @@ -298,7 +298,7 @@ fn tree_state_reduce_up_called_minimally_on_update() { } }); - let mut tree: ClientTree = ClientTree::new(); + let mut tree: RealDom = RealDom::new(); let nodes_updated = tree.apply_mutations(vec![mutations]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); @@ -314,7 +314,7 @@ fn tree_state_reduce_up_called_minimally_on_update() { }]); let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse(|n| { + tree.traverse_depth_first(|n| { assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); }); } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 866d1fc85..3813b97ae 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,10 +5,7 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::{ - client_tree::{ClientTree, TreeNode}, - layout::StretchLayout, -}; +use dioxus_native_core::real_dom::{RealDom, TreeNode}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, @@ -19,6 +16,7 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; +use crate::layout::StretchLayout; use crate::style_attributes::StyleModifier; // a wrapper around the input state for easier access @@ -169,7 +167,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut ClientTree, + tree: &mut RealDom, ) { let previous_mouse = self .mouse @@ -194,7 +192,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut ClientTree, + tree: &mut RealDom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -219,7 +217,7 @@ impl InnerInputState { will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &TreeNode, - tree: &ClientTree, + tree: &RealDom, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { @@ -546,7 +544,7 @@ impl RinkInputHandler { pub fn get_events<'a>( &self, layout: &Stretch, - tree: &mut ClientTree, + tree: &mut RealDom, ) -> Vec { let mut resolved_events = Vec::new(); diff --git a/packages/native-core/src/layout.rs b/packages/tui/src/layout.rs similarity index 84% rename from packages/native-core/src/layout.rs rename to packages/tui/src/layout.rs index 5bfdb238b..9d9be8f6a 100644 --- a/packages/native-core/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,15 +1,9 @@ -use crate::client_tree::BubbledUpState; use dioxus_core::*; +use dioxus_native_core::layout_attributes::apply_layout_attributes; +use dioxus_native_core::real_dom::BubbledUpState; use stretch2::prelude::*; -use crate::layout_attributes::apply_layout_attributes; - -/* -The layout system uses the lineheight as one point. - -stretch uses fractional points, so we can rasterize if we need too, but not with characters -this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger -*/ +/// the size #[derive(Clone, PartialEq, Default, Debug)] pub struct StretchLayout { pub style: Style, @@ -19,7 +13,7 @@ pub struct StretchLayout { impl BubbledUpState for StretchLayout { type Ctx = Stretch; - // Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes + /// Setup the layout fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx) where I: Iterator, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 0fda78071..4c869d1f9 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -9,16 +9,20 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout}; -use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; +use dioxus_native_core::real_dom::RealDom; +use futures::{ + channel::mpsc::{UnboundedReceiver, UnboundedSender}, + pin_mut, StreamExt, +}; +use layout::StretchLayout; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; -use tokio::time::Instant; use tui::{backend::CrosstermBackend, Terminal}; mod config; mod hooks; +mod layout; mod render; mod style; mod style_attributes; @@ -28,6 +32,16 @@ pub use config::*; pub use hooks::*; pub use render::*; +#[derive(Clone)] +pub struct TuiContext { + tx: UnboundedSender, +} +impl TuiContext { + pub fn quit(&self) { + self.tx.unbounded_send(InputEvent::Close).unwrap(); + } +} + pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) } @@ -40,9 +54,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { let (handler, state) = RinkInputHandler::new(rx, cx); - cx.provide_root_context(state); + // Setup input handling + let (event_tx, event_rx) = unbounded(); + let event_tx_clone = event_tx.clone(); + std::thread::spawn(move || { + let tick_rate = Duration::from_millis(1000); + loop { + if crossterm::event::poll(tick_rate).unwrap() { + // if crossterm::event::poll(timeout).unwrap() { + let evt = crossterm::event::read().unwrap(); + if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { + break; + } + } + } + }); - let mut tree: ClientTree = ClientTree::new(); + cx.provide_root_context(state); + cx.provide_root_context(TuiContext { tx: event_tx_clone }); + + let mut tree: RealDom = RealDom::new(); let mutations = dom.rebuild(); let to_update = tree.apply_mutations(vec![mutations]); let mut stretch = Stretch::new(); @@ -50,47 +81,22 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { .update_state(&dom, to_update, &mut stretch, &mut ()) .unwrap(); - render_vdom(&mut dom, tx, handler, cfg, tree, stretch).unwrap(); + render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap(); } -pub fn render_vdom( +fn render_vdom( vdom: &mut VirtualDom, - ctx: UnboundedSender, + crossterm_event_sender: UnboundedSender, + mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut tree: ClientTree, + mut tree: RealDom, mut stretch: Stretch, ) -> Result<()> { - // Setup input handling - let (tx, mut rx) = unbounded(); - std::thread::spawn(move || { - let tick_rate = Duration::from_millis(100); - let mut last_tick = Instant::now(); - loop { - // poll for tick rate duration, if no events, sent tick event. - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or_else(|| Duration::from_secs(0)); - - if crossterm::event::poll(timeout).unwrap() { - let evt = crossterm::event::read().unwrap(); - tx.unbounded_send(InputEvent::UserInput(evt)).unwrap(); - } - - if last_tick.elapsed() >= tick_rate { - tx.unbounded_send(InputEvent::Tick).unwrap(); - last_tick = Instant::now(); - } - } - }); - tokio::runtime::Builder::new_current_thread() .enable_all() .build()? .block_on(async { - /* - Get the terminal to calcualte the layout from - */ enable_raw_mode().unwrap(); let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); @@ -103,10 +109,9 @@ pub fn render_vdom( loop { /* - -> collect all the nodes - -> resolve events -> render the nodes in the right place with tui/crossterm - -> rendering + -> wait for changes + -> resolve events -> lazily update the layout and style based on nodes changed use simd to compare lines for diffing? @@ -119,11 +124,11 @@ pub fn render_vdom( terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); - // println!("{dims:?}"); let width = dims.width; let height = dims.height; let root_id = tree.root_id(); let root_node = tree[root_id].up_state.node.unwrap(); + stretch .compute_layout( root_node, @@ -138,18 +143,12 @@ pub fn render_vdom( })?; } - // resolve events before rendering - // todo: events do not trigger update? - for e in handler.get_events(&stretch, &mut tree) { - vdom.handle_message(SchedulerMsg::Event(e)); - } - use futures::future::{select, Either}; { let wait = vdom.wait_for_work(); pin_mut!(wait); - match select(wait, rx.next()).await { + match select(wait, event_reciever.next()).await { Either::Left((_a, _b)) => { // } @@ -166,17 +165,21 @@ pub fn render_vdom( TermEvent::Resize(_, _) => redraw = true, TermEvent::Mouse(_) => {} }, - InputEvent::Tick => {} // tick InputEvent::Close => break, }; if let InputEvent::UserInput(evt) = evt.unwrap() { - ctx.unbounded_send(evt).unwrap(); + crossterm_event_sender.unbounded_send(evt).unwrap(); } } } } + // resolve events before rendering + for e in handler.get_events(&stretch, &mut tree) { + vdom.handle_message(SchedulerMsg::Event(e)); + } + vdom.process_all_messages(); let mutations = vdom.work_with_deadline(|| false); // updates the tree's nodes let to_update = tree.apply_mutations(mutations); @@ -200,7 +203,6 @@ pub fn render_vdom( enum InputEvent { UserInput(TermEvent), - Tick, #[allow(dead_code)] Close, } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index fe21ba8ae..9e4310be4 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,7 +1,7 @@ +use crate::layout::StretchLayout; use dioxus_native_core::{ - client_tree::{ClientTree, TreeNode}, - layout::StretchLayout, layout_attributes::UnitSystem, + real_dom::{RealDom, TreeNode}, }; use std::io::Stdout; use stretch2::{ @@ -23,11 +23,11 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode<'a>( frame: &mut tui::Frame>, layout: &Stretch, - tree: &ClientTree, + tree: &RealDom, node: &TreeNode, cfg: Config, ) { - use dioxus_native_core::client_tree::TreeNodeType; + use dioxus_native_core::real_dom::TreeNodeType; match &node.node_type { TreeNodeType::Placeholder => return, @@ -51,7 +51,6 @@ pub fn render_vnode<'a>( fn render(self, area: Rect, mut buf: RinkBuffer) { for (i, c) in self.text.char_indices() { let mut new_cell = RinkCell::default(); - // println!("{:?}", self.style); new_cell.set_style(self.style); new_cell.symbol = c.to_string(); buf.set(area.left() + i as u16, area.top(), new_cell); diff --git a/packages/tui/src/style.rs b/packages/tui/src/style.rs index 823a94725..857322079 100644 --- a/packages/tui/src/style.rs +++ b/packages/tui/src/style.rs @@ -28,7 +28,7 @@ impl RinkColor { } else { let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16); let [or, og, ob] = to_rgb(other).map(|e| e as u16); - let sa: u16 = self.alpha as u16; + let sa = self.alpha as u16; let rsa = 255 - sa; Color::Rgb( ((sr * sa + or * rsa) / 255) as u8, diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 90cebe47d..6d0bb9672 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -31,8 +31,8 @@ use dioxus_core::{Attribute, VNode}; use dioxus_native_core::{ - client_tree::PushedDownState, layout_attributes::{parse_value, UnitSystem}, + real_dom::PushedDownState, }; use crate::style::{RinkColor, RinkStyle}; From 32b2e3a135dcebf0c12228492d380d38bf7162ae Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 2 Apr 2022 16:46:46 -0500 Subject: [PATCH 07/33] bugfixes, docs, and pass clippy --- docs/reference/src/platforms/tui.md | 49 ++++- examples/tui_color_test.rs | 1 + packages/native-core/src/layout_attributes.rs | 10 +- packages/native-core/src/real_dom.rs | 172 +++++++-------- packages/tui/src/config.rs | 14 +- packages/tui/src/hooks.rs | 203 ++++++++---------- packages/tui/src/layout.rs | 2 +- packages/tui/src/lib.rs | 68 +++--- packages/tui/src/render.rs | 27 ++- packages/tui/src/style_attributes.rs | 107 +++++---- src/lib.rs | 3 + 11 files changed, 350 insertions(+), 306 deletions(-) diff --git a/docs/reference/src/platforms/tui.md b/docs/reference/src/platforms/tui.md index e646bd4ba..82e9c1f2a 100644 --- a/docs/reference/src/platforms/tui.md +++ b/docs/reference/src/platforms/tui.md @@ -9,13 +9,12 @@ TUI support is currently quite experimental. Even the project name will change. ## Getting Set up -To tinker with TUI support, start by making a new package and adding our TUI package from git. +To tinker with TUI support, start by making a new package and adding our TUI feature. ```shell $ cargo new --bin demo $ cd demo -$ cargo add dioxus -$ cargo add rink --git https://github.com/DioxusLabs/rink.git +$ cargo add dioxus --features tui ``` @@ -27,10 +26,7 @@ Then, edit your `main.rs` with the basic template. use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + dioxus::tui::launch(app); } fn app(cx: Scope) -> Element { @@ -54,7 +50,44 @@ To run our app: $ cargo run ``` -Press "q" to close the app (yes, this is hardcoded, we are working on handlers to expose this in your code.) +Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a Configeration to disable the default quit and use the root TuiContext to quit on your own. + +```rust +// main +use dioxus::events::{KeyCode, KeyboardEvent}; +use dioxus::prelude::*; +use dioxus::tui::TuiContext; + +fn main() { + dioxus::tui::launch_cfg( + app, + dioxus::tui::Config { + ctrl_c_quit: false, + // Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI + rendering_mode: dioxus::tui::RenderingMode::Rgb, + }, + ); +} + +fn app(cx: Scope) -> Element { + let tui_ctx: TuiContext = cx.consume_context().unwrap(); + + cx.render(rsx! { + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code { + tui_ctx.quit(); + }, + + "Hello world!" + } + }) +} +``` ## Notes diff --git a/examples/tui_color_test.rs b/examples/tui_color_test.rs index 358fae1f6..d9ebad275 100644 --- a/examples/tui_color_test.rs +++ b/examples/tui_color_test.rs @@ -5,6 +5,7 @@ fn main() { app, dioxus::tui::Config { rendering_mode: dioxus::tui::RenderingMode::Ansi, + ..Default::default() }, ); } diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index 9518451fc..45461a3f9 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -255,11 +255,11 @@ pub enum UnitSystem { Point(f32), } -impl Into for UnitSystem { - fn into(self) -> Dimension { - match self { - Self::Percent(v) => Dimension::Percent(v), - Self::Point(v) => Dimension::Points(v), +impl From for Dimension { + fn from(other: UnitSystem) -> Dimension { + match other { + UnitSystem::Percent(v) => Dimension::Percent(v), + UnitSystem::Point(v) => Dimension::Points(v), } } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 1d697ff4a..5f914cd56 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -12,25 +12,30 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; #[derive(Debug)] pub struct RealDom { root: usize, - nodes: Vec>>, + nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } +impl Default for RealDom { + fn default() -> Self { + Self::new() + } +} + impl RealDom { pub fn new() -> RealDom { RealDom { root: 0, nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( + let v = vec![Some(Node::new( 0, - TreeNodeType::Element { + NodeType::Element { tag: "Root".to_string(), namespace: Some("Root"), children: Vec::new(), }, - ))); + ))]; v }, nodes_listening: FxHashMap::default(), @@ -47,7 +52,7 @@ impl RealDom { match e { PushRoot { root } => self.node_stack.push(root as usize), AppendChildren { many } => { - let target = if self.node_stack.len() >= many as usize + 1 { + let target = if self.node_stack.len() > many as usize { *self .node_stack .get(self.node_stack.len() - (many as usize + 1)) @@ -96,9 +101,9 @@ impl RealDom { self.remove(root as usize).unwrap(); } CreateTextNode { root, text } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Text { + NodeType::Text { text: text.to_string(), }, ); @@ -106,9 +111,9 @@ impl RealDom { self.node_stack.push(root as usize) } CreateElement { root, tag } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Element { + NodeType::Element { tag: tag.to_string(), namespace: None, children: Vec::new(), @@ -118,9 +123,9 @@ impl RealDom { self.node_stack.push(root as usize) } CreateElementNs { root, tag, ns } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Element { + NodeType::Element { tag: tag.to_string(), namespace: Some(ns), children: Vec::new(), @@ -130,7 +135,7 @@ impl RealDom { self.node_stack.push(root as usize) } CreatePlaceholder { root } => { - let n = TreeNode::new(root, TreeNodeType::Placeholder); + let n = Node::new(root, NodeType::Placeholder); self.insert(n); self.node_stack.push(root as usize) } @@ -159,7 +164,7 @@ impl RealDom { let target = &mut self[root as usize]; nodes_updated.push(root as usize); match &mut target.node_type { - TreeNodeType::Text { text } => { + NodeType::Text { text } => { *text = new_text.to_string(); } _ => unreachable!(), @@ -204,13 +209,13 @@ impl RealDom { let node_type = &node.node_type; let up_state = &mut node.up_state; let children = match node_type { - TreeNodeType::Element { children, .. } => Some(children), + NodeType::Element { children, .. } => Some(children), _ => None, }; // todo: reduce cloning state let old = up_state.clone(); let mut new = up_state.clone(); - let parent = node.parent.clone(); + let parent = node.parent; new.reduce( children .unwrap_or(&Vec::new()) @@ -254,19 +259,16 @@ impl RealDom { if new != old { to_rerender.insert(id); let node = &mut self[id as usize]; - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - let i = to_push.partition_point(|(other_id, h)| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }); - if i >= to_push.len() || to_push[i].0 != c.0 { - to_push.insert(i, (c.0, height + 1)); - } + if let NodeType::Element { children, .. } = &node.node_type { + for c in children { + let i = to_push.partition_point(|(other_id, h)| { + *h < height + 1 || (*h == height + 1 && *other_id < c.0) + }); + if i >= to_push.len() || to_push[i].0 != c.0 { + to_push.insert(i, (c.0, height + 1)); } } - _ => (), - }; + } node.down_state = new; } } @@ -287,31 +289,25 @@ impl RealDom { fn increase_height(&mut self, id: usize, amount: u16) { let n = &mut self[id]; n.height += amount; - match &n.node_type { - TreeNodeType::Element { children, .. } => { - for c in children.clone() { - self.increase_height(c.0, amount); - } + if let NodeType::Element { children, .. } = &n.node_type { + for c in children.clone() { + self.increase_height(c.0, amount); } - _ => (), } } // remove a node and it's children from the tree. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. fn inner( tree: &mut RealDom, id: usize, - ) -> Option> { + ) -> Option> { let mut node = tree.nodes[id as usize].take()?; - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(tree, c.0)?; - } + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children { + inner(tree, c.0)?; } - _ => (), } Some(node) } @@ -320,18 +316,15 @@ impl RealDom { let parent = &mut self[parent]; parent.remove_child(ElementId(id)); } - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(self, c.0)?; - } + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children { + inner(self, c.0)?; } - _ => (), } Some(node) } - fn insert(&mut self, node: TreeNode) { + fn insert(&mut self, node: Node) { let current_len = self.nodes.len(); let id = node.id.0; if current_len - 1 < node.id.0 { @@ -341,15 +334,15 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&TreeNode> { + pub fn get(&self, id: usize) -> Option<&Node> { self.nodes.get(id)?.as_ref() } - pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode> { + pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { self.nodes.get_mut(id)?.as_mut() } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); @@ -369,7 +362,7 @@ impl RealDom { if let Some(id) = e.id.get() { let tree_node = &self[id]; match &tree_node.node_type { - TreeNodeType::Element { + NodeType::Element { tag, namespace, children, @@ -398,7 +391,7 @@ impl RealDom { if let Some(id) = t.id.get() { let tree_node = &self[id]; match &tree_node.node_type { - TreeNodeType::Text { text } => t.text == text, + NodeType::Text { text } => t.text == text, _ => false, } } else { @@ -420,36 +413,53 @@ impl RealDom { } /// Call a function for each node in the tree, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode)) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { fn inner( tree: &RealDom, id: ElementId, - f: &mut impl FnMut(&TreeNode), + f: &mut impl FnMut(&Node), ) { let node = &tree[id]; f(node); - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(tree, *c, f); - } + if let NodeType::Element { children, .. } = &node.node_type { + for c in children { + inner(tree, *c, f); } - _ => (), } } - match &self[self.root].node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(self, *c, &mut f); + if let NodeType::Element { children, .. } = &self[self.root].node_type { + for c in children { + inner(self, *c, &mut f); + } + } + } + + /// Call a function for each node in the tree, depth first. + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { + fn inner( + tree: &mut RealDom, + id: ElementId, + f: &mut impl FnMut(&mut Node), + ) { + let node = &mut tree[id]; + f(node); + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children.clone() { + inner(tree, c, f); } } - _ => (), + } + let root = self.root; + if let NodeType::Element { children, .. } = &mut self[root].node_type { + for c in children.clone() { + inner(self, c, &mut f); + } } } } impl Index for RealDom { - type Output = TreeNode; + type Output = Node; fn index(&self, idx: usize) -> &Self::Output { self.get(idx).expect("Node does not exist") @@ -457,7 +467,7 @@ impl Index for RealDom { } impl Index for RealDom { - type Output = TreeNode; + type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { &self[idx.0] @@ -477,7 +487,7 @@ impl IndexMut for RealDom { +pub struct Node { /// The id of the node this node was created from. pub id: ElementId, /// The parent id of the node. @@ -487,13 +497,13 @@ pub struct TreeNode { /// 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 - pub node_type: TreeNodeType, + pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. pub height: u16, } #[derive(Debug, Clone)] -pub enum TreeNodeType { +pub enum NodeType { Text { text: String, }, @@ -505,9 +515,9 @@ pub enum TreeNodeType { Placeholder, } -impl TreeNode { - fn new(id: u64, node_type: TreeNodeType) -> Self { - TreeNode { +impl Node { + fn new(id: u64, node_type: NodeType) -> Self { + Node { id: ElementId(id as usize), parent: None, node_type, @@ -523,20 +533,14 @@ impl TreeNode { } fn add_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.push(child); - } - _ => (), + if let NodeType::Element { children, .. } = &mut self.node_type { + children.push(child); } } fn remove_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.retain(|c| c != &child); - } - _ => (), + if let NodeType::Element { children, .. } = &mut self.node_type { + children.retain(|c| c != &child); } } diff --git a/packages/tui/src/config.rs b/packages/tui/src/config.rs index 46a0926b8..924163a7d 100644 --- a/packages/tui/src/config.rs +++ b/packages/tui/src/config.rs @@ -1,6 +1,18 @@ -#[derive(Default, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Config { pub rendering_mode: RenderingMode, + /// Should the terminal quit when the user presses `ctrl+c`? + /// To handle quiting on your own, use the [crate::TuiContext] root context. + pub ctrl_c_quit: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rendering_mode: Default::default(), + ctrl_c_quit: true, + } + } } #[derive(Clone, Copy)] diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 3813b97ae..3a488e433 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,8 +5,7 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::real_dom::{RealDom, TreeNode}; -use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; +use dioxus_native_core::real_dom::{Node, RealDom}; use std::{ any::Any, cell::RefCell, @@ -162,7 +161,7 @@ impl InnerInputState { } } - fn update<'a>( + fn update( &mut self, evts: &mut Vec, resolved_events: &mut Vec, @@ -176,9 +175,11 @@ impl InnerInputState { self.wheel = None; + println!("update {evts:?}"); for e in evts.iter_mut() { self.apply_event(e); } + println!("->update {evts:?}"); self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); @@ -216,7 +217,7 @@ impl InnerInputState { data: Arc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &TreeNode, + node: &Node, tree: &RealDom, ) { // only trigger event if the event was not triggered already by a child @@ -231,7 +232,7 @@ impl InnerInputState { priority: EventPriority::Medium, name, element: Some(node.id), - data: data, + data, }) } } @@ -269,17 +270,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if previously_contained { - try_create_event( - "mousemove", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && previously_contained { + try_create_event( + "mousemove", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -295,17 +294,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if !previously_contained { - try_create_event( - "mouseenter", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && !previously_contained { + try_create_event( + "mouseenter", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -321,17 +318,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if !previously_contained { - try_create_event( - "mouseover", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && !previously_contained { + try_create_event( + "mouseover", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -343,17 +338,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.clicked { - try_create_event( - "mousedown", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.clicked { + try_create_event( + "mousedown", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -365,17 +358,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released { - try_create_event( - "mouseup", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released { + try_create_event( + "mouseup", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -387,17 +378,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released && data.mouse_data.button == 0 { - try_create_event( - "click", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released && data.mouse_data.button == 0 { + try_create_event( + "click", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -409,17 +398,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released && data.mouse_data.button == 2 { - try_create_event( - "contextmenu", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released && data.mouse_data.button == 2 { + try_create_event( + "contextmenu", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -431,18 +418,16 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if let Some(w) = data.wheel_data { - if data.wheel_delta != 0.0 { - try_create_event( - "wheel", - Arc::new(clone_wheel_data(w)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if let Some(w) = data.wheel_data { + if currently_contains && data.wheel_delta != 0.0 { + try_create_event( + "wheel", + Arc::new(clone_wheel_data(w)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -511,24 +496,22 @@ pub struct RinkInputHandler { impl RinkInputHandler { /// global context that handles events /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once - pub fn new( - mut receiver: UnboundedReceiver, - cx: &ScopeState, - ) -> (Self, Rc>) { + pub fn new() -> ( + Self, + Rc>, + impl FnMut(crossterm::event::Event), + ) { let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events2 = Rc::downgrade(&queued_events); - cx.push_future(async move { - while let Some(evt) = receiver.next().await { - if let Some(evt) = get_event(evt) { - if let Some(v) = queued_events2.upgrade() { - (*v).borrow_mut().push(evt); - } else { - break; - } + let regester_event = move |evt: crossterm::event::Event| { + if let Some(evt) = get_event(evt) { + if let Some(v) = queued_events2.upgrade() { + println!("queued event: {:?}", evt); + (*v).borrow_mut().push(evt); } } - }); + }; let state = Rc::new(RefCell::new(InnerInputState::new())); @@ -538,14 +521,16 @@ impl RinkInputHandler { queued_events, }, state, + regester_event, ) } - pub fn get_events<'a>( + pub fn get_events( &self, layout: &Stretch, tree: &mut RealDom, ) -> Vec { + println!("get_events"); let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( @@ -799,7 +784,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent Some(EventData::Keyboard(KeyboardData { char_code: code.raw_code(), - key: key_str.to_string(), + key: key_str, key_code: code, alt_key: event.modifiers.contains(KeyModifiers::ALT), ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 9d9be8f6a..4224fbdd5 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -65,7 +65,7 @@ impl BubbledUpState for StretchLayout { } if let Some(n) = self.node { - if &stretch.children(n).unwrap() != &child_layout { + if stretch.children(n).unwrap() != child_layout { stretch.set_children(n, &child_layout).unwrap(); } if self.style != style { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4c869d1f9..a70acbd28 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,6 +1,3 @@ -// notes: -// mouse events binary search was broken for absolutely positioned elements - use anyhow::Result; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, @@ -48,11 +45,8 @@ pub fn launch(app: Component<()>) { pub fn launch_cfg(app: Component<()>, cfg: Config) { let mut dom = VirtualDom::new(app); - let (tx, rx) = unbounded(); - let cx = dom.base_scope(); - - let (handler, state) = RinkInputHandler::new(rx, cx); + let (handler, state, register_event) = RinkInputHandler::new(); // Setup input handling let (event_tx, event_rx) = unbounded(); @@ -70,6 +64,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { } }); + let cx = dom.base_scope(); cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); @@ -81,17 +76,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { .update_state(&dom, to_update, &mut stretch, &mut ()) .unwrap(); - render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap(); + render_vdom( + &mut dom, + event_rx, + handler, + cfg, + tree, + stretch, + register_event, + ) + .unwrap(); } fn render_vdom( vdom: &mut VirtualDom, - crossterm_event_sender: UnboundedSender, mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, mut tree: RealDom, mut stretch: Stretch, + mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() .enable_all() @@ -105,7 +109,7 @@ fn render_vdom( terminal.clear().unwrap(); let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); - let mut redraw = true; + let mut resized = true; loop { /* @@ -119,8 +123,8 @@ fn render_vdom( todo: lazy re-rendering */ - if !to_rerender.is_empty() || redraw { - redraw = false; + if !to_rerender.is_empty() || resized { + resized = false; terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); @@ -138,8 +142,8 @@ fn render_vdom( }, ) .unwrap(); - let root = &tree[tree.root_id()]; - render::render_vnode(frame, &stretch, &tree, &root, cfg); + // let root = &tree[tree.root_id()]; + // render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; } @@ -158,35 +162,44 @@ fn render_vdom( TermEvent::Key(key) => { if matches!(key.code, KeyCode::Char('C' | 'c')) && key.modifiers.contains(KeyModifiers::CONTROL) + && cfg.ctrl_c_quit { break; } } - TermEvent::Resize(_, _) => redraw = true, + TermEvent::Resize(_, _) => resized = true, TermEvent::Mouse(_) => {} }, InputEvent::Close => break, }; if let InputEvent::UserInput(evt) = evt.unwrap() { - crossterm_event_sender.unbounded_send(evt).unwrap(); + register_event(evt); } } } } - // resolve events before rendering - for e in handler.get_events(&stretch, &mut tree) { - vdom.handle_message(SchedulerMsg::Event(e)); + { + // resolve events before rendering + let evts = handler.get_events(&stretch, &mut tree); + println!("evts: {:?}", evts); + for e in evts { + vdom.handle_message(SchedulerMsg::Event(e)); + } + let mutations = vdom.work_with_deadline(|| false); + // updates the tree's nodes + let to_update = tree.apply_mutations(mutations); + // update the style and layout + to_rerender.extend( + tree.update_state(vdom, to_update, &mut stretch, &mut ()) + .unwrap() + .iter(), + ) } - vdom.process_all_messages(); - let mutations = vdom.work_with_deadline(|| false); - // updates the tree's nodes - let to_update = tree.apply_mutations(mutations); - // update the style and layout - to_rerender = tree - .update_state(&vdom, to_update, &mut stretch, &mut ()) - .unwrap(); + println!(); + println!(); + println!(); } disable_raw_mode()?; @@ -203,6 +216,5 @@ fn render_vdom( enum InputEvent { UserInput(TermEvent), - #[allow(dead_code)] Close, } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 9e4310be4..c00830907 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,7 +1,7 @@ use crate::layout::StretchLayout; use dioxus_native_core::{ layout_attributes::UnitSystem, - real_dom::{RealDom, TreeNode}, + real_dom::{Node, RealDom}, }; use std::io::Stdout; use stretch2::{ @@ -20,18 +20,17 @@ use crate::{ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; -pub fn render_vnode<'a>( +pub fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, tree: &RealDom, - node: &TreeNode, + node: &Node, cfg: Config, ) { - use dioxus_native_core::real_dom::TreeNodeType; + use dioxus_native_core::real_dom::NodeType; - match &node.node_type { - TreeNodeType::Placeholder => return, - _ => (), + if let NodeType::Placeholder = &node.node_type { + return; } let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); @@ -40,7 +39,7 @@ pub fn render_vnode<'a>( let Size { width, height } = size; match &node.node_type { - TreeNodeType::Text { text } => { + NodeType::Text { text } => { #[derive(Default)] struct Label<'a> { text: &'a str, @@ -59,7 +58,7 @@ pub fn render_vnode<'a>( } let label = Label { - text: &text, + text, style: node.down_state.style, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -69,7 +68,7 @@ pub fn render_vnode<'a>( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - TreeNodeType::Element { children, .. } => { + NodeType::Element { children, .. } => { let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -81,11 +80,11 @@ pub fn render_vnode<'a>( render_vnode(frame, layout, tree, &tree[c.0], cfg); } } - TreeNodeType::Placeholder => unreachable!(), + NodeType::Placeholder => unreachable!(), } } -impl RinkWidget for &TreeNode { +impl RinkWidget for &Node { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -249,8 +248,8 @@ impl RinkWidget for &TreeNode { fn get_radius(border: &BorderEdge, area: Rect) -> f32 { match border.style { - BorderStyle::HIDDEN => 0.0, - BorderStyle::NONE => 0.0, + BorderStyle::Hidden => 0.0, + BorderStyle::None => 0.0, _ => match border.radius { UnitSystem::Percent(p) => p * area.width as f32 / 100.0, UnitSystem::Point(p) => p, diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 6d0bb9672..710610104 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -51,33 +51,28 @@ impl PushedDownState for StyleModifier { if parent.is_some() { self.style.fg = None; } - match vnode { - VNode::Element(el) => { - // handle text modifier elements - if el.namespace.is_none() { - match el.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), - "mark" => apply_style_attributes( - "background-color", - "rgba(241, 231, 64, 50%)", - self, - ), - _ => (), + if let VNode::Element(el) = vnode { + // handle text modifier elements + if el.namespace.is_none() { + match el.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), + "mark" => { + apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self) } - } - - // gather up all the styles from the attribute list - for &Attribute { name, value, .. } in el.attributes { - apply_style_attributes(name, value, self); + _ => (), } } - _ => (), + + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in el.attributes { + apply_style_attributes(name, value, self); + } } // keep the text styling from the parent element @@ -125,7 +120,7 @@ impl Default for BorderEdge { fn default() -> Self { Self { color: None, - style: BorderStyle::NONE, + style: BorderStyle::None, width: UnitSystem::Point(0.0), radius: UnitSystem::Point(0.0), } @@ -134,16 +129,16 @@ impl Default for BorderEdge { #[derive(Clone, Copy, PartialEq, Debug)] pub enum BorderStyle { - DOTTED, - DASHED, - SOLID, - DOUBLE, - GROOVE, - RIDGE, - INSET, - OUTSET, - HIDDEN, - NONE, + Dotted, + Dashed, + Solid, + Double, + Groove, + Ridge, + Inset, + Outset, + Hidden, + None, } impl BorderStyle { @@ -160,16 +155,16 @@ impl BorderStyle { ..NORMAL }; match self { - BorderStyle::DOTTED => Some(DOTTED), - BorderStyle::DASHED => Some(DASHED), - BorderStyle::SOLID => Some(NORMAL), - BorderStyle::DOUBLE => Some(DOUBLE), - BorderStyle::GROOVE => Some(NORMAL), - BorderStyle::RIDGE => Some(NORMAL), - BorderStyle::INSET => Some(NORMAL), - BorderStyle::OUTSET => Some(NORMAL), - BorderStyle::HIDDEN => None, - BorderStyle::NONE => None, + BorderStyle::Dotted => Some(DOTTED), + BorderStyle::Dashed => Some(DASHED), + BorderStyle::Solid => Some(NORMAL), + BorderStyle::Double => Some(DOUBLE), + BorderStyle::Groove => Some(NORMAL), + BorderStyle::Ridge => Some(NORMAL), + BorderStyle::Inset => Some(NORMAL), + BorderStyle::Outset => Some(NORMAL), + BorderStyle::Hidden => None, + BorderStyle::None => None, } } } @@ -334,16 +329,16 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifier) { fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { fn parse_border_style(v: &str) -> BorderStyle { match v { - "dotted" => BorderStyle::DOTTED, - "dashed" => BorderStyle::DASHED, - "solid" => BorderStyle::SOLID, - "double" => BorderStyle::DOUBLE, - "groove" => BorderStyle::GROOVE, - "ridge" => BorderStyle::RIDGE, - "inset" => BorderStyle::INSET, - "outset" => BorderStyle::OUTSET, - "none" => BorderStyle::NONE, - "hidden" => BorderStyle::HIDDEN, + "dotted" => BorderStyle::Dotted, + "dashed" => BorderStyle::Dashed, + "solid" => BorderStyle::Solid, + "double" => BorderStyle::Double, + "groove" => BorderStyle::Groove, + "ridge" => BorderStyle::Ridge, + "inset" => BorderStyle::Inset, + "outset" => BorderStyle::Outset, + "none" => BorderStyle::None, + "hidden" => BorderStyle::Hidden, _ => todo!(), } } @@ -505,7 +500,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { .zip(style.modifier.borders.slice().iter_mut()) { if let Some(w) = parse_value(v) { - width.width = w.into(); + width.width = w; } } } diff --git a/src/lib.rs b/src/lib.rs index cebffcf17..e9e67408e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub use dioxus_desktop as desktop; #[cfg(feature = "tui")] pub use dioxus_tui as tui; +#[cfg(feature = "native-core")] +pub use dioxus_native_core as native_core; + #[cfg(feature = "fermi")] pub use fermi; From a001b7ce909c91053f158c5dafaac6b8a45af220 Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Sat, 2 Apr 2022 20:02:51 -0500 Subject: [PATCH 08/33] re-enable rendering --- packages/tui/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index a70acbd28..1e1373d9c 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -142,8 +142,8 @@ fn render_vdom( }, ) .unwrap(); - // let root = &tree[tree.root_id()]; - // render::render_vnode(frame, &stretch, &tree, &root, cfg); + let root = &tree[tree.root_id()]; + render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; } From 56af1f7988039cff9e3e8e267512e86f888f9e50 Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Sat, 2 Apr 2022 20:24:33 -0500 Subject: [PATCH 09/33] remove extra prints --- packages/tui/src/hooks.rs | 4 ---- packages/tui/src/lib.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 3a488e433..62b06a1a2 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -175,11 +175,9 @@ impl InnerInputState { self.wheel = None; - println!("update {evts:?}"); for e in evts.iter_mut() { self.apply_event(e); } - println!("->update {evts:?}"); self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); @@ -507,7 +505,6 @@ impl RinkInputHandler { let regester_event = move |evt: crossterm::event::Event| { if let Some(evt) = get_event(evt) { if let Some(v) = queued_events2.upgrade() { - println!("queued event: {:?}", evt); (*v).borrow_mut().push(evt); } } @@ -530,7 +527,6 @@ impl RinkInputHandler { layout: &Stretch, tree: &mut RealDom, ) -> Vec { - println!("get_events"); let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1e1373d9c..3c0fd6aa0 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -183,7 +183,6 @@ fn render_vdom( { // resolve events before rendering let evts = handler.get_events(&stretch, &mut tree); - println!("evts: {:?}", evts); for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } @@ -197,9 +196,6 @@ fn render_vdom( .iter(), ) } - println!(); - println!(); - println!(); } disable_raw_mode()?; From 06e8785938908e4fd7327eaf89d0b7cb316c0993 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Apr 2022 12:19:21 -0500 Subject: [PATCH 10/33] add PopRoot --- packages/native-core/src/real_dom.rs | 3 +++ packages/native-core/tests/change_nodes.rs | 4 ++-- packages/native-core/tests/initial_build.rs | 4 ++-- packages/native-core/tests/state.rs | 8 ++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 5f914cd56..e7eecdbc4 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -176,6 +176,9 @@ impl RealDom { RemoveAttribute { root, .. } => { nodes_updated.push(root as usize); } + PopRoot {} => { + self.node_stack.pop(); + } } } } diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index 387481462..ed606dca0 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -6,7 +6,7 @@ use dioxus_native_core::real_dom::RealDom; use std::cell::Cell; #[test] -fn tree_remove_node() { +fn remove_node() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div {}) @@ -80,7 +80,7 @@ fn tree_remove_node() { } #[test] -fn tree_add_node() { +fn add_node() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div {}) diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index d17ea05d4..08152beef 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -7,7 +7,7 @@ use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; #[test] -fn tree_initial_build_simple() { +fn initial_build_simple() { use std::cell::Cell; #[allow(non_snake_case)] @@ -40,7 +40,7 @@ fn tree_initial_build_simple() { } #[test] -fn tree_initial_build_with_children() { +fn initial_build_with_children() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div {}) diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index a31110f18..17b13ef75 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/state.rs @@ -59,7 +59,7 @@ impl PushedDownState for PushedDownStateTester { } #[test] -fn tree_state_initial() { +fn state_initial() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { @@ -114,7 +114,7 @@ fn tree_state_initial() { } #[test] -fn tree_state_reduce_initally_called_minimally() { +fn state_reduce_initally_called_minimally() { #[derive(Debug, Clone, PartialEq, Default)] struct CallCounter(u32); impl BubbledUpState for CallCounter { @@ -190,7 +190,7 @@ fn tree_state_reduce_initally_called_minimally() { } #[test] -fn tree_state_reduce_down_called_minimally_on_update() { +fn state_reduce_down_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { @@ -255,7 +255,7 @@ fn tree_state_reduce_down_called_minimally_on_update() { } #[test] -fn tree_state_reduce_up_called_minimally_on_update() { +fn state_reduce_up_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { From 351722d136d410dfbae4db22011414b7ec80f3ca Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 4 Apr 2022 13:37:04 -0500 Subject: [PATCH 11/33] update dependancy to stretch2 master and remove refrences to tree --- packages/native-core/Cargo.toml | 3 +- packages/native-core/src/real_dom.rs | 46 +++++++-------- packages/native-core/tests/change_nodes.rs | 40 ++++++------- packages/native-core/tests/initial_build.rs | 30 +++++----- packages/native-core/tests/state.rs | 44 +++++++------- packages/tui/Cargo.toml | 3 +- packages/tui/src/hooks.rs | 56 +++++++++--------- packages/tui/src/lib.rs | 26 ++++----- packages/tui/src/render.rs | 4 +- packages/tui/tests/relayout.rs | 64 --------------------- 10 files changed, 125 insertions(+), 191 deletions(-) delete mode 100644 packages/tui/tests/relayout.rs diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index c3816dac7..83e403487 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -11,8 +11,7 @@ dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } -# stretch2 = "0.4.1" -stretch2 = { git = "https://github.com/Demonthos/stretch.git" } +stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index e7eecdbc4..239c90aea 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -6,9 +6,9 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -/// A tree 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. -/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] to update the tree and [Tree::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)] pub struct RealDom { root: usize, @@ -43,7 +43,7 @@ impl RealDom { } } - /// Updates the tree, 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) -> Vec { let mut nodes_updated = Vec::new(); for mutations in mutations_vec { @@ -299,17 +299,17 @@ impl RealDom { } } - // remove a node and it's children from the tree. + // remove a node and it's children from the dom. fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. fn inner( - tree: &mut RealDom, + dom: &mut RealDom, id: usize, ) -> Option> { - let mut node = tree.nodes[id as usize].take()?; + let mut node = dom.nodes[id as usize].take()?; if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { - inner(tree, c.0)?; + inner(dom, c.0)?; } } Some(node) @@ -355,7 +355,7 @@ impl RealDom { } } - /// Check if the tree contains a node and its children. + /// Check if the dom contains a node and its children. pub fn contains_node(&self, node: &VNode) -> bool { match node { VNode::Component(_) => { @@ -363,8 +363,8 @@ impl RealDom { } VNode::Element(e) => { if let Some(id) = e.id.get() { - let tree_node = &self[id]; - match &tree_node.node_type { + let dom_node = &self[id]; + match &dom_node.node_type { NodeType::Element { tag, namespace, @@ -392,8 +392,8 @@ impl RealDom { VNode::Placeholder(_) => true, VNode::Text(t) => { if let Some(id) = t.id.get() { - let tree_node = &self[id]; - match &tree_node.node_type { + let dom_node = &self[id]; + match &dom_node.node_type { NodeType::Text { text } => t.text == text, _ => false, } @@ -404,9 +404,9 @@ impl RealDom { } } - /// Return the number of nodes in the tree. + /// Return the number of nodes in the dom. pub fn size(&self) -> usize { - // The tree has a root node, ignore it. + // The dom has a root node, ignore it. self.nodes.iter().filter(|n| n.is_some()).count() - 1 } @@ -415,18 +415,18 @@ impl RealDom { self.root } - /// Call a function for each node in the tree, depth first. + /// Call a function for each node in the dom, depth first. pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { fn inner( - tree: &RealDom, + dom: &RealDom, id: ElementId, f: &mut impl FnMut(&Node), ) { - let node = &tree[id]; + let node = &dom[id]; f(node); if let NodeType::Element { children, .. } = &node.node_type { for c in children { - inner(tree, *c, f); + inner(dom, *c, f); } } } @@ -437,18 +437,18 @@ impl RealDom { } } - /// Call a function for each node in the tree, 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)) { fn inner( - tree: &mut RealDom, + dom: &mut RealDom, id: ElementId, f: &mut impl FnMut(&mut Node), ) { - let node = &mut tree[id]; + let node = &mut dom[id]; f(node); if let NodeType::Element { children, .. } = &mut node.node_type { for c in children.clone() { - inner(tree, c, f); + inner(dom, c, f); } } } @@ -488,7 +488,7 @@ impl IndexMut for RealDom { /// The id of the node this node was created from. diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index ed606dca0..6afb225d3 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -20,9 +20,9 @@ fn remove_node() { } }); - let mut tree: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom<(), ()> = RealDom::new(); - let _to_update = tree.apply_mutations(vec![mutations]); + let _to_update = dom.apply_mutations(vec![mutations]); let child_div = VElement { id: Cell::new(Some(ElementId(2))), key: None, @@ -45,10 +45,10 @@ fn remove_node() { children: &[child_div_el], }; - assert_eq!(tree.size(), 2); - assert!(&tree.contains_node(&VNode::Element(&root_div))); - assert_eq!(tree[1].height, 1); - assert_eq!(tree[2].height, 2); + assert_eq!(dom.size(), 2); + assert!(&dom.contains_node(&VNode::Element(&root_div))); + assert_eq!(dom[1].height, 1); + assert_eq!(dom[2].height, 2); let vdom = VirtualDom::new(Base); let mutations = vdom.diff_lazynodes( @@ -61,7 +61,7 @@ fn remove_node() { div{} }, ); - tree.apply_mutations(vec![mutations.1]); + dom.apply_mutations(vec![mutations.1]); let new_root_div = VElement { id: Cell::new(Some(ElementId(1))), @@ -74,9 +74,9 @@ fn remove_node() { children: &[], }; - assert_eq!(tree.size(), 1); - assert!(&tree.contains_node(&VNode::Element(&new_root_div))); - assert_eq!(tree[1].height, 1); + assert_eq!(dom.size(), 1); + assert!(&dom.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(dom[1].height, 1); } #[test] @@ -92,9 +92,9 @@ fn add_node() { div{} }); - let mut tree: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom<(), ()> = RealDom::new(); - let _to_update = tree.apply_mutations(vec![mutations]); + let _to_update = dom.apply_mutations(vec![mutations]); let root_div = VElement { id: Cell::new(Some(ElementId(1))), @@ -107,9 +107,9 @@ fn add_node() { children: &[], }; - assert_eq!(tree.size(), 1); - assert!(&tree.contains_node(&VNode::Element(&root_div))); - assert_eq!(tree[1].height, 1); + assert_eq!(dom.size(), 1); + assert!(&dom.contains_node(&VNode::Element(&root_div))); + assert_eq!(dom[1].height, 1); let vdom = VirtualDom::new(Base); let mutations = vdom.diff_lazynodes( @@ -122,7 +122,7 @@ fn add_node() { } }, ); - tree.apply_mutations(vec![mutations.1]); + dom.apply_mutations(vec![mutations.1]); let child_div = VElement { id: Cell::new(Some(ElementId(2))), @@ -146,8 +146,8 @@ fn add_node() { children: &[child_div_el], }; - assert_eq!(tree.size(), 2); - assert!(&tree.contains_node(&VNode::Element(&new_root_div))); - assert_eq!(tree[1].height, 1); - assert_eq!(tree[2].height, 2); + assert_eq!(dom.size(), 2); + assert!(&dom.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(dom[1].height, 1); + assert_eq!(dom[2].height, 2); } diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index 08152beef..df054dcdc 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -21,9 +21,9 @@ fn initial_build_simple() { div{} }); - let mut tree: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom<(), ()> = RealDom::new(); - let _to_update = tree.apply_mutations(vec![mutations]); + let _to_update = dom.apply_mutations(vec![mutations]); let root_div = VElement { id: Cell::new(Some(ElementId(1))), key: None, @@ -34,9 +34,9 @@ fn initial_build_simple() { attributes: &[], children: &[], }; - assert_eq!(tree.size(), 1); - assert!(&tree.contains_node(&VNode::Element(&root_div))); - assert_eq!(tree[1].height, 1); + assert_eq!(dom.size(), 1); + assert!(&dom.contains_node(&VNode::Element(&root_div))); + assert_eq!(dom[1].height, 1); } #[test] @@ -60,9 +60,9 @@ fn initial_build_with_children() { } }); - let mut tree: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom<(), ()> = RealDom::new(); - let _to_update = tree.apply_mutations(vec![mutations]); + let _to_update = dom.apply_mutations(vec![mutations]); let first_text = VText { id: Cell::new(Some(ElementId(3))), text: "hello", @@ -113,12 +113,12 @@ fn initial_build_with_children() { attributes: &[], children: &[child_div_node], }; - assert_eq!(tree.size(), 6); - assert!(&tree.contains_node(&VNode::Element(&root_div))); - assert_eq!(tree[1].height, 1); - assert_eq!(tree[2].height, 2); - assert_eq!(tree[3].height, 3); - assert_eq!(tree[4].height, 3); - assert_eq!(tree[5].height, 4); - assert_eq!(tree[6].height, 3); + assert_eq!(dom.size(), 6); + assert!(&dom.contains_node(&VNode::Element(&root_div))); + assert_eq!(dom[1].height, 1); + assert_eq!(dom[2].height, 2); + assert_eq!(dom[3].height, 3); + assert_eq!(dom[4].height, 3); + assert_eq!(dom[5].height, 4); + assert_eq!(dom[6].height, 3); } diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index 17b13ef75..ff0dee1d5 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/state.rs @@ -77,12 +77,12 @@ fn state_initial() { } }); - let mut tree: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); - let nodes_updated = tree.apply_mutations(vec![mutations]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42); + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42); - let root_div = &tree[1]; + let root_div = &dom[1]; assert_eq!(root_div.up_state.0, "1"); assert_eq!( root_div.up_state.1, @@ -94,7 +94,7 @@ fn state_initial() { assert_eq!(root_div.down_state.0, "1"); assert_eq!(root_div.down_state.1, None); - let child_p = &tree[2]; + 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"); @@ -103,7 +103,7 @@ fn state_initial() { Some(Box::new(PushedDownStateTester("1".to_string(), None))) ); - let child_h1 = &tree[3]; + 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"); @@ -178,12 +178,12 @@ fn state_reduce_initally_called_minimally() { } }); - let mut tree: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); - let nodes_updated = tree.apply_mutations(vec![mutations]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse_depth_first(|n| { + dom.traverse_depth_first(|n| { assert_eq!(n.up_state.0, 1); assert_eq!(n.down_state.0, 1); }); @@ -233,11 +233,11 @@ fn state_reduce_down_called_minimally_on_update() { } }); - let mut tree: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); - let nodes_updated = tree.apply_mutations(vec![mutations]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); - let nodes_updated = tree.apply_mutations(vec![Mutations { + 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", @@ -247,9 +247,9 @@ fn state_reduce_down_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse_depth_first(|n| { + dom.traverse_depth_first(|n| { assert_eq!(n.down_state.0, 2); }); } @@ -298,11 +298,11 @@ fn state_reduce_up_called_minimally_on_update() { } }); - let mut tree: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); - let nodes_updated = tree.apply_mutations(vec![mutations]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); - let nodes_updated = tree.apply_mutations(vec![Mutations { + 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", @@ -312,9 +312,9 @@ fn state_reduce_up_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); - tree.traverse_depth_first(|n| { + dom.traverse_depth_first(|n| { assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); }); } diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 92c915e76..6a65af607 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -22,7 +22,6 @@ crossterm = "0.23.0" anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" -# stretch2 = "0.4.1" -stretch2 = { git = "https://github.com/Demonthos/stretch.git" } +stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 62b06a1a2..512fab8ba 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -166,7 +166,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut RealDom, + dom: &mut RealDom, ) { let previous_mouse = self .mouse @@ -179,7 +179,7 @@ impl InnerInputState { self.apply_event(e); } - self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); + self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom); // for s in &self.subscribers { // s(); @@ -191,7 +191,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut RealDom, + dom: &mut RealDom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -216,14 +216,14 @@ impl InnerInputState { will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &Node, - tree: &RealDom, + dom: &RealDom, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { let mut parent = node.parent; while let Some(parent_id) = parent { will_bubble.insert(parent_id); - parent = tree[parent_id.0].parent; + parent = dom[parent_id.0].parent; } resolved_events.push(UserEvent { scope_id: None, @@ -260,7 +260,7 @@ impl InnerInputState { { // mousemove let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mousemove") { + for node in dom.get_listening_sorted("mousemove") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -275,7 +275,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -284,7 +284,7 @@ impl InnerInputState { { // mouseenter let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mouseenter") { + for node in dom.get_listening_sorted("mouseenter") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -299,7 +299,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -308,7 +308,7 @@ impl InnerInputState { { // mouseover let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mouseover") { + for node in dom.get_listening_sorted("mouseover") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -323,7 +323,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -332,7 +332,7 @@ impl InnerInputState { { // mousedown let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mousedown") { + for node in dom.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -343,7 +343,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -352,7 +352,7 @@ impl InnerInputState { { // mouseup let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mouseup") { + for node in dom.get_listening_sorted("mouseup") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -363,7 +363,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -372,7 +372,7 @@ impl InnerInputState { { // click let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("click") { + for node in dom.get_listening_sorted("click") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -383,7 +383,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -392,7 +392,7 @@ impl InnerInputState { { // contextmenu let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("contextmenu") { + for node in dom.get_listening_sorted("contextmenu") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -403,7 +403,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -412,7 +412,7 @@ impl InnerInputState { { // wheel let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("wheel") { + for node in dom.get_listening_sorted("wheel") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -424,7 +424,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -434,7 +434,7 @@ impl InnerInputState { { // mouseleave let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mouseleave") { + for node in dom.get_listening_sorted("mouseleave") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -449,7 +449,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -458,7 +458,7 @@ impl InnerInputState { { // mouseout let mut will_bubble = FxHashSet::default(); - for node in tree.get_listening_sorted("mouseout") { + for node in dom.get_listening_sorted("mouseout") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data .old_pos @@ -473,7 +473,7 @@ impl InnerInputState { &mut will_bubble, resolved_events, node, - tree, + dom, ); } } @@ -525,7 +525,7 @@ impl RinkInputHandler { pub fn get_events( &self, layout: &Stretch, - tree: &mut RealDom, + dom: &mut RealDom, ) -> Vec { let mut resolved_events = Vec::new(); @@ -533,7 +533,7 @@ impl RinkInputHandler { &mut (*self.queued_events).borrow_mut(), &mut resolved_events, layout, - tree, + dom, ); let events = self @@ -569,7 +569,7 @@ impl RinkInputHandler { } } for (event, datas) in hm { - for node in tree.get_listening_sorted(event) { + for node in dom.get_listening_sorted(event) { for data in &datas { resolved_events.push(UserEvent { scope_id: None, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 3c0fd6aa0..0d8687b09 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -68,11 +68,11 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); - let mut tree: RealDom = RealDom::new(); + let mut rdom: RealDom = RealDom::new(); let mutations = dom.rebuild(); - let to_update = tree.apply_mutations(vec![mutations]); + let to_update = rdom.apply_mutations(vec![mutations]); let mut stretch = Stretch::new(); - let _to_rerender = tree + let _to_rerender = rdom .update_state(&dom, to_update, &mut stretch, &mut ()) .unwrap(); @@ -81,7 +81,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { event_rx, handler, cfg, - tree, + rdom, stretch, register_event, ) @@ -93,7 +93,7 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut tree: RealDom, + mut rdom: RealDom, mut stretch: Stretch, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { @@ -130,8 +130,8 @@ fn render_vdom( let dims = frame.size(); let width = dims.width; let height = dims.height; - let root_id = tree.root_id(); - let root_node = tree[root_id].up_state.node.unwrap(); + let root_id = rdom.root_id(); + let root_node = rdom[root_id].up_state.node.unwrap(); stretch .compute_layout( @@ -142,8 +142,8 @@ fn render_vdom( }, ) .unwrap(); - let root = &tree[tree.root_id()]; - render::render_vnode(frame, &stretch, &tree, &root, cfg); + let root = &rdom[rdom.root_id()]; + render::render_vnode(frame, &stretch, &rdom, &root, cfg); })?; } @@ -182,16 +182,16 @@ fn render_vdom( { // resolve events before rendering - let evts = handler.get_events(&stretch, &mut tree); + let evts = handler.get_events(&stretch, &mut rdom); for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } let mutations = vdom.work_with_deadline(|| false); - // updates the tree's nodes - let to_update = tree.apply_mutations(mutations); + // updates the dom's nodes + let to_update = rdom.apply_mutations(mutations); // update the style and layout to_rerender.extend( - tree.update_state(vdom, to_update, &mut stretch, &mut ()) + rdom.update_state(vdom, to_update, &mut stretch, &mut ()) .unwrap() .iter(), ) diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index c00830907..94cd90a62 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -23,7 +23,7 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, - tree: &RealDom, + rdom: &RealDom, node: &Node, cfg: Config, ) { @@ -77,7 +77,7 @@ pub fn render_vnode( } for c in children { - render_vnode(frame, layout, tree, &tree[c.0], cfg); + render_vnode(frame, layout, rdom, &rdom[c.0], cfg); } } NodeType::Placeholder => unreachable!(), diff --git a/packages/tui/tests/relayout.rs b/packages/tui/tests/relayout.rs deleted file mode 100644 index 2ed73901a..000000000 --- a/packages/tui/tests/relayout.rs +++ /dev/null @@ -1,64 +0,0 @@ -use stretch::style::Dimension; -use stretch2 as stretch; - -#[test] -fn relayout() { - let mut stretch = stretch::Stretch::new(); - let node1 = stretch - .new_node( - stretch::style::Style { - size: stretch::geometry::Size { width: Dimension::Points(8f32), height: Dimension::Points(80f32) }, - ..Default::default() - }, - &[], - ) - .unwrap(); - let node0 = stretch - .new_node( - stretch::style::Style { - align_self: stretch::prelude::AlignSelf::Center, - size: stretch::geometry::Size { width: Dimension::Auto, height: Dimension::Auto }, - // size: stretch::geometry::Size { width: Dimension::Percent(1.0), height: Dimension::Percent(1.0) }, - ..Default::default() - }, - &[node1], - ) - .unwrap(); - let node = stretch - .new_node( - stretch::style::Style { - size: stretch::geometry::Size { width: Dimension::Percent(1f32), height: Dimension::Percent(1f32) }, - ..Default::default() - }, - &[node0], - ) - .unwrap(); - println!("0:"); - stretch - .compute_layout( - node, - stretch::geometry::Size { - width: stretch::prelude::Number::Defined(100f32), - height: stretch::prelude::Number::Defined(100f32), - }, - ) - .unwrap(); - let initial = stretch.layout(node).unwrap().location; - let initial0 = stretch.layout(node0).unwrap().location; - let initial1 = stretch.layout(node1).unwrap().location; - for i in 1..10 { - println!("\n\n{i}:"); - stretch - .compute_layout( - node, - stretch::geometry::Size { - width: stretch::prelude::Number::Defined(100f32), - height: stretch::prelude::Number::Defined(100f32), - }, - ) - .unwrap(); - assert_eq!(stretch.layout(node).unwrap().location, initial); - assert_eq!(stretch.layout(node0).unwrap().location, initial0); - assert_eq!(stretch.layout(node1).unwrap().location, initial1); - } -} From 3dd18b21b3201a23bd1162d6f20459dbaceabfbb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 5 Apr 2022 12:08:01 -0500 Subject: [PATCH 12/33] added headless mode for CI --- benches/tui_update.rs | 56 +++++++++++++++++++----- packages/tui/src/config.rs | 5 ++- packages/tui/src/lib.rs | 87 +++++++++++++++++++++++++------------- 3 files changed, 106 insertions(+), 42 deletions(-) diff --git a/benches/tui_update.rs b/benches/tui_update.rs index 6c77c9132..cbc8ca467 100644 --- a/benches/tui_update.rs +++ b/benches/tui_update.rs @@ -1,15 +1,13 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use dioxus::prelude::*; -use dioxus_tui::TuiContext; +use dioxus_tui::{Config, TuiContext}; -criterion_group!(mbenches, update_boxes); +criterion_group!(mbenches, tui_update); criterion_main!(mbenches); /// This benchmarks the cache performance of the TUI for small edits by changing one box at a time. -fn update_boxes(c: &mut Criterion) { +fn tui_update(c: &mut Criterion) { let mut group = c.benchmark_group("Update boxes"); - // We can override the configuration on a per-group level - group.sample_size(10); // We can also use loops to define multiple benchmarks, even over multiple dimensions. for size in 1..=6 { @@ -19,12 +17,48 @@ fn update_boxes(c: &mut Criterion) { &size, |b, size| { b.iter(|| match size { - 1 => dioxus::tui::launch(app5), - 2 => dioxus::tui::launch(app10), - 3 => dioxus::tui::launch(app15), - 4 => dioxus::tui::launch(app20), - 5 => dioxus::tui::launch(app25), - 6 => dioxus::tui::launch(app30), + 1 => dioxus::tui::launch_cfg( + app5, + Config { + headless: true, + ..Default::default() + }, + ), + 2 => dioxus::tui::launch_cfg( + app10, + Config { + headless: true, + ..Default::default() + }, + ), + 3 => dioxus::tui::launch_cfg( + app15, + Config { + headless: true, + ..Default::default() + }, + ), + 4 => dioxus::tui::launch_cfg( + app20, + Config { + headless: true, + ..Default::default() + }, + ), + 5 => dioxus::tui::launch_cfg( + app25, + Config { + headless: true, + ..Default::default() + }, + ), + 6 => dioxus::tui::launch_cfg( + app30, + Config { + headless: true, + ..Default::default() + }, + ), _ => (), }) }, diff --git a/packages/tui/src/config.rs b/packages/tui/src/config.rs index 924163a7d..6e839d8a7 100644 --- a/packages/tui/src/config.rs +++ b/packages/tui/src/config.rs @@ -1,9 +1,11 @@ #[derive(Clone, Copy)] pub struct Config { pub rendering_mode: RenderingMode, - /// Should the terminal quit when the user presses `ctrl+c`? + /// Controls if the terminal quit when the user presses `ctrl+c`? /// To handle quiting on your own, use the [crate::TuiContext] root context. pub ctrl_c_quit: bool, + /// Controls if the terminal should dislay anything, usefull for testing. + pub headless: bool, } impl Default for Config { @@ -11,6 +13,7 @@ impl Default for Config { Self { rendering_mode: Default::default(), ctrl_c_quit: true, + headless: false, } } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 0d8687b09..03e137111 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -15,7 +15,7 @@ use layout::StretchLayout; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; -use tui::{backend::CrosstermBackend, Terminal}; +use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; mod hooks; @@ -51,18 +51,20 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { // Setup input handling let (event_tx, event_rx) = unbounded(); let event_tx_clone = event_tx.clone(); - std::thread::spawn(move || { - let tick_rate = Duration::from_millis(1000); - loop { - if crossterm::event::poll(tick_rate).unwrap() { - // if crossterm::event::poll(timeout).unwrap() { - let evt = crossterm::event::read().unwrap(); - if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { - break; + if !cfg.headless { + std::thread::spawn(move || { + let tick_rate = Duration::from_millis(1000); + loop { + if crossterm::event::poll(tick_rate).unwrap() { + // if crossterm::event::poll(timeout).unwrap() { + let evt = crossterm::event::read().unwrap(); + if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { + break; + } } } - } - }); + }); + } let cx = dom.base_scope(); cx.provide_root_context(state); @@ -101,13 +103,17 @@ fn render_vdom( .enable_all() .build()? .block_on(async { - enable_raw_mode().unwrap(); - let mut stdout = std::io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend).unwrap(); + let mut terminal = (!cfg.headless).then(|| { + enable_raw_mode().unwrap(); + let mut stdout = std::io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); + let backend = CrosstermBackend::new(io::stdout()); + Terminal::new(backend).unwrap() + }); + if let Some(terminal) = &mut terminal { + terminal.clear().unwrap(); + } - terminal.clear().unwrap(); let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; @@ -125,9 +131,11 @@ fn render_vdom( if !to_rerender.is_empty() || resized { resized = false; - terminal.draw(|frame| { - // size is guaranteed to not change when rendering - let dims = frame.size(); + fn resize( + dims: Rect, + stretch: &mut Stretch, + rdom: &RealDom, + ) { let width = dims.width; let height = dims.height; let root_id = rdom.root_id(); @@ -142,9 +150,26 @@ fn render_vdom( }, ) .unwrap(); - let root = &rdom[rdom.root_id()]; - render::render_vnode(frame, &stretch, &rdom, &root, cfg); - })?; + } + if let Some(terminal) = &mut terminal { + terminal.draw(|frame| { + // size is guaranteed to not change when rendering + resize(frame.size(), &mut stretch, &rdom); + let root = &rdom[rdom.root_id()]; + render::render_vnode(frame, &stretch, &rdom, &root, cfg); + })?; + } else { + resize( + Rect { + x: 0, + y: 0, + width: 300, + height: 300, + }, + &mut stretch, + &rdom, + ); + } } use futures::future::{select, Either}; @@ -198,13 +223,15 @@ fn render_vdom( } } - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; + if let Some(terminal) = &mut terminal { + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + } Ok(()) }) From 92f48169e986ff0c239ad09299902c9440dc89f6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 8 Apr 2022 21:17:37 -0500 Subject: [PATCH 13/33] WIP new api --- Cargo.toml | 3 +- packages/native-core-macro/Cargo.toml | 12 + packages/native-core-macro/src/lib.rs | 285 +++++++++++++++++++ packages/native-core/Cargo.toml | 2 + packages/native-core/src/lib.rs | 1 + packages/native-core/src/real_dom_new_api.rs | 58 ++++ packages/native-core/tests/parse.rs | 62 ++++ 7 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 packages/native-core-macro/Cargo.toml create mode 100644 packages/native-core-macro/src/lib.rs create mode 100644 packages/native-core/src/real_dom_new_api.rs create mode 100644 packages/native-core/tests/parse.rs diff --git a/Cargo.toml b/Cargo.toml index 95900a1d2..a1e16bada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", 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-rsx = { path = "./packages/rsx", optional = true } @@ -47,7 +48,7 @@ ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] -native-core = ["dioxus-native-core"] +native-core = ["dioxus-native-core", "dioxus-native-core-macro"] [workspace] diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml new file mode 100644 index 000000000..d71b03246 --- /dev/null +++ b/packages/native-core-macro/Cargo.toml @@ -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" diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs new file mode 100644 index 000000000..883c12b2e --- /dev/null +++ b/packages/native-core-macro/src/lib.rs @@ -0,0 +1,285 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, + parse::{Parse, ParseStream}, + Field, Ident, Token, Type, +}; + +#[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.type_id()); + let child_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| f.type_id()); + let parent_types = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| f.type_id()); + + let type_name_str = type_name.to_string(); + + let gen = quote! { + impl State for #type_name{ + fn update_node_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::NodeDepState; + if false {} + #node_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_parent_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, parent: &Self, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ParentDepState; + if false {} + #parent_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn update_child_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, children: Vec<&Self>, ctx: &anymap::AnyMap){ + use dioxus_native_core::real_dom_new_api::ChildDepState; + if false {} + #child_dep_state_fields + else{ + panic!("{:?} not in {}", ty, #type_name_str); + } + } + + fn child_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#child_types,)* + ] + } + + fn parent_dep_types(&self) -> Vec{ + // todo: order should depend on order of dependencies + vec![ + #(#parent_types,)* + ] + } + + fn node_dep_types(&self) -> Vec{ + vec![ + #(#node_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(); + Self { state_members } + } +} + +struct DepTypes { + ctx_ty: Option, + dep_ty: Option, +} + +impl Parse for DepTypes { + fn parse(input: ParseStream) -> Result { + let ctx_ty = input.parse::().ok(); + let comma = input.parse::().ok(); + let dep_ty = input.parse::().ok(); + let dep_ty = comma.and(dep_ty); + Ok(DepTypes { ctx_ty, dep_ty }) + } +} + +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 = 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 { + let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; + quote! {ctx.get().expect(#msg)} + } else { + quote! {&()} + }; + if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &parent.#dep_ident, #get_ctx);) + } + } + } else { + match self.dep_kind { + DepKind::NodeDepState => { + quote!(self.#ident.reduce(node, #get_ctx);) + } + DepKind::ChildDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + DepKind::ParentDepState => { + quote!(self.#ident.reduce(node, &(), #get_ctx);) + } + } + } + } + + fn type_id(&self) -> quote::__private::TokenStream { + let ty = &self.mem.ty; + quote!(std::any::TypeId::of::<#ty>()) + } +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 83e403487..3e37d7e8a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,10 +10,12 @@ homepage = "https://dioxuslabs.com" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", 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" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" [dev-dependencies] rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 26c629446..0ad573834 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,2 +1,3 @@ pub mod layout_attributes; pub mod real_dom; +pub mod real_dom_new_api; diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs new file mode 100644 index 000000000..7740a97d4 --- /dev/null +++ b/packages/native-core/src/real_dom_new_api.rs @@ -0,0 +1,58 @@ +use std::any::TypeId; + +use anymap::AnyMap; +use dioxus_core::{Attribute, VElement}; + +#[repr(transparent)] +pub struct NodeRef<'a>(&'a VElement<'a>); +impl<'a> NodeRef<'a> { + pub fn new(velement: &'a VElement<'a>) -> Self { + Self(velement) + } + + pub fn tag(&self) -> &'a str { + self.0.tag + } + + pub fn namespace(&self) -> Option<&'a str> { + self.0.namespace + } + + pub fn attributes(&self) -> &'a [Attribute<'a>] { + self.0.attributes + } +} + +pub trait ChildDepState: PartialEq { + type Ctx; + type DepState: ChildDepState; + fn reduce(&mut self, node: NodeRef, children: Vec<&Self::DepState>, ctx: &Self::Ctx); +} + +pub trait ParentDepState: PartialEq { + type Ctx; + type DepState: ParentDepState; + fn reduce(&mut self, node: NodeRef, parent: &Self::DepState, ctx: &Self::Ctx); +} + +pub trait NodeDepState: PartialEq { + type Ctx; + fn reduce(&mut self, node: NodeRef, ctx: &Self::Ctx); +} + +pub trait State { + fn update_node_dep_state(&mut self, ty: TypeId, node: NodeRef, ctx: &AnyMap); + fn child_dep_types(&self) -> Vec; + + fn update_parent_dep_state(&mut self, ty: TypeId, node: NodeRef, parent: &Self, ctx: &AnyMap); + fn parent_dep_types(&self) -> Vec; + + fn update_child_dep_state( + &mut self, + ty: TypeId, + node: NodeRef, + children: Vec<&Self>, + ctx: &AnyMap, + ); + fn node_dep_types(&self) -> Vec; +} diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs new file mode 100644 index 000000000..c22e897df --- /dev/null +++ b/packages/native-core/tests/parse.rs @@ -0,0 +1,62 @@ +use dioxus_native_core::real_dom_new_api::*; +use dioxus_native_core_macro::*; + +#[derive(State)] +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(i32, B)] + y: B, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(u8, C)] + z: C, +} + +// struct Z { +// x: A, +// y: B, +// z: C, +// } + +use dioxus_native_core::real_dom_new_api::NodeDepState; + +#[derive(PartialEq)] +struct A; +impl NodeDepState for A { + type Ctx = (); + fn reduce(&mut self, _: NodeRef, _: &()) { + todo!() + } +} + +#[derive(PartialEq)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: Vec<&Self::DepState>, + _: &i32, + ) { + todo!() + } +} + +#[derive(PartialEq)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce( + &mut self, + _: dioxus_native_core::real_dom_new_api::NodeRef, + _: &Self::DepState, + _: &u8, + ) { + todo!() + } +} From 9e9fc09e6dd77abc79ff54afdec57f9d78e042fc Mon Sep 17 00:00:00 2001 From: demonthos Date: Sat, 9 Apr 2022 16:40:26 -0500 Subject: [PATCH 14/33] WIP macro progress --- packages/native-core-macro/src/lib.rs | 80 ++++++----- packages/native-core/src/real_dom_new_api.rs | 139 +++++++++++++++---- packages/native-core/tests/parse.rs | 10 +- 3 files changed, 165 insertions(+), 64 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 883c12b2e..740c214ba 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -36,6 +36,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { }; 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 @@ -86,67 +87,77 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { .state_members .iter() .filter(|f| f.dep_kind == DepKind::NodeDepState) - .map(|f| f.type_id()); + .map(|f| &f.mem.ty); let child_types = state_strct .state_members .iter() .filter(|f| f.dep_kind == DepKind::ChildDepState) - .map(|f| f.type_id()); + .map(|f| &f.mem.ty); let parent_types = state_strct .state_members .iter() .filter(|f| f.dep_kind == DepKind::ParentDepState) - .map(|f| f.type_id()); + .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(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, ctx: &anymap::AnyMap){ + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, ctx: &anymap::AnyMap) -> bool{ use dioxus_native_core::real_dom_new_api::NodeDepState; - if false {} + if false { + unreachable!(); + } #node_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn update_parent_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, parent: &Self, ctx: &anymap::AnyMap){ + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, parent: &Self, ctx: &anymap::AnyMap) -> bool{ use dioxus_native_core::real_dom_new_api::ParentDepState; - if false {} + if false { + unreachable!(); + } #parent_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn update_child_dep_state(&mut self, ty: std::any::TypeId, node: dioxus_native_core::real_dom_new_api::NodeRef, children: Vec<&Self>, ctx: &anymap::AnyMap){ + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: &'a dioxus_core::VElement<'a>, children: Vec<&Self>, ctx: &anymap::AnyMap) -> bool{ use dioxus_native_core::real_dom_new_api::ChildDepState; - if false {} + if false { + unreachable!() + } #child_dep_state_fields else{ - panic!("{:?} not in {}", ty, #type_name_str); + panic!("{:?} not in {}", ty, #type_name_str) } } - fn child_dep_types(&self) -> Vec{ - // todo: order should depend on order of dependencies - vec![ - #(#child_types,)* - ] + fn child_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::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) -> Vec{ - // todo: order should depend on order of dependencies - vec![ - #(#parent_types,)* - ] + fn parent_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::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) -> Vec{ - vec![ - #(#node_types,)* - ] + fn node_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::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 } } }; @@ -176,6 +187,9 @@ impl<'a> StateStruct<'a> { .zip(fields.iter()) .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) .collect(); + + // todo: sort members + Self { state_members } } } @@ -251,28 +265,30 @@ impl<'a> StateMember<'a> { } 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, #get_ctx);) + quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(node, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(node, &parent.#dep_ident, #get_ctx);) + quote!(self.#ident.reduce(#node_view, &parent.#dep_ident, #get_ctx)) } } } else { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(node, #get_ctx);) + quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(node, &(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(node, &(), #get_ctx);) + quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 7740a97d4..269364c81 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -3,56 +3,141 @@ use std::any::TypeId; use anymap::AnyMap; use dioxus_core::{Attribute, VElement}; -#[repr(transparent)] -pub struct NodeRef<'a>(&'a VElement<'a>); -impl<'a> NodeRef<'a> { - pub fn new(velement: &'a VElement<'a>) -> Self { - Self(velement) +pub struct NodeView<'a> { + inner: &'a VElement<'a>, + view: NodeMask, +} +impl<'a> NodeView<'a> { + pub fn new(velement: &'a VElement<'a>, view: NodeMask) -> Self { + Self { + inner: velement, + view: view, + } } - pub fn tag(&self) -> &'a str { - self.0.tag + pub fn tag(&self) -> Option<&'a str> { + if self.view.tag { + Some(self.inner.tag) + } else { + None + } } pub fn namespace(&self) -> Option<&'a str> { - self.0.namespace + if self.view.namespace { + self.inner.namespace + } else { + None + } } - pub fn attributes(&self) -> &'a [Attribute<'a>] { - self.0.attributes + pub fn attributes(&self) -> impl Iterator> { + self.inner + .attributes + .iter() + .filter(|a| self.view.attritutes.contains(&a.name)) } } -pub trait ChildDepState: PartialEq { +#[derive(Default)] +pub struct NodeMask { + // must be sorted + attritutes: &'static [&'static str], + tag: bool, + namespace: bool, +} + +impl NodeMask { + /// attritutes must be sorted! + pub const fn new(attritutes: &'static [&'static str], tag: bool, namespace: bool) -> Self { + Self { + attritutes, + tag, + namespace, + } + } + + pub fn verify(&self) { + debug_assert!( + self.attritutes.windows(2).all(|w| w[0] < w[1]), + "attritutes must be increasing" + ); + } + + pub fn overlaps(&self, other: &Self) -> bool { + (self.tag && other.tag) + || (self.namespace && other.namespace) + || self.attritutes_overlap(other) + } + + fn attritutes_overlap(&self, other: &Self) -> bool { + let mut self_attrs = self.attritutes.iter(); + let mut other_attrs = other.attritutes.iter(); + if let Some(mut other_attr) = other_attrs.next() { + while let Some(self_attr) = self_attrs.next() { + while other_attr < self_attr { + if let Some(attr) = other_attrs.next() { + other_attr = attr; + } else { + return false; + } + } + if other_attr == self_attr { + return true; + } + } + } + false + } +} + +pub trait ChildDepState { type Ctx; type DepState: ChildDepState; - fn reduce(&mut self, node: NodeRef, children: Vec<&Self::DepState>, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; } -pub trait ParentDepState: PartialEq { +pub trait ParentDepState { type Ctx; type DepState: ParentDepState; - fn reduce(&mut self, node: NodeRef, parent: &Self::DepState, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, parent: &Self::DepState, ctx: &Self::Ctx) -> bool; } -pub trait NodeDepState: PartialEq { +pub trait NodeDepState { type Ctx; - fn reduce(&mut self, node: NodeRef, ctx: &Self::Ctx); + const NODE_MASK: NodeMask = NodeMask::new(&[], false, false); + fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } pub trait State { - fn update_node_dep_state(&mut self, ty: TypeId, node: NodeRef, ctx: &AnyMap); - fn child_dep_types(&self) -> Vec; - - fn update_parent_dep_state(&mut self, ty: TypeId, node: NodeRef, parent: &Self, ctx: &AnyMap); - fn parent_dep_types(&self) -> Vec; - - fn update_child_dep_state( - &mut self, + fn update_node_dep_state<'a>( + &'a mut self, ty: TypeId, - node: NodeRef, + node: &'a VElement<'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; + + fn update_parent_dep_state<'a>( + &'a mut self, + ty: TypeId, + node: &'a VElement<'a>, + parent: &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; + + fn update_child_dep_state<'a>( + &'a mut self, + ty: TypeId, + node: &'a VElement<'a>, children: Vec<&Self>, ctx: &AnyMap, - ); - fn node_dep_types(&self) -> Vec; + ) -> 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; } diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index c22e897df..8f680bf88 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -26,7 +26,7 @@ use dioxus_native_core::real_dom_new_api::NodeDepState; struct A; impl NodeDepState for A { type Ctx = (); - fn reduce(&mut self, _: NodeRef, _: &()) { + fn reduce(&mut self, _: NodeView, _: &()) -> bool { todo!() } } @@ -38,10 +38,10 @@ impl ChildDepState for B { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeRef, + _: dioxus_native_core::real_dom_new_api::NodeView, _: Vec<&Self::DepState>, _: &i32, - ) { + ) -> bool { todo!() } } @@ -53,10 +53,10 @@ impl ParentDepState for C { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeRef, + _: dioxus_native_core::real_dom_new_api::NodeView, _: &Self::DepState, _: &u8, - ) { + ) -> bool { todo!() } } From 13a99bce9c831db272a5190d2b889893e0db7e48 Mon Sep 17 00:00:00 2001 From: demonthos Date: Sun, 10 Apr 2022 07:00:44 -0500 Subject: [PATCH 15/33] WIP: intigrate exsisting code --- packages/native-core/src/real_dom.rs | 95 ++++++++------------ packages/native-core/src/real_dom_new_api.rs | 2 +- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 239c90aea..f40aaf474 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,3 +1,4 @@ +use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ collections::VecDeque, @@ -6,25 +7,27 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +use crate::real_dom_new_api::{State, NodeMask}; + /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. /// To get started implement [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)] -pub struct RealDom { +pub struct RealDom { root: usize, - nodes: Vec>>, + nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } -impl Default for RealDom { +impl Default for RealDom { fn default() -> Self { Self::new() } } -impl RealDom { - pub fn new() -> RealDom { +impl RealDom { + pub fn new() -> RealDom { RealDom { root: 0, nodes: { @@ -191,8 +194,7 @@ impl RealDom { &mut self, vdom: &VirtualDom, nodes_updated: Vec, - us_ctx: &mut US::Ctx, - ds_ctx: &mut DS::Ctx, + ctx: AnyMap, ) -> Option> { let mut to_rerender = FxHashSet::default(); to_rerender.extend(nodes_updated.iter()); @@ -210,24 +212,13 @@ impl RealDom { let node = &mut self[id as usize]; let vnode = node.element(vdom); let node_type = &node.node_type; - let up_state = &mut node.up_state; - let children = match node_type { - NodeType::Element { children, .. } => Some(children), - _ => None, - }; + if let + NodeType::Element { children, tag, namespace } =node.node_type{ + + } + let mask = NodeMask::new(attritutes, tag, namespace) // todo: reduce cloning state - let old = up_state.clone(); - let mut new = up_state.clone(); - let parent = node.parent; - new.reduce( - children - .unwrap_or(&Vec::new()) - .clone() - .iter() - .map(|c| &self[*c].up_state), - vnode, - us_ctx, - ); + for id in node.state.child_dep_types(mask) {} if new != old { to_rerender.insert(id); if let Some(p) = parent { @@ -300,12 +291,9 @@ impl RealDom { } // remove a node and it's children from the dom. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. - fn inner( - dom: &mut RealDom, - id: usize, - ) -> Option> { + fn inner(dom: &mut RealDom, id: usize) -> Option> { let mut node = dom.nodes[id as usize].take()?; if let NodeType::Element { children, .. } = &mut node.node_type { for c in children { @@ -327,7 +315,7 @@ impl RealDom { Some(node) } - fn insert(&mut self, node: Node) { + fn insert(&mut self, node: Node) { let current_len = self.nodes.len(); let id = node.id.0; if current_len - 1 < node.id.0 { @@ -337,15 +325,15 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&Node> { + pub fn get(&self, id: usize) -> Option<&Node> { self.nodes.get(id)?.as_ref() } - pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { + pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { self.nodes.get_mut(id)?.as_mut() } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); @@ -416,12 +404,8 @@ impl RealDom { } /// Call a function for each node in the dom, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { - fn inner( - dom: &RealDom, - id: ElementId, - f: &mut impl FnMut(&Node), - ) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { + fn inner(dom: &RealDom, id: ElementId, f: &mut impl FnMut(&Node)) { let node = &dom[id]; f(node); if let NodeType::Element { children, .. } = &node.node_type { @@ -438,12 +422,8 @@ impl RealDom { } /// 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)) { - fn inner( - dom: &mut RealDom, - id: ElementId, - f: &mut impl FnMut(&mut Node), - ) { + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { + fn inner(dom: &mut RealDom, id: ElementId, f: &mut impl FnMut(&mut Node)) { let node = &mut dom[id]; f(node); if let NodeType::Element { children, .. } = &mut node.node_type { @@ -461,28 +441,28 @@ impl RealDom { } } -impl Index for RealDom { - type Output = Node; +impl Index for RealDom { + type Output = Node; fn index(&self, idx: usize) -> &Self::Output { self.get(idx).expect("Node does not exist") } } -impl Index for RealDom { - type Output = Node; +impl Index for RealDom { + type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { &self[idx.0] } } -impl IndexMut for RealDom { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { self.get_mut(idx).expect("Node does not exist") } } -impl IndexMut for RealDom { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { &mut self[idx.0] } @@ -490,15 +470,13 @@ impl IndexMut for RealDom { +pub struct Node { /// The id of the node this node was created from. pub id: ElementId, /// The parent id of the node. pub parent: Option, - /// State of the node that is bubbled up to its parents. The state must depend only on the node and its children. - pub up_state: US, - /// 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, + /// State of the node. + pub state: S, /// Additional inforation specific to the node type pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. @@ -518,14 +496,13 @@ pub enum NodeType { Placeholder, } -impl Node { +impl Node { fn new(id: u64, node_type: NodeType) -> Self { Node { id: ElementId(id as usize), parent: None, node_type, - down_state: DS::default(), - up_state: US::default(), + state: S::default(), height: 0, } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 269364c81..9cc5d0550 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -111,7 +111,7 @@ pub trait NodeDepState { fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } -pub trait State { +pub trait State: Default { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, From 92010b0babbb59b99e683929d317200ecd1e6a40 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 07:51:57 -0500 Subject: [PATCH 16/33] WIP update tests --- packages/native-core-macro/src/lib.rs | 84 ++++-- packages/native-core/src/lib.rs | 1 + packages/native-core/src/real_dom.rs | 274 +++++++++++++------ packages/native-core/src/real_dom_new_api.rs | 213 +++++++++++--- packages/native-core/tests/change_nodes.rs | 9 +- packages/native-core/tests/initial_build.rs | 9 +- packages/native-core/tests/parse.rs | 14 +- packages/native-core/tests/state.rs | 229 ++++++++++------ packages/tui/Cargo.toml | 1 + packages/tui/src/lib.rs | 7 +- 10 files changed, 597 insertions(+), 244 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 740c214ba..5ec2fceba 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -5,7 +5,9 @@ use quote::{quote, ToTokens}; use syn::{ self, parse::{Parse, ParseStream}, - Field, Ident, Token, Type, + punctuated::Punctuated, + token::Paren, + Field, Ident, Token, Type, TypeTuple, }; #[derive(PartialEq)] @@ -103,8 +105,9 @@ 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::VElement<'a>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::NodeDepState; + fn update_node_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::NodeDepState as _; + // println!("called update_node_dep_state with ty: {:?}", ty); if false { unreachable!(); } @@ -114,8 +117,9 @@ 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::VElement<'a>, parent: &Self, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ParentDepState; + fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::ParentDepState as _; + // println!("called update_parent_dep_state with ty: {:?}", ty); if false { unreachable!(); } @@ -125,8 +129,9 @@ 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::VElement<'a>, children: Vec<&Self>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ChildDepState; + fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ + use dioxus_native_core::real_dom_new_api::ChildDepState as _; + // println!("called update_child_dep_state with ty: {:?}", ty); if false { unreachable!() } @@ -200,12 +205,34 @@ struct DepTypes { } impl Parse for DepTypes { - fn parse(input: ParseStream) -> Result { - let ctx_ty = input.parse::().ok(); - let comma = input.parse::().ok(); - let dep_ty = input.parse::().ok(); - let dep_ty = comma.and(dep_ty); - Ok(DepTypes { ctx_ty, dep_ty }) + 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, + } } } @@ -243,7 +270,10 @@ impl<'a> StateMember<'a> { _ => None, }) .flatten()?; - let deps: DepTypes = a.parse_args().ok()?; + let deps: DepTypes = match dep_kind { + DepKind::NodeDepState => a.parse_args::().ok()?.into(), + _ => a.parse_args().ok()?, + }; Some(Self { mem, @@ -260,8 +290,19 @@ impl<'a> StateMember<'a> { fn reduce_self(&self) -> quote::__private::TokenStream { let ident = &self.mem.ident; let get_ctx = if let Some(ctx_ty) = &self.ctx_ty { - let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; - quote! {ctx.get().expect(#msg)} + 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! {&()} }; @@ -276,7 +317,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, &parent.#dep_ident, #get_ctx)) + quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) } } } else { @@ -288,7 +329,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) + quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx)) } } } @@ -296,6 +337,11 @@ impl<'a> StateMember<'a> { fn type_id(&self) -> quote::__private::TokenStream { let ty = &self.mem.ty; - quote!(std::any::TypeId::of::<#ty>()) + // quote!(std::any::TypeId::of::<#ty>()) + quote!({ + let type_id = std::any::TypeId::of::<#ty>(); + // println!("{:?}", type_id); + type_id + }) } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 0ad573834..580380f68 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,3 +1,4 @@ pub mod layout_attributes; pub mod real_dom; pub mod real_dom_new_api; +pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f40aaf474..55ae7e0a8 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,13 +1,14 @@ use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ + any::TypeId, collections::VecDeque, ops::{Index, IndexMut}, }; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::real_dom_new_api::{State, NodeMask}; +use crate::real_dom_new_api::{AttributeMask, NodeMask, State}; /// 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. @@ -47,7 +48,7 @@ impl RealDom { } /// 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) -> Vec { + pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec<(usize, NodeMask)> { let mut nodes_updated = Vec::new(); for mutations in mutations_vec { for e in mutations.edits { @@ -69,7 +70,7 @@ impl RealDom { .collect(); for ns in drained { self.link_child(ns, target).unwrap(); - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); } } ReplaceWith { root, m } => { @@ -77,7 +78,7 @@ impl RealDom { let target = root.parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -85,7 +86,7 @@ impl RealDom { let target = self[root as usize].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } @@ -93,13 +94,13 @@ impl RealDom { let target = self[root as usize].parent.unwrap().0; let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect(); for ns in drained { - nodes_updated.push(ns); + nodes_updated.push((ns, NodeMask::ALL)); self.link_child(ns, target).unwrap(); } } Remove { root } => { 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(); } @@ -165,7 +166,7 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push(root as usize); + nodes_updated.push((root as usize, NodeMask::NONE)); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -173,11 +174,19 @@ impl RealDom { _ => unreachable!(), } } - SetAttribute { root, .. } => { - nodes_updated.push(root as usize); + SetAttribute { root, field, .. } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false), + )); } - RemoveAttribute { root, .. } => { - nodes_updated.push(root as usize); + RemoveAttribute { + root, name: field, .. + } => { + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::single(field), false, false), + )); } PopRoot {} => { self.node_stack.pop(); @@ -193,77 +202,148 @@ impl RealDom { pub fn update_state( &mut self, vdom: &VirtualDom, - nodes_updated: Vec, + nodes_updated: Vec<(usize, NodeMask)>, ctx: AnyMap, ) -> Option> { + #[derive(PartialEq, Clone, Debug)] + enum StatesToCheck { + All, + Some(Vec), + } + 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 .into_iter() - .map(|id| (id, self[id].height)) + .map(|(id, mask)| (id, self[id].height, mask, StatesToCheck::All)) .collect(); // Sort nodes first by height, then if the height is the same id. 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 current_key = None; + for i in 0..nodes_updated.len() { + let current = nodes_updated; + } + } + println!("{:?}", nodes_updated); + + // update the state that only depends on nodes. The order does not matter. + for (id, _height, mask, to_check) in &nodes_updated { + let mut changed = false; + let node = &mut self[*id as usize]; + let ids = match to_check { + StatesToCheck::All => node.state.node_dep_types(&mask), + StatesToCheck::Some(_) => unreachable!(), + }; + for ty in ids { + let node = &mut self[*id as usize]; + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + changed |= node.state.update_node_dep_state(ty, el, &ctx); + } + if changed { + to_rerender.insert(*id); + } + } // 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(); - while let Some((id, height)) = to_bubble.pop_back() { - let node = &mut self[id as usize]; - let vnode = node.element(vdom); - let node_type = &node.node_type; - if let - NodeType::Element { children, tag, namespace } =node.node_type{ - + while let Some((id, height, mask, to_check)) = to_bubble.pop_back() { + 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(&mask), + StatesToCheck::Some(ids) => ids, + }; + let mut changed = Vec::new(); + for ty in ids { + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + if node + .state + .update_child_dep_state(ty, el, &children_state, &ctx) + { + changed.push(ty); } - let mask = NodeMask::new(attritutes, tag, namespace) - // todo: reduce cloning state - for id in node.state.child_dep_types(mask) {} - if new != old { - to_rerender.insert(id); - if let Some(p) = parent { - let i = to_bubble.partition_point(|(other_id, h)| { - *h < height - 1 || (*h == height - 1 && *other_id < p.0) + } + if let Some(parent_id) = node.parent { + if !changed.is_empty() { + to_rerender.insert(id); + let i = to_bubble.partition_point(|(other_id, h, ..)| { + *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0) }); // make sure the parent is not already queued - if i >= to_bubble.len() || to_bubble[i].0 != p.0 { - to_bubble.insert(i, (p.0, height - 1)); + if i >= to_bubble.len() || to_bubble[i].0 != parent_id.0 { + to_bubble.insert( + i, + ( + parent_id.0, + height - 1, + NodeMask::NONE, + 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. let mut to_push: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_push.pop_front() { - let node = &self[id as usize]; - // todo: reduce cloning state - let old = node.down_state.clone(); - let mut new = node.down_state.clone(); - let vnode = node.element(vdom); - new.reduce( - node.parent - .filter(|e| e.0 != 0) - .map(|e| &self[e].down_state), - vnode, - ds_ctx, - ); - if new != old { - to_rerender.insert(id); - let node = &mut self[id as usize]; + while let Some((id, height, mask, to_check)) = to_push.pop_front() { + let node = &self[id]; + let ids = match to_check { + StatesToCheck::All => node.state.parent_dep_types(&mask), + StatesToCheck::Some(ids) => ids, + }; + let mut changed = Vec::new(); + let (node, parent) = self.get_node_parent_mut(id).unwrap(); + for ty in ids { + let el = if let &VNode::Element(e) = node.element(vdom) { + Some(e) + } else { + None + }; + let parent = parent.as_deref(); + let state = &mut node.state; + if state.update_parent_dep_state( + ty, + el, + 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 { for c in children { - let i = to_push.partition_point(|(other_id, h)| { + let i = to_push.partition_point(|(other_id, h, ..)| { *h < height + 1 || (*h == height + 1 && *other_id < c.0) }); if i >= to_push.len() || to_push[i].0 != c.0 { - to_push.insert(i, (c.0, height + 1)); + to_push.insert( + i, + ( + c.0, + height + 1, + NodeMask::NONE, + StatesToCheck::Some(changed.clone()), + ), + ); } } } - node.down_state = new; } } @@ -333,6 +413,56 @@ impl RealDom { self.nodes.get_mut(id)?.as_mut() } + // 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, Vec<&'a mut Node>)> { + 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, Option<&'a mut Node>)> { + 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> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); @@ -528,39 +658,3 @@ impl Node { self.parent = Some(parent); } } - -/// This state that is passed down to children. For example text properties (`` `` ``) 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, - Self: 'a; -} -impl BubbledUpState for () { - type Ctx = (); - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - } -} diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/real_dom_new_api.rs index 9cc5d0550..042e625c1 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/real_dom_new_api.rs @@ -3,12 +3,13 @@ use std::any::TypeId; use anymap::AnyMap; use dioxus_core::{Attribute, VElement}; +#[derive(Debug)] pub struct NodeView<'a> { - inner: &'a VElement<'a>, + inner: Option<&'a VElement<'a>>, view: NodeMask, } impl<'a> NodeView<'a> { - pub fn new(velement: &'a VElement<'a>, view: NodeMask) -> Self { + pub fn new(velement: Option<&'a VElement<'a>>, view: NodeMask) -> Self { Self { inner: velement, view: view, @@ -17,7 +18,7 @@ impl<'a> NodeView<'a> { pub fn tag(&self) -> Option<&'a str> { if self.view.tag { - Some(self.inner.tag) + self.inner.map(|el| el.tag) } else { None } @@ -25,7 +26,7 @@ impl<'a> NodeView<'a> { pub fn namespace(&self) -> Option<&'a str> { if self.view.namespace { - self.inner.namespace + self.inner.map(|el| el.namespace).flatten() } else { None } @@ -33,23 +34,159 @@ impl<'a> NodeView<'a> { pub fn attributes(&self) -> impl Iterator> { self.inner - .attributes + .map(|el| el.attributes) + .unwrap_or_default() .iter() - .filter(|a| self.view.attritutes.contains(&a.name)) + .filter(|a| self.view.attritutes.contains_attribute(&a.name)) } } -#[derive(Default)] +#[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.contains(&attr), + AttributeMask::Static(l) => l.contains(&attr), + } + } + + 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 { + pub fn union_iter( + s_iter: impl Iterator, + o_iter: impl Iterator, + ) -> Vec<&'static str> { + let mut s_peekable = s_iter.peekable(); + let mut o_peekable = o_iter.peekable(); + let mut v = Vec::new(); + while let Some(s_i) = s_peekable.peek() { + loop { + if let Some(o_i) = o_peekable.peek() { + if o_i > s_i { + break; + } else { + 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); + } + v + } + + let new = match (self, other) { + (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + (AttributeMask::Static(s), AttributeMask::Static(o)) => { + AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) + } + _ => AttributeMask::All, + }; + new.verify(); + new + } + + fn overlaps(&self, other: &Self) -> bool { + fn overlaps_iter( + mut self_iter: impl Iterator, + mut other_iter: impl Iterator, + ) -> 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: &'static [&'static str], + attritutes: AttributeMask, tag: bool, namespace: bool, } impl NodeMask { + pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false); + pub const ALL: Self = Self::new(AttributeMask::All, true, true); + /// attritutes must be sorted! - pub const fn new(attritutes: &'static [&'static str], tag: bool, namespace: bool) -> Self { + pub const fn new(attritutes: AttributeMask, tag: bool, namespace: bool) -> Self { Self { attritutes, tag, @@ -57,13 +194,6 @@ impl NodeMask { } } - pub fn verify(&self) { - debug_assert!( - self.attritutes.windows(2).all(|w| w[0] < w[1]), - "attritutes must be increasing" - ); - } - pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) || (self.namespace && other.namespace) @@ -71,51 +201,48 @@ impl NodeMask { } fn attritutes_overlap(&self, other: &Self) -> bool { - let mut self_attrs = self.attritutes.iter(); - let mut other_attrs = other.attritutes.iter(); - if let Some(mut other_attr) = other_attrs.next() { - while let Some(self_attr) = self_attrs.next() { - while other_attr < self_attr { - if let Some(attr) = other_attrs.next() { - other_attr = attr; - } else { - return false; - } - } - if other_attr == self_attr { - return true; - } - } - } - false + self.attritutes.overlaps(&other.attritutes) } } +/// 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::new(&[], false, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; } +/// This state that is passed down to children. For example text properties (`` `` ``) 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::new(&[], false, false); - fn reduce(&mut self, node: NodeView, parent: &Self::DepState, ctx: &Self::Ctx) -> bool; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + 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::new(&[], false, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } -pub trait State: Default { +pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, + node: Option<&'a VElement<'a>>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -124,8 +251,8 @@ pub trait State: Default { fn update_parent_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, - parent: &Self, + node: Option<&'a VElement<'a>>, + parent: Option<&Self>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -134,8 +261,8 @@ pub trait State: Default { fn update_child_dep_state<'a>( &'a mut self, ty: TypeId, - node: &'a VElement<'a>, - children: Vec<&Self>, + node: Option<&'a VElement<'a>>, + children: &[&Self], ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index 6afb225d3..bf3de4368 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -3,8 +3,13 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core_macro::State; use std::cell::Cell; +#[derive(State, Default, Clone)] +struct Empty {} + #[test] fn remove_node() { #[allow(non_snake_case)] @@ -20,7 +25,7 @@ fn remove_node() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let child_div = VElement { @@ -92,7 +97,7 @@ fn add_node() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index df054dcdc..321820b7b 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -5,6 +5,11 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core_macro::State; + +#[derive(State, Default, Clone)] +struct Empty {} #[test] fn initial_build_simple() { @@ -21,7 +26,7 @@ fn initial_build_simple() { div{} }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let root_div = VElement { @@ -60,7 +65,7 @@ fn initial_build_with_children() { } }); - let mut dom: RealDom<(), ()> = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); let first_text = VText { diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index 8f680bf88..a3cf69ecf 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -1,16 +1,16 @@ use dioxus_native_core::real_dom_new_api::*; use dioxus_native_core_macro::*; -#[derive(State)] +#[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(i32, B)] + #[child_dep_state(B, i32)] y: B, // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(u8, C)] + #[parent_dep_state(C, u8)] z: C, } @@ -22,7 +22,7 @@ struct Z { use dioxus_native_core::real_dom_new_api::NodeDepState; -#[derive(PartialEq)] +#[derive(Default, Clone)] struct A; impl NodeDepState for A { type Ctx = (); @@ -31,7 +31,7 @@ impl NodeDepState for A { } } -#[derive(PartialEq)] +#[derive(Default, Clone)] struct B; impl ChildDepState for B { type Ctx = i32; @@ -46,7 +46,7 @@ impl ChildDepState for B { } } -#[derive(PartialEq)] +#[derive(Default, Clone)] struct C; impl ParentDepState for C { type Ctx = u8; @@ -54,7 +54,7 @@ impl ParentDepState for C { fn reduce( &mut self, _: dioxus_native_core::real_dom_new_api::NodeView, - _: &Self::DepState, + _: Option<&Self::DepState>, _: &u8, ) -> bool { todo!() diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index ff0dee1d5..2a17ac520 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/state.rs @@ -1,60 +1,125 @@ +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::real_dom_new_api::{ + AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, +}; +use dioxus_native_core_macro::State; -#[derive(Debug, Clone, PartialEq, Default)] -struct CallCounter(u32); -impl BubbledUpState for CallCounter { +#[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 = (); - - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce( + &mut self, + _node: NodeView, + _children: Vec<&Self::DepState>, + _ctx: &Self::Ctx, + ) -> bool { self.0 += 1; + true } } -impl PushedDownState for CallCounter { +#[derive(Debug, Clone, Default)] +struct ParentDepCallCounter(u32); +impl ParentDepState for ParentDepCallCounter { type Ctx = (); - - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::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(String, Vec>); -impl BubbledUpState for BubbledUpStateTester { +struct BubbledUpStateTester(Option, Vec>); +impl ChildDepState for BubbledUpStateTester { type Ctx = u32; - - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = BubbledUpStateTester( - vnode.mounted_id().to_string(), - children.map(|c| Box::new(c.clone())).collect(), + 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(String, Option>); -impl PushedDownState for PushedDownStateTester { +struct PushedDownStateTester(Option, Option>); +impl ParentDepState for PushedDownStateTester { type Ctx = u32; - - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) { + type DepState = Self; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = PushedDownStateTester( - vnode.mounted_id().to_string(), + 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, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, 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 } } @@ -72,71 +137,68 @@ fn state_initial() { let mutations = vdom.create_vnodes(rsx! { div { - p{} + p{ + color: "red" + } h1{} } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut 42, &mut 42); + 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.up_state.0, "1"); + assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!( - root_div.up_state.1, + root_div.state.bubbled.1, vec![ - Box::new(BubbledUpStateTester("2".to_string(), Vec::new())), - Box::new(BubbledUpStateTester("3".to_string(), Vec::new())) + Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())), + Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new())) ] ); - assert_eq!(root_div.down_state.0, "1"); - assert_eq!(root_div.down_state.1, None); + 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.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.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.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) + 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.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.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.down_state.1, - Some(Box::new(PushedDownStateTester("1".to_string(), None))) + 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() { - #[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, - 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 { @@ -178,14 +240,15 @@ fn state_reduce_initally_called_minimally() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, 1); - assert_eq!(n.down_state.0, 1); + assert_eq!(n.state.child_counter.0, 1); + assert_eq!(n.state.parent_counter.0, 1); + assert_eq!(n.state.node_counter.0, 1); }); } @@ -194,6 +257,7 @@ 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{} @@ -233,10 +297,10 @@ fn state_reduce_down_called_minimally_on_update() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + 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, @@ -247,11 +311,13 @@ fn state_reduce_down_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.down_state.0, 2); + println!("{:?}", n.state); + // assert_eq!(n.state.parent_counter.0, 2); }); + panic!() } #[test] @@ -259,6 +325,7 @@ 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{} @@ -298,10 +365,10 @@ fn state_reduce_up_called_minimally_on_update() { } }); - let mut dom: RealDom = RealDom::new(); + let mut dom: RealDom = RealDom::new(); let nodes_updated = dom.apply_mutations(vec![mutations]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + 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, @@ -312,9 +379,11 @@ fn state_reduce_up_called_minimally_on_update() { dirty_scopes: fxhash::FxHashSet::default(), refs: Vec::new(), }]); - let _to_rerender = dom.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); + println!("{:?}", n.state); + // assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); + panic!() } diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 6a65af607..43064bb63 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -25,3 +25,4 @@ futures = "0.3.19" stretch2 = { git = "https://github.com/DioxusLabs/stretch" } smallvec = "1.6" fxhash = "0.2" +anymap = "0.12.1" \ No newline at end of file diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 03e137111..4aa9b1313 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -6,7 +6,9 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::{ + dioxus_native_core_macro::State, real_dom::RealDom, real_dom_new_api::State, +}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, @@ -29,6 +31,9 @@ pub use config::*; pub use hooks::*; pub use render::*; +#[derive(Debug, Clone, State, Default)] +struct NodeState {} + #[derive(Clone)] pub struct TuiContext { tx: UnboundedSender, From 9eaf7212a3e74489dc65f3c568a0ca588330dfcc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:46:16 -0500 Subject: [PATCH 17/33] tests passing, and tui updated --- packages/native-core-macro/src/lib.rs | 22 +- packages/native-core/src/lib.rs | 2 +- packages/native-core/src/real_dom.rs | 207 ++++++++++++------ .../src/{real_dom_new_api.rs => state.rs} | 189 +++++++++------- packages/native-core/tests/change_nodes.rs | 2 +- packages/native-core/tests/initial_build.rs | 2 +- packages/native-core/tests/parse.rs | 17 +- .../tests/{state.rs => update_state.rs} | 39 ++-- packages/tui/src/hooks.rs | 36 ++- packages/tui/src/layout.rs | 123 ++++++----- packages/tui/src/lib.rs | 58 ++--- packages/tui/src/render.rs | 31 ++- packages/tui/src/style_attributes.rs | 27 ++- 13 files changed, 450 insertions(+), 305 deletions(-) rename packages/native-core/src/{real_dom_new_api.rs => state.rs} (66%) rename packages/native-core/tests/{state.rs => update_state.rs} (93%) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 5ec2fceba..cc99ee746 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -105,8 +105,8 @@ 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: Option<&'a dioxus_core::VElement<'a>>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::NodeDepState as _; + 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!(); @@ -117,8 +117,8 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn update_parent_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, parent: Option<&Self>, ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ParentDepState as _; + 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!(); @@ -129,8 +129,8 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn update_child_dep_state<'a>(&'a mut self, ty: std::any::TypeId, node: Option<&'a dioxus_core::VElement<'a>>, children: &[&Self], ctx: &anymap::AnyMap) -> bool{ - use dioxus_native_core::real_dom_new_api::ChildDepState as _; + 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!() @@ -141,7 +141,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { } } - fn child_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + 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>()); @@ -149,7 +149,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { dep_types } - fn parent_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + 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>()); @@ -157,7 +157,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { dep_types } - fn node_dep_types(&self, mask: &dioxus_native_core::real_dom_new_api::NodeMask) -> Vec{ + 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>()); @@ -314,7 +314,7 @@ impl<'a> StateMember<'a> { quote!(self.#ident.reduce(#node_view, #get_ctx)) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident).collect(), #get_ctx)) + 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)) @@ -337,10 +337,8 @@ impl<'a> StateMember<'a> { fn type_id(&self) -> quote::__private::TokenStream { let ty = &self.mem.ty; - // quote!(std::any::TypeId::of::<#ty>()) quote!({ let type_id = std::any::TypeId::of::<#ty>(); - // println!("{:?}", type_id); type_id }) } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 580380f68..7692a5ae1 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,4 +1,4 @@ pub mod layout_attributes; pub mod real_dom; -pub mod real_dom_new_api; +pub mod state; pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 55ae7e0a8..1c25806c3 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -8,7 +8,7 @@ use std::{ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::real_dom_new_api::{AttributeMask, NodeMask, State}; +use crate::state::{union_ordered_iter, AttributeMask, NodeMask, State}; /// 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. @@ -166,7 +166,10 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push((root as usize, NodeMask::NONE)); + nodes_updated.push(( + root as usize, + NodeMask::new(AttributeMask::NONE, false, false, true), + )); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -177,7 +180,7 @@ impl RealDom { SetAttribute { root, field, .. } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false), + NodeMask::new(AttributeMask::single(field), false, false, false), )); } RemoveAttribute { @@ -185,7 +188,7 @@ impl RealDom { } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false), + NodeMask::new(AttributeMask::single(field), false, false, false), )); } PopRoot {} => { @@ -210,65 +213,122 @@ impl RealDom { All, Some(Vec), } + 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 { + // 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(); to_rerender.extend(nodes_updated.iter().map(|(id, _)| id)); let mut nodes_updated: Vec<_> = nodes_updated .into_iter() - .map(|(id, mask)| (id, self[id].height, mask, StatesToCheck::All)) + .map(|(id, mask)| NodeRef { + id, + height: self[id].height, + node_mask: mask, + to_check: StatesToCheck::All, + }) .collect(); - // Sort nodes first by height, then if the height is the same id. - nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0))); - { - // Combine mutations that affect the same node. - let current_key = None; - for i in 0..nodes_updated.len() { - let current = nodes_updated; + nodes_updated.sort(); + + // Combine mutations that affect the same node. + let mut last_node: Option = 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); } } - println!("{:?}", nodes_updated); + 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 (id, _height, mask, to_check) in &nodes_updated { + for node_ref in &nodes_updated { let mut changed = false; - let node = &mut self[*id as usize]; - let ids = match to_check { - StatesToCheck::All => node.state.node_dep_types(&mask), + 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[*id as usize]; - let el = if let &VNode::Element(e) = node.element(vdom) { - Some(e) - } else { - None - }; - changed |= node.state.update_node_dep_state(ty, el, &ctx); + 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(*id); + to_rerender.insert(node_ref.id); } } // 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(); - while let Some((id, height, mask, to_check)) = to_bubble.pop_back() { + let mut to_bubble = nodes_updated.clone(); + while let Some(node_ref) = to_bubble.pop_back() { + let NodeRef { + id, + height, + node_mask, + to_check, + } = node_ref; 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(&mask), + StatesToCheck::All => node.state.child_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); for ty in ids { - let el = if let &VNode::Element(e) = node.element(vdom) { - Some(e) - } else { - None - }; + let vnode = node.element(vdom); if node .state - .update_child_dep_state(ty, el, &children_state, &ctx) + .update_child_dep_state(ty, vnode, &children_state, &ctx) { changed.push(ty); } @@ -276,19 +336,28 @@ impl RealDom { if let Some(parent_id) = node.parent { if !changed.is_empty() { to_rerender.insert(id); - let i = to_bubble.partition_point(|(other_id, h, ..)| { - *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0) - }); + let i = to_bubble.partition_point( + |NodeRef { + id: other_id, + height: h, + .. + }| { + *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0) + }, + ); // make sure the parent is not already queued - if i >= to_bubble.len() || to_bubble[i].0 != parent_id.0 { + if i < to_bubble.len() && to_bubble[i].id == parent_id.0 { + to_bubble[i].to_check = + to_bubble[i].to_check.union(&StatesToCheck::Some(changed)); + } else { to_bubble.insert( i, - ( - parent_id.0, - height - 1, - NodeMask::NONE, - StatesToCheck::Some(changed), - ), + NodeRef { + id: parent_id.0, + height: height - 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed), + }, ); } } @@ -296,26 +365,28 @@ impl RealDom { } // 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(); - while let Some((id, height, mask, to_check)) = to_push.pop_front() { + let mut to_push = nodes_updated.clone(); + while let Some(node_ref) = to_push.pop_front() { + let NodeRef { + id, + height, + node_mask, + to_check, + } = node_ref; let node = &self[id]; let ids = match to_check { - StatesToCheck::All => node.state.parent_dep_types(&mask), + StatesToCheck::All => node.state.parent_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); let (node, parent) = self.get_node_parent_mut(id).unwrap(); for ty in ids { - let el = if let &VNode::Element(e) = node.element(vdom) { - Some(e) - } else { - None - }; + let vnode = node.element(vdom); let parent = parent.as_deref(); let state = &mut node.state; if state.update_parent_dep_state( ty, - el, + vnode, parent.filter(|n| n.id.0 != 0).map(|n| &n.state), &ctx, ) { @@ -328,18 +399,28 @@ impl RealDom { let node = &self[id]; if let NodeType::Element { children, .. } = &node.node_type { for c in children { - let i = to_push.partition_point(|(other_id, h, ..)| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }); - if i >= to_push.len() || to_push[i].0 != c.0 { + let i = to_push.partition_point( + |NodeRef { + id: other_id, + height: h, + .. + }| { + *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, - ( - c.0, - height + 1, - NodeMask::NONE, - StatesToCheck::Some(changed.clone()), - ), + NodeRef { + id: c.0, + height: height + 1, + node_mask: NodeMask::NONE, + to_check: StatesToCheck::Some(changed.clone()), + }, ); } } diff --git a/packages/native-core/src/real_dom_new_api.rs b/packages/native-core/src/state.rs similarity index 66% rename from packages/native-core/src/real_dom_new_api.rs rename to packages/native-core/src/state.rs index 042e625c1..1e7135580 100644 --- a/packages/native-core/src/real_dom_new_api.rs +++ b/packages/native-core/src/state.rs @@ -1,43 +1,96 @@ -use std::any::TypeId; +use std::{any::TypeId, fmt::Debug}; use anymap::AnyMap; -use dioxus_core::{Attribute, VElement}; +use dioxus_core::{Attribute, ElementId, VElement, VNode, VText}; + +pub(crate) fn union_ordered_iter( + s_iter: impl Iterator, + o_iter: impl Iterator, + new_len_guess: usize, +) -> Vec { + 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: Option<&'a VElement<'a>>, - view: NodeMask, + inner: &'a VNode<'a>, + mask: NodeMask, } impl<'a> NodeView<'a> { - pub fn new(velement: Option<&'a VElement<'a>>, view: NodeMask) -> Self { + pub fn new(vnode: &'a VNode<'a>, view: NodeMask) -> Self { Self { - inner: velement, - view: view, + inner: vnode, + mask: view, } } + pub fn id(&self) -> ElementId { + self.inner.mounted_id() + } + pub fn tag(&self) -> Option<&'a str> { - if self.view.tag { - self.inner.map(|el| el.tag) - } else { - None - } + self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten() } pub fn namespace(&self) -> Option<&'a str> { - if self.view.namespace { - self.inner.map(|el| el.namespace).flatten() + self.mask + .namespace + .then(|| self.el().map(|el| el.namespace).flatten()) + .flatten() + } + + pub fn attributes(&self) -> impl Iterator> { + 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 } } - pub fn attributes(&self) -> impl Iterator> { - self.inner - .map(|el| el.attributes) - .unwrap_or_default() - .iter() - .filter(|a| self.view.attritutes.contains_attribute(&a.name)) + fn txt(&self) -> Option<&'a VText<'a>> { + if let VNode::Text(txt) = &self.inner { + Some(txt) + } else { + None + } } } @@ -54,8 +107,8 @@ impl AttributeMask { fn contains_attribute(&self, attr: &'static str) -> bool { match self { AttributeMask::All => true, - AttributeMask::Dynamic(l) => l.contains(&attr), - AttributeMask::Static(l) => l.contains(&attr), + AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(), + AttributeMask::Static(l) => l.binary_search(&attr).is_ok(), } } @@ -78,46 +131,19 @@ impl AttributeMask { } pub fn union(&self, other: &Self) -> Self { - pub fn union_iter( - s_iter: impl Iterator, - o_iter: impl Iterator, - ) -> Vec<&'static str> { - let mut s_peekable = s_iter.peekable(); - let mut o_peekable = o_iter.peekable(); - let mut v = Vec::new(); - while let Some(s_i) = s_peekable.peek() { - loop { - if let Some(o_i) = o_peekable.peek() { - if o_i > s_i { - break; - } else { - 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); - } - v - } - let new = match (self, other) { - (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => { - AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) - } - (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => { - AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) - } - (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => { - AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) - } - (AttributeMask::Static(s), AttributeMask::Static(o)) => { - AttributeMask::Dynamic(union_iter(s.iter().copied(), o.iter().copied())) - } + (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(); @@ -179,29 +205,37 @@ pub struct NodeMask { attritutes: AttributeMask, tag: bool, namespace: bool, + text: bool, } impl NodeMask { - pub const NONE: Self = Self::new(AttributeMask::Static(&[]), false, false); - pub const ALL: Self = Self::new(AttributeMask::All, true, true); + 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) -> Self { + 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_overlap(other) + || self.attritutes.overlaps(&other.attritutes) + || (self.text && other.text) } - fn attritutes_overlap(&self, other: &Self) -> bool { - self.attritutes.overlaps(&other.attritutes) + 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, + } } } @@ -213,8 +247,15 @@ pub trait ChildDepState { /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ChildDepState; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); - fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a; } /// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. @@ -225,7 +266,7 @@ pub trait ParentDepState { /// This is sometimes nessisary for lifetime purposes. type Ctx; type DepState: ParentDepState; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; } @@ -234,7 +275,7 @@ pub trait ParentDepState { /// Called at most once per update. pub trait NodeDepState { type Ctx; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false); + const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; } @@ -242,7 +283,7 @@ pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, ctx: &AnyMap, ) -> bool; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -251,7 +292,7 @@ pub trait State: Default + Clone { fn update_parent_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, parent: Option<&Self>, ctx: &AnyMap, ) -> bool; @@ -261,7 +302,7 @@ pub trait State: Default + Clone { fn update_child_dep_state<'a>( &'a mut self, ty: TypeId, - node: Option<&'a VElement<'a>>, + node: &'a VNode<'a>, children: &[&Self], ctx: &AnyMap, ) -> bool; diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index bf3de4368..8f4e6c8f0 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/packages/native-core/tests/change_nodes.rs @@ -3,7 +3,7 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; -use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core::state::State; use dioxus_native_core_macro::State; use std::cell::Cell; diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index 321820b7b..750987a29 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core/tests/initial_build.rs @@ -5,7 +5,7 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::RealDom; -use dioxus_native_core::real_dom_new_api::State; +use dioxus_native_core::state::State; use dioxus_native_core_macro::State; #[derive(State, Default, Clone)] diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs index a3cf69ecf..a0bbd4f27 100644 --- a/packages/native-core/tests/parse.rs +++ b/packages/native-core/tests/parse.rs @@ -1,4 +1,4 @@ -use dioxus_native_core::real_dom_new_api::*; +use dioxus_native_core::state::*; use dioxus_native_core_macro::*; #[derive(State, Default, Clone)] @@ -20,7 +20,7 @@ struct Z { // z: C, // } -use dioxus_native_core::real_dom_new_api::NodeDepState; +use dioxus_native_core::state::NodeDepState; #[derive(Default, Clone)] struct A; @@ -36,12 +36,15 @@ struct B; impl ChildDepState for B { type Ctx = i32; type DepState = Self; - fn reduce( + fn reduce<'a>( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeView, - _: Vec<&Self::DepState>, + _: dioxus_native_core::state::NodeView, + _: impl Iterator, _: &i32, - ) -> bool { + ) -> bool + where + Self::DepState: 'a, + { todo!() } } @@ -53,7 +56,7 @@ impl ParentDepState for C { type DepState = Self; fn reduce( &mut self, - _: dioxus_native_core::real_dom_new_api::NodeView, + _: dioxus_native_core::state::NodeView, _: Option<&Self::DepState>, _: &u8, ) -> bool { diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/update_state.rs similarity index 93% rename from packages/native-core/tests/state.rs rename to packages/native-core/tests/update_state.rs index 2a17ac520..5c614922f 100644 --- a/packages/native-core/tests/state.rs +++ b/packages/native-core/tests/update_state.rs @@ -4,7 +4,7 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_native_core::real_dom::*; -use dioxus_native_core::real_dom_new_api::{ +use dioxus_native_core::state::{ AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, }; use dioxus_native_core_macro::State; @@ -25,12 +25,15 @@ impl ChildDepState for ChildDepCallCounter { type Ctx = (); type DepState = Self; const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce( + fn reduce<'a>( &mut self, - _node: NodeView, - _children: Vec<&Self::DepState>, - _ctx: &Self::Ctx, - ) -> bool { + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { self.0 += 1; true } @@ -70,7 +73,15 @@ impl ChildDepState for BubbledUpStateTester { type Ctx = u32; type DepState = Self; const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); - fn reduce(&mut self, node: NodeView, children: Vec<&Self::DepState>, ctx: &Self::Ctx) -> bool { + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { assert_eq!(*ctx, 42); *self = BubbledUpStateTester( node.tag().map(|s| s.to_string()), @@ -245,6 +256,10 @@ fn state_reduce_initally_called_minimally() { 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); @@ -314,10 +329,10 @@ fn state_reduce_down_called_minimally_on_update() { 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); + // println!("{:?}", n.state); + assert_eq!(n.state.parent_counter.0, 2); }); - panic!() + // panic!() } #[test] @@ -382,8 +397,6 @@ fn state_reduce_up_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { - println!("{:?}", n.state); - // assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); + assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); - panic!() } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 512fab8ba..434e9e7c5 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,7 +5,6 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::real_dom::{Node, RealDom}; use std::{ any::Any, cell::RefCell, @@ -17,6 +16,7 @@ use stretch2::{prelude::Layout, Stretch}; use crate::layout::StretchLayout; use crate::style_attributes::StyleModifier; +use crate::{Dom, Node}; // a wrapper around the input state for easier access // todo: fix loop @@ -166,7 +166,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { let previous_mouse = self .mouse @@ -191,7 +191,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - dom: &mut RealDom, + dom: &mut Dom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -215,8 +215,8 @@ impl InnerInputState { data: Arc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &Node, - dom: &RealDom, + node: &Node, + dom: &Dom, ) { // only trigger event if the event was not triggered already by a child if will_bubble.insert(node.id) { @@ -261,7 +261,7 @@ impl InnerInputState { // mousemove let mut will_bubble = FxHashSet::default(); 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 .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -285,7 +285,7 @@ impl InnerInputState { // mouseenter let mut will_bubble = FxHashSet::default(); 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 .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -309,7 +309,7 @@ impl InnerInputState { // mouseover let mut will_bubble = FxHashSet::default(); 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 .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -333,7 +333,7 @@ impl InnerInputState { // mousedown let mut will_bubble = FxHashSet::default(); 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); if currently_contains && data.clicked { @@ -353,7 +353,7 @@ impl InnerInputState { // mouseup let mut will_bubble = FxHashSet::default(); 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); if currently_contains && data.released { @@ -373,7 +373,7 @@ impl InnerInputState { // click let mut will_bubble = FxHashSet::default(); 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); if currently_contains && data.released && data.mouse_data.button == 0 { @@ -393,7 +393,7 @@ impl InnerInputState { // contextmenu let mut will_bubble = FxHashSet::default(); 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); if currently_contains && data.released && data.mouse_data.button == 2 { @@ -413,7 +413,7 @@ impl InnerInputState { // wheel let mut will_bubble = FxHashSet::default(); 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); if let Some(w) = data.wheel_data { @@ -435,7 +435,7 @@ impl InnerInputState { // mouseleave let mut will_bubble = FxHashSet::default(); 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 .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -459,7 +459,7 @@ impl InnerInputState { // mouseout let mut will_bubble = FxHashSet::default(); 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 .old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) @@ -522,11 +522,7 @@ impl RinkInputHandler { ) } - pub fn get_events( - &self, - layout: &Stretch, - dom: &mut RealDom, - ) -> Vec { + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 4224fbdd5..5924a0fd1 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -1,83 +1,90 @@ +use std::cell::RefCell; +use std::rc::Rc; + use dioxus_core::*; 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::*; -/// the size #[derive(Clone, PartialEq, Default, Debug)] pub struct StretchLayout { pub style: Style, pub node: Option, } -impl BubbledUpState for StretchLayout { - type Ctx = Stretch; +impl ChildDepState for StretchLayout { + type Ctx = Rc>; + type DepState = Self; + // todo: update mask + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true); /// 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, + ctx: &Self::Ctx, + ) -> bool where - I: Iterator, - Self: 'a, + Self::DepState: 'a, { - match vnode { - VNode::Text(t) => { - let char_len = t.text.chars().count(); + let mut stretch = ctx.borrow_mut(); + if let Some(text) = node.text() { + let char_len = text.chars().count(); - let style = Style { - size: Size { - // characters are 1 point tall - height: Dimension::Points(1.0), + let style = Style { + size: Size { + // characters are 1 point tall + height: Dimension::Points(1.0), - // text is as long as it is declared - width: Dimension::Points(char_len as f32), - }, - ..Default::default() - }; + // text is as long as it is declared + width: Dimension::Points(char_len as f32), + }, + ..Default::default() + }; - if let Some(n) = self.node { - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); + if let Some(n) = self.node { + if self.style != style { + stretch.set_style(n, style).unwrap(); } - - self.style = style; + } else { + 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 { - apply_layout_attributes(name, value, &mut style); - } + self.style = style; + } else { + // gather up all the styles from the attribute list + let mut style = Style::default(); - // the root node fills the entire area - if el.id.get() == Some(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; + for &Attribute { name, value, .. } in node.attributes() { + apply_layout_attributes(name, value, &mut 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 } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4aa9b1313..1286eba06 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use anymap::AnyMap; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, execute, @@ -6,14 +7,14 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{ - dioxus_native_core_macro::State, real_dom::RealDom, real_dom_new_api::State, -}; +use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; use layout::StretchLayout; +use std::cell::RefCell; +use std::rc::Rc; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; @@ -31,8 +32,17 @@ pub use config::*; pub use hooks::*; pub use render::*; +type Dom = RealDom; +type Node = dioxus_native_core::real_dom::Node; + #[derive(Debug, Clone, State, Default)] -struct NodeState {} +struct NodeState { + #[child_dep_state(StretchLayout, RefCell)] + layout: StretchLayout, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(StyleModifier)] + style: StyleModifier, +} #[derive(Clone)] pub struct TuiContext { @@ -75,13 +85,13 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); - let mut rdom: RealDom = RealDom::new(); + let mut rdom: Dom = RealDom::new(); let mutations = dom.rebuild(); let to_update = rdom.apply_mutations(vec![mutations]); - let mut stretch = Stretch::new(); - let _to_rerender = rdom - .update_state(&dom, to_update, &mut stretch, &mut ()) - .unwrap(); + let stretch = Rc::new(RefCell::new(Stretch::new())); + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); render_vdom( &mut dom, @@ -100,8 +110,8 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut rdom: RealDom, - mut stretch: Stretch, + mut rdom: Dom, + mut stretch: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() @@ -119,7 +129,7 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; loop { @@ -136,15 +146,11 @@ fn render_vdom( if !to_rerender.is_empty() || resized { resized = false; - fn resize( - dims: Rect, - stretch: &mut Stretch, - rdom: &RealDom, - ) { + 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].up_state.node.unwrap(); + let root_node = rdom[root_id].state.layout.node.unwrap(); stretch .compute_layout( @@ -159,9 +165,9 @@ fn render_vdom( if let Some(terminal) = &mut terminal { terminal.draw(|frame| { // 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()]; - render::render_vnode(frame, &stretch, &rdom, &root, cfg); + render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg); })?; } else { resize( @@ -171,7 +177,7 @@ fn render_vdom( width: 300, height: 300, }, - &mut stretch, + &mut stretch.borrow_mut(), &rdom, ); } @@ -212,7 +218,7 @@ fn render_vdom( { // 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 { vdom.handle_message(SchedulerMsg::Event(e)); } @@ -220,11 +226,9 @@ fn render_vdom( // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); // update the style and layout - to_rerender.extend( - rdom.update_state(vdom, to_update, &mut stretch, &mut ()) - .unwrap() - .iter(), - ) + let mut any_map = AnyMap::new(); + any_map.insert(stretch.clone()); + let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap(); } } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 94cd90a62..8cee7c55c 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,8 +1,5 @@ use crate::layout::StretchLayout; -use dioxus_native_core::{ - layout_attributes::UnitSystem, - real_dom::{Node, RealDom}, -}; +use dioxus_native_core::layout_attributes::UnitSystem; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -15,16 +12,16 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, StyleModifier, + Config, Dom, Node, StyleModifier, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; -pub fn render_vnode( +pub(crate) fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, - rdom: &RealDom, - node: &Node, + rdom: &Dom, + node: &Node, cfg: Config, ) { use dioxus_native_core::real_dom::NodeType; @@ -33,7 +30,7 @@ pub fn render_vnode( 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 Size { width, height } = size; @@ -59,7 +56,7 @@ pub fn render_vnode( let label = Label { 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); @@ -84,7 +81,7 @@ pub fn render_vnode( } } -impl RinkWidget for &Node { +impl RinkWidget for &Node { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -268,14 +265,14 @@ impl RinkWidget for &Node { for x in area.left()..area.right() { for y in area.top()..area.bottom() { 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; } 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 current_edge = &borders.top; @@ -292,7 +289,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) 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(); if let Some(c) = color { new_cell.fg = c; @@ -327,7 +324,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) 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(); if let Some(c) = color { new_cell.fg = c; @@ -362,7 +359,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) 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(); if let Some(c) = color { new_cell.fg = c; @@ -397,7 +394,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) 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(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 710610104..5438eb53d 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -32,7 +32,7 @@ use dioxus_core::{Attribute, VNode}; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, - real_dom::PushedDownState, + state::{AttributeMask, NodeMask, NodeView, ParentDepState}, }; use crate::style::{RinkColor, RinkStyle}; @@ -43,18 +43,22 @@ pub struct StyleModifier { pub modifier: TuiModifier, } -impl PushedDownState for StyleModifier { +impl ParentDepState for StyleModifier { 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>, ctx: &Self::Ctx) -> bool { *self = StyleModifier::default(); if parent.is_some() { self.style.fg = None; } - if let VNode::Element(el) = vnode { - // handle text modifier elements - if el.namespace.is_none() { - match el.tag { + + // 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), @@ -68,11 +72,11 @@ impl PushedDownState for StyleModifier { _ => (), } } + } - // gather up all the styles from the attribute list - for &Attribute { name, value, .. } in el.attributes { - apply_style_attributes(name, value, self); - } + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in node.attributes() { + apply_style_attributes(name, value, self); } // keep the text styling from the parent element @@ -81,6 +85,7 @@ impl PushedDownState for StyleModifier { new_style.bg = self.style.bg; self.style = new_style; } + true } } From 2b383bc4f4dab4871c61323ac84be689795df1c6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:54:26 -0500 Subject: [PATCH 18/33] fixed test --- packages/native-core/tests/update_state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/native-core/tests/update_state.rs b/packages/native-core/tests/update_state.rs index 5c614922f..d4825fdd5 100644 --- a/packages/native-core/tests/update_state.rs +++ b/packages/native-core/tests/update_state.rs @@ -72,7 +72,7 @@ struct BubbledUpStateTester(Option, Vec>); impl ChildDepState for BubbledUpStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); fn reduce<'a>( &mut self, node: NodeView, @@ -96,7 +96,7 @@ struct PushedDownStateTester(Option, Option>) impl ParentDepState for PushedDownStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false); + 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( @@ -121,7 +121,7 @@ struct StateTester { struct NodeStateTester(Option, Vec<(String, String)>); impl NodeDepState for NodeStateTester { type Ctx = u32; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false); + 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( From 71707fec096056767b94b02e95c5dba706d74d7a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 12 Apr 2022 18:58:25 -0500 Subject: [PATCH 19/33] pass cargo check --- packages/tui/src/hooks.rs | 2 -- packages/tui/src/lib.rs | 3 +-- packages/tui/src/render.rs | 3 +-- packages/tui/src/style_attributes.rs | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 434e9e7c5..c09eed50b 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -14,8 +14,6 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; -use crate::layout::StretchLayout; -use crate::style_attributes::StyleModifier; use crate::{Dom, Node}; // a wrapper around the input state for easier access diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1286eba06..872356efb 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -30,7 +30,6 @@ mod widget; pub use config::*; pub use hooks::*; -pub use render::*; type Dom = RealDom; type Node = dioxus_native_core::real_dom::Node; @@ -111,7 +110,7 @@ fn render_vdom( handler: RinkInputHandler, cfg: Config, mut rdom: Dom, - mut stretch: Rc>, + stretch: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 8cee7c55c..e3f03f0d8 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,3 @@ -use crate::layout::StretchLayout; use dioxus_native_core::layout_attributes::UnitSystem; use std::io::Stdout; use stretch2::{ @@ -12,7 +11,7 @@ use crate::{ style::{RinkColor, RinkStyle}, style_attributes::{BorderEdge, BorderStyle}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, - Config, Dom, Node, StyleModifier, + Config, Dom, Node, }; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 5438eb53d..b49707668 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -29,7 +29,7 @@ - [ ] pub aspect_ratio: Number, */ -use dioxus_core::{Attribute, VNode}; +use dioxus_core::Attribute; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, state::{AttributeMask, NodeMask, NodeView, ParentDepState}, @@ -49,7 +49,7 @@ impl ParentDepState for StyleModifier { // todo: seperate each attribute into it's own class const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, true, false); - fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { *self = StyleModifier::default(); if parent.is_some() { self.style.fg = None; From 3b06059834b18619401c4dced3aa12ce937fd294 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Apr 2022 11:39:38 -0500 Subject: [PATCH 20/33] 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 } } From 0fb9aed273b9ac32a24dff0e57e3d3afa5b18005 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 13 Apr 2022 12:49:53 -0500 Subject: [PATCH 21/33] add children on initial build and add sorted slice macro --- packages/native-core-macro/src/lib.rs | 49 ++- .../src/lib.rs~Stashed changes | 368 ------------------ packages/native-core/src/layout_attributes.rs | 2 - packages/native-core/src/real_dom.rs | 8 +- packages/tui/src/layout.rs | 206 +++++++++- packages/tui/src/lib.rs | 1 - packages/tui/src/render.rs | 1 - 7 files changed, 221 insertions(+), 414 deletions(-) delete mode 100644 packages/native-core-macro/src/lib.rs~Stashed changes diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 084349831..a4c7fed7c 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -1,37 +1,44 @@ extern crate proc_macro; +use std::collections::BTreeMap; + use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::{ - self, + self, bracketed, parse::{Parse, ParseStream, Result}, + parse_macro_input, punctuated::Punctuated, token::Paren, Field, Ident, LitStr, Token, Type, TypeTuple, }; -// struct StrSlice { -// init: LitStr, -// } +struct StrSlice { + map: BTreeMap, +} -// 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, -// }) -// } -// } +impl Parse for StrSlice { + fn parse(input: ParseStream) -> Result { + let content; + bracketed!(content in input); + let mut map = BTreeMap::new(); + while let Ok(s) = content.parse::() { + map.insert(s.value(), s); + #[allow(unused_must_use)] + { + content.parse::(); + } + } + Ok(StrSlice { map }) + } +} -// #[proc_macro] -// pub fn sorted_str_slice(input: TokenStream) -> TokenStream {} +#[proc_macro] +pub fn sorted_str_slice(input: TokenStream) -> TokenStream { + let slice: StrSlice = parse_macro_input!(input as StrSlice); + let strings = slice.map.values(); + quote!([#(#strings, )*]).into() +} #[derive(PartialEq)] enum DepKind { diff --git a/packages/native-core-macro/src/lib.rs~Stashed changes b/packages/native-core-macro/src/lib.rs~Stashed changes deleted file mode 100644 index 084349831..000000000 --- a/packages/native-core-macro/src/lib.rs~Stashed changes +++ /dev/null @@ -1,368 +0,0 @@ -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 b675183a7..45461a3f9 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -29,8 +29,6 @@ - [ ] 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 8c2a05e9f..ecb165c26 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -384,13 +384,7 @@ impl RealDom { let vnode = node.element(vdom); let parent = parent.as_deref(); let state = &mut node.state; - if state.update_parent_dep_state( - ty, - vnode, - vdom, - parent.filter(|n| n.id.0 != 0).map(|n| &n.state), - &ctx, - ) { + if state.update_parent_dep_state(ty, vnode, vdom, parent.map(|n| &n.state), &ctx) { changed.push(ty); } } diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index f2a26c65e..de4f13569 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -2,21 +2,46 @@ use std::cell::RefCell; use std::rc::Rc; use dioxus_core::*; +use dioxus_native_core::dioxus_native_core_macro::sorted_str_slice; use dioxus_native_core::layout_attributes::apply_layout_attributes; use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView}; use stretch2::prelude::*; +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum PossiblyUninitalized { + Uninitalized, + Initialized(T), +} +impl PossiblyUninitalized { + pub fn unwrap(self) -> T { + match self { + Self::Initialized(i) => i, + _ => panic!(), + } + } +} +impl Default for PossiblyUninitalized { + fn default() -> Self { + Self::Uninitalized + } +} + #[derive(Clone, PartialEq, Default, Debug)] -pub struct StretchLayout { +pub(crate) struct StretchLayout { pub style: Style, - pub node: Option, + pub node: PossiblyUninitalized, } impl ChildDepState for StretchLayout { type Ctx = Rc>; type DepState = Self; // todo: update mask - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, true); + const NODE_MASK: NodeMask = NodeMask::new( + AttributeMask::Static(SORTED_LAYOUT_ATTRS), + false, + false, + true, + ); /// Setup the layout fn reduce<'a>( @@ -44,16 +69,18 @@ impl ChildDepState for StretchLayout { }, ..Default::default() }; - if let Some(n) = self.node { + if let PossiblyUninitalized::Initialized(n) = self.node { if self.style != style { stretch.set_style(n, style).unwrap(); } } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); + self.node = + PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap()); } } else { // gather up all the styles from the attribute list for &Attribute { name, value, .. } in node.attributes() { + assert!(SORTED_LAYOUT_ATTRS.binary_search(&name).is_ok()); apply_layout_attributes(name, value, &mut style); } @@ -69,14 +96,7 @@ impl ChildDepState for StretchLayout { child_layout.push(l.node.unwrap()); } - if let Some(n) = self.node { - if self.style != style { - stretch.set_style(n, style).unwrap(); - } - } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); - } - if let Some(n) = self.node { + if let PossiblyUninitalized::Initialized(n) = self.node { if self.style != style { stretch.set_style(n, style).unwrap(); } @@ -84,7 +104,9 @@ impl ChildDepState for StretchLayout { stretch.set_children(n, &child_layout).unwrap(); } } else { - self.node = Some(stretch.new_node(style, &[]).unwrap()); + self.node = PossiblyUninitalized::Initialized( + stretch.new_node(style, &child_layout).unwrap(), + ); } } if self.style != style { @@ -94,3 +116,159 @@ impl ChildDepState for StretchLayout { changed } } + +// these are the attributes in layout_attiributes in native-core +const SORTED_LAYOUT_ATTRS: &'static [&'static str] = &sorted_str_slice!([ + "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" +]); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 2c1844eb5..1eb3f7612 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -215,7 +215,6 @@ fn render_vdom( } { - // resolve events before rendering let evts = handler.get_events(&stretch.borrow(), &mut rdom); for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 4a69b7901..e3f03f0d8 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -30,7 +30,6 @@ 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; From 436c6a02f7189d2b0514175ab147b59e9ecb9f59 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 16 Apr 2022 12:23:31 -0500 Subject: [PATCH 22/33] add node dependancy --- packages/native-core-macro/Cargo.toml | 10 + packages/native-core-macro/src/lib.rs | 683 ++++++++++++------ .../native-core-macro/src/sorted_slice.rs | 28 + .../tests/change_nodes.rs | 0 .../tests/initial_build.rs | 2 +- packages/native-core-macro/tests/parse.rs | 74 ++ packages/native-core-macro/tests/test.rs | 70 ++ .../tests/update_state.rs | 151 +++- packages/native-core/Cargo.toml | 1 - packages/native-core/src/lib.rs | 2 +- packages/native-core/src/node_ref.rs | 212 ++++++ packages/native-core/src/real_dom.rs | 73 +- packages/native-core/src/state.rs | 294 ++------ packages/native-core/tests/parse.rs | 59 -- packages/tui/Cargo.toml | 1 + packages/tui/src/layout.rs | 5 +- packages/tui/src/lib.rs | 7 +- packages/tui/src/style_attributes.rs | 215 +++++- 18 files changed, 1312 insertions(+), 575 deletions(-) create mode 100644 packages/native-core-macro/src/sorted_slice.rs rename packages/{native-core => native-core-macro}/tests/change_nodes.rs (100%) rename packages/{native-core => native-core-macro}/tests/initial_build.rs (99%) create mode 100644 packages/native-core-macro/tests/parse.rs create mode 100644 packages/native-core-macro/tests/test.rs rename packages/{native-core => native-core-macro}/tests/update_state.rs (75%) create mode 100644 packages/native-core/src/node_ref.rs delete mode 100644 packages/native-core/tests/parse.rs diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index d71b03246..e80d67d81 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -10,3 +10,13 @@ proc-macro = true [dependencies] syn = { version = "1.0.11", features = ["extra-traits"] } quote = "1.0" +dioxus-native-core = { path = "../native-core" } + +[dev-dependencies] +dioxus-core = { path = "../core", version = "^0.2.0" } +dioxus-html = { path = "../html", version = "^0.2.0" } +dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } + +smallvec = "1.6" +fxhash = "0.2" +anymap = "0.12.1" \ No newline at end of file diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index a4c7fed7c..658653c19 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -1,38 +1,17 @@ extern crate proc_macro; -use std::collections::BTreeMap; +mod sorted_slice; +use dioxus_native_core::state::MemberId; use proc_macro::TokenStream; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, __private::Span}; +use sorted_slice::StrSlice; use syn::{ - self, bracketed, + self, parse::{Parse, ParseStream, Result}, - parse_macro_input, - punctuated::Punctuated, - token::Paren, - Field, Ident, LitStr, Token, Type, TypeTuple, + parse_macro_input, parse_quote, Error, Field, Ident, Token, Type, }; -struct StrSlice { - map: BTreeMap, -} - -impl Parse for StrSlice { - fn parse(input: ParseStream) -> Result { - let content; - bracketed!(content in input); - let mut map = BTreeMap::new(); - while let Ok(s) = content.parse::() { - map.insert(s.value(), s); - #[allow(unused_must_use)] - { - content.parse::(); - } - } - Ok(StrSlice { map }) - } -} - #[proc_macro] pub fn sorted_str_slice(input: TokenStream) -> TokenStream { let slice: StrSlice = parse_macro_input!(input as StrSlice); @@ -40,14 +19,13 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream { quote!([#(#strings, )*]).into() } -#[derive(PartialEq)] +#[derive(PartialEq, Debug, Clone)] 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(); @@ -60,153 +38,149 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { 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"), + syn::Fields::Unit => todo!("unit structs"), } .iter() .collect(), _ => unimplemented!(), }; - let strct = Struct::parse(&fields); - let state_strct = StateStruct::parse(&fields, &strct); + let strct = Struct::new(type_name.clone(), &fields); + match StateStruct::parse(&fields, &strct) { + Ok(state_strct) => { + let node_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::NodeDepState) + .map(|f| f.reduce_self()); + let child_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ChildDepState) + .map(|f| f.reduce_self()); + let parent_dep_state_fields = state_strct + .state_members + .iter() + .filter(|f| f.dep_kind == DepKind::ParentDepState) + .map(|f| f.reduce_self()); - 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 node_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::NodeDepState); + let node_ids = node_iter.clone().map(|m| m.member_id.0); + let node_ids_clone = node_ids.clone(); + let node_types = node_iter.map(|f| &f.mem.ty); + + let child_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::ChildDepState); + let child_ids = child_iter.clone().map(|m| m.member_id.0); + let child_ids_clone = child_ids.clone(); + let child_types = child_iter.map(|f| &f.mem.ty); + + let parent_iter = state_strct + .state_members + .iter() + .filter(|m| m.dep_kind == DepKind::ParentDepState); + let parent_ids = parent_iter.clone().map(|m| m.member_id.0); + let parent_ids_clone = parent_ids.clone(); + let parent_types = parent_iter.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: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::NodeDepState as _; + match ty.0{ + #( + #node_ids => #node_dep_state_fields, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn update_parent_dep_state<'a>( + &'a mut self, + ty: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + parent: Option<&Self>, + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::ParentDepState as _; + match ty.0{ + #( + #parent_ids => #parent_dep_state_fields, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn update_child_dep_state<'a>( + &'a mut self, + ty: dioxus_native_core::state::MemberId, + node: &'a dioxus_core::VNode<'a>, + vdom: &'a dioxus_core::VirtualDom, + children: &[&Self], + ctx: &anymap::AnyMap, + ) -> Option{ + use dioxus_native_core::state::ChildDepState as _; + match ty.0{ + #( + #child_ids => {#child_dep_state_fields}, + )* + _ => panic!("{:?} not in {}", ty, #type_name_str), + } + } + + fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #child_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); + })* + dep_types + } + + fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #parent_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); + })* + dep_types + } + + fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec{ + let mut dep_types = Vec::new(); + #(if #node_types::NODE_MASK.overlaps(mask) { + dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); + })* + dep_types } } - }), - ); - 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() } - }; - gen.into() + Err(e) => e.into_compile_error().into(), + } } struct Struct { + name: Ident, members: Vec, } impl Struct { - fn parse(fields: &[&Field]) -> Self { + fn new(name: Ident, fields: &[&Field]) -> Self { let members = fields.iter().filter_map(|f| Member::parse(f)).collect(); - Self { members } + Self { name, members } } } @@ -215,57 +189,198 @@ struct StateStruct<'a> { } impl<'a> StateStruct<'a> { - fn parse(fields: &[&'a Field], strct: &'a Struct) -> Self { - let state_members = strct + fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result { + let mut parse_err = Ok(()); + let state_members: Vec<_> = strct .members .iter() .zip(fields.iter()) - .filter_map(|(m, f)| StateMember::parse(f, m, &strct)) + .filter_map(|(m, f)| match StateMember::parse(f, m, &strct) { + Ok(m) => m, + Err(err) => { + parse_err = Err(err); + None + } + }) .collect(); + parse_err?; - // todo: sort members + #[derive(Debug, Clone)] + struct DepNode<'a> { + state_mem: StateMember<'a>, + depandants: Vec>>, + } + impl<'a> DepNode<'a> { + fn new(state_mem: StateMember<'a>) -> Self { + Self { + state_mem, + depandants: Vec::new(), + } + } - Self { state_members } - } -} + /// flattens the node in pre order + fn flatten(self) -> Vec> { + let DepNode { + state_mem, + depandants, + } = self; + let mut flat = vec![state_mem]; + for d in depandants { + flat.append(&mut d.flatten()); + } + flat + } -struct DepTypes { - ctx_ty: Option, - dep_ty: Option, -} + fn set_ids(&mut self, current_id: &mut usize) { + self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id); + // if the node depends on itself, we need to add the dependency seperately + if let Some(dep) = self.state_mem.dep_mem { + if dep == self.state_mem.mem { + self.state_mem + .dependants + .push((MemberId(*current_id), self.state_mem.dep_kind.clone())); + } + } + *current_id += 1; + for d in &mut self.depandants { + self.state_mem + .dependants + .push((MemberId(*current_id), d.state_mem.dep_kind.clone())); + d.set_ids(current_id); + } + } -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, - }) - } -} + fn contains_member(&self, member: &Member) -> bool { + if self.state_mem.mem == member { + true + } else { + self.depandants.iter().any(|d| d.contains_member(member)) + } + } -struct NodeDepTypes { - ctx_ty: Option, -} + // check if there are any mixed child/parent dependancies + fn check(&self) -> Option { + self.kind().err() + } -impl Parse for NodeDepTypes { - fn parse(input: ParseStream) -> Result { - let ctx_ty = input.parse().ok(); - Ok(Self { ctx_ty }) - } -} + fn kind(&self) -> Result<&DepKind> { + fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> { + match (dk1, dk2) { + (DepKind::ChildDepState, DepKind::ParentDepState) + | (DepKind::ParentDepState, DepKind::ChildDepState) => Err(Error::new( + Span::call_site(), + "There is a ChildDepState that depends on a ParentDepState", + )), + // node dep state takes the lowest priority + (DepKind::NodeDepState, important) | (important, DepKind::NodeDepState) => { + Ok(important) + } + // they are the same + (fst, _) => Ok(fst), + } + } + reduce_kind( + self.depandants + .iter() + .try_fold(&DepKind::NodeDepState, |dk1, dk2| { + reduce_kind(dk1, dk2.kind()?) + })?, + &self.state_mem.dep_kind, + ) + } -impl From for DepTypes { - fn from(node_dep_types: NodeDepTypes) -> Self { - Self { - ctx_ty: node_dep_types.ctx_ty, - dep_ty: None, + fn insert_dependant(&mut self, other: DepNode<'a>) -> bool { + let dep = other.state_mem.dep_mem.unwrap(); + if self.contains_member(dep) { + if self.state_mem.mem == dep { + self.depandants.push(Box::new(other)); + true + } else { + self.depandants + .iter_mut() + .find(|d| d.contains_member(dep)) + .unwrap() + .insert_dependant(other) + } + } else { + false + } + } + } + + // members need to be sorted so that members are updated after the members they depend on + let mut roots: Vec = vec![]; + for m in state_members.into_iter() { + if let Some(dep) = m.dep_mem { + let root_depends_on = roots + .iter() + .filter_map(|m| m.state_mem.dep_mem) + .any(|d| m.mem == d); + + if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) { + let new = DepNode::new(m); + if root_depends_on { + return Err(Error::new( + new.state_mem.mem.ident.span(), + format!("{} has a circular dependancy", new.state_mem.mem.ident), + )); + } + // return Err(Error::new(new.state_mem.mem.ident.span(), "stuff")); + r.insert_dependant(new); + continue; + } + } + let mut new = DepNode::new(m); + let mut i = 0; + while i < roots.len() { + if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) { + let child = roots.remove(i); + new.insert_dependant(child); + } else { + i += 1; + } + } + roots.push(new); + } + let mut current_id = 0; + for r in &mut roots { + r.set_ids(&mut current_id); + } + if let Some(err) = roots.iter().find_map(DepNode::check) { + Err(err) + } else { + let state_members: Vec<_> = roots + .into_iter() + .map(|r| r.flatten().into_iter()) + .flatten() + .collect(); + + Ok(Self { state_members }) } } } +struct Dependancy { + ctx_ty: Option, + dep: Option, +} + +impl Parse for Dependancy { + fn parse(input: ParseStream) -> Result { + let dep = input + .parse() + .ok() + .filter(|i: &Ident| format!("{}", i) != "NONE"); + let comma: Option = input.parse().ok(); + let ctx_ty = input.parse().ok(); + Ok(Self { + ctx_ty: comma.and(ctx_ty), + dep, + }) + } +} + +#[derive(PartialEq, Debug)] struct Member { ty: Type, ident: Ident, @@ -280,16 +395,25 @@ impl Member { } } +#[derive(Debug, Clone)] struct StateMember<'a> { mem: &'a Member, dep_kind: DepKind, dep_mem: Option<&'a Member>, ctx_ty: Option, + dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>, + // This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in. + member_id: dioxus_native_core::state::MemberId, } impl<'a> StateMember<'a> { - fn parse(field: &Field, mem: &'a Member, parent: &'a Struct) -> Option> { - field.attrs.iter().find_map(|a| { + fn parse( + field: &Field, + mem: &'a Member, + parent: &'a Struct, + ) -> Result>> { + let mut err = Ok(()); + let member = field.attrs.iter().find_map(|a| { let dep_kind = a .path .get_ident() @@ -300,34 +424,44 @@ impl<'a> StateMember<'a> { _ => 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, - }) - }) + match a.parse_args::() { + Ok(dependancy) => { + let dep_mem = if let Some(name) = &dependancy.dep { + if let Some(found) = parent.members.iter().find(|m| &m.ident == name) { + Some(found) + } else { + err = Err(Error::new( + name.span(), + format!("{} not found in {}", name, parent.name), + )); + None + } + } else { + None + }; + Some(Self { + mem, + dep_kind, + dep_mem, + ctx_ty: dependancy.ctx_ty, + dependants: Vec::new(), + member_id: dioxus_native_core::state::MemberId(0), + }) + } + Err(e) => { + err = Err(e); + None + } + } + }); + err?; + Ok(member) } 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(), - }) - { + if ctx_ty == &parse_quote!(()) { quote! {&()} } else { let msg = ctx_ty.to_token_stream().to_string() + " not found in context"; @@ -336,40 +470,119 @@ impl<'a> StateMember<'a> { } else { quote! {&()} }; + let states_changed = { + let child_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::ChildDepState) + .map(|(id, _)| id.0); + let parent_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::ParentDepState) + .map(|(id, _)| id.0); + let node_dep = self + .dependants + .iter() + .filter(|(_, kind)| kind == &DepKind::NodeDepState) + .map(|(id, _)| id.0); + match self.dep_kind { + DepKind::NodeDepState => { + quote! { + dioxus_native_core::state::NodeStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + } + } + } + DepKind::ChildDepState => { + quote! { + dioxus_native_core::state::ChildStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*], + } + } + } + DepKind::ParentDepState => { + quote! { + dioxus_native_core::state::ParentStatesChanged{ + node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*], + } + } + } + } + }; + let ty = &self.mem.ty; - let node_view = quote!(NodeView::new(node, #ty::NODE_MASK, vdom)); + let node_view = + quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom)); + let id = self.member_id.0; 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)) + quote!({ + // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx)) + quote!({ + // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx)) + quote!({ + // println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } } } else { match self.dep_kind { DepKind::NodeDepState => { - quote!(self.#ident.reduce(#node_view, #get_ctx)) + quote!({ + // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, &(), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ChildDepState => { - quote!(self.#ident.reduce(#node_view, &(), #get_ctx)) + quote!({ + // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } DepKind::ParentDepState => { - quote!(self.#ident.reduce(#node_view, Some(&()), #get_ctx)) + quote!({ + println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); + if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ + Some(#states_changed) + } else{ + None + } + }) } } } } - - 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-macro/src/sorted_slice.rs b/packages/native-core-macro/src/sorted_slice.rs new file mode 100644 index 000000000..fa975767c --- /dev/null +++ b/packages/native-core-macro/src/sorted_slice.rs @@ -0,0 +1,28 @@ +extern crate proc_macro; + +use std::collections::BTreeMap; + +use syn::{ + self, bracketed, + parse::{Parse, ParseStream, Result}, + LitStr, Token, +}; +pub struct StrSlice { + pub map: BTreeMap, +} + +impl Parse for StrSlice { + fn parse(input: ParseStream) -> Result { + let content; + bracketed!(content in input); + let mut map = BTreeMap::new(); + while let Ok(s) = content.parse::() { + map.insert(s.value(), s); + #[allow(unused_must_use)] + { + content.parse::(); + } + } + Ok(StrSlice { map }) + } +} diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs similarity index 100% rename from packages/native-core/tests/change_nodes.rs rename to packages/native-core-macro/tests/change_nodes.rs diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs similarity index 99% rename from packages/native-core/tests/initial_build.rs rename to packages/native-core-macro/tests/initial_build.rs index 750987a29..e6bd651e2 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/packages/native-core-macro/tests/initial_build.rs @@ -8,7 +8,7 @@ use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::State; use dioxus_native_core_macro::State; -#[derive(State, Default, Clone)] +#[derive(Default, Clone, State)] struct Empty {} #[test] diff --git a/packages/native-core-macro/tests/parse.rs b/packages/native-core-macro/tests/parse.rs new file mode 100644 index 000000000..6103015e7 --- /dev/null +++ b/packages/native-core-macro/tests/parse.rs @@ -0,0 +1,74 @@ +use dioxus_native_core::node_ref::*; +use dioxus_native_core::state::*; +use dioxus_native_core_macro::*; + +#[derive(State, Default, Clone)] +#[allow(dead_code)] +struct Z { + // depends on text, the C component of it's parent and a u16 context + #[parent_dep_state(c, u16)] + d: D, + // depends on just attributes and no context + #[node_dep_state()] + a: A, + // depends on the B component of children and i32 context + #[child_dep_state(b, i32)] + b: B, + // depends on the C component of it's parent and a u8 context + #[parent_dep_state(c, u8)] + c: C, + // this will remain uneffected on updates + n: i32, +} + +use dioxus_native_core::state::NodeDepState; + +#[derive(Default, Clone, Debug)] +struct A; +impl NodeDepState for A { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, false); + fn reduce(&mut self, _: NodeView, _: &Self::DepState, _: &()) -> bool { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct B; +impl ChildDepState for B { + type Ctx = i32; + type DepState = Self; + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &i32, + ) -> bool + where + Self::DepState: 'a, + { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct C; +impl ParentDepState for C { + type Ctx = u8; + type DepState = Self; + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u8) -> bool { + todo!() + } +} + +#[derive(Default, Clone, Debug)] +struct D; +impl ParentDepState for D { + type Ctx = u16; + type DepState = C; + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false, true); + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u16) -> bool { + todo!() + } +} diff --git a/packages/native-core-macro/tests/test.rs b/packages/native-core-macro/tests/test.rs new file mode 100644 index 000000000..ea0231cd7 --- /dev/null +++ b/packages/native-core-macro/tests/test.rs @@ -0,0 +1,70 @@ +use dioxus_native_core::node_ref::*; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core_macro::State; +#[derive(Debug, Clone, PartialEq, Default)] +struct BubbledUpStateTester(Option, Vec>); +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, + 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, Option>); +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(Debug, Clone, PartialEq, Default)] +struct NodeStateTester(Option, Vec<(String, String)>); +impl NodeDepState for NodeStateTester { + type Ctx = u32; + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); + fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, 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 + } +} + +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(bubbled, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(pushed, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(NONE, u32)] + node: NodeStateTester, +} diff --git a/packages/native-core/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs similarity index 75% rename from packages/native-core/tests/update_state.rs rename to packages/native-core-macro/tests/update_state.rs index d4825fdd5..d6e5e22b7 100644 --- a/packages/native-core/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -3,17 +3,16 @@ use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; +use dioxus_native_core::node_ref::*; use dioxus_native_core::real_dom::*; -use dioxus_native_core::state::{ - AttributeMask, ChildDepState, NodeDepState, NodeMask, NodeView, ParentDepState, State, -}; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core_macro::State; #[derive(Debug, Clone, Default, State)] struct CallCounterState { - #[child_dep_state(ChildDepCallCounter)] + #[child_dep_state(child_counter)] child_counter: ChildDepCallCounter, - #[parent_dep_state(ParentDepCallCounter)] + #[parent_dep_state(parent_counter)] parent_counter: ParentDepCallCounter, #[node_dep_state()] node_counter: NodeDepCallCounter, @@ -60,8 +59,9 @@ impl ParentDepState for ParentDepCallCounter { struct NodeDepCallCounter(u32); impl NodeDepState for NodeDepCallCounter { type Ctx = (); + type DepState = (); const NODE_MASK: NodeMask = NodeMask::ALL; - fn reduce(&mut self, _node: NodeView, _ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { self.0 += 1; true } @@ -107,22 +107,13 @@ impl ParentDepState for PushedDownStateTester { } } -#[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, Vec<(String, String)>); impl NodeDepState for NodeStateTester { type Ctx = u32; + type DepState = (); const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); - fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool { + fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( node.tag().map(|s| s.to_string()), @@ -134,6 +125,16 @@ impl NodeDepState for NodeStateTester { } } +#[derive(State, Clone, Default, Debug)] +struct StateTester { + #[child_dep_state(bubbled, u32)] + bubbled: BubbledUpStateTester, + #[parent_dep_state(pushed, u32)] + pushed: PushedDownStateTester, + #[node_dep_state(NONE, u32)] + node: NodeStateTester, +} + #[test] fn state_initial() { #[allow(non_snake_case)] @@ -172,7 +173,10 @@ fn state_initial() { ] ); assert_eq!(root_div.state.pushed.0, Some("div".to_string())); - assert_eq!(root_div.state.pushed.1, None); + assert_eq!( + root_div.state.pushed.1, + Some(Box::new(PushedDownStateTester(None, None))) + ); assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.1, vec![]); @@ -184,7 +188,7 @@ fn state_initial() { child_p.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), - None + Some(Box::new(PushedDownStateTester(None, None))) ))) ); assert_eq!(child_p.state.node.0, Some("p".to_string())); @@ -201,7 +205,7 @@ fn state_initial() { child_h1.state.pushed.1, Some(Box::new(PushedDownStateTester( Some("div".to_string()), - None + Some(Box::new(PushedDownStateTester(None, None))) ))) ); assert_eq!(child_h1.state.node.0, Some("h1".to_string())); @@ -256,10 +260,6 @@ fn state_reduce_initally_called_minimally() { 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); @@ -268,7 +268,7 @@ fn state_reduce_initally_called_minimally() { } #[test] -fn state_reduce_down_called_minimally_on_update() { +fn state_reduce_parent_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { @@ -329,21 +329,20 @@ fn state_reduce_down_called_minimally_on_update() { 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() { +fn state_reduce_child_called_minimally_on_update() { #[allow(non_snake_case)] fn Base(cx: Scope) -> Element { rsx!(cx, div { - width: "100%", div{ div{ - p{} + p{ + width: "100%", + } } p{ "hello" @@ -362,10 +361,11 @@ fn state_reduce_up_called_minimally_on_update() { let mutations = vdom.create_vnodes(rsx! { div { - width: "100%", div{ div{ - p{} + p{ + width: "100%", + } } p{ "hello" @@ -397,6 +397,93 @@ fn state_reduce_up_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { + println!("{:?}", n); assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); } + +#[derive(Debug, Clone, Default, State)] +struct UnorderedDependanciesState { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct ADepCallCounter(usize, BDepCallCounter); +impl NodeDepState for ADepCallCounter { + type Ctx = (); + type DepState = BDepCallCounter; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct BDepCallCounter(usize, CDepCallCounter); +impl NodeDepState for BDepCallCounter { + type Ctx = (); + type DepState = CDepCallCounter; + const NODE_MASK: NodeMask = NodeMask::NONE; + fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + self.1 = sibling.clone(); + true + } +} + +#[derive(Debug, Clone, Default, PartialEq)] +struct CDepCallCounter(usize); +impl NodeDepState for CDepCallCounter { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = NodeMask::ALL; + fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + self.0 += 1; + true + } +} + +#[test] +fn dependancies_order_independant() { + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div { + width: "100%", + p{ + "hello" + } + }) + } + + let vdom = VirtualDom::new(Base); + + let mutations = vdom.create_vnodes(rsx! { + div { + width: "100%", + p{ + "hello" + } + } + }); + + let mut dom: RealDom = RealDom::new(); + + let nodes_updated = dom.apply_mutations(vec![mutations]); + let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); + + let c = CDepCallCounter(1); + let b = BDepCallCounter(1, c.clone()); + let a = ADepCallCounter(1, b.clone()); + dom.traverse_depth_first(|n| { + assert_eq!(&n.state.a, &a); + assert_eq!(&n.state.b, &b); + assert_eq!(&n.state.c, &c); + }); +} diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 3e37d7e8a..ab0e4f6f8 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -10,7 +10,6 @@ homepage = "https://dioxuslabs.com" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", 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" } smallvec = "1.6" diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 7692a5ae1..a6e011fa9 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,4 +1,4 @@ pub mod layout_attributes; +pub mod node_ref; pub mod real_dom; pub mod state; -pub use dioxus_native_core_macro; diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs new file mode 100644 index 000000000..bed0dc9ff --- /dev/null +++ b/packages/native-core/src/node_ref.rs @@ -0,0 +1,212 @@ +use dioxus_core::*; + +use crate::state::union_ordered_iter; + +#[derive(Debug)] +pub struct NodeView<'a> { + inner: &'a VNode<'a>, + mask: NodeMask, +} +impl<'a> NodeView<'a> { + 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, + } + } + + 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> { + 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, + mut other_iter: impl Iterator, + ) -> 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, + } + } +} diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index ecb165c26..496af5797 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,14 +1,17 @@ use anymap::AnyMap; use fxhash::{FxHashMap, FxHashSet}; use std::{ - any::TypeId, collections::VecDeque, ops::{Index, IndexMut}, }; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; -use crate::state::{union_ordered_iter, AttributeMask, NodeMask, State}; +use crate::state::{union_ordered_iter, State}; +use crate::{ + node_ref::{AttributeMask, NodeMask}, + state::MemberId, +}; /// 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. @@ -211,7 +214,7 @@ impl RealDom { #[derive(PartialEq, Clone, Debug)] enum StatesToCheck { All, - Some(Vec), + Some(Vec), } impl StatesToCheck { fn union(&self, other: &Self) -> Self { @@ -294,14 +297,28 @@ impl RealDom { for node_ref in &nodes_updated { let mut changed = false; let node = &mut self[node_ref.id]; - let ids = match &node_ref.to_check { + let mut ids = match &node_ref.to_check { StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask), + // this should only be triggered from the current node, so all members will need to be checked StatesToCheck::Some(_) => unreachable!(), }; - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let node = &mut self[node_ref.id]; let vnode = node.element(vdom); - changed |= node.state.update_node_dep_state(ty, vnode, vdom, &ctx); + if let Some(members_effected) = + node.state.update_node_dep_state(id, vnode, vdom, &ctx) + { + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + changed = true; + } + i += 1; } if changed { to_rerender.insert(node_ref.id); @@ -319,19 +336,30 @@ impl RealDom { } = node_ref; 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 { + let mut ids = match to_check { StatesToCheck::All => node.state.child_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let vnode = node.element(vdom); - if node - .state - .update_child_dep_state(ty, vnode, vdom, &children_state, &ctx) + if let Some(members_effected) = + node.state + .update_child_dep_state(id, vnode, vdom, &children_state, &ctx) { - changed.push(ty); + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + for m in members_effected.child_dep { + changed.push(*m); + } } + i += 1; } if let Some(parent_id) = node.parent { if !changed.is_empty() { @@ -374,19 +402,32 @@ impl RealDom { to_check, } = node_ref; let node = &self[id]; - let ids = match to_check { + let mut ids = match to_check { StatesToCheck::All => node.state.parent_dep_types(&node_mask), StatesToCheck::Some(ids) => ids, }; let mut changed = Vec::new(); let (node, parent) = self.get_node_parent_mut(id).unwrap(); - for ty in ids { + let mut i = 0; + while i < ids.len() { + let id = ids[i]; let vnode = node.element(vdom); let parent = parent.as_deref(); let state = &mut node.state; - if state.update_parent_dep_state(ty, vnode, vdom, parent.map(|n| &n.state), &ctx) { - changed.push(ty); + if let Some(members_effected) = + state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx) + { + debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); + for m in members_effected.node_dep { + if let Err(idx) = ids.binary_search(m) { + ids.insert(idx, *m); + } + } + for m in members_effected.parent_dep { + changed.push(*m); + } } + i += 1; } to_rerender.insert(id); diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 13397837a..5febd0d2c 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,7 +1,9 @@ -use std::{any::TypeId, fmt::Debug}; +use std::fmt::Debug; use anymap::AnyMap; -use dioxus_core::{Attribute, ElementId, VElement, VNode, VText, VirtualDom}; +use dioxus_core::VNode; + +use crate::node_ref::{NodeMask, NodeView}; pub(crate) fn union_ordered_iter( s_iter: impl Iterator, @@ -34,215 +36,6 @@ pub(crate) fn union_ordered_iter( v } -#[derive(Debug)] -pub struct NodeView<'a> { - inner: &'a VNode<'a>, - mask: NodeMask, -} -impl<'a> NodeView<'a> { - 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, - } - } - - 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> { - 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, - mut other_iter: impl Iterator, - ) -> 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. @@ -250,7 +43,8 @@ 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; + /// This must be either a [ChildDepState] or [NodeDepState] + type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce<'a>( &mut self, @@ -269,7 +63,8 @@ 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; + /// This must be either a [ParentDepState] or [NodeDepState] + type DepState; const NODE_MASK: NodeMask = NodeMask::NONE; fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool; } @@ -279,40 +74,93 @@ pub trait ParentDepState { /// Called at most once per update. pub trait NodeDepState { type Ctx; + type DepState: NodeDepState; const NODE_MASK: NodeMask = NodeMask::NONE; - fn reduce(&mut self, node: NodeView, ctx: &Self::Ctx) -> bool; + fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool; +} + +#[derive(Debug)] +pub struct ChildStatesChanged { + pub node_dep: &'static [MemberId], + pub child_dep: &'static [MemberId], +} + +#[derive(Debug)] +pub struct ParentStatesChanged { + pub node_dep: &'static [MemberId], + pub parent_dep: &'static [MemberId], +} + +#[derive(Debug)] +pub struct NodeStatesChanged { + pub node_dep: &'static [MemberId], } pub trait State: Default + Clone { fn update_node_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn child_dep_types(&self, mask: &NodeMask) -> Vec; + fn child_dep_types(&self, mask: &NodeMask) -> Vec; fn update_parent_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, parent: Option<&Self>, ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn parent_dep_types(&self, mask: &NodeMask) -> Vec; + fn parent_dep_types(&self, mask: &NodeMask) -> Vec; fn update_child_dep_state<'a>( &'a mut self, - ty: TypeId, + ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, children: &[&Self], ctx: &AnyMap, - ) -> bool; + ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) - fn node_dep_types(&self, mask: &NodeMask) -> Vec; + fn node_dep_types(&self, mask: &NodeMask) -> Vec; } + +impl ChildDepState for () { + type Ctx = (); + type DepState = (); + fn reduce<'a>( + &mut self, + _: NodeView, + _: impl Iterator, + _: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + false + } +} + +impl ParentDepState for () { + type Ctx = (); + type DepState = (); + fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool { + false + } +} + +impl NodeDepState for () { + type Ctx = (); + type DepState = (); + fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { + false + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct MemberId(pub usize); diff --git a/packages/native-core/tests/parse.rs b/packages/native-core/tests/parse.rs deleted file mode 100644 index a035faf4b..000000000 --- a/packages/native-core/tests/parse.rs +++ /dev/null @@ -1,59 +0,0 @@ -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, -} - -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, - _: &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!() - } -} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 43064bb63..58d71db66 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -16,6 +16,7 @@ license = "MIT/Apache-2.0" dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } +dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } tui = "0.17.0" crossterm = "0.23.0" diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index de4f13569..45e279f45 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -2,9 +2,10 @@ use std::cell::RefCell; use std::rc::Rc; use dioxus_core::*; -use dioxus_native_core::dioxus_native_core_macro::sorted_str_slice; use dioxus_native_core::layout_attributes::apply_layout_attributes; -use dioxus_native_core::state::{AttributeMask, ChildDepState, NodeMask, NodeView}; +use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; +use dioxus_native_core::state::ChildDepState; +use dioxus_native_core_macro::sorted_str_slice; use stretch2::prelude::*; #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 1eb3f7612..91e75e8db 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,8 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{dioxus_native_core_macro::State, real_dom::RealDom, state::*}; +use dioxus_native_core::{real_dom::RealDom, state::*}; +use dioxus_native_core_macro::State; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, @@ -36,10 +37,10 @@ type Node = dioxus_native_core::real_dom::Node; #[derive(Debug, Clone, State, Default)] struct NodeState { - #[child_dep_state(StretchLayout, RefCell)] + #[child_dep_state(layout, RefCell)] layout: StretchLayout, // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(StyleModifier)] + #[parent_dep_state(style)] style: StyleModifier, } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index b676d3409..7bd228f8b 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -32,8 +32,10 @@ use dioxus_core::Attribute; use dioxus_native_core::{ layout_attributes::{parse_value, UnitSystem}, - state::{AttributeMask, NodeMask, NodeView, ParentDepState}, + node_ref::{AttributeMask, NodeMask, NodeView}, + state::ParentDepState, }; +use dioxus_native_core_macro::sorted_str_slice; use crate::style::{RinkColor, RinkStyle}; @@ -47,7 +49,8 @@ impl ParentDepState for StyleModifier { 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); + const NODE_MASK: NodeMask = + NodeMask::new(AttributeMask::Static(SORTED_STYLE_ATTRS), true, true, false); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { let mut new = StyleModifier::default(); @@ -586,3 +589,211 @@ fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) { todo!() } + +const SORTED_STYLE_ATTRS: &'static [&'static str] = &sorted_str_slice!([ + "animation", + "animation-delay", + "animation-direction", + "animation-duration", + "animation-fill-mode", + "animation-iteration-count", + "animation-name", + "animation-play-state", + "animation-timing-function", + "backface-visibility", + "background", + "background-attachment", + "background-clip", + "background-color", + "background-image", + "background-origin", + "background-position", + "background-repeat", + "background-size", + "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", + "color", + "columns", + "content", + "counter-increment", + "counter-reset", + "cursor", + "empty-cells", + "float", + "font", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "letter-spacing", + "line-height", + "list-style", + "list-style-image", + "list-style-position", + "list-style-type", + "opacity", + "order", + "outline", + "outline-color", + "outline-offset", + "outline-style", + "outline-width", + "page-break-after", + "page-break-before", + "page-break-inside", + "perspective", + "perspective-origin", + "pointer-events", + "quotes", + "resize", + "tab-size", + "table-layout", + "text-align", + "text-align-last", + "text-decoration", + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-indent", + "text-justify", + "text-overflow", + "text-shadow", + "text-transform", + "transition", + "transition-delay", + "transition-duration", + "transition-property", + "transition-timing-function", + "visibility", + "white-space", + "background-color", + "background", + "background-attachment", + "background-clip", + "background-image", + "background-origin", + "background-position", + "background-repeat", + "background-size", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "none", + "hidden", + "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", + " ", + "animation", + "animation-delay", + "animation-direction", + "animation-duration", + "animation-fill-mode", + "animation-itera ", + "animation-name", + "animation-play-state", + "animation-timing-function", + "font", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "italic", + "oblique", + "font-variant", + "font-weight", + "bold", + "normal", + "text-align", + "text-align-last", + "text-decoration", + "text-decoration-line", + "line-through", + "underline", + "text-decoration-color", + "text-decoration-style", + "text-indent", + "text-justify", + "text-overflow", + "text-shadow", + "text-transform" +]); From b9c266408945061568417450c7524ed7dbe1a199 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 16 Apr 2022 17:22:03 -0500 Subject: [PATCH 23/33] add builder methods to NodeMask --- packages/native-core-macro/tests/parse.rs | 74 ------------------- packages/native-core-macro/tests/test.rs | 70 ------------------ .../native-core-macro/tests/update_state.rs | 6 +- packages/native-core/src/node_ref.rs | 48 +++++++++--- packages/native-core/src/real_dom.rs | 9 +-- packages/tui/src/layout.rs | 8 +- packages/tui/src/style_attributes.rs | 2 +- 7 files changed, 45 insertions(+), 172 deletions(-) delete mode 100644 packages/native-core-macro/tests/parse.rs delete mode 100644 packages/native-core-macro/tests/test.rs diff --git a/packages/native-core-macro/tests/parse.rs b/packages/native-core-macro/tests/parse.rs deleted file mode 100644 index 6103015e7..000000000 --- a/packages/native-core-macro/tests/parse.rs +++ /dev/null @@ -1,74 +0,0 @@ -use dioxus_native_core::node_ref::*; -use dioxus_native_core::state::*; -use dioxus_native_core_macro::*; - -#[derive(State, Default, Clone)] -#[allow(dead_code)] -struct Z { - // depends on text, the C component of it's parent and a u16 context - #[parent_dep_state(c, u16)] - d: D, - // depends on just attributes and no context - #[node_dep_state()] - a: A, - // depends on the B component of children and i32 context - #[child_dep_state(b, i32)] - b: B, - // depends on the C component of it's parent and a u8 context - #[parent_dep_state(c, u8)] - c: C, - // this will remain uneffected on updates - n: i32, -} - -use dioxus_native_core::state::NodeDepState; - -#[derive(Default, Clone, Debug)] -struct A; -impl NodeDepState for A { - type Ctx = (); - type DepState = (); - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, false, false, false); - fn reduce(&mut self, _: NodeView, _: &Self::DepState, _: &()) -> bool { - todo!() - } -} - -#[derive(Default, Clone, Debug)] -struct B; -impl ChildDepState for B { - type Ctx = i32; - type DepState = Self; - fn reduce<'a>( - &mut self, - _: NodeView, - _: impl Iterator, - _: &i32, - ) -> bool - where - Self::DepState: 'a, - { - todo!() - } -} - -#[derive(Default, Clone, Debug)] -struct C; -impl ParentDepState for C { - type Ctx = u8; - type DepState = Self; - fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u8) -> bool { - todo!() - } -} - -#[derive(Default, Clone, Debug)] -struct D; -impl ParentDepState for D { - type Ctx = u16; - type DepState = C; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, false, false, true); - fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &u16) -> bool { - todo!() - } -} diff --git a/packages/native-core-macro/tests/test.rs b/packages/native-core-macro/tests/test.rs deleted file mode 100644 index ea0231cd7..000000000 --- a/packages/native-core-macro/tests/test.rs +++ /dev/null @@ -1,70 +0,0 @@ -use dioxus_native_core::node_ref::*; -use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; -use dioxus_native_core_macro::State; -#[derive(Debug, Clone, PartialEq, Default)] -struct BubbledUpStateTester(Option, Vec>); -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, - 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, Option>); -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(Debug, Clone, PartialEq, Default)] -struct NodeStateTester(Option, Vec<(String, String)>); -impl NodeDepState for NodeStateTester { - type Ctx = u32; - type DepState = (); - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); - fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, 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 - } -} - -#[derive(State, Clone, Default, Debug)] -struct StateTester { - #[child_dep_state(bubbled, u32)] - bubbled: BubbledUpStateTester, - #[parent_dep_state(pushed, u32)] - pushed: PushedDownStateTester, - #[node_dep_state(NONE, u32)] - node: NodeStateTester, -} diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index d6e5e22b7..611fa752e 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -72,7 +72,7 @@ struct BubbledUpStateTester(Option, Vec>); impl ChildDepState for BubbledUpStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); + const NODE_MASK: NodeMask = NodeMask::new().with_tag(); fn reduce<'a>( &mut self, node: NodeView, @@ -96,7 +96,7 @@ struct PushedDownStateTester(Option, Option>) impl ParentDepState for PushedDownStateTester { type Ctx = u32; type DepState = Self; - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::NONE, true, false, false); + const NODE_MASK: NodeMask = NodeMask::new().with_tag(); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = PushedDownStateTester( @@ -112,7 +112,7 @@ struct NodeStateTester(Option, Vec<(String, String)>); impl NodeDepState for NodeStateTester { type Ctx = u32; type DepState = (); - const NODE_MASK: NodeMask = NodeMask::new(AttributeMask::All, true, false, false); + const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag(); fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool { assert_eq!(*ctx, 42); *self = NodeStateTester( diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index bed0dc9ff..bf06368d2 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -181,18 +181,10 @@ pub struct NodeMask { } 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 const NONE: Self = Self::new(); + pub const ALL: Self = Self::new_with_attrs(AttributeMask::All) + .with_text() + .with_element(); pub fn overlaps(&self, other: &Self) -> bool { (self.tag && other.tag) @@ -209,4 +201,36 @@ impl NodeMask { text: self.text | other.text, } } + + pub const fn new_with_attrs(attritutes: AttributeMask) -> Self { + Self { + attritutes, + tag: false, + namespace: false, + text: false, + } + } + + pub const fn new() -> Self { + Self::new_with_attrs(AttributeMask::NONE) + } + + pub const fn with_tag(mut self) -> Self { + self.tag = true; + self + } + + pub const fn with_namespace(mut self) -> Self { + self.namespace = true; + self + } + + pub const fn with_element(self) -> Self { + self.with_namespace().with_tag() + } + + pub const fn with_text(mut self) -> Self { + self.text = true; + self + } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 496af5797..2bfdaa21b 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -169,10 +169,7 @@ impl RealDom { text: new_text, } => { let target = &mut self[root as usize]; - nodes_updated.push(( - root as usize, - NodeMask::new(AttributeMask::NONE, false, false, true), - )); + nodes_updated.push((root as usize, NodeMask::new().with_text())); match &mut target.node_type { NodeType::Text { text } => { *text = new_text.to_string(); @@ -183,7 +180,7 @@ impl RealDom { SetAttribute { root, field, .. } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false, false), + NodeMask::new_with_attrs(AttributeMask::single(field)), )); } RemoveAttribute { @@ -191,7 +188,7 @@ impl RealDom { } => { nodes_updated.push(( root as usize, - NodeMask::new(AttributeMask::single(field), false, false, false), + NodeMask::new_with_attrs(AttributeMask::single(field)), )); } PopRoot {} => { diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 45e279f45..4eb209905 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -37,12 +37,8 @@ impl ChildDepState for StretchLayout { type Ctx = Rc>; type DepState = Self; // todo: update mask - const NODE_MASK: NodeMask = NodeMask::new( - AttributeMask::Static(SORTED_LAYOUT_ATTRS), - false, - false, - true, - ); + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)).with_text(); /// Setup the layout fn reduce<'a>( diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 7bd228f8b..87353bbe0 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -50,7 +50,7 @@ impl ParentDepState for StyleModifier { type DepState = Self; // todo: seperate each attribute into it's own class const NODE_MASK: NodeMask = - NodeMask::new(AttributeMask::Static(SORTED_STYLE_ATTRS), true, true, false); + NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element(); fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { let mut new = StyleModifier::default(); From 47186683892f44aa5d8558b7b3da4134dbab06c1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Apr 2022 07:10:15 -0500 Subject: [PATCH 24/33] add docoumentation for custom_renderer --- docs/reference/src/guide/custom_renderer.md | 442 ++++++++++++++++++++ 1 file changed, 442 insertions(+) diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/reference/src/guide/custom_renderer.md index 577b4f5d3..b60eba8ac 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/reference/src/guide/custom_renderer.md @@ -1 +1,443 @@ # Custom Renderer + +Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer. + +Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `DomEdits` and sending `UserEvents`. + +## The specifics: + +Implementing the renderer is fairly straightforward. The renderer needs to: + +1. Handle the stream of edits generated by updates to the virtual DOM +2. Register listeners and pass events into the virtual DOM's event system + +Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen. + +Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves. + +For reference, check out the javascript interperter or tui renderer as a starting point for your custom renderer. + +## DomEdits + +The "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set: + +```rust +enum DomEdit { + PushRoot, + AppendChildren, + ReplaceWith, + InsertAfter, + InsertBefore, + Remove, + CreateTextNode, + CreateElement, + CreateElementNs, + CreatePlaceholder, + NewEventListener, + RemoveEventListener, + SetText, + SetAttribute, + RemoveAttribute, + PopRoot, +} +``` + +The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack. + + +### An example + +For the sake of understanding, lets consider this example - a very simple UI declaration: + +```rust +rsx!( h1 {"hello world"} ) +``` + +To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container. + +When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this: + +```rust +instructions: [ + PushRoot(Container) +] +stack: [ + ContainerNode, +] +``` + +Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack: + +```rust +instructions: [ + PushRoot(Container), + CreateElement(h1), +] +stack: [ + ContainerNode, + h1, +] +``` +Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit: +```rust +instructions: [ + PushRoot(Container), + CreateElement(h1), + CreateTextNode("hello world") +] +stack: [ + ContainerNode, + h1, + "hello world" +] +``` +Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line. + +```rust +instructions: [ + PushRoot(Container), + CreateElement(h1), + CreateTextNode("hello world"), + AppendChildren(1) +] +stack: [ + ContainerNode, + h1 +] +``` +We call `AppendChildren` again, popping off the h1 node and attaching it to the parent: +```rust +instructions: [ + PushRoot(Container), + CreateElement(h1), + CreateTextNode("hello world"), + AppendChildren(1), + AppendChildren(1) +] +stack: [ + ContainerNode, +] +``` +Finally, the container is popped since we don't need it anymore. +```rust +instructions: [ + PushRoot(Container), + CreateElement(h1), + CreateTextNode("hello world"), + AppendChildren(1), + AppendChildren(1), + PopRoot +] +stack: [] +``` +Over time, our stack looked like this: +```rust +[] +[Container] +[Container, h1] +[Container, h1, "hello world"] +[Container, h1] +[Container] +[] +``` + +Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics. + +Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing. + +It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64. + +Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims IDs of elements when removed. To stay in sync with Dioxus you can use a sparce Vec (Vec>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when a id does not exist. + +This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against. + +## Event loop + +Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too. + +The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is: + +```rust +pub async fn run(&mut self) -> dioxus_core::error::Result<()> { + // Push the body element onto the WebsysDom's stack machine + let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom()); + websys_dom.stack.push(root_node); + + // Rebuild or hydrate the virtualdom + let mutations = self.internal_dom.rebuild(); + websys_dom.apply_mutations(mutations); + + // Wait for updates from the real dom and progress the virtual dom + loop { + let user_input_future = websys_dom.wait_for_event(); + let internal_event_future = self.internal_dom.wait_for_work(); + + match select(user_input_future, internal_event_future).await { + Either::Left((_, _)) => { + let mutations = self.internal_dom.work_with_deadline(|| false); + websys_dom.apply_mutations(mutations); + }, + Either::Right((event, _)) => websys_dom.handle_event(event), + } + + // render + } +} +``` + +It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down. + +```rust +fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { + match event.type_().as_str() { + "keydown" => { + let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap(); + UserEvent::KeyboardEvent(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: "keydown", + // This should be whatever element is focused + element: Some(ElementId(0)), + data: Arc::new(KeyboardData{ + char_code: event.char_code(), + key: event.key(), + key_code: event.key_code(), + alt_key: event.alt_key(), + ctrl_key: event.ctrl_key(), + meta_key: event.meta_key(), + shift_key: event.shift_key(), + locale: "".to_string(), + location: event.location(), + repeat: event.repeat(), + which: event.which(), + }) + }) + } + _ => todo!() + } +} +``` + +## Custom raw elements + +If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. + +These custom elements are defined as unit structs with trait implementations. + +For example, the `div` element is (approximately!) defined as such: + +```rust +struct div; +impl div { + /// Some glorious documentation about the class property. + const TAG_NAME: &'static str = "div"; + const NAME_SPACE: Option<&'static str> = None; + // define the class attribute + pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("class", val, None, false) + } + // more attributes +} +``` + +You've probably noticed that many elements in the `rsx!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro. + +# Native Core + +Renderers take a lot of work. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you. + +## RealDom + +The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to lazily update the state of nodes based on what attributes change. + +### Example + +Let's build a toy renderer with borders, size, and text color. +Before we start lets take a look at an exaple element we can render: +```rust +cx.render(rsx!{ + div{ + color: "red", + p{ + border: "1px solid black", + "hello world" + } + } +}) +``` + +In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node. + +```mermaid +flowchart TB + subgraph context + text_width(text width) + end + subgraph div + state1(state)-->color1(color) + state1(state)-->border1(border) + border1-.->text_width + linkStyle 2 stroke:#5555ff,stroke-width:4px; + state1(state)-->layout_width1(layout width) + end + subgraph p + state2(state)-->color2(color) + color2-.->color1(color) + linkStyle 5 stroke:#0000ff,stroke-width:4px; + state2(state)-->border2(border) + border2-.->text_width + linkStyle 7 stroke:#5555ff,stroke-width:4px; + state2(state)-->layout_width2(layout width) + layout_width1-.->layout_width2 + linkStyle 9 stroke:#aaaaff,stroke-width:4px; + end + subgraph hello world + state3(state)-->color3(color) + color3-.->color2(color) + linkStyle 11 stroke:#0000ff,stroke-width:4px; + state3(state)-->border3(border) + border3-.->text_width + linkStyle 13 stroke:#5555ff,stroke-width:4px; + state3(state)-->layout_width3(layout width) + layout_width2-.->layout_width3 + linkStyle 15 stroke:#aaaaff,stroke-width:4px; + end +``` + +To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState. + +```rust +use dioxus_native_core::node_ref::*; +use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core_macro::{sorted_str_slice, State}; + +#[derive(Default, Copy, Clone)] +struct Size(f32, f32); +// Size only depends on the current node and its children, so it implements ChildDepState +impl ChildDepState for Size { + // Size accepts a font size context + type Ctx = f32; + // Size depends on the Size part of each child + type DepState = Self; + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text(); + fn reduce<'a>( + &mut self, + node: NodeView, + children: impl Iterator, + ctx: &Self::Ctx, + ) -> bool + where + Self::DepState: 'a, + { + let mut width; + let mut height; + if let Some(text) = node.text() { + // if the node has text, use the text to size our object + width = text.len() as f32 * ctx; + height = ctx; + } else { + // otherwise, the size is the maximum size of the children + width = *children + .reduce(|accum, item| if accum >= item.0 { accum } else { item.0 }) + .unwrap_or(0.0)); + height = *children + .reduce(|accum, item| if accum >= item.1 { accum } else { item.1 }) + .unwrap_or(&0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node.attibutes(){ + match a.name{ + "width" => width = a.value.parse().unwrap(), + "height" => height = a.value.parse().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!() + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} +// TextColor only depends on the current node and its parent, so it implements ParentDepState +impl ParentDepState for TextColor { + type Ctx = (); + // TextColor depends on the TextColor part of the parent + type DepState = Self; + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"])); + fn reduce( + &mut self, + node: NodeView, + parent: Option<&Self::DepState>, + _ctx: &Self::Ctx, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node.attributes().next() { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(_) => panic!("unknown color"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some(parent) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct Border(bool); +// TextColor only depends on the current node, so it implements NodeDepState +impl NodeDepState for Border { + type Ctx = (); + // Border does not depended on any other member in the current node + type DepState = (); + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(&["border"])); + fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool { + // check if the node contians a border attribute + let new = Self(node.attributes().next().map(|a| a.name == "border").is_some()); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)] +#[derive(State, Default, Clone)] +struct ToyState { + // the color member of it's parent and no context + #[parent_dep_state(color)] + color: TextColor, + // depends on the node, and no context + #[node_dep_state()] + border: Border, + // depends on the layout_width member of children and f32 context (for text size) + #[child_dep_state(size, f32)] + size: Size, +} +``` + +## Layout +For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style. + +## Conclusion +That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community. \ No newline at end of file From d9276bd64cfe76369ea8affbba0b3b93c28210be Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Apr 2022 07:29:35 -0500 Subject: [PATCH 25/33] clean up code and docs --- docs/reference/src/platforms/tui.md | 2 +- packages/html/src/events.rs | 30 +++++------ packages/tui/src/hooks.rs | 81 +++++++--------------------- packages/tui/src/render.rs | 12 ++--- packages/tui/src/style_attributes.rs | 26 ++++----- packages/tui/tests/margin.rs | 7 --- src/lib.rs | 2 + 7 files changed, 55 insertions(+), 105 deletions(-) diff --git a/docs/reference/src/platforms/tui.md b/docs/reference/src/platforms/tui.md index 82e9c1f2a..2b5a3ef96 100644 --- a/docs/reference/src/platforms/tui.md +++ b/docs/reference/src/platforms/tui.md @@ -50,7 +50,7 @@ To run our app: $ cargo run ``` -Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a Configeration to disable the default quit and use the root TuiContext to quit on your own. +Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own. ```rust // main diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 7e95375cf..d564fbbec 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -398,21 +398,21 @@ pub mod on { pub type ClipboardEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct ClipboardData { // DOMDataTransfer clipboardData } pub type CompositionEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct CompositionData { pub data: String, } pub type KeyboardEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct KeyboardData { pub char_code: u32, @@ -481,12 +481,12 @@ pub mod on { pub type FocusEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct FocusData {/* DOMEventInner: Send + SyncTarget relatedTarget */} pub type FormEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct FormData { pub value: String, pub values: HashMap, @@ -495,7 +495,7 @@ pub mod on { pub type MouseEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct MouseData { pub alt_key: bool, pub button: i16, @@ -514,7 +514,7 @@ pub mod on { pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct PointerData { // Mouse only pub alt_key: bool, @@ -544,12 +544,12 @@ pub mod on { pub type SelectionEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct SelectionData {} pub type TouchEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct TouchData { pub alt_key: bool, pub ctrl_key: bool, @@ -563,7 +563,7 @@ pub mod on { pub type WheelEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct WheelData { pub delta_mode: u32, pub delta_x: f64, @@ -573,19 +573,19 @@ pub mod on { pub type MediaEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct MediaData {} pub type ImageEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct ImageData { pub load_error: bool, } pub type AnimationEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct AnimationData { pub animation_name: String, pub pseudo_element: String, @@ -594,7 +594,7 @@ pub mod on { pub type TransitionEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct TransitionData { pub property_name: String, pub pseudo_element: String, @@ -603,7 +603,7 @@ pub mod on { pub type ToggleEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct ToggleData {} } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index c09eed50b..63b71139a 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -30,12 +30,12 @@ use crate::{Dom, Node}; // pub fn mouse(&self) -> Option { // let data = (**self.0).borrow(); -// data.mouse.as_ref().map(|m| clone_mouse_data(m)) +// data.mouse.as_ref().map(|m| m.clone()) // } // pub fn wheel(&self) -> Option { // let data = (**self.0).borrow(); -// data.wheel.as_ref().map(|w| clone_wheel_data(w)) +// data.wheel.as_ref().map(|w| w.clone()) // } // pub fn screen(&self) -> Option<(u16, u16)> { @@ -47,7 +47,7 @@ use crate::{Dom, Node}; // let data = (**self.0).borrow(); // data.last_key_pressed // .as_ref() -// .map(|k| clone_keyboard_data(&k.0)) +// .map(|k| &k.0.clone()) // } // } @@ -99,7 +99,7 @@ impl InnerInputState { EventData::Mouse(ref mut m) => match &mut self.mouse { Some(state) => { let mut buttons = state.0.buttons; - state.0 = clone_mouse_data(m); + state.0 = m.clone(); match evt.0 { // this code only runs when there are no buttons down "mouseup" => { @@ -135,7 +135,7 @@ impl InnerInputState { } None => { self.mouse = Some(( - clone_mouse_data(m), + m.clone(), if m.buttons == 0 { Vec::new() } else { @@ -144,7 +144,7 @@ impl InnerInputState { )); } }, - EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)), + EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { let repeat = self @@ -153,7 +153,7 @@ impl InnerInputState { .filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME) .is_some(); k.repeat = repeat; - let new = clone_keyboard_data(k); + let new = k.clone(); self.last_key_pressed = Some((new, Instant::now())); } } @@ -166,10 +166,7 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - let previous_mouse = self - .mouse - .as_ref() - .map(|m| (clone_mouse_data(&m.0), m.1.clone())); + let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); self.wheel = None; @@ -269,7 +266,7 @@ impl InnerInputState { if currently_contains && previously_contained { try_create_event( "mousemove", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -293,7 +290,7 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseenter", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -317,7 +314,7 @@ impl InnerInputState { if currently_contains && !previously_contained { try_create_event( "mouseover", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -337,7 +334,7 @@ impl InnerInputState { if currently_contains && data.clicked { try_create_event( "mousedown", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -357,7 +354,7 @@ impl InnerInputState { if currently_contains && data.released { try_create_event( "mouseup", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -377,7 +374,7 @@ impl InnerInputState { if currently_contains && data.released && data.mouse_data.button == 0 { try_create_event( "click", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -397,7 +394,7 @@ impl InnerInputState { if currently_contains && data.released && data.mouse_data.button == 2 { try_create_event( "contextmenu", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -418,7 +415,7 @@ impl InnerInputState { if currently_contains && data.wheel_delta != 0.0 { try_create_event( "wheel", - Arc::new(clone_wheel_data(w)), + Arc::new(w.clone()), &mut will_bubble, resolved_events, node, @@ -443,7 +440,7 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseleave", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -467,7 +464,7 @@ impl InnerInputState { if !currently_contains && previously_contained { try_create_event( "mouseout", - Arc::new(clone_mouse_data(data.mouse_data)), + Arc::new(data.mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -786,45 +783,3 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { which: Default::default(), })) } - -fn clone_mouse_data(m: &MouseData) -> MouseData { - MouseData { - client_x: m.client_x, - client_y: m.client_y, - page_x: m.page_x, - page_y: m.page_y, - screen_x: m.screen_x, - screen_y: m.screen_y, - alt_key: m.alt_key, - ctrl_key: m.ctrl_key, - meta_key: m.meta_key, - shift_key: m.shift_key, - button: m.button, - buttons: m.buttons, - } -} - -fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData { - KeyboardData { - char_code: k.char_code, - key: k.key.clone(), - key_code: k.key_code, - alt_key: k.alt_key, - ctrl_key: k.ctrl_key, - meta_key: k.meta_key, - shift_key: k.shift_key, - locale: k.locale.clone(), - location: k.location, - repeat: k.repeat, - which: k.which, - } -} - -fn clone_wheel_data(w: &WheelData) -> WheelData { - WheelData { - delta_mode: w.delta_mode, - delta_x: w.delta_x, - delta_y: w.delta_y, - delta_z: w.delta_x, - } -} diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index e3f03f0d8..3e75f3a5e 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -55,7 +55,7 @@ pub(crate) fn render_vnode( let label = Label { text, - style: node.state.style.style, + style: node.state.style.core, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -264,7 +264,7 @@ impl RinkWidget for &Node { for x in area.left()..area.right() { for y in area.top()..area.bottom() { let mut new_cell = RinkCell::default(); - if let Some(c) = self.state.style.style.bg { + if let Some(c) = self.state.style.core.bg { new_cell.bg = c; } buf.set(x, y, new_cell); @@ -288,7 +288,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.style.fg); + let color = current_edge.color.or(self.state.style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -323,7 +323,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.style.fg); + let color = current_edge.color.or(self.state.style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -358,7 +358,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.style.fg); + let color = current_edge.color.or(self.state.style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; @@ -393,7 +393,7 @@ impl RinkWidget for &Node { (last_r * RADIUS_MULTIPLIER[0]) as u16, (last_r * RADIUS_MULTIPLIER[1]) as u16, ]; - let color = current_edge.color.or(self.state.style.style.fg); + let color = current_edge.color.or(self.state.style.core.fg); let mut new_cell = RinkCell::default(); if let Some(c) = color { new_cell.fg = c; diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 87353bbe0..c2e0db3fd 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -41,7 +41,7 @@ use crate::style::{RinkColor, RinkStyle}; #[derive(Default, Clone, PartialEq, Debug)] pub struct StyleModifier { - pub style: RinkStyle, + pub core: RinkStyle, pub modifier: TuiModifier, } @@ -55,7 +55,7 @@ impl ParentDepState for StyleModifier { fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool { let mut new = StyleModifier::default(); if parent.is_some() { - new.style.fg = None; + new.core.fg = None; } // handle text modifier elements @@ -84,9 +84,9 @@ impl ParentDepState for StyleModifier { // keep the text styling from the parent element if let Some(parent) = parent { - let mut new_style = new.style.merge(parent.style); - new_style.bg = new.style.bg; - new.style = new_style; + let mut new_style = new.core.merge(parent.core); + new_style.bg = new.core.bg; + new.core = new_style; } if &mut new != self { *self = new; @@ -255,7 +255,7 @@ pub fn apply_style_attributes( "color" => { if let Ok(c) = value.parse() { - style.style.fg.replace(c); + style.core.fg.replace(c); } } @@ -324,7 +324,7 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifier) { match name { "background-color" => { if let Ok(c) = value.parse() { - style.style.bg.replace(c); + style.core.bg.replace(c); } } "background" => {} @@ -546,14 +546,14 @@ fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { "font-size-adjust" => (), "font-stretch" => (), "font-style" => match value { - "italic" => style.style = style.style.add_modifier(Modifier::ITALIC), - "oblique" => style.style = style.style.add_modifier(Modifier::ITALIC), + "italic" => style.core = style.core.add_modifier(Modifier::ITALIC), + "oblique" => style.core = style.core.add_modifier(Modifier::ITALIC), _ => (), }, "font-variant" => todo!(), "font-weight" => match value { - "bold" => style.style = style.style.add_modifier(Modifier::BOLD), - "normal" => style.style = style.style.remove_modifier(Modifier::BOLD), + "bold" => style.core = style.core.add_modifier(Modifier::BOLD), + "normal" => style.core = style.core.remove_modifier(Modifier::BOLD), _ => (), }, _ => (), @@ -569,8 +569,8 @@ fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { "text-decoration" | "text-decoration-line" => { for v in value.split(' ') { match v { - "line-through" => style.style = style.style.add_modifier(Modifier::CROSSED_OUT), - "underline" => style.style = style.style.add_modifier(Modifier::UNDERLINED), + "line-through" => style.core = style.core.add_modifier(Modifier::CROSSED_OUT), + "underline" => style.core = style.core.add_modifier(Modifier::UNDERLINED), _ => (), } } diff --git a/packages/tui/tests/margin.rs b/packages/tui/tests/margin.rs index 44f909d69..53e8978e5 100644 --- a/packages/tui/tests/margin.rs +++ b/packages/tui/tests/margin.rs @@ -90,11 +90,4 @@ fn margin_and_flex_row2() { assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); - - dbg!(stretch.layout(node0)).unwrap(); - - // assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32); - // assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); - // assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32); - // assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); } diff --git a/src/lib.rs b/src/lib.rs index 008769177..6f034cea6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,8 @@ pub use dioxus_tui as tui; #[cfg(feature = "native-core")] pub use dioxus_native_core as native_core; +#[cfg(feature = "native-core")] +pub use dioxus_native_core_macro as native_core_macro; #[cfg(feature = "fermi")] pub use fermi; From e9da4c0fb7f9328b665a60ff022713b56c903dea Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Apr 2022 08:08:40 -0500 Subject: [PATCH 26/33] add todo --- packages/native-core/src/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 5febd0d2c..5fd2c473c 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -130,6 +130,7 @@ pub trait State: Default + Clone { fn node_dep_types(&self, mask: &NodeMask) -> Vec; } +// Todo: once GATs land we can model multable dependencies impl ChildDepState for () { type Ctx = (); type DepState = (); From b79a58cc664afca39e48eb242885ae62da6b5949 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Apr 2022 08:43:15 -0500 Subject: [PATCH 27/33] pass clippy --- packages/native-core-macro/src/lib.rs | 90 +++++++++++++-------------- packages/native-core/src/node_ref.rs | 8 +-- packages/native-core/src/real_dom.rs | 30 +++++---- packages/native-core/src/state.rs | 16 ++--- packages/tui/src/hooks.rs | 2 +- packages/tui/src/layout.rs | 2 +- packages/tui/src/lib.rs | 4 +- packages/tui/src/style_attributes.rs | 2 +- 8 files changed, 73 insertions(+), 81 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 658653c19..61d03b545 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -21,9 +21,9 @@ pub fn sorted_str_slice(input: TokenStream) -> TokenStream { #[derive(PartialEq, Debug, Clone)] enum DepKind { - NodeDepState, - ChildDepState, - ParentDepState, + Node, + Child, + Parent, } #[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] @@ -50,23 +50,23 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let node_dep_state_fields = state_strct .state_members .iter() - .filter(|f| f.dep_kind == DepKind::NodeDepState) + .filter(|f| f.dep_kind == DepKind::Node) .map(|f| f.reduce_self()); let child_dep_state_fields = state_strct .state_members .iter() - .filter(|f| f.dep_kind == DepKind::ChildDepState) + .filter(|f| f.dep_kind == DepKind::Child) .map(|f| f.reduce_self()); let parent_dep_state_fields = state_strct .state_members .iter() - .filter(|f| f.dep_kind == DepKind::ParentDepState) + .filter(|f| f.dep_kind == DepKind::Parent) .map(|f| f.reduce_self()); let node_iter = state_strct .state_members .iter() - .filter(|m| m.dep_kind == DepKind::NodeDepState); + .filter(|m| m.dep_kind == DepKind::Node); let node_ids = node_iter.clone().map(|m| m.member_id.0); let node_ids_clone = node_ids.clone(); let node_types = node_iter.map(|f| &f.mem.ty); @@ -74,7 +74,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let child_iter = state_strct .state_members .iter() - .filter(|m| m.dep_kind == DepKind::ChildDepState); + .filter(|m| m.dep_kind == DepKind::Child); let child_ids = child_iter.clone().map(|m| m.member_id.0); let child_ids_clone = child_ids.clone(); let child_types = child_iter.map(|f| &f.mem.ty); @@ -82,7 +82,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let parent_iter = state_strct .state_members .iter() - .filter(|m| m.dep_kind == DepKind::ParentDepState); + .filter(|m| m.dep_kind == DepKind::Parent); let parent_ids = parent_iter.clone().map(|m| m.member_id.0); let parent_ids_clone = parent_ids.clone(); let parent_types = parent_iter.map(|f| &f.mem.ty); @@ -191,24 +191,22 @@ struct StateStruct<'a> { impl<'a> StateStruct<'a> { fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result { let mut parse_err = Ok(()); - let state_members: Vec<_> = strct + let state_members = strct .members .iter() .zip(fields.iter()) - .filter_map(|(m, f)| match StateMember::parse(f, m, &strct) { + .filter_map(|(m, f)| match StateMember::parse(f, m, strct) { Ok(m) => m, Err(err) => { parse_err = Err(err); None } - }) - .collect(); - parse_err?; + }); #[derive(Debug, Clone)] struct DepNode<'a> { state_mem: StateMember<'a>, - depandants: Vec>>, + depandants: Vec>, } impl<'a> DepNode<'a> { fn new(state_mem: StateMember<'a>) -> Self { @@ -266,15 +264,14 @@ impl<'a> StateStruct<'a> { fn kind(&self) -> Result<&DepKind> { fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> { match (dk1, dk2) { - (DepKind::ChildDepState, DepKind::ParentDepState) - | (DepKind::ParentDepState, DepKind::ChildDepState) => Err(Error::new( - Span::call_site(), - "There is a ChildDepState that depends on a ParentDepState", - )), - // node dep state takes the lowest priority - (DepKind::NodeDepState, important) | (important, DepKind::NodeDepState) => { - Ok(important) + (DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => { + Err(Error::new( + Span::call_site(), + "There is a ChildDepState that depends on a ParentDepState", + )) } + // node dep state takes the lowest priority + (DepKind::Node, important) | (important, DepKind::Node) => Ok(important), // they are the same (fst, _) => Ok(fst), } @@ -282,9 +279,7 @@ impl<'a> StateStruct<'a> { reduce_kind( self.depandants .iter() - .try_fold(&DepKind::NodeDepState, |dk1, dk2| { - reduce_kind(dk1, dk2.kind()?) - })?, + .try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?, &self.state_mem.dep_kind, ) } @@ -293,7 +288,7 @@ impl<'a> StateStruct<'a> { let dep = other.state_mem.dep_mem.unwrap(); if self.contains_member(dep) { if self.state_mem.mem == dep { - self.depandants.push(Box::new(other)); + self.depandants.push(other); true } else { self.depandants @@ -310,7 +305,7 @@ impl<'a> StateStruct<'a> { // members need to be sorted so that members are updated after the members they depend on let mut roots: Vec = vec![]; - for m in state_members.into_iter() { + for m in state_members { if let Some(dep) = m.dep_mem { let root_depends_on = roots .iter() @@ -342,6 +337,7 @@ impl<'a> StateStruct<'a> { } roots.push(new); } + parse_err?; let mut current_id = 0; for r in &mut roots { r.set_ids(&mut current_id); @@ -351,8 +347,7 @@ impl<'a> StateStruct<'a> { } else { let state_members: Vec<_> = roots .into_iter() - .map(|r| r.flatten().into_iter()) - .flatten() + .flat_map(|r| r.flatten().into_iter()) .collect(); Ok(Self { state_members }) @@ -417,13 +412,12 @@ impl<'a> StateMember<'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), + .and_then(|i| match i.to_string().as_str() { + "node_dep_state" => Some(DepKind::Node), + "child_dep_state" => Some(DepKind::Child), + "parent_dep_state" => Some(DepKind::Parent), _ => None, - }) - .flatten()?; + })?; match a.parse_args::() { Ok(dependancy) => { let dep_mem = if let Some(name) = &dependancy.dep { @@ -474,27 +468,27 @@ impl<'a> StateMember<'a> { let child_dep = self .dependants .iter() - .filter(|(_, kind)| kind == &DepKind::ChildDepState) + .filter(|(_, kind)| kind == &DepKind::Child) .map(|(id, _)| id.0); let parent_dep = self .dependants .iter() - .filter(|(_, kind)| kind == &DepKind::ParentDepState) + .filter(|(_, kind)| kind == &DepKind::Parent) .map(|(id, _)| id.0); let node_dep = self .dependants .iter() - .filter(|(_, kind)| kind == &DepKind::NodeDepState) + .filter(|(_, kind)| kind == &DepKind::Node) .map(|(id, _)| id.0); match self.dep_kind { - DepKind::NodeDepState => { + DepKind::Node => { quote! { dioxus_native_core::state::NodeStatesChanged{ node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], } } } - DepKind::ChildDepState => { + DepKind::Child => { quote! { dioxus_native_core::state::ChildStatesChanged{ node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], @@ -502,7 +496,7 @@ impl<'a> StateMember<'a> { } } } - DepKind::ParentDepState => { + DepKind::Parent => { quote! { dioxus_native_core::state::ParentStatesChanged{ node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], @@ -519,7 +513,7 @@ impl<'a> StateMember<'a> { let id = self.member_id.0; if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { match self.dep_kind { - DepKind::NodeDepState => { + DepKind::Node => { quote!({ // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ @@ -529,7 +523,7 @@ impl<'a> StateMember<'a> { } }) } - DepKind::ChildDepState => { + DepKind::Child => { quote!({ // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ @@ -539,7 +533,7 @@ impl<'a> StateMember<'a> { } }) } - DepKind::ParentDepState => { + DepKind::Parent => { quote!({ // println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ @@ -552,7 +546,7 @@ impl<'a> StateMember<'a> { } } else { match self.dep_kind { - DepKind::NodeDepState => { + DepKind::Node => { quote!({ // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &(), #get_ctx){ @@ -562,7 +556,7 @@ impl<'a> StateMember<'a> { } }) } - DepKind::ChildDepState => { + DepKind::Child => { quote!({ // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ @@ -572,7 +566,7 @@ impl<'a> StateMember<'a> { } }) } - DepKind::ParentDepState => { + DepKind::Parent => { quote!({ println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index bf06368d2..3816dd77a 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -30,7 +30,7 @@ impl<'a> NodeView<'a> { pub fn namespace(&self) -> Option<&'a str> { self.mask .namespace - .then(|| self.el().map(|el| el.namespace).flatten()) + .then(|| self.el().and_then(|el| el.namespace)) .flatten() } @@ -39,7 +39,7 @@ impl<'a> NodeView<'a> { .map(|el| el.attributes) .unwrap_or_default() .iter() - .filter(|a| self.mask.attritutes.contains_attribute(&a.name)) + .filter(|a| self.mask.attritutes.contains_attribute(a.name)) } pub fn text(&self) -> Option<&str> { @@ -124,11 +124,11 @@ impl AttributeMask { fn overlaps(&self, other: &Self) -> bool { fn overlaps_iter( - mut self_iter: impl Iterator, + self_iter: impl Iterator, mut other_iter: impl Iterator, ) -> bool { if let Some(mut other_attr) = other_iter.next() { - while let Some(self_attr) = self_iter.next() { + for self_attr in self_iter { while other_attr < self_attr { if let Some(attr) = other_iter.next() { other_attr = attr; diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 2bfdaa21b..7bfb27492 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -390,7 +390,7 @@ impl RealDom { } // push down state. To avoid calling reduce more times than nessisary start from the top and go down. - let mut to_push = nodes_updated.clone(); + let mut to_push = nodes_updated; while let Some(node_ref) = to_push.pop_front() { let NodeRef { id, @@ -528,10 +528,10 @@ impl RealDom { } // this is safe because no node will have itself as a child - pub fn get_node_children_mut<'a>( - &'a mut self, + pub fn get_node_children_mut( + &mut self, id: usize, - ) -> Option<(&'a mut Node, Vec<&'a mut Node>)> { + ) -> Option<(&mut Node, Vec<&mut Node>)> { let ptr = self.nodes.as_mut_ptr(); unsafe { if id >= self.nodes.len() { @@ -546,7 +546,7 @@ impl RealDom { .collect(), _ => Vec::new(), }; - return Some((node, children)); + Some((node, children)) } else { None } @@ -555,24 +555,22 @@ impl RealDom { } // this is safe because no node will have itself as a parent - pub fn get_node_parent_mut<'a>( - &'a mut self, + pub fn get_node_parent_mut( + &mut self, id: usize, - ) -> Option<(&'a mut Node, Option<&'a mut Node>)> { + ) -> Option<(&mut Node, Option<&mut Node>)> { 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()); + Some((node, parent)) } 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 - } + None } } } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 5fd2c473c..9e008d416 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::{cmp::Ordering, fmt::Debug}; use anymap::AnyMap; use dioxus_core::VNode; @@ -14,20 +14,20 @@ pub(crate) fn union_ordered_iter( 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 { + while let Some(o_i) = o_peekable.peek() { + match o_i.cmp(s_i) { + Ordering::Greater => { break; - } else if o_i < s_i { + } + Ordering::Less => { v.push(o_peekable.next().unwrap()); } - } else { - break; + Ordering::Equal => (), } } v.push(s_peekable.next().unwrap()); } - while let Some(o_i) = o_peekable.next() { + for o_i in o_peekable { v.push(o_i); } for w in v.windows(2) { diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 63b71139a..8da04fc5e 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -161,7 +161,7 @@ impl InnerInputState { fn update( &mut self, - evts: &mut Vec, + evts: &mut [EventCore], resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 4eb209905..8258b32ea 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -115,7 +115,7 @@ impl ChildDepState for StretchLayout { } // these are the attributes in layout_attiributes in native-core -const SORTED_LAYOUT_ATTRS: &'static [&'static str] = &sorted_str_slice!([ +const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([ "align-content", "align-items", "align-self", diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 91e75e8db..9f6bd78ed 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -166,7 +166,7 @@ fn render_vdom( // size is guaranteed to not change when rendering resize(frame.size(), &mut stretch.borrow_mut(), &rdom); let root = &rdom[0]; - render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg); + render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg); })?; } else { resize( @@ -226,7 +226,7 @@ fn render_vdom( // update the style and layout let mut any_map = AnyMap::new(); any_map.insert(stretch.clone()); - let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap(); + let _to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap(); } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index c2e0db3fd..b9feab36a 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -590,7 +590,7 @@ fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) { todo!() } -const SORTED_STYLE_ATTRS: &'static [&'static str] = &sorted_str_slice!([ +const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ "animation", "animation-delay", "animation-direction", From 7c30d93a3d016877b7901c5b999a66673fa044d5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Apr 2022 20:12:57 -0500 Subject: [PATCH 28/33] nested state --- docs/reference/src/guide/custom_renderer.md | 44 ++++- packages/native-core-macro/src/lib.rs | 152 ++++++++++++++++-- .../native-core-macro/tests/update_state.rs | 44 +++++ packages/native-core/src/real_dom.rs | 14 +- packages/native-core/src/state.rs | 46 +++++- 5 files changed, 270 insertions(+), 30 deletions(-) diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/reference/src/guide/custom_renderer.md index b60eba8ac..9a73dd7f7 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/reference/src/guide/custom_renderer.md @@ -304,7 +304,7 @@ flowchart TB end ``` -To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState. +To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. ```rust use dioxus_native_core::node_ref::*; @@ -436,6 +436,48 @@ struct ToyState { } ``` +Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed. +```rust +fn main(){ + fn app(cx: Scope) -> Element { + cx.render(rsx!{ + div{ + color: "red", + "hello world" + } + }) + } + let vdom = VirtualDom::new(app); + let rdom: RealDom = RealDom::new(); + + let mutations = dom.rebuild(); + // update the structure of the real_dom tree + let to_update = rdom.apply_mutations(vec![mutations]); + let mut ctx = AnyMap::new(); + // set the font size to 3.3 + ctx.insert(3.3); + // update the ToyState for nodes in the real_dom tree + let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap(); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop{ + let wait = vdom.wait_for_work(); + let mutations = vdom.work_with_deadline(|| false); + let to_update = rdom.apply_mutations(mutations); + let mut ctx = AnyMap::new(); + ctx.insert(3.3); + let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap(); + + // render... + } + }) +} +``` + ## Layout For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style. diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 61d03b545..d645a2398 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -4,6 +4,7 @@ mod sorted_slice; use dioxus_native_core::state::MemberId; use proc_macro::TokenStream; +use quote::format_ident; use quote::{quote, ToTokens, __private::Span}; use sorted_slice::StrSlice; use syn::{ @@ -26,7 +27,10 @@ enum DepKind { Parent, } -#[proc_macro_derive(State, attributes(node_dep_state, child_dep_state, parent_dep_state))] +#[proc_macro_derive( + State, + attributes(node_dep_state, child_dep_state, parent_dep_state, state) +)] pub fn state_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_derive_macro(&ast) @@ -89,8 +93,51 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let type_name_str = type_name.to_string(); + let child_states = &state_strct.child_states; + + let member_size = state_strct.state_members.len(); + + let child_state_ty = child_states.iter().map(|m| &m.ty); + let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect(); + let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| { + let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); + let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); + if i == 0 { + quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1; + const #ident: usize = #member_size + #ty::SIZE;) + } else { + let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); + quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1; + const #ident: usize = #prev_ident + #ty::SIZE;) + } + }); + let sum_idents: Vec<_> = std::iter::once(quote!(#member_size)) + .chain((0..child_states.len()).map(|i| { + let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string()); + quote!(#ident) + })) + .collect(); + + let child_state_ranges: Vec<_> = (0..child_state_ty.len()) + .map(|i| { + let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string()); + let previous = if i == 0 { + quote!(#member_size) + } else { + let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string()); + quote!(#ident) + }; + quote!(#previous..=#current) + }) + .collect(); + let gen = quote! { + #( + #sum_const_declarations + )* impl State for #type_name{ + const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*; + fn update_node_dep_state<'a>( &'a mut self, ty: dioxus_native_core::state::MemberId, @@ -99,10 +146,26 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { ctx: &anymap::AnyMap, ) -> Option{ use dioxus_native_core::state::NodeDepState as _; + use dioxus_native_core::state::State as _; match ty.0{ #( #node_ids => #node_dep_state_fields, )* + #( + #child_state_ranges => { + self.#child_state_idents.update_node_dep_state( + ty - #sum_idents, + node, + vdom, + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + changed + }) + } + )* _ => panic!("{:?} not in {}", ty, #type_name_str), } } @@ -120,6 +183,25 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #( #parent_ids => #parent_dep_state_fields, )* + #( + #child_state_ranges => { + self.#child_state_idents.update_parent_dep_state( + ty - #sum_idents, + node, + vdom, + parent.map(|p| &p.#child_state_idents), + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + for id in &mut changed.parent_dep{ + *id += #sum_idents; + } + changed + }) + } + )* _ => panic!("{:?} not in {}", ty, #type_name_str), } } @@ -129,13 +211,32 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { ty: dioxus_native_core::state::MemberId, node: &'a dioxus_core::VNode<'a>, vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], + children: &Vec<&Self>, ctx: &anymap::AnyMap, ) -> Option{ use dioxus_native_core::state::ChildDepState as _; match ty.0{ #( - #child_ids => {#child_dep_state_fields}, + #child_ids => #child_dep_state_fields, + )* + #( + #child_state_ranges => { + self.#child_state_idents.update_child_dep_state( + ty - #sum_idents, + node, + vdom, + &children.iter().map(|p| &p.#child_state_idents).collect(), + ctx, + ).map(|mut changed|{ + for id in &mut changed.node_dep{ + *id += #sum_idents; + } + for id in &mut changed.child_dep{ + *id += #sum_idents; + } + changed + }) + } )* _ => panic!("{:?} not in {}", ty, #type_name_str), } @@ -146,6 +247,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #child_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } @@ -154,6 +258,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #parent_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } @@ -162,6 +269,9 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { #(if #node_types::NODE_MASK.overlaps(mask) { dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone)); })* + #( + dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents)); + )* dep_types } } @@ -186,6 +296,7 @@ impl Struct { struct StateStruct<'a> { state_members: Vec>, + child_states: Vec<&'a Member>, } impl<'a> StateStruct<'a> { @@ -203,6 +314,20 @@ impl<'a> StateStruct<'a> { } }); + let child_states = strct + .members + .iter() + .zip(fields.iter()) + .filter(|(_, f)| { + f.attrs.iter().any(|a| { + a.path + .get_ident() + .filter(|i| i.to_string().as_str() == "state") + .is_some() + }) + }) + .map(|(m, _)| m); + #[derive(Debug, Clone)] struct DepNode<'a> { state_mem: StateMember<'a>, @@ -350,7 +475,10 @@ impl<'a> StateStruct<'a> { .flat_map(|r| r.flatten().into_iter()) .collect(); - Ok(Self { state_members }) + Ok(Self { + state_members, + child_states: child_states.collect(), + }) } } } @@ -484,23 +612,23 @@ impl<'a> StateMember<'a> { DepKind::Node => { quote! { dioxus_native_core::state::NodeStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], } } } DepKind::Child => { quote! { dioxus_native_core::state::ChildStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], - child_dep: &[#(dioxus_native_core::state::MemberId(#child_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], + child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*], } } } DepKind::Parent => { quote! { dioxus_native_core::state::ParentStatesChanged{ - node_dep: &[#(dioxus_native_core::state::MemberId(#node_dep), )*], - parent_dep: &[#(dioxus_native_core::state::MemberId(#parent_dep), )*], + node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*], + parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*], } } } @@ -515,7 +643,6 @@ impl<'a> StateMember<'a> { match self.dep_kind { DepKind::Node => { quote!({ - // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){ Some(#states_changed) } else{ @@ -525,7 +652,6 @@ impl<'a> StateMember<'a> { } DepKind::Child => { quote!({ - // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){ Some(#states_changed) } else{ @@ -535,7 +661,6 @@ impl<'a> StateMember<'a> { } DepKind::Parent => { quote!({ - // println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){ Some(#states_changed) } else{ @@ -548,7 +673,6 @@ impl<'a> StateMember<'a> { match self.dep_kind { DepKind::Node => { quote!({ - // println!("node: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, &(), #get_ctx){ Some(#states_changed) } else{ @@ -558,7 +682,6 @@ impl<'a> StateMember<'a> { } DepKind::Child => { quote!({ - // println!("child: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){ Some(#states_changed) } else{ @@ -568,7 +691,6 @@ impl<'a> StateMember<'a> { } DepKind::Parent => { quote!({ - println!("parent: {:?} {:?} {:?}", self.#ident, #id, #node_view.id()); if self.#ident.reduce(#node_view, Some(&()), #get_ctx){ Some(#states_changed) } else{ diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 611fa752e..dd0e09f73 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -8,12 +8,36 @@ use dioxus_native_core::real_dom::*; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; use dioxus_native_core_macro::State; +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart1 { + #[child_dep_state(child_counter)] + child_counter: ChildDepCallCounter, +} + +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart2 { + #[parent_dep_state(parent_counter)] + parent_counter: ParentDepCallCounter, +} + +#[derive(Debug, Clone, Default, State)] +struct CallCounterStatePart3 { + #[node_dep_state()] + node_counter: NodeDepCallCounter, +} + #[derive(Debug, Clone, Default, State)] struct CallCounterState { #[child_dep_state(child_counter)] child_counter: ChildDepCallCounter, + #[state] + part2: CallCounterStatePart2, #[parent_dep_state(parent_counter)] parent_counter: ParentDepCallCounter, + #[state] + part1: CallCounterStatePart1, + #[state] + part3: CallCounterStatePart3, #[node_dep_state()] node_counter: NodeDepCallCounter, } @@ -261,8 +285,11 @@ fn state_reduce_initally_called_minimally() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { + assert_eq!(n.state.part1.child_counter.0, 1); assert_eq!(n.state.child_counter.0, 1); + assert_eq!(n.state.part2.parent_counter.0, 1); assert_eq!(n.state.parent_counter.0, 1); + assert_eq!(n.state.part3.node_counter.0, 1); assert_eq!(n.state.node_counter.0, 1); }); } @@ -329,6 +356,7 @@ fn state_reduce_parent_called_minimally_on_update() { let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new()); dom.traverse_depth_first(|n| { + assert_eq!(n.state.part2.parent_counter.0, 2); assert_eq!(n.state.parent_counter.0, 2); }); } @@ -398,6 +426,10 @@ fn state_reduce_child_called_minimally_on_update() { dom.traverse_depth_first(|n| { println!("{:?}", n); + assert_eq!( + n.state.part1.child_counter.0, + if n.id.0 > 4 { 1 } else { 2 } + ); assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 }); }); } @@ -487,3 +519,15 @@ fn dependancies_order_independant() { assert_eq!(&n.state.c, &c); }); } + +#[derive(Clone, Default, State)] +struct DependanciesStateTest { + #[node_dep_state(c)] + b: BDepCallCounter, + #[node_dep_state()] + c: CDepCallCounter, + #[node_dep_state(b)] + a: ADepCallCounter, + #[state] + child: UnorderedDependanciesState, +} diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7bfb27492..b43327713 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -308,7 +308,7 @@ impl RealDom { node.state.update_node_dep_state(id, vnode, vdom, &ctx) { debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); - for m in members_effected.node_dep { + for m in &members_effected.node_dep { if let Err(idx) = ids.binary_search(m) { ids.insert(idx, *m); } @@ -348,12 +348,12 @@ impl RealDom { { debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); for m in members_effected.node_dep { - if let Err(idx) = ids.binary_search(m) { - ids.insert(idx, *m); + if let Err(idx) = ids.binary_search(&m) { + ids.insert(idx, m); } } for m in members_effected.child_dep { - changed.push(*m); + changed.push(m); } } i += 1; @@ -416,12 +416,12 @@ impl RealDom { { debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id)); for m in members_effected.node_dep { - if let Err(idx) = ids.binary_search(m) { - ids.insert(idx, *m); + if let Err(idx) = ids.binary_search(&m) { + ids.insert(idx, m); } } for m in members_effected.parent_dep { - changed.push(*m); + changed.push(m); } } i += 1; diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index 9e008d416..b7c1f59c7 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,4 +1,8 @@ -use std::{cmp::Ordering, fmt::Debug}; +use std::{ + cmp::Ordering, + fmt::Debug, + ops::{Add, AddAssign, Sub, SubAssign}, +}; use anymap::AnyMap; use dioxus_core::VNode; @@ -81,22 +85,24 @@ pub trait NodeDepState { #[derive(Debug)] pub struct ChildStatesChanged { - pub node_dep: &'static [MemberId], - pub child_dep: &'static [MemberId], + pub node_dep: Vec, + pub child_dep: Vec, } #[derive(Debug)] pub struct ParentStatesChanged { - pub node_dep: &'static [MemberId], - pub parent_dep: &'static [MemberId], + pub node_dep: Vec, + pub parent_dep: Vec, } #[derive(Debug)] pub struct NodeStatesChanged { - pub node_dep: &'static [MemberId], + pub node_dep: Vec, } pub trait State: Default + Clone { + const SIZE: usize; + fn update_node_dep_state<'a>( &'a mut self, ty: MemberId, @@ -123,7 +129,7 @@ pub trait State: Default + Clone { ty: MemberId, node: &'a VNode<'a>, vdom: &'a dioxus_core::VirtualDom, - children: &[&Self], + children: &Vec<&Self>, ctx: &AnyMap, ) -> Option; /// This must be a valid resolution order. (no nodes updated before a state they rely on) @@ -165,3 +171,29 @@ impl NodeDepState for () { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct MemberId(pub usize); + +impl Sub for MemberId { + type Output = MemberId; + fn sub(self, rhs: usize) -> Self::Output { + MemberId(self.0 - rhs) + } +} + +impl Add for MemberId { + type Output = MemberId; + fn add(self, rhs: usize) -> Self::Output { + MemberId(self.0 + rhs) + } +} + +impl SubAssign for MemberId { + fn sub_assign(&mut self, rhs: usize) { + *self = *self - rhs; + } +} + +impl AddAssign for MemberId { + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +} From cebf17088bbaa1f75e639a0ac2f10970ebc0e418 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 20 Apr 2022 12:17:16 -0500 Subject: [PATCH 29/33] fixed infinite loop in union_ordered_iter --- packages/native-core-macro/src/lib.rs | 1 - packages/native-core/src/state.rs | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index d645a2398..508754bf7 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -638,7 +638,6 @@ impl<'a> StateMember<'a> { let ty = &self.mem.ty; let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom)); - let id = self.member_id.0; if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) { match self.dep_kind { DepKind::Node => { diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index b7c1f59c7..b55f217d0 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -26,7 +26,10 @@ pub(crate) fn union_ordered_iter( Ordering::Less => { v.push(o_peekable.next().unwrap()); } - Ordering::Equal => (), + Ordering::Equal => { + o_peekable.next(); + break; + } } } v.push(s_peekable.next().unwrap()); From 7b355661694ab91333d7021765ab16417104368e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 21 Apr 2022 15:10:53 -0500 Subject: [PATCH 30/33] allow nodes to depend on the listeners of a node --- packages/native-core/src/node_ref.rs | 13 +++++++++++++ packages/native-core/src/real_dom.rs | 2 ++ 2 files changed, 15 insertions(+) diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 3816dd77a..b1e1b5e1d 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -49,6 +49,10 @@ impl<'a> NodeView<'a> { .flatten() } + pub fn listeners(&self) -> &'a [Listener<'a>] { + self.el().map(|el| el.listeners).unwrap_or_default() + } + fn el(&self) -> Option<&'a VElement<'a>> { if let VNode::Element(el) = &self.inner { Some(el) @@ -178,6 +182,7 @@ pub struct NodeMask { tag: bool, namespace: bool, text: bool, + listeners: bool, } impl NodeMask { @@ -191,6 +196,7 @@ impl NodeMask { || (self.namespace && other.namespace) || self.attritutes.overlaps(&other.attritutes) || (self.text && other.text) + || (self.listeners && other.listeners) } pub fn union(&self, other: &Self) -> Self { @@ -199,6 +205,7 @@ impl NodeMask { tag: self.tag | other.tag, namespace: self.namespace | other.namespace, text: self.text | other.text, + listeners: self.listeners | other.listeners, } } @@ -208,6 +215,7 @@ impl NodeMask { tag: false, namespace: false, text: false, + listeners: false, } } @@ -233,4 +241,9 @@ impl NodeMask { self.text = true; self } + + pub const fn with_listeners(mut self) -> Self { + self.listeners = true; + self + } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index b43327713..4d6964572 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -152,6 +152,7 @@ impl RealDom { scope: _, root, } => { + nodes_updated.push((root as usize, NodeMask::new().with_listeners())); if let Some(v) = self.nodes_listening.get_mut(event_name) { v.insert(root as usize); } else { @@ -161,6 +162,7 @@ impl RealDom { } } RemoveEventListener { root, event } => { + nodes_updated.push((root as usize, NodeMask::new().with_listeners())); let v = self.nodes_listening.get_mut(event).unwrap(); v.remove(&(root as usize)); } From e1120bb64442fab667a35068604513b48b0ee6fd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 May 2022 21:19:11 -0500 Subject: [PATCH 31/33] ensure all nodes have a intialized layout --- packages/tui/src/layout.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 8258b32ea..f56f48955 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -36,9 +36,11 @@ pub(crate) struct StretchLayout { impl ChildDepState for StretchLayout { type Ctx = Rc>; type DepState = Self; - // todo: update mask + // use tag to force this to be called when a node is built const NODE_MASK: NodeMask = - NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)).with_text(); + NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS)) + .with_text() + .with_tag(); /// Setup the layout fn reduce<'a>( @@ -73,6 +75,7 @@ impl ChildDepState for StretchLayout { } else { self.node = PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap()); + changed = true; } } else { // gather up all the styles from the attribute list @@ -104,6 +107,7 @@ impl ChildDepState for StretchLayout { self.node = PossiblyUninitalized::Initialized( stretch.new_node(style, &child_layout).unwrap(), ); + changed = true; } } if self.style != style { From 3adcba5f9387321db6ba38749d21b2b6462c37e7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 2 May 2022 20:25:10 -0500 Subject: [PATCH 32/33] remove empty attributes --- packages/tui/src/style_attributes.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index b9feab36a..cb140cfcf 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -734,8 +734,6 @@ const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ "border-bottom-width", "border-collapse", "border-color", - " ", - " ", "border-image", "border-image-outset", "border-image-repeat", @@ -747,7 +745,6 @@ const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ "border-left-style", "border-left-width", "border-radius", - " ", "border-right", "border-right-color", "border-right-style", @@ -761,7 +758,6 @@ const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([ "border-top-style", "border-top-width", "border-width", - " ", "animation", "animation-delay", "animation-direction", From 0b1e17c830211bc6a8e05b9e631cc57ba2a12aa5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 2 May 2022 20:25:29 -0500 Subject: [PATCH 33/33] add native-core-macro to workspace --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index a1e16bada..32e882a1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ members = [ "packages/tui", "packages/liveview", "packages/native-core", + "packages/native-core-macro", ] [dev-dependencies]