Compare commits
10 Commits
735f111866
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f289d1075 | |||
| 45523fa08e | |||
|
|
6a324736e5 | ||
| b0d8d9f0bf | |||
| 959b60de32 | |||
| 6baef469c8 | |||
| 6c10db760d | |||
| 8a91f662dd | |||
| 37d7b77f90 | |||
| 3e23a73f6b |
+13
-10
@@ -1,15 +1,18 @@
|
||||
[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.25.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"]
|
||||
rust-version = "1.82.0"
|
||||
include = ["src/**/*", "examples/**/*", "benches/**/*", "README.md"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
@@ -23,15 +26,15 @@ debug = true
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
ratatui = { version = "0.29", default-features = false }
|
||||
ratatui = { version = "0.30", default-features = false }
|
||||
unicode-width = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
ratatui = "0.29"
|
||||
ratatui = "0.30"
|
||||
|
||||
[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,83 @@
|
||||
# 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).
|
||||
## Installation
|
||||
Add this widget to your project using the following command:
|
||||
|
||||
```shell
|
||||
cargo add managarr-tree-widget
|
||||
```
|
||||
|
||||
## Running the example
|
||||
To run the example widget, simply run:
|
||||
|
||||
```shell
|
||||
cargo run --example example
|
||||
```
|
||||
|
||||
## Usage
|
||||
The following is an example of how to create a tree of strings (namely one like the one used in the [example](./examples/example.rs)):
|
||||
|
||||
```rust
|
||||
fn draw(&mut self, frame: &mut Frame) {
|
||||
let area = frame.area();
|
||||
let tree_items = vec![
|
||||
TreeItem::new_leaf("Alfa"),
|
||||
TreeItem::new(
|
||||
"Bravo",
|
||||
vec![
|
||||
TreeItem::new_leaf("Charlie"),
|
||||
TreeItem::new(
|
||||
"Delta",
|
||||
vec![TreeItem::new_leaf("Echo"), TreeItem::new_leaf("Foxtrot")],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("Golf"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("Hotel"),
|
||||
];
|
||||
let widget = Tree::new(&tree_items)
|
||||
.expect("all item identifiers are unique")
|
||||
.block(
|
||||
Block::bordered()
|
||||
.title("Tree Widget"),
|
||||
)
|
||||
.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);
|
||||
}
|
||||
```
|
||||
|
||||
This will generate the following tree structure:
|
||||
|
||||
```
|
||||
┌── Alfa
|
||||
├── Bravo
|
||||
│ ├── Charlie
|
||||
│ ├── Delta
|
||||
│ │ ├── Echo
|
||||
│ │ └── Foxtrot
|
||||
│ └── Golf
|
||||
└── Hotel
|
||||
```
|
||||
|
||||
This example assumes the existence of a `self.state` field that is initialized with `TreeState::default()`. The `TreeItem` struct is used to create a tree of items, and the `Tree` struct is used to create the widget itself.
|
||||
|
||||
A more detailed and feature-complete example is available in the [example](./examples/example.rs) file.
|
||||
|
||||
## Credit
|
||||
The original project for this widget is the [Ratatui Tree Widget](https://github.com/EdJoPaTo/tui-rs-tree-widget), which was purpose built for the specific use
|
||||
case of [`mqttui`](https://github.com/EdJoPaTo/mqttui).
|
||||
|
||||
The updated version of the tree widget that allows more generic types is created by me, [Alex Clarke](https://github.com/Dark-Alex-17).
|
||||
|
||||
|
||||
+28
-36
@@ -1,70 +1,62 @@
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput};
|
||||
use managarr_tree_widget::{Tree, TreeItem, TreeState};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::StatefulWidget;
|
||||
use tui_tree_widget::{Tree, TreeItem, TreeState};
|
||||
|
||||
#[must_use]
|
||||
fn example_items() -> Vec<TreeItem<'static, &'static str>> {
|
||||
fn example_items() -> Vec<TreeItem<&'static str>> {
|
||||
vec![
|
||||
TreeItem::new_leaf("a", "Alfa"),
|
||||
TreeItem::new_leaf("Alfa"),
|
||||
TreeItem::new(
|
||||
"b",
|
||||
"Bravo",
|
||||
vec![
|
||||
TreeItem::new_leaf("c", "Charlie"),
|
||||
TreeItem::new_leaf("Charlie"),
|
||||
TreeItem::new(
|
||||
"d",
|
||||
"Delta",
|
||||
vec![
|
||||
TreeItem::new_leaf("e", "Echo"),
|
||||
TreeItem::new_leaf("f", "Foxtrot"),
|
||||
],
|
||||
vec![TreeItem::new_leaf("Echo"), TreeItem::new_leaf("Foxtrot")],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("g", "Golf"),
|
||||
TreeItem::new_leaf("Golf"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("h", "Hotel"),
|
||||
TreeItem::new_leaf("Hotel"),
|
||||
TreeItem::new(
|
||||
"i",
|
||||
"India",
|
||||
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("Juliet"),
|
||||
TreeItem::new_leaf("Kilo"),
|
||||
TreeItem::new_leaf("Lima"),
|
||||
TreeItem::new_leaf("Mike"),
|
||||
TreeItem::new_leaf("November"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("o", "Oscar"),
|
||||
TreeItem::new_leaf("Oscar"),
|
||||
TreeItem::new(
|
||||
"p",
|
||||
"Papa",
|
||||
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("Quebec"),
|
||||
TreeItem::new_leaf("Romeo"),
|
||||
TreeItem::new_leaf("Sierra"),
|
||||
TreeItem::new_leaf("Tango"),
|
||||
TreeItem::new_leaf("Uniform"),
|
||||
TreeItem::new(
|
||||
"v",
|
||||
"Victor",
|
||||
vec![
|
||||
TreeItem::new_leaf("w", "Whiskey"),
|
||||
TreeItem::new_leaf("x", "Xray"),
|
||||
TreeItem::new_leaf("y", "Yankee"),
|
||||
TreeItem::new_leaf("Whiskey"),
|
||||
TreeItem::new_leaf("Xray"),
|
||||
TreeItem::new_leaf("Yankee"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("z", "Zulu"),
|
||||
TreeItem::new_leaf("Zulu"),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -74,15 +66,15 @@ 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 items = Vec::<TreeItem<String>>::new();
|
||||
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 +88,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<String>> = vec![];
|
||||
let tree = Tree::new(&items).unwrap();
|
||||
let mut state = TreeState::default();
|
||||
bencher.iter_batched(
|
||||
@@ -112,8 +104,8 @@ fn renders(criterion: &mut Criterion) {
|
||||
let items = example_items();
|
||||
let tree = Tree::new(&items).unwrap();
|
||||
let mut state = TreeState::default();
|
||||
state.open(vec!["b"]);
|
||||
state.open(vec!["b", "d"]);
|
||||
state.open(vec![2]);
|
||||
state.open(vec![2, 4]);
|
||||
bencher.iter_batched(
|
||||
|| (tree.clone(), Buffer::empty(buffer_size)),
|
||||
|(tree, mut buffer)| {
|
||||
|
||||
+40
-58
@@ -1,18 +1,18 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use managarr_tree_widget::{Tree, TreeItem, TreeState};
|
||||
use ratatui::backend::{Backend, CrosstermBackend};
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers, MouseEventKind};
|
||||
use ratatui::layout::{Position, Rect};
|
||||
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 ratatui::{Frame, Terminal, crossterm};
|
||||
|
||||
#[must_use]
|
||||
struct App {
|
||||
state: TreeState<&'static str>,
|
||||
items: Vec<TreeItem<&'static str, String>>,
|
||||
state: TreeState,
|
||||
items: Vec<TreeItem<&'static str>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -20,86 +20,60 @@ impl App {
|
||||
Self {
|
||||
state: TreeState::default(),
|
||||
items: vec![
|
||||
TreeItem::new_leaf("a", "Alfa".to_owned()),
|
||||
TreeItem::new_leaf("Alfa"),
|
||||
TreeItem::new(
|
||||
"b",
|
||||
"Bravo".to_owned(),
|
||||
"Bravo",
|
||||
vec![
|
||||
TreeItem::new_leaf("c", "Charlie".to_owned()),
|
||||
TreeItem::new_leaf("Charlie"),
|
||||
TreeItem::new(
|
||||
"d",
|
||||
"Delta".to_owned(),
|
||||
vec![
|
||||
TreeItem::new_leaf("e", "Echo".to_owned()),
|
||||
TreeItem::new_leaf("f", "Foxtrot".to_owned()),
|
||||
],
|
||||
"Delta",
|
||||
vec![TreeItem::new_leaf("Echo"), TreeItem::new_leaf("Foxtrot")],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("g", "Golf".to_owned()),
|
||||
TreeItem::new_leaf("Golf"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("h", "Hotel".to_owned()),
|
||||
TreeItem::new_leaf("Hotel"),
|
||||
TreeItem::new(
|
||||
"i",
|
||||
"India".to_owned(),
|
||||
"India",
|
||||
vec![
|
||||
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()),
|
||||
TreeItem::new_leaf("Juliet"),
|
||||
TreeItem::new_leaf("Kilo"),
|
||||
TreeItem::new_leaf("Lima"),
|
||||
TreeItem::new_leaf("Mike"),
|
||||
TreeItem::new_leaf("November"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("o", "Oscar".to_owned()),
|
||||
TreeItem::new_leaf("Oscar"),
|
||||
TreeItem::new(
|
||||
"p",
|
||||
"Papa".to_owned(),
|
||||
"Papa",
|
||||
vec![
|
||||
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_leaf("Quebec"),
|
||||
TreeItem::new_leaf("Romeo"),
|
||||
TreeItem::new_leaf("Sierra"),
|
||||
TreeItem::new_leaf("Tango"),
|
||||
TreeItem::new_leaf("Uniform"),
|
||||
TreeItem::new(
|
||||
"v",
|
||||
"Victor".to_owned(),
|
||||
"Victor",
|
||||
vec![
|
||||
TreeItem::new_leaf("w", "Whiskey".to_owned()),
|
||||
TreeItem::new_leaf("x", "Xray".to_owned()),
|
||||
TreeItem::new_leaf("y", "Yankee".to_owned()),
|
||||
TreeItem::new_leaf("Whiskey"),
|
||||
TreeItem::new_leaf("Xray"),
|
||||
TreeItem::new_leaf("Yankee"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
TreeItem::new_leaf("z", "Zulu".to_owned()),
|
||||
TreeItem::new_leaf("Zulu"),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
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 +87,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);
|
||||
}
|
||||
@@ -150,7 +129,10 @@ fn main() -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> std::io::Result<()> {
|
||||
fn run_app<B: Backend<Error = std::io::Error>>(
|
||||
terminal: &mut Terminal<B>,
|
||||
mut app: App,
|
||||
) -> std::io::Result<()> {
|
||||
const DEBOUNCE: Duration = Duration::from_millis(20); // 50 FPS
|
||||
|
||||
let before = Instant::now();
|
||||
@@ -165,7 +147,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> std::io::Res
|
||||
let update = match crossterm::event::read()? {
|
||||
Event::Key(key) => match key.code {
|
||||
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
KeyCode::Char('q') => return Ok(()),
|
||||
KeyCode::Char('\n' | ' ') => app.state.toggle_selected(),
|
||||
|
||||
+81
-30
@@ -1,24 +1,24 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::text::Text;
|
||||
|
||||
use crate::tree_item::TreeItem;
|
||||
use ratatui::text::ToText;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A flattened item of all visible [`TreeItem`]s.
|
||||
///
|
||||
/// Generated via [`TreeState::flatten`](crate::TreeState::flatten).
|
||||
#[must_use]
|
||||
pub struct Flattened<'a, Identifier, T>
|
||||
pub struct Flattened<'a, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
pub identifier: Vec<Identifier>,
|
||||
pub item: &'a TreeItem<Identifier, T>,
|
||||
pub identifier: Vec<u64>,
|
||||
pub item: &'a TreeItem<T>,
|
||||
}
|
||||
|
||||
impl<'a, Identifier, T> Flattened<'a, Identifier, T>
|
||||
impl<'a, T> Flattened<'a, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
/// Zero based depth. Depth 0 means top level with 0 indentation.
|
||||
#[must_use]
|
||||
@@ -31,19 +31,18 @@ where
|
||||
///
|
||||
/// `current` starts empty: `&[]`
|
||||
#[must_use]
|
||||
pub fn flatten<'a, Identifier, T>(
|
||||
open_identifiers: &HashSet<Vec<Identifier>>,
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
current: &[Identifier],
|
||||
) -> Vec<Flattened<'a, Identifier, T>>
|
||||
pub fn flatten<'a, T>(
|
||||
open_identifiers: &HashSet<Vec<u64>>,
|
||||
items: &'a [TreeItem<T>],
|
||||
current: &[u64],
|
||||
) -> Vec<Flattened<'a, T>>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
for item in items {
|
||||
let mut child_identifier = current.to_vec();
|
||||
child_identifier.push(item.identifier.clone());
|
||||
child_identifier.push(item.identifier);
|
||||
|
||||
let child_result = open_identifiers
|
||||
.contains(&child_identifier)
|
||||
@@ -63,9 +62,15 @@ where
|
||||
|
||||
#[test]
|
||||
fn depth_works() {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let mut open = HashSet::new();
|
||||
open.insert(vec!["b"]);
|
||||
open.insert(vec!["b", "d"]);
|
||||
let hash = |s: &str| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
open.insert(vec![hash("Bravo")]);
|
||||
open.insert(vec![hash("Bravo"), hash("Delta")]);
|
||||
let depths = flatten(&open, &TreeItem::example(), &[])
|
||||
.into_iter()
|
||||
.map(|flattened| flattened.depth())
|
||||
@@ -74,7 +79,7 @@ fn depth_works() {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn flatten_works(open: &HashSet<Vec<&'static str>>, expected: &[&str]) {
|
||||
fn flatten_works(open: &HashSet<Vec<u64>>, expected: &[u64]) {
|
||||
let items = TreeItem::example();
|
||||
let result = flatten(open, &items, &[]);
|
||||
let actual = result
|
||||
@@ -86,29 +91,75 @@ fn flatten_works(open: &HashSet<Vec<&'static str>>, expected: &[&str]) {
|
||||
|
||||
#[test]
|
||||
fn flatten_nothing_open_is_top_level() {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let open = HashSet::new();
|
||||
flatten_works(&open, &["a", "b", "h"]);
|
||||
let hash = |s: &str| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
flatten_works(&open, &[hash("Alfa"), hash("Bravo"), hash("Hotel")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_wrong_open_is_only_top_level() {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let mut open = HashSet::new();
|
||||
open.insert(vec!["a"]);
|
||||
open.insert(vec!["b", "d"]);
|
||||
flatten_works(&open, &["a", "b", "h"]);
|
||||
let hash = |s: &str| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
open.insert(vec![hash("Alfa")]);
|
||||
open.insert(vec![hash("Bravo"), hash("Delta")]);
|
||||
flatten_works(&open, &[hash("Alfa"), hash("Bravo"), hash("Hotel")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_one_is_open() {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let mut open = HashSet::new();
|
||||
open.insert(vec!["b"]);
|
||||
flatten_works(&open, &["a", "b", "c", "d", "g", "h"]);
|
||||
let hash = |s: &str| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
open.insert(vec![hash("Bravo")]);
|
||||
flatten_works(
|
||||
&open,
|
||||
&[
|
||||
hash("Alfa"),
|
||||
hash("Bravo"),
|
||||
hash("Charlie"),
|
||||
hash("Delta"),
|
||||
hash("Golf"),
|
||||
hash("Hotel"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten_all_open() {
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let mut open = HashSet::new();
|
||||
open.insert(vec!["b"]);
|
||||
open.insert(vec!["b", "d"]);
|
||||
flatten_works(&open, &["a", "b", "c", "d", "e", "f", "g", "h"]);
|
||||
let hash = |s: &str| {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
open.insert(vec![hash("Bravo")]);
|
||||
open.insert(vec![hash("Bravo"), hash("Delta")]);
|
||||
flatten_works(
|
||||
&open,
|
||||
&[
|
||||
hash("Alfa"),
|
||||
hash("Bravo"),
|
||||
hash("Charlie"),
|
||||
hash("Delta"),
|
||||
hash("Echo"),
|
||||
hash("Foxtrot"),
|
||||
hash("Golf"),
|
||||
hash("Hotel"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
+34
-32
@@ -5,13 +5,14 @@ Tree widget [`Tree`] is generated with [`TreeItem`]s (which itself can contain [
|
||||
The user interaction state (like the current selection) is stored in the [`TreeState`].
|
||||
*/
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::text::ToText;
|
||||
use ratatui::widgets::{Block, Scrollbar, ScrollbarState, StatefulWidget, Widget};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub use crate::flatten::Flattened;
|
||||
@@ -24,40 +25,36 @@ mod tree_state;
|
||||
|
||||
/// A `Tree` which can be rendered.
|
||||
///
|
||||
/// The generic argument `Identifier` is used to keep the state like the currently selected or opened [`TreeItem`]s in the [`TreeState`].
|
||||
/// For more information see [`TreeItem`].
|
||||
///
|
||||
/// # 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;
|
||||
/// # let mut terminal = Terminal::new(TestBackend::new(32, 32)).unwrap();
|
||||
/// let mut state = TreeState::default();
|
||||
///
|
||||
/// let item = TreeItem::new_leaf("l", "leaf");
|
||||
/// let item = TreeItem::new_leaf("leaf");
|
||||
/// 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")
|
||||
/// .block(Block::bordered().title("Tree Widget"));
|
||||
///
|
||||
/// frame.render_stateful_widget(tree_widget, area, &mut state);
|
||||
/// })?;
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// }).unwrap();
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tree<'a, Identifier, T>
|
||||
pub struct Tree<'a, T>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
items: &'a [TreeItem<T>],
|
||||
|
||||
block: Option<Block<'a>>,
|
||||
scrollbar: Option<Scrollbar<'a>>,
|
||||
@@ -77,17 +74,16 @@ where
|
||||
node_no_children_symbol: &'a str,
|
||||
}
|
||||
|
||||
impl<'a, Identifier, T> Tree<'a, Identifier, T>
|
||||
impl<'a, T> Tree<'a, T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
/// Create a new `Tree`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors when there are duplicate identifiers in the children.
|
||||
pub fn new(items: &'a [TreeItem<Identifier, T>]) -> std::io::Result<Self> {
|
||||
pub fn new(items: &'a [TreeItem<T>]) -> std::io::Result<Self> {
|
||||
let identifiers = items
|
||||
.iter()
|
||||
.map(|item| &item.identifier)
|
||||
@@ -121,7 +117,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;
|
||||
@@ -162,18 +158,17 @@ where
|
||||
#[test]
|
||||
#[should_panic = "duplicate identifiers"]
|
||||
fn tree_new_errors_with_duplicate_identifiers() {
|
||||
let item = TreeItem::new_leaf("same", "text");
|
||||
let item = TreeItem::new_leaf("text".to_owned());
|
||||
let another = item.clone();
|
||||
let items = [item, another];
|
||||
let _ = Tree::new(&items).unwrap();
|
||||
}
|
||||
|
||||
impl<'a, Identifier, T> StatefulWidget for Tree<'a, Identifier, T>
|
||||
impl<'a, T> StatefulWidget for Tree<'a, T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
type State = TreeState<Identifier>;
|
||||
type State = TreeState;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn render(self, full_area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
@@ -278,7 +273,7 @@ where
|
||||
height,
|
||||
};
|
||||
|
||||
let text = item.content.clone().into();
|
||||
let text = item.content.to_text();
|
||||
let item_style = text.style;
|
||||
|
||||
let is_selected = state.selected == *identifier;
|
||||
@@ -338,10 +333,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Identifier, T> Widget for Tree<'a, Identifier, T>
|
||||
impl<'a, T> Widget for Tree<'a, T>
|
||||
where
|
||||
Identifier: Clone + Default + Eq + core::hash::Hash,
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut state = TreeState::default();
|
||||
@@ -352,10 +346,11 @@ where
|
||||
#[cfg(test)]
|
||||
mod render_tests {
|
||||
use super::*;
|
||||
use std::hash::{DefaultHasher, Hasher};
|
||||
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
fn render(width: u16, height: u16, state: &mut TreeState<&'static str>) -> Buffer {
|
||||
fn render(width: u16, height: u16, state: &mut TreeState) -> Buffer {
|
||||
let items = TreeItem::example();
|
||||
let tree = Tree::new(&items).unwrap();
|
||||
let area = Rect::new(0, 0, width, height);
|
||||
@@ -388,7 +383,9 @@ mod render_tests {
|
||||
#[test]
|
||||
fn depth_one() {
|
||||
let mut state = TreeState::default();
|
||||
state.open(vec!["b"]);
|
||||
let mut hasher = DefaultHasher::new();
|
||||
"Bravo".hash(&mut hasher);
|
||||
state.open(vec![hasher.finish()]);
|
||||
let buffer = render(13, 7, &mut state);
|
||||
let expected = Buffer::with_lines([
|
||||
" Alfa ",
|
||||
@@ -405,8 +402,13 @@ mod render_tests {
|
||||
#[test]
|
||||
fn depth_two() {
|
||||
let mut state = TreeState::default();
|
||||
state.open(vec!["b"]);
|
||||
state.open(vec!["b", "d"]);
|
||||
let mut hasher = DefaultHasher::new();
|
||||
"Bravo".hash(&mut hasher);
|
||||
let bravo_hash = hasher.finish();
|
||||
let mut hasher = DefaultHasher::new();
|
||||
"Delta".hash(&mut hasher);
|
||||
state.open(vec![bravo_hash]);
|
||||
state.open(vec![bravo_hash, hasher.finish()]);
|
||||
let buffer = render(15, 9, &mut state);
|
||||
let expected = Buffer::with_lines([
|
||||
" Alfa ",
|
||||
|
||||
+49
-48
@@ -1,21 +1,22 @@
|
||||
use ratatui::text::ToText;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::text::Text;
|
||||
use std::fmt::Display;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
/// One item inside a [`Tree`](crate::Tree).
|
||||
///
|
||||
/// Can have zero or more `children`.
|
||||
///
|
||||
/// # Identifier
|
||||
/// # identifier
|
||||
///
|
||||
/// The generic argument `Identifier` is used to keep the state like the currently selected or opened [`TreeItem`]s in the [`TreeState`](crate::TreeState).
|
||||
/// The `identifier` is used to keep the state like the currently selected or opened [`TreeItem`]s in the [`TreeState`](crate::TreeState).
|
||||
///
|
||||
/// It needs to be unique among its siblings but can be used again on parent or child [`TreeItem`]s.
|
||||
/// A common example would be a filename which has to be unique in its directory while it can exist in another.
|
||||
///
|
||||
/// 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,42 +29,31 @@ use ratatui::text::Text;
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use tui_tree_widget::TreeItem;
|
||||
/// let a = TreeItem::new_leaf("l", "Leaf");
|
||||
/// let b = TreeItem::new("r", "Root", vec![a])?;
|
||||
/// # use managarr_tree_widget::TreeItem;
|
||||
/// let a = TreeItem::new_leaf("Leaf");
|
||||
/// let b = TreeItem::new("Root", vec![a])?;
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeItem<Identifier, T>
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TreeItem<T>
|
||||
where
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
pub(super) identifier: Identifier,
|
||||
pub(super) identifier: u64,
|
||||
pub(super) content: T,
|
||||
pub(super) children: Vec<Self>,
|
||||
}
|
||||
|
||||
impl<Identifier, T> TreeItem<Identifier, T>
|
||||
impl<T> TreeItem<T>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
T: for<'a> Into<Text<'a>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
/// Create a new `TreeItem` without children.
|
||||
#[must_use]
|
||||
pub fn new_leaf(identifier: Identifier, content: T) -> Self {
|
||||
Self {
|
||||
identifier,
|
||||
content,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `TreeItem` with children.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors when there are duplicate identifiers in the children.
|
||||
pub fn new(identifier: Identifier, content: T, children: Vec<Self>) -> std::io::Result<Self> {
|
||||
pub fn new(content: T, children: Vec<Self>) -> std::io::Result<Self> {
|
||||
let identifiers = children
|
||||
.iter()
|
||||
.map(|item| &item.identifier)
|
||||
@@ -75,17 +65,33 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
content.hash(&mut hasher);
|
||||
|
||||
Ok(Self {
|
||||
identifier,
|
||||
identifier: hasher.finish(),
|
||||
content,
|
||||
children,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `TreeItem` without children.
|
||||
#[must_use]
|
||||
pub fn new_leaf(content: T) -> Self {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
content.hash(&mut hasher);
|
||||
|
||||
Self {
|
||||
identifier: hasher.finish(),
|
||||
content,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the identifier.
|
||||
#[must_use]
|
||||
pub const fn identifier(&self) -> &Identifier {
|
||||
&self.identifier
|
||||
pub const fn identifier(&self) -> u64 {
|
||||
self.identifier
|
||||
}
|
||||
|
||||
/// Get a reference to the text.
|
||||
@@ -107,7 +113,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)
|
||||
@@ -115,7 +121,7 @@ where
|
||||
|
||||
#[must_use]
|
||||
pub fn height(&self) -> usize {
|
||||
self.content.clone().into().height()
|
||||
self.content.clone().to_text().height()
|
||||
}
|
||||
|
||||
/// Add a child to the `TreeItem`.
|
||||
@@ -141,31 +147,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeItem<&'static str, String> {
|
||||
impl TreeItem<&'static str> {
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
pub(crate) fn example() -> Vec<Self> {
|
||||
vec![
|
||||
Self::new_leaf("a", "Alfa".to_owned()),
|
||||
Self::new_leaf("Alfa"),
|
||||
Self::new(
|
||||
"b",
|
||||
"Bravo".to_owned(),
|
||||
"Bravo",
|
||||
vec![
|
||||
Self::new_leaf("c", "Charlie".to_owned()),
|
||||
Self::new_leaf("Charlie"),
|
||||
Self::new(
|
||||
"d",
|
||||
"Delta".to_owned(),
|
||||
vec![
|
||||
Self::new_leaf("e", "Echo".to_owned()),
|
||||
Self::new_leaf("f", "Foxtrot".to_owned()),
|
||||
],
|
||||
"Delta",
|
||||
vec![Self::new_leaf("Echo"), Self::new_leaf("Foxtrot")],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
Self::new_leaf("g", "Golf".to_owned()),
|
||||
Self::new_leaf("Golf"),
|
||||
],
|
||||
)
|
||||
.expect("all item identifiers are unique"),
|
||||
Self::new_leaf("h", "Hotel".to_owned()),
|
||||
Self::new_leaf("Hotel"),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -173,16 +174,16 @@ impl TreeItem<&'static str, String> {
|
||||
#[test]
|
||||
#[should_panic = "duplicate identifiers"]
|
||||
fn tree_item_new_errors_with_duplicate_identifiers() {
|
||||
let item = TreeItem::new_leaf("same", "text");
|
||||
let item = TreeItem::new_leaf("text");
|
||||
let another = item.clone();
|
||||
TreeItem::new("root", "Root", vec![item, another]).unwrap();
|
||||
TreeItem::new("Root", vec![item, another]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "identifier already exists"]
|
||||
fn tree_item_add_child_errors_with_duplicate_identifiers() {
|
||||
let item = TreeItem::new_leaf("same", "text");
|
||||
let item = TreeItem::new_leaf("text");
|
||||
let another = item.clone();
|
||||
let mut root = TreeItem::new("root", "Root", vec![item]).unwrap();
|
||||
let mut root = TreeItem::new("Root", vec![item]).unwrap();
|
||||
root.add_child(another).unwrap();
|
||||
}
|
||||
|
||||
+32
-43
@@ -1,44 +1,38 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ratatui::layout::{Position, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::text::ToText;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::flatten::{flatten, Flattened};
|
||||
use crate::flatten::{Flattened, flatten};
|
||||
use crate::tree_item::TreeItem;
|
||||
|
||||
/// Keeps the state of what is currently selected and what was opened in a [`Tree`](crate::Tree).
|
||||
///
|
||||
/// The generic argument `Identifier` is used to keep the state like the currently selected or opened [`TreeItem`]s in the [`TreeState`].
|
||||
/// For more information see [`TreeItem`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use tui_tree_widget::TreeState;
|
||||
/// type Identifier = usize;
|
||||
/// # use managarr_tree_widget::TreeState;
|
||||
///
|
||||
/// let mut state = TreeState::<Identifier>::default();
|
||||
/// let mut state = TreeState::default();
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TreeState<Identifier> {
|
||||
pub struct TreeState {
|
||||
pub(super) offset: usize,
|
||||
pub(super) opened: HashSet<Vec<Identifier>>,
|
||||
pub(super) selected: Vec<Identifier>,
|
||||
pub(super) opened: HashSet<Vec<u64>>,
|
||||
pub(super) selected: Vec<u64>,
|
||||
pub(super) ensure_selected_in_view_on_next_render: bool,
|
||||
|
||||
pub(super) last_area: Rect,
|
||||
pub(super) last_biggest_index: usize,
|
||||
/// All identifiers open on last render
|
||||
pub(super) last_identifiers: Vec<Vec<Identifier>>,
|
||||
pub(super) last_identifiers: Vec<Vec<u64>>,
|
||||
/// Identifier rendered at `y` on last render
|
||||
pub(super) last_rendered_identifiers: Vec<(u16, Vec<Identifier>)>,
|
||||
pub(super) last_rendered_identifiers: Vec<(u16, Vec<u64>)>,
|
||||
}
|
||||
|
||||
impl<Identifier> TreeState<Identifier>
|
||||
where
|
||||
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
|
||||
{
|
||||
impl TreeState {
|
||||
#[must_use]
|
||||
pub const fn get_offset(&self) -> usize {
|
||||
self.offset
|
||||
@@ -46,28 +40,25 @@ where
|
||||
|
||||
#[must_use]
|
||||
#[deprecated = "Use self.opened()"]
|
||||
pub fn get_all_opened(&self) -> Vec<Vec<Identifier>> {
|
||||
pub fn get_all_opened(&self) -> Vec<Vec<u64>> {
|
||||
self.opened.iter().cloned().collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn opened(&self) -> &HashSet<Vec<Identifier>> {
|
||||
pub const fn opened(&self) -> &HashSet<Vec<u64>> {
|
||||
&self.opened
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn selected(&self) -> &[Identifier] {
|
||||
pub fn selected(&self) -> &[u64] {
|
||||
&self.selected
|
||||
}
|
||||
|
||||
/// Get a flat list of all currently viewable (including by scrolling) [`TreeItem`]s with this `TreeState`.
|
||||
#[must_use]
|
||||
pub fn flatten<'a, T>(
|
||||
&self,
|
||||
items: &'a [TreeItem<Identifier, T>],
|
||||
) -> Vec<Flattened<'a, Identifier, T>>
|
||||
pub fn flatten<'a, T>(&self, items: &'a [TreeItem<T>]) -> Vec<Flattened<'a, T>>
|
||||
where
|
||||
T: for<'b> Into<Text<'b>> + Clone,
|
||||
T: ToText + Clone + Default + Display + Hash + PartialEq + Eq,
|
||||
{
|
||||
flatten(&self.opened, items, &[])
|
||||
}
|
||||
@@ -79,11 +70,11 @@ where
|
||||
/// Clear the selection by passing an empty identifier vector:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use tui_tree_widget::TreeState;
|
||||
/// # let mut state = TreeState::<usize>::default();
|
||||
/// # use managarr_tree_widget::TreeState;
|
||||
/// # let mut state = TreeState::default();
|
||||
/// state.select(Vec::new());
|
||||
/// ```
|
||||
pub fn select(&mut self, identifier: Vec<Identifier>) -> bool {
|
||||
pub fn select(&mut self, identifier: Vec<u64>) -> bool {
|
||||
self.ensure_selected_in_view_on_next_render = true;
|
||||
let changed = self.selected != identifier;
|
||||
self.selected = identifier;
|
||||
@@ -93,7 +84,7 @@ where
|
||||
/// Open a tree node.
|
||||
/// Returns `true` when it was closed and has been opened.
|
||||
/// Returns `false` when it was already open.
|
||||
pub fn open(&mut self, identifier: Vec<Identifier>) -> bool {
|
||||
pub fn open(&mut self, identifier: Vec<u64>) -> bool {
|
||||
if identifier.is_empty() {
|
||||
false
|
||||
} else {
|
||||
@@ -104,7 +95,7 @@ where
|
||||
/// Close a tree node.
|
||||
/// Returns `true` when it was open and has been closed.
|
||||
/// Returns `false` when it was already closed.
|
||||
pub fn close(&mut self, identifier: &[Identifier]) -> bool {
|
||||
pub fn close(&mut self, identifier: &[u64]) -> bool {
|
||||
self.opened.remove(identifier)
|
||||
}
|
||||
|
||||
@@ -113,7 +104,7 @@ where
|
||||
///
|
||||
/// Returns `true` when a node is opened / closed.
|
||||
/// As toggle always changes something, this only returns `false` when an empty identifier is given.
|
||||
pub fn toggle(&mut self, identifier: Vec<Identifier>) -> bool {
|
||||
pub fn toggle(&mut self, identifier: Vec<u64>) -> bool {
|
||||
if identifier.is_empty() {
|
||||
false
|
||||
} else if self.opened.contains(&identifier) {
|
||||
@@ -195,13 +186,12 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use tui_tree_widget::TreeState;
|
||||
/// # type Identifier = usize;
|
||||
/// # let mut state = TreeState::<Identifier>::default();
|
||||
/// # use managarr_tree_widget::TreeState;
|
||||
/// # 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 +220,12 @@ where
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use tui_tree_widget::TreeState;
|
||||
/// # type Identifier = usize;
|
||||
/// # let mut state = TreeState::<Identifier>::default();
|
||||
/// # use managarr_tree_widget::TreeState;
|
||||
/// # 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))
|
||||
/// });
|
||||
/// ```
|
||||
@@ -259,7 +248,7 @@ where
|
||||
|
||||
/// Get the identifier that was rendered for the given position on last render.
|
||||
#[must_use]
|
||||
pub fn rendered_at(&self, position: Position) -> Option<&[Identifier]> {
|
||||
pub fn rendered_at(&self, position: Position) -> Option<&[u64]> {
|
||||
if !self.last_area.contains(position) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user