use gpui::{App, KeyBinding, actions, div, prelude::*, px};
use theme::{TextStyle, Theme, Typeset, hairline};
use crate::widgets::Layout;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Row {
pub depth: usize,
pub expanded: Option<bool>,
}
impl Row {
pub fn leaf(depth: usize) -> Self {
Self {
depth,
expanded: None,
}
}
pub fn branch(depth: usize, expanded: bool) -> Self {
Self {
depth,
expanded: Some(expanded),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Move {
To(usize),
Expand(usize),
Collapse(usize),
}
pub fn parent_of(rows: &[Row], index: usize) -> Option<usize> {
let depth = rows.get(index)?.depth;
rows[..index]
.iter()
.rposition(|candidate| candidate.depth < depth)
}
pub fn step(rows: &[Row], cursor: usize, direction: Direction) -> Option<Move> {
let row = rows.get(cursor)?;
match direction {
Direction::Up => cursor.checked_sub(1).map(Move::To),
Direction::Down => (cursor + 1 < rows.len()).then_some(Move::To(cursor + 1)),
Direction::Right => match row.expanded {
Some(false) => Some(Move::Expand(cursor)),
Some(true) => (cursor + 1 < rows.len()).then_some(Move::To(cursor + 1)),
None => None,
},
Direction::Left => match row.expanded {
Some(true) => Some(Move::Collapse(cursor)),
_ => parent_of(rows, cursor).map(Move::To),
},
}
}
actions!(bezel_tree, [SelectPrevious, SelectNext, Collapse, Expand]);
pub const KEY_CONTEXT: &str = "Tree";
pub fn init(cx: &mut App) {
let ctx = Some(KEY_CONTEXT);
cx.bind_keys([
KeyBinding::new("up", SelectPrevious, ctx),
KeyBinding::new("down", SelectNext, ctx),
KeyBinding::new("left", Collapse, ctx),
KeyBinding::new("right", Expand, ctx),
]);
}
pub const INDENT: f32 = 14.0;
const CHEVRON: f32 = 16.0;
pub fn tree() -> gpui::Div {
div().flex().flex_col().w_full()
}
pub fn tree_row(theme: &Theme, row: &Row, selected: bool, cursor: bool) -> gpui::Div {
let mut frame = div()
.flex()
.flex_row()
.items_center()
.w_full()
.py(px(3.0))
.pr(px(8.0))
.text_style(TextStyle::Callout)
.cursor_pointer();
frame = if selected {
frame.bg(theme::card_selected_bg()).text_color(theme.text)
} else if cursor {
frame.bg(theme::wash(0.05)).text_color(theme.text)
} else {
frame.text_color(theme.text_muted)
};
frame
.children((0..row.depth).map(|_| {
div()
.flex_none()
.w(px(INDENT))
.h(px(18.0))
.border_l_1()
.border_color(hairline(0.08))
}))
.child(
div()
.flex_none()
.w(px(CHEVRON))
.flex()
.items_center()
.justify_center()
.when_some(row.expanded, |slot, expanded| {
slot.child(theme.disclosure(expanded))
}),
)
}