Skip to content

Commit

Permalink
make trees generic over any type that can be viewed as txt
Browse files Browse the repository at this point in the history
  • Loading branch information
inanna-malick committed Feb 26, 2023
1 parent 86bb3e5 commit 2d6481b
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 56 deletions.
16 changes: 10 additions & 6 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tui::{
use tui_tree_widget::{Tree, TreeItem};

struct App<'a> {
tree: StatefulTree<'a>,
tree: StatefulTree<&'a str>,
}

impl<'a> App<'a> {
Expand All @@ -29,15 +29,17 @@ impl<'a> App<'a> {
"b",
vec![
TreeItem::new_leaf("c"),
TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]),
TreeItem::new(
"d",
vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")],
),
TreeItem::new_leaf("g"),
],
),
TreeItem::new_leaf("h"),
]),
}
}

}

fn main() -> Result<(), Box<dyn Error>> {
Expand Down Expand Up @@ -93,10 +95,12 @@ fn run_app<'a, B: Backend>(terminal: &mut Terminal<B>, mut app: App<'a>) -> io::
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('a') => {
app.tree.with_selected_leaf(|node| if let Some(node) = node {
node.add_child(TreeItem::new_leaf("text"));
app.tree.with_selected_leaf(|node| {
if let Some(node) = node {
node.add_child(TreeItem::new_leaf("text"));
}
});
},
}
KeyCode::Char('\n' | ' ') => app.tree.toggle(),
KeyCode::Left => app.tree.left(),
KeyCode::Right => app.tree.right(),
Expand Down
23 changes: 14 additions & 9 deletions examples/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use tui_tree_widget::{TreeItem, TreeState};
use tui_tree_widget::{TreeItem, TreeState, TreeItemRender};

pub struct StatefulTree<'a> {
pub struct StatefulTree<A> {
pub state: TreeState,
pub items: Vec<TreeItem<'a>>,
pub items: Vec<TreeItem<A>>,
}

impl<'a> StatefulTree<'a> {
impl<A: TreeItemRender> StatefulTree<A> {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
Expand All @@ -14,7 +14,7 @@ impl<'a> StatefulTree<'a> {
}
}

pub fn with_items(items: Vec<TreeItem<'a>>) -> Self {
pub fn with_items(items: Vec<TreeItem<A>>) -> Self {
Self {
state: TreeState::default(),
items,
Expand Down Expand Up @@ -49,13 +49,18 @@ impl<'a> StatefulTree<'a> {
self.state.toggle_selected();
}

fn items_mut<'b>(&'b mut self) -> &'b mut Vec<TreeItem<'a>> {
fn items_mut<'b>(&'b mut self) -> &'b mut Vec<TreeItem<A>> {
&mut self.items
}

pub fn with_selected_leaf<'b>(&'b mut self, f: impl FnOnce(Option<&'b mut TreeItem<'a>>)) where 'a: 'b
{
fn traverse<'short, 'long>(path: Vec<usize>, nodes: &'short mut [TreeItem<'long>]) -> Option<&'short mut TreeItem<'long>> where 'long: 'short {
pub fn with_selected_leaf<'b>(&'b mut self, f: impl FnOnce(Option<&'b mut TreeItem<A>>))
where
{
fn traverse<'short, A: TreeItemRender>(
path: Vec<usize>,
nodes: &'short mut [TreeItem<A>],
) -> Option<&'short mut TreeItem<A>>
{
let first = path.first()?;
let node = nodes.get_mut(*first)?;
if path.len() == 1 {
Expand Down
34 changes: 20 additions & 14 deletions src/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,30 @@
use crate::identifier::{TreeIdentifier, TreeIdentifierVec};
use crate::TreeItem;

pub struct Flattened<'a> {
pub struct Flattened<'a, A> {
pub identifier: Vec<usize>,
pub item: &'a TreeItem<'a>,
pub item: &'a TreeItem<A>,
}

impl<'a> Flattened<'a> {
impl<'a, A> Flattened<'a, A> {
pub fn depth(&self) -> usize {
self.identifier.len() - 1
}
}

/// Get a flat list of all visible [`TreeItem`s](TreeItem)
pub fn flatten<'a>(opened: &[TreeIdentifierVec], items: &'a [TreeItem<'a>]) -> Vec<Flattened<'a>> {
pub fn flatten<'a, A>(
opened: &[TreeIdentifierVec],
items: &'a [TreeItem<A>],
) -> Vec<Flattened<'a, A>> {
internal(opened, items, &[])
}

fn internal<'a>(
fn internal<'a, A>(
opened: &[TreeIdentifierVec],
items: &'a [TreeItem<'a>],
items: &'a [TreeItem<A>],
current: TreeIdentifier,
) -> Vec<Flattened<'a>> {
) -> Vec<Flattened<'a, A>> {
let mut result = Vec::new();

for (index, item) in items.iter().enumerate() {
Expand Down Expand Up @@ -57,14 +60,17 @@ fn get_naive_string_from_text(text: &tui::text::Text<'_>) -> String {
}

#[cfg(test)]
fn get_example_tree_items() -> Vec<TreeItem<'static>> {
fn get_example_tree_items() -> Vec<TreeItem<&'static str>> {
vec![
TreeItem::new_leaf("a"),
TreeItem::new(
"b",
vec![
TreeItem::new_leaf("c"),
TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]),
TreeItem::new(
"d",
vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")],
),
TreeItem::new_leaf("g"),
],
),
Expand All @@ -78,7 +84,7 @@ fn get_opened_nothing_opened_is_top_level() {
let result = flatten(&[], &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.map(|o| o.item.elem)
.collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "h"]);
}
Expand All @@ -90,7 +96,7 @@ fn get_opened_wrong_opened_is_only_top_level() {
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.map(|o| o.item.elem)
.collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "h"]);
}
Expand All @@ -102,7 +108,7 @@ fn get_opened_one_is_opened() {
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.map(|o| o.item.elem)
.collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "c", "d", "g", "h"]);
}
Expand All @@ -114,7 +120,7 @@ fn get_opened_all_opened() {
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.collect::<Vec<_>>();
.map(|o| o.item.elem)
.collect::<Vec<&str>>();
assert_eq!(result_text, ["a", "b", "c", "d", "e", "f", "g", "h"]);
}
60 changes: 33 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl TreeState {
}

