makeover-layout 0.52.4

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
use crate::Nesting;

// Names this module's prose links to, resolved for rustdoc.
#[allow(unused_imports)]
use crate::{Awaiting, FieldKind};

/// A named dimension a set can be narrowed by.
///
/// One word for six things that were six mechanisms. MNW's discover page filters
/// by free text, a flat any-of over item types, a tree of tags, a numeric range
/// over price, a nested one-of over AI tier, and a browse position in the tag
/// tree held separately from the tag selection, and the last two being separate
/// is the whole reason a filter row there needs a tick box *and* a chevron. The
/// panel is a mixed bag of hand-written controls because nothing named the thing
/// they all are.
///
/// Deliberately wider than that one page. audiofiles' library browser and
/// goingson's filters are the same shape, and a word that only fitted discover
/// would be discover's markup with a neutral name on it.
///
/// # What it does not say
///
/// **What picking a value calls.** This crate names no address, so a facet is
/// paired with routes the way a sortable column is paired with what
/// reordering calls.
///
/// **How a tree is drawn.** Indented rows, a column of panes, a breadcrumb and a
/// list: all four are honest renderings of the same described facet, and a
/// terminal will not pick the same one a browser does. [`FacetValue::depth`] is
/// what a renderer needs to draw any of them; the choice is not described.
///
/// **Which values to show.** A tag tree has thousands of nodes and a panel shows
/// a handful. Deciding which handful is the app's, it is the same question as
/// which rows go in a table, and no table member answers it either.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Facet<'a> {
    /// What the dimension is called, as the user reads it.
    pub name: &'a str,
    /// How many of its values may be in force, and in what shape.
    pub mode: Selecting,
    /// The values on offer, in the order they are drawn.
    ///
    /// A [`Selecting::Text`] facet has none: the value is whatever was typed,
    /// and a description that listed the possible strings would be listing the
    /// corpus. A [`Selecting::Range`] facet has none either, for the reason
    /// [`FieldKind::Range`] takes bounds rather than options, the ends are the
    /// question and the values between them are not enumerable.
    pub values: &'a [FacetValue<'a>],
}

impl<'a> Facet<'a> {
    /// A dimension with values to pick from.
    #[must_use]
    pub const fn new(name: &'a str, mode: Selecting, values: &'a [FacetValue<'a>]) -> Self {
        Self { name, mode, values }
    }

    /// Whether the facet is narrowing the set right now.
    ///
    /// The question a renderer asks to decide whether to offer a way out of it,
    /// and the reason it is derived rather than carried: a facet with nothing
    /// standing is unengaged by construction, so a member saying so could
    /// disagree with the values beside it. [`Standing::Inherited`] does not
    /// count, something further up is what is doing the narrowing, and clearing
    /// a child that was never picked clears nothing.
    ///
    /// Always false for [`Selecting::Text`] and [`Selecting::Range`], which
    /// carry no values. A host that wants a clear affordance on those knows
    /// whether its own box is empty; the description does not hold the typed
    /// string.
    #[must_use]
    pub fn engaged(&self) -> bool {
        self.values.iter().any(|value| value.standing.is_picked())
    }

    /// The deepest value in the facet, or zero when it is flat.
    ///
    /// What an indenting renderer needs to reserve a gutter before it draws the
    /// first row, which is "First paint is final paint" applied to a tree: a
    /// gutter widened as deeper values arrive is the reflow that rule forbids.
    #[must_use]
    pub fn reach(&self) -> u8 {
        self.values
            .iter()
            .map(|value| value.depth.level)
            .max()
            .unwrap_or(0)
    }
}

/// How many of a [`Facet`]'s values may be in force, and in what shape.
///
/// Five, and the fifth is what made this an enum rather than a bool. `one-of`,
/// `any-of`, a range and free text are the four a form vocabulary already has in
/// [`FieldKind`]; a tree's selection is none of them, and describing tags as
/// any-of was what forced browsing to be a second mechanism beside filtering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Selecting {
    /// Exactly one value, and picking another replaces it.
    ///
    /// MNW's AI tier, whose three options are nested ranges rather than
    /// independent values, so two of them at once means nothing.
    OneOf,
    /// Any number of values, each independent of the others.
    AnyOf,
    /// A low end, a high end, or both.
    ///
    /// Carries no values for [`FieldKind::Range`]'s reason: the ends are the
    /// question.
    Range,
    /// Whatever the user types.
    Text,
    /// A position in a tree, edited by taking branches in and pruning branches
    /// out.
    ///
    /// The one mode that is not reducible to the others, and the one gesture
    /// that replaced two. Picking a value narrows the set to it *and* reveals
    /// its children, so browsing a tree and filtering by it stop being separate
    /// mechanisms with separate state. What a selection then is: a set of
    /// branches taken and a set pruned, resolved nearest-ancestor-first, so
    /// `music` in and `music/synths` out is sayable and no flat mode can say it.
    ///
    /// Resolution happens in the app, and what reaches a renderer is the
    /// [`Standing`] each drawn value ended up with. A renderer walking ancestors
    /// itself would be a renderer that can disagree with the results beside it.
    Subtree,
}

