From 3e23a73f6b31411431bdfaf0e08b64d7349f982d Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 14 Nov 2024 15:38:37 -0700 Subject: [PATCH] feat: Updated the Ratatui Tree Widget to allow the tree to story any data type that implements Into, so that users can also easily fetch data from within the tree based on what's selected for non-text data types. --- Cargo.toml | 14 +++++----- README.md | 7 +++-- benches/bench.rs | 62 ++++++++++++++++++++++----------------------- examples/example.rs | 27 +++++--------------- src/flatten.rs | 8 +++--- src/lib.rs | 15 ++++++----- src/tree_item.rs | 13 +++++----- src/tree_state.rs | 16 ++++++------ 8 files changed, 78 insertions(+), 84 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 603cd65..3fa1c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "tui-tree-widget" -description = "Tree Widget for ratatui" -version = "0.23.0" +name = "managarr-tree-widget" +description = "Tree Widget for Managarr" +version = "0.24.0" license = "MIT" -repository = "https://github.com/EdJoPaTo/tui-rs-tree-widget" -authors = ["EdJoPaTo "] +repository = "https://github.com/Dark-Alex-17/managarr-tree-widget" +authors = ["EdJoPaTo ", "Dark-Alex-17 "] edition = "2021" -keywords = ["tui", "terminal", "tree", "widget"] +keywords = ["tui", "terminal", "tree", "widget", "managarr"] categories = ["command-line-interface"] include = ["src/**/*", "examples/**/*", "benches/**/*", "README.md"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,7 +31,7 @@ criterion = "0.5" ratatui = "0.29" [target.'cfg(target_family = "unix")'.dev-dependencies] -pprof = { version = "0.13", features = ["criterion", "flamegraph"] } +pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] } [[bench]] name = "bench" diff --git a/README.md b/README.md index f875f1b..2f24103 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# Ratatui Tree Widget +# Managarr Tree Widget [Ratatui](https://docs.rs/ratatui) Widget built to show Tree Data structures. ![Screenshot](media/screenshot.png) -Built for the specific use case of [`mqttui`](https://github.com/EdJoPaTo/mqttui). + +## Credit +The original project for this widget is the [Ratatui Tree Widget](https://github.com/EdJoPaTo/tui-rs-tree-widget), which was purppose built for the specific use +case of [`mqttui`](https://github.com/EdJoPaTo/mqttui). diff --git a/benches/bench.rs b/benches/bench.rs index b477777..a083bb3 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -4,67 +4,67 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughpu use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::StatefulWidget; -use tui_tree_widget::{Tree, TreeItem, TreeState}; +use managarr_tree_widget::{Tree, TreeItem, TreeState}; #[must_use] -fn example_items() -> Vec> { +fn example_items() -> Vec> { vec![ - TreeItem::new_leaf("a", "Alfa"), + TreeItem::new_leaf("a", "Alfa".to_owned()), TreeItem::new( "b", - "Bravo", + "Bravo".to_owned(), vec![ - TreeItem::new_leaf("c", "Charlie"), + TreeItem::new_leaf("c", "Charlie".to_owned()), TreeItem::new( "d", - "Delta", + "Delta".to_owned(), vec![ - TreeItem::new_leaf("e", "Echo"), - TreeItem::new_leaf("f", "Foxtrot"), + TreeItem::new_leaf("e", "Echo".to_owned()), + TreeItem::new_leaf("f", "Foxtrot".to_owned()), ], ) .expect("all item identifiers are unique"), - TreeItem::new_leaf("g", "Golf"), + TreeItem::new_leaf("g", "Golf".to_owned()), ], ) .expect("all item identifiers are unique"), - TreeItem::new_leaf("h", "Hotel"), + TreeItem::new_leaf("h", "Hotel".to_owned()), TreeItem::new( "i", - "India", + "India".to_owned(), vec![ - TreeItem::new_leaf("j", "Juliett"), - TreeItem::new_leaf("k", "Kilo"), - TreeItem::new_leaf("l", "Lima"), - TreeItem::new_leaf("m", "Mike"), - TreeItem::new_leaf("n", "November"), + TreeItem::new_leaf("j", "Juliett".to_owned()), + TreeItem::new_leaf("k", "Kilo".to_owned()), + TreeItem::new_leaf("l", "Lima".to_owned()), + TreeItem::new_leaf("m", "Mike".to_owned()), + TreeItem::new_leaf("n", "November".to_owned()), ], ) .expect("all item identifiers are unique"), - TreeItem::new_leaf("o", "Oscar"), + TreeItem::new_leaf("o", "Oscar".to_owned()), TreeItem::new( "p", - "Papa", + "Papa".to_owned(), vec![ - TreeItem::new_leaf("q", "Quebec"), - TreeItem::new_leaf("r", "Romeo"), - TreeItem::new_leaf("s", "Sierra"), - TreeItem::new_leaf("t", "Tango"), - TreeItem::new_leaf("u", "Uniform"), + TreeItem::new_leaf("q", "Quebec".to_owned()), + TreeItem::new_leaf("r", "Romeo".to_owned()), + TreeItem::new_leaf("s", "Sierra".to_owned()), + TreeItem::new_leaf("t", "Tango".to_owned()), + TreeItem::new_leaf("u", "Uniform".to_owned()), TreeItem::new( "v", - "Victor", + "Victor".to_owned(), vec![ - TreeItem::new_leaf("w", "Whiskey"), - TreeItem::new_leaf("x", "Xray"), - TreeItem::new_leaf("y", "Yankee"), + TreeItem::new_leaf("w", "Whiskey".to_owned()), + TreeItem::new_leaf("x", "Xray".to_owned()), + TreeItem::new_leaf("y", "Yankee".to_owned()), ], ) .expect("all item identifiers are unique"), ], ) .expect("all item identifiers are unique"), - TreeItem::new_leaf("z", "Zulu"), + TreeItem::new_leaf("z", "Zulu".to_owned()), ] } @@ -75,14 +75,14 @@ fn init(criterion: &mut Criterion) { group.bench_function("empty", |bencher| { bencher.iter(|| { let items = vec![]; - let _: Tree = black_box(Tree::new(black_box(&items))).unwrap(); + let _ = black_box(Tree::new(black_box(&items))).unwrap(); }); }); group.bench_function("example-items", |bencher| { bencher.iter(|| { let items = example_items(); - let _: Tree<_> = black_box(Tree::new(black_box(&items))).unwrap(); + let _ = black_box(Tree::new(black_box(&items))).unwrap(); }); }); @@ -96,7 +96,7 @@ fn renders(criterion: &mut Criterion) { let buffer_size = Rect::new(0, 0, 100, 100); group.bench_function("empty", |bencher| { - let items: Vec> = vec![]; + let items: Vec> = vec![]; let tree = Tree::new(&items).unwrap(); let mut state = TreeState::default(); bencher.iter_batched( diff --git a/examples/example.rs b/examples/example.rs index 4d2a1a4..e934f20 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -7,7 +7,7 @@ use ratatui::style::{Color, Modifier, Style}; use ratatui::text::Span; use ratatui::widgets::{Block, Scrollbar, ScrollbarOrientation}; use ratatui::{crossterm, Frame, Terminal}; -use tui_tree_widget::{Tree, TreeItem, TreeState}; +use managarr_tree_widget::{Tree, TreeItem, TreeState}; #[must_use] struct App { @@ -82,24 +82,6 @@ impl App { fn draw(&mut self, frame: &mut Frame) { let area = frame.area(); - let selected = self.state.selected(); - let flatten = self.state.flatten(&self.items); - let current_selection = flatten - .iter() - .find(|i| self.state.selected() == i.identifier); - let is_selected = current_selection.is_some() - && current_selection.unwrap().item.content().to_string() == *"Echo"; - let style = if is_selected { - Style::new() - .fg(Color::Black) - .bg(Color::Cyan) - .add_modifier(Modifier::BOLD) - } else { - Style::new() - .fg(Color::Black) - .bg(Color::LightGreen) - .add_modifier(Modifier::BOLD) - }; let widget = Tree::new(&self.items) .expect("all item identifiers are unique") .block( @@ -113,7 +95,12 @@ impl App { .track_symbol(None) .end_symbol(None), )) - .highlight_style(style) + .highlight_style( + Style::new() + .fg(Color::Black) + .bg(Color::LightGreen) + .add_modifier(Modifier::BOLD), + ) .highlight_symbol(">> "); frame.render_stateful_widget(widget, area, &mut self.state); } diff --git a/src/flatten.rs b/src/flatten.rs index e623aeb..a4ce339 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -10,7 +10,8 @@ use crate::tree_item::TreeItem; #[must_use] pub struct Flattened<'a, Identifier, T> where - T: for<'b> Into> + Clone, + Identifier: Clone + PartialEq + Eq + core::hash::Hash, + T: for<'b> Into> + Clone + Default, { pub identifier: Vec, pub item: &'a TreeItem, @@ -18,7 +19,8 @@ where impl<'a, Identifier, T> Flattened<'a, Identifier, T> where - T: for<'b> Into> + Clone, + Identifier: Clone + PartialEq + Eq + core::hash::Hash, + T: for<'b> Into> + Clone + Default, { /// Zero based depth. Depth 0 means top level with 0 indentation. #[must_use] @@ -38,7 +40,7 @@ pub fn flatten<'a, Identifier, T>( ) -> Vec> where Identifier: Clone + PartialEq + Eq + core::hash::Hash, - T: for<'b> Into> + Clone, + T: for<'b> Into> + Clone + Default, { let mut result = Vec::new(); for item in items { diff --git a/src/lib.rs b/src/lib.rs index 989af33..ce8cc0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ mod tree_state; /// # Example /// /// ``` -/// # use tui_tree_widget::{Tree, TreeItem, TreeState}; +/// # use managarr_tree_widget::{Tree, TreeItem, TreeState}; /// # use ratatui::backend::TestBackend; /// # use ratatui::Terminal; /// # use ratatui::widgets::Block; @@ -41,7 +41,7 @@ mod tree_state; /// let items = vec![item]; /// /// terminal.draw(|frame| { -/// let area = frame.size(); +/// let area = frame.area(); /// /// let tree_widget = Tree::new(&items) /// .expect("all item identifiers are unique") @@ -55,7 +55,8 @@ mod tree_state; #[derive(Debug, Clone)] pub struct Tree<'a, Identifier, T> where - T: for<'b> Into> + Clone, + Identifier: Clone + PartialEq + Eq + core::hash::Hash, + T: for<'b> Into> + Clone + Default, { items: &'a [TreeItem], @@ -80,7 +81,7 @@ where impl<'a, Identifier, T> Tree<'a, Identifier, T> where Identifier: Clone + PartialEq + Eq + core::hash::Hash, - T: for<'b> Into> + Clone, + T: for<'b> Into> + Clone + Default, { /// Create a new `Tree`. /// @@ -121,7 +122,7 @@ where /// Show the scrollbar when rendering this widget. /// /// Experimental: Can change on any release without any additional notice. - /// Its there to test and experiment with whats possible with scrolling widgets. + /// It's there to test and experiment with what's possible with scrolling widgets. /// Also see pub const fn experimental_scrollbar(mut self, scrollbar: Option>) -> Self { self.scrollbar = scrollbar; @@ -171,7 +172,7 @@ fn tree_new_errors_with_duplicate_identifiers() { impl<'a, Identifier, T> StatefulWidget for Tree<'a, Identifier, T> where Identifier: Clone + PartialEq + Eq + core::hash::Hash, - T: for<'b> Into> + Clone, + T: for<'b> Into> + Clone + Default, { type State = TreeState; @@ -341,7 +342,7 @@ where impl<'a, Identifier, T> Widget for Tree<'a, Identifier, T> where Identifier: Clone + Default + Eq + core::hash::Hash, - T: for<'b> Into> + Clone, + T: for<'b> Into> + Clone + Default, { fn render(self, area: Rect, buf: &mut Buffer) { let mut state = TreeState::default(); diff --git a/src/tree_item.rs b/src/tree_item.rs index 0daa115..ed8d469 100644 --- a/src/tree_item.rs +++ b/src/tree_item.rs @@ -15,7 +15,7 @@ use ratatui::text::Text; /// /// The `text` can be different from its `identifier`. /// To repeat the filename analogy: File browsers sometimes hide file extensions. -/// The filename `main.rs` is the identifier while its shown as `main`. +/// The filename `main.rs` is the identifier while it's shown as `main`. /// Two files `main.rs` and `main.toml` can exist in the same directory and can both be displayed as `main` but their identifier is different. /// /// Just like every file in a file system can be uniquely identified with its file and directory names each [`TreeItem`] in a [`Tree`](crate::Tree) can be with these identifiers. @@ -28,7 +28,7 @@ use ratatui::text::Text; /// # Example /// /// ``` -/// # use tui_tree_widget::TreeItem; +/// # use managarr_tree_widget::TreeItem; /// let a = TreeItem::new_leaf("l", "Leaf"); /// let b = TreeItem::new("r", "Root", vec![a])?; /// # Ok::<(), std::io::Error>(()) @@ -36,7 +36,8 @@ use ratatui::text::Text; #[derive(Debug, Clone)] pub struct TreeItem where - T: for<'a> Into> + Clone, + Identifier: Clone + PartialEq + Eq + core::hash::Hash, + T: for<'a> Into> + Clone + Default, { pub(super) identifier: Identifier, pub(super) content: T, @@ -46,11 +47,11 @@ where impl TreeItem where Identifier: Clone + PartialEq + Eq + core::hash::Hash, - T: for<'a> Into> + Clone, + T: for<'a> Into> + Clone + Default, { /// Create a new `TreeItem` without children. #[must_use] - pub fn new_leaf(identifier: Identifier, content: T) -> Self { + pub const fn new_leaf(identifier: Identifier, content: T) -> Self { Self { identifier, content, @@ -107,7 +108,7 @@ where /// Get a mutable reference to a child by index. /// - /// When you choose to change the `identifier` the [`TreeState`](crate::TreeState) might not work as expected afterwards. + /// When you choose to change the `identifier` the [`TreeState`](crate::TreeState) might not work as expected afterward. #[must_use] pub fn child_mut(&mut self, index: usize) -> Option<&mut Self> { self.children.get_mut(index) diff --git a/src/tree_state.rs b/src/tree_state.rs index 2be9c3e..a02dd51 100644 --- a/src/tree_state.rs +++ b/src/tree_state.rs @@ -14,7 +14,7 @@ use crate::tree_item::TreeItem; /// # Example /// /// ``` -/// # use tui_tree_widget::TreeState; +/// # use managarr_tree_widget::TreeState; /// type Identifier = usize; /// /// let mut state = TreeState::::default(); @@ -67,7 +67,7 @@ where items: &'a [TreeItem], ) -> Vec> where - T: for<'b> Into> + Clone, + T: for<'b> Into> + Clone + Default, { flatten(&self.opened, items, &[]) } @@ -79,7 +79,7 @@ where /// Clear the selection by passing an empty identifier vector: /// /// ```rust - /// # use tui_tree_widget::TreeState; + /// # use managarr_tree_widget::TreeState; /// # let mut state = TreeState::::default(); /// state.select(Vec::new()); /// ``` @@ -195,13 +195,13 @@ where /// # Example /// /// ``` - /// # use tui_tree_widget::TreeState; + /// # use managarr_tree_widget::TreeState; /// # type Identifier = usize; /// # let mut state = TreeState::::default(); /// // Move the selection one down - /// state.select_visible_relative(|current| { + /// state.select_relative(|current| { /// // When nothing is currently selected, select index 0 - /// // Otherwise select current + 1 (without panicing) + /// // Otherwise select current + 1 (without panicking) /// current.map_or(0, |current| current.saturating_add(1)) /// }); /// ``` @@ -230,13 +230,13 @@ where /// # Example /// /// ``` - /// # use tui_tree_widget::TreeState; + /// # use managarr_tree_widget::TreeState; /// # type Identifier = usize; /// # let mut state = TreeState::::default(); /// // Move the selection one down /// state.select_relative(|current| { /// // When nothing is currently selected, select index 0 - /// // Otherwise select current + 1 (without panicing) + /// // Otherwise select current + 1 (without panicking) /// current.map_or(0, |current| current.saturating_add(1)) /// }); /// ```