feat: Changed the tree to accept any type that implements Into<Text> so I can use the tree to fully store structs in the TUI as well as listing them
This commit is contained in:
+46
-33
@@ -12,7 +12,7 @@ use tui_tree_widget::{Tree, TreeItem, TreeState};
|
||||
#[must_use]
|
||||
struct App {
|
||||
state: TreeState<&'static str>,
|
||||
items: Vec<TreeItem<'static, &'static str>>,
|
||||
items: Vec<TreeItem<&'static str, String>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -20,68 +20,86 @@ impl App {
|
||||
Self {
|
||||
state: TreeState::default(),
|
||||
items: 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()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -95,12 +113,7 @@ impl App {
|
||||
.track_symbol(None)
|
||||
.end_symbol(None),
|
||||
))
|
||||
.highlight_style(
|
||||
Style::new()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::LightGreen)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_style(style)
|
||||
.highlight_symbol(">> ");
|
||||
frame.render_stateful_widget(widget, area, &mut self.state);
|
||||
}
|
||||
|
||||
+15
-6
@@ -1,17 +1,25 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::text::Text;
|
||||
|
||||
use crate::tree_item::TreeItem;
|
||||
|
||||
/// A flattened item of all visible [`TreeItem`]s.
|
||||
///
|
||||
/// Generated via [`TreeState::flatten`](crate::TreeState::flatten).
|
||||
#[must_use]
|
||||
pub struct Flattened<'text, Identifier> {
|
||||
pub struct Flattened<'a, Identifier, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
pub identifier: Vec<Identifier>,
|
||||
pub item: &'text TreeItem<'text, Identifier>,
|
||||
pub item: &'a TreeItem<Identifier, T>,
|
||||
}
|
||||
|
||||
impl<Identifier> Flattened<'_, Identifier> {
|
||||
impl<'a, Identifier, T> Flattened<'a, Identifier, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
/// Zero based depth. Depth 0 means top level with 0 indentation.
|
||||
#[must_use]
|
||||
pub fn depth(&self) -> usize {
|
||||
@@ -23,13 +31,14 @@ impl<Identifier> Flattened<'_, Identifier> {
|
||||
///
|
||||
/// `current` starts empty: `&[]`
|
||||
#[must_use]
|
||||
pub fn flatten<'text, Identifier>(
|
||||
pub fn flatten<'a, Identifier, T>(
|
||||
open_identifiers: &HashSet<Vec<Identifier>>,
|
||||
items: &'text [TreeItem<'text, Identifier>],
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
current: &[Identifier],
|
||||
) -> Vec<Flattened<'text, Identifier>>
|
||||
) -> Vec<Flattened<'a, Identifier, T>>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
for item in items {
|
||||
|
||||
+16
-9
@@ -10,6 +10,7 @@ use std::collections::HashSet;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Block, Scrollbar, ScrollbarState, StatefulWidget, Widget};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -52,8 +53,11 @@ mod tree_state;
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tree<'a, Identifier> {
|
||||
items: &'a [TreeItem<'a, Identifier>],
|
||||
pub struct Tree<'a, Identifier, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
|
||||
block: Option<Block<'a>>,
|
||||
scrollbar: Option<Scrollbar<'a>>,
|
||||
@@ -73,16 +77,17 @@ pub struct Tree<'a, Identifier> {
|
||||
node_no_children_symbol: &'a str,
|
||||
}
|
||||
|
||||
impl<'a, Identifier> Tree<'a, Identifier>
|
||||
impl<'a, Identifier, T> Tree<'a, Identifier, T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
/// Create a new `Tree`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors when there are duplicate identifiers in the children.
|
||||
pub fn new(items: &'a [TreeItem<'a, Identifier>]) -> std::io::Result<Self> {
|
||||
pub fn new(items: &'a [TreeItem<Identifier, T>]) -> std::io::Result<Self> {
|
||||
let identifiers = items
|
||||
.iter()
|
||||
.map(|item| &item.identifier)
|
||||
@@ -160,12 +165,13 @@ fn tree_new_errors_with_duplicate_identifiers() {
|
||||
let item = TreeItem::new_leaf("same", "text");
|
||||
let another = item.clone();
|
||||
let items = [item, another];
|
||||
let _: Tree<_> = Tree::new(&items).unwrap();
|
||||
let _ = Tree::new(&items).unwrap();
|
||||
}
|
||||
|
||||
impl<Identifier> StatefulWidget for Tree<'_, Identifier>
|
||||
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,
|
||||
{
|
||||
type State = TreeState<Identifier>;
|
||||
|
||||
@@ -272,7 +278,7 @@ where
|
||||
height,
|
||||
};
|
||||
|
||||
let text = &item.text;
|
||||
let text = item.content.clone().into();
|
||||
let item_style = text.style;
|
||||
|
||||
let is_selected = state.selected == *identifier;
|
||||
@@ -299,7 +305,7 @@ where
|
||||
);
|
||||
let symbol = if item.children.is_empty() {
|
||||
self.node_no_children_symbol
|
||||
} else if state.opened.contains(identifier) {
|
||||
} else if state.opened.contains(identifier.as_slice()) {
|
||||
self.node_open_symbol
|
||||
} else {
|
||||
self.node_closed_symbol
|
||||
@@ -332,9 +338,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Identifier> Widget for Tree<'_, Identifier>
|
||||
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,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TreeState::default();
|
||||
|
||||
+25
-24
@@ -34,25 +34,26 @@ use ratatui::text::Text;
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeItem<'text, Identifier> {
|
||||
pub struct TreeItem<Identifier, T>
|
||||
where
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
{
|
||||
pub(super) identifier: Identifier,
|
||||
pub(super) text: Text<'text>,
|
||||
pub(super) content: T,
|
||||
pub(super) children: Vec<Self>,
|
||||
}
|
||||
|
||||
impl<'text, Identifier> TreeItem<'text, Identifier>
|
||||
impl<Identifier, T> TreeItem<Identifier, T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
{
|
||||
/// Create a new `TreeItem` without children.
|
||||
#[must_use]
|
||||
pub fn new_leaf<T>(identifier: Identifier, text: T) -> Self
|
||||
where
|
||||
T: Into<Text<'text>>,
|
||||
{
|
||||
pub fn new_leaf(identifier: Identifier, content: T) -> Self {
|
||||
Self {
|
||||
identifier,
|
||||
text: text.into(),
|
||||
content,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -62,10 +63,7 @@ where
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors when there are duplicate identifiers in the children.
|
||||
pub fn new<T>(identifier: Identifier, text: T, children: Vec<Self>) -> std::io::Result<Self>
|
||||
where
|
||||
T: Into<Text<'text>>,
|
||||
{
|
||||
pub fn new(identifier: Identifier, content: T, children: Vec<Self>) -> std::io::Result<Self> {
|
||||
let identifiers = children
|
||||
.iter()
|
||||
.map(|item| &item.identifier)
|
||||
@@ -79,7 +77,7 @@ where
|
||||
|
||||
Ok(Self {
|
||||
identifier,
|
||||
text: text.into(),
|
||||
content,
|
||||
children,
|
||||
})
|
||||
}
|
||||
@@ -92,8 +90,8 @@ where
|
||||
|
||||
/// Get a reference to the text.
|
||||
#[must_use]
|
||||
pub const fn text(&self) -> &Text<'text> {
|
||||
&self.text
|
||||
pub const fn content(&self) -> &T {
|
||||
&self.content
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -117,7 +115,7 @@ where
|
||||
|
||||
#[must_use]
|
||||
pub fn height(&self) -> usize {
|
||||
self.text.height()
|
||||
self.content.clone().into().height()
|
||||
}
|
||||
|
||||
/// Add a child to the `TreeItem`.
|
||||
@@ -143,28 +141,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeItem<'static, &'static str> {
|
||||
impl TreeItem<&'static str, String> {
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
pub(crate) fn example() -> Vec<Self> {
|
||||
vec![
|
||||
Self::new_leaf("a", "Alfa"),
|
||||
Self::new_leaf("a", "Alfa".to_owned()),
|
||||
Self::new(
|
||||
"b",
|
||||
"Bravo",
|
||||
"Bravo".to_owned(),
|
||||
vec![
|
||||
Self::new_leaf("c", "Charlie"),
|
||||
Self::new_leaf("c", "Charlie".to_owned()),
|
||||
Self::new(
|
||||
"d",
|
||||
"Delta",
|
||||
vec![Self::new_leaf("e", "Echo"), Self::new_leaf("f", "Foxtrot")],
|
||||
"Delta".to_owned(),
|
||||
vec![
|
||||
Self::new_leaf("e", "Echo".to_owned()),
|
||||
Self::new_leaf("f", "Foxtrot".to_owned()),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
Self::new_leaf("g", "Golf"),
|
||||
Self::new_leaf("g", "Golf".to_owned()),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
Self::new_leaf("h", "Hotel"),
|
||||
Self::new_leaf("h", "Hotel".to_owned()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::layout::{Position, Rect};
|
||||
use ratatui::text::Text;
|
||||
|
||||
use crate::flatten::{flatten, Flattened};
|
||||
use crate::tree_item::TreeItem;
|
||||
@@ -61,10 +62,13 @@ where
|
||||
|
||||
/// Get a flat list of all currently viewable (including by scrolling) [`TreeItem`]s with this `TreeState`.
|
||||
#[must_use]
|
||||
pub fn flatten<'text>(
|
||||
pub fn flatten<'a, T>(
|
||||
&self,
|
||||
items: &'text [TreeItem<'text, Identifier>],
|
||||
) -> Vec<Flattened<'text, Identifier>> {
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
) -> Vec<Flattened<'a, Identifier, T>>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
{
|
||||
flatten(&self.opened, items, &[])
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user