| 599 |
599 |
|
// shipped themes it currently bites.
|
| 600 |
600 |
|
derived.push(("bevel-light".into(), lighten(raised, 0.14)));
|
| 601 |
601 |
|
derived.push(("bevel-dark".into(), darken(raised, 0.18)));
|
|
602 |
+ |
|
|
603 |
+ |
// An inset well: the content surface inside a raised container, so a
|
|
604 |
+ |
// list reads as content in a container rather than as bands on a panel.
|
|
605 |
+ |
// `surface-sunken` cannot serve, because a theme is free to author it
|
|
606 |
+ |
// darker than raised (goingson does) and a well has to go the other way.
|
|
607 |
+ |
//
|
|
608 |
+ |
// Which way is "the other way" depends on the theme, and this is the one
|
|
609 |
+ |
// derivation here that inverts. A well is lighter than its face on a
|
|
610 |
+ |
// light theme and darker on a dark one, where the bevel pair sidesteps
|
|
611 |
+ |
// the question by emitting both directions at once.
|
|
612 |
+ |
//
|
|
613 |
+ |
// Read the direction off `content` rather than off `Variant`. A theme
|
|
614 |
+ |
// whose text is dark is a theme whose surfaces are light, whatever its
|
|
615 |
+ |
// `variant` field claims, so this resolves correctly even when that
|
|
616 |
+ |
// field is wrong and it keeps the branch on measured color rather than
|
|
617 |
+ |
// on metadata.
|
|
618 |
+ |
//
|
|
619 |
+ |
// Deltas are asymmetric for the same reason the bevel's are, and smaller
|
|
620 |
+ |
// than the bevel's because a well is an area rather than an edge. The
|
|
621 |
+ |
// step up is the specimen's, measured: #D9DDF4 to #F3F5FD is 0.069.
|
|
622 |
+ |
//
|
|
623 |
+ |
// A face at the top of its ramp cannot hold a lighter well, the same
|
|
624 |
+ |
// clamp `bevel-light` hits; `well_is_visible_against_its_face` names the
|
|
625 |
+ |
// shipped themes where it bites.
|
|
626 |
+ |
if let Some(content) = get(&intents, "content") {
|
|
627 |
+ |
let content_is_darker = content.to_oklab().l < raised.to_oklab().l;
|
|
628 |
+ |
let well = if content_is_darker {
|
|
629 |
+ |
lighten(raised, 0.07)
|
|
630 |
+ |
} else {
|
|
631 |
+ |
darken(raised, 0.09)
|
|
632 |
+ |
};
|
|
633 |
+ |
derived.push(("surface-well".into(), well));
|
|
634 |
+ |
}
|
| 602 |
635 |
|
}
|
| 603 |
636 |
|
if let Some(sunken) = get(&intents, "surface-sunken") {
|
| 604 |
637 |
|
derived.push(("hover-surface".into(), sunken));
|
| 1593 |
1626 |
|
);
|
| 1594 |
1627 |
|
}
|
| 1595 |
1628 |
|
|
|
1629 |
+ |
// The well inverts by theme, so assert both directions explicitly rather
|
|
1630 |
+ |
// than only the one the light themes happen to take.
|
|
1631 |
+ |
#[test]
|
|
1632 |
+ |
fn resolve_well_intent_follows_the_content_direction() {
|
|
1633 |
+ |
// nord is dark: light text on a dark raised surface, so the well goes
|
|
1634 |
+ |
// down and away from the text.
|
|
1635 |
+ |
let dark = resolve(&parse_theme_str("nord", nord_toml(), false).unwrap());
|
|
1636 |
+ |
let dark_raised = Rgb::from_hex("#3b4252").unwrap();
|
|
1637 |
+ |
assert_eq!(
|
|
1638 |
+ |
dark.hex("surface-well").unwrap(),
|
|
1639 |
+ |
darken(dark_raised, 0.09).to_hex()
|
|
1640 |
+ |
);
|
|
1641 |
+ |
|
|
1642 |
+ |
// The shipped light themes take the other branch.
|
|
1643 |
+ |
let goingson = embedded_themes()
|
|
1644 |
+ |
.into_iter()
|
|
1645 |
+ |
.find(|(id, _)| *id == "goingson")
|
|
1646 |
+ |
.expect("goingson is embedded")
|
|
1647 |
+ |
.1;
|
|
1648 |
+ |
let light = resolve(&parse_theme_str("goingson", goingson, false).unwrap());
|
|
1649 |
+ |
let light_raised = light
|
|
1650 |
+ |
.hex("surface-raised")
|
|
1651 |
+ |
.and_then(Rgb::from_hex)
|
|
1652 |
+ |
.expect("goingson authors a raised surface");
|
|
1653 |
+ |
assert_eq!(
|
|
1654 |
+ |
light.hex("surface-well").unwrap(),
|
|
1655 |
+ |
lighten(light_raised, 0.07).to_hex()
|
|
1656 |
+ |
);
|
|
1657 |
+ |
}
|
|
1658 |
+ |
|
|
1659 |
+ |
// A well is a fill, not an edge, so the only thing that makes it read is
|
|
1660 |
+ |
// being a different color from the surface it is cut into.
|
|
1661 |
+ |
//
|
|
1662 |
+ |
// Same shape and the same asserted-list discipline as
|
|
1663 |
+ |
// `bevel_edges_are_distinct_from_their_face`, and it bites the same two
|
|
1664 |
+ |
// themes for the same reason: a raised surface already at the top of the
|
|
1665 |
+ |
// ramp has nothing lighter to go to.
|
|
1666 |
+ |
#[test]
|
|
1667 |
+ |
fn well_is_distinct_from_its_face() {
|
|
1668 |
+ |
const CANNOT_WELL: &[&str] = &["neobrute", "oxocarbon-light"];
|
|
1669 |
+ |
|
|
1670 |
+ |
let mut degenerate: Vec<String> = Vec::new();
|
|
1671 |
+ |
for (id, source) in embedded_themes() {
|
|
1672 |
+ |
let theme = parse_theme_str(id, source, false).unwrap();
|
|
1673 |
+ |
let t = resolve(&theme);
|
|
1674 |
+ |
let Some(raised) = t.hex("surface-raised") else {
|
|
1675 |
+ |
continue;
|
|
1676 |
+ |
};
|
|
1677 |
+ |
let well = t.hex("surface-well").expect("raised implies surface-well");
|
|
1678 |
+ |
if well == raised {
|
|
1679 |
+ |
degenerate.push(id.to_string());
|
|
1680 |
+ |
}
|
|
1681 |
+ |
}
|
|
1682 |
+ |
degenerate.sort();
|
|
1683 |
+ |
|
|
1684 |
+ |
assert_eq!(
|
|
1685 |
+ |
degenerate, CANNOT_WELL,
|
|
1686 |
+ |
"themes whose raised surface cannot hold a well"
|
|
1687 |
+ |
);
|
|
1688 |
+ |
}
|
|
1689 |
+ |
|
|
1690 |
+ |
// Distinct is not the same as visible. A face near the top of the ramp
|
|
1691 |
+ |
// clamps partway rather than exactly, which yields a well that differs from
|
|
1692 |
+ |
// its face by a hex digit and by nothing the eye can find. `rosepine-dawn`
|
|
1693 |
+ |
// authors raised at L=0.987 and gets 0.009 of the 0.07 it asked for.
|
|
1694 |
+ |
//
|
|
1695 |
+ |
// Worth a separate test from the one above because the fix differs: an
|
|
1696 |
+ |
// exactly-degenerate theme needs its raised surface off the ramp end, while
|
|
1697 |
+ |
// these need it merely lowered. Both fixes are the theme's, not this
|
|
1698 |
+ |
// derivation's, which is why the list is asserted rather than warned about.
|
|
1699 |
+ |
#[test]
|
|
1700 |
+ |
fn well_is_visible_against_its_face() {
|
|
1701 |
+ |
// Below this, the well and its face are the same surface to a reader.
|
|
1702 |
+ |
const MIN_DELTA_L: f32 = 0.02;
|
|
1703 |
+ |
const CANNOT_HOLD_A_VISIBLE_WELL: &[&str] =
|
|
1704 |
+ |
&["neobrute", "oxocarbon-light", "rosepine-dawn"];
|
|
1705 |
+ |
|
|
1706 |
+ |
let mut invisible: Vec<String> = Vec::new();
|
|
1707 |
+ |
for (id, source) in embedded_themes() {
|
|
1708 |
+ |
let theme = parse_theme_str(id, source, false).unwrap();
|
|
1709 |
+ |
let t = resolve(&theme);
|
|
1710 |
+ |
let (Some(raised), Some(well)) = (
|
|
1711 |
+ |
t.hex("surface-raised").and_then(Rgb::from_hex),
|
|
1712 |
+ |
t.hex("surface-well").and_then(Rgb::from_hex),
|
|
1713 |
+ |
) else {
|
|
1714 |
+ |
continue;
|
|
1715 |
+ |
};
|
|
1716 |
+ |
if (well.to_oklab().l - raised.to_oklab().l).abs() < MIN_DELTA_L {
|
|
1717 |
+ |
invisible.push(id.to_string());
|
|
1718 |
+ |
}
|
|
1719 |
+ |
}
|
|
1720 |
+ |
invisible.sort();
|
|
1721 |
+ |
|
|
1722 |
+ |
assert_eq!(
|
|
1723 |
+ |
invisible, CANNOT_HOLD_A_VISIBLE_WELL,
|
|
1724 |
+ |
"themes whose well is too close to its face to read as one"
|
|
1725 |
+ |
);
|
|
1726 |
+ |
}
|
|
1727 |
+ |
|
| 1596 |
1728 |
|
// What the bevel pair does on a sixteen-color terminal, measured across the
|
| 1597 |
1729 |
|
// shipped set rather than assumed. Two results, both load-bearing for a
|
| 1598 |
1730 |
|
// consumer that has to render one there.
|