From baaa1752d3ccd5918fc472b1071b95a12b019687 Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Tue, 5 Jul 2022 19:44:54 -0500 Subject: [PATCH] add query context (#400) --- examples/tui_colorpicker.rs | 27 ++++++++++++ packages/tui/src/layout.rs | 6 +++ packages/tui/src/lib.rs | 36 +++++++++++----- packages/tui/src/query.rs | 85 +++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 examples/tui_colorpicker.rs create mode 100644 packages/tui/src/query.rs diff --git a/examples/tui_colorpicker.rs b/examples/tui_colorpicker.rs new file mode 100644 index 000000000..da3f179f8 --- /dev/null +++ b/examples/tui_colorpicker.rs @@ -0,0 +1,27 @@ +use dioxus::prelude::*; +use dioxus_tui::query::Query; +use dioxus_tui::Size; + +fn main() { + dioxus::tui::launch(app); +} + +fn app(cx: Scope) -> Element { + let hue = use_state(&cx, || 0.0); + let brightness = use_state(&cx, || 0.0); + let tui_query: Query = cx.consume_context().unwrap(); + cx.render(rsx! { + div{ + width: "100%", + background_color: "hsl({hue}, 70%, {brightness}%)", + onmousemove: move |evt| { + let node = tui_query.get(cx.root_node().mounted_id()); + let Size{width, height} = node.size().unwrap(); + let pos = evt.data.element_coordinates(); + hue.set((pos.x as f32/width as f32)*255.0); + brightness.set((pos.y as f32/height as f32)*100.0); + }, + "hsl({hue}, 70%, {brightness}%)", + } + }) +} diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 231eb1285..4685d9705 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -20,6 +20,12 @@ impl PossiblyUninitalized { _ => panic!(), } } + pub fn ok(self) -> Option { + match self { + Self::Initialized(i) => Some(i), + _ => None, + } + } } impl Default for PossiblyUninitalized { fn default() -> Self { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 51ff5aa73..bceb4e7ef 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -13,10 +13,12 @@ use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, pin_mut, StreamExt, }; +use query::Query; use std::cell::RefCell; use std::rc::Rc; use std::{io, time::Duration}; -use taffy::{geometry::Point, prelude::Size, Taffy}; +use taffy::Taffy; +pub use taffy::{geometry::Point, prelude::Size}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; @@ -24,6 +26,7 @@ mod focus; mod hooks; mod layout; mod node; +pub mod query; mod render; mod style; mod style_attributes; @@ -76,16 +79,23 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { } let cx = dom.base_scope(); + let rdom = Rc::new(RefCell::new(RealDom::new())); + let taffy = Rc::new(RefCell::new(Taffy::new())); cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); + cx.provide_root_context(Query { + rdom: rdom.clone(), + stretch: taffy.clone(), + }); - let mut rdom: Dom = RealDom::new(); - let mutations = dom.rebuild(); - let to_update = rdom.apply_mutations(vec![mutations]); - let taffy = Rc::new(RefCell::new(Taffy::new())); - let mut any_map = AnyMap::new(); - any_map.insert(taffy.clone()); - let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); + { + let mut rdom = rdom.borrow_mut(); + let mutations = dom.rebuild(); + let to_update = rdom.apply_mutations(vec![mutations]); + let mut any_map = AnyMap::new(); + any_map.insert(taffy.clone()); + let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap(); + } render_vdom( &mut dom, @@ -104,7 +114,7 @@ fn render_vdom( mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut rdom: Dom, + rdom: Rc>, taffy: Rc>, mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { @@ -157,6 +167,7 @@ fn render_vdom( } if let Some(terminal) = &mut terminal { terminal.draw(|frame| { + let rdom = rdom.borrow(); // size is guaranteed to not change when rendering resize(frame.size(), &mut taffy.borrow_mut(), &rdom); let root = &rdom[0]; @@ -170,6 +181,7 @@ fn render_vdom( ); })?; } else { + let rdom = rdom.borrow(); resize( Rect { x: 0, @@ -217,13 +229,17 @@ fn render_vdom( } { - let evts = handler.get_events(&taffy.borrow(), &mut rdom); + let evts = { + let mut rdom = rdom.borrow_mut(); + handler.get_events(&taffy.borrow(), &mut rdom) + }; { updated |= handler.state().focus_state.clean(); } for e in evts { vdom.handle_message(SchedulerMsg::Event(e)); } + let mut rdom = rdom.borrow_mut(); let mutations = vdom.work_with_deadline(|| false); for m in &mutations { handler.prune(m, &rdom); diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs new file mode 100644 index 000000000..495a80539 --- /dev/null +++ b/packages/tui/src/query.rs @@ -0,0 +1,85 @@ +use std::{ + cell::{Ref, RefCell}, + rc::Rc, +}; + +use dioxus_core::ElementId; +use taffy::{ + geometry::Point, + prelude::{Layout, Size}, + Taffy, +}; + +use crate::Dom; + +/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered. +/// Provided as a root context for all tui applictions. +/// # Example +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus::tui::query::Query; +/// use dioxus::tui::Size; +/// +/// fn main() { +/// dioxus::tui::launch(app); +/// } +/// +/// fn app(cx: Scope) -> Element { +/// let hue = use_state(&cx, || 0.0); +/// let brightness = use_state(&cx, || 0.0); +/// let tui_query: Query = cx.consume_context().unwrap(); +/// cx.render(rsx! { +/// div{ +/// width: "100%", +/// background_color: "hsl({hue}, 70%, {brightness}%)", +/// onmousemove: move |evt| { +/// let node = tui_query.get(cx.root_node().mounted_id()); +/// let Size{width, height} = node.size().unwrap(); +/// hue.set((evt.data.offset_x as f32/width as f32)*255.0); +/// brightness.set((evt.data.offset_y as f32/height as f32)*100.0); +/// }, +/// "hsl({hue}, 70%, {brightness}%)", +/// } +/// }) +/// } +/// ``` +#[derive(Clone)] +pub struct Query { + pub(crate) rdom: Rc>, + pub(crate) stretch: Rc>, +} + +impl Query { + pub fn get(&self, id: ElementId) -> ElementRef { + ElementRef::new(self.rdom.borrow(), self.stretch.borrow(), id) + } +} + +pub struct ElementRef<'a> { + inner: Ref<'a, Dom>, + stretch: Ref<'a, Taffy>, + id: ElementId, +} + +impl<'a> ElementRef<'a> { + fn new(inner: Ref<'a, Dom>, stretch: Ref<'a, Taffy>, id: ElementId) -> Self { + Self { inner, stretch, id } + } + + pub fn size(&self) -> Option> { + self.layout().map(|l| l.size.map(|v| v as u32)) + } + + pub fn pos(&self) -> Option> { + self.layout().map(|l| Point { + x: l.location.x as u32, + y: l.location.y as u32, + }) + } + + pub fn layout(&self) -> Option<&Layout> { + self.stretch + .layout(self.inner[self.id].state.layout.node.ok()?) + .ok() + } +}