makeover-layout 0.51.7

The renderer-agnostic half of the make-family design system: what a thing IS, named as intents and relationships and never as values. Colour defers to makeover, spacing to makeover-geometry; what is left is composition, plus which affordances exist at a given input class and window size.
Documentation
//! A tag tree as a [`Selecting::Subtree`] facet, from tagtree's three answers.
//!
//! [`Selecting::Subtree`] leaves resolution to the app, and tagtree (published
//! as `mnw-tagtree`) is where the resolution lives: an outline for the rows in
//! draw order, a selection for how each row stands, and a rollup for how many
//! items sit under each. This is the mapping from those to [`FacetValue`]s, in
//! one place rather than once per app. Behind the `tagtree` feature, so a
//! consumer that draws no tag tree takes no dependency for it.
//!
//! Which rows to show stays the app's, as [`Facet`] says, and
//! [`tagtree::Selection::reveals`] is the usual answer: roots, the children of
//! what was picked, and the path down to every decision.

use crate::{FacetValue, Nesting, Standing};

impl From<tagtree::Standing> for Standing {
    fn from(standing: tagtree::Standing) -> Self {
        match standing {
            tagtree::Standing::Open => Self::Open,
            tagtree::Standing::Taken => Self::Taken,
            tagtree::Standing::Inherited => Self::Inherited,
            tagtree::Standing::Pruned => Self::Pruned,
            tagtree::Standing::PrunedAbove => Self::PrunedAbove,
        }
    }
}

/// One facet value per outline row, standing from `selection` and counted from
/// `counts` when the app measured them.
///
/// `rows` is whichever part of [`tagtree::TagIndex::outline`] the app chose to
/// show, in the order it came: the outline is pre-order, which is draw order.
/// A row's [`depth`](FacetValue::depth) counts from zero at a root, where
/// tagtree counts segments from one. `branching` is the outline's own answer,
/// whether anything hangs below the node in the index, rather than whether its
/// children happen to be shown, so a reader is told a branch opens before
/// pressing it.
///
/// ```
/// use makeover_layout::{Standing, tag_facet::values};
///
/// let index = tagtree::TagIndex::new(vec!["audio.genre.ambient".into(), "video.essay".into()]);
/// let mut selection = tagtree::Selection::new();
/// selection.pick("audio");
/// let outline = index.outline();
/// let shown: Vec<_> = outline.into_iter().filter(|n| selection.reveals(n.path)).collect();
/// let rollup = tagtree::Rollup::from_sets([(vec!["audio.genre.ambient"], 3)]);
///
/// let facet = values(&shown, &selection, Some(&rollup));
/// let audio = &facet[0];
/// assert_eq!((audio.value, audio.standing, audio.count), ("audio", Standing::Taken, Some(3)));
/// assert_eq!(facet[1].value, "audio.genre");
/// assert_eq!(facet[1].standing, Standing::Inherited);
/// assert_eq!(facet[1].depth.level, 1);
/// ```
#[must_use]
pub fn values<'a>(
    rows: &[tagtree::OutlineNode<'a>],
    selection: &tagtree::Selection,
    counts: Option<&tagtree::Rollup>,
) -> Vec<FacetValue<'a>> {
    rows.iter()
        .map(|row| {
            let value = FacetValue::new(row.path, row.label)
                .standing(selection.standing(row.path).into())
                .at(
                    Nesting::at(u8::try_from(row.depth.saturating_sub(1)).unwrap_or(u8::MAX)),
                    row.branching,
                );
            match counts {
                Some(rollup) => value.counted(rollup.total(row.path)),
                None => value,
            }
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn every_tagtree_standing_maps_to_its_namesake() {
        assert_eq!(Standing::from(tagtree::Standing::Open), Standing::Open);
        assert_eq!(Standing::from(tagtree::Standing::Taken), Standing::Taken);
        assert_eq!(
            Standing::from(tagtree::Standing::Inherited),
            Standing::Inherited
        );
        assert_eq!(Standing::from(tagtree::Standing::Pruned), Standing::Pruned);
        assert_eq!(
            Standing::from(tagtree::Standing::PrunedAbove),
            Standing::PrunedAbove
        );
    }

    #[test]
    fn an_unmeasured_facet_carries_no_counts() {
        let index = tagtree::TagIndex::new(vec!["a.b".into()]);
        let outline = index.outline();
        let facet = values(&outline, &tagtree::Selection::new(), None);
        assert!(facet.iter().all(|v| v.count.is_none()));
        assert!(facet[0].branching && !facet[1].branching);
        assert_eq!(facet[0].label, "a");
        assert_eq!(facet[1].label, "b");
    }

    #[test]
    fn a_prune_reads_out_below_it() {
        let index = tagtree::TagIndex::new(vec!["a.b.c".into()]);
        let outline = index.outline();
        let mut selection = tagtree::Selection::taking(["a"]);
        selection.prune("a.b");
        let facet = values(&outline, &selection, None);
        let standings: Vec<Standing> = facet.iter().map(|v| v.standing).collect();
        assert_eq!(
            standings,
            [Standing::Taken, Standing::Pruned, Standing::PrunedAbove]
        );
    }
}