impl Selecting {
    /// Whether the mode picks from values the description lists.
    ///
    /// False for [`Text`](Self::Text) and [`Range`](Self::Range), which are the
    /// two whose answer is not one of a set. A renderer asks this before it
    /// looks at [`Facet::values`], the way it asks
    /// [`FieldKind::offers_options`] before it looks at a field's options.
    #[must_use]
    pub const fn offers_values(self) -> bool {
        matches!(self, Self::OneOf | Self::AnyOf | Self::Subtree)
    }

    /// Whether a value can be pruned as well as picked.
    ///
    /// [`Subtree`](Self::Subtree) alone. Excluding a value from a flat facet is
    /// the same fact as not picking it, so an exclude affordance there would be
    /// a second control for a state the first one already holds.
    #[must_use]
    pub const fn prunes(self) -> bool {
        matches!(self, Self::Subtree)
    }

    /// Whether picking a second value keeps the first.
    #[must_use]
    pub const fn accumulates(self) -> bool {
        matches!(self, Self::AnyOf | Self::Subtree)
    }
}

/// One value a [`Facet`] offers, as it currently stands.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FacetValue<'a> {
    /// What identifies it, and what a host keys its route on.
    ///
    /// An option's split between what submits and what reads, and a tree is
    /// why it is not optional: two leaves under different parents are
    /// legitimately both called "Ambient",
    /// and the path is the only thing telling them apart. It is also what
    /// nearest-ancestor-wins resolves over, so an app that carried only labels
    /// could not compute the [`standing`](Self::standing) it hands back here.
    pub value: &'a str,
    /// What it is called, as the user reads it.
    ///
    /// The leaf's own name rather than its path: a facet drawn as an indented
    /// tree repeats every ancestor on every line otherwise, and one drawn as a
    /// breadcrumb has the ancestors already.
    pub label: &'a str,
    /// How many members of the set carry it.
    ///
    /// Optional, and settled that way rather than made mandatory: a count is a
    /// measured fact the app may not have. Counting a tag subtree under an
    /// active text search is a second query, and an app that will not pay for it
    /// should be able to describe the facet anyway rather than write a zero that
    /// reads as "none of them". That is [`Awaiting::amount`]'s rule in a second
    /// place, state a number when it was measured, and nothing when it was not.
    pub count: Option<u64>,
    /// Whether it is narrowing the set, and how it came to be.
    pub standing: Standing,
    /// How far down the tree it sits, counting from zero at the root.
    ///
    /// Always zero for a flat facet, which is what makes an indenting renderer
    /// one code path rather than two. A renderer that draws no tree at all still
    /// reads this, since a value's depth is what distinguishes two same-named
    /// leaves under different parents.
    pub depth: Nesting,
    /// Whether taking it reveals values under it.
    ///
    /// Distinct from having a nonzero [`depth`](Self::depth): a leaf deep in the
    /// tree branches no further, and a root with children does. Both facts are
    /// needed and neither implies the other, which is why the pair is two
    /// members rather than one count.
    pub branching: bool,
    /// Whether pressing it narrows the set to it.
    ///
    /// True for every value of a flat facet. In a tree, a node that is not
    /// pickable is a group: a heading whose press opens or closes it, and which
    /// offers no exclude. A non-pickable node that does not branch is a link
    /// row ("31 more", "More groups"), whose press is its whole meaning. Which
    /// nodes are pickable is data, never a name: an app passes it in.
    pub pickable: bool,
    /// Whether its children are drawn, when that is view state of its own.
    ///
    /// `None` keeps the older reading, open while in force, because picking a
    /// value used to be the only way to look inside it. `Some` separates the
    /// two: opening a group is not picking it.
    pub open: Option<bool>,
    /// How the picks among its children combine, for a node whose children
    /// are picked: any of them, or all of them. `None` where nothing under it
    /// can be picked. A renderer offers the toggle on the open row.
    pub join: Option<Join>,
    /// Whether [`count`](Self::count) is a gain rather than a total: how many
    /// more results picking it adds, under a parent combining any of its picks
    /// that already holds one. A renderer writes it as `+n`.
    pub gain: bool,
    /// Where a group ranks among the others, for a group the app ranked.
    pub rank: Option<GroupRank>,
}

