diff --git a/docs/reference/src/platforms/tui.md b/docs/reference/src/platforms/tui.md index e646bd4ba..82e9c1f2a 100644 --- a/docs/reference/src/platforms/tui.md +++ b/docs/reference/src/platforms/tui.md @@ -9,13 +9,12 @@ TUI support is currently quite experimental. Even the project name will change. ## Getting Set up -To tinker with TUI support, start by making a new package and adding our TUI package from git. +To tinker with TUI support, start by making a new package and adding our TUI feature. ```shell $ cargo new --bin demo $ cd demo -$ cargo add dioxus -$ cargo add rink --git https://github.com/DioxusLabs/rink.git +$ cargo add dioxus --features tui ``` @@ -27,10 +26,7 @@ Then, edit your `main.rs` with the basic template. use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + dioxus::tui::launch(app); } fn app(cx: Scope) -> Element { @@ -54,7 +50,44 @@ To run our app: $ cargo run ``` -Press "q" to close the app (yes, this is hardcoded, we are working on handlers to expose this in your code.) +Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a Configeration to disable the default quit and use the root TuiContext to quit on your own. + +```rust +// main +use dioxus::events::{KeyCode, KeyboardEvent}; +use dioxus::prelude::*; +use dioxus::tui::TuiContext; + +fn main() { + dioxus::tui::launch_cfg( + app, + dioxus::tui::Config { + ctrl_c_quit: false, + // Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI + rendering_mode: dioxus::tui::RenderingMode::Rgb, + }, + ); +} + +fn app(cx: Scope) -> Element { + let tui_ctx: TuiContext = cx.consume_context().unwrap(); + + cx.render(rsx! { + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code { + tui_ctx.quit(); + }, + + "Hello world!" + } + }) +} +``` ## Notes diff --git a/examples/tui_color_test.rs b/examples/tui_color_test.rs index 358fae1f6..d9ebad275 100644 --- a/examples/tui_color_test.rs +++ b/examples/tui_color_test.rs @@ -5,6 +5,7 @@ fn main() { app, dioxus::tui::Config { rendering_mode: dioxus::tui::RenderingMode::Ansi, + ..Default::default() }, ); } diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index 9518451fc..45461a3f9 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/packages/native-core/src/layout_attributes.rs @@ -255,11 +255,11 @@ pub enum UnitSystem { Point(f32), } -impl Into for UnitSystem { - fn into(self) -> Dimension { - match self { - Self::Percent(v) => Dimension::Percent(v), - Self::Point(v) => Dimension::Points(v), +impl From for Dimension { + fn from(other: UnitSystem) -> Dimension { + match other { + UnitSystem::Percent(v) => Dimension::Percent(v), + UnitSystem::Point(v) => Dimension::Points(v), } } } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 1d697ff4a..5f914cd56 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -12,25 +12,30 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; #[derive(Debug)] pub struct RealDom { root: usize, - nodes: Vec>>, + nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } +impl Default for RealDom { + fn default() -> Self { + Self::new() + } +} + impl RealDom { pub fn new() -> RealDom { RealDom { root: 0, nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( + let v = vec![Some(Node::new( 0, - TreeNodeType::Element { + NodeType::Element { tag: "Root".to_string(), namespace: Some("Root"), children: Vec::new(), }, - ))); + ))]; v }, nodes_listening: FxHashMap::default(), @@ -47,7 +52,7 @@ impl RealDom { match e { PushRoot { root } => self.node_stack.push(root as usize), AppendChildren { many } => { - let target = if self.node_stack.len() >= many as usize + 1 { + let target = if self.node_stack.len() > many as usize { *self .node_stack .get(self.node_stack.len() - (many as usize + 1)) @@ -96,9 +101,9 @@ impl RealDom { self.remove(root as usize).unwrap(); } CreateTextNode { root, text } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Text { + NodeType::Text { text: text.to_string(), }, ); @@ -106,9 +111,9 @@ impl RealDom { self.node_stack.push(root as usize) } CreateElement { root, tag } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Element { + NodeType::Element { tag: tag.to_string(), namespace: None, children: Vec::new(), @@ -118,9 +123,9 @@ impl RealDom { self.node_stack.push(root as usize) } CreateElementNs { root, tag, ns } => { - let n = TreeNode::new( + let n = Node::new( root, - TreeNodeType::Element { + NodeType::Element { tag: tag.to_string(), namespace: Some(ns), children: Vec::new(), @@ -130,7 +135,7 @@ impl RealDom { self.node_stack.push(root as usize) } CreatePlaceholder { root } => { - let n = TreeNode::new(root, TreeNodeType::Placeholder); + let n = Node::new(root, NodeType::Placeholder); self.insert(n); self.node_stack.push(root as usize) } @@ -159,7 +164,7 @@ impl RealDom { let target = &mut self[root as usize]; nodes_updated.push(root as usize); match &mut target.node_type { - TreeNodeType::Text { text } => { + NodeType::Text { text } => { *text = new_text.to_string(); } _ => unreachable!(), @@ -204,13 +209,13 @@ impl RealDom { let node_type = &node.node_type; let up_state = &mut node.up_state; let children = match node_type { - TreeNodeType::Element { children, .. } => Some(children), + NodeType::Element { children, .. } => Some(children), _ => None, }; // todo: reduce cloning state let old = up_state.clone(); let mut new = up_state.clone(); - let parent = node.parent.clone(); + let parent = node.parent; new.reduce( children .unwrap_or(&Vec::new()) @@ -254,19 +259,16 @@ impl RealDom { if new != old { to_rerender.insert(id); let node = &mut self[id as usize]; - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - let i = to_push.partition_point(|(other_id, h)| { - *h < height + 1 || (*h == height + 1 && *other_id < c.0) - }); - if i >= to_push.len() || to_push[i].0 != c.0 { - to_push.insert(i, (c.0, height + 1)); - } + if let NodeType::Element { children, .. } = &node.node_type { + for c in children { + let i = to_push.partition_point(|(other_id, h)| { + *h < height + 1 || (*h == height + 1 && *other_id < c.0) + }); + if i >= to_push.len() || to_push[i].0 != c.0 { + to_push.insert(i, (c.0, height + 1)); } } - _ => (), - }; + } node.down_state = new; } } @@ -287,31 +289,25 @@ impl RealDom { fn increase_height(&mut self, id: usize, amount: u16) { let n = &mut self[id]; n.height += amount; - match &n.node_type { - TreeNodeType::Element { children, .. } => { - for c in children.clone() { - self.increase_height(c.0, amount); - } + if let NodeType::Element { children, .. } = &n.node_type { + for c in children.clone() { + self.increase_height(c.0, amount); } - _ => (), } } // remove a node and it's children from the tree. - fn remove(&mut self, id: usize) -> Option> { + fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. fn inner( tree: &mut RealDom, id: usize, - ) -> Option> { + ) -> Option> { let mut node = tree.nodes[id as usize].take()?; - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(tree, c.0)?; - } + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children { + inner(tree, c.0)?; } - _ => (), } Some(node) } @@ -320,18 +316,15 @@ impl RealDom { let parent = &mut self[parent]; parent.remove_child(ElementId(id)); } - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(self, c.0)?; - } + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children { + inner(self, c.0)?; } - _ => (), } Some(node) } - fn insert(&mut self, node: TreeNode) { + fn insert(&mut self, node: Node) { let current_len = self.nodes.len(); let id = node.id.0; if current_len - 1 < node.id.0 { @@ -341,15 +334,15 @@ impl RealDom { self.nodes[id] = Some(node); } - pub fn get(&self, id: usize) -> Option<&TreeNode> { + pub fn get(&self, id: usize) -> Option<&Node> { self.nodes.get(id)?.as_ref() } - pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode> { + pub fn get_mut(&mut self, id: usize) -> Option<&mut Node> { self.nodes.get_mut(id)?.as_mut() } - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node> { if let Some(nodes) = self.nodes_listening.get(event) { let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); @@ -369,7 +362,7 @@ impl RealDom { if let Some(id) = e.id.get() { let tree_node = &self[id]; match &tree_node.node_type { - TreeNodeType::Element { + NodeType::Element { tag, namespace, children, @@ -398,7 +391,7 @@ impl RealDom { if let Some(id) = t.id.get() { let tree_node = &self[id]; match &tree_node.node_type { - TreeNodeType::Text { text } => t.text == text, + NodeType::Text { text } => t.text == text, _ => false, } } else { @@ -420,36 +413,53 @@ impl RealDom { } /// Call a function for each node in the tree, depth first. - pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode)) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { fn inner( tree: &RealDom, id: ElementId, - f: &mut impl FnMut(&TreeNode), + f: &mut impl FnMut(&Node), ) { let node = &tree[id]; f(node); - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(tree, *c, f); - } + if let NodeType::Element { children, .. } = &node.node_type { + for c in children { + inner(tree, *c, f); } - _ => (), } } - match &self[self.root].node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - inner(self, *c, &mut f); + if let NodeType::Element { children, .. } = &self[self.root].node_type { + for c in children { + inner(self, *c, &mut f); + } + } + } + + /// Call a function for each node in the tree, depth first. + pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { + fn inner( + tree: &mut RealDom, + id: ElementId, + f: &mut impl FnMut(&mut Node), + ) { + let node = &mut tree[id]; + f(node); + if let NodeType::Element { children, .. } = &mut node.node_type { + for c in children.clone() { + inner(tree, c, f); } } - _ => (), + } + let root = self.root; + if let NodeType::Element { children, .. } = &mut self[root].node_type { + for c in children.clone() { + inner(self, c, &mut f); + } } } } impl Index for RealDom { - type Output = TreeNode; + type Output = Node; fn index(&self, idx: usize) -> &Self::Output { self.get(idx).expect("Node does not exist") @@ -457,7 +467,7 @@ impl Index for RealDom { } impl Index for RealDom { - type Output = TreeNode; + type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { &self[idx.0] @@ -477,7 +487,7 @@ impl IndexMut for RealDom { +pub struct Node { /// The id of the node this node was created from. pub id: ElementId, /// The parent id of the node. @@ -487,13 +497,13 @@ pub struct TreeNode { /// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent. pub down_state: DS, /// Additional inforation specific to the node type - pub node_type: TreeNodeType, + pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. pub height: u16, } #[derive(Debug, Clone)] -pub enum TreeNodeType { +pub enum NodeType { Text { text: String, }, @@ -505,9 +515,9 @@ pub enum TreeNodeType { Placeholder, } -impl TreeNode { - fn new(id: u64, node_type: TreeNodeType) -> Self { - TreeNode { +impl Node { + fn new(id: u64, node_type: NodeType) -> Self { + Node { id: ElementId(id as usize), parent: None, node_type, @@ -523,20 +533,14 @@ impl TreeNode { } fn add_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.push(child); - } - _ => (), + if let NodeType::Element { children, .. } = &mut self.node_type { + children.push(child); } } fn remove_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.retain(|c| c != &child); - } - _ => (), + if let NodeType::Element { children, .. } = &mut self.node_type { + children.retain(|c| c != &child); } } diff --git a/packages/tui/src/config.rs b/packages/tui/src/config.rs index 46a0926b8..924163a7d 100644 --- a/packages/tui/src/config.rs +++ b/packages/tui/src/config.rs @@ -1,6 +1,18 @@ -#[derive(Default, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Config { pub rendering_mode: RenderingMode, + /// Should the terminal quit when the user presses `ctrl+c`? + /// To handle quiting on your own, use the [crate::TuiContext] root context. + pub ctrl_c_quit: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rendering_mode: Default::default(), + ctrl_c_quit: true, + } + } } #[derive(Clone, Copy)] diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 3813b97ae..3a488e433 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,8 +5,7 @@ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::real_dom::{RealDom, TreeNode}; -use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; +use dioxus_native_core::real_dom::{Node, RealDom}; use std::{ any::Any, cell::RefCell, @@ -162,7 +161,7 @@ impl InnerInputState { } } - fn update<'a>( + fn update( &mut self, evts: &mut Vec, resolved_events: &mut Vec, @@ -176,9 +175,11 @@ impl InnerInputState { self.wheel = None; + println!("update {evts:?}"); for e in evts.iter_mut() { self.apply_event(e); } + println!("->update {evts:?}"); self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); @@ -216,7 +217,7 @@ impl InnerInputState { data: Arc, will_bubble: &mut FxHashSet, resolved_events: &mut Vec, - node: &TreeNode, + node: &Node, tree: &RealDom, ) { // only trigger event if the event was not triggered already by a child @@ -231,7 +232,7 @@ impl InnerInputState { priority: EventPriority::Medium, name, element: Some(node.id), - data: data, + data, }) } } @@ -269,17 +270,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if previously_contained { - try_create_event( - "mousemove", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && previously_contained { + try_create_event( + "mousemove", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -295,17 +294,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if !previously_contained { - try_create_event( - "mouseenter", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && !previously_contained { + try_create_event( + "mouseenter", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -321,17 +318,15 @@ impl InnerInputState { .is_some(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if !previously_contained { - try_create_event( - "mouseover", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && !previously_contained { + try_create_event( + "mouseover", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -343,17 +338,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.clicked { - try_create_event( - "mousedown", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.clicked { + try_create_event( + "mousedown", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -365,17 +358,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released { - try_create_event( - "mouseup", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released { + try_create_event( + "mouseup", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -387,17 +378,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released && data.mouse_data.button == 0 { - try_create_event( - "click", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released && data.mouse_data.button == 0 { + try_create_event( + "click", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -409,17 +398,15 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if data.released && data.mouse_data.button == 2 { - try_create_event( - "contextmenu", - Arc::new(clone_mouse_data(data.mouse_data)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if currently_contains && data.released && data.mouse_data.button == 2 { + try_create_event( + "contextmenu", + Arc::new(clone_mouse_data(data.mouse_data)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -431,18 +418,16 @@ impl InnerInputState { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); - if currently_contains { - if let Some(w) = data.wheel_data { - if data.wheel_delta != 0.0 { - try_create_event( - "wheel", - Arc::new(clone_wheel_data(w)), - &mut will_bubble, - resolved_events, - node, - tree, - ); - } + if let Some(w) = data.wheel_data { + if currently_contains && data.wheel_delta != 0.0 { + try_create_event( + "wheel", + Arc::new(clone_wheel_data(w)), + &mut will_bubble, + resolved_events, + node, + tree, + ); } } } @@ -511,24 +496,22 @@ pub struct RinkInputHandler { impl RinkInputHandler { /// global context that handles events /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once - pub fn new( - mut receiver: UnboundedReceiver, - cx: &ScopeState, - ) -> (Self, Rc>) { + pub fn new() -> ( + Self, + Rc>, + impl FnMut(crossterm::event::Event), + ) { let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events2 = Rc::downgrade(&queued_events); - cx.push_future(async move { - while let Some(evt) = receiver.next().await { - if let Some(evt) = get_event(evt) { - if let Some(v) = queued_events2.upgrade() { - (*v).borrow_mut().push(evt); - } else { - break; - } + let regester_event = move |evt: crossterm::event::Event| { + if let Some(evt) = get_event(evt) { + if let Some(v) = queued_events2.upgrade() { + println!("queued event: {:?}", evt); + (*v).borrow_mut().push(evt); } } - }); + }; let state = Rc::new(RefCell::new(InnerInputState::new())); @@ -538,14 +521,16 @@ impl RinkInputHandler { queued_events, }, state, + regester_event, ) } - pub fn get_events<'a>( + pub fn get_events( &self, layout: &Stretch, tree: &mut RealDom, ) -> Vec { + println!("get_events"); let mut resolved_events = Vec::new(); (*self.state).borrow_mut().update( @@ -799,7 +784,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent Some(EventData::Keyboard(KeyboardData { char_code: code.raw_code(), - key: key_str.to_string(), + key: key_str, key_code: code, alt_key: event.modifiers.contains(KeyModifiers::ALT), ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 9d9be8f6a..4224fbdd5 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -65,7 +65,7 @@ impl BubbledUpState for StretchLayout { } if let Some(n) = self.node { - if &stretch.children(n).unwrap() != &child_layout { + if stretch.children(n).unwrap() != child_layout { stretch.set_children(n, &child_layout).unwrap(); } if self.style != style { diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 4c869d1f9..a70acbd28 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -1,6 +1,3 @@ -// notes: -// mouse events binary search was broken for absolutely positioned elements - use anyhow::Result; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, @@ -48,11 +45,8 @@ pub fn launch(app: Component<()>) { pub fn launch_cfg(app: Component<()>, cfg: Config) { let mut dom = VirtualDom::new(app); - let (tx, rx) = unbounded(); - let cx = dom.base_scope(); - - let (handler, state) = RinkInputHandler::new(rx, cx); + let (handler, state, register_event) = RinkInputHandler::new(); // Setup input handling let (event_tx, event_rx) = unbounded(); @@ -70,6 +64,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { } }); + let cx = dom.base_scope(); cx.provide_root_context(state); cx.provide_root_context(TuiContext { tx: event_tx_clone }); @@ -81,17 +76,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { .update_state(&dom, to_update, &mut stretch, &mut ()) .unwrap(); - render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap(); + render_vdom( + &mut dom, + event_rx, + handler, + cfg, + tree, + stretch, + register_event, + ) + .unwrap(); } fn render_vdom( vdom: &mut VirtualDom, - crossterm_event_sender: UnboundedSender, mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, mut tree: RealDom, mut stretch: Stretch, + mut register_event: impl FnMut(crossterm::event::Event), ) -> Result<()> { tokio::runtime::Builder::new_current_thread() .enable_all() @@ -105,7 +109,7 @@ fn render_vdom( terminal.clear().unwrap(); let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); - let mut redraw = true; + let mut resized = true; loop { /* @@ -119,8 +123,8 @@ fn render_vdom( todo: lazy re-rendering */ - if !to_rerender.is_empty() || redraw { - redraw = false; + if !to_rerender.is_empty() || resized { + resized = false; terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); @@ -138,8 +142,8 @@ fn render_vdom( }, ) .unwrap(); - let root = &tree[tree.root_id()]; - render::render_vnode(frame, &stretch, &tree, &root, cfg); + // let root = &tree[tree.root_id()]; + // render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; } @@ -158,35 +162,44 @@ fn render_vdom( TermEvent::Key(key) => { if matches!(key.code, KeyCode::Char('C' | 'c')) && key.modifiers.contains(KeyModifiers::CONTROL) + && cfg.ctrl_c_quit { break; } } - TermEvent::Resize(_, _) => redraw = true, + TermEvent::Resize(_, _) => resized = true, TermEvent::Mouse(_) => {} }, InputEvent::Close => break, }; if let InputEvent::UserInput(evt) = evt.unwrap() { - crossterm_event_sender.unbounded_send(evt).unwrap(); + register_event(evt); } } } } - // resolve events before rendering - for e in handler.get_events(&stretch, &mut tree) { - vdom.handle_message(SchedulerMsg::Event(e)); + { + // resolve events before rendering + let evts = handler.get_events(&stretch, &mut tree); + println!("evts: {:?}", evts); + for e in evts { + vdom.handle_message(SchedulerMsg::Event(e)); + } + let mutations = vdom.work_with_deadline(|| false); + // updates the tree's nodes + let to_update = tree.apply_mutations(mutations); + // update the style and layout + to_rerender.extend( + tree.update_state(vdom, to_update, &mut stretch, &mut ()) + .unwrap() + .iter(), + ) } - vdom.process_all_messages(); - let mutations = vdom.work_with_deadline(|| false); - // updates the tree's nodes - let to_update = tree.apply_mutations(mutations); - // update the style and layout - to_rerender = tree - .update_state(&vdom, to_update, &mut stretch, &mut ()) - .unwrap(); + println!(); + println!(); + println!(); } disable_raw_mode()?; @@ -203,6 +216,5 @@ fn render_vdom( enum InputEvent { UserInput(TermEvent), - #[allow(dead_code)] Close, } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 9e4310be4..c00830907 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,7 +1,7 @@ use crate::layout::StretchLayout; use dioxus_native_core::{ layout_attributes::UnitSystem, - real_dom::{RealDom, TreeNode}, + real_dom::{Node, RealDom}, }; use std::io::Stdout; use stretch2::{ @@ -20,18 +20,17 @@ use crate::{ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; -pub fn render_vnode<'a>( +pub fn render_vnode( frame: &mut tui::Frame>, layout: &Stretch, tree: &RealDom, - node: &TreeNode, + node: &Node, cfg: Config, ) { - use dioxus_native_core::real_dom::TreeNodeType; + use dioxus_native_core::real_dom::NodeType; - match &node.node_type { - TreeNodeType::Placeholder => return, - _ => (), + if let NodeType::Placeholder = &node.node_type { + return; } let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); @@ -40,7 +39,7 @@ pub fn render_vnode<'a>( let Size { width, height } = size; match &node.node_type { - TreeNodeType::Text { text } => { + NodeType::Text { text } => { #[derive(Default)] struct Label<'a> { text: &'a str, @@ -59,7 +58,7 @@ pub fn render_vnode<'a>( } let label = Label { - text: &text, + text, style: node.down_state.style, }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); @@ -69,7 +68,7 @@ pub fn render_vnode<'a>( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - TreeNodeType::Element { children, .. } => { + NodeType::Element { children, .. } => { let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -81,11 +80,11 @@ pub fn render_vnode<'a>( render_vnode(frame, layout, tree, &tree[c.0], cfg); } } - TreeNodeType::Placeholder => unreachable!(), + NodeType::Placeholder => unreachable!(), } } -impl RinkWidget for &TreeNode { +impl RinkWidget for &Node { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { use tui::symbols::line::*; @@ -249,8 +248,8 @@ impl RinkWidget for &TreeNode { fn get_radius(border: &BorderEdge, area: Rect) -> f32 { match border.style { - BorderStyle::HIDDEN => 0.0, - BorderStyle::NONE => 0.0, + BorderStyle::Hidden => 0.0, + BorderStyle::None => 0.0, _ => match border.radius { UnitSystem::Percent(p) => p * area.width as f32 / 100.0, UnitSystem::Point(p) => p, diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 6d0bb9672..710610104 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -51,33 +51,28 @@ impl PushedDownState for StyleModifier { if parent.is_some() { self.style.fg = None; } - match vnode { - VNode::Element(el) => { - // handle text modifier elements - if el.namespace.is_none() { - match el.tag { - "b" => apply_style_attributes("font-weight", "bold", self), - "strong" => apply_style_attributes("font-weight", "bold", self), - "u" => apply_style_attributes("text-decoration", "underline", self), - "ins" => apply_style_attributes("text-decoration", "underline", self), - "del" => apply_style_attributes("text-decoration", "line-through", self), - "i" => apply_style_attributes("font-style", "italic", self), - "em" => apply_style_attributes("font-style", "italic", self), - "mark" => apply_style_attributes( - "background-color", - "rgba(241, 231, 64, 50%)", - self, - ), - _ => (), + if let VNode::Element(el) = vnode { + // handle text modifier elements + if el.namespace.is_none() { + match el.tag { + "b" => apply_style_attributes("font-weight", "bold", self), + "strong" => apply_style_attributes("font-weight", "bold", self), + "u" => apply_style_attributes("text-decoration", "underline", self), + "ins" => apply_style_attributes("text-decoration", "underline", self), + "del" => apply_style_attributes("text-decoration", "line-through", self), + "i" => apply_style_attributes("font-style", "italic", self), + "em" => apply_style_attributes("font-style", "italic", self), + "mark" => { + apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self) } - } - - // gather up all the styles from the attribute list - for &Attribute { name, value, .. } in el.attributes { - apply_style_attributes(name, value, self); + _ => (), } } - _ => (), + + // gather up all the styles from the attribute list + for &Attribute { name, value, .. } in el.attributes { + apply_style_attributes(name, value, self); + } } // keep the text styling from the parent element @@ -125,7 +120,7 @@ impl Default for BorderEdge { fn default() -> Self { Self { color: None, - style: BorderStyle::NONE, + style: BorderStyle::None, width: UnitSystem::Point(0.0), radius: UnitSystem::Point(0.0), } @@ -134,16 +129,16 @@ impl Default for BorderEdge { #[derive(Clone, Copy, PartialEq, Debug)] pub enum BorderStyle { - DOTTED, - DASHED, - SOLID, - DOUBLE, - GROOVE, - RIDGE, - INSET, - OUTSET, - HIDDEN, - NONE, + Dotted, + Dashed, + Solid, + Double, + Groove, + Ridge, + Inset, + Outset, + Hidden, + None, } impl BorderStyle { @@ -160,16 +155,16 @@ impl BorderStyle { ..NORMAL }; match self { - BorderStyle::DOTTED => Some(DOTTED), - BorderStyle::DASHED => Some(DASHED), - BorderStyle::SOLID => Some(NORMAL), - BorderStyle::DOUBLE => Some(DOUBLE), - BorderStyle::GROOVE => Some(NORMAL), - BorderStyle::RIDGE => Some(NORMAL), - BorderStyle::INSET => Some(NORMAL), - BorderStyle::OUTSET => Some(NORMAL), - BorderStyle::HIDDEN => None, - BorderStyle::NONE => None, + BorderStyle::Dotted => Some(DOTTED), + BorderStyle::Dashed => Some(DASHED), + BorderStyle::Solid => Some(NORMAL), + BorderStyle::Double => Some(DOUBLE), + BorderStyle::Groove => Some(NORMAL), + BorderStyle::Ridge => Some(NORMAL), + BorderStyle::Inset => Some(NORMAL), + BorderStyle::Outset => Some(NORMAL), + BorderStyle::Hidden => None, + BorderStyle::None => None, } } } @@ -334,16 +329,16 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifier) { fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { fn parse_border_style(v: &str) -> BorderStyle { match v { - "dotted" => BorderStyle::DOTTED, - "dashed" => BorderStyle::DASHED, - "solid" => BorderStyle::SOLID, - "double" => BorderStyle::DOUBLE, - "groove" => BorderStyle::GROOVE, - "ridge" => BorderStyle::RIDGE, - "inset" => BorderStyle::INSET, - "outset" => BorderStyle::OUTSET, - "none" => BorderStyle::NONE, - "hidden" => BorderStyle::HIDDEN, + "dotted" => BorderStyle::Dotted, + "dashed" => BorderStyle::Dashed, + "solid" => BorderStyle::Solid, + "double" => BorderStyle::Double, + "groove" => BorderStyle::Groove, + "ridge" => BorderStyle::Ridge, + "inset" => BorderStyle::Inset, + "outset" => BorderStyle::Outset, + "none" => BorderStyle::None, + "hidden" => BorderStyle::Hidden, _ => todo!(), } } @@ -505,7 +500,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { .zip(style.modifier.borders.slice().iter_mut()) { if let Some(w) = parse_value(v) { - width.width = w.into(); + width.width = w; } } } diff --git a/src/lib.rs b/src/lib.rs index cebffcf17..e9e67408e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub use dioxus_desktop as desktop; #[cfg(feature = "tui")] pub use dioxus_tui as tui; +#[cfg(feature = "native-core")] +pub use dioxus_native_core as native_core; + #[cfg(feature = "fermi")] pub use fermi;