/// Select the last node.
pub fn select_last(&mut self, items: &[TreeItem]) {
pub fn select_last<A>(&mut self, items: &[TreeItem<A>]) {
let visible = flatten(&self.get_all_opened(), items);
let new_identifier = visible
.last()
Expand All @@ -113,7 +113,7 @@ impl TreeState {

/// Handles the up arrow key.
/// Moves up in the current depth or to its parent.
pub fn key_up(&mut self, items: &[TreeItem]) {
pub fn key_up<A>(&mut self, items: &[TreeItem<A>]) {
let visible = flatten(&self.get_all_opened(), items);
let current_identifier = self.selected();
let current_index = visible
Expand All @@ -128,7 +128,7 @@ impl TreeState {

/// Handles the down arrow key.
/// Moves down in the current depth or into a child node.
pub fn key_down(&mut self, items: &[TreeItem]) {
pub fn key_down<A>(&mut self, items: &[TreeItem<A>]) {
let visible = flatten(&self.get_all_opened(), items);
let current_identifier = self.selected();
let current_index = visible
Expand Down Expand Up @@ -170,41 +170,47 @@ impl TreeState {
/// let b = TreeItem::new("root", vec![a]);
/// ```
#[derive(Debug, Clone)]
pub struct TreeItem<'a> {
text: Text<'a>,
pub struct TreeItem<A> {
elem: A, // TODO: text as fn of A?
style: Style,
children: Vec<TreeItem<'a>>,
children: Vec<TreeItem<A>>,
}

impl<'a> TreeItem<'a> {
pub fn new_leaf<T>(text: T) -> Self
where
T: Into<Text<'a>>,
{
pub trait TreeItemRender {
fn as_text(&self) -> Text;
}

impl TreeItemRender for &str {
fn as_text(&self) -> Text {
(*self).into()
}
}

impl<A: TreeItemRender> TreeItem<A> {
pub fn new_leaf(elem: A) -> Self {
Self {
text: text.into(),
style: Style::default(),
children: Vec::new(),
elem,
}
}

pub fn new<T, Children>(text: T, children: Children) -> Self
pub fn new<Children>(elem: A, children: Children) -> Self
where
T: Into<Text<'a>>,
Children: Into<Vec<TreeItem<'a>>>,
Children: Into<Vec<TreeItem<A>>>,
{
Self {
text: text.into(),
style: Style::default(),
children: children.into(),
elem,
}
}

pub fn children(&self) -> &[TreeItem] {
pub fn children(&self) -> &[TreeItem<A>] {
&self.children
}

pub fn children_mut<'b>(&'b mut self) -> &'b mut [TreeItem<'a>] where 'a: 'b{
pub fn children_mut(&mut self) -> &mut [TreeItem<A>] {
&mut self.children
}

Expand All @@ -217,7 +223,7 @@ impl<'a> TreeItem<'a> {
}

pub fn height(&self) -> usize {
self.text.height()
self.elem.as_text().height()
}

#[must_use]
Expand All @@ -226,7 +232,7 @@ impl<'a> TreeItem<'a> {
self
}

pub fn add_child(&mut self, child: TreeItem<'a>) {
pub fn add_child(&mut self, child: TreeItem<A>) {
self.children.push(child);
}
}
Expand Down Expand Up @@ -259,9 +265,9 @@ impl<'a> TreeItem<'a> {
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Tree<'a> {
pub struct Tree<'a, A> {
block: Option<Block<'a>>,
items: Vec<TreeItem<'a>>,
items: Vec<TreeItem<A>>,
/// Style used as a base style for the widget
style: Style,
start_corner: Corner,
Expand All @@ -271,10 +277,10 @@ pub struct Tree<'a> {
highlight_symbol: Option<&'a str>,
}

impl<'a> Tree<'a> {
impl<'a, A> Tree<'a, A> {
pub fn new<T>(items: T) -> Self
where
T: Into<Vec<TreeItem<'a>>>,
T: Into<Vec<TreeItem<A>>>,
{
Self {
block: None,
Expand Down Expand Up @@ -318,7 +324,7 @@ impl<'a> Tree<'a> {
}
}

impl<'a> StatefulWidget for Tree<'a> {
impl<'a, A: TreeItemRender> StatefulWidget for Tree<'a, A> {
type State = TreeState;

#[allow(clippy::too_many_lines)]
Expand Down Expand Up @@ -437,7 +443,7 @@ impl<'a> StatefulWidget for Tree<'a> {
};

let max_element_width = area.width.saturating_sub(after_depth_x - x);
for (j, line) in item.item.text.lines.iter().enumerate() {
for (j, line) in item.item.elem.as_text().lines.iter().enumerate() {
buf.set_spans(after_depth_x, y + j as u16, line, max_element_width);
}
if is_selected {
Expand All @@ -447,7 +453,7 @@ impl<'a> StatefulWidget for Tree<'a> {
}
}

impl<'a> Widget for Tree<'a> {
impl<'a, A: TreeItemRender> Widget for Tree<'a, A> {
fn render(self, area: Rect, buf: &mut Buffer) {
let mut state = TreeState::default();
StatefulWidget::render(self, area, buf, &mut state);
Expand Down

0 comments on commit 2d6481b

Please sign in to comment.