From 8d42191d7ac5877bd6858a43dafa1b4c806bf295 Mon Sep 17 00:00:00 2001 From: Aster Date: Sat, 12 Feb 2022 20:57:13 +0800 Subject: [PATCH 001/105] Add `From` trait to convert web_sys Events to html Data --- .gitignore | 5 +- packages/html/Cargo.toml | 21 +++ packages/html/src/lib.rs | 2 + packages/html/src/web_sys_bind/events.rs | 161 +++++++++++++++++++++++ packages/html/src/web_sys_bind/mod.rs | 1 + packages/web/Cargo.toml | 2 +- packages/web/src/dom.rs | 97 +------------- 7 files changed, 197 insertions(+), 92 deletions(-) create mode 100644 packages/html/src/web_sys_bind/events.rs create mode 100644 packages/html/src/web_sys_bind/mod.rs diff --git a/.gitignore b/.gitignore index 854f5cb26..e093afe8a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ Cargo.lock !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -tarpaulin-report.html \ No newline at end of file +tarpaulin-report.html + +# Jetbrain +.idea/ \ No newline at end of file diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 293d4c465..775fcc015 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -14,7 +14,28 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.1.9" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } +wasm-bindgen = { version = "0.2.79", optional = true } + +[dependencies.web-sys] +optional = true +version = "0.3.56" +features = [ + "TouchEvent", + "MouseEvent", + "InputEvent", + "ClipboardEvent", + "KeyboardEvent", + "TouchEvent", + "WheelEvent", + "AnimationEvent", + "TransitionEvent", + "PointerEvent", + "FocusEvent", + "CompositionEvent", + "ClipboardEvent", +] [features] default = [] serialize = ["serde", "serde_repr"] +wasm-bind = ["web-sys", "wasm-bindgen"] \ No newline at end of file diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 79c00b1fc..1440fd9ae 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -16,6 +16,8 @@ mod elements; mod events; mod global_attributes; +#[cfg(feature = "wasm-bind")] +mod web_sys_bind; pub use elements::*; pub use events::*; diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs new file mode 100644 index 000000000..4b721e45d --- /dev/null +++ b/packages/html/src/web_sys_bind/events.rs @@ -0,0 +1,161 @@ +use crate::on::{ + AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, + TransitionData, WheelData, +}; +use crate::KeyCode; +use wasm_bindgen::JsCast; +use web_sys::{ + AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent, + TransitionEvent, WheelEvent, +}; + +macro_rules! uncheck_convert { + ($t:ty, $d:ty) => { + impl From for $d { + #[inline] + fn from(e: Event) -> Self { + let e: $t = e.unchecked_into(); + Self::from(&e) + } + } + + impl From<&Event> for $d { + #[inline] + fn from(e: &Event) -> Self { + let e: &$t = e.unchecked_ref(); + Self::from(e) + } + } + }; + ($($t:ty => $d:ty),+ $(,)?) => { + $(uncheck_convert!($t, $d);)+ + }; +} + +uncheck_convert![ + CompositionEvent => CompositionData, + KeyboardEvent => KeyboardData, + MouseEvent => MouseData, + TouchEvent => TouchData, + PointerEvent => PointerData, + WheelEvent => WheelData, + AnimationEvent => AnimationData, + TransitionEvent => TransitionData, +]; + +impl From<&CompositionEvent> for CompositionData { + fn from(e: &CompositionEvent) -> Self { + Self { + data: e.data().unwrap_or_default(), + } + } +} + +impl From<&KeyboardEvent> for KeyboardData { + fn from(e: &KeyboardEvent) -> Self { + Self { + alt_key: e.alt_key(), + char_code: e.char_code(), + key: e.key(), + key_code: KeyCode::from_raw_code(e.key_code() as u8), + ctrl_key: e.ctrl_key(), + locale: "not implemented".to_string(), + location: e.location() as usize, + meta_key: e.meta_key(), + repeat: e.repeat(), + shift_key: e.shift_key(), + which: e.which() as usize, + } + } +} + +impl From<&MouseEvent> for MouseData { + fn from(e: &MouseEvent) -> Self { + Self { + alt_key: e.alt_key(), + button: e.button(), + buttons: e.buttons(), + client_x: e.client_x(), + client_y: e.client_y(), + ctrl_key: e.ctrl_key(), + meta_key: e.meta_key(), + screen_x: e.screen_x(), + screen_y: e.screen_y(), + shift_key: e.shift_key(), + page_x: e.page_x(), + page_y: e.page_y(), + } + } +} + +impl From<&TouchEvent> for TouchData { + fn from(e: &TouchEvent) -> Self { + Self { + alt_key: e.alt_key(), + ctrl_key: e.ctrl_key(), + meta_key: e.meta_key(), + shift_key: e.shift_key(), + } + } +} + +impl From<&PointerEvent> for PointerData { + fn from(e: &PointerEvent) -> Self { + Self { + alt_key: e.alt_key(), + button: e.button(), + buttons: e.buttons(), + client_x: e.client_x(), + client_y: e.client_y(), + ctrl_key: e.ctrl_key(), + meta_key: e.meta_key(), + page_x: e.page_x(), + page_y: e.page_y(), + screen_x: e.screen_x(), + screen_y: e.screen_y(), + shift_key: e.shift_key(), + pointer_id: e.pointer_id(), + width: e.width(), + height: e.height(), + pressure: e.pressure(), + tangential_pressure: e.tangential_pressure(), + tilt_x: e.tilt_x(), + tilt_y: e.tilt_y(), + twist: e.twist(), + pointer_type: e.pointer_type(), + is_primary: e.is_primary(), + // get_modifier_state: evt.get_modifier_state(), + } + } +} + +impl From<&WheelEvent> for WheelData { + fn from(e: &WheelEvent) -> Self { + Self { + delta_x: e.delta_x(), + delta_y: e.delta_y(), + delta_z: e.delta_z(), + delta_mode: e.delta_mode(), + } + } +} + +impl From<&AnimationEvent> for AnimationData { + fn from(e: &AnimationEvent) -> Self { + Self { + elapsed_time: e.elapsed_time(), + animation_name: e.animation_name(), + pseudo_element: e.pseudo_element(), + } + } +} + +impl From<&TransitionEvent> for TransitionData { + fn from(e: &TransitionEvent) -> Self { + Self { + elapsed_time: e.elapsed_time(), + property_name: e.property_name(), + pseudo_element: e.pseudo_element(), + } + } +} diff --git a/packages/html/src/web_sys_bind/mod.rs b/packages/html/src/web_sys_bind/mod.rs new file mode 100644 index 000000000..98277fed2 --- /dev/null +++ b/packages/html/src/web_sys_bind/mod.rs @@ -0,0 +1 @@ +mod events; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 7a3baa955..2870640c4 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9" } -dioxus-html = { path = "../html", version = "^0.1.6" } +dioxus-html = { path = "../html", version = "^0.1.6", features = ["wasm-bind"] } js-sys = "0.3.56" wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } lazy_static = "1.4.0" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 48fb25e6e..44f782b62 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -170,7 +170,6 @@ fn virtual_event_from_websys_event( target: Element, ) -> Arc { use dioxus_html::on::*; - use dioxus_html::KeyCode; match event.type_().as_str() { "copy" | "cut" | "paste" => Arc::new(ClipboardData {}), @@ -180,22 +179,7 @@ fn virtual_event_from_websys_event( data: evt.data().unwrap_or_default(), }) } - "keydown" | "keypress" | "keyup" => { - let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap(); - Arc::new(KeyboardData { - alt_key: evt.alt_key(), - char_code: evt.char_code(), - key: evt.key(), - key_code: KeyCode::from_raw_code(evt.key_code() as u8), - ctrl_key: evt.ctrl_key(), - locale: "not implemented".to_string(), - location: evt.location() as usize, - meta_key: evt.meta_key(), - repeat: evt.repeat(), - shift_key: evt.shift_key(), - which: evt.which() as usize, - }) - } + "keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)), "focus" | "blur" => Arc::new(FocusData {}), // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy @@ -272,88 +256,21 @@ fn virtual_event_from_websys_event( "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap(); - Arc::new(MouseData { - alt_key: evt.alt_key(), - button: evt.button(), - buttons: evt.buttons(), - client_x: evt.client_x(), - client_y: evt.client_y(), - ctrl_key: evt.ctrl_key(), - meta_key: evt.meta_key(), - screen_x: evt.screen_x(), - screen_y: evt.screen_y(), - shift_key: evt.shift_key(), - page_x: evt.page_x(), - page_y: evt.page_y(), - }) + Arc::new(MouseData::from(event)) } "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap(); - Arc::new(PointerData { - alt_key: evt.alt_key(), - button: evt.button(), - buttons: evt.buttons(), - client_x: evt.client_x(), - client_y: evt.client_y(), - ctrl_key: evt.ctrl_key(), - meta_key: evt.meta_key(), - page_x: evt.page_x(), - page_y: evt.page_y(), - screen_x: evt.screen_x(), - screen_y: evt.screen_y(), - shift_key: evt.shift_key(), - pointer_id: evt.pointer_id(), - width: evt.width(), - height: evt.height(), - pressure: evt.pressure(), - tangential_pressure: evt.tangential_pressure(), - tilt_x: evt.tilt_x(), - tilt_y: evt.tilt_y(), - twist: evt.twist(), - pointer_type: evt.pointer_type(), - is_primary: evt.is_primary(), - // get_modifier_state: evt.get_modifier_state(), - }) + Arc::new(PointerData::from(event)) } "select" => Arc::new(SelectionData {}), - "touchcancel" | "touchend" | "touchmove" | "touchstart" => { - let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap(); - Arc::new(TouchData { - alt_key: evt.alt_key(), - ctrl_key: evt.ctrl_key(), - meta_key: evt.meta_key(), - shift_key: evt.shift_key(), - }) - } + "touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)), "scroll" => Arc::new(()), - "wheel" => { - let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap(); - Arc::new(WheelData { - delta_x: evt.delta_x(), - delta_y: evt.delta_y(), - delta_z: evt.delta_z(), - delta_mode: evt.delta_mode(), - }) - } + "wheel" => Arc::new(WheelData::from(event)), "animationstart" | "animationend" | "animationiteration" => { - let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap(); - Arc::new(AnimationData { - elapsed_time: evt.elapsed_time(), - animation_name: evt.animation_name(), - pseudo_element: evt.pseudo_element(), - }) - } - "transitionend" => { - let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap(); - Arc::new(TransitionData { - elapsed_time: evt.elapsed_time(), - property_name: evt.property_name(), - pseudo_element: evt.pseudo_element(), - }) + Arc::new(AnimationData::from(event)) } + "transitionend" => Arc::new(TransitionData::from(event)), "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" From 499971e9b3a9cab21e0aec1864177d1840456de6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 18 Mar 2022 10:43:43 -0500 Subject: [PATCH 002/105] 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 003/105] 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 004/105] 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 005/105] 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 006/105] 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 007/105] 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 008/105] 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 009/105] 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 010/105] 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 011/105] 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 012/105] 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 013/105] 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 014/105] 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 015/105] 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 016/105] 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 017/105] 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 018/105] 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 019/105] 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 020/105] 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 021/105] 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 022/105] 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 023/105] 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 024/105] 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 025/105] 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 026/105] 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 027/105] 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 028/105] 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 029/105] 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 030/105] 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 031/105] 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 329b182e2db17511c7cc3e318d4fe793ef9a09b0 Mon Sep 17 00:00:00 2001 From: Aster <192608617@qq.com> Date: Sun, 24 Apr 2022 11:48:15 +0800 Subject: [PATCH 032/105] Add feature gate --- packages/web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 45a3a8ee8..cd8c1d50e 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.2.0" } -dioxus-html = { path = "../html", version = "^0.2.0" } +dioxus-html = { path = "../html", version = "^0.2.0", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0", features = [ "web", ] } From f332ffc5ad7371a52aeaf93a70144222f8880f9f Mon Sep 17 00:00:00 2001 From: Ian Date: Sun, 24 Apr 2022 00:37:46 -0400 Subject: [PATCH 033/105] support starting liveview with props --- .../liveview/src/adapters/axum_adapter.rs | 19 ++++++++++++++++--- .../liveview/src/adapters/warp_adapter.rs | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/liveview/src/adapters/axum_adapter.rs b/packages/liveview/src/adapters/axum_adapter.rs index d85b6da68..349ef5fe4 100644 --- a/packages/liveview/src/adapters/axum_adapter.rs +++ b/packages/liveview/src/adapters/axum_adapter.rs @@ -12,18 +12,31 @@ use tokio_util::task::LocalPoolHandle; #[cfg(feature = "axum")] impl crate::Liveview { pub async fn upgrade(&self, ws: WebSocket, app: fn(Scope) -> Element) { - connect(ws, self.pool.clone(), app).await; + connect(ws, self.pool.clone(), app, ()).await; + } + pub async fn upgrade_with_props(&self, ws: WebSocket, app: fn(Scope) -> Element, props: T) + where + T: Send + Sync + 'static, + { + connect(ws, self.pool.clone(), app, props).await; } } -pub async fn connect(socket: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) { +pub async fn connect( + socket: WebSocket, + pool: LocalPoolHandle, + app: fn(Scope) -> Element, + props: T, +) where + T: Send + Sync + 'static, +{ let (mut user_ws_tx, mut user_ws_rx) = socket.split(); let (event_tx, event_rx) = mpsc::unbounded_channel(); let (edits_tx, edits_rx) = mpsc::unbounded_channel(); let mut edits_rx = UnboundedReceiverStream::new(edits_rx); let mut event_rx = UnboundedReceiverStream::new(event_rx); let vdom_fut = pool.clone().spawn_pinned(move || async move { - let mut vdom = VirtualDom::new(app); + let mut vdom = VirtualDom::new_with_props(app, props); let edits = vdom.rebuild(); let serialized = serde_json::to_string(&edits.edits).unwrap(); edits_tx.send(serialized).unwrap(); diff --git a/packages/liveview/src/adapters/warp_adapter.rs b/packages/liveview/src/adapters/warp_adapter.rs index 5bdcf671f..b73d02861 100644 --- a/packages/liveview/src/adapters/warp_adapter.rs +++ b/packages/liveview/src/adapters/warp_adapter.rs @@ -9,11 +9,20 @@ use warp::ws::{Message, WebSocket}; #[cfg(feature = "warp")] impl crate::Liveview { pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) { - connect(ws, self.pool.clone(), app).await; + connect(ws, self.pool.clone(), app, ()).await; + } + pub async fn upgrade_with_props(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element, props: T) + where + T: Send + Sync + 'static + { + connect(ws, self.pool.clone(), app, props) } } -pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) { +pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element, props: T) +where + T: Send + Sync+ 'static +{ // Use a counter to assign a new unique ID for this user. // Split the socket into a sender and receive of messages. @@ -26,7 +35,7 @@ pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Ele let mut event_rx = UnboundedReceiverStream::new(event_rx); let vdom_fut = pool.spawn_pinned(move || async move { - let mut vdom = VirtualDom::new(app); + let mut vdom = VirtualDom::new_with_props(app, props); let edits = vdom.rebuild(); From f67b71c5a56e15379b01be11729b3dcbfd9199d8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 24 Apr 2022 19:44:20 -0400 Subject: [PATCH 034/105] feat: custom element example --- examples/custom_element.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/custom_element.rs diff --git a/examples/custom_element.rs b/examples/custom_element.rs new file mode 100644 index 000000000..e0b12d87e --- /dev/null +++ b/examples/custom_element.rs @@ -0,0 +1,30 @@ +//! This example shows to wrap a webcomponent / custom element with a component. +//! +//! Oftentimes, a third party library will provide a webcomponent that you want +//! to use in your application. This example shows how to create that custom element +//! directly with the raw_element method on NodeFactory. + +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + let _ = dom.rebuild(); + + let output = dioxus::ssr::render_vdom(&dom); + + println!("{}", output); +} + +fn app(cx: Scope) -> Element { + let nf = NodeFactory::new(&cx); + + let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump()); + + attrs.push(nf.attr("client-id", format_args!("abc123"), None, false)); + + attrs.push(nf.attr("name", format_args!("bob"), None, false)); + + attrs.push(nf.attr("age", format_args!("47"), None, false)); + + Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None)) +} From 70a6f95c8c7f3d617b233e89567812c0372c7976 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 24 Apr 2022 20:47:22 -0400 Subject: [PATCH 035/105] fix: allow fermi atomref to be cloned --- examples/fermi.rs | 25 +++++++++++++++++++++++++ packages/fermi/src/hooks/atom_ref.rs | 11 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/examples/fermi.rs b/examples/fermi.rs index f4391de5e..9f87961b3 100644 --- a/examples/fermi.rs +++ b/examples/fermi.rs @@ -14,6 +14,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { div { "hello {name}!" } Child {} + ChildWithRef{} }) } @@ -27,3 +28,27 @@ fn Child(cx: Scope) -> Element { } }) } + +static NAMES: AtomRef> = |_| vec!["world".to_string()]; + +fn ChildWithRef(cx: Scope) -> Element { + let names = use_atom_ref(&cx, NAMES); + + cx.render(rsx! { + div { + ul { + names.read().iter().map(|f| rsx!{ + li { "hello: {f}" } + }) + } + button { + onclick: move |_| { + let names = names.clone(); + cx.spawn(async move { + names.write().push("asd".to_string()); + }) + } + } + } + }) +} diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs index da9ad9367..f9c7f2869 100644 --- a/packages/fermi/src/hooks/atom_ref.rs +++ b/packages/fermi/src/hooks/atom_ref.rs @@ -34,6 +34,17 @@ pub struct UseAtomRef { scope_id: ScopeId, } +impl Clone for UseAtomRef { + fn clone(&self) -> Self { + Self { + ptr: self.ptr, + value: self.value.clone(), + root: self.root.clone(), + scope_id: self.scope_id, + } + } +} + impl UseAtomRef { pub fn read(&self) -> Ref { self.value.borrow() From 07529e491cf85a5fd81d5c7718c43a41432c9468 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 24 Apr 2022 20:51:52 -0400 Subject: [PATCH 036/105] fix: subscription should apply internally to hook --- examples/fermi.rs | 3 ++- packages/fermi/src/hooks/atom_ref.rs | 40 +++++++++++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/examples/fermi.rs b/examples/fermi.rs index 9f87961b3..79e3da1d6 100644 --- a/examples/fermi.rs +++ b/examples/fermi.rs @@ -47,7 +47,8 @@ fn ChildWithRef(cx: Scope) -> Element { cx.spawn(async move { names.write().push("asd".to_string()); }) - } + }, + "Add name" } } }) diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs index f9c7f2869..fe488ee2f 100644 --- a/packages/fermi/src/hooks/atom_ref.rs +++ b/packages/fermi/src/hooks/atom_ref.rs @@ -16,15 +16,35 @@ use std::{ pub fn use_atom_ref(cx: &ScopeState, atom: AtomRef) -> &UseAtomRef { let root = use_atom_root(cx); - cx.use_hook(|_| { + &cx.use_hook(|_| { root.initialize(atom); - UseAtomRef { - ptr: atom.unique_id(), - root: root.clone(), - scope_id: cx.scope_id(), - value: root.register(atom, cx.scope_id()), - } + ( + UseAtomRef { + ptr: atom.unique_id(), + root: root.clone(), + scope_id: cx.scope_id(), + value: root.register(atom, cx.scope_id()), + }, + AtomRefSubscription { + ptr: atom.unique_id(), + root: root.clone(), + scope_id: cx.scope_id(), + }, + ) }) + .0 +} + +pub struct AtomRefSubscription { + ptr: AtomId, + root: Rc, + scope_id: ScopeId, +} + +impl Drop for AtomRefSubscription { + fn drop(&mut self) { + self.root.unsubscribe(self.ptr, self.scope_id) + } } pub struct UseAtomRef { @@ -65,9 +85,3 @@ impl UseAtomRef { self.root.set(self.ptr, new); } } - -impl Drop for UseAtomRef { - fn drop(&mut self) { - self.root.unsubscribe(self.ptr, self.scope_id) - } -} From 4b3d200c0df3f7dc4f4dfa09f12aa731e84ff36c Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 26 Apr 2022 09:50:19 -0400 Subject: [PATCH 037/105] fixed cargo check issue --- packages/liveview/src/adapters/axum_adapter.rs | 1 - packages/liveview/src/adapters/warp_adapter.rs | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/liveview/src/adapters/axum_adapter.rs b/packages/liveview/src/adapters/axum_adapter.rs index 349ef5fe4..4122950bc 100644 --- a/packages/liveview/src/adapters/axum_adapter.rs +++ b/packages/liveview/src/adapters/axum_adapter.rs @@ -9,7 +9,6 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::task::LocalPoolHandle; -#[cfg(feature = "axum")] impl crate::Liveview { pub async fn upgrade(&self, ws: WebSocket, app: fn(Scope) -> Element) { connect(ws, self.pool.clone(), app, ()).await; diff --git a/packages/liveview/src/adapters/warp_adapter.rs b/packages/liveview/src/adapters/warp_adapter.rs index b73d02861..c6ec9106f 100644 --- a/packages/liveview/src/adapters/warp_adapter.rs +++ b/packages/liveview/src/adapters/warp_adapter.rs @@ -6,20 +6,19 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_util::task::LocalPoolHandle; use warp::ws::{Message, WebSocket}; -#[cfg(feature = "warp")] impl crate::Liveview { pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) { connect(ws, self.pool.clone(), app, ()).await; } - pub async fn upgrade_with_props(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element, props: T) + pub async fn upgrade_with_props(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element, props: T) where T: Send + Sync + 'static { - connect(ws, self.pool.clone(), app, props) + connect(ws, self.pool.clone(), app, props).await; } } -pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element, props: T) +pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element, props: T) where T: Send + Sync+ 'static { From 3b45edacc06fb5a31ad19d2af24ab2f034ea2f31 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 26 Apr 2022 09:54:12 -0400 Subject: [PATCH 038/105] features don't play nice with vscode development --- .../liveview/src/adapters/warp_adapter.rs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/liveview/src/adapters/warp_adapter.rs b/packages/liveview/src/adapters/warp_adapter.rs index c6ec9106f..f3cf3914b 100644 --- a/packages/liveview/src/adapters/warp_adapter.rs +++ b/packages/liveview/src/adapters/warp_adapter.rs @@ -10,17 +10,25 @@ impl crate::Liveview { pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) { connect(ws, self.pool.clone(), app, ()).await; } - pub async fn upgrade_with_props(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element, props: T) - where - T: Send + Sync + 'static + pub async fn upgrade_with_props( + &self, + ws: warp::ws::WebSocket, + app: fn(Scope) -> Element, + props: T, + ) where + T: Send + Sync + 'static, { connect(ws, self.pool.clone(), app, props).await; } } -pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element, props: T) -where - T: Send + Sync+ 'static +pub async fn connect( + ws: WebSocket, + pool: LocalPoolHandle, + app: fn(Scope) -> Element, + props: T, +) where + T: Send + Sync + 'static, { // Use a counter to assign a new unique ID for this user. From eb3fd74bf9a0b3ffc3ea73ada269d6184a4168c3 Mon Sep 17 00:00:00 2001 From: Koptan Date: Tue, 26 Apr 2022 18:50:45 +0200 Subject: [PATCH 039/105] fix: unmarking component as dirty in silent write --- packages/fermi/src/hooks/atom_ref.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs index fe488ee2f..14c98453d 100644 --- a/packages/fermi/src/hooks/atom_ref.rs +++ b/packages/fermi/src/hooks/atom_ref.rs @@ -76,7 +76,6 @@ impl UseAtomRef { } pub fn write_silent(&self) -> RefMut { - self.root.force_update(self.ptr); self.value.borrow_mut() } From 04cb82b31a6bcd1c28b1008a94a6dcb705ebf3d3 Mon Sep 17 00:00:00 2001 From: Koptan Date: Tue, 26 Apr 2022 19:19:13 +0200 Subject: [PATCH 040/105] fix: export useeffect in hooks module --- packages/hooks/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 0c91a7889..1790eff1d 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -16,5 +16,8 @@ pub use usecoroutine::*; mod usefuture; pub use usefuture::*; +mod useeffect; +pub use useeffect::*; + // mod usesuspense; // pub use usesuspense::*; From 1594ac386c7e1c634cd8c9633a4bf413af6fdac4 Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Wed, 27 Apr 2022 07:37:34 +0800 Subject: [PATCH 041/105] Update docs.yml --- .github/workflows/docs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 81b1b64cb..0e19ecaec 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,8 +24,7 @@ jobs: run: cd docs && cd guide && mdbook build -d ../nightly/guide && cd .. && cd reference && mdbook build -d ../nightly/reference && cd .. && - cd router && mdbook build -d ../nightly/router && cd .. && - cd cli && mdbook build -d ../nightly/cli && cd .. + cd router && mdbook build -d ../nightly/router && cd .. - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4.2.3 From 00354c9b6a195ae419d0db3effcb6f793284a310 Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Wed, 27 Apr 2022 07:38:31 +0800 Subject: [PATCH 042/105] Move `cli` docs to cli repo --- docs/cli/.gitignore | 1 - docs/cli/book.toml | 6 -- docs/cli/src/SUMMARY.md | 11 --- docs/cli/src/cmd/README.md | 26 ------ docs/cli/src/cmd/build.md | 47 ----------- docs/cli/src/cmd/clean.md | 18 ----- docs/cli/src/cmd/serve.md | 49 ------------ docs/cli/src/cmd/translate.md | 57 ------------- docs/cli/src/configure.md | 147 ---------------------------------- docs/cli/src/creating.md | 38 --------- docs/cli/src/installation.md | 24 ------ docs/cli/src/introduction.md | 21 ----- 12 files changed, 445 deletions(-) delete mode 100644 docs/cli/.gitignore delete mode 100644 docs/cli/book.toml delete mode 100644 docs/cli/src/SUMMARY.md delete mode 100644 docs/cli/src/cmd/README.md delete mode 100644 docs/cli/src/cmd/build.md delete mode 100644 docs/cli/src/cmd/clean.md delete mode 100644 docs/cli/src/cmd/serve.md delete mode 100644 docs/cli/src/cmd/translate.md delete mode 100644 docs/cli/src/configure.md delete mode 100644 docs/cli/src/creating.md delete mode 100644 docs/cli/src/installation.md delete mode 100644 docs/cli/src/introduction.md diff --git a/docs/cli/.gitignore b/docs/cli/.gitignore deleted file mode 100644 index 7585238ef..000000000 --- a/docs/cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/docs/cli/book.toml b/docs/cli/book.toml deleted file mode 100644 index c5c216fe4..000000000 --- a/docs/cli/book.toml +++ /dev/null @@ -1,6 +0,0 @@ -[book] -authors = ["YuKun Liu"] -language = "en" -multilingual = false -src = "src" -title = "Dioxus Cli" diff --git a/docs/cli/src/SUMMARY.md b/docs/cli/src/SUMMARY.md deleted file mode 100644 index ed7bcbfaf..000000000 --- a/docs/cli/src/SUMMARY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Summary - -- [Introduction](./introduction.md) -- [Installation](./installation.md) -- [Create a Project](./creating.md) -- [Configure Project](./configure.md) -- [Commands](./cmd/README.md) - - [Build](./cmd/build.md) - - [Serve](./cmd/serve.md) - - [Clean](./cmd/clean.md) - - [Translate](./cmd/translate.md) \ No newline at end of file diff --git a/docs/cli/src/cmd/README.md b/docs/cli/src/cmd/README.md deleted file mode 100644 index c642b475f..000000000 --- a/docs/cli/src/cmd/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Commands - -In this chapter we will introduce all `dioxus-cli` commands. - -> you can also use `dioxus --help` to get cli help info. - -``` -dioxus -Build, bundle, & ship your Dioxus app - -USAGE: - dioxus [OPTIONS] - -OPTIONS: - -h, --help Print help information - -v Enable verbose logging - -SUBCOMMANDS: - build Build the Rust WASM app and all of its assets - clean Clean output artifacts - config Dioxus config file controls - create Init a new project for Dioxus - help Print this message or the help of the given subcommand(s) - serve Build, watch & serve the Rust WASM app and all of its assets - translate Translate some source file into Dioxus code -``` \ No newline at end of file diff --git a/docs/cli/src/cmd/build.md b/docs/cli/src/cmd/build.md deleted file mode 100644 index e81973f2d..000000000 --- a/docs/cli/src/cmd/build.md +++ /dev/null @@ -1,47 +0,0 @@ -# Build - -The `dioxus build` command can help you `pack & build` a dioxus project. - -``` -dioxus-build -Build the Rust WASM app and all of its assets - -USAGE: - dioxus build [OPTIONS] - -OPTIONS: - --example [default: ""] - --platform [default: "default_platform"] - --release [default: false] -``` - -You can use this command to build project to `out_dir` : - -``` -dioxus build --release -``` - -## Target platform - -Use option `platform` choose build target platform: - -``` -# for desktop project -dioxus build --platform desktop -``` - -`platform` only supports `desktop` & `web`. - -``` -# for web project -dioxus build --platform web -``` - -## Build Example - -You can use `--example {name}` to build a example code. - -``` -# build `example/test` code -dioxus build --exmaple test -``` \ No newline at end of file diff --git a/docs/cli/src/cmd/clean.md b/docs/cli/src/cmd/clean.md deleted file mode 100644 index 2f889c1a8..000000000 --- a/docs/cli/src/cmd/clean.md +++ /dev/null @@ -1,18 +0,0 @@ -# Clean - -`dioxus clean` will call `target clean` and remove `out_dir` directory. - -``` -dioxus-clean -Clean output artifacts - -USAGE: - dioxus clean -``` - -you can use this command to clean all build cache and the `out_dir` content. - -``` -dioxus clean -``` - diff --git a/docs/cli/src/cmd/serve.md b/docs/cli/src/cmd/serve.md deleted file mode 100644 index e5c10a902..000000000 --- a/docs/cli/src/cmd/serve.md +++ /dev/null @@ -1,49 +0,0 @@ -# Serve - -The `dioxus serve` can start a dev server (include hot-reload tool) to run the project. - -``` -dioxus-serve -Build, watch & serve the Rust WASM app and all of its assets - -USAGE: - dioxus serve [OPTIONS] - -OPTIONS: - --example [default: ""] - --platform [default: "default_platform"] - --release [default: false] -``` - -You can use this command to build project and start a `dev server` : - -``` -dioxus serve -``` - -## Target platform - -Use option `platform` choose build target platform: - -``` -# for desktop project -dioxus serve --platform desktop -``` - -`platform` only supports `desktop` & `web`. - -`dev-server` only for `web` project. - -``` -# for web project -dioxus serve --platform web -``` - -## Serve Example - -You can use `--example {name}` to start a example code. - -``` -# build `example/test` code -dioxus serve --exmaple test -``` \ No newline at end of file diff --git a/docs/cli/src/cmd/translate.md b/docs/cli/src/cmd/translate.md deleted file mode 100644 index 415a88fba..000000000 --- a/docs/cli/src/cmd/translate.md +++ /dev/null @@ -1,57 +0,0 @@ -# Translate - -`dioxus translate` can translate some source file into Dioxus code. - -``` -dioxus-translate -Translate some source file into Dioxus code - -USAGE: - dioxus translate [OPTIONS] [OUTPUT] - -ARGS: - Output file, stdout if not present - -OPTIONS: - -c, --component Activate debug mode - -f, --file Input file -``` - -## Translate HTML to stdout - -``` -dioxus transtale --file ./index.html -``` - -## Output in a file - -``` -dioxus translate --component --file ./index.html component.rsx -``` - -set `component` flag will wrap `dioxus rsx` code in a component function. - -## Example - -```html -
-

