//! Which rows a shut branch folds away, as the reader has left it. //! //! [`Row::depth`](quasi_router::Row::depth) and //! [`Row::open`](quasi_router::Row::open) describe a hierarchy as a flat list of //! rows each saying how deep it sits, and which rows a shut branch covers falls //! out of that. [`quasi_router::folded_by`] is the walk, in the description //! layer, so a terminal and a webview cannot disagree about what a branch holds. //! //! What is here is the other half: the description says where the outline //! starts and the reader says where it is now. A fold is this renderer's, the //! way a scroll offset and a tick are, and [`View::open`] is where it is kept. //! //! Three walks read a screen and have to agree exactly -- the focus walk, the //! height walk and the drawing -- so all three ask this rather than each //! deciding for itself which rows are there. use quasi_router::Outline; use crate::View; /// One answer per row: whether a shut branch above it is hiding it. /// /// `view` is the reader's own state, or `None` for a caller with none, which /// gets the outline exactly as the description drew it. pub(crate) fn folds(rows: &[T], view: Option<&View>) -> Vec { quasi_router::folded_by(rows.iter().map(|row| { let open = match (row.open(), view) { (Some(described), Some(view)) => Some(view.open(&row.key(), described)), (open, _) => open, }; (row.depth(), open) })) } /// The rows a reader can actually see, with their positions in the description. /// /// Positions rather than a filtered list, because a caller drawing a row still /// needs to know which row it was: focus is claimed per row and a renumbered /// list would move the caret when a branch folds. pub(crate) fn showing<'a, T: Outline>( rows: &'a [T], view: Option<&View>, ) -> impl Iterator { let folded = folds(rows, view); rows.iter().enumerate().filter(move |(at, _)| !folded[*at]) } /// How far a row is indented, in cells. /// /// Two per level, which is the indent a terminal file manager uses and the /// smallest one that reads as a level at all. A size, and this renderer's to /// pick: a terminal measures in cells, so there is nothing to defer to. pub(crate) const STEP: u16 = 2; /// The columns a row spends before its run: its indent, plus the chevron /// column when anything in the list has one. /// /// The column is spent on every row of a list that holds a branch, leaf or not, /// so the labels line up under each other. A list with no branch in it spends /// nothing, which is every list described before `Row::open` existed. pub(crate) fn lead(depth: quasi_router::layout::Nesting, branches: bool) -> u16 { let indent = u16::from(depth.level).saturating_mul(STEP); indent.saturating_add(if branches { STEP } else { 0 }) } /// The mark a branch draws, open or shut. /// /// The heavier glyph of each pair on purpose: a chevron is being read at a /// glance in a column of them, and the light forms disappear at small sizes and /// in low contrast. pub(crate) const fn chevron(open: bool) -> &'static str { if open { "▼" } else { "▶" } }