Skip to main content

max / quasi

3.1 KB · 74 lines History Blame Raw
1 //! Which rows a shut branch folds away, as the reader has left it.
2 //!
3 //! [`Row::depth`](quasi_router::Row::depth) and
4 //! [`Row::open`](quasi_router::Row::open) describe a hierarchy as a flat list of
5 //! rows each saying how deep it sits, and which rows a shut branch covers falls
6 //! out of that. [`quasi_router::folded_by`] is the walk, in the description
7 //! layer, so a terminal and a webview cannot disagree about what a branch holds.
8 //!
9 //! What is here is the other half: the description says where the outline
10 //! starts and the reader says where it is now. A fold is this renderer's, the
11 //! way a scroll offset and a tick are, and [`View::open`] is where it is kept.
12 //!
13 //! Three walks read a screen and have to agree exactly -- the focus walk, the
14 //! height walk and the drawing -- so all three ask this rather than each
15 //! deciding for itself which rows are there.
16
17 use quasi_router::Outline;
18
19 use crate::View;
20
21 /// One answer per row: whether a shut branch above it is hiding it.
22 ///
23 /// `view` is the reader's own state, or `None` for a caller with none, which
24 /// gets the outline exactly as the description drew it.
25 pub(crate) fn folds<T: Outline>(rows: &[T], view: Option<&View>) -> Vec<bool> {
26 quasi_router::folded_by(rows.iter().map(|row| {
27 let open = match (row.open(), view) {
28 (Some(described), Some(view)) => Some(view.open(&row.key(), described)),
29 (open, _) => open,
30 };
31 (row.depth(), open)
32 }))
33 }
34
35 /// The rows a reader can actually see, with their positions in the description.
36 ///
37 /// Positions rather than a filtered list, because a caller drawing a row still
38 /// needs to know which row it was: focus is claimed per row and a renumbered
39 /// list would move the caret when a branch folds.
40 pub(crate) fn showing<'a, T: Outline>(
41 rows: &'a [T],
42 view: Option<&View>,
43 ) -> impl Iterator<Item = (usize, &'a T)> {
44 let folded = folds(rows, view);
45 rows.iter().enumerate().filter(move |(at, _)| !folded[*at])
46 }
47
48 /// How far a row is indented, in cells.
49 ///
50 /// Two per level, which is the indent a terminal file manager uses and the
51 /// smallest one that reads as a level at all. A size, and this renderer's to
52 /// pick: a terminal measures in cells, so there is nothing to defer to.
53 pub(crate) const STEP: u16 = 2;
54
55 /// The columns a row spends before its run: its indent, plus the chevron
56 /// column when anything in the list has one.
57 ///
58 /// The column is spent on every row of a list that holds a branch, leaf or not,
59 /// so the labels line up under each other. A list with no branch in it spends
60 /// nothing, which is every list described before `Row::open` existed.
61 pub(crate) fn lead(depth: quasi_router::layout::Nesting, branches: bool) -> u16 {
62 let indent = u16::from(depth.level).saturating_mul(STEP);
63 indent.saturating_add(if branches { STEP } else { 0 })
64 }
65
66 /// The mark a branch draws, open or shut.
67 ///
68 /// The heavier glyph of each pair on purpose: a chevron is being read at a
69 /// glance in a column of them, and the light forms disappear at small sizes and
70 /// in low contrast.
71 pub(crate) const fn chevron(open: bool) -> &'static str {
72 if open { "" } else { "" }
73 }
74