From ae1300b015eee92ce3a635b382075299e59da969 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Mar 2023 21:26:44 -0500 Subject: [PATCH] improve ergonomics of custom drivers and add a counter example to rink --- packages/dioxus-tui/src/lib.rs | 8 +- packages/native-core/src/node.rs | 48 +++++ packages/native-core/src/real_dom.rs | 6 +- packages/rink/examples/counter.rs | 104 +++++++++++ packages/rink/examples/driven.rs | 260 --------------------------- packages/rink/examples/grid.rs | 173 ++++++++++++++++++ packages/rink/examples/widgets.rs | 93 ---------- packages/rink/src/lib.rs | 10 +- 8 files changed, 338 insertions(+), 364 deletions(-) create mode 100644 packages/rink/examples/counter.rs delete mode 100644 packages/rink/examples/driven.rs create mode 100644 packages/rink/examples/grid.rs delete mode 100644 packages/rink/examples/widgets.rs diff --git a/packages/dioxus-tui/src/lib.rs b/packages/dioxus-tui/src/lib.rs index a0f016147..296fd4697 100644 --- a/packages/dioxus-tui/src/lib.rs +++ b/packages/dioxus-tui/src/lib.rs @@ -12,8 +12,8 @@ use dioxus_html::EventData; use dioxus_native_core::dioxus::{DioxusState, NodeImmutableDioxusExt}; use dioxus_native_core::prelude::*; -pub use rink::{query::Query, Config, Size, TuiContext, RenderingMode}; -use rink::{render, Renderer}; +pub use rink::{query::Query, Config, RenderingMode, Size, TuiContext}; +use rink::{render, Driver}; pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) @@ -66,8 +66,8 @@ struct DioxusRenderer { hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, } -impl Renderer for DioxusRenderer { - fn render(&mut self, rdom: &Arc>) { +impl Driver for DioxusRenderer { + fn update(&mut self, rdom: &Arc>) { let muts = self.vdom.render_immediate(); { let mut rdom = rdom.write().unwrap(); diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index a3362ada2..48f9397a4 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -62,6 +62,24 @@ pub struct OwnedAttributeDiscription { pub namespace: Option, } +impl From for OwnedAttributeDiscription { + fn from(name: String) -> Self { + Self { + name, + namespace: None, + } + } +} + +impl, N: Into> From<(S, N)> for OwnedAttributeDiscription { + fn from(name: (S, N)) -> Self { + Self { + name: name.0.into(), + namespace: Some(name.1.into()), + } + } +} + /// An attribute on a DOM node, such as `id="my-thing"` or /// `href="https://example.com"`. #[derive(Clone, Copy, Debug)] @@ -88,6 +106,36 @@ pub enum OwnedAttributeValue { Custom(V), } +impl From for OwnedAttributeValue { + fn from(value: String) -> Self { + Self::Text(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: f64) -> Self { + Self::Float(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: i64) -> Self { + Self::Int(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From for OwnedAttributeValue { + fn from(value: V) -> Self { + Self::Custom(value) + } +} + /// Something that can be converted from a borrowed [Any] value. pub trait FromAnyValue: Clone + 'static { /// Convert from an [Any] value. diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 016633fc7..1ac34a421 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -933,9 +933,11 @@ impl ElementNodeMut<'_, V> { /// Set an attribute in the element pub fn set_attribute( &mut self, - name: OwnedAttributeDiscription, - value: OwnedAttributeValue, + name: impl Into, + value: impl Into>, ) -> Option> { + let name = name.into(); + let value = value.into(); self.dirty_nodes.mark_dirty( self.id, NodeMaskBuilder::new() diff --git a/packages/rink/examples/counter.rs b/packages/rink/examples/counter.rs new file mode 100644 index 000000000..4d2428d8a --- /dev/null +++ b/packages/rink/examples/counter.rs @@ -0,0 +1,104 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::TextNode, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, +}; +use rink::{render, Config, Driver}; +use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +#[derive(Default)] +struct Counter { + count: usize, + counter_id: NodeId, + button_id: NodeId, +} + +impl Counter { + fn create(mut root: NodeMut) -> Self { + let mut myself = Self::default(); + + let root_id = root.id(); + let rdom = root.real_dom_mut(); + + // create the counter + let count = myself.count; + myself.counter_id = 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: [ + ("display".to_string().into(), "flex".to_string().into()), + ( + ("background-color", "style").into(), + format!("rgb({}, {}, {})", count * 10, 0, 0,).into(), + ), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + ( + ("justify-content", "style").into(), + "center".to_string().into(), + ), + (("align-items", "style").into(), "center".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(myself.counter_id); + myself.button_id = button.id(); + rdom.get_mut(root_id).unwrap().add_child(myself.button_id); + + myself + } +} + +impl Driver for Counter { + fn update(&mut self, rdom: &Arc>) { + // update the counter + let mut rdom = rdom.write().unwrap(); + let mut node = rdom.get_mut(self.button_id).unwrap(); + if let NodeTypeMut::Element(mut el) = node.node_type_mut() { + el.set_attribute( + ("background-color", "style"), + format!("rgb({}, {}, {})", self.count * 10, 0, 0,), + ); + } + let mut text = rdom.get_mut(self.counter_id).unwrap(); + let type_mut = text.node_type_mut(); + if let NodeTypeMut::Text(mut text) = type_mut { + *text = self.count.to_string(); + } + } + + fn handle_event( + &mut self, + _: &Arc>, + _: NodeId, + _: &str, + _: Rc, + _: bool, + ) { + // when a click or wheel event is fired, increment the counter + self.count += 1; + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |rdom, _, _| { + let mut rdom = rdom.write().unwrap(); + let root = rdom.root_id(); + Counter::create(rdom.get_mut(root).unwrap()) + }) + .unwrap(); +} diff --git a/packages/rink/examples/driven.rs b/packages/rink/examples/driven.rs deleted file mode 100644 index b5d231888..000000000 --- a/packages/rink/examples/driven.rs +++ /dev/null @@ -1,260 +0,0 @@ -use dioxus_html::EventData; -use dioxus_native_core::{ - node::{OwnedAttributeDiscription, OwnedAttributeValue, TextNode}, - prelude::*, - real_dom::{NodeImmutable, NodeTypeMut}, - NodeId, -}; -use dioxus_tui::{self, render, Config, Renderer}; -use rustc_hash::FxHashSet; -use std::rc::Rc; -use std::sync::{Arc, RwLock}; - -const SIZE: usize = 10; - -struct Test { - node_states: [[usize; SIZE]; SIZE], - dirty: FxHashSet<(usize, usize)>, -} - -impl Default for Test { - fn default() -> Self { - Self { - node_states: [[0; SIZE]; SIZE], - dirty: FxHashSet::default(), - } - } -} - -impl Test { - fn create(mut root: NodeMut) -> Self { - let myself = Self::default(); - - // 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(); - let rdom = root.real_dom_mut(); - - // create the grid - for (x, row) in myself.node_states.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); - } - myself - } -} - -impl Renderer for Test { - fn render(&mut self, rdom: &Arc>) { - let mut rdom = rdom.write().unwrap(); - let root_id = rdom.root_id(); - let mut root = rdom.get_mut(root_id).unwrap(); - for (x, y) in self.dirty.drain() { - let row_id = root.child_ids()[x]; - let rdom = root.real_dom_mut(); - let row = rdom.get(row_id).unwrap(); - let node_id = row.child_ids()[y]; - let mut node = rdom.get_mut(node_id).unwrap(); - if let NodeTypeMut::Element(mut el) = node.node_type_mut() { - el.set_attribute( - OwnedAttributeDiscription { - name: "background-color".into(), - namespace: None, - }, - OwnedAttributeValue::Text(format!( - "rgb({}, {}, {})", - self.node_states[x][y] * 10, - 0, - (x + y) * 10, - )), - ); - } - let text_id = *node.child_ids().first().unwrap(); - let mut text = rdom.get_mut(text_id).unwrap(); - let type_mut = text.node_type_mut(); - if let NodeTypeMut::Text(mut text) = type_mut { - *text = self.node_states[x][y].to_string(); - } - } - } - - fn handle_event( - &mut self, - rdom: &Arc>, - id: NodeId, - _: &str, - _: Rc, - _: bool, - ) { - let rdom = rdom.read().unwrap(); - let node = rdom.get(id).unwrap(); - if let Some(parent) = node.parent() { - let child_number = parent - .child_ids() - .iter() - .position(|id| *id == node.id()) - .unwrap(); - if let Some(parents_parent) = parent.parent() { - let parents_child_number = parents_parent - .child_ids() - .iter() - .position(|id| *id == parent.id()) - .unwrap(); - self.node_states[parents_child_number][child_number] += 1; - self.dirty.insert((parents_child_number, child_number)); - } - } - } - - fn poll_async(&mut self) -> std::pin::Pin + '_>> { - Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) - } -} - -fn main() { - render(Config::new(), |rdom, _, _| { - let mut rdom = rdom.write().unwrap(); - let root = rdom.root_id(); - Test::create(rdom.get_mut(root).unwrap()) - }) - .unwrap(); -} diff --git a/packages/rink/examples/grid.rs b/packages/rink/examples/grid.rs new file mode 100644 index 000000000..7abc4669f --- /dev/null +++ b/packages/rink/examples/grid.rs @@ -0,0 +1,173 @@ +use dioxus_html::EventData; +use dioxus_native_core::{ + node::TextNode, + prelude::*, + real_dom::{NodeImmutable, NodeTypeMut}, + NodeId, +}; +use rink::{render, Config, Driver}; +use rustc_hash::FxHashSet; +use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +const SIZE: usize = 20; + +#[derive(Default, Clone, Copy)] +struct Node { + container_id: Option, + text_id: Option, + count: usize, +} + +struct Test { + node_states: [[Node; SIZE]; SIZE], + dirty: FxHashSet<(usize, usize)>, +} + +impl Default for Test { + fn default() -> Self { + Self { + node_states: [[Node { + container_id: None, + text_id: None, + count: 0, + }; SIZE]; SIZE], + dirty: FxHashSet::default(), + } + } +} + +impl Test { + fn create(mut root: NodeMut) -> Self { + let mut myself = Self::default(); + + // 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("display".to_string(), "flex".to_string()); + el.set_attribute(("flex-direction", "style"), "column".to_string()); + el.set_attribute(("width", "style"), "100%".to_string()); + el.set_attribute(("height", "style"), "100%".to_string()); + } + + let root_id = root.id(); + let rdom = root.real_dom_mut(); + + // create the grid + for (x, row) in myself.node_states.iter_mut().enumerate() { + let row_node = rdom + .create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [ + ("display".to_string().into(), "flex".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })) + .id(); + for (y, node) in row.iter_mut().enumerate() { + let count = node.count; + let id = 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: [ + ("display".to_string().into(), "flex".to_string().into()), + ( + ("background-color", "style").into(), + format!("rgb({}, {}, {})", count * 10, 0, (x + y),).into(), + ), + (("width", "style").into(), "100%".to_string().into()), + (("height", "style").into(), "100%".to_string().into()), + (("flex-direction", "style").into(), "row".to_string().into()), + ( + ("justify-content", "style").into(), + "center".to_string().into(), + ), + (("align-items", "style").into(), "center".to_string().into()), + ] + .into_iter() + .collect(), + ..Default::default() + })); + button.add_event_listener("click"); + button.add_event_listener("wheel"); + button.add_child(id); + let button_id = button.id(); + rdom.get_mut(row_node).unwrap().add_child(button_id); + node.container_id = Some(button_id); + node.text_id = Some(id); + } + rdom.get_mut(root_id).unwrap().add_child(row_node); + } + myself + } +} + +impl Driver for Test { + fn update(&mut self, rdom: &Arc>) { + let mut rdom = rdom.write().unwrap(); + for (x, y) in self.dirty.drain() { + let node = self.node_states[x][y]; + let node_id = node.container_id.unwrap(); + let mut container = rdom.get_mut(node_id).unwrap(); + if let NodeTypeMut::Element(mut el) = container.node_type_mut() { + el.set_attribute( + ("background-color", "style"), + format!("rgb({}, {}, {})", node.count * 10, 0, (x + y),), + ); + } + let text_id = node.text_id.unwrap(); + let mut text = rdom.get_mut(text_id).unwrap(); + let type_mut = text.node_type_mut(); + if let NodeTypeMut::Text(mut text) = type_mut { + *text = node.count.to_string(); + } + } + } + + fn handle_event( + &mut self, + rdom: &Arc>, + id: NodeId, + _: &str, + _: Rc, + _: bool, + ) { + let rdom = rdom.read().unwrap(); + let node = rdom.get(id).unwrap(); + if let Some(parent) = node.parent() { + let child_number = parent + .child_ids() + .iter() + .position(|id| *id == node.id()) + .unwrap(); + if let Some(parents_parent) = parent.parent() { + let parents_child_number = parents_parent + .child_ids() + .iter() + .position(|id| *id == parent.id()) + .unwrap(); + self.node_states[parents_child_number][child_number].count += 1; + self.dirty.insert((parents_child_number, child_number)); + } + } + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + Box::pin(async move { tokio::time::sleep(std::time::Duration::from_millis(1000)).await }) + } +} + +fn main() { + render(Config::new(), |rdom, _, _| { + let mut rdom = rdom.write().unwrap(); + let root = rdom.root_id(); + Test::create(rdom.get_mut(root).unwrap()) + }) + .unwrap(); +} diff --git a/packages/rink/examples/widgets.rs b/packages/rink/examples/widgets.rs deleted file mode 100644 index 793057bc5..000000000 --- a/packages/rink/examples/widgets.rs +++ /dev/null @@ -1,93 +0,0 @@ -use dioxus::prelude::*; -use dioxus_html::FormData; -use dioxus_tui::prelude::*; -use dioxus_tui::Config; - -fn main() { - dioxus_tui::launch_cfg(app, Config::new()); -} - -fn app(cx: Scope) -> Element { - let bg_green = use_state(cx, || false); - - let color = if *bg_green.get() { "green" } else { "red" }; - cx.render(rsx! { - div{ - width: "100%", - background_color: "{color}", - flex_direction: "column", - align_items: "center", - justify_content: "center", - - Input{ - oninput: |data: FormData| if &data.value == "good"{ - bg_green.set(true); - } else{ - bg_green.set(false); - }, - r#type: "checkbox", - value: "good", - width: "50%", - height: "10%", - checked: "true", - } - Input{ - oninput: |data: FormData| if &data.value == "hello world"{ - bg_green.set(true); - } else{ - bg_green.set(false); - }, - width: "50%", - height: "10%", - maxlength: "11", - } - Input{ - oninput: |data: FormData| { - if (data.value.parse::().unwrap() - 40.0).abs() < 5.0 { - bg_green.set(true); - } else{ - bg_green.set(false); - } - }, - r#type: "range", - width: "50%", - height: "10%", - min: "20", - max: "80", - } - Input{ - oninput: |data: FormData| { - if data.value == "10"{ - bg_green.set(true); - } else{ - bg_green.set(false); - } - }, - r#type: "number", - width: "50%", - height: "10%", - maxlength: "4", - } - Input{ - oninput: |data: FormData| { - if data.value == "hello world"{ - bg_green.set(true); - } else{ - bg_green.set(false); - } - }, - r#type: "password", - width: "50%", - height: "10%", - maxlength: "11", - } - Input{ - onclick: |_: FormData| bg_green.set(true), - r#type: "button", - value: "green", - width: "50%", - height: "10%", - } - } - }) -} diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index b8bcc1dac..1111d73dc 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -76,7 +76,7 @@ impl TuiContext { } } -pub fn render( +pub fn render( cfg: Config, create_renderer: impl FnOnce( &Arc>, @@ -115,7 +115,7 @@ pub fn render( let mut renderer = create_renderer(&rdom, &taffy, event_tx_clone); { - renderer.render(&rdom); + renderer.update(&rdom); let mut any_map = SendAnyMap::new(); any_map.insert(taffy.clone()); let mut rdom = rdom.write().unwrap(); @@ -261,7 +261,7 @@ pub fn render( } } // updates the dom's nodes - renderer.render(&rdom); + renderer.update(&rdom); // update the style and layout let mut rdom = rdom.write().unwrap(); let mut any_map = SendAnyMap::new(); @@ -297,8 +297,8 @@ pub enum InputEvent { Close, } -pub trait Renderer { - fn render(&mut self, rdom: &Arc>); +pub trait Driver { + fn update(&mut self, rdom: &Arc>); fn handle_event( &mut self, rdom: &Arc>,