From 26d92b6e51d34f1c898896e74be8883cf3310c9d Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 23 Mar 2022 14:18:17 -0500 Subject: [PATCH] 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); } }