From 2d6481ba4329c258ae03ccb884b2cdb4e12dbb5c Mon Sep 17 00:00:00 2001 From: Inanna Malick Date: Sun, 26 Feb 2023 12:15:49 -0800 Subject: [PATCH] make trees generic over any type that can be viewed as txt --- examples/example.rs | 16 +++++++----- examples/util/mod.rs | 23 ++++++++++------- src/flatten.rs | 34 ++++++++++++++----------- src/lib.rs | 60 ++++++++++++++++++++++++-------------------- 4 files changed, 77 insertions(+), 56 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 440efe9..f8e277a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -17,7 +17,7 @@ use tui::{ use tui_tree_widget::{Tree, TreeItem}; struct App<'a> { - tree: StatefulTree<'a>, + tree: StatefulTree<&'a str>, } impl<'a> App<'a> { @@ -29,7 +29,10 @@ impl<'a> App<'a> { "b", vec![ TreeItem::new_leaf("c"), - TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]), + TreeItem::new( + "d", + vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")], + ), TreeItem::new_leaf("g"), ], ), @@ -37,7 +40,6 @@ impl<'a> App<'a> { ]), } } - } fn main() -> Result<(), Box> { @@ -93,10 +95,12 @@ fn run_app<'a, B: Backend>(terminal: &mut Terminal, mut app: App<'a>) -> io:: match key.code { KeyCode::Char('q') => return Ok(()), KeyCode::Char('a') => { - app.tree.with_selected_leaf(|node| if let Some(node) = node { - node.add_child(TreeItem::new_leaf("text")); + app.tree.with_selected_leaf(|node| { + if let Some(node) = node { + node.add_child(TreeItem::new_leaf("text")); + } }); - }, + } KeyCode::Char('\n' | ' ') => app.tree.toggle(), KeyCode::Left => app.tree.left(), KeyCode::Right => app.tree.right(), diff --git a/examples/util/mod.rs b/examples/util/mod.rs index 2b2395f..4f4cb6e 100644 --- a/examples/util/mod.rs +++ b/examples/util/mod.rs @@ -1,11 +1,11 @@ -use tui_tree_widget::{TreeItem, TreeState}; +use tui_tree_widget::{TreeItem, TreeState, TreeItemRender}; -pub struct StatefulTree<'a> { +pub struct StatefulTree { pub state: TreeState, - pub items: Vec>, + pub items: Vec>, } -impl<'a> StatefulTree<'a> { +impl StatefulTree { #[allow(dead_code)] pub fn new() -> Self { Self { @@ -14,7 +14,7 @@ impl<'a> StatefulTree<'a> { } } - pub fn with_items(items: Vec>) -> Self { + pub fn with_items(items: Vec>) -> Self { Self { state: TreeState::default(), items, @@ -49,13 +49,18 @@ impl<'a> StatefulTree<'a> { self.state.toggle_selected(); } - fn items_mut<'b>(&'b mut self) -> &'b mut Vec> { + fn items_mut<'b>(&'b mut self) -> &'b mut Vec> { &mut self.items } - pub fn with_selected_leaf<'b>(&'b mut self, f: impl FnOnce(Option<&'b mut TreeItem<'a>>)) where 'a: 'b - { - fn traverse<'short, 'long>(path: Vec, nodes: &'short mut [TreeItem<'long>]) -> Option<&'short mut TreeItem<'long>> where 'long: 'short { + pub fn with_selected_leaf<'b>(&'b mut self, f: impl FnOnce(Option<&'b mut TreeItem>)) + where + { + fn traverse<'short, A: TreeItemRender>( + path: Vec, + nodes: &'short mut [TreeItem], + ) -> Option<&'short mut TreeItem> + { let first = path.first()?; let node = nodes.get_mut(*first)?; if path.len() == 1 { diff --git a/src/flatten.rs b/src/flatten.rs index 132a84d..be41df2 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -3,27 +3,30 @@ use crate::identifier::{TreeIdentifier, TreeIdentifierVec}; use crate::TreeItem; -pub struct Flattened<'a> { +pub struct Flattened<'a, A> { pub identifier: Vec, - pub item: &'a TreeItem<'a>, + pub item: &'a TreeItem, } -impl<'a> Flattened<'a> { +impl<'a, A> Flattened<'a, A> { pub fn depth(&self) -> usize { self.identifier.len() - 1 } } /// Get a flat list of all visible [`TreeItem`s](TreeItem) -pub fn flatten<'a>(opened: &[TreeIdentifierVec], items: &'a [TreeItem<'a>]) -> Vec> { +pub fn flatten<'a, A>( + opened: &[TreeIdentifierVec], + items: &'a [TreeItem], +) -> Vec> { internal(opened, items, &[]) } -fn internal<'a>( +fn internal<'a, A>( opened: &[TreeIdentifierVec], - items: &'a [TreeItem<'a>], + items: &'a [TreeItem], current: TreeIdentifier, -) -> Vec> { +) -> Vec> { let mut result = Vec::new(); for (index, item) in items.iter().enumerate() { @@ -57,14 +60,17 @@ fn get_naive_string_from_text(text: &tui::text::Text<'_>) -> String { } #[cfg(test)] -fn get_example_tree_items() -> Vec> { +fn get_example_tree_items() -> Vec> { vec![ TreeItem::new_leaf("a"), TreeItem::new( "b", vec![ TreeItem::new_leaf("c"), - TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]), + TreeItem::new( + "d", + vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")], + ), TreeItem::new_leaf("g"), ], ), @@ -78,7 +84,7 @@ fn get_opened_nothing_opened_is_top_level() { let result = flatten(&[], &items); let result_text = result .iter() - .map(|o| get_naive_string_from_text(&o.item.text)) + .map(|o| o.item.elem) .collect::>(); assert_eq!(result_text, ["a", "b", "h"]); } @@ -90,7 +96,7 @@ fn get_opened_wrong_opened_is_only_top_level() { let result = flatten(&opened, &items); let result_text = result .iter() - .map(|o| get_naive_string_from_text(&o.item.text)) + .map(|o| o.item.elem) .collect::>(); assert_eq!(result_text, ["a", "b", "h"]); } @@ -102,7 +108,7 @@ fn get_opened_one_is_opened() { let result = flatten(&opened, &items); let result_text = result .iter() - .map(|o| get_naive_string_from_text(&o.item.text)) + .map(|o| o.item.elem) .collect::>(); assert_eq!(result_text, ["a", "b", "c", "d", "g", "h"]); } @@ -114,7 +120,7 @@ fn get_opened_all_opened() { let result = flatten(&opened, &items); let result_text = result .iter() - .map(|o| get_naive_string_from_text(&o.item.text)) - .collect::>(); + .map(|o| o.item.elem) + .collect::>(); assert_eq!(result_text, ["a", "b", "c", "d", "e", "f", "g", "h"]); } diff --git a/src/lib.rs b/src/lib.rs index 5c514a7..9e6de28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ impl TreeState { } /// Select the last node. - pub fn select_last(&mut self, items: &[TreeItem]) { + pub fn select_last(&mut self, items: &[TreeItem]) { let visible = flatten(&self.get_all_opened(), items); let new_identifier = visible .last() @@ -113,7 +113,7 @@ impl TreeState { /// Handles the up arrow key. /// Moves up in the current depth or to its parent. - pub fn key_up(&mut self, items: &[TreeItem]) { + pub fn key_up(&mut self, items: &[TreeItem]) { let visible = flatten(&self.get_all_opened(), items); let current_identifier = self.selected(); let current_index = visible @@ -128,7 +128,7 @@ impl TreeState { /// Handles the down arrow key. /// Moves down in the current depth or into a child node. - pub fn key_down(&mut self, items: &[TreeItem]) { + pub fn key_down(&mut self, items: &[TreeItem]) { let visible = flatten(&self.get_all_opened(), items); let current_identifier = self.selected(); let current_index = visible @@ -170,41 +170,47 @@ impl TreeState { /// let b = TreeItem::new("root", vec![a]); /// ``` #[derive(Debug, Clone)] -pub struct TreeItem<'a> { - text: Text<'a>, +pub struct TreeItem { + elem: A, // TODO: text as fn of A? style: Style, - children: Vec>, + children: Vec>, } -impl<'a> TreeItem<'a> { - pub fn new_leaf(text: T) -> Self - where - T: Into>, - { +pub trait TreeItemRender { + fn as_text(&self) -> Text; +} + +impl TreeItemRender for &str { + fn as_text(&self) -> Text { + (*self).into() + } +} + +impl TreeItem { + pub fn new_leaf(elem: A) -> Self { Self { - text: text.into(), style: Style::default(), children: Vec::new(), + elem, } } - pub fn new(text: T, children: Children) -> Self + pub fn new(elem: A, children: Children) -> Self where - T: Into>, - Children: Into>>, + Children: Into>>, { Self { - text: text.into(), style: Style::default(), children: children.into(), + elem, } } - pub fn children(&self) -> &[TreeItem] { + pub fn children(&self) -> &[TreeItem] { &self.children } - pub fn children_mut<'b>(&'b mut self) -> &'b mut [TreeItem<'a>] where 'a: 'b{ + pub fn children_mut(&mut self) -> &mut [TreeItem] { &mut self.children } @@ -217,7 +223,7 @@ impl<'a> TreeItem<'a> { } pub fn height(&self) -> usize { - self.text.height() + self.elem.as_text().height() } #[must_use] @@ -226,7 +232,7 @@ impl<'a> TreeItem<'a> { self } - pub fn add_child(&mut self, child: TreeItem<'a>) { + pub fn add_child(&mut self, child: TreeItem) { self.children.push(child); } } @@ -259,9 +265,9 @@ impl<'a> TreeItem<'a> { /// # } /// ``` #[derive(Debug, Clone)] -pub struct Tree<'a> { +pub struct Tree<'a, A> { block: Option>, - items: Vec>, + items: Vec>, /// Style used as a base style for the widget style: Style, start_corner: Corner, @@ -271,10 +277,10 @@ pub struct Tree<'a> { highlight_symbol: Option<&'a str>, } -impl<'a> Tree<'a> { +impl<'a, A> Tree<'a, A> { pub fn new(items: T) -> Self where - T: Into>>, + T: Into>>, { Self { block: None, @@ -318,7 +324,7 @@ impl<'a> Tree<'a> { } } -impl<'a> StatefulWidget for Tree<'a> { +impl<'a, A: TreeItemRender> StatefulWidget for Tree<'a, A> { type State = TreeState; #[allow(clippy::too_many_lines)] @@ -437,7 +443,7 @@ impl<'a> StatefulWidget for Tree<'a> { }; let max_element_width = area.width.saturating_sub(after_depth_x - x); - for (j, line) in item.item.text.lines.iter().enumerate() { + for (j, line) in item.item.elem.as_text().lines.iter().enumerate() { buf.set_spans(after_depth_x, y + j as u16, line, max_element_width); } if is_selected { @@ -447,7 +453,7 @@ impl<'a> StatefulWidget for Tree<'a> { } } -impl<'a> Widget for Tree<'a> { +impl<'a, A: TreeItemRender> Widget for Tree<'a, A> { fn render(self, area: Rect, buf: &mut Buffer) { let mut state = TreeState::default(); StatefulWidget::render(self, area, buf, &mut state);