use std::collections::HashSet; use std::fmt::Display; use std::hash::{DefaultHasher, Hash, Hasher}; use ratatui::text::ToText; /// One item inside a [`Tree`](crate::Tree). /// /// Can have zero or more `children`. /// /// # identifier /// /// 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 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. /// As an example the following two identifiers describe the main file in a Rust cargo project: `vec!["src", "main.rs"]`. /// /// The identifier does not need to be a `String` and is therefore generic. /// Until version 0.14 this crate used `usize` and indices. /// This might still be perfect for your use case. /// /// # Example /// /// ``` /// # 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 where T: ToText + Clone + Default + Display + Hash, { pub(super) identifier: u64, pub(super) content: T, pub(super) children: Vec, } impl TreeItem where T: ToText + Clone + Default + Display + Hash, { /// Create a new `TreeItem` with children. /// /// # Errors /// /// Errors when there are duplicate identifiers in the children. pub fn new(content: T, children: Vec) -> std::io::Result { let identifiers = children .iter() .map(|item| &item.identifier) .collect::>(); if identifiers.len() != children.len() { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, "The children contain duplicate identifiers", )); } let mut hasher = DefaultHasher::new(); content.hash(&mut hasher); Ok(Self { 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) -> u64 { self.identifier } /// Get a reference to the text. #[must_use] pub const fn content(&self) -> &T { &self.content } #[must_use] pub fn children(&self) -> &[Self] { &self.children } /// Get a reference to a child by index. #[must_use] pub fn child(&self, index: usize) -> Option<&Self> { self.children.get(index) } /// 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 afterward. #[must_use] pub fn child_mut(&mut self, index: usize) -> Option<&mut Self> { self.children.get_mut(index) } #[must_use] pub fn height(&self) -> usize { self.content.clone().to_text().height() } /// Add a child to the `TreeItem`. /// /// # Errors /// /// Errors when the `identifier` of the `child` already exists in the children. pub fn add_child(&mut self, child: Self) -> std::io::Result<()> { let existing = self .children .iter() .map(|item| &item.identifier) .collect::>(); if existing.contains(&child.identifier) { return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, "identifier already exists in the children", )); } self.children.push(child); Ok(()) } } impl TreeItem<&'static str> { #[cfg(test)] #[must_use] pub(crate) fn example() -> Vec { vec![ Self::new_leaf("Alfa"), Self::new( "Bravo", vec![ Self::new_leaf("Charlie"), Self::new( "Delta", vec![ Self::new_leaf("Echo"), Self::new_leaf( "Foxtrot"), ], ) .expect("all item identifiers are unique"), Self::new_leaf("Golf"), ], ) .expect("all item identifiers are unique"), Self::new_leaf( "Hotel"), ] } } #[test] #[should_panic = "duplicate identifiers"] fn tree_item_new_errors_with_duplicate_identifiers() { let item = TreeItem::new_leaf( "text"); let another = item.clone(); 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("text"); let another = item.clone(); let mut root = TreeItem::new( "Root", vec![item]).unwrap(); root.add_child(another).unwrap(); }