Hello World

- Link -
-``` - -Translate HTML to Dioxus component code. - -```rust -fn component(cx: Scope) -> Element { - cx.render(rsx! { - div { - h1 { "Hello World" }, - a { - href: "https://dioxuslabs.com/", - "Link" - } - } - }) -} -``` \ No newline at end of file diff --git a/docs/cli/src/configure.md b/docs/cli/src/configure.md deleted file mode 100644 index f274ce70d..000000000 --- a/docs/cli/src/configure.md +++ /dev/null @@ -1,147 +0,0 @@ -# Configure Project - -This chapter will introduce `Dioxus.toml` and anatomy the config file. - -## Structure - -We use `toml` to define some info for `dioxus` project. - -### Application - -1. ***name*** - project name & title -2. ***default_platform*** - which platform target for this project. - ``` - # current support: web, desktop - # default: web - default_platform = "web" - ``` - change this to `desktop`, the `dioxus build & serve` will default build desktop app. -3. ***out_dir*** - which directory to put the output file; use `dioxus build & service`, the output will put at this directory, and the `assets` will be also copy to here. - ``` - out_dir = "dist" - ``` -4. ***asset_dir*** - which direcotry to put your `static, assets` file, cli will automatic copy all file to `out_dir`, so you can put some resource file in there, like `CSS, JS, Image` file. - ``` - asset_dir = "public" - ``` - -### Web.App - -Web platform application config: - -1. ***title*** - this value will display on the web page title. like `` tag. - ``` - # HTML title tag content - title = "dioxus app | ⛺" - ``` - -### Web.Watcher - -Web platform `dev-server` watcher config: - -1. ***reload_html*** - a boolean value; when watcher trigger, regenerate `index.html` file. - ``` - # when watcher trigger, regenerate the `index.html` - reload_html = true - ``` -2. ***watch_path*** - which files & directories will be watcher monitoring. - ``` - watch_path = ["src", "public"] - ``` - -### Web.Resource - -Include some `CSS Javascript` resources into html file. - -1. ***style*** - include some style(CSS) file into html. - ``` - style = [ - # include from public_dir. - "./assets/style.css", - # or some asset from online cdn. - "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" - ] - ``` -2. ***script*** - include some script(JS) file into html. - ``` - style = [ - # include from public_dir. - "./assets/index.js", - # or some asset from online cdn. - "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js" - ] - ``` - -### Web.Resource.Dev - -Only include resources at `Dev` mode. - -1. ***style*** - include some style(CSS) file into html. - ``` - style = [ - # include from public_dir. - "./assets/style.css", - # or some asset from online cdn. - "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" - ] - ``` -2. ***script*** - include some script(JS) file into html. - ``` - style = [ - # include from public_dir. - "./assets/index.js", - # or some asset from online cdn. - "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js" - ] - ``` - -## Config example - -```toml -[application] - -# App (Project) Name -name = "{{project-name}}" - -# Dioxus App Default Platform -# desktop, web, mobile, ssr -default_platform = "web" - -# `build` & `serve` dist path -out_dir = "dist" - -# resource (public) file folder -asset_dir = "public" - -[web.app] - -# HTML title tag content -title = "dioxus | ⛺" - -[web.watcher] - -# when watcher trigger, regenerate the `index.html` -reload_html = true - -# which files or dirs will be watcher monitoring -watch_path = ["src", "public"] - -# include `assets` in web platform -[web.resource] - -# CSS style file -style = [] - -# Javascript code file -script = [] - -[web.resource.dev] - -# serve: [dev-server] only - -# CSS style file -style = [] - -# Javascript code file -script = [] -``` \ No newline at end of file diff --git a/docs/cli/src/creating.md b/docs/cli/src/creating.md deleted file mode 100644 index fa725a4f7..000000000 --- a/docs/cli/src/creating.md +++ /dev/null @@ -1,38 +0,0 @@ -# Create a Project - -Once you have the Dioxus CLI tool installed, you can use it to create dioxus project. - -## Initializing a default project - -The `dioxus create` command will create a new directory containing a project template. -``` -dioxus create hello-dioxus -``` - -It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template) - -> This default template is use for `web` platform application. - -then you can change the current directory in to the project: - -``` -cd hello-dioxus -``` - -> Make sure `wasm32 target` is installed before running the Web project. - -now we can create a `dev server` to display the project: - -``` -dioxus serve -``` - -by default, the dioxus dev server will running at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/) - -## Initalizing from other repository - -you can assign which repository you want to create: - -``` -dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template -``` \ No newline at end of file diff --git a/docs/cli/src/installation.md b/docs/cli/src/installation.md deleted file mode 100644 index 7151de1ff..000000000 --- a/docs/cli/src/installation.md +++ /dev/null @@ -1,24 +0,0 @@ -# Installation - -There are multiple ways to install the Dioxus CLI tool. Choose any one of the methods below that best suit your needs. - -## Install from latest master version - -We suggest you use `github master` version to install it now. - -We have not yet released the latest version to `crates.io` - -``` -cargo install --git https://github.com/Dioxuslabs/cli -``` - -This will automatically download `Dioxus-CLI` source from github master branch, -and install it in Cargo's global binary directory (`~/.cargo/bin/` by default). - -## Install from `crates.io` version - -``` -cargo install dioxus-cli -``` - -Make sure to add the Cargo bin directory to your `PATH`. \ No newline at end of file diff --git a/docs/cli/src/introduction.md b/docs/cli/src/introduction.md deleted file mode 100644 index 2f857a2a5..000000000 --- a/docs/cli/src/introduction.md +++ /dev/null @@ -1,21 +0,0 @@ -# Introduction - -📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground. - -![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png) - -It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus. - -## Features - -- [x] `html` to `rsx` conversion tool -- [x] hot reload for `web` platform -- [x] create dioxus project from `git` repo -- [x] build & pack dioxus project -- [ ] autoformat dioxus `rsx` code - -## Contributors - -Contributors to this guide: - -- [mrxiaozhuox](https://github.com/mrxiaozhuox) \ No newline at end of file From cdbdc4c4a20747afea38a9a62062fbd125037f4c Mon Sep 17 00:00:00 2001 From: Zachary Date: Thu, 28 Apr 2022 20:11:56 +0200 Subject: [PATCH 043/105] Fix cfg conditions for wasm --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 748772aa4..8f6655022 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,10 +6,10 @@ pub mod hooks { #[cfg(feature = "hooks")] pub use dioxus_hooks::*; - #[cfg(all(target = "wasm", feature = "web"))] + #[cfg(all(target_arch = "wasm32", feature = "web"))] pub use dioxus_web::use_eval; - #[cfg(all(not(target = "wasm"), feature = "desktop"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))] pub use dioxus_desktop::use_eval; } From 99fea74786988e3584350661bccdac9ad20bd870 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 30 Apr 2022 16:52:54 -0500 Subject: [PATCH 044/105] change with_ctrl_c_quit to without_ctrl_c_quit --- docs/reference/src/platforms/tui.md | 8 +++----- packages/tui/src/config.rs | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/reference/src/platforms/tui.md b/docs/reference/src/platforms/tui.md index 2b5a3ef96..f8c97fa04 100644 --- a/docs/reference/src/platforms/tui.md +++ b/docs/reference/src/platforms/tui.md @@ -53,7 +53,6 @@ $ 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 configuration 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; @@ -61,11 +60,10 @@ use dioxus::tui::TuiContext; fn main() { dioxus::tui::launch_cfg( app, - dioxus::tui::Config { - ctrl_c_quit: false, + dioxus::tui::Config::new() + .without_ctrl_c_quit() // 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, - }, + .with_rendering_mode(dioxus::tui::RenderingMode::Rgb), ); } diff --git a/packages/tui/src/config.rs b/packages/tui/src/config.rs index fc2fb3da5..8251b13b6 100644 --- a/packages/tui/src/config.rs +++ b/packages/tui/src/config.rs @@ -21,9 +21,9 @@ impl Config { } } - pub fn with_ctrl_c_quit(self) -> Self { + pub fn without_ctrl_c_quit(self) -> Self { Self { - ctrl_c_quit: true, + ctrl_c_quit: false, ..self } } From e1120bb64442fab667a35068604513b48b0ee6fd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 1 May 2022 21:19:11 -0500 Subject: [PATCH 045/105] 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 046/105] 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 047/105] 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] From 22a423e2f6cbefd485f5033b5c4f710904cc87b9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 2 May 2022 22:17:01 -0400 Subject: [PATCH 048/105] chore: use fixed stretch version --- packages/native-core/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index ab0e4f6f8..0aee45c48 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -11,10 +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 = { git = "https://github.com/DioxusLabs/stretch" } +stretch2 = "0.4.2" smallvec = "1.6" fxhash = "0.2" anymap = "0.12.1" [dev-dependencies] -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" From b8e8e6952390c5e5a921e73e21697e7952860355 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 2 May 2022 22:20:52 -0400 Subject: [PATCH 049/105] fix: use published stretch --- packages/tui/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 58d71db66..dc84653b6 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -23,7 +23,7 @@ crossterm = "0.23.0" anyhow = "1.0.42" tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" -stretch2 = { git = "https://github.com/DioxusLabs/stretch" } +stretch2 = "0.4.2" smallvec = "1.6" fxhash = "0.2" -anymap = "0.12.1" \ No newline at end of file +anymap = "0.12.1" From 5503aeb7a60e844b3c29788e1d33892d8eb8d656 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 2 May 2022 23:45:59 -0400 Subject: [PATCH 050/105] fix: specify dioxus version in liveview --- packages/liveview/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 313892f45..44b085e24 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -23,8 +23,8 @@ serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" tokio-util = { version = "0.7.0", features = ["full"] } -dioxus-html = { path = "../html", features = ["serialize"] } -dioxus-core = { path = "../core", features = ["serialize"] } +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" } +dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.0" } # warp @@ -42,4 +42,4 @@ axum = { version = "0.5.1", features = ["ws"] } tower = "0.4.12" [features] -default = [] \ No newline at end of file +default = [] From 3febd42588947854602b987041caa443c36bb466 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 2 May 2022 23:57:20 -0400 Subject: [PATCH 051/105] Release dioxus-core v0.2.1, dioxus-core-macro v0.2.1, dioxus-html v0.2.1, dioxus-interpreter-js v0.2.1, dioxus-desktop v0.2.3, dioxus-hooks v0.2.1, dioxus-liveview v0.1.0, dioxus-native-core v0.2.0, dioxus-native-core-macro v0.2.0, dioxus-router v0.2.3, dioxus-ssr v0.2.1, dioxus-tui v0.2.2, dioxus-web v0.2.1, fermi v0.2.1, dioxus v0.2.4 --- Cargo.toml | 26 +++++++++++++------------- packages/core-macro/Cargo.toml | 2 +- packages/core/Cargo.toml | 2 +- packages/desktop/Cargo.toml | 8 ++++---- packages/fermi/Cargo.toml | 4 ++-- packages/hooks/Cargo.toml | 6 +++--- packages/html/Cargo.toml | 6 +++--- packages/interpreter/Cargo.toml | 2 +- packages/liveview/Cargo.toml | 4 ++-- packages/mobile/Cargo.toml | 2 +- packages/native-core-macro/Cargo.toml | 8 ++++---- packages/native-core/Cargo.toml | 6 +++--- packages/router/Cargo.toml | 8 ++++---- packages/ssr/Cargo.toml | 4 ++-- packages/tui/Cargo.toml | 6 +++--- packages/web/Cargo.toml | 8 ++++---- 16 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32e882a1a..bbc1d13d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus" -version = "0.2.3" +version = "0.2.4" authors = ["Jonathan Kelley"] edition = "2021" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -12,19 +12,19 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] rust-version = "1.56.0" [dependencies] -dioxus-core = { path = "./packages/core", version = "^0.2.0" } -dioxus-html = { path = "./packages/html", version = "^0.2.0", optional = true } -dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.0", optional = true } -dioxus-hooks = { path = "./packages/hooks", version = "^0.2.0", optional = true } -fermi = { path = "./packages/fermi", version = "^0.2.0", optional = true } +dioxus-core = { path = "./packages/core", version = "^0.2.1" } +dioxus-html = { path = "./packages/html", version = "^0.2.1", optional = true } +dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.1", optional = true } +dioxus-hooks = { path = "./packages/hooks", version = "^0.2.1", optional = true } +fermi = { path = "./packages/fermi", version = "^0.2.1", optional = true } -dioxus-web = { path = "./packages/web", version = "^0.2.0", optional = true } -dioxus-desktop = { path = "./packages/desktop", version = "^0.2.2", optional = true } -dioxus-ssr = { path = "./packages/ssr", version = "^0.2.0", optional = true } +dioxus-web = { path = "./packages/web", version = "^0.2.1", optional = true } +dioxus-desktop = { path = "./packages/desktop", version = "^0.2.3", optional = true } +dioxus-ssr = { path = "./packages/ssr", version = "^0.2.1", optional = true } -dioxus-router = { path = "./packages/router", version = "^0.2.1", optional = true } -dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.0", optional = true } -dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } +dioxus-router = { path = "./packages/router", version = "^0.2.3", optional = true } +dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true } +dioxus-tui = { path = "./packages/tui", version = "^0.2.2", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } @@ -97,4 +97,4 @@ harness = false [[bench]] name = "tui_update" -harness = false \ No newline at end of file +harness = false diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 5922ffad1..358f5edf0 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core-macro" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2021" description = "Core macro for Dioxus Virtual DOM" diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 480c3904d..fc697df12 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index a1dc1adc8..912a29abf 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-desktop" -version = "0.2.2" +version = "0.2.3" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for a remote webview instance" @@ -12,9 +12,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] } -dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" } -dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0" } +dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] } +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" } +dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" } serde = "1.0.136" serde_json = "1.0.79" diff --git a/packages/fermi/Cargo.toml b/packages/fermi/Cargo.toml index 7ccee40f6..05ae2ec21 100644 --- a/packages/fermi/Cargo.toml +++ b/packages/fermi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fermi" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Global state management for Dioxus" @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0" } +dioxus-core = { path = "../core", version = "^0.2.1" } im-rc = { version = "15.0.0", features = ["serde"] } log = "0.4.14" diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index d90b27d63..41c4d2854 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-hooks" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for a remote webview instance" @@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version = "^0.2.0" } +dioxus-core = { path = "../../packages/core", version = "^0.2.1" } futures-channel = "0.3.21" log = { version = "0.4" } [dev-dependencies] futures-util = { version = "0.3", default-features = false } -dioxus-core = { path = "../../packages/core", version = "^0.2.0" } +dioxus-core = { path = "../../packages/core", version = "^0.2.1" } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index c7a125138..5daaa1407 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-html" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -11,7 +11,7 @@ documentation = "https://docs.rs/dioxus" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0" } +dioxus-core = { path = "../core", version = "^0.2.1" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } @@ -38,4 +38,4 @@ features = [ [features] default = [] serialize = ["serde", "serde_repr"] -wasm-bind = ["web-sys", "wasm-bindgen"] \ No newline at end of file +wasm-bind = ["web-sys", "wasm-bindgen"] diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index e7a8b44be..4eb7410bd 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-interpreter-js" -version = "0.2.0" +version = "0.2.1" edition = "2018" authors = ["Jonathan Kelley"] description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 44b085e24..50bb7b3e9 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -23,8 +23,8 @@ serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" tokio-util = { version = "0.7.0", features = ["full"] } -dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" } -dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.0" } +dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" } +dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" } # warp diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 68d3e8102..1308c9f23 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-desktop = { path = "../desktop", version = "^0.2.0" } +dioxus-desktop = { path = "../desktop", version = "^0.2.3" } [lib] doctest = false diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index e80d67d81..9a2a119c0 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -13,10 +13,10 @@ 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" } +dioxus-core = { path = "../core", version = "^0.2.1" } +dioxus-html = { path = "../html", version = "^0.2.1" } +dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" } smallvec = "1.6" fxhash = "0.2" -anymap = "0.12.1" \ No newline at end of file +anymap = "0.12.1" diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 0aee45c48..b2678bf13 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -7,9 +7,9 @@ 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" } +dioxus-core = { path = "../core", version = "^0.2.1" } +dioxus-html = { path = "../html", version = "^0.2.1" } +dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" } stretch2 = "0.4.2" smallvec = "1.6" diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 2673f646e..02d6cb01a 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-router" -version = "0.2.2" +version = "0.2.3" edition = "2018" description = "Cross-platform router for Dioxus apps" license = "MIT/Apache-2.0" @@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0", default-features = false } -dioxus-html = { path = "../html", version = "^0.2.0", default-features = false } -dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } +dioxus-core = { path = "../core", version = "^0.2.1", default-features = false } +dioxus-html = { path = "../html", version = "^0.2.1", default-features = false } +dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" } futures-channel = "0.3.21" url = { version = "2.2.2", default-features = false } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 8b8e52c87..a2c238b35 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-ssr" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus render-to-string" @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] } +dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] } [dev-dependencies] diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index dc84653b6..efa312772 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-tui" -version = "0.2.1" +version = "0.2.2" authors = ["Jonathan Kelley, @dementhos"] edition = "2021" description = "TUI-based renderer for Dioxus" @@ -13,8 +13,8 @@ license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0" } -dioxus-html = { path = "../html", version = "^0.2.0" } +dioxus-core = { path = "../core", version = "^0.2.1" } +dioxus-html = { path = "../html", version = "^0.2.1" } dioxus-native-core = { path = "../native-core", version = "^0.2.0" } dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index cd8c1d50e..d9b66b7a5 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-web" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" @@ -11,9 +11,9 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.2.0" } -dioxus-html = { path = "../html", version = "^0.2.0", features = ["wasm-bind"] } -dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0", features = [ +dioxus-core = { path = "../core", version = "^0.2.1" } +dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] } +dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [ "web", ] } From 05ab8d2c788ea75d52664fba21947be3546769ba Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:00:16 -0400 Subject: [PATCH 052/105] fix: update liveview docs --- packages/liveview/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 50bb7b3e9..6439b7e8a 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -6,6 +6,9 @@ repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] +description = "Build server-side apps with Dioxus" +license = "MIT/Apache-2.0" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8352c1bc3fe71acf2ab2bc2a072a15a530723833 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:00:33 -0400 Subject: [PATCH 053/105] Release dioxus-liveview v0.1.0, dioxus-native-core v0.2.0, dioxus-native-core-macro v0.2.0, dioxus-router v0.2.3, dioxus-ssr v0.2.1, dioxus-tui v0.2.2, dioxus-web v0.2.1, fermi v0.2.1, dioxus v0.2.4 From 85a09314a6b524a36f9220755f021ecb04edc383 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:02:09 -0400 Subject: [PATCH 054/105] chore: fill out cargo meta --- packages/native-core-macro/Cargo.toml | 7 +++++++ packages/native-core/Cargo.toml | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index 9a2a119c0..db5bb11e9 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -2,6 +2,13 @@ name = "dioxus-native-core-macro" version = "0.2.0" edition = "2021" +description = "Build natively rendered apps with Dioxus" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] + [lib] proc-macro = true diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index b2678bf13..284a9394a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" +description = "Build natively rendered apps with Dioxus" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] + [dependencies] dioxus-core = { path = "../core", version = "^0.2.1" } From 0bcaa11463500547d96bd1b1c5d7f6435211d610 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:02:12 -0400 Subject: [PATCH 055/105] Release dioxus-native-core v0.2.0, dioxus-native-core-macro v0.2.0, dioxus-router v0.2.3, dioxus-ssr v0.2.1, dioxus-tui v0.2.2, dioxus-web v0.2.1, fermi v0.2.1, dioxus v0.2.4 From cf378d34d8f6bbe9e56deba36eddc182321c31ad Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:03:31 -0400 Subject: [PATCH 056/105] fix: add version for package --- packages/native-core-macro/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/native-core-macro/Cargo.toml b/packages/native-core-macro/Cargo.toml index db5bb11e9..837f8890e 100644 --- a/packages/native-core-macro/Cargo.toml +++ b/packages/native-core-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] syn = { version = "1.0.11", features = ["extra-traits"] } quote = "1.0" -dioxus-native-core = { path = "../native-core" } +dioxus-native-core = { path = "../native-core", version = "^0.2.0" } [dev-dependencies] dioxus-core = { path = "../core", version = "^0.2.1" } From 12420f34c2e0e02d82c9a5c5439d009c414cde2e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:03:35 -0400 Subject: [PATCH 057/105] Release dioxus-native-core-macro v0.2.0, dioxus-router v0.2.3, dioxus-ssr v0.2.1, dioxus-tui v0.2.2, dioxus-web v0.2.1, fermi v0.2.1, dioxus v0.2.4 From 4ef2cfaba5cb31bb9d0cfec840927a4ef02a6e82 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:05:54 -0400 Subject: [PATCH 058/105] fix: specify version --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbc1d13d4..e0a0b4034 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,10 @@ dioxus-router = { path = "./packages/router", version = "^0.2.3", optional = tru dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true } dioxus-tui = { path = "./packages/tui", version = "^0.2.2", optional = true } -dioxus-liveview = { path = "./packages/liveview", optional = true } +dioxus-liveview = { path = "./packages/liveview", version = "^0.1.0", optional = true } -dioxus-native-core = { path = "./packages/native-core", optional = true } -dioxus-native-core-macro = { path = "./packages/native-core-macro", optional = true } +dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true } +dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true } # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true } # dioxus-rsx = { path = "./packages/rsx", optional = true } From a62e729bfaf4fac37576b22a55482bd21bdca8a0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 3 May 2022 00:05:58 -0400 Subject: [PATCH 059/105] Release dioxus v0.2.4 From 96c178f91f7fd264f62b14b5894ffa868ce28e02 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 16:09:18 +0300 Subject: [PATCH 060/105] Add docs for event MouseData Adapted from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent --- packages/html/src/events.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index b43766dc1..bee91f4d6 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -497,17 +497,45 @@ pub mod on { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct MouseData { + /// True if the alt key was down when the mouse event was fired. pub alt_key: bool, + /// The button number that was pressed (if applicable) when the mouse event was fired. pub button: i16, + /// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered. + /// + /// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4). + /// + /// - 1: Primary button (usually the left button) + /// - 2: Secondary button (usually the right button) + /// - 4: Auxiliary button (usually the mouse wheel button or middle button) + /// - 8: 4th button (typically the "Browser Back" button) + /// - 16 : 5th button (typically the "Browser Forward" button) pub buttons: u16, + /// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). + /// + /// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally. pub client_x: i32, + /// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). + /// + /// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically. pub client_y: i32, + /// True if the control key was down when the mouse event was fired. pub ctrl_key: bool, + /// True if the meta key was down when the mouse event was fired. pub meta_key: bool, + /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible. + /// + /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300. pub page_x: i32, + /// The Y (vertical) coordinate in pixels of the event relative to the whole document. + /// + /// See `page_x`. pub page_y: i32, + /// The X coordinate of the mouse pointer in global (screen) coordinates. pub screen_x: i32, + /// The Y coordinate of the mouse pointer in global (screen) coordinates. pub screen_y: i32, + /// True if the shift key was down when the mouse event was fired. pub shift_key: bool, // fn get_modifier_state(&self, key_code: &str) -> bool; } From d84d414170b67f12fa5ada55b9a8f406ba5206f3 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 16:42:14 +0300 Subject: [PATCH 061/105] Add offset coordinates to mouse events on TUI and desktop. --- packages/html/src/events.rs | 4 ++++ packages/interpreter/src/interpreter.js | 4 ++++ packages/tui/src/hooks.rs | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index bee91f4d6..cfa901a2e 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -523,6 +523,10 @@ pub mod on { pub ctrl_key: bool, /// True if the meta key was down when the mouse event was fired. pub meta_key: bool, + /// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node. + pub offset_x: i32, + /// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node. + pub offset_y: i32, /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible. /// /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300. diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 4725fff31..a908ad74c 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -409,6 +409,8 @@ export function serialize_event(event) { clientY, ctrlKey, metaKey, + offsetX, + offsetY, pageX, pageY, screenX, @@ -423,6 +425,8 @@ export function serialize_event(event) { client_y: clientY, ctrl_key: ctrlKey, meta_key: metaKey, + offset_x: offsetX, + offset_y: offsetY, page_x: pageX, page_y: pageY, screen_x: screenX, diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 8da04fc5e..04d320fcb 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -602,6 +602,10 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { Some(MouseButton::Right) => 2, }; // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + + // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. + // todo? + // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. EventData::Mouse(MouseData { alt_key: alt, button: button_state, @@ -610,6 +614,8 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { client_y: y, ctrl_key: ctrl, meta_key: meta, + offset_x: x, + offset_y: y, page_x: x, page_y: y, screen_x: x, From b5f5c6ebc13d2b097db00af6b07e34041871f72b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 16:43:11 +0300 Subject: [PATCH 062/105] Add example showcasing 3 different coordinate systems --- examples/coordinates.rs | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/coordinates.rs diff --git a/examples/coordinates.rs b/examples/coordinates.rs new file mode 100644 index 000000000..bdfabb119 --- /dev/null +++ b/examples/coordinates.rs @@ -0,0 +1,48 @@ +use dioxus::prelude::*; +use dioxus_core::UiEvent; +use dioxus_html::on::MouseData; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let page_coordinates = use_state(&cx, ||"".to_string()); + let screen_coordinates = use_state(&cx, ||"".to_string()); + let offset_coordinates = use_state(&cx, ||"".to_string()); + + let container_style = r#" + display: flex; + flex-direction: column; + align-items: center; + "#; + let rect_style = r#" + background: deepskyblue; + height: 50vh; + width: 50vw; + "#; + + let update_mouse_position = move |event: UiEvent| { + let mouse_data = event.data; + + page_coordinates.set(format!("{:?}", (mouse_data.page_x, mouse_data.page_y))); + screen_coordinates.set(format!("{:?}", (mouse_data.screen_x, mouse_data.screen_y))); + offset_coordinates.set(format!("{:?}", (mouse_data.offset_x, mouse_data.offset_y))); + + // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. + }; + + cx.render(rsx! ( + div { + style: "{container_style}", + "Hover over to display coordinates:", + div { + style: "{rect_style}", + onmousemove: update_mouse_position, + } + div {"Page coordinates: {page_coordinates}"}, + div {"Screen coordinates: {screen_coordinates}"}, + div {"Offset coordinates: {offset_coordinates}"}, + } + )) +} From dbdcea6fc665445058046b69bb5476b39fbc905f Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 19:55:42 +0300 Subject: [PATCH 063/105] Add offset_ fields for web targets --- packages/html/src/web_sys_bind/events.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 4b721e45d..d43130193 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -79,6 +79,8 @@ impl From<&MouseEvent> for MouseData { client_y: e.client_y(), ctrl_key: e.ctrl_key(), meta_key: e.meta_key(), + offset_x: e.offset_x(), + offset_y: e.offset_y(), screen_x: e.screen_x(), screen_y: e.screen_y(), shift_key: e.shift_key(), From f81ecf59747abead66a8192ef1cab9ca9c242dd5 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 20:05:21 +0300 Subject: [PATCH 064/105] Update liveview interpreter to match desktop --- packages/liveview/src/interpreter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/liveview/src/interpreter.js b/packages/liveview/src/interpreter.js index cd2c3dfe2..383f33a5c 100644 --- a/packages/liveview/src/interpreter.js +++ b/packages/liveview/src/interpreter.js @@ -418,6 +418,8 @@ function serialize_event(event) { clientY, ctrlKey, metaKey, + offsetX, + offsetY, pageX, pageY, screenX, @@ -432,6 +434,8 @@ function serialize_event(event) { client_y: clientY, ctrl_key: ctrlKey, meta_key: metaKey, + offset_x: offsetX, + offset_y: offsetY, page_x: pageX, page_y: pageY, screen_x: screenX, From 152bad64339cc81895d960b2687116fe3c9e99f7 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 4 May 2022 20:15:55 +0300 Subject: [PATCH 065/105] rustfmt --- examples/coordinates.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/coordinates.rs b/examples/coordinates.rs index bdfabb119..0c45c2e3d 100644 --- a/examples/coordinates.rs +++ b/examples/coordinates.rs @@ -7,9 +7,9 @@ fn main() { } fn app(cx: Scope) -> Element { - let page_coordinates = use_state(&cx, ||"".to_string()); - let screen_coordinates = use_state(&cx, ||"".to_string()); - let offset_coordinates = use_state(&cx, ||"".to_string()); + let page_coordinates = use_state(&cx, || "".to_string()); + let screen_coordinates = use_state(&cx, || "".to_string()); + let offset_coordinates = use_state(&cx, || "".to_string()); let container_style = r#" display: flex; From a4eb4dc8dfd33c6056fbcf5af3d19601a2060d1e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 May 2022 13:58:48 -0500 Subject: [PATCH 066/105] add offset mouse data to tui --- packages/tui/src/hooks.rs | 98 ++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 04d320fcb..658229fae 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -188,16 +188,6 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - struct Data<'b> { - new_pos: (i32, i32), - old_pos: Option<(i32, i32)>, - clicked: bool, - released: bool, - wheel_delta: f64, - mouse_data: &'b MouseData, - wheel_data: &'b Option, - } - fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool { layout.location.x as i32 <= point.0 && layout.location.x as i32 + layout.size.width as i32 >= point.0 @@ -230,6 +220,13 @@ impl InnerInputState { } } + fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData { + let mut data = mouse_data.clone(); + data.offset_x = data.client_x - layout.location.x as i32; + data.offset_y = data.client_y - layout.location.y as i32; + data + } + if let Some(mouse) = &self.mouse { let new_pos = (mouse.0.screen_x, mouse.0.screen_y); let old_pos = previous_mouse @@ -242,31 +239,21 @@ impl InnerInputState { let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); let mouse_data = &mouse.0; let wheel_data = &self.wheel; - let data = Data { - new_pos, - old_pos, - clicked, - released, - wheel_delta, - mouse_data, - wheel_data, - }; { // mousemove let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousemove") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = data - .old_pos + let previously_contained = old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && previously_contained { try_create_event( "mousemove", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -281,16 +268,15 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseenter") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = data - .old_pos + let previously_contained = old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && !previously_contained { try_create_event( "mouseenter", - Arc::new(data.mouse_data.clone()), + Arc::new(mouse_data.clone()), &mut will_bubble, resolved_events, node, @@ -305,16 +291,15 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseover") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = data - .old_pos + let previously_contained = old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && !previously_contained { try_create_event( "mouseover", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -329,12 +314,12 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && data.clicked { + if currently_contains && clicked { try_create_event( "mousedown", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -349,12 +334,12 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseup") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && data.released { + if currently_contains && released { try_create_event( "mouseup", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -369,12 +354,12 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("click") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && data.released && data.mouse_data.button == 0 { + if currently_contains && released && mouse_data.button == 0 { try_create_event( "click", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -389,12 +374,12 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("contextmenu") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && data.released && data.mouse_data.button == 2 { + if currently_contains && released && mouse_data.button == 2 { try_create_event( "contextmenu", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -409,10 +394,10 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("wheel") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); - if let Some(w) = data.wheel_data { - if currently_contains && data.wheel_delta != 0.0 { + if let Some(w) = wheel_data { + if currently_contains && wheel_delta != 0.0 { try_create_event( "wheel", Arc::new(w.clone()), @@ -431,16 +416,15 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseleave") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = data - .old_pos + let previously_contained = old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); if !currently_contains && previously_contained { try_create_event( "mouseleave", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -455,16 +439,15 @@ impl InnerInputState { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mouseout") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = data - .old_pos + let previously_contained = old_pos .filter(|pos| layout_contains_point(node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, data.new_pos); + let currently_contains = layout_contains_point(node_layout, new_pos); if !currently_contains && previously_contained { try_create_event( "mouseout", - Arc::new(data.mouse_data.clone()), + Arc::new(prepare_mouse_data(mouse_data, node_layout)), &mut will_bubble, resolved_events, node, @@ -603,7 +586,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { }; // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent - // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. + // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. EventData::Mouse(MouseData { @@ -614,8 +597,9 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { client_y: y, ctrl_key: ctrl, meta_key: meta, - offset_x: x, - offset_y: y, + // offset x/y are set when the origin of the event is assigned to an element + offset_x: 0, + offset_y: 0, page_x: x, page_y: y, screen_x: x, From 8ab1c4d0dec39ee681512a117088e17ee4bce650 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 5 May 2022 16:29:24 -0400 Subject: [PATCH 067/105] feat: upgrade wry --- Cargo.toml | 1 - packages/desktop/Cargo.toml | 8 ++------ packages/desktop/src/desktop_context.rs | 2 +- packages/desktop/src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0a0b4034..86a952e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ html = ["dioxus-html"] ssr = ["dioxus-ssr"] web = ["dioxus-web", "dioxus-router/web"] desktop = ["dioxus-desktop"] -ayatana = ["dioxus-desktop/ayatana"] router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 912a29abf..e61120d64 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -20,7 +20,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.13.1" } +wry = { version = "0.16.0" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", @@ -28,7 +28,7 @@ tokio = { version = "1.16.1", features = [ "rt", "time", ], optional = true, default-features = false } -webbrowser = "0.6.0" +webbrowser = "0.7.1" mime_guess = "2.0.3" dunce = "1.0.2" @@ -38,13 +38,9 @@ core-foundation = "0.9.3" [features] default = ["tokio_runtime"] tokio_runtime = ["tokio"] - -devtool = ["wry/devtool"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] - tray = ["wry/tray"] -ayatana = ["wry/ayatana"] [dev-dependencies] diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 155f7af1b..0b37910b0 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -191,7 +191,7 @@ pub(super) fn handler( SetTitle(content) => window.set_title(&content), SetDecorations(state) => window.set_decorations(state), - DevTool => webview.devtool(), + DevTool => {} Eval(code) => webview .evaluate_script(code.as_str()) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 1e3898f05..160ed7d43 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -200,7 +200,7 @@ pub fn launch_with_props( ) } else { // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_dev_tool(true); + webview = webview.with_devtools(true); } desktop.webviews.insert(window_id, webview.build().unwrap()); From 356f37e9ee5514fc441bbc1fade99c1edec09e52 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 5 May 2022 16:50:33 -0400 Subject: [PATCH 068/105] feat: arbitrary attributes --- packages/core/src/arbitrary_value.rs | 265 +++++++++++++++++++++++++++ packages/core/src/lib.rs | 2 + packages/core/src/mutations.rs | 7 +- packages/core/src/nodes.rs | 23 ++- packages/ssr/src/lib.rs | 6 +- packages/web/Cargo.toml | 2 +- packages/web/src/dom.rs | 2 +- 7 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 packages/core/src/arbitrary_value.rs diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs new file mode 100644 index 000000000..80acc0ca6 --- /dev/null +++ b/packages/core/src/arbitrary_value.rs @@ -0,0 +1,265 @@ +use std::fmt::Formatter; + +// trying to keep values at 3 bytes +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq)] +pub enum AttributeValue<'a> { + Text(&'a str), + Float32(f32), + Float64(f64), + Int32(i32), + Int64(i64), + Uint32(u32), + Uint64(u64), + Bool(bool), + ColorRGB(u8, u8, u8), + ColorRGBA(u8, u8, u8, u8), + ColorHex(u32), + ColorHexAlpha(u32, u8), + Vec3Float(f32, f32, f32), + Vec3Int(i32, i32, i32), + Vec3Uint(u32, u32, u32), + Vec4Float(f32, f32, f32, f32), + Vec4Int(i32, i32, i32, i32), + Vec4Uint(u32, u32, u32, u32), + Bytes(&'a [u8]), + Any(ArbitraryAttributeValue<'a>), +} + +impl<'a> AttributeValue<'a> { + pub fn is_truthy(&self) -> bool { + match self { + AttributeValue::Text(t) => *t == "true", + AttributeValue::Bool(t) => *t, + _ => false, + } + } + + pub fn is_falsy(&self) -> bool { + match self { + AttributeValue::Text(t) => *t == "false", + AttributeValue::Bool(t) => !(*t), + _ => false, + } + } +} + +impl<'a> std::fmt::Display for AttributeValue<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AttributeValue::Text(a) => write!(f, "{}", a), + AttributeValue::Float32(a) => write!(f, "{}", a), + AttributeValue::Float64(a) => write!(f, "{}", a), + AttributeValue::Int32(a) => write!(f, "{}", a), + AttributeValue::Int64(a) => write!(f, "{}", a), + AttributeValue::Uint32(a) => write!(f, "{}", a), + AttributeValue::Uint64(a) => write!(f, "{}", a), + AttributeValue::Bool(a) => write!(f, "{}", a), + AttributeValue::ColorRGB(_, _, _) => todo!(), + AttributeValue::ColorRGBA(_, _, _, _) => todo!(), + AttributeValue::ColorHex(_) => todo!(), + AttributeValue::ColorHexAlpha(_, _) => todo!(), + AttributeValue::Vec3Float(_, _, _) => todo!(), + AttributeValue::Vec3Int(_, _, _) => todo!(), + AttributeValue::Vec3Uint(_, _, _) => todo!(), + AttributeValue::Vec4Float(_, _, _, _) => todo!(), + AttributeValue::Vec4Int(_, _, _, _) => todo!(), + AttributeValue::Vec4Uint(_, _, _, _) => todo!(), + AttributeValue::Bytes(_) => todo!(), + AttributeValue::Any(_) => todo!(), + } + } +} + +#[derive(Clone, Copy)] +pub struct ArbitraryAttributeValue<'a> { + pub value: &'a dyn std::any::Any, + pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool, +} + +impl PartialEq for ArbitraryAttributeValue<'_> { + fn eq(&self, other: &Self) -> bool { + (self.cmp)(self.value, other.value) + } +} + +impl std::fmt::Debug for ArbitraryAttributeValue<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ArbitraryAttributeValue").finish() + } +} + +#[cfg(feature = "serialize")] +impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> { + fn serialize(&self, _serializer: S) -> Result + where + S: serde::Serializer, + { + panic!("ArbitraryAttributeValue should not be serialized") + } +} +#[cfg(feature = "serialize")] +impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + panic!("ArbitraryAttributeValue is not deserializable!") + } +} +#[cfg(feature = "serialize")] +impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + panic!("ArbitraryAttributeValue is not deserializable!") + } +} + +impl<'a> AttributeValue<'a> { + pub fn as_text(&self) -> Option<&'a str> { + match self { + AttributeValue::Text(s) => Some(s), + _ => None, + } + } + + pub fn as_float32(&self) -> Option { + match self { + AttributeValue::Float32(f) => Some(*f), + _ => None, + } + } + + pub fn as_float64(&self) -> Option { + match self { + AttributeValue::Float64(f) => Some(*f), + _ => None, + } + } + + pub fn as_int32(&self) -> Option { + match self { + AttributeValue::Int32(i) => Some(*i), + _ => None, + } + } + + pub fn as_int64(&self) -> Option { + match self { + AttributeValue::Int64(i) => Some(*i), + _ => None, + } + } + + pub fn as_uint32(&self) -> Option { + match self { + AttributeValue::Uint32(i) => Some(*i), + _ => None, + } + } + + pub fn as_uint64(&self) -> Option { + match self { + AttributeValue::Uint64(i) => Some(*i), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + AttributeValue::Bool(b) => Some(*b), + _ => None, + } + } + + pub fn as_color_rgb(&self) -> Option<(u8, u8, u8)> { + match self { + AttributeValue::ColorRGB(r, g, b) => Some((*r, *g, *b)), + _ => None, + } + } + + pub fn as_color_rgba(&self) -> Option<(u8, u8, u8, u8)> { + match self { + AttributeValue::ColorRGBA(r, g, b, a) => Some((*r, *g, *b, *a)), + _ => None, + } + } + + pub fn as_color_hex(&self) -> Option { + match self { + AttributeValue::ColorHex(c) => Some(*c), + _ => None, + } + } + + pub fn as_color_hex_alpha(&self) -> Option<(u32, u8)> { + match self { + AttributeValue::ColorHexAlpha(c, a) => Some((*c, *a)), + _ => None, + } + } + + pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> { + match self { + AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> { + match self { + AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> { + match self { + AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> { + match self { + AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> { + match self { + AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> { + match self { + AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_bytes(&self) -> Option<&[u8]> { + match self { + AttributeValue::Bytes(b) => Some(b), + _ => None, + } + } + + pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> { + match self { + AttributeValue::Any(a) => Some(a), + _ => None, + } + } +} + +#[test] +fn test_attribute_value_size() { + assert_eq!(std::mem::size_of::>(), 24); +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f31e2e328..708df1020 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] #![deny(missing_docs)] +pub(crate) mod arbitrary_value; pub(crate) mod diff; pub(crate) mod events; pub(crate) mod lazynodes; @@ -13,6 +14,7 @@ pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { + pub use crate::arbitrary_value::*; pub use crate::events::*; pub use crate::lazynodes::*; pub use crate::mutations::*; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2d3115ce3..4218e613f 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -167,8 +167,9 @@ pub enum DomEdit<'bump> { field: &'static str, /// The value of the attribute. - value: &'bump str, + value: AttributeValue<'bump>, + // value: &'bump str, /// The (optional) namespace of the attribute. /// For instance, "style" is in the "style" namespace. ns: Option<&'bump str>, @@ -286,7 +287,7 @@ impl<'a> Mutations<'a> { self.edits.push(SetText { text, root }); } - pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) { + pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) { let Attribute { name, value, @@ -296,7 +297,7 @@ impl<'a> Mutations<'a> { self.edits.push(SetAttribute { field: name, - value, + value: value.clone(), ns: *namespace, root, }); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d3359a8c..086c6b20d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, + innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState, AttributeValue}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -339,7 +339,7 @@ pub struct Attribute<'a> { pub name: &'static str, /// The value of the attribute. - pub value: &'a str, + pub value: AttributeValue<'a>, /// An indication if this attribute can be ignored during diffing /// @@ -357,6 +357,7 @@ pub struct Attribute<'a> { pub namespace: Option<&'static str>, } + /// An event listener. /// IE onclick, onkeydown, etc pub struct Listener<'bump> { @@ -610,6 +611,24 @@ impl<'a> NodeFactory<'a> { is_volatile: bool, ) -> Attribute<'a> { let (value, is_static) = self.raw_text(val); + Attribute { + name, + value: AttributeValue::Text(value), + is_static, + namespace, + is_volatile, + } + } + + /// Create a new [`Attribute`] using non-arguments + pub fn custom_attr( + &self, + name: &'static str, + value: AttributeValue<'a>, + namespace: Option<&'static str>, + is_volatile: bool, + is_static: bool, + ) -> Attribute<'a> { Attribute { name, value, diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index b473e7fa8..9eb204f78 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> { while let Some(attr) = attr_iter.next() { match attr.namespace { None => match attr.name { - "dangerous_inner_html" => inner_html = Some(attr.value), + "dangerous_inner_html" => { + inner_html = Some(attr.value.as_text().unwrap()) + } "allowfullscreen" | "allowpaymentrequest" | "async" @@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> { | "reversed" | "selected" | "truespeed" => { - if attr.value != "false" { + if attr.value.is_truthy() { write!(f, " {}=\"{}\"", attr.name, attr.value)? } } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index d9b66b7a5..387349ec5 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.2.1" } dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [ - "web", + "web" ] } js-sys = "0.3.56" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 84101423b..94d66460f 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -146,7 +146,7 @@ impl WebsysDom { value, ns, } => { - let value = serde_wasm_bindgen::to_value(value).unwrap(); + let value = serde_wasm_bindgen::to_value(&value).unwrap(); self.interpreter.SetAttribute(root, field, value, ns) } } From bded3142517975758e6a9a2ac9c8e1a42d8d8179 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 29 Apr 2022 23:19:35 -0400 Subject: [PATCH 069/105] wip: remove color --- packages/core/src/arbitrary_value.rs | 47 +++++----------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs index 80acc0ca6..792bf405c 100644 --- a/packages/core/src/arbitrary_value.rs +++ b/packages/core/src/arbitrary_value.rs @@ -12,16 +12,15 @@ pub enum AttributeValue<'a> { Uint32(u32), Uint64(u64), Bool(bool), - ColorRGB(u8, u8, u8), - ColorRGBA(u8, u8, u8, u8), - ColorHex(u32), - ColorHexAlpha(u32, u8), + Vec3Float(f32, f32, f32), Vec3Int(i32, i32, i32), Vec3Uint(u32, u32, u32), + Vec4Float(f32, f32, f32, f32), Vec4Int(i32, i32, i32, i32), Vec4Uint(u32, u32, u32, u32), + Bytes(&'a [u8]), Any(ArbitraryAttributeValue<'a>), } @@ -55,10 +54,6 @@ impl<'a> std::fmt::Display for AttributeValue<'a> { AttributeValue::Uint32(a) => write!(f, "{}", a), AttributeValue::Uint64(a) => write!(f, "{}", a), AttributeValue::Bool(a) => write!(f, "{}", a), - AttributeValue::ColorRGB(_, _, _) => todo!(), - AttributeValue::ColorRGBA(_, _, _, _) => todo!(), - AttributeValue::ColorHex(_) => todo!(), - AttributeValue::ColorHexAlpha(_, _) => todo!(), AttributeValue::Vec3Float(_, _, _) => todo!(), AttributeValue::Vec3Int(_, _, _) => todo!(), AttributeValue::Vec3Uint(_, _, _) => todo!(), @@ -174,34 +169,6 @@ impl<'a> AttributeValue<'a> { } } - pub fn as_color_rgb(&self) -> Option<(u8, u8, u8)> { - match self { - AttributeValue::ColorRGB(r, g, b) => Some((*r, *g, *b)), - _ => None, - } - } - - pub fn as_color_rgba(&self) -> Option<(u8, u8, u8, u8)> { - match self { - AttributeValue::ColorRGBA(r, g, b, a) => Some((*r, *g, *b, *a)), - _ => None, - } - } - - pub fn as_color_hex(&self) -> Option { - match self { - AttributeValue::ColorHex(c) => Some(*c), - _ => None, - } - } - - pub fn as_color_hex_alpha(&self) -> Option<(u32, u8)> { - match self { - AttributeValue::ColorHexAlpha(c, a) => Some((*c, *a)), - _ => None, - } - } - pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> { match self { AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)), @@ -259,7 +226,7 @@ impl<'a> AttributeValue<'a> { } } -#[test] -fn test_attribute_value_size() { - assert_eq!(std::mem::size_of::>(), 24); -} +// #[test] +// fn test_attribute_value_size() { +// assert_eq!(std::mem::size_of::>(), 24); +// } From 21df7d214222899a8145d62476a9dc1c24984d8e Mon Sep 17 00:00:00 2001 From: Jonathan Moore Date: Thu, 5 May 2022 17:49:08 -0500 Subject: [PATCH 070/105] Fix typo --- docs/guide/src/elements/lists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/src/elements/lists.md b/docs/guide/src/elements/lists.md index 8cd58c841..9f8a8072c 100644 --- a/docs/guide/src/elements/lists.md +++ b/docs/guide/src/elements/lists.md @@ -102,7 +102,7 @@ For keen Rustaceans: notice how we don't actually call `collect` on the name lis ## Keeping list items in order with `key` -The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient. +The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inefficient. To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list. From 0dbd3e370e48a9078512727b6f0add31d06bb4ca Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Fri, 6 May 2022 23:54:07 +0300 Subject: [PATCH 071/105] Rusty MouseData API --- examples/coordinates.rs | 10 +-- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 151 ++++++++++++++++++++++++++++++++++ packages/html/src/geometry.rs | 13 +++ packages/html/src/lib.rs | 1 + packages/tui/src/hooks.rs | 2 + 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 packages/html/src/geometry.rs diff --git a/examples/coordinates.rs b/examples/coordinates.rs index 0c45c2e3d..24ee5da62 100644 --- a/examples/coordinates.rs +++ b/examples/coordinates.rs @@ -9,7 +9,7 @@ fn main() { fn app(cx: Scope) -> Element { let page_coordinates = use_state(&cx, || "".to_string()); let screen_coordinates = use_state(&cx, || "".to_string()); - let offset_coordinates = use_state(&cx, || "".to_string()); + let element_coordinates = use_state(&cx, || "".to_string()); let container_style = r#" display: flex; @@ -25,9 +25,9 @@ fn app(cx: Scope) -> Element { let update_mouse_position = move |event: UiEvent| { let mouse_data = event.data; - page_coordinates.set(format!("{:?}", (mouse_data.page_x, mouse_data.page_y))); - screen_coordinates.set(format!("{:?}", (mouse_data.screen_x, mouse_data.screen_y))); - offset_coordinates.set(format!("{:?}", (mouse_data.offset_x, mouse_data.offset_y))); + page_coordinates.set(format!("{:?}", mouse_data.page_coordinates())); + screen_coordinates.set(format!("{:?}", mouse_data.screen_coordinates())); + element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. }; @@ -42,7 +42,7 @@ fn app(cx: Scope) -> Element { } div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, - div {"Offset coordinates: {offset_coordinates}"}, + div {"Element coordinates: {element_coordinates}"}, } )) } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 5daaa1407..fd1df29da 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -15,6 +15,7 @@ dioxus-core = { path = "../core", version = "^0.2.1" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } +euclid = "0.22.7" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index cfa901a2e..dfa0b49da 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -3,6 +3,7 @@ use dioxus_core::exports::bumpalo; use dioxus_core::*; pub mod on { + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; use std::collections::HashMap; use super::*; @@ -498,8 +499,10 @@ pub mod on { #[derive(Debug, Clone)] pub struct MouseData { /// True if the alt key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub alt_key: bool, /// The button number that was pressed (if applicable) when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use trigger_button() instead")] pub button: i16, /// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered. /// @@ -510,40 +513,188 @@ pub mod on { /// - 4: Auxiliary button (usually the mouse wheel button or middle button) /// - 8: 4th button (typically the "Browser Back" button) /// - 16 : 5th button (typically the "Browser Forward" button) + #[deprecated(since = "0.3.0", note = "use held_buttons() instead")] pub buttons: u16, /// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). /// /// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally. + #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")] pub client_x: i32, /// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). /// /// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically. + #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")] pub client_y: i32, /// True if the control key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub ctrl_key: bool, /// True if the meta key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub meta_key: bool, /// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node. + #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")] pub offset_x: i32, /// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node. + #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")] pub offset_y: i32, /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible. /// /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300. + #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")] pub page_x: i32, /// The Y (vertical) coordinate in pixels of the event relative to the whole document. /// /// See `page_x`. + #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")] pub page_y: i32, /// The X coordinate of the mouse pointer in global (screen) coordinates. + #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")] pub screen_x: i32, /// The Y coordinate of the mouse pointer in global (screen) coordinates. + #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")] pub screen_y: i32, /// True if the shift key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub shift_key: bool, // fn get_modifier_state(&self, key_code: &str) -> bool; } + impl MouseData { + /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). + // + // For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + pub fn client_coordinates(&self) -> ClientPoint { + #[allow(deprecated)] + ClientPoint::new(self.client_x.into(), self.client_y.into()) + } + + /// The event's coordinates relative to the padding edge of the target element + /// + /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.) + pub fn element_coordinates(&self) -> ElementPoint { + #[allow(deprecated)] + ElementPoint::new(self.offset_x.into(), self.offset_y.into()) + } + + /// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible. + /// + /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.) + pub fn page_coordinates(&self) -> PagePoint { + #[allow(deprecated)] + PagePoint::new(self.page_x.into(), self.page_y.into()) + } + + /// The event's coordinates relative to the entire screen. This takes into account the window's offset. + pub fn screen_coordinates(&self) -> ScreenPoint { + #[allow(deprecated)] + ScreenPoint::new(self.screen_x.into(), self.screen_y.into()) + } + + /// The set of modifier keys which were pressed when the event occurred + pub fn modifiers(&self) -> ModifierSet { + #[allow(deprecated)] + ModifierSet { + has_alt: self.alt_key, + has_ctrl: self.ctrl_key, + has_meta: self.meta_key, + has_shift: self.shift_key, + } + } + + /// The set of mouse buttons which were held when the event occurred. + pub fn held_buttons(&self) -> MouseButtonSet { + #[allow(deprecated)] + MouseButtonSet { + has_primary: self.buttons & 0b1 != 0, + has_secondary: self.buttons & 0b10 != 0, + has_auxiliary: self.buttons & 0b100 != 0, + has_fourth: self.buttons & 0b1000 != 0, + has_fifth: self.buttons & 0b10000 != 0, + } + } + + /// The mouse button that triggered the event + /// + // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here + /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. + pub fn trigger_button(&self) -> MouseButton { + #[allow(deprecated)] + let button_code = self.button; + + match button_code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + other => MouseButton::Other(other), + } + } + } + + pub struct ModifierSet { + has_alt: bool, + has_ctrl: bool, + has_meta: bool, + has_shift: bool, + } + + impl ModifierSet { + pub fn has_alt(&self) -> bool { + self.has_alt + } + pub fn has_ctrl(&self) -> bool { + self.has_ctrl + } + pub fn has_meta(&self) -> bool { + self.has_meta + } + pub fn has_shift(&self) -> bool { + self.has_shift + } + + pub fn is_empty(&self) -> bool { + !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) + } + } + + pub struct MouseButtonSet { + has_primary: bool, + has_secondary: bool, + has_auxiliary: bool, + has_fourth: bool, + has_fifth: bool, + } + + impl MouseButtonSet { + pub fn has_primary(&self) -> bool { + self.has_primary + } + pub fn has_secondary(&self) -> bool { + self.has_secondary + } + pub fn has_auxiliary(&self) -> bool { + self.has_auxiliary + } + pub fn has_fourth(&self) -> bool { + self.has_fourth + } + pub fn has_fifth(&self) -> bool { + self.has_fifth + } + } + + pub enum MouseButton { + Primary, + Secondary, + Auxiliary, + Fourth, + Fifth, + Other(i16), + } + pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs new file mode 100644 index 000000000..9260c12ae --- /dev/null +++ b/packages/html/src/geometry.rs @@ -0,0 +1,13 @@ +pub use euclid::*; + +pub struct ScreenSpace; +pub type ScreenPoint = Point2D; + +pub struct ClientSpace; +pub type ClientPoint = Point2D; + +pub struct ElementSpace; +pub type ElementPoint = Point2D; + +pub struct PageSpace; +pub type PagePoint = Point2D; \ No newline at end of file diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 1440fd9ae..4247bf39b 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -18,6 +18,7 @@ mod events; mod global_attributes; #[cfg(feature = "wasm-bind")] mod web_sys_bind; +mod geometry; pub use elements::*; pub use events::*; diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 04d320fcb..29ce634be 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -94,6 +94,7 @@ impl InnerInputState { // stores current input state and transforms events based on that state fn apply_event(&mut self, evt: &mut EventCore) { + #[allow(deprecated)] match evt.1 { // limitations: only two buttons may be held at once EventData::Mouse(ref mut m) => match &mut self.mouse { @@ -606,6 +607,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. + #[allow(deprecated)] EventData::Mouse(MouseData { alt_key: alt, button: button_state, From 3c447d7266158d12f6aa733dee30922ea64de08b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 00:49:47 +0300 Subject: [PATCH 072/105] Documentation and examples Also derive useful traits --- examples/{coordinates.rs => mouse_event.rs} | 8 ++++++ packages/html/src/events.rs | 30 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) rename examples/{coordinates.rs => mouse_event.rs} (81%) diff --git a/examples/coordinates.rs b/examples/mouse_event.rs similarity index 81% rename from examples/coordinates.rs rename to examples/mouse_event.rs index 24ee5da62..698de1c68 100644 --- a/examples/coordinates.rs +++ b/examples/mouse_event.rs @@ -10,6 +10,8 @@ fn app(cx: Scope) -> Element { let page_coordinates = use_state(&cx, || "".to_string()); let screen_coordinates = use_state(&cx, || "".to_string()); let element_coordinates = use_state(&cx, || "".to_string()); + let buttons = use_state(&cx, || "".to_string()); + let modifiers = use_state(&cx, || "".to_string()); let container_style = r#" display: flex; @@ -30,6 +32,9 @@ fn app(cx: Scope) -> Element { element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. + + buttons.set(format!("{:?}", mouse_data.held_buttons())); + modifiers.set(format!("{:?}", mouse_data.modifiers())); }; cx.render(rsx! ( @@ -39,10 +44,13 @@ fn app(cx: Scope) -> Element { div { style: "{rect_style}", onmousemove: update_mouse_position, + prevent_default: "mousedown", } div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, div {"Element coordinates: {element_coordinates}"}, + div {"{buttons}"}, + div {"{modifiers}"}, } )) } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index dfa0b49da..98cd5b323 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -561,8 +561,8 @@ pub mod on { impl MouseData { /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). - // - // For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + /// + /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. pub fn client_coordinates(&self) -> ClientPoint { #[allow(deprecated)] ClientPoint::new(self.client_x.into(), self.client_y.into()) @@ -634,6 +634,9 @@ pub mod on { } } + /// A set of modifier keys + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct ModifierSet { has_alt: bool, has_ctrl: bool, @@ -642,24 +645,33 @@ pub mod on { } impl ModifierSet { + /// Whether the set includes the alt key pub fn has_alt(&self) -> bool { self.has_alt } + /// Whether the set includes the control key pub fn has_ctrl(&self) -> bool { self.has_ctrl } + /// Whether the set includes the meta (windows/command) key + pub fn has_meta(&self) -> bool { self.has_meta } + /// Whether the set includes the shift key pub fn has_shift(&self) -> bool { self.has_shift } + /// True if the set contains no keys pub fn is_empty(&self) -> bool { !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) } } + /// A set of mouse buttons + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct MouseButtonSet { has_primary: bool, has_secondary: bool, @@ -669,29 +681,43 @@ pub mod on { } impl MouseButtonSet { + /// Whether the set includes the primary button (typically the left button) pub fn has_primary(&self) -> bool { self.has_primary } + /// Whether the set includes the secondary button (typically the right button) pub fn has_secondary(&self) -> bool { self.has_secondary } + /// Whether the set includes the auxiliary button (typically the middle button) pub fn has_auxiliary(&self) -> bool { self.has_auxiliary } + /// Whether the set includes the fourth button (typically the "Browser Back" button) pub fn has_fourth(&self) -> bool { self.has_fourth } + /// Whether the set includes the fifth button (typically the "Browser Forward" button) pub fn has_fifth(&self) -> bool { self.has_fifth } } + /// A mouse button type (such as Primary/Secondary) + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum MouseButton { + /// Primary button (typically the left button) Primary, + /// Secondary button (typically the right button) Secondary, + /// Auxiliary button (typically the middle button) Auxiliary, + /// Fourth button (typically the "Browser Back" button) Fourth, + /// Fifth button (typically the "Browser Forward" button) Fifth, + /// A button with an unknown code Other(i16), } From 6a5a3f47cce98d4000037e9e64fbe48c3062e931 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 00:59:12 +0300 Subject: [PATCH 073/105] Remove `allow(deprecated)` to bring back the warnings --- packages/html/src/lib.rs | 2 +- packages/tui/src/hooks.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 4247bf39b..bff22ec5e 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -15,10 +15,10 @@ mod elements; mod events; +pub mod geometry; mod global_attributes; #[cfg(feature = "wasm-bind")] mod web_sys_bind; -mod geometry; pub use elements::*; pub use events::*; diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 29ce634be..04d320fcb 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -94,7 +94,6 @@ impl InnerInputState { // stores current input state and transforms events based on that state fn apply_event(&mut self, evt: &mut EventCore) { - #[allow(deprecated)] match evt.1 { // limitations: only two buttons may be held at once EventData::Mouse(ref mut m) => match &mut self.mouse { @@ -607,7 +606,6 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. - #[allow(deprecated)] EventData::Mouse(MouseData { alt_key: alt, button: button_state, From 2c48ae271fc411e91006bcc87039ad2fb5d93b15 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 01:01:38 +0300 Subject: [PATCH 074/105] rustfmt --- packages/html/src/geometry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index 9260c12ae..d456efa57 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -10,4 +10,4 @@ pub struct ElementSpace; pub type ElementPoint = Point2D; pub struct PageSpace; -pub type PagePoint = Point2D; \ No newline at end of file +pub type PagePoint = Point2D; From e3757f97ed9321590c981b17096397852b47bc4b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 6 May 2022 17:01:50 -0500 Subject: [PATCH 075/105] only trigger onmousemove if the position has changed --- packages/tui/src/hooks.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 658229fae..38ab6659b 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -242,23 +242,25 @@ impl InnerInputState { { // mousemove - let mut will_bubble = FxHashSet::default(); - for node in dom.get_listening_sorted("mousemove") { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) - .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + if old_pos != Some(new_pos) { + let mut will_bubble = FxHashSet::default(); + for node in dom.get_listening_sorted("mousemove") { + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); + let previously_contained = old_pos + .filter(|pos| layout_contains_point(node_layout, *pos)) + .is_some(); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && previously_contained { - try_create_event( - "mousemove", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), - &mut will_bubble, - resolved_events, - node, - dom, - ); + if currently_contains && previously_contained { + try_create_event( + "mousemove", + Arc::new(prepare_mouse_data(mouse_data, node_layout)), + &mut will_bubble, + resolved_events, + node, + dom, + ); + } } } } From c72cc9ae419cbf0ac07bf21f4d7acb65b598d084 Mon Sep 17 00:00:00 2001 From: Maccesch Date: Fri, 6 May 2022 23:36:16 +0100 Subject: [PATCH 076/105] fixed FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 332e03eb2..ae9a2478a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,4 @@ # These are supported funding model platforms -github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: DioxusLabs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] open_collective: dioxus-labs # Replace with a single Open Collective username From ac95ab52226b73fb716896258c6a7cfb399daa5e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 6 May 2022 19:35:55 -0500 Subject: [PATCH 077/105] switch released pressed --- packages/tui/src/hooks.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 38ab6659b..fd3f7fe01 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -232,10 +232,12 @@ impl InnerInputState { let old_pos = previous_mouse .as_ref() .map(|m| (m.0.screen_x, m.0.screen_y)); - let clicked = - (!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0; + // the a mouse button is pressed if a button was not down and is now down + let pressed = + (mouse.0.buttons & !previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0; + // the a mouse button is pressed if a button was down and is now not down let released = - (mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0; + (!mouse.0.buttons & previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0; let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); let mouse_data = &mouse.0; let wheel_data = &self.wheel; @@ -311,8 +313,8 @@ impl InnerInputState { } } - { - // mousedown + // mousedown + if pressed { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); From 03110b85cd685977356d220482c023009e55939e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 6 May 2022 20:46:33 -0400 Subject: [PATCH 078/105] fix: cargo fmt --- packages/core/src/nodes.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 086c6b20d..66c25f1db 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState, AttributeValue}, + innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -357,7 +357,6 @@ pub struct Attribute<'a> { pub namespace: Option<&'static str>, } - /// An event listener. /// IE onclick, onkeydown, etc pub struct Listener<'bump> { From 9c9928d226687466497422d5b970e69e48f8672e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 6 May 2022 20:48:57 -0400 Subject: [PATCH 079/105] fix: tui values --- packages/tui/src/hooks.rs | 2 +- packages/tui/src/layout.rs | 8 +++++--- packages/tui/src/style_attributes.rs | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index fd3f7fe01..c9d1e2a8c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -320,7 +320,7 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && clicked { + if currently_contains { try_create_event( "mousedown", Arc::new(prepare_mouse_data(mouse_data, node_layout)), diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index f56f48955..c13435896 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -79,9 +79,11 @@ impl ChildDepState for StretchLayout { } } 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); + for Attribute { name, value, .. } in node.attributes() { + assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok()); + if let Some(text) = value.as_text() { + apply_layout_attributes(name, text, &mut style); + } } // the root node fills the entire area diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index cb140cfcf..fd8d51cb3 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -78,8 +78,10 @@ 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, &mut new); + for Attribute { name, value, .. } in node.attributes() { + if let Some(text) = value.as_text() { + apply_style_attributes(name, text, &mut new); + } } // keep the text styling from the parent element From 6ba067a617b53bb7558370f6e5a86f62571764e5 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 09:31:44 +0300 Subject: [PATCH 080/105] Use EnumSet for modifier and button sets --- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 169 ++++++++++++++++------------------ packages/html/src/geometry.rs | 54 ++++++++++- 3 files changed, 131 insertions(+), 93 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index fd1df29da..956f9966f 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -16,6 +16,7 @@ serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } euclid = "0.22.7" +enumset = "1.0.11" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 98cd5b323..898246377 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -1,9 +1,11 @@ use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; use dioxus_core::*; +use enumset::EnumSetType; pub mod on { - use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; + use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use enumset::EnumSet; use std::collections::HashMap; use super::*; @@ -592,25 +594,54 @@ pub mod on { /// The set of modifier keys which were pressed when the event occurred pub fn modifiers(&self) -> ModifierSet { + let mut set = EnumSet::empty(); + #[allow(deprecated)] - ModifierSet { - has_alt: self.alt_key, - has_ctrl: self.ctrl_key, - has_meta: self.meta_key, - has_shift: self.shift_key, + { + if self.alt_key { + set |= Modifier::Alt; + } + if self.ctrl_key { + set |= Modifier::Ctrl; + } + if self.meta_key { + set |= Modifier::Meta; + } + if self.shift_key { + set |= Modifier::Shift; + } } + + set } /// The set of mouse buttons which were held when the event occurred. pub fn held_buttons(&self) -> MouseButtonSet { + let mut set = EnumSet::empty(); + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons #[allow(deprecated)] - MouseButtonSet { - has_primary: self.buttons & 0b1 != 0, - has_secondary: self.buttons & 0b10 != 0, - has_auxiliary: self.buttons & 0b100 != 0, - has_fourth: self.buttons & 0b1000 != 0, - has_fifth: self.buttons & 0b10000 != 0, + { + if self.buttons & 0b1 != 0 { + set |= MouseButton::Primary; + } + if self.buttons & 0b10 != 0 { + set |= MouseButton::Secondary; + } + if self.buttons & 0b100 != 0 { + set |= MouseButton::Auxiliary; + } + if self.buttons & 0b1000 != 0 { + set |= MouseButton::Fourth; + } + if self.buttons & 0b10000 != 0 { + set |= MouseButton::Fifth; + } + if self.buttons & (!0b11111) != 0 { + set |= MouseButton::Unknown; + } } + set } /// The mouse button that triggered the event @@ -619,92 +650,24 @@ pub mod on { /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. pub fn trigger_button(&self) -> MouseButton { #[allow(deprecated)] - let button_code = self.button; - - match button_code { - 0 => MouseButton::Primary, - // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - 1 => MouseButton::Auxiliary, - 2 => MouseButton::Secondary, - 3 => MouseButton::Fourth, - 4 => MouseButton::Fifth, - other => MouseButton::Other(other), - } + MouseButton::from_web_code(self.button) } } - /// A set of modifier keys - #[derive(Debug, Clone)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub struct ModifierSet { - has_alt: bool, - has_ctrl: bool, - has_meta: bool, - has_shift: bool, + // note: EnumSetType also derives Copy and Clone for some reason + #[derive(EnumSetType, Debug)] + pub enum Modifier { + Alt, + Ctrl, + Meta, + Shift, } - impl ModifierSet { - /// Whether the set includes the alt key - pub fn has_alt(&self) -> bool { - self.has_alt - } - /// Whether the set includes the control key - pub fn has_ctrl(&self) -> bool { - self.has_ctrl - } - /// Whether the set includes the meta (windows/command) key - - pub fn has_meta(&self) -> bool { - self.has_meta - } - /// Whether the set includes the shift key - pub fn has_shift(&self) -> bool { - self.has_shift - } - - /// True if the set contains no keys - pub fn is_empty(&self) -> bool { - !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) - } - } - - /// A set of mouse buttons - #[derive(Debug, Clone)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub struct MouseButtonSet { - has_primary: bool, - has_secondary: bool, - has_auxiliary: bool, - has_fourth: bool, - has_fifth: bool, - } - - impl MouseButtonSet { - /// Whether the set includes the primary button (typically the left button) - pub fn has_primary(&self) -> bool { - self.has_primary - } - /// Whether the set includes the secondary button (typically the right button) - pub fn has_secondary(&self) -> bool { - self.has_secondary - } - /// Whether the set includes the auxiliary button (typically the middle button) - pub fn has_auxiliary(&self) -> bool { - self.has_auxiliary - } - /// Whether the set includes the fourth button (typically the "Browser Back" button) - pub fn has_fourth(&self) -> bool { - self.has_fourth - } - /// Whether the set includes the fifth button (typically the "Browser Forward" button) - pub fn has_fifth(&self) -> bool { - self.has_fifth - } - } + pub type ModifierSet = EnumSet; /// A mouse button type (such as Primary/Secondary) - #[derive(Debug, Clone)] + // note: EnumSetType also derives Copy and Clone for some reason + #[derive(EnumSetType, Debug)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum MouseButton { /// Primary button (typically the left button) @@ -718,9 +681,31 @@ pub mod on { /// Fifth button (typically the "Browser Forward" button) Fifth, /// A button with an unknown code - Other(i16), + Unknown, } + impl MouseButton { + /// Constructs a MouseButton for the specified button code + /// + /// E.g. 0 => Primary; 1 => Auxiliary + /// + /// Unknown codes get mapped to MouseButton::Unknown. + pub fn from_web_code(code: i16) -> Self { + match code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + _ => MouseButton::Unknown, + } + } + } + + pub type MouseButtonSet = EnumSet; + pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index d456efa57..38c497427 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -1,13 +1,65 @@ -pub use euclid::*; +pub use euclid; +use euclid::*; +/// Coordinate space relative to the screen pub struct ScreenSpace; pub type ScreenPoint = Point2D; +/// Coordinate space relative to the viewport pub struct ClientSpace; pub type ClientPoint = Point2D; +/// Coordinate space relative to an element pub struct ElementSpace; pub type ElementPoint = Point2D; +/// Coordinate space relative to the page pub struct PageSpace; pub type PagePoint = Point2D; + +/// Coordinates of a point in the app's interface +pub struct Coordinates { + screen: ScreenPoint, + client: ClientPoint, + element: ElementPoint, + page: PagePoint, +} + +impl Coordinates { + /// Construct new coordinates with the specified screen-, client-, element- and page-relative points + pub fn new( + screen: ScreenPoint, + client: ClientPoint, + element: ElementPoint, + page: PagePoint, + ) -> Self { + Self { + screen, + client, + element, + page, + } + } + /// Coordinates relative to the entire screen. This takes into account the window's offset. + pub fn screen(&self) -> ScreenPoint { + self.screen + } + /// Coordinates relative to the application's viewport (as opposed to the coordinate within the page). + /// + /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + pub fn client(&self) -> ClientPoint { + self.client + } + /// Coordinates relative to the padding edge of the target element + /// + /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.) + pub fn element(&self) -> ElementPoint { + self.element + } + /// Coordinates relative to the entire document. This includes any portion of the document not currently visible. + /// + /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.) + pub fn page(&self) -> PagePoint { + self.page + } +} From 2ef332085c852fe86069062903dd71ae55e7759a Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 09:39:53 +0300 Subject: [PATCH 081/105] Refactor: move input types to separate module --- packages/html/src/events.rs | 56 ++---------------------------------- packages/html/src/input.rs | 57 +++++++++++++++++++++++++++++++++++++ packages/html/src/lib.rs | 2 ++ 3 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 packages/html/src/input.rs diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 898246377..12c946eeb 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -1,10 +1,10 @@ use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; use dioxus_core::*; -use enumset::EnumSetType; pub mod on { - use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; + use crate::input::{Modifier, ModifierSet, MouseButton, MouseButtonSet}; use enumset::EnumSet; use std::collections::HashMap; @@ -654,58 +654,6 @@ pub mod on { } } - // note: EnumSetType also derives Copy and Clone for some reason - #[derive(EnumSetType, Debug)] - pub enum Modifier { - Alt, - Ctrl, - Meta, - Shift, - } - - pub type ModifierSet = EnumSet; - - /// A mouse button type (such as Primary/Secondary) - // note: EnumSetType also derives Copy and Clone for some reason - #[derive(EnumSetType, Debug)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub enum MouseButton { - /// Primary button (typically the left button) - Primary, - /// Secondary button (typically the right button) - Secondary, - /// Auxiliary button (typically the middle button) - Auxiliary, - /// Fourth button (typically the "Browser Back" button) - Fourth, - /// Fifth button (typically the "Browser Forward" button) - Fifth, - /// A button with an unknown code - Unknown, - } - - impl MouseButton { - /// Constructs a MouseButton for the specified button code - /// - /// E.g. 0 => Primary; 1 => Auxiliary - /// - /// Unknown codes get mapped to MouseButton::Unknown. - pub fn from_web_code(code: i16) -> Self { - match code { - 0 => MouseButton::Primary, - // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - 1 => MouseButton::Auxiliary, - 2 => MouseButton::Secondary, - 3 => MouseButton::Fourth, - 4 => MouseButton::Fifth, - _ => MouseButton::Unknown, - } - } - } - - pub type MouseButtonSet = EnumSet; - pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs new file mode 100644 index 000000000..06787023b --- /dev/null +++ b/packages/html/src/input.rs @@ -0,0 +1,57 @@ +use enumset::{EnumSet, EnumSetType}; + +// note: EnumSetType also derives Copy and Clone for some reason +/// A modifier key, such as Alt or Ctrl +#[derive(EnumSetType, Debug)] +pub enum Modifier { + Alt, + Ctrl, + /// The meta key (windows key, or command key) + Meta, + Shift, +} + +/// A set of modifier keys +pub type ModifierSet = EnumSet; + +/// A mouse button type (such as Primary/Secondary) +// note: EnumSetType also derives Copy and Clone for some reason +#[derive(EnumSetType, Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub enum MouseButton { + /// Primary button (typically the left button) + Primary, + /// Secondary button (typically the right button) + Secondary, + /// Auxiliary button (typically the middle button) + Auxiliary, + /// Fourth button (typically the "Browser Back" button) + Fourth, + /// Fifth button (typically the "Browser Forward" button) + Fifth, + /// A button with an unknown code + Unknown, +} + +impl MouseButton { + /// Constructs a MouseButton for the specified button code + /// + /// E.g. 0 => Primary; 1 => Auxiliary + /// + /// Unknown codes get mapped to MouseButton::Unknown. + pub fn from_web_code(code: i16) -> Self { + match code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + _ => MouseButton::Unknown, + } + } +} + +/// A set of mouse buttons +pub type MouseButtonSet = EnumSet; diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index bff22ec5e..3de940e25 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,9 +17,11 @@ mod elements; mod events; pub mod geometry; mod global_attributes; +mod input; #[cfg(feature = "wasm-bind")] mod web_sys_bind; pub use elements::*; pub use events::*; pub use global_attributes::*; +pub use input::*; From 3fec31fe50966d3a8f7f1df8b12f8e03e5caf364 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:05:52 +0300 Subject: [PATCH 082/105] Use keyboard_types instead of bespoke ModifierSet --- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 19 +++++++++++-------- packages/html/src/geometry.rs | 8 ++++++++ packages/html/src/input.rs | 16 +++------------- packages/html/src/lib.rs | 3 +-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 956f9966f..ac5cd340f 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -17,6 +17,7 @@ serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } euclid = "0.22.7" enumset = "1.0.11" +keyboard-types = "0.6.2" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 12c946eeb..414639523 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -3,9 +3,12 @@ use dioxus_core::exports::bumpalo; use dioxus_core::*; pub mod on { + //! Input events and associated data + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{Modifier, ModifierSet, MouseButton, MouseButtonSet}; + use crate::input::{MouseButton, MouseButtonSet}; use enumset::EnumSet; + use keyboard_types::Modifiers; use std::collections::HashMap; use super::*; @@ -593,26 +596,26 @@ pub mod on { } /// The set of modifier keys which were pressed when the event occurred - pub fn modifiers(&self) -> ModifierSet { - let mut set = EnumSet::empty(); + pub fn modifiers(&self) -> Modifiers { + let mut modifiers = Modifiers::empty(); #[allow(deprecated)] { if self.alt_key { - set |= Modifier::Alt; + modifiers.insert(Modifiers::ALT); } if self.ctrl_key { - set |= Modifier::Ctrl; + modifiers.insert(Modifiers::CONTROL); } if self.meta_key { - set |= Modifier::Meta; + modifiers.insert(Modifiers::META); } if self.shift_key { - set |= Modifier::Shift; + modifiers.insert(Modifiers::SHIFT); } } - set + modifiers } /// The set of mouse buttons which were held when the event occurred. diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index 38c497427..5a7c8112e 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -1,20 +1,28 @@ +//! Geometry primitives for representing e.g. mouse events + +/// A re-export of euclid, which we use for geometry primitives pub use euclid; + use euclid::*; /// Coordinate space relative to the screen pub struct ScreenSpace; +/// A point in ScreenSpace pub type ScreenPoint = Point2D; /// Coordinate space relative to the viewport pub struct ClientSpace; +/// A point in ClientSpace pub type ClientPoint = Point2D; /// Coordinate space relative to an element pub struct ElementSpace; +/// A point in ElementSpace pub type ElementPoint = Point2D; /// Coordinate space relative to the page pub struct PageSpace; +/// A point in PageSpace pub type PagePoint = Point2D; /// Coordinates of a point in the app's interface diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs index 06787023b..7c722b384 100644 --- a/packages/html/src/input.rs +++ b/packages/html/src/input.rs @@ -1,18 +1,8 @@ +//! Data structures representing user input, such as modifier keys and mouse buttons use enumset::{EnumSet, EnumSetType}; -// note: EnumSetType also derives Copy and Clone for some reason -/// A modifier key, such as Alt or Ctrl -#[derive(EnumSetType, Debug)] -pub enum Modifier { - Alt, - Ctrl, - /// The meta key (windows key, or command key) - Meta, - Shift, -} - -/// A set of modifier keys -pub type ModifierSet = EnumSet; +/// A re-export of keyboard_types +pub use keyboard_types; /// A mouse button type (such as Primary/Secondary) // note: EnumSetType also derives Copy and Clone for some reason diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 3de940e25..705a076e4 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,11 +17,10 @@ mod elements; mod events; pub mod geometry; mod global_attributes; -mod input; +pub mod input; #[cfg(feature = "wasm-bind")] mod web_sys_bind; pub use elements::*; pub use events::*; pub use global_attributes::*; -pub use input::*; From 6b2753bd4f189630bd271e6e20e85762e1bbcaf0 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:12:08 +0300 Subject: [PATCH 083/105] Update example --- examples/mouse_event.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mouse_event.rs b/examples/mouse_event.rs index 698de1c68..165d9445c 100644 --- a/examples/mouse_event.rs +++ b/examples/mouse_event.rs @@ -49,8 +49,8 @@ fn app(cx: Scope) -> Element { div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, div {"Element coordinates: {element_coordinates}"}, - div {"{buttons}"}, - div {"{modifiers}"}, + div {"Buttons: {buttons}"}, + div {"Modifiers: {modifiers}"}, } )) } From c35dddc203f0011fb1183864caeb98e5c380e375 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:31:05 +0300 Subject: [PATCH 084/105] Create constructor for MouseData --- packages/html/src/events.rs | 71 +++++++++++++++++++++++------------- packages/html/src/input.rs | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 26 deletions(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 414639523..4f2507292 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -5,8 +5,10 @@ use dioxus_core::*; pub mod on { //! Input events and associated data - use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{MouseButton, MouseButtonSet}; + use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use crate::input::{ + decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, + }; use enumset::EnumSet; use keyboard_types::Modifiers; use std::collections::HashMap; @@ -502,6 +504,9 @@ pub mod on { pub type MouseEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] + /// Data associated with a mouse event + /// + /// Do not use the deprecated fields; they may change or become private in the future. pub struct MouseData { /// True if the alt key was down when the mouse event was fired. #[deprecated(since = "0.3.0", note = "use modifiers() instead")] @@ -565,6 +570,43 @@ pub mod on { } impl MouseData { + pub fn new( + coordinates: Coordinates, + trigger_button: MouseButton, + held_buttons: MouseButtonSet, + modifiers: Modifiers, + ) -> Self { + let alt_key = modifiers.contains(Modifiers::ALT); + let ctrl_key = modifiers.contains(Modifiers::CONTROL); + let meta_key = modifiers.contains(Modifiers::META); + let shift_key = modifiers.contains(Modifiers::SHIFT); + + let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into(); + let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into(); + let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into(); + let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into(); + + #[allow(deprecated)] + Self { + alt_key, + ctrl_key, + meta_key, + shift_key, + + button: trigger_button.into_web_code(), + buttons: encode_mouse_button_set(held_buttons), + + client_x, + client_y, + offset_x, + offset_y, + page_x, + page_y, + screen_x, + screen_y, + } + } + /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). /// /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. @@ -620,31 +662,8 @@ pub mod on { /// The set of mouse buttons which were held when the event occurred. pub fn held_buttons(&self) -> MouseButtonSet { - let mut set = EnumSet::empty(); - - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons #[allow(deprecated)] - { - if self.buttons & 0b1 != 0 { - set |= MouseButton::Primary; - } - if self.buttons & 0b10 != 0 { - set |= MouseButton::Secondary; - } - if self.buttons & 0b100 != 0 { - set |= MouseButton::Auxiliary; - } - if self.buttons & 0b1000 != 0 { - set |= MouseButton::Fourth; - } - if self.buttons & 0b10000 != 0 { - set |= MouseButton::Fifth; - } - if self.buttons & (!0b11111) != 0 { - set |= MouseButton::Unknown; - } - } - set + decode_mouse_button_set(self.buttons) } /// The mouse button that triggered the event diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs index 7c722b384..b5bc73a44 100644 --- a/packages/html/src/input.rs +++ b/packages/html/src/input.rs @@ -41,7 +41,80 @@ impl MouseButton { _ => MouseButton::Unknown, } } + + /// Converts MouseButton into the corresponding button code + /// + /// MouseButton::Unknown will get mapped to -1 + pub fn into_web_code(self) -> i16 { + match self { + MouseButton::Primary => 0, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + MouseButton::Auxiliary => 1, + MouseButton::Secondary => 2, + MouseButton::Fourth => 3, + MouseButton::Fifth => 4, + MouseButton::Unknown => -1, + } + } } /// A set of mouse buttons pub type MouseButtonSet = EnumSet; + +pub fn decode_mouse_button_set(code: u16) -> MouseButtonSet { + let mut set = EnumSet::empty(); + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + #[allow(deprecated)] + { + if code & 0b1 != 0 { + set |= MouseButton::Primary; + } + if code & 0b10 != 0 { + set |= MouseButton::Secondary; + } + if code & 0b100 != 0 { + set |= MouseButton::Auxiliary; + } + if code & 0b1000 != 0 { + set |= MouseButton::Fourth; + } + if code & 0b10000 != 0 { + set |= MouseButton::Fifth; + } + if code & (!0b11111) != 0 { + set |= MouseButton::Unknown; + } + } + + set +} + +pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 { + let mut code = 0; + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + { + if set.contains(MouseButton::Primary) { + code |= 0b1; + } + if set.contains(MouseButton::Secondary) { + code |= 0b10; + } + if set.contains(MouseButton::Auxiliary) { + code |= 0b100; + } + if set.contains(MouseButton::Fourth) { + code |= 0b1000; + } + if set.contains(MouseButton::Fifth) { + code |= 0b10000; + } + if set.contains(MouseButton::Unknown) { + code |= 0b100000; + } + } + + code +} From 9c6cd42ca24119358a69653e1934fe0bae9221bc Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:49:49 +0300 Subject: [PATCH 085/105] TUI: use constructor instead of deprecated fields --- packages/html/src/events.rs | 12 ++++--- packages/tui/src/hooks.rs | 65 +++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 4f2507292..fa1791e60 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -9,7 +9,6 @@ pub mod on { use crate::input::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; - use enumset::EnumSet; use keyboard_types::Modifiers; use std::collections::HashMap; @@ -570,9 +569,12 @@ pub mod on { } impl MouseData { + /// Construct MouseData with the specified properties + /// + /// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part. pub fn new( coordinates: Coordinates, - trigger_button: MouseButton, + trigger_button: Option, held_buttons: MouseButtonSet, modifiers: Modifiers, ) -> Self { @@ -593,7 +595,7 @@ pub mod on { meta_key, shift_key, - button: trigger_button.into_web_code(), + button: trigger_button.map_or(0, |b| b.into_web_code()), buttons: encode_mouse_button_set(held_buttons), client_x, @@ -670,9 +672,9 @@ pub mod on { /// // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. - pub fn trigger_button(&self) -> MouseButton { + pub fn trigger_button(&self) -> Option { #[allow(deprecated)] - MouseButton::from_web_code(self.button) + Some(MouseButton::from_web_code(self.button)) } } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 04d320fcb..722c2598c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,6 +4,10 @@ use crossterm::event::{ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; +use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; +use dioxus_html::input::keyboard_types::Modifiers; +use dioxus_html::input::MouseButton as DioxusMouseButton; +use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, @@ -588,40 +592,45 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { let ctrl = m.modifiers.contains(KeyModifiers::CONTROL); let meta = false; - let get_mouse_data = |b| { - let buttons = match b { - None => 0, - Some(MouseButton::Left) => 1, - Some(MouseButton::Right) => 2, - Some(MouseButton::Middle) => 4, - }; - let button_state = match b { - None => 0, - Some(MouseButton::Left) => 0, - Some(MouseButton::Middle) => 1, - Some(MouseButton::Right) => 2, + let get_mouse_data = |crossterm_button: Option| { + let button = crossterm_button.map(|b| match b { + MouseButton::Left => DioxusMouseButton::Primary, + MouseButton::Right => DioxusMouseButton::Secondary, + MouseButton::Middle => DioxusMouseButton::Auxiliary, + }); + + let button_set = match button { + None => DioxusMouseButtons::empty(), + Some(button) => DioxusMouseButtons::only(button), }; + // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. - EventData::Mouse(MouseData { - alt_key: alt, - button: button_state, - buttons, - client_x: x, - client_y: y, - ctrl_key: ctrl, - meta_key: meta, - offset_x: x, - offset_y: y, - page_x: x, - page_y: y, - screen_x: x, - screen_y: y, - shift_key: shift, - }) + let coordinates = Coordinates::new( + ScreenPoint::new(x, y), + ClientPoint::new(x, y), + ElementPoint::new(x, y), + PagePoint::new(x, y), + ); + + let mut modifiers = Modifiers::empty(); + if shift { + modifiers.insert(Modifiers::SHIFT); + } + if ctrl { + modifiers.insert(Modifiers::CONTROL); + } + if meta { + modifiers.insert(Modifiers::META); + } + if alt { + modifiers.insert(Modifiers::ALT); + } + + EventData::Mouse(MouseData::new(coordinates, button, button_set, modifiers)) }; let get_wheel_data = |up| { From 2eca71e6443850330e9f7dcffd06c1b6fd9f3300 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 12:17:47 +0300 Subject: [PATCH 086/105] Fix: serialize AttributeValue as a simple value --- packages/core/src/arbitrary_value.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs index 792bf405c..27f9ac62a 100644 --- a/packages/core/src/arbitrary_value.rs +++ b/packages/core/src/arbitrary_value.rs @@ -2,6 +2,7 @@ use std::fmt::Formatter; // trying to keep values at 3 bytes #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serialize", serde(untagged))] #[derive(Clone, Debug, PartialEq)] pub enum AttributeValue<'a> { Text(&'a str), From 85aa4584ff67e9be01f98dafb05b02604c8e01ea Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 13:43:40 +0300 Subject: [PATCH 087/105] Fix: expose AttributeValue; update tests to pass --- packages/core/src/arbitrary_value.rs | 6 ++++++ packages/core/src/lib.rs | 8 ++++---- packages/native-core-macro/tests/update_state.rs | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs index 27f9ac62a..f30e0a2e8 100644 --- a/packages/core/src/arbitrary_value.rs +++ b/packages/core/src/arbitrary_value.rs @@ -1,9 +1,11 @@ use std::fmt::Formatter; +/// Possible values for an attribute // trying to keep values at 3 bytes #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", serde(untagged))] #[derive(Clone, Debug, PartialEq)] +#[allow(missing_docs)] pub enum AttributeValue<'a> { Text(&'a str), Float32(f32), @@ -26,6 +28,8 @@ pub enum AttributeValue<'a> { Any(ArbitraryAttributeValue<'a>), } +// todo +#[allow(missing_docs)] impl<'a> AttributeValue<'a> { pub fn is_truthy(&self) -> bool { match self { @@ -113,6 +117,8 @@ impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> { } } +// todo +#[allow(missing_docs)] impl<'a> AttributeValue<'a> { pub fn as_text(&self) -> Option<&'a str> { match self { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 708df1020..f69fe925b 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,10 +73,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - AnyEvent, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, - EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, - Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, VComponent, - VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom, + AnyEvent, Attribute, AttributeValue, Component, DioxusElement, DomEdit, Element, ElementId, + ElementIdIterator, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, + NodeFactory, Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, + VComponent, VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index dd0e09f73..5cd23732e 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -1,4 +1,5 @@ use anymap::AnyMap; +use dioxus_core::AttributeValue; use dioxus_core::VNode; use dioxus_core::*; use dioxus_core_macro::*; @@ -347,7 +348,7 @@ fn state_reduce_parent_called_minimally_on_update() { edits: vec![DomEdit::SetAttribute { root: 1, field: "width", - value: "99%", + value: AttributeValue::Text("99%"), ns: Some("style"), }], dirty_scopes: fxhash::FxHashSet::default(), @@ -416,7 +417,7 @@ fn state_reduce_child_called_minimally_on_update() { edits: vec![DomEdit::SetAttribute { root: 4, field: "width", - value: "99%", + value: AttributeValue::Text("99%"), ns: Some("style"), }], dirty_scopes: fxhash::FxHashSet::default(), From f48baae0b04a9318a0a04009e66f52228019f7c9 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 13:54:59 +0300 Subject: [PATCH 088/105] CI: Update gh jobs to install libayatana --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 27116f17d..7362a8e40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev - uses: actions-rs/cargo@v1 with: command: check @@ -56,7 +56,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev - uses: davidB/rust-cargo-make@v1 - uses: browser-actions/setup-firefox@latest - uses: jetli/wasm-pack-action@v0.3.0 @@ -96,7 +96,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: @@ -117,7 +117,7 @@ jobs: # run: | # apt-get update &&\ # apt-get install build-essential &&\ - # apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\ + # apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev -y &&\ # cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml # - name: Upload to codecov.io # uses: codecov/codecov-action@v2 From 285a6d080befee2eddb3948fdb73c679450f4ccc Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 15:32:19 +0300 Subject: [PATCH 089/105] Add coordinates to tui_hover example --- examples/tui_hover.rs | 29 ++++++++++++++++++++++++++++- packages/tui/src/hooks.rs | 25 +++++++++++++++++++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/examples/tui_hover.rs b/examples/tui_hover.rs index 2e6f91643..2de409f17 100644 --- a/examples/tui_hover.rs +++ b/examples/tui_hover.rs @@ -1,6 +1,7 @@ use std::{convert::TryInto, sync::Arc}; use dioxus::{events::MouseData, prelude::*}; +use dioxus_core::UiEvent; fn main() { dioxus::tui::launch(app); @@ -26,6 +27,24 @@ fn app(cx: Scope) -> Element { let q3_color_str = to_str(q3_color); let q4_color_str = to_str(q4_color); + let page_coordinates = use_state(&cx, || "".to_string()); + let element_coordinates = use_state(&cx, || "".to_string()); + let buttons = use_state(&cx, || "".to_string()); + let modifiers = use_state(&cx, || "".to_string()); + + let update_data = move |event: UiEvent| { + let mouse_data = event.data; + + page_coordinates.set(format!("{:?}", mouse_data.page_coordinates())); + element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); + + // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. + // There are also screen coordinates, but they are currently the same as client coordinates due to technical limitations + + buttons.set(format!("{:?}", mouse_data.held_buttons())); + modifiers.set(format!("{:?}", mouse_data.modifiers())); + }; + cx.render(rsx! { div { width: "100%", @@ -48,6 +67,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]), onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]), onmouseleave: move |_| q1_color.set([200; 3]), + onmousemove: update_data, "click me" } div { @@ -61,6 +81,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]), onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta_y) as i32;3]), onmouseleave: move |_| q2_color.set([200; 3]), + onmousemove: update_data, "click me" } } @@ -80,6 +101,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]), onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]), onmouseleave: move |_| q3_color.set([200; 3]), + onmousemove: update_data, "click me" } div { @@ -93,9 +115,14 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]), onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]), onmouseleave: move |_| q4_color.set([200; 3]), + onmousemove: update_data, "click me" } - } + }, + div {"Page coordinates: {page_coordinates}"}, + div {"Element coordinates: {element_coordinates}"}, + div {"Buttons: {buttons}"}, + div {"Modifiers: {modifiers}"}, } }) } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 077787666..350b836a4 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -16,6 +16,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use stretch2::geometry::Point; use stretch2::{prelude::Layout, Stretch}; use crate::{Dom, Node}; @@ -225,10 +226,26 @@ impl InnerInputState { } fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData { - let mut data = mouse_data.clone(); - data.offset_x = data.client_x - layout.location.x as i32; - data.offset_y = data.client_y - layout.location.y as i32; - data + let Point { x, y } = layout.location; + let node_origin = ClientPoint::new(x.into(), y.into()); + + let new_client_coordinates = (mouse_data.client_coordinates() - node_origin) + .to_point() + .cast_unit(); + + let coordinates = Coordinates::new( + mouse_data.screen_coordinates(), + mouse_data.client_coordinates(), + new_client_coordinates, + mouse_data.page_coordinates(), + ); + + MouseData::new( + coordinates, + mouse_data.trigger_button(), + mouse_data.held_buttons(), + mouse_data.modifiers(), + ) } if let Some(mouse) = &self.mouse { From 81d3c990bfea22ff02e459c874c81555dfbecfcf Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 16:11:48 +0300 Subject: [PATCH 090/105] Simplify mouse button tracking delete the shitcodes --- examples/tui_hover.rs | 2 +- packages/html/src/events.rs | 9 +++ packages/tui/src/hooks.rs | 108 +++++++++++++++--------------------- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/examples/tui_hover.rs b/examples/tui_hover.rs index 2de409f17..136213c25 100644 --- a/examples/tui_hover.rs +++ b/examples/tui_hover.rs @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { } fn get_brightness(m: Arc) -> i32 { - let b: i32 = m.buttons.count_ones().try_into().unwrap(); + let b: i32 = m.held_buttons().len().try_into().unwrap(); 127 * b } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index fa1791e60..64c9ecf68 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -639,6 +639,15 @@ pub mod on { ScreenPoint::new(self.screen_x.into(), self.screen_y.into()) } + pub fn coordinates(&self) -> Coordinates { + Coordinates::new( + self.screen_coordinates(), + self.client_coordinates(), + self.element_coordinates(), + self.page_coordinates(), + ) + } + /// The set of modifier keys which were pressed when the event occurred pub fn modifiers(&self) -> Modifiers { let mut modifiers = Modifiers::empty(); diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 350b836a4..c8d890d83 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -6,8 +6,8 @@ use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; use dioxus_html::input::keyboard_types::Modifiers; -use dioxus_html::input::MouseButton as DioxusMouseButton; use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; +use dioxus_html::input::{MouseButton as DioxusMouseButton, MouseButtonSet}; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, @@ -79,7 +79,7 @@ impl EventData { const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); pub struct InnerInputState { - mouse: Option<(MouseData, Vec)>, + mouse: Option, wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, screen: Option<(u16, u16)>, @@ -101,54 +101,38 @@ impl InnerInputState { fn apply_event(&mut self, evt: &mut EventCore) { match evt.1 { // limitations: only two buttons may be held at once - EventData::Mouse(ref mut m) => match &mut self.mouse { - Some(state) => { - let mut buttons = state.0.buttons; - state.0 = m.clone(); - match evt.0 { - // this code only runs when there are no buttons down - "mouseup" => { - buttons = 0; - state.1 = Vec::new(); - } - "mousedown" => { - if state.1.contains(&m.buttons) { - // if we already pressed a button and there is another button released the button crossterm sends is the button remaining - if state.1.len() > 1 { - evt.0 = "mouseup"; - state.1 = vec![m.buttons]; - } - // otherwise some other button was pressed. In testing it was consistantly this mapping - else { - match m.buttons { - 0x01 => state.1.push(0x02), - 0x02 => state.1.push(0x01), - 0x04 => state.1.push(0x01), - _ => (), - } - } - } else { - state.1.push(m.buttons); - } + EventData::Mouse(ref mut m) => { + let mut held_buttons = match &self.mouse { + Some(previous_data) => previous_data.held_buttons(), + None => MouseButtonSet::empty(), + }; - buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); - } - _ => (), + match evt.0 { + "mousedown" => { + held_buttons.insert( + m.trigger_button() + .expect("No trigger button for mousedown event"), + ); } - state.0.buttons = buttons; - m.buttons = buttons; + "mouseup" => { + held_buttons.remove( + m.trigger_button() + .expect("No trigger button for mouseup event"), + ); + } + _ => {} } - None => { - self.mouse = Some(( - m.clone(), - if m.buttons == 0 { - Vec::new() - } else { - vec![m.buttons] - }, - )); - } - }, + + let new_mouse_data = MouseData::new( + m.coordinates(), + m.trigger_button(), + held_buttons, + m.modifiers(), + ); + + self.mouse = Some(new_mouse_data.clone()); + *m = new_mouse_data; + } EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { @@ -171,7 +155,7 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); + let previous_mouse = self.mouse.clone(); self.wheel = None; @@ -188,7 +172,7 @@ impl InnerInputState { fn resolve_mouse_events( &self, - previous_mouse: Option<(MouseData, Vec)>, + previous_mouse: Option, resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, @@ -248,19 +232,16 @@ impl InnerInputState { ) } - if let Some(mouse) = &self.mouse { - let new_pos = (mouse.0.screen_x, mouse.0.screen_y); - let old_pos = previous_mouse - .as_ref() - .map(|m| (m.0.screen_x, m.0.screen_y)); + if let Some(mouse_data) = &self.mouse { + let new_pos = (mouse_data.screen_x, mouse_data.screen_y); + let old_pos = previous_mouse.as_ref().map(|m| (m.screen_x, m.screen_y)); // the a mouse button is pressed if a button was not down and is now down let pressed = - (mouse.0.buttons & !previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0; + (mouse_data.buttons & !previous_mouse.as_ref().map(|m| m.buttons).unwrap_or(0)) > 0; // the a mouse button is pressed if a button was down and is now not down let released = - (!mouse.0.buttons & previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0; + (!mouse_data.buttons & previous_mouse.map(|m| m.buttons).unwrap_or(0)) > 0; let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); - let mouse_data = &mouse.0; let wheel_data = &self.wheel; { @@ -603,11 +584,6 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { MouseButton::Middle => DioxusMouseButton::Auxiliary, }); - let button_set = match button { - None => DioxusMouseButtons::empty(), - Some(button) => DioxusMouseButtons::only(button), - }; - // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. @@ -635,7 +611,13 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { modifiers.insert(Modifiers::ALT); } - EventData::Mouse(MouseData::new(coordinates, button, button_set, modifiers)) + // held mouse buttons get set later by maintaining state, as crossterm does not provide them + EventData::Mouse(MouseData::new( + coordinates, + button, + DioxusMouseButtons::empty(), + modifiers, + )) }; let get_wheel_data = |up| { From df59d5148ed7d79dc39f872018e24e3b8139f8f8 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 16:27:34 +0300 Subject: [PATCH 091/105] TUI mouse press&release: Replace usage of deprecated fields with accessors --- packages/tui/src/hooks.rs | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index c8d890d83..1dcddf5f4 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,6 +4,7 @@ use crossterm::event::{ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; +use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; use dioxus_html::input::keyboard_types::Modifiers; use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; @@ -16,7 +17,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use stretch2::geometry::Point; +use stretch2::geometry::{Point, Size}; use stretch2::{prelude::Layout, Stretch}; use crate::{Dom, Node}; @@ -177,11 +178,12 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool { - layout.location.x as i32 <= point.0 - && layout.location.x as i32 + layout.size.width as i32 >= point.0 - && layout.location.y as i32 <= point.1 - && layout.location.y as i32 + layout.size.height as i32 >= point.1 + fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool { + let Point { x, y } = layout.location; + let Size { width, height } = layout.size; + + let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height)); + layout_rect.contains(point.cast()) } fn try_create_event( @@ -233,14 +235,19 @@ impl InnerInputState { } if let Some(mouse_data) = &self.mouse { - let new_pos = (mouse_data.screen_x, mouse_data.screen_y); - let old_pos = previous_mouse.as_ref().map(|m| (m.screen_x, m.screen_y)); - // the a mouse button is pressed if a button was not down and is now down - let pressed = - (mouse_data.buttons & !previous_mouse.as_ref().map(|m| m.buttons).unwrap_or(0)) > 0; - // the a mouse button is pressed if a button was down and is now not down - let released = - (!mouse_data.buttons & previous_mouse.map(|m| m.buttons).unwrap_or(0)) > 0; + let new_pos = mouse_data.screen_coordinates(); + let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates()); + + // a mouse button is pressed if a button was not down and is now down + let previous_buttons = previous_mouse + .map_or(MouseButtonSet::empty(), |previous_data| { + previous_data.held_buttons() + }); + let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty(); + + // a mouse button is released if a button was down and is now not down + let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty(); + let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); let wheel_data = &self.wheel; @@ -316,7 +323,7 @@ impl InnerInputState { } // mousedown - if pressed { + if was_pressed { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); @@ -342,7 +349,7 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released { + if currently_contains && was_released { try_create_event( "mouseup", Arc::new(prepare_mouse_data(mouse_data, node_layout)), @@ -362,7 +369,10 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 0 { + if currently_contains + && was_released + && mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) + { try_create_event( "click", Arc::new(prepare_mouse_data(mouse_data, node_layout)), @@ -382,7 +392,10 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 2 { + if currently_contains + && was_released + && mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) + { try_create_event( "contextmenu", Arc::new(prepare_mouse_data(mouse_data, node_layout)), From 83209e5e03dc85639abcd223a12b06480010afed Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 7 May 2022 09:10:17 -0500 Subject: [PATCH 092/105] check mouse state before iterating through elements --- packages/tui/src/hooks.rs | 115 ++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index c9d1e2a8c..57ca27dcd 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -335,81 +335,90 @@ impl InnerInputState { { // mouseup - let mut will_bubble = FxHashSet::default(); - for node in dom.get_listening_sorted("mouseup") { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, new_pos); + if released { + let mut will_bubble = FxHashSet::default(); + for node in dom.get_listening_sorted("mouseup") { + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released { - try_create_event( - "mouseup", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), - &mut will_bubble, - resolved_events, - node, - dom, - ); + if currently_contains { + try_create_event( + "mouseup", + Arc::new(prepare_mouse_data(mouse_data, node_layout)), + &mut will_bubble, + resolved_events, + node, + dom, + ); + } } } } { // click - let mut will_bubble = FxHashSet::default(); - for node in dom.get_listening_sorted("click") { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, new_pos); + if mouse_data.button == 0 { + let mut will_bubble = FxHashSet::default(); + for node in dom.get_listening_sorted("click") { + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 0 { - try_create_event( - "click", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), - &mut will_bubble, - resolved_events, - node, - dom, - ); + if currently_contains && released { + try_create_event( + "click", + Arc::new(prepare_mouse_data(mouse_data, node_layout)), + &mut will_bubble, + resolved_events, + node, + dom, + ); + } } } } { // contextmenu - let mut will_bubble = FxHashSet::default(); - for node in dom.get_listening_sorted("contextmenu") { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, new_pos); + if mouse_data.button == 2 { + let mut will_bubble = FxHashSet::default(); + for node in dom.get_listening_sorted("contextmenu") { + let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 2 { - try_create_event( - "contextmenu", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), - &mut will_bubble, - resolved_events, - node, - dom, - ); + if currently_contains && released { + try_create_event( + "contextmenu", + Arc::new(prepare_mouse_data(mouse_data, node_layout)), + &mut will_bubble, + resolved_events, + node, + dom, + ); + } } } } { // wheel - let mut will_bubble = FxHashSet::default(); - for node in dom.get_listening_sorted("wheel") { - let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); - let currently_contains = layout_contains_point(node_layout, new_pos); + if let Some(w) = wheel_data { + if wheel_delta != 0.0 { + let mut will_bubble = FxHashSet::default(); + for node in dom.get_listening_sorted("wheel") { + let node_layout = + layout.layout(node.state.layout.node.unwrap()).unwrap(); + let currently_contains = layout_contains_point(node_layout, new_pos); - if let Some(w) = wheel_data { - if currently_contains && wheel_delta != 0.0 { - try_create_event( - "wheel", - Arc::new(w.clone()), - &mut will_bubble, - resolved_events, - node, - dom, - ); + if currently_contains { + try_create_event( + "wheel", + Arc::new(w.clone()), + &mut will_bubble, + resolved_events, + node, + dom, + ); + } } } } From 2ea587359b7a8dd7a794e10b2190afd05ed9fb82 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 7 May 2022 09:10:34 -0500 Subject: [PATCH 093/105] only re-render if the state has changed --- 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 9f6bd78ed..012d92fed 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -129,7 +129,7 @@ fn render_vdom( terminal.clear().unwrap(); } - let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut resized = true; loop { @@ -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(); + to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap(); } } From 6f260e8a4a6a38fd0abe74250fd2baffd8dd5657 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 7 May 2022 09:16:21 -0500 Subject: [PATCH 094/105] move released into mouse condition --- packages/tui/src/hooks.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 57ca27dcd..9def817a8 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -357,13 +357,13 @@ impl InnerInputState { { // click - if mouse_data.button == 0 { + if mouse_data.button == 0 && released { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("click") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released { + if currently_contains { try_create_event( "click", Arc::new(prepare_mouse_data(mouse_data, node_layout)), @@ -379,13 +379,13 @@ impl InnerInputState { { // contextmenu - if mouse_data.button == 2 { + if mouse_data.button == 2 && released { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("contextmenu") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released { + if currently_contains { try_create_event( "contextmenu", Arc::new(prepare_mouse_data(mouse_data, node_layout)), From 5bddafb9439d40a3f67e4c4a9a587cdd9024554e Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 18:28:15 +0300 Subject: [PATCH 095/105] Update tui_keys example and web_sys to use new accessor methods --- examples/tui_keys.rs | 16 +++++---- packages/html/src/web_sys_bind/events.rs | 43 +++++++++++++++--------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index 4fde8c2c0..11a96a9b2 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -1,5 +1,7 @@ use dioxus::events::WheelEvent; use dioxus::prelude::*; +use dioxus_html::geometry::ScreenPoint; +use dioxus_html::input::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; use dioxus_html::KeyCode; @@ -9,9 +11,9 @@ fn main() { fn app(cx: Scope) -> Element { let key = use_state(&cx, || "".to_string()); - let mouse = use_state(&cx, || (0, 0)); + let mouse = use_state(&cx, || ScreenPoint::zero()); let count = use_state(&cx, || 0); - let buttons = use_state(&cx, || 0); + let buttons = use_state(&cx, || MouseButtonSet::empty()); let mouse_clicked = use_state(&cx, || false); cx.render(rsx! { @@ -36,21 +38,21 @@ fn app(cx: Scope) -> Element { count.set(count + evt.data.delta_y as i64); }, ondrag: move |evt: MouseEvent| { - mouse.set((evt.data.screen_x, evt.data.screen_y)); + mouse.set(evt.data.screen_coordinates()); }, onmousedown: move |evt: MouseEvent| { - mouse.set((evt.data.screen_x, evt.data.screen_y)); - buttons.set(evt.data.buttons); + mouse.set(evt.data.screen_coordinates()); + buttons.set(evt.data.held_buttons()); mouse_clicked.set(true); }, onmouseup: move |evt: MouseEvent| { - buttons.set(evt.data.buttons); + buttons.set(evt.data.held_buttons()); mouse_clicked.set(false); }, "count: {count:?}", "key: {key}", - "mouse buttons: {buttons:b}", + "mouse buttons: {buttons:?}", "mouse pos: {mouse:?}", "mouse button pressed: {mouse_clicked}" } diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index d43130193..0bf1179e4 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -1,8 +1,11 @@ +use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; +use crate::input::{decode_mouse_button_set, MouseButton}; use crate::on::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, }; use crate::KeyCode; +use keyboard_types::Modifiers; use wasm_bindgen::JsCast; use web_sys::{ AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent, @@ -71,22 +74,32 @@ impl From<&KeyboardEvent> for KeyboardData { impl From<&MouseEvent> for MouseData { fn from(e: &MouseEvent) -> Self { - Self { - alt_key: e.alt_key(), - button: e.button(), - buttons: e.buttons(), - client_x: e.client_x(), - client_y: e.client_y(), - ctrl_key: e.ctrl_key(), - meta_key: e.meta_key(), - offset_x: e.offset_x(), - offset_y: e.offset_y(), - screen_x: e.screen_x(), - screen_y: e.screen_y(), - shift_key: e.shift_key(), - page_x: e.page_x(), - page_y: e.page_y(), + let mut modifiers = Modifiers::empty(); + + if e.alt_key() { + modifiers.insert(Modifiers::ALT); } + if e.ctrl_key() { + modifiers.insert(Modifiers::CONTROL); + } + if e.meta_key() { + modifiers.insert(Modifiers::META); + } + if e.shift_key() { + modifiers.insert(Modifiers::SHIFT); + } + + MouseData::new( + Coordinates::new( + ScreenPoint::new(e.screen_x().into(), e.screen_y().into()), + ClientPoint::new(e.client_x().into(), e.client_y().into()), + ElementPoint::new(e.offset_x().into(), e.offset_y().into()), + PagePoint::new(e.page_x().into(), e.page_y().into()), + ), + Some(MouseButton::from_web_code(e.button().into())), + decode_mouse_button_set(e.buttons()), + modifiers, + ) } } From 95573d1f547628ef1d634438778b10d54f237bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rowin=20Hern=C3=A1ndez?= Date: Mon, 9 May 2022 18:48:50 +0100 Subject: [PATCH 096/105] Update useref.rs defualt -> default --- packages/hooks/src/useref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 5fa8eb9f0..6a384a5ff 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -24,7 +24,7 @@ use std::{ /// val.write().insert(1, "hello".to_string()); /// ``` /// -/// You can avoid this defualt behavior with `write_silent` +/// You can avoid this default behavior with `write_silent` /// /// ``` /// // with `write_silent`, the component will not be re-rendered From b7e9dca87cd9b8b566966403cd6a7ce9bdaa67de Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:09:31 +0800 Subject: [PATCH 097/105] fix: add eval script failed log --- packages/desktop/src/desktop_context.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 0b37910b0..93aad7ee8 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -193,9 +193,11 @@ pub(super) fn handler( DevTool => {} - Eval(code) => webview - .evaluate_script(code.as_str()) - .expect("eval shouldn't panic"), + Eval(code) => { + if let Err(e) = webview.evaluate_script(code.as_str()) { + log::error!("Eval script error: {e}"); + } + } } } From 8aadeff9d207f57f09b45d95e6b4d4190f32ef1e Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:29:31 +0800 Subject: [PATCH 098/105] fix: add eval script failed log --- packages/desktop/src/desktop_context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 93aad7ee8..6479e825a 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -195,7 +195,8 @@ pub(super) fn handler( Eval(code) => { if let Err(e) = webview.evaluate_script(code.as_str()) { - log::error!("Eval script error: {e}"); + // we can't panic this error. + log::warn!("Eval script error: {e}"); } } } From 4f55fcb84ab088a544ade666cc04f58f3c9cd9e9 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:49:46 +0800 Subject: [PATCH 099/105] feat: use_window add `set_zoom_level` --- packages/desktop/src/desktop_context.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 6479e825a..39fd5fd16 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -115,6 +115,11 @@ impl DesktopContext { let _ = self.proxy.send_event(SetDecorations(decoration)); } + /// set window zoom level + pub fn set_zoom_level(&self, scale_factor: f64) { + let _ = self.proxy.send_event(SetZoomLevel(scale_factor)); + } + /// opens DevTool window pub fn devtool(&self) { let _ = self.proxy.send_event(DevTool); @@ -148,6 +153,8 @@ pub enum UserWindowEvent { SetTitle(String), SetDecorations(bool), + SetZoomLevel(f64), + DevTool, Eval(String), @@ -191,6 +198,8 @@ pub(super) fn handler( SetTitle(content) => window.set_title(&content), SetDecorations(state) => window.set_decorations(state), + SetZoomLevel(scale_factor) => webview.zoom(scale_factor), + DevTool => {} Eval(code) => { From 77a92a1cf622306ee3bfcd259ccf8d8a0bc996ea Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:56:37 +0800 Subject: [PATCH 100/105] feat: use_window add `print` & `devtool` --- packages/desktop/src/desktop_context.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 39fd5fd16..3d4bf0b26 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -120,6 +120,11 @@ impl DesktopContext { let _ = self.proxy.send_event(SetZoomLevel(scale_factor)); } + /// launch print modal + pub fn print(&self) { + let _ = self.proxy.send_event(Print); + } + /// opens DevTool window pub fn devtool(&self) { let _ = self.proxy.send_event(DevTool); @@ -155,6 +160,7 @@ pub enum UserWindowEvent { SetZoomLevel(f64), + Print, DevTool, Eval(String), @@ -200,7 +206,13 @@ pub(super) fn handler( SetZoomLevel(scale_factor) => webview.zoom(scale_factor), - DevTool => {} + Print => { + if let Err(e) = webview.print() { + // we can't panic this error. + log::warn!("Open print modal failed: {e}"); + } + } + DevTool => webview.open_devtools(), Eval(code) => { if let Err(e) = webview.evaluate_script(code.as_str()) { From 765baf6e48c67d6445f1cf3b4004f9217d9cd03a Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 11 May 2022 13:47:58 +0300 Subject: [PATCH 101/105] Rename module `input` to avoid naming conflict with element of the same name Not sure if this was supposed to be allowed, but it was causing https://github.com/rust-lang/rust/issues/96934 --- examples/tui_keys.rs | 2 +- packages/html/src/events.rs | 2 +- packages/html/src/{input.rs => input_data.rs} | 0 packages/html/src/lib.rs | 2 +- packages/html/src/web_sys_bind/events.rs | 2 +- packages/tui/src/hooks.rs | 6 +++--- 6 files changed, 7 insertions(+), 7 deletions(-) rename packages/html/src/{input.rs => input_data.rs} (100%) diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index 11a96a9b2..138eec2fe 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -1,7 +1,7 @@ use dioxus::events::WheelEvent; use dioxus::prelude::*; use dioxus_html::geometry::ScreenPoint; -use dioxus_html::input::MouseButtonSet; +use dioxus_html::input_data::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; use dioxus_html::KeyCode; diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 64c9ecf68..6d7cbe06e 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -6,7 +6,7 @@ pub mod on { //! Input events and associated data use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{ + use crate::input_data::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; use keyboard_types::Modifiers; diff --git a/packages/html/src/input.rs b/packages/html/src/input_data.rs similarity index 100% rename from packages/html/src/input.rs rename to packages/html/src/input_data.rs diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 705a076e4..cb356af7e 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,7 +17,7 @@ mod elements; mod events; pub mod geometry; mod global_attributes; -pub mod input; +pub mod input_data; #[cfg(feature = "wasm-bind")] mod web_sys_bind; diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 0bf1179e4..a69ac5fef 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -1,5 +1,5 @@ use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; -use crate::input::{decode_mouse_button_set, MouseButton}; +use crate::input_data::{decode_mouse_button_set, MouseButton}; use crate::on::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 74af56bef..5481d6b1c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -6,9 +6,9 @@ use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; -use dioxus_html::input::keyboard_types::Modifiers; -use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; -use dioxus_html::input::{MouseButton as DioxusMouseButton, MouseButtonSet}; +use dioxus_html::input_data::keyboard_types::Modifiers; +use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; +use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, From 77c22161b9ef9f1755c17fb3fea4293ce1c27ebe Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 00:03:42 +0800 Subject: [PATCH 102/105] example: add `window_zoom` example --- examples/window_zoom.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/window_zoom.rs diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs new file mode 100644 index 000000000..792ab6de5 --- /dev/null +++ b/examples/window_zoom.rs @@ -0,0 +1,23 @@ +use dioxus::desktop::use_window; +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let window = use_window(&cx); + + let level = use_state(&cx, || 1.0); + + window.set_zoom_level(*level.get()); + cx.render(rsx! { + input { + r#type: "number", + value: "{level_display}", + oninput: |e| { + level.set(e.value.parse::().unwrap_or_default()) + } + } + }) +} From 31a2ded2b9ad37f2ce4bc9492f4fea0afb6e9d53 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 00:05:59 +0800 Subject: [PATCH 103/105] example: add `window_zoom` example --- examples/window_zoom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 792ab6de5..3d185c78c 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { input { r#type: "number", - value: "{level_display}", + value: "{level}", oninput: |e| { level.set(e.value.parse::().unwrap_or_default()) } From 3f4897cd1c63f2c5d0600c9f86a62a90beb4bd95 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 08:41:30 +0800 Subject: [PATCH 104/105] example: update example --- examples/window_zoom.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 3d185c78c..cfb831550 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -9,14 +9,14 @@ fn app(cx: Scope) -> Element { let window = use_window(&cx); let level = use_state(&cx, || 1.0); - - window.set_zoom_level(*level.get()); cx.render(rsx! { input { r#type: "number", value: "{level}", oninput: |e| { - level.set(e.value.parse::().unwrap_or_default()) + let num = e.value.parse::().unwrap_or(1.0); + level.set(num); + window.set_zoom_level(num); } } }) From d6eaf8778fd3115b0a5e933960d17c519515f9ca Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 21 May 2022 18:08:52 -0500 Subject: [PATCH 105/105] handle backtab --- packages/tui/src/hooks.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 5481d6b1c..13cef2c41 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -664,6 +664,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { let (code, key_str); + let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT); if let TermKeyCode::Char(c) = event.code { code = match c { 'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() { @@ -777,7 +778,11 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { 12 => KeyCode::F12, _ => return None, }, - TermKeyCode::BackTab => return None, + // backtab is Shift + Tab + TermKeyCode::BackTab => { + shift_key = true; + KeyCode::Tab + } TermKeyCode::Null => return None, _ => return None, }; @@ -795,7 +800,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { alt_key: event.modifiers.contains(KeyModifiers::ALT), ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), meta_key: false, - shift_key: event.modifiers.contains(KeyModifiers::SHIFT), + shift_key, locale: Default::default(), location: 0x00, repeat: Default::default(),