From 71e34452dae596b6362b3c1048081259c1535683 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Feb 2023 14:14:04 -0600 Subject: [PATCH] create driven example for tui renderer --- packages/native-core/src/dioxus.rs | 29 ++-- packages/native-core/src/lib.rs | 8 +- packages/native-core/src/node.rs | 20 ++- packages/native-core/src/node_ref.rs | 3 +- packages/native-core/src/real_dom.rs | 25 +++- packages/native-core/src/tree.rs | 7 +- packages/tui/examples/driven.rs | 208 +++++++++++++++++++++++++++ packages/tui/src/hooks.rs | 4 - packages/tui/src/lib.rs | 4 +- packages/tui/src/render.rs | 2 +- 10 files changed, 276 insertions(+), 34 deletions(-) create mode 100644 packages/tui/examples/driven.rs diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index f524035a4..fa7ca4802 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -2,7 +2,10 @@ use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - node::{ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue}, + node::{ + ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, + TextNode, + }, prelude::NodeImmutable, real_dom::NodeTypeMut, NodeId, NodeMut, RealDom, @@ -83,7 +86,10 @@ impl DioxusState { self.stack.push(node_id); } CreateTextNode { value, id } => { - let node_data = NodeType::Text(value.to_string()); + let node_data = NodeType::Text(TextNode { + listeners: FxHashSet::default(), + text: value.to_string(), + }); let node = rdom.create_node(node_data); let node_id = node.id(); self.set_element_id(node, id); @@ -97,7 +103,10 @@ impl DioxusState { if let NodeTypeMut::Text(text) = node.node_type_mut() { *text = value.to_string(); } else { - node.set_type(NodeType::Text(value.to_string())); + node.set_type(NodeType::Text(TextNode { + text: value.to_string(), + listeners: FxHashSet::default(), + })); } } LoadTemplate { name, index, id } => { @@ -153,14 +162,12 @@ impl DioxusState { element.remove_attributes(&OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), - volatile: false, }); } else { element.set_attribute( OwnedAttributeDiscription { name: name.to_string(), namespace: ns.map(|s| s.to_string()), - volatile: false, }, OwnedAttributeValue::from(value), ); @@ -219,7 +226,6 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { OwnedAttributeDiscription { namespace: namespace.map(|s| s.to_string()), name: name.to_string(), - volatile: false, }, OwnedAttributeValue::Text(value.to_string()), )), @@ -235,9 +241,16 @@ fn create_template_node(rdom: &mut RealDom, node: &TemplateNode) -> NodeId { } node_id } - TemplateNode::Text { text } => rdom.create_node(NodeType::Text(text.to_string())).id(), + TemplateNode::Text { text } => rdom + .create_node(NodeType::Text(TextNode { + text: text.to_string(), + ..Default::default() + })) + .id(), TemplateNode::Dynamic { .. } => rdom.create_node(NodeType::Placeholder).id(), - TemplateNode::DynamicText { .. } => rdom.create_node(NodeType::Text(String::new())).id(), + TemplateNode::DynamicText { .. } => { + rdom.create_node(NodeType::Text(TextNode::default())).id() + } } } diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index eeb3b86e2..f5d3bab56 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -35,10 +35,8 @@ pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; pub type SendAnyMap = anymap::Map; -pub trait Renderer { +pub trait Renderer { fn render(&mut self, root: NodeMut); - fn handle_event(&mut self, node: NodeMut, event: &str, value: E); - fn poll_async(&mut self) -> Pin + Send>> { - Box::pin(async {}) - } + fn handle_event(&mut self, node: NodeMut, event: &str, value: E, bubbles: bool); + fn poll_async(&mut self) -> Pin + Send>>; } diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index f0fe7073a..51e096a97 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -1,7 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::{any::Any, fmt::Debug}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ElementNode { pub tag: String, pub namespace: Option, @@ -9,10 +9,25 @@ pub struct ElementNode { pub listeners: FxHashSet, } +#[derive(Debug, Clone, Default)] +pub struct TextNode { + pub text: String, + pub listeners: FxHashSet, +} + +impl TextNode { + pub fn new(text: String) -> Self { + Self { + text, + listeners: Default::default(), + } + } +} + /// A type of node with data specific to the node type. The types are a subset of the [VNode] types. #[derive(Debug, Clone)] pub enum NodeType { - Text(String), + Text(TextNode), Element(ElementNode), Placeholder, } @@ -21,7 +36,6 @@ pub enum NodeType { pub struct OwnedAttributeDiscription { pub name: String, pub namespace: Option, - pub volatile: bool, } /// An attribute on a DOM node, such as `id="my-thing"` or diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 99b4c53e2..e50374c0b 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -73,10 +73,11 @@ impl<'a, V: FromAnyValue> NodeView<'a, V> { self.mask .text .then_some(match &self.inner { - NodeType::Text(text) => Some(&**text), + NodeType::Text(text) => Some(&text.text), _ => None, }) .flatten() + .map(|x| &**x) } /// Get the listeners if it is enabled in the mask diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index f466d2803..9ca94bb4c 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::sync::RwLock; use crate::node::{ - ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, + ElementNode, FromAnyValue, NodeType, OwnedAttributeDiscription, OwnedAttributeValue, TextNode, }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::NodeWatcher; @@ -487,6 +487,19 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn remove(&mut self) { let id = self.id(); + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = + self.dom.get_state_mut_raw::>(id).unwrap() + { + let listeners = std::mem::take(listeners); + for event in listeners { + self.dom + .nodes_listening + .get_mut(&event) + .unwrap() + .remove(&id); + } + } self.mark_removed(); if let Some(parent_id) = self.real_dom_mut().tree.parent_id(id) { self.real_dom_mut() @@ -519,7 +532,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn add_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); - if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = node_type + { self.dom .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); @@ -540,7 +555,9 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { pub fn remove_event_listener(&mut self, event: &str) { let id = self.id(); let node_type: &mut NodeType = self.dom.tree.get_mut(self.id).unwrap(); - if let NodeType::Element(ElementNode { listeners, .. }) = node_type { + if let NodeType::Element(ElementNode { listeners, .. }) + | NodeType::Text(TextNode { listeners, .. }) = node_type + { self.dom .dirty_nodes .mark_dirty(self.id, NodeMaskBuilder::new().with_listeners().build()); @@ -579,7 +596,7 @@ impl<'a, V: FromAnyValue + Send + Sync> NodeMut<'a, V> { NodeType::Text(text) => { dirty_nodes.mark_dirty(self.id, NodeMaskBuilder::new().with_text().build()); - NodeTypeMut::Text(text) + NodeTypeMut::Text(&mut text.text) } NodeType::Placeholder => NodeTypeMut::Placeholder, } diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 580dd4848..1ba4f0bda 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -262,12 +262,7 @@ pub struct TreeStateViewEntry<'a, 'b> { impl<'a, 'b> AnyMapLike<'a> for TreeStateViewEntry<'a, 'b> { fn get(self) -> Option<&'a T> { let slab = self.view.get_slab(); - dbg!(slab.is_some()); - slab.and_then(|slab| { - let r = slab.get(self.id); - dbg!(r.is_some()); - r - }) + slab.and_then(|slab| slab.get(self.id)) } } diff --git a/packages/tui/examples/driven.rs b/packages/tui/examples/driven.rs new file mode 100644 index 000000000..de2ef0025 --- /dev/null +++ b/packages/tui/examples/driven.rs @@ -0,0 +1,208 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, Renderer, +}; +use dioxus_tui::{self, render, Config}; +use std::sync::{Arc, RwLock}; +use std::{rc::Rc, sync::Mutex}; +use taffy::Taffy; + +struct Test([[usize; 10]; 10]); + +impl Renderer> for Test { + fn render(&mut self, mut root: dioxus_native_core::NodeMut) { + // Set the root node to be a flexbox with a column direction. + if let NodeTypeMut::Element(mut el) = root.node_type_mut() { + el.set_attribute( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("column".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ); + el.set_attribute( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ); + } + + let root_id = root.id(); + // Remove old grid. Frameworks should retain the grid and only update the values. + let children_ids = root.child_ids().map(|ids| ids.to_vec()); + let rdom = root.real_dom_mut(); + if let Some(children) = children_ids { + for child in children { + rdom.get_mut(child).unwrap().remove(); + } + } + + // create the grid + for (x, row) in self.0.iter().copied().enumerate() { + let row_node = rdom + .create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ), + ( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("row".into()), + ), + ( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ] + .into_iter() + .collect(), + ..Default::default() + })) + .id(); + for (y, count) in row.iter().copied().enumerate() { + let node = rdom + .create_node(NodeType::Text(TextNode::new(count.to_string()))) + .id(); + let mut button = rdom.create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ( + OwnedAttributeDiscription { + name: "background-color".into(), + namespace: None, + }, + OwnedAttributeValue::Text(format!( + "rgb({}, {}, {})", + count * 10, + 0, + (x + y) * 10, + )), + ), + ( + OwnedAttributeDiscription { + name: "width".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "height".into(), + namespace: None, + }, + OwnedAttributeValue::Text("100%".into()), + ), + ( + OwnedAttributeDiscription { + name: "display".into(), + namespace: None, + }, + OwnedAttributeValue::Text("flex".into()), + ), + ( + OwnedAttributeDiscription { + name: "flex-direction".into(), + namespace: None, + }, + OwnedAttributeValue::Text("row".into()), + ), + ( + OwnedAttributeDiscription { + name: "justify-content".into(), + namespace: None, + }, + OwnedAttributeValue::Text("center".into()), + ), + ( + OwnedAttributeDiscription { + name: "align-items".into(), + namespace: None, + }, + OwnedAttributeValue::Text("center".into()), + ), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(node); + let button_id = button.id(); + rdom.get_mut(row_node).unwrap().add_child(button_id); + } + rdom.get_mut(root_id).unwrap().add_child(row_node); + } + } + + fn handle_event( + &mut self, + node: dioxus_native_core::NodeMut<()>, + event: &str, + value: Rc, + bubbles: bool, + ) { + if let Some(parent) = node.parent() { + let child_number = parent + .child_ids() + .unwrap() + .iter() + .position(|id| *id == node.id()) + .unwrap(); + if let Some(parents_parent) = parent.parent() { + let parents_child_number = parents_parent + .child_ids() + .unwrap() + .iter() + .position(|id| *id == parent.id()) + .unwrap(); + self.0[parents_child_number][child_number] += 1; + } + } + } + + fn poll_async(&mut self) -> std::pin::Pin + Send>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |_, _, _| Test(Default::default())).unwrap(); +} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 31b466816..8c509f592 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -75,7 +75,6 @@ pub struct InnerInputState { mouse: Option, wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, - screen: Option<(u16, u16)>, pub(crate) focus_state: FocusState, // subscribers: Vec>, } @@ -86,7 +85,6 @@ impl InnerInputState { mouse: None, wheel: None, last_key_pressed: None, - screen: None, // subscribers: Vec::new(), focus_state: FocusState::create(rdom), } @@ -181,7 +179,6 @@ impl InnerInputState { if old_focus != self.focus_state.last_focused_id { // elements with listeners will always have a element id if let Some(id) = self.focus_state.last_focused_id { - let element = dom.get(id).unwrap(); resolved_events.push(Event { name: "focus", id, @@ -196,7 +193,6 @@ impl InnerInputState { }); } if let Some(id) = old_focus { - let element = dom.get(id).unwrap(); resolved_events.push(Event { name: "focusout", id, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f39564a4c..30a207c23 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -68,7 +68,7 @@ impl TuiContext { } } -pub fn render>>( +pub fn render>>( cfg: Config, f: impl FnOnce(&Arc>, &Arc>, UnboundedSender) -> R, ) -> Result<()> { @@ -271,7 +271,7 @@ pub fn render>>( for e in evts { let node = rdom.get_mut(e.id).unwrap(); - renderer.handle_event(node, e.name, e.data); + renderer.handle_event(node, e.name, e.data, e.bubbles); } } let mut rdom = rdom.write().unwrap(); diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index fe6600ade..c10c52252 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -67,7 +67,7 @@ pub(crate) fn render_vnode( } let label = Label { - text, + text: &text.text, style: node.get::().unwrap().core, }; let area = Rect::new(x, y, width, height);