From 7a176834474630aeb4805ac32290377c734bd6a5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 11:01:49 -0500 Subject: [PATCH 01/21] add persistant iterator to native core --- .../tests/peristant_iterator.rs | 342 ++++++++++++++++++ packages/native-core/src/lib.rs | 1 + packages/native-core/src/utils.rs | 224 ++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 packages/native-core-macro/tests/peristant_iterator.rs create mode 100644 packages/native-core/src/utils.rs diff --git a/packages/native-core-macro/tests/peristant_iterator.rs b/packages/native-core-macro/tests/peristant_iterator.rs new file mode 100644 index 000000000..29df3afe5 --- /dev/null +++ b/packages/native-core-macro/tests/peristant_iterator.rs @@ -0,0 +1,342 @@ +use dioxus_native_core::{ + real_dom::{NodeType, RealDom}, + state::State, + utils::PersistantElementIter, +}; +use dioxus_native_core_macro::State; + +#[derive(State, Default, Clone)] +struct Empty {} + +#[test] +#[allow(unused_variables)] +fn traverse() { + use dioxus_core::*; + use dioxus_core_macro::*; + use dioxus_html as dioxus_elements; + + #[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 rdom: RealDom = RealDom::new(); + + let _to_update = rdom.apply_mutations(vec![mutations]); + + let mut iter = PersistantElementIter::new(); + let div_tag = "div".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + let text1 = "hello".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Text { text: text1, .. } + )); + let p_tag = "p".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text2 = "world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Text { text: text2, .. } + )); + let text3 = "hello world".to_string(); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.next(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Text { text: text3, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Text { text: text2, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Element { tag: p_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Text { text: text1, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Element { tag: div_tag, .. } + )); + assert!(matches!( + &rdom[iter.prev(&rdom).id()].node_type, + NodeType::Text { text: text3, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_removes() { + use dioxus_core::VNode; + use dioxus_core::*; + use dioxus_core_macro::*; + use dioxus_html as dioxus_elements; + + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + let vdom = VirtualDom::new(Base); + let (build, update) = vdom.diff_lazynodes( + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "2", + "world" + } + p{ + key: "3", + "hello world" + } + } + }, + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "3", + "hello world" + } + } + }, + ); + + let mut rdom: RealDom = RealDom::new(); + + let _to_update = rdom.apply_mutations(vec![build]); + + // this will end on the node that is removed + let mut iter1 = PersistantElementIter::new(); + // this will end on the after node that is removed + let mut iter2 = PersistantElementIter::new(); + // div + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "hello" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // "world" + iter1.next(&rdom).id(); + iter2.next(&rdom).id(); + // p + iter2.next(&rdom).id(); + // "hello world" + iter2.next(&rdom).id(); + + iter1.prune(&update, &rdom); + iter2.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(vec![update]); + + let p_tag = "p".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "hello world".to_string(); + let idx = iter1.next(&rdom).id(); + assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. })); + let div_tag = "div".to_string(); + let idx = iter2.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_type, + NodeType::Element { tag: div_tag, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_instertions_before() { + use dioxus_core::*; + use dioxus_core_macro::*; + use dioxus_html as dioxus_elements; + + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + let vdom = VirtualDom::new(Base); + let (build, update) = vdom.diff_lazynodes( + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "3", + "hello world" + } + } + }, + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "2", + "world" + } + p{ + key: "3", + "hello world" + } + } + }, + ); + + let mut rdom: RealDom = RealDom::new(); + + let _to_update = rdom.apply_mutations(vec![build]); + + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "hello" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "hello world" + iter.next(&rdom).id(); + + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(vec![update]); + + let p_tag = "div".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_type, + NodeType::Element { tag: p_tag, .. } + )); +} + +#[test] +#[allow(unused_variables)] +fn persist_instertions_after() { + use dioxus_core::*; + use dioxus_core_macro::*; + use dioxus_html as dioxus_elements; + + #[allow(non_snake_case)] + fn Base(cx: Scope) -> Element { + rsx!(cx, div {}) + } + let vdom = VirtualDom::new(Base); + let (build, update) = vdom.diff_lazynodes( + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "2", + "world" + } + } + }, + rsx! { + div{ + p{ + key: "1", + "hello" + } + p{ + key: "2", + "world" + } + p{ + key: "3", + "hello world" + } + } + }, + ); + + let mut rdom: RealDom = RealDom::new(); + + let _to_update = rdom.apply_mutations(vec![build]); + + let mut iter = PersistantElementIter::new(); + // div + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "hello" + iter.next(&rdom).id(); + // p + iter.next(&rdom).id(); + // "world" + iter.next(&rdom).id(); + + iter.prune(&update, &rdom); + let _to_update = rdom.apply_mutations(vec![update]); + + let p_tag = "p".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!( + &rdom[idx].node_type, + NodeType::Element { tag: p_tag, .. } + )); + let text = "hello world".to_string(); + let idx = iter.next(&rdom).id(); + assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. })); +} diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index a6e011fa9..2319ca4ac 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -2,3 +2,4 @@ pub mod layout_attributes; pub mod node_ref; pub mod real_dom; pub mod state; +pub mod utils; diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs new file mode 100644 index 000000000..41f79a04b --- /dev/null +++ b/packages/native-core/src/utils.rs @@ -0,0 +1,224 @@ +use crate::{ + real_dom::{NodeType, RealDom}, + state::State, +}; +use dioxus_core::{DomEdit, ElementId, Mutations}; + +pub enum ElementProduced { + Progressed(ElementId), + Looped(ElementId), +} +impl ElementProduced { + pub fn id(&self) -> ElementId { + match self { + ElementProduced::Progressed(id) => *id, + ElementProduced::Looped(id) => *id, + } + } +} + +#[derive(Debug)] +enum NodePosition { + AtNode, + InChild(usize), +} + +impl NodePosition { + fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self { + match self { + Self::AtNode => Self::AtNode, + Self::InChild(i) => Self::InChild(f(*i)), + } + } + + fn get_or_insert(&mut self, child_idx: usize) -> usize { + match self { + Self::AtNode => { + *self = Self::InChild(child_idx); + child_idx + } + Self::InChild(i) => *i, + } + } +} + +/// The focus system needs a iterator that can persist through changes in the [VirtualDom]. +/// Iterate through it with [ElementIter::next] [ElementIter::prev], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]). +/// The iterator loops around when it reaches the end or the beginning. +pub struct PersistantElementIter { + // stack of elements and fragments + stack: smallvec::SmallVec<[(ElementId, NodePosition); 5]>, +} + +impl Default for PersistantElementIter { + fn default() -> Self { + PersistantElementIter { + stack: smallvec::smallvec![(ElementId(0), NodePosition::AtNode)], + } + } +} + +impl PersistantElementIter { + pub fn new() -> Self { + Self::default() + } + + /// remove stale element refreneces + pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) { + let ids_removed: Vec<_> = mutations + .edits + .iter() + .filter_map(|e| { + if let DomEdit::Remove { root } = e { + Some(*root) + } else { + None + } + }) + .collect(); + if let Some(r) = self + .stack + .iter() + .position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id)) + { + self.stack.truncate(r); + } + // if a child is removed or inserted before or at the current element, update the child index + for (el_id, child_idx) in self.stack.iter_mut() { + if let NodePosition::InChild(child_idx) = child_idx { + if let NodeType::Element { children, .. } = &rdom[*el_id].node_type { + for m in &mutations.edits { + match m { + DomEdit::Remove { root } => { + if children + .iter() + .take(*child_idx + 1) + .any(|c| c.as_u64() == *root) + { + *child_idx -= 1; + } + } + DomEdit::InsertBefore { root, n } => { + if children + .iter() + .take(*child_idx + 1) + .any(|c| c.as_u64() == *root) + { + *child_idx += *n as usize; + } + } + DomEdit::InsertAfter { root, n } => { + if children + .iter() + .take(*child_idx) + .any(|c| c.as_u64() == *root) + { + *child_idx += *n as usize; + } + } + _ => (), + } + } + } + } + } + } + + /// get the next element + pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { + if self.stack.is_empty() { + let id = ElementId(0); + let new = (id, NodePosition::AtNode); + self.stack.push(new); + ElementProduced::Looped(id) + } else { + let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let node = &rdom[*last]; + match &node.node_type { + NodeType::Element { children, .. } => { + *o_child_idx = o_child_idx.map(|i| i + 1); + // if we have children, go to the next child + let child_idx = o_child_idx.get_or_insert(0); + if child_idx >= children.len() { + self.pop(); + self.next(rdom) + } else { + let id = children[child_idx]; + if let NodeType::Element { .. } = &rdom[id].node_type { + self.stack.push((id, NodePosition::AtNode)); + } + ElementProduced::Progressed(id) + } + } + + NodeType::Text { .. } | NodeType::Placeholder { .. } => { + // we are at a leaf, so we are done + ElementProduced::Progressed(self.pop()) + } + } + } + } + + /// get the previous element + pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { + fn push_back( + stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>, + new_node: ElementId, + rdom: &RealDom, + ) -> ElementId { + match &rdom[new_node].node_type { + NodeType::Element { children, .. } => { + if children.is_empty() { + new_node + } else { + stack.push((new_node, NodePosition::InChild(children.len() - 1))); + push_back(stack, *children.last().unwrap(), rdom) + } + } + _ => new_node, + } + } + if self.stack.is_empty() { + let new_node = ElementId(0); + ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) + } else { + let (last, o_child_idx) = self.stack.last_mut().unwrap(); + let node = &rdom[*last]; + match &node.node_type { + NodeType::Element { children, .. } => { + // if we have children, go to the next child + if let NodePosition::InChild(0) = o_child_idx { + ElementProduced::Progressed(self.pop()) + } else { + *o_child_idx = o_child_idx.map(|i| i - 1); + if let NodePosition::InChild(child_idx) = o_child_idx { + if *child_idx >= children.len() || children.len() == 0 { + self.pop(); + self.prev(rdom) + } else { + let new_node = children[*child_idx]; + ElementProduced::Progressed(push_back( + &mut self.stack, + new_node, + rdom, + )) + } + } else { + self.pop(); + self.prev(rdom) + } + } + } + + NodeType::Text { .. } | NodeType::Placeholder { .. } => { + // we are at a leaf, so we are done + ElementProduced::Progressed(self.pop()) + } + } + } + } + + fn pop(&mut self) -> ElementId { + self.stack.pop().unwrap().0 + } +} From 35ee243d0d0663f112cc8ae82306d475901d95f8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 11:02:35 -0500 Subject: [PATCH 02/21] intigrate focus system with tui --- examples/tui_buttons.rs | 59 ++++++++++++++++++++++++++++++++++++ packages/tui/src/hooks.rs | 17 ++++++----- packages/tui/src/lib.rs | 61 +++++++++++++++++++++++++++++++++----- packages/tui/src/render.rs | 10 +++++-- packages/tui/src/widget.rs | 5 ++-- 5 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 examples/tui_buttons.rs diff --git a/examples/tui_buttons.rs b/examples/tui_buttons.rs new file mode 100644 index 000000000..3581a0d7c --- /dev/null +++ b/examples/tui_buttons.rs @@ -0,0 +1,59 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; + +fn main() { + dioxus::tui::launch(app); +} + +fn Button(cx: Scope) -> Element { + let state = use_state(&cx, || false); + let color = if *state.get() { "red" } else { "blue" }; + let text = if *state.get() { "☐" } else { "☒" }; + + cx.render(rsx! { + div { + border_width: "1px", + width: "50%", + height: "100%", + background_color: "{color}", + justify_content: "center", + align_items: "center", + onkeydown: |_| state.modify(|s| !s), + + "{text}" + } + }) +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + + div { + width: "100%", + height: "50%", + flex_direction: "row", + + Button{}, + Button{}, + Button{}, + Button{}, + } + + div { + width: "100%", + height: "50%", + flex_direction: "row", + + Button{}, + Button{}, + Button{}, + Button{}, + } + } + }) +} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 8da04fc5e..d36682750 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -550,7 +550,6 @@ 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: FxHashMap<&'static str, Vec>> = FxHashMap::default(); for (event, data) in events { if let Some(v) = hm.get_mut(event) { @@ -562,13 +561,15 @@ impl RinkInputHandler { for (event, datas) in hm { for node in dom.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(), - }); + if node.state.focused { + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: event, + element: Some(node.id), + data: data.clone(), + }); + } } } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 9f6bd78ed..033eb1d8d 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,9 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::{real_dom::RealDom, state::*}; +use dioxus_native_core::real_dom::{NodeType, RealDom}; +use dioxus_native_core::state::*; +use dioxus_native_core::utils::PersistantElementIter; use dioxus_native_core_macro::State; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, @@ -42,6 +44,7 @@ struct NodeState { // depends on attributes, the C component of it's parent and a u8 context #[parent_dep_state(style)] style: StyleModifier, + focused: bool, } #[derive(Clone)] @@ -130,7 +133,10 @@ fn render_vdom( } let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); - let mut resized = true; + let mut updated = true; + + let mut focus_iter = PersistantElementIter::new(); + let mut focus_id = ElementId(0); loop { /* @@ -144,8 +150,8 @@ fn render_vdom( todo: lazy re-rendering */ - if !to_rerender.is_empty() || resized { - resized = false; + if !to_rerender.is_empty() || updated { + updated = false; fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) { let width = dims.width; let height = dims.height; @@ -192,6 +198,8 @@ fn render_vdom( // } Either::Right((evt, _o)) => { + let mut evt_intersepted = false; + match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { @@ -201,15 +209,51 @@ fn render_vdom( { break; } + if let KeyCode::BackTab = key.code { + let mut new_focused_id; + loop { + new_focused_id = focus_iter.prev(&rdom).id(); + if let NodeType::Element { .. } = + &rdom[new_focused_id].node_type + { + break; + } + } + rdom[focus_id].state.focused = false; + focus_id = new_focused_id; + rdom[focus_id].state.focused = true; + evt_intersepted = true; + updated = true; + // println!("{:?}", focus_id); + } + if let KeyCode::Tab = key.code { + let mut new_focused_id; + loop { + new_focused_id = focus_iter.next(&rdom).id(); + if let NodeType::Element { .. } = + &rdom[new_focused_id].node_type + { + break; + } + } + rdom[focus_id].state.focused = false; + focus_id = new_focused_id; + rdom[focus_id].state.focused = true; + evt_intersepted = true; + updated = true; + // println!("{:?}", focus_id); + } } - TermEvent::Resize(_, _) => resized = true, + TermEvent::Resize(_, _) => updated = true, TermEvent::Mouse(_) => {} }, InputEvent::Close => break, }; - if let InputEvent::UserInput(evt) = evt.unwrap() { - register_event(evt); + if !evt_intersepted { + if let InputEvent::UserInput(evt) = evt.unwrap() { + register_event(evt); + } } } } @@ -221,6 +265,9 @@ fn render_vdom( vdom.handle_message(SchedulerMsg::Event(e)); } let mutations = vdom.work_with_deadline(|| false); + for m in &mutations { + focus_iter.prune(m, &rdom); + } // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); // update the style and layout diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 3e75f3a5e..7fa09094e 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -5,7 +5,7 @@ use stretch2::{ prelude::{Layout, Size}, Stretch, }; -use tui::{backend::CrosstermBackend, layout::Rect}; +use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ style::{RinkColor, RinkStyle}, @@ -43,7 +43,7 @@ pub(crate) fn render_vnode( } impl<'a> RinkWidget for Label<'a> { - fn render(self, area: Rect, mut buf: RinkBuffer) { + fn render(self, area: Rect, buf: &mut RinkBuffer) { for (i, c) in self.text.char_indices() { let mut new_cell = RinkCell::default(); new_cell.set_style(self.style); @@ -81,7 +81,7 @@ pub(crate) fn render_vnode( } impl RinkWidget for &Node { - fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { + fn render(self, area: Rect, mut buf: &mut RinkBuffer<'_>) { use tui::symbols::line::*; enum Direction { @@ -267,6 +267,10 @@ impl RinkWidget for &Node { if let Some(c) = self.state.style.core.bg { new_cell.bg = c; } + if self.state.focused { + new_cell.bg.alpha = 100; + new_cell.bg.color = new_cell.bg.blend(Color::White); + } buf.set(x, y, new_cell); } } diff --git a/packages/tui/src/widget.rs b/packages/tui/src/widget.rs index b0c96d607..1e4af52fc 100644 --- a/packages/tui/src/widget.rs +++ b/packages/tui/src/widget.rs @@ -41,7 +41,7 @@ impl<'a> RinkBuffer<'a> { } pub trait RinkWidget { - fn render(self, area: Rect, buf: RinkBuffer); + fn render(self, area: Rect, buf: &mut RinkBuffer); } pub struct WidgetWithContext { @@ -57,7 +57,8 @@ impl WidgetWithContext { impl Widget for WidgetWithContext { fn render(self, area: Rect, buf: &mut Buffer) { - self.widget.render(area, RinkBuffer::new(buf, self.config)) + let mut rbuf = RinkBuffer::new(buf, self.config); + self.widget.render(area, &mut rbuf); } } From ce5ade9fa6a584178256a4b4d05e33adf85c229e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 15:06:07 -0500 Subject: [PATCH 03/21] add focus state --- packages/tui/src/focus.rs | 268 ++++++++++++++++++++++++++++++++++++++ packages/tui/src/lib.rs | 46 ++----- 2 files changed, 279 insertions(+), 35 deletions(-) create mode 100644 packages/tui/src/focus.rs diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs new file mode 100644 index 000000000..cc89e9660 --- /dev/null +++ b/packages/tui/src/focus.rs @@ -0,0 +1,268 @@ +use crate::Dom; + +use dioxus_core::ElementId; +use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; + +use std::num::NonZeroU16; + +use dioxus_native_core::{ + node_ref::{AttributeMask, NodeMask, NodeView}, + real_dom::NodeType, + state::NodeDepState, +}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord)] +pub(crate) enum FocusLevel { + Unfocusable, + Focusable, + Ordered(std::num::NonZeroU16), +} + +impl FocusLevel { + pub fn focusable(&self) -> bool { + match self { + FocusLevel::Unfocusable => false, + FocusLevel::Focusable => true, + FocusLevel::Ordered(_) => true, + } + } +} + +impl PartialOrd for FocusLevel { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (FocusLevel::Unfocusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Equal), + (FocusLevel::Unfocusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Less), + (FocusLevel::Unfocusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Less), + (FocusLevel::Focusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater), + (FocusLevel::Focusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Equal), + (FocusLevel::Focusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Greater), + (FocusLevel::Ordered(_), FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater), + (FocusLevel::Ordered(_), FocusLevel::Focusable) => Some(std::cmp::Ordering::Less), + (FocusLevel::Ordered(a), FocusLevel::Ordered(b)) => a.partial_cmp(b), + } + } +} + +impl Default for FocusLevel { + fn default() -> Self { + FocusLevel::Unfocusable + } +} + +#[derive(Clone, PartialEq, Debug, Default)] +pub(crate) struct Focus { + pub pass_focus: bool, + pub level: FocusLevel, +} + +impl NodeDepState for Focus { + type Ctx = (); + type DepState = (); + const NODE_MASK: NodeMask = + NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners(); + + fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { + let new = Focus { + pass_focus: !node + .attributes() + .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "true"), + level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { + if let Ok(index) = a.value.parse::() { + if index < 0 { + FocusLevel::Unfocusable + } else if index == 0 { + FocusLevel::Focusable + } else { + FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap()) + } + } else { + FocusLevel::Unfocusable + } + } else { + if node + .listeners() + .iter() + .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok()) + { + FocusLevel::Focusable + } else { + FocusLevel::Unfocusable + } + }, + }; + if *self != new { + *self = new; + true + } else { + false + } + } +} + +// must be sorted +const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; +const FOCUS_ATTRIBUTES: &[&str] = &["dioxus-prevent-default", "tabindex"]; + +#[derive(Default)] +pub(crate) struct FocusState { + pub(crate) focus_iter: PersistantElementIter, + pub(crate) last_focused_id: Option, + pub(crate) focus_level: FocusLevel, +} + +impl FocusState { + // returns true if the focus has changed + pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool { + if let Some(last) = self.last_focused_id { + if !rdom[last].state.focus.pass_focus { + return false; + } + } + let mut loop_marker_id = self.last_focused_id; + let focus_level = &mut self.focus_level; + let mut next_focus = None; + let starting_focus_level = *focus_level; + + loop { + let new = if forward { + self.focus_iter.next(&rdom) + } else { + self.focus_iter.prev(&rdom) + }; + let new_id = new.id(); + let current_level = rdom[new_id].state.focus.level; + if let ElementProduced::Looped(_) = new { + let mut closest_level = None; + + if forward { + // find the closest focusable element after the current level + rdom.traverse_depth_first(|n| { + let current_level = n.state.focus.level; + if current_level != *focus_level { + if current_level > *focus_level { + if let Some(level) = &mut closest_level { + if current_level < *level { + *level = current_level; + } + } else { + closest_level = Some(current_level); + } + } + } + }); + } else { + // find the closest focusable element before the current level + rdom.traverse_depth_first(|n| { + let current_level = n.state.focus.level; + if current_level != *focus_level { + if current_level < *focus_level { + if let Some(level) = &mut closest_level { + if current_level > *level { + *level = current_level; + } + } else { + closest_level = Some(current_level); + } + } + } + }); + } + + // extend the loop_marker_id to allow for another pass + loop_marker_id = None; + + if let Some(level) = closest_level { + *focus_level = level; + } else { + if forward { + *focus_level = FocusLevel::Unfocusable; + } else { + *focus_level = FocusLevel::Focusable; + } + } + + // if the focus level looped, we are done + if *focus_level == starting_focus_level { + break; + } + } + + // once we have looked at all the elements exit the loop + if let Some(last) = loop_marker_id { + if new_id == last { + break; + } + } else { + loop_marker_id = Some(new_id); + } + + let after_previous_focused = if forward { + current_level >= *focus_level + } else { + current_level <= *focus_level + }; + if after_previous_focused && current_level.focusable() { + if current_level == *focus_level { + next_focus = Some((new_id, current_level)); + break; + } + } + } + + if let Some((id, order)) = next_focus { + if order.focusable() { + rdom[id].state.focused = true; + if let Some(old) = self.last_focused_id.replace(id) { + rdom[old].state.focused = false; + } + // reset the position to the currently focused element + while if forward { + self.focus_iter.next(&rdom).id() + } else { + self.focus_iter.prev(&rdom).id() + } != id + {} + return true; + } + } + + false + } + + pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) { + fn remove_children( + to_prune: &mut [&mut Option], + rdom: &Dom, + removed: ElementId, + ) { + for opt in to_prune.iter_mut() { + if let Some(id) = opt { + if *id == removed { + **opt = None; + } + } + } + if let NodeType::Element { children, .. } = &rdom[removed].node_type { + for child in children { + remove_children(to_prune, rdom, *child); + } + } + } + for m in &mutations.edits { + match m { + dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( + &mut [&mut self.last_focused_id], + rdom, + ElementId(*root as usize), + ), + dioxus_core::DomEdit::Remove { root } => remove_children( + &mut [&mut self.last_focused_id], + rdom, + ElementId(*root as usize), + ), + _ => (), + } + } + } +} diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 033eb1d8d..d8dec50e9 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,10 +7,11 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::real_dom::{NodeType, RealDom}; +use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::*; -use dioxus_native_core::utils::PersistantElementIter; use dioxus_native_core_macro::State; +use focus::Focus; +use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, @@ -24,6 +25,7 @@ use style_attributes::StyleModifier; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; +mod focus; mod hooks; mod layout; mod render; @@ -44,6 +46,8 @@ struct NodeState { // depends on attributes, the C component of it's parent and a u8 context #[parent_dep_state(style)] style: StyleModifier, + #[node_dep_state()] + focus: Focus, focused: bool, } @@ -135,8 +139,7 @@ fn render_vdom( let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut updated = true; - let mut focus_iter = PersistantElementIter::new(); - let mut focus_id = ElementId(0); + let mut focus_state = FocusState::default(); loop { /* @@ -210,38 +213,11 @@ fn render_vdom( break; } if let KeyCode::BackTab = key.code { - let mut new_focused_id; - loop { - new_focused_id = focus_iter.prev(&rdom).id(); - if let NodeType::Element { .. } = - &rdom[new_focused_id].node_type - { - break; - } - } - rdom[focus_id].state.focused = false; - focus_id = new_focused_id; - rdom[focus_id].state.focused = true; - evt_intersepted = true; - updated = true; - // println!("{:?}", focus_id); + evt_intersepted = + focus_state.progress(&mut rdom, false); } if let KeyCode::Tab = key.code { - let mut new_focused_id; - loop { - new_focused_id = focus_iter.next(&rdom).id(); - if let NodeType::Element { .. } = - &rdom[new_focused_id].node_type - { - break; - } - } - rdom[focus_id].state.focused = false; - focus_id = new_focused_id; - rdom[focus_id].state.focused = true; - evt_intersepted = true; - updated = true; - // println!("{:?}", focus_id); + evt_intersepted = focus_state.progress(&mut rdom, true); } } TermEvent::Resize(_, _) => updated = true, @@ -266,7 +242,7 @@ fn render_vdom( } let mutations = vdom.work_with_deadline(|| false); for m in &mutations { - focus_iter.prune(m, &rdom); + focus_state.prune(m, &rdom); } // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); From 23376fef8e2d452394cc3dc113a88e36570f2f32 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 16:44:53 -0500 Subject: [PATCH 04/21] move focus into event system --- examples/tui_buttons.rs | 1 + packages/tui/src/focus.rs | 38 ++++++------ packages/tui/src/hooks.rs | 122 +++++++++++++++++++++++--------------- packages/tui/src/lib.rs | 22 ++----- 4 files changed, 100 insertions(+), 83 deletions(-) diff --git a/examples/tui_buttons.rs b/examples/tui_buttons.rs index 3581a0d7c..39c458919 100644 --- a/examples/tui_buttons.rs +++ b/examples/tui_buttons.rs @@ -20,6 +20,7 @@ fn Button(cx: Scope) -> Element { justify_content: "center", align_items: "center", onkeydown: |_| state.modify(|s| !s), + onclick: |_| state.modify(|s| !s), "{text}" } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index cc89e9660..3590075dd 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -2,6 +2,7 @@ use crate::Dom; use dioxus_core::ElementId; use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; +use dioxus_native_core_macro::sorted_str_slice; use std::num::NonZeroU16; @@ -100,9 +101,8 @@ impl NodeDepState for Focus { } } -// must be sorted -const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"]; -const FOCUS_ATTRIBUTES: &[&str] = &["dioxus-prevent-default", "tabindex"]; +const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]); +const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["dioxus-prevent-default", "tabindex"]); #[derive(Default)] pub(crate) struct FocusState { @@ -123,6 +123,7 @@ impl FocusState { let focus_level = &mut self.focus_level; let mut next_focus = None; let starting_focus_level = *focus_level; + let mut focus_level_changed = false; loop { let new = if forward { @@ -131,22 +132,21 @@ impl FocusState { self.focus_iter.prev(&rdom) }; let new_id = new.id(); - let current_level = rdom[new_id].state.focus.level; if let ElementProduced::Looped(_) = new { let mut closest_level = None; if forward { // find the closest focusable element after the current level rdom.traverse_depth_first(|n| { - let current_level = n.state.focus.level; - if current_level != *focus_level { - if current_level > *focus_level { + let node_level = n.state.focus.level; + if node_level != *focus_level && node_level.focusable() { + if node_level > *focus_level { if let Some(level) = &mut closest_level { - if current_level < *level { - *level = current_level; + if node_level < *level { + *level = node_level; } } else { - closest_level = Some(current_level); + closest_level = Some(node_level); } } } @@ -154,15 +154,15 @@ impl FocusState { } else { // find the closest focusable element before the current level rdom.traverse_depth_first(|n| { - let current_level = n.state.focus.level; - if current_level != *focus_level { - if current_level < *focus_level { + let node_level = n.state.focus.level; + if node_level != *focus_level && node_level.focusable() { + if node_level < *focus_level { if let Some(level) = &mut closest_level { - if current_level > *level { - *level = current_level; + if node_level > *level { + *level = node_level; } } else { - closest_level = Some(current_level); + closest_level = Some(node_level); } } } @@ -174,6 +174,9 @@ impl FocusState { if let Some(level) = closest_level { *focus_level = level; + if *focus_level != starting_focus_level { + focus_level_changed = true; + } } else { if forward { *focus_level = FocusLevel::Unfocusable; @@ -183,7 +186,7 @@ impl FocusState { } // if the focus level looped, we are done - if *focus_level == starting_focus_level { + if *focus_level == starting_focus_level && focus_level_changed { break; } } @@ -197,6 +200,7 @@ impl FocusState { loop_marker_id = Some(new_id); } + let current_level = rdom[new_id].state.focus.level; let after_previous_focused = if forward { current_level >= *focus_level } else { diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index d36682750..e045a8f25 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -14,6 +14,7 @@ use std::{ }; use stretch2::{prelude::Layout, Stretch}; +use crate::FocusState; use crate::{Dom, Node}; // a wrapper around the input state for easier access @@ -78,6 +79,7 @@ pub struct InnerInputState { wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, screen: Option<(u16, u16)>, + focus_state: FocusState, // subscribers: Vec>, } @@ -89,6 +91,7 @@ impl InnerInputState { last_key_pressed: None, screen: None, // subscribers: Vec::new(), + focus_state: FocusState::default(), } } @@ -96,54 +99,56 @@ 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) => { + 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); + } - buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); + buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); + } + _ => (), } - _ => (), + state.0.buttons = buttons; + m.buttons = buttons; + } + None => { + self.mouse = Some(( + m.clone(), + if m.buttons == 0 { + Vec::new() + } else { + vec![m.buttons] + }, + )); } - state.0.buttons = buttons; - m.buttons = buttons; } - None => { - self.mouse = Some(( - m.clone(), - if m.buttons == 0 { - Vec::new() - } else { - vec![m.buttons] - }, - )); - } - }, + } EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { @@ -161,14 +166,27 @@ impl InnerInputState { fn update( &mut self, - evts: &mut [EventCore], + evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, - ) { + ) -> bool { let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); self.wheel = None; + let mut force_redraw = false; + + evts.retain(|e| match &e.1 { + EventData::Keyboard(k) => match k.key_code { + KeyCode::Tab => { + let focus_event = self.focus_state.progress(dom, !k.shift_key); + force_redraw |= focus_event; + !focus_event + } + _ => true, + }, + _ => true, + }); for e in evts.iter_mut() { self.apply_event(e); @@ -179,6 +197,7 @@ impl InnerInputState { // for s in &self.subscribers { // s(); // } + force_redraw } fn resolve_mouse_events( @@ -517,10 +536,15 @@ impl RinkInputHandler { ) } - pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { + pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) { + self.state.borrow_mut().focus_state.prune(mutations, rdom); + } + + // returns a list of events and if a event will force a rerender + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> (Vec, bool) { let mut resolved_events = Vec::new(); - (*self.state).borrow_mut().update( + let rerender = (*self.state).borrow_mut().update( &mut (*self.queued_events).borrow_mut(), &mut resolved_events, layout, @@ -574,7 +598,7 @@ impl RinkInputHandler { } } - resolved_events + (resolved_events, rerender) } } @@ -759,7 +783,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { 12 => KeyCode::F12, _ => return None, }, - TermKeyCode::BackTab => return None, + TermKeyCode::BackTab => KeyCode::Tab, TermKeyCode::Null => return None, _ => return None, }; diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index d8dec50e9..cb1628206 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -139,8 +139,6 @@ fn render_vdom( let to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); let mut updated = true; - let mut focus_state = FocusState::default(); - loop { /* -> render the nodes in the right place with tui/crossterm @@ -201,8 +199,6 @@ fn render_vdom( // } Either::Right((evt, _o)) => { - let mut evt_intersepted = false; - match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { @@ -212,13 +208,6 @@ fn render_vdom( { break; } - if let KeyCode::BackTab = key.code { - evt_intersepted = - focus_state.progress(&mut rdom, false); - } - if let KeyCode::Tab = key.code { - evt_intersepted = focus_state.progress(&mut rdom, true); - } } TermEvent::Resize(_, _) => updated = true, TermEvent::Mouse(_) => {} @@ -226,23 +215,22 @@ fn render_vdom( InputEvent::Close => break, }; - if !evt_intersepted { - if let InputEvent::UserInput(evt) = evt.unwrap() { - register_event(evt); - } + if let InputEvent::UserInput(evt) = evt.unwrap() { + register_event(evt); } } } } { - let evts = handler.get_events(&stretch.borrow(), &mut rdom); + let (evts, rerender) = handler.get_events(&stretch.borrow(), &mut rdom); + updated |= rerender; for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } let mutations = vdom.work_with_deadline(|| false); for m in &mutations { - focus_state.prune(m, &rdom); + handler.prune(m, &rdom); } // updates the dom's nodes let to_update = rdom.apply_mutations(mutations); From 9ed722ae4560e321582785c4c45f2dfb913c27e1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 17:19:16 -0500 Subject: [PATCH 05/21] change focus when an element is clicked --- packages/native-core/src/utils.rs | 6 ++- packages/tui/src/focus.rs | 25 ++++++++- packages/tui/src/hooks.rs | 84 ++++++++++++++++++------------- packages/tui/src/lib.rs | 6 ++- 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index 41f79a04b..efd2a1d3b 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -64,7 +64,9 @@ impl PersistantElementIter { } /// remove stale element refreneces - pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) { + /// returns true if the focused element is removed + pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { + let mut changed = false; let ids_removed: Vec<_> = mutations .edits .iter() @@ -82,6 +84,7 @@ impl PersistantElementIter { .position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id)) { self.stack.truncate(r); + changed = true; } // if a child is removed or inserted before or at the current element, update the child index for (el_id, child_idx) in self.stack.iter_mut() { @@ -122,6 +125,7 @@ impl PersistantElementIter { } } } + changed } /// get the next element diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 3590075dd..cb578a8f1 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -109,10 +109,11 @@ pub(crate) struct FocusState { pub(crate) focus_iter: PersistantElementIter, pub(crate) last_focused_id: Option, pub(crate) focus_level: FocusLevel, + pub(crate) dirty: bool, } impl FocusState { - // returns true if the focus has changed + /// Returns true if the focus has changed. pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { if !rdom[last].state.focus.pass_focus { @@ -227,6 +228,7 @@ impl FocusState { self.focus_iter.prev(&rdom).id() } != id {} + self.dirty = true; return true; } } @@ -253,6 +255,9 @@ impl FocusState { } } } + if self.focus_iter.prune(mutations, rdom) { + self.dirty = true; + } for m in &mutations.edits { match m { dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( @@ -269,4 +274,22 @@ impl FocusState { } } } + + pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) { + if let Some(old) = self.last_focused_id.replace(id) { + rdom[old].state.focused = false; + } + let state = &mut rdom[id].state; + state.focused = true; + self.focus_level = state.focus.level; + // reset the position to the currently focused element + while self.focus_iter.next(&rdom).id() != id {} + self.dirty = true; + } + + pub(crate) fn clean(&mut self) -> bool { + let old = self.dirty; + self.dirty = false; + old + } } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index e045a8f25..6dd3e88bb 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -7,7 +7,7 @@ use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, - cell::RefCell, + cell::{RefCell, RefMut}, rc::Rc, sync::Arc, time::{Duration, Instant}, @@ -79,7 +79,7 @@ pub struct InnerInputState { wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, screen: Option<(u16, u16)>, - focus_state: FocusState, + pub(crate) focus_state: FocusState, // subscribers: Vec>, } @@ -170,19 +170,14 @@ impl InnerInputState { resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, - ) -> bool { + ) { let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); self.wheel = None; - let mut force_redraw = false; evts.retain(|e| match &e.1 { EventData::Keyboard(k) => match k.key_code { - KeyCode::Tab => { - let focus_event = self.focus_state.progress(dom, !k.shift_key); - force_redraw |= focus_event; - !focus_event - } + KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key), _ => true, }, _ => true, @@ -197,11 +192,10 @@ impl InnerInputState { // for s in &self.subscribers { // s(); // } - force_redraw } fn resolve_mouse_events( - &self, + &mut self, previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, @@ -343,14 +337,14 @@ impl InnerInputState { } } - { - // mousedown + // mousedown + if data.clicked { 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); - if currently_contains && data.clicked { + if currently_contains { try_create_event( "mousedown", Arc::new(data.mouse_data.clone()), @@ -363,14 +357,14 @@ impl InnerInputState { } } - { - // mouseup + // mouseup + if data.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, data.new_pos); - if currently_contains && data.released { + if currently_contains { try_create_event( "mouseup", Arc::new(data.mouse_data.clone()), @@ -383,14 +377,14 @@ impl InnerInputState { } } - { - // click + // click + if data.released && data.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, data.new_pos); - if currently_contains && data.released && data.mouse_data.button == 0 { + if currently_contains { try_create_event( "click", Arc::new(data.mouse_data.clone()), @@ -403,14 +397,14 @@ impl InnerInputState { } } - { - // contextmenu + // contextmenu + if data.released && data.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, data.new_pos); - if currently_contains && data.released && data.mouse_data.button == 2 { + if currently_contains { try_create_event( "contextmenu", Arc::new(data.mouse_data.clone()), @@ -423,15 +417,15 @@ impl InnerInputState { } } - { - // 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, data.new_pos); + // wheel + if let Some(w) = data.wheel_data { + if data.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, data.new_pos); - if let Some(w) = data.wheel_data { - if currently_contains && data.wheel_delta != 0.0 { + if currently_contains { try_create_event( "wheel", Arc::new(w.clone()), @@ -492,6 +486,24 @@ impl InnerInputState { } } } + + // update focus + if data.released { + let mut focus_id = None; + dom.traverse_depth_first(|node| { + 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 { + if node.state.focus.level.focusable() { + focus_id = Some(node.id); + } + } + }); + if let Some(id) = focus_id { + self.focus_state.set_focus(dom, id); + } + } } } @@ -541,10 +553,10 @@ impl RinkInputHandler { } // returns a list of events and if a event will force a rerender - pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> (Vec, bool) { + pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec { let mut resolved_events = Vec::new(); - let rerender = (*self.state).borrow_mut().update( + (*self.state).borrow_mut().update( &mut (*self.queued_events).borrow_mut(), &mut resolved_events, layout, @@ -598,7 +610,11 @@ impl RinkInputHandler { } } - (resolved_events, rerender) + resolved_events + } + + pub(crate) fn state(&self) -> RefMut { + self.state.borrow_mut() } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index cb1628206..f8ee2ec80 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -223,8 +223,10 @@ fn render_vdom( } { - let (evts, rerender) = handler.get_events(&stretch.borrow(), &mut rdom); - updated |= rerender; + let evts = handler.get_events(&stretch.borrow(), &mut rdom); + { + updated |= handler.state().focus_state.clean(); + } for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } From 592f6ca225bcf0b6c6ea214e6d3ad905ed9e8a11 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 17:50:04 -0500 Subject: [PATCH 06/21] change button example to showcase tabindex --- examples/tui_buttons.rs | 103 ++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/examples/tui_buttons.rs b/examples/tui_buttons.rs index 39c458919..31d2e1ce0 100644 --- a/examples/tui_buttons.rs +++ b/examples/tui_buttons.rs @@ -1,28 +1,51 @@ -#![allow(non_snake_case)] - -use dioxus::prelude::*; +use dioxus::{events::KeyCode, prelude::*}; fn main() { dioxus::tui::launch(app); } -fn Button(cx: Scope) -> Element { - let state = use_state(&cx, || false); - let color = if *state.get() { "red" } else { "blue" }; - let text = if *state.get() { "☐" } else { "☒" }; +#[derive(PartialEq, Props)] +struct ButtonProps { + color_offset: u32, + layer: u16, +} + +#[allow(non_snake_case)] +fn Button(cx: Scope) -> Element { + let toggle = use_state(&cx, || false); + let hovered = use_state(&cx, || false); + + let hue = cx.props.color_offset % 255; + let saturation = if *toggle.get() { 50 } else { 25 } + if *hovered.get() { 50 } else { 25 }; + let brightness = saturation / 2; + let color = format!("hsl({hue}, {saturation}, {brightness})"); cx.render(rsx! { - div { - border_width: "1px", - width: "50%", + div{ + width: "100%", height: "100%", background_color: "{color}", + tabindex: "{cx.props.layer}", + onkeydown: |e| { + if let KeyCode::Space = e.data.key_code{ + toggle.modify(|f| !f); + } + }, + onclick: |_| { + toggle.modify(|f| !f); + }, + onmouseenter: |_|{ + hovered.set(true); + }, + onmouseleave: |_|{ + hovered.set(false); + }, justify_content: "center", align_items: "center", - onkeydown: |_| state.modify(|s| !s), - onclick: |_| state.modify(|s| !s), + display: "flex", + flex_direction: "column", - "{text}" + p{"tabindex: {cx.props.layer}"} } }) } @@ -30,31 +53,41 @@ fn Button(cx: Scope) -> Element { fn app(cx: Scope) -> Element { cx.render(rsx! { div { + display: "flex", + flex_direction: "column", width: "100%", height: "100%", - flex_direction: "column", - div { - width: "100%", - height: "50%", - flex_direction: "row", - - Button{}, - Button{}, - Button{}, - Button{}, - } - - div { - width: "100%", - height: "50%", - flex_direction: "row", - - Button{}, - Button{}, - Button{}, - Button{}, - } + (1..8).map(|y| + cx.render(rsx!{ + div{ + display: "flex", + flex_direction: "row", + width: "100%", + height: "100%", + (1..8).map(|x|{ + if (x + y) % 2 == 0{ + cx.render(rsx!{ + div{ + width: "100%", + height: "100%", + background_color: "rgb(100, 100, 100)", + } + }) + } + else{ + let layer = (x + y) % 3; + cx.render(rsx!{ + Button{ + color_offset: x * y, + layer: layer as u16, + } + }) + } + }) + } + }) + ) } }) } From 2176da4d75db3dfeeac849ad6716f1cf7b1806c2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 18:43:28 -0500 Subject: [PATCH 07/21] update readme --- packages/tui/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/tui/README.md b/packages/tui/README.md index 20adaeb6c..ccce60ea3 100644 --- a/packages/tui/README.md +++ b/packages/tui/README.md @@ -85,11 +85,10 @@ Rink features: - [x] Flexbox based layout system - [ ] CSS selectors - [x] inline CSS support -- [ ] Built-in focusing system +- [x] Built-in focusing system - [ ] high-quality keyboard support -- [ ] Support for events, hooks, and callbacks -* [ ] Html tags1 - -1 Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality. - +* [ ] Support for events, hooks, and callbacks1 +* [ ] Html tags2 +1 Basic keyboard and mouse events are implemented. +2 Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality. From 4aaf790ae9d4d37ff200055f39aaac399c1c0484 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 May 2022 13:35:30 -0500 Subject: [PATCH 08/21] revert widget trait --- packages/tui/src/render.rs | 4 ++-- packages/tui/src/widget.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 7fa09094e..ab25d6dc6 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -43,7 +43,7 @@ pub(crate) fn render_vnode( } impl<'a> RinkWidget for Label<'a> { - fn render(self, area: Rect, buf: &mut RinkBuffer) { + fn render(self, area: Rect, mut buf: RinkBuffer) { for (i, c) in self.text.char_indices() { let mut new_cell = RinkCell::default(); new_cell.set_style(self.style); @@ -81,7 +81,7 @@ pub(crate) fn render_vnode( } impl RinkWidget for &Node { - fn render(self, area: Rect, mut buf: &mut RinkBuffer<'_>) { + fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; enum Direction { diff --git a/packages/tui/src/widget.rs b/packages/tui/src/widget.rs index 1e4af52fc..416538462 100644 --- a/packages/tui/src/widget.rs +++ b/packages/tui/src/widget.rs @@ -41,7 +41,7 @@ impl<'a> RinkBuffer<'a> { } pub trait RinkWidget { - fn render(self, area: Rect, buf: &mut RinkBuffer); + fn render(self, area: Rect, buf: RinkBuffer); } pub struct WidgetWithContext { @@ -57,8 +57,7 @@ impl WidgetWithContext { impl Widget for WidgetWithContext { fn render(self, area: Rect, buf: &mut Buffer) { - let mut rbuf = RinkBuffer::new(buf, self.config); - self.widget.render(area, &mut rbuf); + self.widget.render(area, RinkBuffer::new(buf, self.config)); } } From 749150e22d222575d46c837f5199fbb38fdaac05 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 May 2022 14:15:07 -0500 Subject: [PATCH 09/21] change prevent default to listen to tab index --- packages/tui/src/focus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index cb578a8f1..2ea83a897 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -67,7 +67,7 @@ impl NodeDepState for Focus { let new = Focus { pass_focus: !node .attributes() - .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "true"), + .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "keydown"), level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { if let Ok(index) = a.value.parse::() { if index < 0 { From dd7784d20582b422257a95f0c0b1eae3d518cff6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 May 2022 14:28:02 -0500 Subject: [PATCH 10/21] clean up and document focus system --- packages/native-core/src/utils.rs | 2 ++ packages/tui/src/focus.rs | 36 +++++++++---------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index efd2a1d3b..cd18df01e 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -78,6 +78,7 @@ impl PersistantElementIter { } }) .collect(); + // if any element is removed in the chain, remove it and its children from the stack if let Some(r) = self .stack .iter() @@ -165,6 +166,7 @@ impl PersistantElementIter { /// get the previous element pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { + // recursively add the last child element to the stack fn push_back( stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>, new_node: ElementId, diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 2ea83a897..fa2e1dd97 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -120,11 +120,10 @@ impl FocusState { return false; } } + // the id that started focused to track when a loop has happened let mut loop_marker_id = self.last_focused_id; let focus_level = &mut self.focus_level; let mut next_focus = None; - let starting_focus_level = *focus_level; - let mut focus_level_changed = false; loop { let new = if forward { @@ -175,9 +174,6 @@ impl FocusState { if let Some(level) = closest_level { *focus_level = level; - if *focus_level != starting_focus_level { - focus_level_changed = true; - } } else { if forward { *focus_level = FocusLevel::Unfocusable; @@ -185,11 +181,6 @@ impl FocusState { *focus_level = FocusLevel::Focusable; } } - - // if the focus level looped, we are done - if *focus_level == starting_focus_level && focus_level_changed { - break; - } } // once we have looked at all the elements exit the loop @@ -209,28 +200,21 @@ impl FocusState { }; if after_previous_focused && current_level.focusable() { if current_level == *focus_level { - next_focus = Some((new_id, current_level)); + next_focus = Some(new_id); break; } } } - if let Some((id, order)) = next_focus { - if order.focusable() { - rdom[id].state.focused = true; - if let Some(old) = self.last_focused_id.replace(id) { - rdom[old].state.focused = false; - } - // reset the position to the currently focused element - while if forward { - self.focus_iter.next(&rdom).id() - } else { - self.focus_iter.prev(&rdom).id() - } != id - {} - self.dirty = true; - return true; + if let Some(id) = next_focus { + rdom[id].state.focused = true; + if let Some(old) = self.last_focused_id.replace(id) { + rdom[old].state.focused = false; } + // reset the position to the currently focused element + while self.focus_iter.next(&rdom).id() != id {} + self.dirty = true; + return true; } false From 5d5e7f55fdae6f928df85b76f5efdae036b96896 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 4 May 2022 14:37:30 -0500 Subject: [PATCH 11/21] pass clippy --- packages/native-core/src/utils.rs | 2 +- packages/tui/src/focus.rs | 97 ++++++++++++++++--------------- packages/tui/src/hooks.rs | 6 +- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index cd18df01e..143149521 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -198,7 +198,7 @@ impl PersistantElementIter { } else { *o_child_idx = o_child_idx.map(|i| i - 1); if let NodePosition::InChild(child_idx) = o_child_idx { - if *child_idx >= children.len() || children.len() == 0 { + if *child_idx >= children.len() || children.is_empty() { self.pop(); self.prev(rdom) } else { diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index fa2e1dd97..623bc255c 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -4,7 +4,7 @@ use dioxus_core::ElementId; use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; use dioxus_native_core_macro::sorted_str_slice; -use std::num::NonZeroU16; +use std::{cmp::Ordering, num::NonZeroU16}; use dioxus_native_core::{ node_ref::{AttributeMask, NodeMask, NodeView}, @@ -12,7 +12,7 @@ use dioxus_native_core::{ state::NodeDepState, }; -#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum FocusLevel { Unfocusable, Focusable, @@ -45,6 +45,12 @@ impl PartialOrd for FocusLevel { } } +impl Ord for FocusLevel { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + impl Default for FocusLevel { fn default() -> Self { FocusLevel::Unfocusable @@ -70,26 +76,24 @@ impl NodeDepState for Focus { .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "keydown"), level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { if let Ok(index) = a.value.parse::() { - if index < 0 { - FocusLevel::Unfocusable - } else if index == 0 { - FocusLevel::Focusable - } else { - FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap()) + match index.cmp(&0) { + Ordering::Less => FocusLevel::Unfocusable, + Ordering::Equal => FocusLevel::Focusable, + Ordering::Greater => { + FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap()) + } } } else { FocusLevel::Unfocusable } + } else if node + .listeners() + .iter() + .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok()) + { + FocusLevel::Focusable } else { - if node - .listeners() - .iter() - .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok()) - { - FocusLevel::Focusable - } else { - FocusLevel::Unfocusable - } + FocusLevel::Unfocusable }, }; if *self != new { @@ -127,9 +131,9 @@ impl FocusState { loop { let new = if forward { - self.focus_iter.next(&rdom) + self.focus_iter.next(rdom) } else { - self.focus_iter.prev(&rdom) + self.focus_iter.prev(rdom) }; let new_id = new.id(); if let ElementProduced::Looped(_) = new { @@ -139,15 +143,16 @@ impl FocusState { // find the closest focusable element after the current level rdom.traverse_depth_first(|n| { let node_level = n.state.focus.level; - if node_level != *focus_level && node_level.focusable() { - if node_level > *focus_level { - if let Some(level) = &mut closest_level { - if node_level < *level { - *level = node_level; - } - } else { - closest_level = Some(node_level); + if node_level != *focus_level + && node_level.focusable() + && node_level > *focus_level + { + if let Some(level) = &mut closest_level { + if node_level < *level { + *level = node_level; } + } else { + closest_level = Some(node_level); } } }); @@ -155,15 +160,16 @@ impl FocusState { // find the closest focusable element before the current level rdom.traverse_depth_first(|n| { let node_level = n.state.focus.level; - if node_level != *focus_level && node_level.focusable() { - if node_level < *focus_level { - if let Some(level) = &mut closest_level { - if node_level > *level { - *level = node_level; - } - } else { - closest_level = Some(node_level); + if node_level != *focus_level + && node_level.focusable() + && node_level < *focus_level + { + if let Some(level) = &mut closest_level { + if node_level > *level { + *level = node_level; } + } else { + closest_level = Some(node_level); } } }); @@ -174,12 +180,10 @@ impl FocusState { if let Some(level) = closest_level { *focus_level = level; + } else if forward { + *focus_level = FocusLevel::Unfocusable; } else { - if forward { - *focus_level = FocusLevel::Unfocusable; - } else { - *focus_level = FocusLevel::Focusable; - } + *focus_level = FocusLevel::Focusable; } } @@ -198,11 +202,10 @@ impl FocusState { } else { current_level <= *focus_level }; - if after_previous_focused && current_level.focusable() { - if current_level == *focus_level { - next_focus = Some(new_id); - break; - } + if after_previous_focused && current_level.focusable() && current_level == *focus_level + { + next_focus = Some(new_id); + break; } } @@ -212,7 +215,7 @@ impl FocusState { rdom[old].state.focused = false; } // reset the position to the currently focused element - while self.focus_iter.next(&rdom).id() != id {} + while self.focus_iter.next(rdom).id() != id {} self.dirty = true; return true; } @@ -267,7 +270,7 @@ impl FocusState { state.focused = true; self.focus_level = state.focus.level; // reset the position to the currently focused element - while self.focus_iter.next(&rdom).id() != id {} + while self.focus_iter.next(rdom).id() != id {} self.dirty = true; } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 6dd3e88bb..79a32c5e1 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -494,10 +494,8 @@ impl InnerInputState { 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 { - if node.state.focus.level.focusable() { - focus_id = Some(node.id); - } + if currently_contains && node.state.focus.level.focusable() { + focus_id = Some(node.id); } }); if let Some(id) = focus_id { From a87edd1a76b8efb964bb97c666a314b78e45fc67 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 May 2022 07:08:51 -0500 Subject: [PATCH 12/21] add focus event --- packages/tui/src/hooks.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 79a32c5e1..bb3ffb784 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -500,6 +500,13 @@ impl InnerInputState { }); if let Some(id) = focus_id { self.focus_state.set_focus(dom, id); + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: "focus", + element: Some(id), + data: Arc::new(FocusData {}), + }) } } } From acfde71d7e254cf46375b1686e7b66c5e4945999 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 May 2022 07:09:18 -0500 Subject: [PATCH 13/21] refactor prevent-default --- packages/tui/src/focus.rs | 10 ++--- packages/tui/src/lib.rs | 22 +--------- packages/tui/src/node.rs | 91 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 packages/tui/src/node.rs diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 623bc255c..c3fffd119 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -1,4 +1,4 @@ -use crate::Dom; +use crate::{node::PreventDefault, Dom}; use dioxus_core::ElementId; use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; @@ -59,7 +59,6 @@ impl Default for FocusLevel { #[derive(Clone, PartialEq, Debug, Default)] pub(crate) struct Focus { - pub pass_focus: bool, pub level: FocusLevel, } @@ -71,9 +70,6 @@ impl NodeDepState for Focus { fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { let new = Focus { - pass_focus: !node - .attributes() - .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "keydown"), level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { if let Ok(index) = a.value.parse::() { match index.cmp(&0) { @@ -106,7 +102,7 @@ impl NodeDepState for Focus { } const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]); -const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["dioxus-prevent-default", "tabindex"]); +const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]); #[derive(Default)] pub(crate) struct FocusState { @@ -120,7 +116,7 @@ impl FocusState { /// Returns true if the focus has changed. pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool { if let Some(last) = self.last_focused_id { - if !rdom[last].state.focus.pass_focus { + if rdom[last].state.prevent_default == PreventDefault::KeyDown { return false; } } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f8ee2ec80..b310b1186 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -8,26 +8,22 @@ use crossterm::{ use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; use dioxus_native_core::real_dom::RealDom; -use dioxus_native_core::state::*; -use dioxus_native_core_macro::State; -use focus::Focus; use focus::FocusState; 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; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; mod focus; mod hooks; mod layout; +mod node; mod render; mod style; mod style_attributes; @@ -35,21 +31,7 @@ mod widget; pub use config::*; pub use hooks::*; - -type Dom = RealDom; -type Node = dioxus_native_core::real_dom::Node; - -#[derive(Debug, Clone, State, Default)] -struct NodeState { - #[child_dep_state(layout, RefCell)] - layout: StretchLayout, - // depends on attributes, the C component of it's parent and a u8 context - #[parent_dep_state(style)] - style: StyleModifier, - #[node_dep_state()] - focus: Focus, - focused: bool, -} +pub(crate) use node::*; #[derive(Clone)] pub struct TuiContext { diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs new file mode 100644 index 000000000..d16c9cc19 --- /dev/null +++ b/packages/tui/src/node.rs @@ -0,0 +1,91 @@ +use crate::focus::Focus; +use crate::layout::StretchLayout; +use crate::style_attributes::StyleModifier; +use dioxus_native_core::{real_dom::RealDom, state::*}; +use dioxus_native_core_macro::State; + +pub(crate) type Dom = RealDom; +pub(crate) type Node = dioxus_native_core::real_dom::Node; + +#[derive(Debug, Clone, State, Default)] +pub(crate) struct NodeState { + #[child_dep_state(layout, RefCell)] + pub layout: StretchLayout, + // depends on attributes, the C component of it's parent and a u8 context + #[parent_dep_state(style)] + pub style: StyleModifier, + #[node_dep_state()] + pub prevent_default: PreventDefault, + #[node_dep_state()] + pub focus: Focus, + pub focused: bool, +} + +#[derive(PartialEq, Debug, Clone)] +pub(crate) enum PreventDefault { + Focus, + KeyPress, + KeyRelease, + KeyDown, + KeyUp, + MouseDown, + Click, + MouseEnter, + MouseLeave, + MouseOut, + Unknown, + MouseOver, + ContextMenu, + Wheel, + MouseUp, +} + +impl Default for PreventDefault { + fn default() -> Self { + PreventDefault::Unknown + } +} + +impl NodeDepState for PreventDefault { + type Ctx = (); + + type DepState = (); + + const NODE_MASK: dioxus_native_core::node_ref::NodeMask = + dioxus_native_core::node_ref::NodeMask::NONE; + + fn reduce( + &mut self, + node: dioxus_native_core::node_ref::NodeView, + _sibling: &Self::DepState, + _ctx: &Self::Ctx, + ) -> bool { + let new = match node + .attributes() + .find(|a| a.name == "dioxus-prevent-default") + .map(|a| a.value) + { + Some("focus") => PreventDefault::Focus, + Some("keypress") => PreventDefault::KeyPress, + Some("keyrelease") => PreventDefault::KeyRelease, + Some("keydown") => PreventDefault::KeyDown, + Some("keyup") => PreventDefault::KeyUp, + Some("click") => PreventDefault::Click, + Some("mousedown") => PreventDefault::MouseDown, + Some("mouseup") => PreventDefault::MouseUp, + Some("mouseenter") => PreventDefault::MouseEnter, + Some("mouseover") => PreventDefault::MouseOver, + Some("mouseleave") => PreventDefault::MouseLeave, + Some("mouseout") => PreventDefault::MouseOut, + Some("wheel") => PreventDefault::Wheel, + Some("contextmenu") => PreventDefault::ContextMenu, + _ => return false, + }; + if new == *self { + false + } else { + *self = new; + true + } + } +} From 07c1964ebf98dc6fc5eec88813d144c1b73b9e9f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 May 2022 07:17:33 -0500 Subject: [PATCH 14/21] fix focus event --- packages/tui/src/hooks.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index bb3ffb784..f787a9da9 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -175,6 +175,8 @@ impl InnerInputState { self.wheel = None; + let old_focus = self.focus_state.last_focused_id; + evts.retain(|e| match &e.1 { EventData::Keyboard(k) => match k.key_code { KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key), @@ -189,6 +191,18 @@ impl InnerInputState { self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom); + if old_focus != self.focus_state.last_focused_id { + if let Some(id) = self.focus_state.last_focused_id { + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: "focus", + element: Some(id), + data: Arc::new(FocusData {}), + }); + } + } + // for s in &self.subscribers { // s(); // } @@ -500,13 +514,6 @@ impl InnerInputState { }); if let Some(id) = focus_id { self.focus_state.set_focus(dom, id); - resolved_events.push(UserEvent { - scope_id: None, - priority: EventPriority::Medium, - name: "focus", - element: Some(id), - data: Arc::new(FocusData {}), - }) } } } From bbe9c1bbf03bae674036e89de60c7eb01a809677 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 May 2022 07:21:36 -0500 Subject: [PATCH 15/21] change prevent default mask --- packages/tui/src/node.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index d16c9cc19..8e19e2632 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -2,7 +2,7 @@ use crate::focus::Focus; use crate::layout::StretchLayout; use crate::style_attributes::StyleModifier; use dioxus_native_core::{real_dom::RealDom, state::*}; -use dioxus_native_core_macro::State; +use dioxus_native_core_macro::{sorted_str_slice, State}; pub(crate) type Dom = RealDom; pub(crate) type Node = dioxus_native_core::real_dom::Node; @@ -52,7 +52,11 @@ impl NodeDepState for PreventDefault { type DepState = (); const NODE_MASK: dioxus_native_core::node_ref::NodeMask = - dioxus_native_core::node_ref::NodeMask::NONE; + dioxus_native_core::node_ref::NodeMask::new_with_attrs( + dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([ + "dioxus-prevent-default" + ])), + ); fn reduce( &mut self, From 53d5ea2f4292fd42c25ebf406ebb30e075545270 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 5 May 2022 11:37:11 -0500 Subject: [PATCH 16/21] change prevent default to detect on{event} rather than {event} --- packages/tui/src/node.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 8e19e2632..80d5ef094 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -69,20 +69,20 @@ impl NodeDepState for PreventDefault { .find(|a| a.name == "dioxus-prevent-default") .map(|a| a.value) { - Some("focus") => PreventDefault::Focus, - Some("keypress") => PreventDefault::KeyPress, - Some("keyrelease") => PreventDefault::KeyRelease, - Some("keydown") => PreventDefault::KeyDown, - Some("keyup") => PreventDefault::KeyUp, - Some("click") => PreventDefault::Click, - Some("mousedown") => PreventDefault::MouseDown, - Some("mouseup") => PreventDefault::MouseUp, - Some("mouseenter") => PreventDefault::MouseEnter, - Some("mouseover") => PreventDefault::MouseOver, - Some("mouseleave") => PreventDefault::MouseLeave, - Some("mouseout") => PreventDefault::MouseOut, - Some("wheel") => PreventDefault::Wheel, - Some("contextmenu") => PreventDefault::ContextMenu, + Some("onfocus") => PreventDefault::Focus, + Some("onkeypress") => PreventDefault::KeyPress, + Some("onkeyrelease") => PreventDefault::KeyRelease, + Some("onkeydown") => PreventDefault::KeyDown, + Some("onkeyup") => PreventDefault::KeyUp, + Some("onclick") => PreventDefault::Click, + Some("onmousedown") => PreventDefault::MouseDown, + Some("onmouseup") => PreventDefault::MouseUp, + Some("onmouseenter") => PreventDefault::MouseEnter, + Some("onmouseover") => PreventDefault::MouseOver, + Some("onmouseleave") => PreventDefault::MouseLeave, + Some("onmouseout") => PreventDefault::MouseOut, + Some("onwheel") => PreventDefault::Wheel, + Some("oncontextmenu") => PreventDefault::ContextMenu, _ => return false, }; if new == *self { From 3cba79d835c7f559bd2fd80ed87e3b7ff68c13d4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 7 May 2022 08:39:55 -0500 Subject: [PATCH 17/21] add focus in and focus out events --- packages/tui/src/hooks.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 706e8c341..8c91ddcef 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -200,6 +200,22 @@ impl InnerInputState { element: Some(id), data: Arc::new(FocusData {}), }); + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: "focusin", + element: Some(id), + data: Arc::new(FocusData {}), + }); + } + if let Some(id) = old_focus { + resolved_events.push(UserEvent { + scope_id: None, + priority: EventPriority::Medium, + name: "focusout", + element: Some(id), + data: Arc::new(FocusData {}), + }); } } From 31c7907f257a35c10317088e687cb961736410ff Mon Sep 17 00:00:00 2001 From: demonthos Date: Sat, 7 May 2022 13:56:11 -0500 Subject: [PATCH 18/21] update with AttritubeValue --- packages/tui/src/focus.rs | 6 +++++- packages/tui/src/node.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index c3fffd119..18e2fbd3f 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -71,7 +71,11 @@ impl NodeDepState for Focus { fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool { let new = Focus { level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") { - if let Ok(index) = a.value.parse::() { + if let Some(index) = a + .value + .as_int32() + .or(a.value.as_text().and_then(|v| v.parse::().ok())) + { match index.cmp(&0) { Ordering::Less => FocusLevel::Unfocusable, Ordering::Equal => FocusLevel::Focusable, diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index 80d5ef094..58b80a80f 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -67,7 +67,7 @@ impl NodeDepState for PreventDefault { let new = match node .attributes() .find(|a| a.name == "dioxus-prevent-default") - .map(|a| a.value) + .and_then(|a| a.value.as_text()) { Some("onfocus") => PreventDefault::Focus, Some("onkeypress") => PreventDefault::KeyPress, From bdbe452b336fbb12bf3e5ff34e647fdf7c51dfe3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 12 May 2022 20:52:06 -0500 Subject: [PATCH 19/21] fix merge --- 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 4070f865a..921b72ed0 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -213,7 +213,7 @@ impl InnerInputState { } fn resolve_mouse_events( - &self, + &mut self, previous_mouse: Option, resolved_events: &mut Vec, layout: &Stretch, @@ -259,8 +259,13 @@ impl InnerInputState { 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, @@ -517,7 +522,7 @@ impl InnerInputState { } // update focus - if released { + if was_released { let mut focus_id = None; dom.traverse_depth_first(|node| { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); From 1d699b0b6ea0cbe9609aaf02a24ac5683d922eed Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Jun 2022 19:42:03 -0500 Subject: [PATCH 20/21] fix absolute position for mouse events --- packages/tui/src/hooks.rs | 84 +++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 3a1646fdf..98102c5ba 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -256,16 +256,16 @@ impl InnerInputState { 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 node_layout = get_abs_layout(node, dom, layout); let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) + .filter(|pos| layout_contains_point(&node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + 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)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -280,11 +280,11 @@ impl InnerInputState { // mouseenter 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 node_layout = get_abs_layout(node, dom, layout); let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) + .filter(|pos| layout_contains_point(&node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains && !previously_contained { try_create_event( @@ -303,16 +303,16 @@ impl InnerInputState { // mouseover 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 node_layout = get_abs_layout(node, dom, layout); let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) + .filter(|pos| layout_contains_point(&node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains && !previously_contained { try_create_event( "mouseover", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -326,13 +326,13 @@ impl InnerInputState { 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(); - let currently_contains = layout_contains_point(node_layout, new_pos); + let node_layout = get_abs_layout(node, dom, layout); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains { try_create_event( "mousedown", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -347,13 +347,13 @@ impl InnerInputState { if was_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); + let node_layout = get_abs_layout(node, dom, layout); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains { try_create_event( "mouseup", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -369,13 +369,13 @@ impl InnerInputState { if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_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); + let node_layout = get_abs_layout(node, dom, layout); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains { try_create_event( "click", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -392,13 +392,13 @@ 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, new_pos); + let node_layout = get_abs_layout(node, dom, layout); + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains { try_create_event( "contextmenu", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -415,9 +415,9 @@ impl InnerInputState { 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); + let node_layout = get_abs_layout(node, dom, layout); + + let currently_contains = layout_contains_point(&node_layout, new_pos); if currently_contains { try_create_event( @@ -438,16 +438,16 @@ impl InnerInputState { // mouseleave 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 node_layout = get_abs_layout(node, dom, layout); let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) + .filter(|pos| layout_contains_point(&node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + let currently_contains = layout_contains_point(&node_layout, new_pos); if !currently_contains && previously_contained { try_create_event( "mouseleave", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -461,16 +461,16 @@ impl InnerInputState { // mouseout 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 node_layout = get_abs_layout(node, dom, layout); let previously_contained = old_pos - .filter(|pos| layout_contains_point(node_layout, *pos)) + .filter(|pos| layout_contains_point(&node_layout, *pos)) .is_some(); - let currently_contains = layout_contains_point(node_layout, new_pos); + let currently_contains = layout_contains_point(&node_layout, new_pos); if !currently_contains && previously_contained { try_create_event( "mouseout", - Arc::new(prepare_mouse_data(mouse_data, node_layout)), + Arc::new(prepare_mouse_data(mouse_data, &node_layout)), &mut will_bubble, resolved_events, node, @@ -487,6 +487,22 @@ impl InnerInputState { // } } +fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout { + let mut node_layout = taffy + .layout(node.state.layout.node.unwrap()) + .unwrap() + .clone(); + let mut current = node; + while let Some(parent_id) = current.parent { + let parent = &dom[parent_id]; + current = parent; + let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap(); + node_layout.location.x += parent_layout.location.x; + node_layout.location.y += parent_layout.location.y; + } + node_layout +} + pub struct RinkInputHandler { state: Rc>, queued_events: Rc>>, From 8e6b56c636926d468a41227ef487eb4a51fc81de Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Jun 2022 20:57:43 -0500 Subject: [PATCH 21/21] remove outdated comment --- packages/tui/src/node.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tui/src/node.rs b/packages/tui/src/node.rs index ead5e657e..9cf4ba6d2 100644 --- a/packages/tui/src/node.rs +++ b/packages/tui/src/node.rs @@ -11,7 +11,6 @@ pub(crate) type Node = dioxus_native_core::real_dom::Node; pub(crate) struct NodeState { #[child_dep_state(layout, RefCell)] pub layout: TaffyLayout, - // depends on attributes, the C component of it's parent and a u8 context #[parent_dep_state(style)] pub style: StyleModifier, #[node_dep_state()]