| 160 |
160 |
|
//! with a width and a priority and could not say that a table is ordered by a
|
| 161 |
161 |
|
//! column, so a described table could draw no caret and offer no reordering.
|
| 162 |
162 |
|
//!
|
| 163 |
|
- |
//! What each of those deliberately leaves out is the address — what pressing a
|
|
163 |
+ |
//! 0.14.0 is two additive members on two `#[non_exhaustive]` enums, released
|
|
164 |
+ |
//! together because publishing twice for that is waste and the cascade below
|
|
165 |
+ |
//! this crate is nine repos.
|
|
166 |
+ |
//!
|
|
167 |
+ |
//! - [`Depth::Overlay`]. The enum could say raised, well, sunken and flat, and
|
|
168 |
+ |
//! could not say that a surface sits *over* the page. Every renderer already
|
|
169 |
+ |
//! had the surface — `makeover-tui`'s `Palette::overlay`,
|
|
170 |
+ |
//! `makeover-immediate`'s `Palette::elevation`, `makeover-webview`'s
|
|
171 |
+ |
//! `--elevation-overlay` — and none of them could be reached from a
|
|
172 |
+ |
//! description. buckets_of_money has 16 modals waiting on it.
|
|
173 |
+ |
//! - [`CellPart`], which is [`RowPart`] for tables. A row's parts have carried
|
|
174 |
+ |
//! their own content intent since 0.2.0, so `.row-actions` inherits rather
|
|
175 |
+ |
//! than taking a text colour; a table cell had no such vocabulary and
|
|
176 |
+ |
//! `makeover-webview` emitted one undifferentiated `.cell`, so a button in a
|
|
177 |
+ |
//! cell was painted as text. The four members are the four things quasi's
|
|
178 |
+ |
//! `Cell` was measured to hold, and the count is in that crate's history
|
|
179 |
+ |
//! rather than assumed here.
|
|
180 |
+ |
//!
|
|
181 |
+ |
//! What each of the 0.12.0 findings deliberately leaves out is the address — what pressing a
|
| 164 |
182 |
|
//! header calls, and where an empty state's "Add your first project" button
|
| 165 |
183 |
|
//! goes. That is the boundary this crate is defined by, and four findings moved
|
| 166 |
184 |
|
//! across it rather than being answered here.
|
| 366 |
384 |
|
/// Recessed and level-with are different claims, and only one of them
|
| 367 |
385 |
|
/// needs a colour.
|
| 368 |
386 |
|
Sunken,
|
|
387 |
+ |
/// A surface sitting *over* the page rather than in it. A modal, a popover,
|
|
388 |
+ |
/// a menu.
|
|
389 |
+ |
///
|
|
390 |
+ |
/// Takes elevation and no bevel: a surface overlaying the page is lifted
|
|
391 |
+ |
/// off it, and a surface in the page is cut into it. That is the same
|
|
392 |
+ |
/// pairing rule the rest of the enum holds, applied to the one case where
|
|
393 |
+ |
/// the separation is not an edge at all — the lift and the scrim behind it
|
|
394 |
+ |
/// are already saying where the surface is.
|
|
395 |
+ |
///
|
|
396 |
+ |
/// Every renderer had the surface before it had this variant.
|
|
397 |
+ |
/// `makeover-tui` carries `Palette::overlay`, `makeover-immediate` gained
|
|
398 |
+ |
/// `Palette::elevation` at 0.10.0, and `makeover-webview` emits
|
|
399 |
+ |
/// `--elevation-overlay`. What was missing was the route from a description
|
|
400 |
+ |
/// to any of them, which is why this is one variant rather than a feature.
|
|
401 |
+ |
Overlay,
|
| 369 |
402 |
|
}
|
| 370 |
403 |
|
|
| 371 |
404 |
|
impl Depth {
|
| 377 |
410 |
|
// because nothing separates it from its surroundings, and Sunken has
|
| 378 |
411 |
|
// none because its colour is already doing the separating.
|
| 379 |
412 |
|
Self::Flat | Self::Sunken => None,
|
|
413 |
+ |
// A third reason to have no edge, which is why it gets its own arm
|
|
414 |
+ |
// rather than joining the two above: an overlay is separated by the
|
|
415 |
+ |
// lift and by the scrim behind it, so an edge would be a second
|
|
416 |
+ |
// answer to a question already answered.
|
|
417 |
+ |
Self::Overlay => None,
|
| 380 |
418 |
|
Self::Raised => Some(Bevel::Raised),
|
| 381 |
419 |
|
Self::Well => Some(Bevel::Inset),
|
| 382 |
420 |
|
}
|
| 393 |
431 |
|
Self::Raised => Some(Fill::Raised),
|
| 394 |
432 |
|
Self::Well => Some(Fill::Well),
|
| 395 |
433 |
|
Self::Sunken => Some(Fill::Sunken),
|
|
434 |
+ |
Self::Overlay => Some(Fill::Overlay),
|
| 396 |
435 |
|
}
|
| 397 |
436 |
|
}
|
| 398 |
437 |
|
|
| 399 |
438 |
|
/// Pressing a raised region reads as a well, and nothing else moves.
|
|
439 |
+ |
///
|
|
440 |
+ |
/// [`Depth::Overlay`] is untouched along with the rest: an overlay is a
|
|
441 |
+ |
/// surface, not a control, so there is nothing there to press.
|
| 400 |
442 |
|
#[must_use]
|
| 401 |
443 |
|
pub const fn pressed(self) -> Self {
|
| 402 |
444 |
|
match self {
|
| 1657 |
1699 |
|
}
|
| 1658 |
1700 |
|
}
|
| 1659 |
1701 |
|
|
|
1702 |
+ |
/// What a table cell holds.
|
|
1703 |
+ |
///
|
|
1704 |
+ |
/// [`RowPart`] for tables, and it exists for the same reason: a part that
|
|
1705 |
+ |
/// carries a control is not text, and a renderer with one class for the whole
|
|
1706 |
+ |
/// cell paints it as though it were. `makeover-webview` emitted a single
|
|
1707 |
+ |
/// `.cell` until 0.25.0, so a button in a cell inherited the cell's content
|
|
1708 |
+ |
/// colour, which is the exact drift [`RowPart::intent`] prevents for rows and
|
|
1709 |
+ |
/// prevented for nothing here.
|
|
1710 |
+ |
///
|
|
1711 |
+ |
/// Four members, and the count is what quasi's `Cell` was measured to carry:
|
|
1712 |
+ |
/// a value, tokens (33 cells across 22 server templates), actions (30 rows
|
|
1713 |
+ |
/// carrying a control, 5 beside a value) and a link (35 cells across 18
|
|
1714 |
+ |
/// templates). Nothing was added past what something holds.
|
|
1715 |
+ |
///
|
|
1716 |
+ |
/// `#[non_exhaustive]` for [`RowPart`]'s reason: growth here must not be a
|
|
1717 |
+ |
/// lockstep event across three renderers.
|
|
1718 |
+ |
///
|
|
1719 |
+ |
/// # No hover-reveal
|
|
1720 |
+ |
///
|
|
1721 |
+ |
/// [`RowPart`] carried a `revealed_on_hover` until 0.13.0 retired it, and this
|
|
1722 |
+ |
/// enum never gets one. A cell's actions are shown at rest in every consumer
|
|
1723 |
+ |
/// measured, and a member nothing uses is one three renderers owe an answer
|
|
1724 |
+ |
/// for.
|
|
1725 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
1726 |
+ |
#[non_exhaustive]
|
|
1727 |
+ |
pub enum CellPart {
|
|
1728 |
+ |
/// The cell's own text.
|
|
1729 |
+ |
Value,
|
|
1730 |
+ |
/// Small labelled things in the cell: a status badge, a chip.
|
|
1731 |
+ |
Tokens,
|
|
1732 |
+ |
/// Controls that act on what the row is about.
|
|
1733 |
+ |
Actions,
|
|
1734 |
+ |
/// The cell's value, where the value is itself a link.
|
|
1735 |
+ |
Link,
|
|
1736 |
+ |
}
|
|
1737 |
+ |
|
|
1738 |
+ |
impl CellPart {
|
|
1739 |
+ |
/// The content intent the part takes.
|
|
1740 |
+ |
///
|
|
1741 |
+ |
/// One part is text and three are not, so three answer with the intent
|
|
1742 |
+ |
/// inheriting already gives. That is [`RowPart::intent`]'s shape with the
|
|
1743 |
+ |
/// text side narrower: a cell's secondary and muted readings are the
|
|
1744 |
+ |
/// column's business, not the cell's.
|
|
1745 |
+ |
#[must_use]
|
|
1746 |
+ |
pub const fn intent(self) -> &'static str {
|
|
1747 |
+ |
match self {
|
|
1748 |
+ |
Self::Value => "content",
|
|
1749 |
+ |
// A token carries its own tone, and a part-level intent underneath
|
|
1750 |
+ |
// it would fight the token sitting on it.
|
|
1751 |
+ |
Self::Tokens => "content",
|
|
1752 |
+ |
// Actions carry controls rather than text.
|
|
1753 |
+ |
Self::Actions => "content",
|
|
1754 |
+ |
// A link takes the action colour from the control it is, rather
|
|
1755 |
+ |
// than the cell's text colour from the cell it sits in.
|
|
1756 |
+ |
Self::Link => "content",
|
|
1757 |
+ |
}
|
|
1758 |
+ |
}
|
|
1759 |
+ |
}
|
|
1760 |
+ |
|
| 1660 |
1761 |
|
#[cfg(test)]
|
| 1661 |
1762 |
|
mod tests {
|
| 1662 |
1763 |
|
use super::*;
|
| 1963 |
2064 |
|
// Only raised regions respond to being pressed.
|
| 1964 |
2065 |
|
assert_eq!(Depth::Flat.pressed(), Depth::Flat);
|
| 1965 |
2066 |
|
assert_eq!(Depth::Well.pressed(), Depth::Well);
|
|
2067 |
+ |
// An overlay is a surface, not a control.
|
|
2068 |
+ |
assert_eq!(Depth::Overlay.pressed(), Depth::Overlay);
|
|
2069 |
+ |
}
|
|
2070 |
+ |
|
|
2071 |
+ |
#[test]
|
|
2072 |
+ |
fn an_overlay_is_lifted_rather_than_edged() {
|
|
2073 |
+ |
// The wave-2 rule: a surface over the page takes elevation, a surface
|
|
2074 |
+ |
// in the page takes a bevel. Both halves come off the one Depth, so
|
|
2075 |
+ |
// they cannot disagree.
|
|
2076 |
+ |
assert_eq!(Depth::Overlay.fill(), Some(Fill::Overlay));
|
|
2077 |
+ |
assert_eq!(Depth::Overlay.bevel(), None);
|
|
2078 |
+ |
|
|
2079 |
+ |
// Three depths have no bevel and they are not the same claim. Flat has
|
|
2080 |
+ |
// nothing to separate from, Sunken's colour is doing the separating,
|
|
2081 |
+ |
// and an overlay is separated by the lift.
|
|
2082 |
+ |
assert_ne!(Depth::Overlay.fill(), Depth::Sunken.fill());
|
|
2083 |
+ |
assert_ne!(Depth::Overlay.fill(), Depth::Flat.fill());
|
| 1966 |
2084 |
|
}
|
| 1967 |
2085 |
|
|
| 1968 |
2086 |
|
#[test]
|
| 2034 |
2152 |
|
assert_eq!(RowPart::Tokens.intent(), "content");
|
| 2035 |
2153 |
|
}
|
| 2036 |
2154 |
|
|
|
2155 |
+ |
#[test]
|
|
2156 |
+ |
fn a_cell_part_names_an_intent_and_only_the_value_is_text() {
|
|
2157 |
+ |
// The table half of what RowPart::intent does for rows. A cell holding
|
|
2158 |
+ |
// a control and a cell holding text answered alike until 0.14.0, and a
|
|
2159 |
+ |
// control in a cell took the cell's text colour.
|
|
2160 |
+ |
assert_eq!(CellPart::Value.intent(), "content");
|
|
2161 |
+ |
|
|
2162 |
+ |
for part in [CellPart::Tokens, CellPart::Actions, CellPart::Link] {
|
|
2163 |
+ |
// Each for its own reason -- a token carries its tone, an action is
|
|
2164 |
+ |
// a control, a link takes the action colour -- and all three reach
|
|
2165 |
+ |
// the intent inheriting already gives.
|
|
2166 |
+ |
assert_eq!(part.intent(), CellPart::Value.intent(), "{part:?}");
|
|
2167 |
+ |
}
|
|
2168 |
+ |
}
|
|
2169 |
+ |
|
|
2170 |
+ |
#[test]
|
|
2171 |
+ |
fn every_cell_part_answers_with_a_token_and_never_a_value() {
|
|
2172 |
+ |
for part in [
|
|
2173 |
+ |
CellPart::Value,
|
|
2174 |
+ |
CellPart::Tokens,
|
|
2175 |
+ |
CellPart::Actions,
|
|
2176 |
+ |
CellPart::Link,
|
|
2177 |
+ |
] {
|
|
2178 |
+ |
let intent = part.intent();
|
|
2179 |
+ |
assert!(!intent.is_empty(), "{part:?} names nothing");
|
|
2180 |
+ |
assert!(!intent.starts_with('#'), "{part:?} looks like a value");
|
|
2181 |
+ |
}
|
|
2182 |
+ |
}
|
|
2183 |
+ |
|
| 2037 |
2184 |
|
#[test]
|
| 2038 |
2185 |
|
fn a_separator_is_what_tells_a_section_from_a_subsection() {
|
| 2039 |
2186 |
|
assert!(Heading::Section.separated());
|