/// How the picks under one parent combine.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Join {
    /// A member needs at least one of them.
    #[default]
    Any,
    /// A member needs every one of them.
    All,
}

/// Where a group ranks: how much a pick inside it would narrow the set.
///
/// A group is offered first when picking in it cuts the most, which the app
/// measures from its results (tagtree's `rank_groups` is one way). Nothing
/// here names a group, so the order moves as the set narrows.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct GroupRank {
    /// A pick sits inside it already.
    pub in_use: bool,
    /// Some pick inside it would still leave results. A dead group says so
    /// rather than offering choices that empty the set.
    pub live: bool,
    /// How hard a pick here cuts, from 0 to 100, against the best group on
    /// offer: what a bar draws.
    pub cut: u8,
    /// The best group not yet in use: where a newcomer should start.
    pub start: bool,
}

impl GroupRank {
    /// A live group cutting `cut` (clamped to 100).
    #[must_use]
    pub const fn cutting(cut: u8) -> Self {
        Self {
            in_use: false,
            live: true,
            cut: if cut > 100 { 100 } else { cut },
            start: false,
        }
    }

    /// A group with a pick inside it.
    #[must_use]
    pub const fn in_use() -> Self {
        Self {
            in_use: true,
            live: true,
            cut: 0,
            start: false,
        }
    }

    /// A group no pick in which would leave anything.
    #[must_use]
    pub const fn dead() -> Self {
        Self {
            in_use: false,
            live: false,
            cut: 0,
            start: false,
        }
    }

    /// Marked as where to start.
    #[must_use]
    pub const fn starting(mut self) -> Self {
        self.start = true;
        self
    }
}

impl<'a> FacetValue<'a> {
    /// An unpicked value at the root of the facet.
    #[must_use]
    pub const fn new(value: &'a str, label: &'a str) -> Self {
        Self {
            value,
            label,
            count: None,
            standing: Standing::Open,
            depth: Nesting::top(),
            branching: false,
            pickable: true,
            open: None,
            join: None,
            gain: false,
            rank: None,
        }
    }

    /// A value whose identifier is also what the user reads.
    ///
    /// A plain option's convenience, and it is the flat case: a type or a tier
    /// is its own name, and only a tree needs a path that is not one.
    #[must_use]
    pub const fn of(value: &'a str) -> Self {
        Self::new(value, value)
    }

    /// How many members carry it, when that was measured.
    #[must_use]
    pub const fn counted(mut self, count: u64) -> Self {
        self.count = Some(count);
        self
    }

    /// How it stands in the current selection.
    #[must_use]
    pub const fn standing(mut self, standing: Standing) -> Self {
        self.standing = standing;
        self
    }

    /// Where it sits in the tree, and whether anything hangs off it.
    #[must_use]
    pub const fn at(mut self, depth: Nesting, branching: bool) -> Self {
        self.depth = depth;
        self.branching = branching;
        self
    }

    /// A group: a heading whose press opens it, never a pick.
    #[must_use]
    pub const fn heading(mut self) -> Self {
        self.pickable = false;
        self
    }

    /// Whether its children are drawn, as view state apart from its standing.
    #[must_use]
    pub const fn opened(mut self, open: bool) -> Self {
        self.open = Some(open);
        self
    }

    /// How the picks among its children combine.
    #[must_use]
    pub const fn joined(mut self, join: Join) -> Self {
        self.join = Some(join);
        self
    }

