Skip to content

Commit

Permalink
Merge branch 'main' into tree-data-trait
Browse files Browse the repository at this point in the history
Conflicts:
	.github/workflows/rust.yml
	Cargo.toml
	benches/bench.rs
	examples/example.rs
	src/flatten.rs
	src/lib.rs
	src/tree_state.rs
  • Loading branch information
EdJoPaTo committed May 15, 2024
2 parents 4932050 + 2811a79 commit 81692d0
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 97 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ on:
env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
RUSTFLAGS: --allow unknown-lints --deny warnings

jobs:
rustfmt:
Expand Down Expand Up @@ -67,6 +66,8 @@ jobs:
- ubuntu-latest
- macos-latest
- windows-latest
env:
RUSTFLAGS: --allow unknown-lints --deny warnings
steps:
- name: Setup Rust
id: rust
Expand Down Expand Up @@ -120,7 +121,7 @@ jobs:
key: test-${{ matrix.os }}-${{ steps.rust.outputs.cachekey }}-${{ hashFiles('**/Cargo.*') }}
path: target/
- run: cargo build --offline --all-features --all-targets
- run: cargo test --offline --all-features
- run: cargo test --offline --all-features --no-fail-fast

release:
name: Release ${{ matrix.triple }}
Expand Down Expand Up @@ -149,6 +150,8 @@ jobs:
os: windows-latest
- triple: aarch64-pc-windows-msvc
os: windows-latest
env:
RUSTFLAGS: --deny warnings
steps:
- name: Setup Rust
id: rust
Expand Down
117 changes: 57 additions & 60 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,82 +9,79 @@ use ratatui::widgets::{Block, Scrollbar, ScrollbarOrientation};
use ratatui::{Frame, Terminal};
use tui_tree_widget::{Tree, TreeItem, TreeState};

