gpui_kit_grid/state/tree.rs
1//! The tree on screen: turning a tree on and off, the column that shows it,
2//! and what the keys and the menu ask of a tree row.
3//!
4//! The rest is shared with group captions: the indent and the chevron, the
5//! press on a chevron and the keys (`state/disclosure.rs`), the selection
6//! of tree rows ([`SelectionPolicy`](crate::SelectionPolicy),
7//! `state/selection.rs`).
8
9use std::mem;
10
11use gpui_kit::Context;
12
13use super::{
14 ColumnOf, KeyOf, LiveGridDelegate, LiveGridState,
15 events::{ExpandTarget, GroupOrigin},
16 menu::GridTarget,
17};
18use crate::{
19 engine::TreeSpec,
20 model::{ColumnIx, GridColumns as _, GridRow as _},
21};
22
23impl<D: LiveGridDelegate> LiveGridState<D> {
24 /// Shows the rows as a tree by `GridRow::parent_key` (`Some`) or flat
25 /// (`None`), and rebuilds at once. The new order is shown from the top,
26 /// unless the focused line (a row or a group caption) is on screen and
27 /// still shown: it keeps its screen line. A tree replaces the grouping,
28 /// as [`Self::set_group_by`] replaces a tree. Emits `GroupChange` with
29 /// `GroupOrigin::Programmatic`, after `OrderHeldChange { held: false }`
30 /// if a held order was dropped; a `ProjectionChange` follows.
31 ///
32 /// A [`TreeSpec`] has no equality (its rules are closures), so a tree
33 /// always rebuilds and emits; `None` on a view that is no tree does
34 /// nothing. The expansion of tree rows is kept by key: turning the tree
35 /// off and on again brings it back.
36 pub fn set_tree(&mut self, tree: Option<TreeSpec<D::Row>>, cx: &mut Context<Self>) {
37 self.apply_tree(tree, GroupOrigin::Programmatic, cx);
38 }
39
40 /// The one path of every tree change but `set_view`, which compares the
41 /// whole view: `set_tree` and a restored layout. Returns false, doing
42 /// nothing, for `None` on a view that is no tree; otherwise puts the
43 /// tree into the view (a grouping goes) or makes the view flat, and
44 /// rebuilds at once, as a user action, shown as any new view
45 /// (`view_changed`): `OrderHeldChange { held: false }` if a held order
46 /// is dropped, then `GroupChange { origin }`.
47 pub(crate) fn apply_tree(
48 &mut self,
49 tree: Option<TreeSpec<D::Row>>,
50 origin: GroupOrigin,
51 cx: &mut Context<Self>,
52 ) -> bool {
53 let view = mem::take(&mut self.view);
54 let is_tree = view.tree().is_some();
55 self.view = match tree {
56 Some(tree) => view.with_tree(tree),
57 None if is_tree => view.with_group_by(std::iter::empty()),
58 None => view,
59 };
60 if !is_tree && self.view.tree().is_none() {
61 return false;
62 }
63 self.view_changed(None, Some(origin), cx);
64 true
65 }
66
67 /// The column that shows the tree now: [`TreeSpec::column`] if it is
68 /// visible, else the first visible column in display order (also while
69 /// a tree projection is still shown for a view that is no tree any
70 /// more). Read from the column layout every frame, so hiding or moving
71 /// columns never loses the tree.
72 pub(crate) fn tree_column(&self) -> Option<ColumnIx> {
73 self.view
74 .tree()
75 .and_then(|tree| tree.column())
76 .map(|column| column.ix())
77 .filter(|ix| self.columns.is_visible(*ix))
78 .or_else(|| self.columns.visible().next())
79 }
80
81 /// Returns true if the shown projection is a tree and the row with
82 /// `key` has shown children in it.
83 pub(super) fn is_tree_branch(&self, key: &KeyOf<D>) -> bool {
84 self.projection.is_tree()
85 && self
86 .snapshot
87 .row_id(key)
88 .is_some_and(|id| self.projection.branch(id).is_some())
89 }
90
91 /// The parent of a tree row in the shown projection: its nearest
92 /// ancestor the view passes (a filter may hide the rows between).
93 /// `None` for a root, and outside a tree. O(depth), once per key press.
94 pub(super) fn tree_parent(&self, key: &KeyOf<D>) -> Option<KeyOf<D>> {
95 let (projection, snapshot) = (&self.projection, &self.snapshot);
96 let tree = projection.tree_data()?;
97 let id = snapshot.row_id(key)?;
98 // A root: no parent, an orphan, or a row of a cycle.
99 if tree.depth(projection.order_ix(id)?) == 0 {
100 return None;
101 }
102 let mut row = snapshot.row(id)?;
103 // Up the parent keys to the nearest row the projection holds; the
104 // bound ends a cycle of rows it does not.
105 for _ in 0..=u16::MAX {
106 let parent = snapshot.row_id(row.parent_key()?)?;
107 row = snapshot.row(parent)?;
108 if projection.contains(parent) {
109 return Some(row.key().clone());
110 }
111 }
112 None
113 }
114
115 /// Whether the line of a menu target is expanded, if it expands: a
116 /// group caption, or a tree row with children.
117 pub(super) fn target_expansion(
118 &self,
119 target: &GridTarget<KeyOf<D>, ColumnOf<D>>,
120 ) -> Option<bool> {
121 match target {
122 GridTarget::GroupCaption { group, .. } => {
123 Some(self.is_expanded(&ExpandTarget::Group(group.clone())))
124 }
125 GridTarget::Cell { key, .. } if self.is_tree_branch(key) => {
126 Some(self.is_expanded(&ExpandTarget::Row(key.clone())))
127 }
128 _ => None,
129 }
130 }
131}