From 35ee243d0d0663f112cc8ae82306d475901d95f8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 3 May 2022 11:02:35 -0500 Subject: [PATCH] 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); } }