    /// Its count is a gain, `+n`, rather than a total.
    #[must_use]
    pub const fn gained(mut self) -> Self {
        self.gain = true;
        self
    }

    /// Where it ranks among the groups.
    #[must_use]
    pub const fn ranked(mut self, rank: GroupRank) -> Self {
        self.rank = Some(rank);
        self
    }

    /// Whether its children are drawn: its own open state when it has one,
    /// else whether it is in force.
    #[must_use]
    pub const fn is_open(&self) -> bool {
        match self.open {
            Some(open) => open,
            None => self.standing.in_force(),
        }
    }

    /// A link row: neither pickable nor branching, so pressing it is its
    /// whole meaning ("31 more").
    #[must_use]
    pub const fn is_link(&self) -> bool {
        !self.pickable && !self.branching
    }
}

/// Whether a [`FacetValue`] is narrowing the set, and how it came to be.
///
/// Five rather than a bool, and the extra members are what a tree costs. A
/// pruned branch and an untaken one are not the same state (one was decided
/// against and the other was never reached) and a child under a taken parent is
/// in force without anybody having picked it. A renderer given a bool either
/// marks every descendant of a taken branch, which reads as forty deliberate
/// choices, or marks none of them, which reads as unfiltered.
///
/// The out side needs the same pair the in side has. A child under a pruned
/// branch is out without anybody having pruned it: [`PrunedAbove`] to
/// [`Pruned`]'s decision, as [`Inherited`] is to [`Taken`]'s.
///
/// [`PrunedAbove`]: Self::PrunedAbove
/// [`Pruned`]: Self::Pruned
/// [`Inherited`]: Self::Inherited
/// [`Taken`]: Self::Taken
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Standing {
    /// Not picked, and nothing above it is either.
    #[default]
    Open,
    /// Picked here. The set is narrowed to it and whatever hangs off it.
    Taken,
    /// In force because something above it was taken.
    Inherited,
    /// Pruned out, though something above it was taken.
    ///
    /// The state that only [`Selecting::Subtree`] can reach, and the reason
    /// exclusion is drawn as a visible affordance beside each label rather than
    /// as a modifier on the ordinary one: a gesture a terminal cannot express is
    /// a gesture half the renderers would have to leave out, and an affordance
    /// nothing teaches is one users do not find.
    Pruned,
    /// Out because something above it was pruned.
    ///
    /// Not picked: clearing it clears nothing, because the decision is further
    /// up, which is [`Inherited`](Self::Inherited)'s position on the other
    /// side.
    PrunedAbove,
}

impl Standing {
    /// Whether the user decided this value, either way.
    ///
    /// True for [`Taken`](Self::Taken) and [`Pruned`](Self::Pruned), both are
    /// choices, and both are things a "clear this" affordance has to clear.
    /// [`Inherited`](Self::Inherited) is not: clearing it clears nothing,
    /// because the decision is further up.
    #[must_use]
    pub const fn is_picked(self) -> bool {
        matches!(self, Self::Taken | Self::Pruned)
    }

    /// Whether the value narrows the set in.
    ///
    /// [`Taken`](Self::Taken) and [`Inherited`](Self::Inherited): one was picked
    /// and one came down from above, and to the set they mean the same thing.
    /// The pair is named here so a renderer colouring in-force values does not
    /// have to know which is which.
    #[must_use]
    pub const fn in_force(self) -> bool {
        matches!(self, Self::Taken | Self::Inherited)
    }

    /// The content intent the value takes.
    ///
    /// [`Pruned`](Self::Pruned) reads back a step, which is the three-tone rule
    /// above rather than a new decision: a pruned branch is still a live control
    /// (pressing it takes the prune off) so it may not wear `content-muted`,
    /// and it is not the thing itself either.
    #[must_use]
    pub const fn intent(self) -> &'static str {
        match self {
            Self::Taken | Self::Inherited | Self::Open => "content",
            Self::Pruned | Self::PrunedAbove => "content-secondary",
        }
    }

    /// Whether the value is out of the set by a prune, here or above.
    ///
    /// [`in_force`](Self::in_force)'s counterpart, so a renderer reading a
    /// value back a step does not have to know which of the two it has.
    #[must_use]
    pub const fn pruned_out(self) -> bool {
        matches!(self, Self::Pruned | Self::PrunedAbove)
    }
}