Components / Data
Tree view
Markdown crates/ui/src/tree.rs docs.rs
use ui::tree::{self, Row};
tree::init(cx); // once, at startup
let rows = self.flatten(); // Vec<(Row, label)>
tree::tree().children(rows.iter().enumerate().map(|(index, (row, label))| {
tree::tree_row(&theme, row, self.selected == Some(index), self.cursor == index)
.id(("row", index))
.child(label.clone())
}))The app flattens what is currently visible — which it has to do to render it anyway. A depth-annotated flat list is a complete navigation model: down and up are neighbouring indices, a first child is the next row, and a parent is the nearest row above with a smaller depth.
Keys
match tree::step(&rows, self.cursor, tree::Direction::Right) {
Some(tree::Move::To(index)) => self.cursor = index,
Some(tree::Move::Expand(index)) => self.open.insert(index),
Some(tree::Move::Collapse(index)) => self.open.remove(&index),
None => {}
}API
// ui::tree
pub fn init(cx: &mut App);
pub fn tree() -> gpui::Div;
/// `selected` is what the app considers chosen, `cursor` is where the keyboard
/// is — the same pair a menu row draws with.
pub fn tree_row(theme: &Theme, row: &Row, selected: bool, cursor: bool) -> gpui::Div;
/// `expanded: None` is a leaf, which is a different thing from a closed branch
/// — the difference is what stops `right` pretending a file can open.
pub struct Row {
pub depth: usize,
pub expanded: Option<bool>,
}
/// Reports an intent rather than performing one: applying it means touching
/// the expansion set the app owns. Neither end wraps.
pub fn step(rows: &[Row], cursor: usize, direction: Direction) -> Option<Move>;
pub enum Move { To(usize), Expand(usize), Collapse(usize) }
// ...Scrolling is the caller's, through scroll. Expansion stays with the app because it is app data: a file tree's open folders often outlive the window.