| 81 |
81 |
|
//! DOM, an immediate-mode renderer holds a `&mut` to the app's own field, and
|
| 82 |
82 |
|
//! a description that carried it would be a form model.
|
| 83 |
83 |
|
//!
|
|
84 |
+ |
//! 0.9.0 opens [`RowPart`], which was the last closed enum in the vocabulary,
|
|
85 |
+ |
//! and adds [`RowPart::Tokens`]. Both halves come from the same finding, made
|
|
86 |
+ |
//! by the first two real screens described through the router rather than by
|
|
87 |
+ |
//! reading a stylesheet.
|
|
88 |
+ |
//!
|
|
89 |
+ |
//! A goingson project card carries two trailing badges, a type and a toned
|
|
90 |
+ |
//! status; a contact card carries a primary email *and* a strip of tags. `Meta`
|
|
91 |
+ |
//! is one slot and one string, so both ports joined their facts with a
|
|
92 |
+ |
//! separator and lost what the second one was: a status reads as text where it
|
|
93 |
+ |
//! used to read as colour. [`Token`] already says exactly the right thing — a
|
|
94 |
+ |
//! small labelled thing with a kind, a tone and an optional action — and could
|
|
95 |
+ |
//! only ever be a node in its own right, never inside a row.
|
|
96 |
+ |
//!
|
|
97 |
+ |
//! So the missing thing was permission rather than a concept. `Tokens` is that
|
|
98 |
+ |
//! permission, and `#[non_exhaustive]` arrives with it so the next member is not
|
|
99 |
+ |
//! a lockstep event across three renderers. The pairing is the point: this
|
|
100 |
+ |
//! enum's own consumer in `makeover-webview` carried a comment predicting it
|
|
101 |
+ |
//! would stop compiling one day, which is a lockstep break written down and
|
|
102 |
+ |
//! waited for rather than prevented.
|
|
103 |
+ |
//!
|
|
104 |
+ |
//! Balanced Breakfast was checked before the member was added, because one
|
|
105 |
+ |
//! consumer wanting something is not evidence. It packs a count and two icon
|
|
106 |
+ |
//! buttons into the same single `Meta` slot while leaving `Actions` empty, so
|
|
107 |
+ |
//! the slot was already straining under a second consumer for a different
|
|
108 |
+ |
//! reason.
|
|
109 |
+ |
//!
|
| 84 |
110 |
|
//! # Where the description stops
|
| 85 |
111 |
|
//!
|
| 86 |
112 |
|
//! The bespoke widgets, a day-plan timeline and a kanban board and a calendar,
|
| 543 |
569 |
|
|
| 544 |
570 |
|
/// The parts of a list row.
|
| 545 |
571 |
|
///
|
| 546 |
|
- |
/// Four, taken from Balanced Breakfast, which is the only consumer that had all
|
| 547 |
|
- |
/// of them (`row-primary`, `row-secondary`, `row-meta`, `row-actions`).
|
| 548 |
|
- |
/// audiofiles has two and no slot structure at all, so it gains meta and
|
| 549 |
|
- |
/// actions as real work rather than a rename; goingson moves off
|
|
572 |
+ |
/// Four to begin with, taken from Balanced Breakfast, which was the only
|
|
573 |
+ |
/// consumer that had all of them (`row-primary`, `row-secondary`, `row-meta`,
|
|
574 |
+ |
/// `row-actions`). audiofiles has two and no slot structure at all, so it gains
|
|
575 |
+ |
/// meta and actions as real work rather than a rename; goingson moves off
|
| 550 |
576 |
|
/// `task-row` / `task-cell`.
|
|
577 |
+ |
///
|
|
578 |
+ |
/// [`Tokens`](Self::Tokens) joined at 0.9.0, and `#[non_exhaustive]` with it.
|
|
579 |
+ |
/// See the crate header for why the two arrived together.
|
|
580 |
+ |
///
|
|
581 |
+ |
/// # Meta against Tokens
|
|
582 |
+ |
///
|
|
583 |
+ |
/// The line is whether the thing has its own standing. `Meta` is one short
|
|
584 |
+ |
/// trailing fact about the row, written as text: a count, a size, a date.
|
|
585 |
+ |
/// `Tokens` is a set of small labelled things, each of which can be toned and
|
|
586 |
+ |
/// can answer a click. "3 files" is meta. A status badge that is amber, and a
|
|
587 |
+ |
/// tag you can click to filter by, are tokens.
|
|
588 |
+ |
///
|
|
589 |
+ |
/// Keeping them apart is what a single widened slot would have foreclosed. A
|
|
590 |
+ |
/// renderer can right-align one string and cannot usefully do the same to a
|
|
591 |
+ |
/// strip of chips, and a fact that is not clickable should not be drawn as
|
|
592 |
+ |
/// though it were.
|
| 551 |
593 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
594 |
+ |
#[non_exhaustive]
|
| 552 |
595 |
|
pub enum RowPart {
|
| 553 |
596 |
|
/// The thing itself. What the row is called.
|
| 554 |
597 |
|
Primary,
|
| 558 |
601 |
|
Meta,
|
| 559 |
602 |
|
/// Controls that act on this row.
|
| 560 |
603 |
|
Actions,
|
|
604 |
+ |
/// Small labelled things belonging to the row: badges, chips, tags.
|
|
605 |
+ |
///
|
|
606 |
+ |
/// Each carries its own [`Token`] kind and [`Tone`], so a renderer with no
|
|
607 |
+ |
/// colour still has the kind to work with, and one with no chips still has
|
|
608 |
+ |
/// the label. That is the constrained-consumer test this vocabulary exists
|
|
609 |
+ |
/// to pass, and it is why the tone lives on the token rather than on the
|
|
610 |
+ |
/// part.
|
|
611 |
+ |
Tokens,
|
| 561 |
612 |
|
}
|
| 562 |
613 |
|
|
| 563 |
614 |
|
impl RowPart {
|
| 583 |
634 |
|
Self::Meta => "content-muted",
|
| 584 |
635 |
|
// Actions carry controls rather than text, so they inherit.
|
| 585 |
636 |
|
Self::Actions => "content",
|
|
637 |
+ |
// So do tokens: each one carries its own tone, and a part-level
|
|
638 |
+ |
// intent underneath it would fight the token that sits on it.
|
|
639 |
+ |
Self::Tokens => "content",
|
| 586 |
640 |
|
}
|
| 587 |
641 |
|
}
|
| 588 |
642 |
|
}
|
| 1323 |
1377 |
|
|
| 1324 |
1378 |
|
#[test]
|
| 1325 |
1379 |
|
fn only_the_actions_part_hides_until_hovered() {
|
| 1326 |
|
- |
for p in [RowPart::Primary, RowPart::Secondary, RowPart::Meta] {
|
|
1380 |
+ |
for p in [
|
|
1381 |
+ |
RowPart::Primary,
|
|
1382 |
+ |
RowPart::Secondary,
|
|
1383 |
+ |
RowPart::Meta,
|
|
1384 |
+ |
RowPart::Tokens,
|
|
1385 |
+ |
] {
|
| 1327 |
1386 |
|
assert!(!p.revealed_on_hover(), "{p:?} should always be visible");
|
| 1328 |
1387 |
|
}
|
| 1329 |
1388 |
|
assert!(RowPart::Actions.revealed_on_hover());
|
| 1333 |
1392 |
|
assert_eq!(RowPart::Meta.intent(), "content-muted");
|
| 1334 |
1393 |
|
}
|
| 1335 |
1394 |
|
|
|
1395 |
+ |
#[test]
|
|
1396 |
+ |
fn a_token_part_carries_no_intent_of_its_own() {
|
|
1397 |
+ |
// Each token carries its own tone, so a part-level intent underneath
|
|
1398 |
+ |
// would fight the thing sitting on it. Same reasoning as actions, which
|
|
1399 |
+ |
// is why they answer alike.
|
|
1400 |
+ |
assert_eq!(RowPart::Tokens.intent(), RowPart::Actions.intent());
|
|
1401 |
+ |
assert_eq!(RowPart::Tokens.intent(), "content");
|
|
1402 |
+ |
}
|
|
1403 |
+ |
|
| 1336 |
1404 |
|
#[test]
|
| 1337 |
1405 |
|
fn a_separator_is_what_tells_a_section_from_a_subsection() {
|
| 1338 |
1406 |
|
assert!(Heading::Section.separated());
|