feat: Updated the Ratatui Tree Widget to allow the tree to story any data type that implements Into<Text>, so that users can also easily fetch data from within the tree based on what's selected for non-text data types.
This commit is contained in:
+7
-7
@@ -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 <tui-tree-widget-rust-crate@edjopato.de>"]
|
||||
repository = "https://github.com/Dark-Alex-17/managarr-tree-widget"
|
||||
authors = ["EdJoPaTo <tui-tree-widget-rust-crate@edjopato.de>", "Dark-Alex-17 <alex.j.tusa@gmail.com>"]
|
||||
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"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# Ratatui Tree Widget
|
||||
# Managarr Tree Widget
|
||||
|
||||
[Ratatui](https://docs.rs/ratatui) Widget built to show Tree Data structures.
|
||||
|
||||

|
||||
|
||||
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).
|
||||
|
||||
+31
-31
@@ -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<TreeItem<'static, &'static str>> {
|
||||
fn example_items() -> Vec<TreeItem<&'static str, String>> {
|
||||
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<usize> = 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<TreeItem<usize>> = vec![];
|
||||
let items: Vec<TreeItem<usize, String>> = vec![];
|
||||
let tree = Tree::new(&items).unwrap();
|
||||
let mut state = TreeState::default();
|
||||
bencher.iter_batched(
|
||||
|
||||
+7
-20
@@ -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);
|
||||
}
|
||||
|
||||
+5
-3
@@ -10,7 +10,8 @@ use crate::tree_item::TreeItem;
|
||||
#[must_use]
|
||||
pub struct Flattened<'a, Identifier, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone + Default,
|
||||
{
|
||||
pub identifier: Vec<Identifier>,
|
||||
pub item: &'a TreeItem<Identifier, T>,
|
||||
@@ -18,7 +19,8 @@ where
|
||||
|
||||
impl<'a, Identifier, T> Flattened<'a, Identifier, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + 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<Flattened<'a, Identifier, T>>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: for<'b> Into<Text<'b>> + Clone + Default,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
for item in items {
|
||||
|
||||
+8
-7
@@ -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<Text<'b>> + Clone,
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone + Default,
|
||||
{
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
|
||||
@@ -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<Text<'b>> + Clone,
|
||||
T: for<'b> Into<Text<'b>> + 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 <https://github.com/ratatui-org/ratatui/issues/174>
|
||||
pub const fn experimental_scrollbar(mut self, scrollbar: Option<Scrollbar<'a>>) -> 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<Text<'b>> + Clone,
|
||||
T: for<'b> Into<Text<'b>> + Clone + Default,
|
||||
{
|
||||
type State = TreeState<Identifier>;
|
||||
|
||||
@@ -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<Text<'b>> + Clone,
|
||||
T: for<'b> Into<Text<'b>> + Clone + Default,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TreeState::default();
|
||||
|
||||
+7
-6
@@ -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<Identifier, T>
|
||||
where
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'a> Into<Text<'a>> + Clone + Default,
|
||||
{
|
||||
pub(super) identifier: Identifier,
|
||||
pub(super) content: T,
|
||||
@@ -46,11 +47,11 @@ where
|
||||
impl<Identifier, T> TreeItem<Identifier, T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
T: for<'a> Into<Text<'a>> + 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)
|
||||
|
||||
+8
-8
@@ -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::<Identifier>::default();
|
||||
@@ -67,7 +67,7 @@ where
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
) -> Vec<Flattened<'a, Identifier, T>>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: for<'b> Into<Text<'b>> + 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::<usize>::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::<Identifier>::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::<Identifier>::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))
|
||||
/// });
|
||||
/// ```
|
||||
|
||||
Reference in New Issue
Block a user