fn example_data() -> Vec<TreeItem<'static, &'static str>> {
vec![
TreeItem::new_leaf("a", "Alfa"),
TreeItem::new(
"b",
"Bravo",
vec![
TreeItem::new_leaf("c", "Charlie"),
struct App {
state: TreeState<&'static str>,
items: Vec<TreeItem<'static, &'static str>>,
}

impl App {
fn new() -> Self {
Self {
state: TreeState::default(),
items: vec![
TreeItem::new_leaf("a", "Alfa"),
TreeItem::new(
"d",
"Delta",
"b",
"Bravo",
vec![
TreeItem::new_leaf("e", "Echo"),
TreeItem::new_leaf("f", "Foxtrot"),
TreeItem::new_leaf("c", "Charlie"),
TreeItem::new(
"d",
"Delta",
vec![
TreeItem::new_leaf("e", "Echo"),
TreeItem::new_leaf("f", "Foxtrot"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("g", "Golf"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("g", "Golf"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("h", "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"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("o", "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("h", "Hotel"),
TreeItem::new(
"v",
"Victor",
"i",
"India",
vec![
TreeItem::new_leaf("w", "Whiskey"),
TreeItem::new_leaf("x", "Xray"),
TreeItem::new_leaf("y", "Yankee"),
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"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("o", "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(
"v",
"Victor",
vec![
TreeItem::new_leaf("w", "Whiskey"),
TreeItem::new_leaf("x", "Xray"),
TreeItem::new_leaf("y", "Yankee"),
],
)
.expect("all item identifiers are unique"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("z", "Zulu"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("z", "Zulu"),
]
}

struct App {
state: TreeState<&'static str>,
}

impl App {
fn new() -> Self {
Self {
state: TreeState::default(),
}
}

fn draw(&mut self, frame: &mut Frame) {
let area = frame.size();
let data = example_data();
let widget = Tree::new(&data)
let widget = Tree::new(&self.items)
.block(
Block::bordered()
.title("Tree Widget")
Expand Down
87 changes: 87 additions & 0 deletions src/tree_item/flatten.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::collections::HashSet;

use crate::{Node, TreeItem};

/// Recursive implementation of [`TreeData::get_nodes`](crate::TreeData::get_nodes) for [`TreeItem`]s.
#[must_use]
pub fn flatten<Identifier>(
open_identifiers: &HashSet<Vec<Identifier>>,
items: &[TreeItem<'_, Identifier>],
current: &[Identifier],
) -> Vec<Node<Identifier>>
where
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
{
let mut result = Vec::new();
for item in items {
let mut child_identifier = current.to_vec();
child_identifier.push(item.identifier.clone());

let child_result = open_identifiers
.contains(&child_identifier)
.then(|| flatten(open_identifiers, &item.children, &child_identifier));

result.push(Node {
identifier: child_identifier,
has_children: !item.children.is_empty(),
height: item.text.height(),
});

if let Some(mut child_result) = child_result {
result.append(&mut child_result);
}
}
result
}

#[test]
fn depth_works() {
let mut opened = HashSet::new();
opened.insert(vec!["b"]);
opened.insert(vec!["b", "d"]);
let depths = flatten(&opened, &TreeItem::example(), &[])
.into_iter()
.map(|flattened| flattened.depth())
.collect::<Vec<_>>();
assert_eq!(depths, [0, 0, 1, 1, 2, 2, 1, 0]);
}

#[cfg(test)]
fn flatten_works(opened: &HashSet<Vec<&'static str>>, expected: &[&str]) {
let items = TreeItem::example();
let result = flatten(opened, &items, &[]);
let actual = result
.into_iter()
.map(|flattened| flattened.identifier.into_iter().last().unwrap())
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}

#[test]
fn get_opened_nothing_opened_is_top_level() {
let opened = HashSet::new();
flatten_works(&opened, &["a", "b", "h"]);
}

#[test]
fn get_opened_wrong_opened_is_only_top_level() {
let mut opened = HashSet::new();
opened.insert(vec!["a"]);
opened.insert(vec!["b", "d"]);
flatten_works(&opened, &["a", "b", "h"]);
}

#[test]
fn get_opened_one_is_opened() {
let mut opened = HashSet::new();
opened.insert(vec!["b"]);
flatten_works(&opened, &["a", "b", "c", "d", "g", "h"]);
}

#[test]
fn get_opened_all_opened() {
let mut opened = HashSet::new();
opened.insert(vec!["b"]);
opened.insert(vec!["b", "d"]);
flatten_works(&opened, &["a", "b", "c", "d", "e", "f", "g", "h"]);
}
34 changes: 3 additions & 31 deletions src/tree_item.rs → src/tree_item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use ratatui::text::Text;

use crate::{Node, TreeData};

mod flatten;

/// One item inside a [`Tree`](crate::Tree).
///
/// Can have zero or more `children`.
Expand Down Expand Up @@ -210,7 +212,7 @@ where
&self,
open_identifiers: &HashSet<Vec<Self::Identifier>>,
) -> Vec<Node<Self::Identifier>> {
flatten(open_identifiers, self, &[])
flatten::flatten(open_identifiers, self, &[])
}

fn render(
Expand All @@ -225,33 +227,3 @@ where
ratatui::widgets::Widget::render(&item.text, area, buffer);
}
}

fn flatten<Identifier>(
open_identifiers: &HashSet<Vec<Identifier>>,
items: &[TreeItem<'_, Identifier>],
current_identifier: &[Identifier],
) -> Vec<Node<Identifier>>
where
Identifier: Clone + PartialEq + Eq + core::hash::Hash,
{
let mut result = Vec::new();
for item in items {
let mut child_identifier = current_identifier.to_vec();
child_identifier.push(item.identifier.clone());

let child_result = open_identifiers
.contains(&child_identifier)
.then(|| flatten(open_identifiers, &item.children, &child_identifier));

result.push(Node {
identifier: child_identifier,
has_children: !item.children.is_empty(),
height: item.text.height(),
});

if let Some(mut child_result) = child_result {
result.append(&mut child_result);
}
}
result
}
29 changes: 25 additions & 4 deletions src/tree_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ where
}

#[must_use]
#[deprecated = "use self.get_selected"]
pub fn selected(&self) -> Vec<Identifier> {
self.selected.clone()
}

#[must_use]
pub fn get_selected(&self) -> &[Identifier] {
&self.selected
}

/// Selects the given identifier.
///
/// Returns `true` when the selection changed.
Expand Down Expand Up @@ -104,8 +110,19 @@ where
/// Returns `true` when a node is opened / closed.
/// As toggle always changes something, this only returns `false` when nothing is selected.
pub fn toggle_selected(&mut self) -> bool {
if self.selected.is_empty() {
return false;
}

self.ensure_selected_in_view_on_next_render = true;
self.toggle(self.selected())

// Reimplement self.close because of multiple different borrows
let was_open = self.open.remove(&self.selected);
if was_open {
return true;
}

self.open(self.selected.clone())
}

/// Closes all open nodes.
Expand Down Expand Up @@ -259,9 +276,13 @@ where
/// Opens the currently selected.
///
/// Returns `true` when it was closed and has been opened.
/// Returns `false` when it was already open.
/// Returns `false` when it was already open or nothing being selected.
pub fn key_right(&mut self) -> bool {
self.ensure_selected_in_view_on_next_render = true;
self.open(self.selected())
if self.selected.is_empty() {
false
} else {
self.ensure_selected_in_view_on_next_render = true;
self.open(self.selected.clone())
}
}
}

0 comments on commit 81692d0

Please sign in to comment.