| 15 |
15 |
|
//! and pull colors from it. Apps build one `Theme` per theme load (via
|
| 16 |
16 |
|
//! `makeover::load_theme` + `Theme::from_theme`) and thread it through.
|
| 17 |
17 |
|
|
|
18 |
+ |
use std::sync::OnceLock;
|
|
19 |
+ |
|
| 18 |
20 |
|
use makeover::{Rgb, ThemeColors};
|
|
21 |
+ |
use makeover_tui::Palette;
|
| 19 |
22 |
|
use ratatui::style::Color;
|
| 20 |
23 |
|
|
|
24 |
+ |
/// What the terminal can show, from the family's renderer.
|
|
25 |
+ |
///
|
|
26 |
+ |
/// Re-exported rather than restated. Alloy had its own three-valued `ColorDepth`
|
|
27 |
+ |
/// with its own `COLORTERM`/`TERM` reading, which is one answer too many now
|
|
28 |
+ |
/// that the rendering goes through `makeover-tui`: the quantization below and
|
|
29 |
+ |
/// the glyph fallback over there have to agree about what the terminal is, and
|
|
30 |
+ |
/// two enums agreeing by convention is how they stop agreeing.
|
|
31 |
+ |
pub use makeover_tui::Fidelity;
|
|
32 |
+ |
|
| 21 |
33 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 22 |
34 |
|
pub enum Mode {
|
| 23 |
35 |
|
Light,
|
| 49 |
61 |
|
pub surface_sunken: Color,
|
| 50 |
62 |
|
pub surface_overlay: Color,
|
| 51 |
63 |
|
|
|
64 |
+ |
/// makeover's inset content surface: the surface inside a raised container,
|
|
65 |
+ |
/// so a list reads as content in a container rather than as bands on a panel.
|
|
66 |
+ |
///
|
|
67 |
+ |
/// Not [`surface_sunken`](Theme::surface_sunken), and the distinction is the
|
|
68 |
+ |
/// reason this field exists rather than being aliased onto that one. A theme
|
|
69 |
+ |
/// is free to author sunken *darker* than raised (goingson does) while a well
|
|
70 |
+ |
/// always inverts away from the text, so substituting one for the other lands
|
|
71 |
+ |
/// a well on the wrong side of its face on exactly the themes where it
|
|
72 |
+ |
/// matters. `makeover-tui` deleted that substitution from the description on
|
|
73 |
+ |
/// purpose; reintroducing it here would put it back a layer down.
|
|
74 |
+ |
///
|
|
75 |
+ |
/// `None` where makeover derived nothing, which is a theme that authors no
|
|
76 |
+ |
/// raised surface or no content color. Left missing rather than guessed, per
|
|
77 |
+ |
/// the same rule: [`Palette::fill`] answers a missing well with structure.
|
|
78 |
+ |
pub surface_well: Option<Color>,
|
|
79 |
+ |
|
| 52 |
80 |
|
pub content_primary: Color,
|
| 53 |
81 |
|
pub content_secondary: Color,
|
| 54 |
82 |
|
pub content_muted: Color,
|
| 151 |
179 |
|
surface_sunken: rgb(get("surface.sunken")?),
|
| 152 |
180 |
|
surface_overlay: rgb(get("surface.overlay")?),
|
| 153 |
181 |
|
|
|
182 |
+ |
// Optional where the others are required, because it is derived
|
|
183 |
+ |
// rather than authored: makeover emits it only when the theme gave
|
|
184 |
+ |
// it both a raised surface and a content color to read the direction
|
|
185 |
+ |
// off. Demanding it would reject a theme that is otherwise complete.
|
|
186 |
+ |
surface_well: resolved
|
|
187 |
+ |
.hex("surface-well")
|
|
188 |
+ |
.and_then(Rgb::from_hex)
|
|
189 |
+ |
.map(rgb),
|
|
190 |
+ |
|
| 154 |
191 |
|
content_primary: rgb(content_primary),
|
| 155 |
192 |
|
content_secondary: rgb(get("content.secondary")?),
|
| 156 |
193 |
|
content_muted: rgb(get("content.muted")?),
|
| 185 |
222 |
|
Color::Rgb(c.r, c.g, c.b)
|
| 186 |
223 |
|
}
|
| 187 |
224 |
|
|
| 188 |
|
- |
/// How much color the terminal being drawn to can actually show.
|
| 189 |
|
- |
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 190 |
|
- |
pub enum ColorDepth {
|
| 191 |
|
- |
/// 24-bit. Theme colors are sent as authored.
|
| 192 |
|
- |
Full,
|
| 193 |
|
- |
/// The xterm 256-color table, addressed by index.
|
| 194 |
|
- |
///
|
| 195 |
|
- |
/// Enough to keep a two-tone bevel: both Akari themes put the two edges and
|
| 196 |
|
- |
/// the face they surround on three separate entries here, where sixteen
|
| 197 |
|
- |
/// colors has nothing between a face and its neighbour and one edge lands
|
| 198 |
|
- |
/// back on the face.
|
| 199 |
|
- |
Ansi256,
|
| 200 |
|
- |
/// The sixteen ANSI colors, addressed by index.
|
| 201 |
|
- |
Ansi16,
|
|
225 |
+ |
/// What the terminal can show, read once per process.
|
|
226 |
+ |
///
|
|
227 |
+ |
/// The answer an application wants in both places it is needed: passed to
|
|
228 |
+ |
/// [`Theme::for_terminal`] to quantize the theme, and to [`Theme::palette`] so
|
|
229 |
+ |
/// the renderer knows what the colors it was handed were quantized *to*. Asking
|
|
230 |
+ |
/// once and threading the same value through is what keeps those two consistent;
|
|
231 |
+ |
/// calling [`Fidelity::detect`] twice would too, but nothing enforces that it is
|
|
232 |
+ |
/// the same call, and this is cheaper besides.
|
|
233 |
+ |
///
|
|
234 |
+ |
/// [`Fidelity::detect`] reads two environment variables. They cannot change under
|
|
235 |
+ |
/// a running process in any way that matters, and a bevel is drawn many times a
|
|
236 |
+ |
/// frame, so asking once is both cheaper and more consistent than asking per
|
|
237 |
+ |
/// render.
|
|
238 |
+ |
///
|
|
239 |
+ |
/// Replaces `detect_color_depth`, which answered the same question in Alloy's own
|
|
240 |
+ |
/// vocabulary.
|
|
241 |
+ |
#[must_use]
|
|
242 |
+ |
pub fn fidelity() -> Fidelity {
|
|
243 |
+ |
static DETECTED: OnceLock<Fidelity> = OnceLock::new();
|
|
244 |
+ |
*DETECTED.get_or_init(Fidelity::detect)
|
| 202 |
245 |
|
}
|
| 203 |
246 |
|
|
| 204 |
|
- |
impl ColorDepth {
|
| 205 |
|
- |
/// The palette to quantize into, and what to add to an index in it to get
|
| 206 |
|
- |
/// the number the terminal wants.
|
| 207 |
|
- |
///
|
| 208 |
|
- |
/// 256 resolves to makeover's fixed region rather than the whole table: the
|
| 209 |
|
- |
/// low sixteen are repaintable in every emulator, so a match landing there
|
| 210 |
|
- |
/// is a match against a color the user may have moved out from under it.
|
| 211 |
|
- |
fn palette(self) -> Option<(&'static [makeover::Rgb], usize)> {
|
| 212 |
|
- |
match self {
|
| 213 |
|
- |
ColorDepth::Full => None,
|
| 214 |
|
- |
ColorDepth::Ansi256 => Some((makeover::ANSI_240, makeover::ANSI_240_OFFSET)),
|
| 215 |
|
- |
ColorDepth::Ansi16 => Some((&makeover::ANSI_16, 0)),
|
| 216 |
|
- |
}
|
| 217 |
|
- |
}
|
| 218 |
|
- |
}
|
| 219 |
|
- |
|
| 220 |
|
- |
/// What the environment says the terminal can show.
|
|
247 |
+ |
/// The palette to quantize into, and what to add to an index in it to get the
|
|
248 |
+ |
/// number the terminal wants.
|
| 221 |
249 |
|
///
|
| 222 |
|
- |
/// `COLORTERM` is the only positive signal a terminal gives for 24-bit color,
|
| 223 |
|
- |
/// and `TERM=linux` is the case this exists for: the Linux virtual console,
|
| 224 |
|
- |
/// which is what an installer and a machine with no desktop draw on.
|
| 225 |
|
- |
///
|
| 226 |
|
- |
/// A `TERM` ending in `-256color` and no `COLORTERM` is the terminal saying what
|
| 227 |
|
- |
/// it has. Taking it at its word beats the old behavior of calling it
|
| 228 |
|
- |
/// [`Full`](ColorDepth::Full) and sending 24-bit for it to approximate, because
|
| 229 |
|
- |
/// its approximation is per-color and collapses tones the theme keeps apart,
|
| 230 |
|
- |
/// which is the same failure that cost the console its frame on the VT.
|
| 231 |
|
- |
///
|
| 232 |
|
- |
/// Everything else is assumed to manage 24-bit, which is the safer wrong answer:
|
| 233 |
|
- |
/// guessing [`Full`](ColorDepth::Full) on a limited terminal costs some fidelity,
|
| 234 |
|
- |
/// and guessing [`Ansi16`](ColorDepth::Ansi16) on a capable one throws away color
|
| 235 |
|
- |
/// the user paid for.
|
| 236 |
|
- |
pub fn detect_color_depth() -> ColorDepth {
|
| 237 |
|
- |
depth_from_env(
|
| 238 |
|
- |
&std::env::var("COLORTERM").unwrap_or_default(),
|
| 239 |
|
- |
&std::env::var("TERM").unwrap_or_default(),
|
| 240 |
|
- |
)
|
| 241 |
|
- |
}
|
| 242 |
|
- |
|
| 243 |
|
- |
/// [`detect_color_depth`] with the environment passed in, so the decision can be
|
| 244 |
|
- |
/// tested without mutating a process-wide variable from a parallel test.
|
| 245 |
|
- |
fn depth_from_env(colorterm: &str, term: &str) -> ColorDepth {
|
| 246 |
|
- |
if colorterm == "truecolor" || colorterm == "24bit" {
|
| 247 |
|
- |
return ColorDepth::Full;
|
| 248 |
|
- |
}
|
| 249 |
|
- |
match term {
|
| 250 |
|
- |
"linux" | "vt100" | "vt220" | "ansi" | "dumb" => ColorDepth::Ansi16,
|
| 251 |
|
- |
_ if term.ends_with("-256color") => ColorDepth::Ansi256,
|
| 252 |
|
- |
_ => ColorDepth::Full,
|
|
250 |
+ |
/// 256 resolves to makeover's fixed region rather than the whole table: the low
|
|
251 |
+ |
/// sixteen are repaintable in every emulator, so a match landing there is a
|
|
252 |
+ |
/// match against a color the user may have moved out from under it.
|
|
253 |
+ |
fn palette_for(fidelity: Fidelity) -> Option<(&'static [makeover::Rgb], usize)> {
|
|
254 |
+ |
match fidelity {
|
|
255 |
+ |
Fidelity::TrueColor => None,
|
|
256 |
+ |
Fidelity::Ansi256 => Some((makeover::ANSI_240, makeover::ANSI_240_OFFSET)),
|
|
257 |
+ |
Fidelity::Ansi16 => Some((&makeover::ANSI_16, 0)),
|
| 253 |
258 |
|
}
|
| 254 |
259 |
|
}
|
| 255 |
260 |
|
|
| 291 |
296 |
|
impl Theme {
|
| 292 |
297 |
|
/// This theme as the terminal can actually draw it.
|
| 293 |
298 |
|
///
|
| 294 |
|
- |
/// At [`ColorDepth::Full`] the theme is returned untouched. Otherwise every
|
|
299 |
+ |
/// At [`Fidelity::TrueColor`] the theme is returned untouched. Otherwise every
|
| 295 |
300 |
|
/// color becomes a palette index, which is the point: left as 24-bit, the
|
| 296 |
301 |
|
/// terminal approximates them itself, and its approximation collapses tones
|
| 297 |
302 |
|
/// that the theme keeps apart. Alloy's console lost its frame that way,
|
| 307 |
312 |
|
/// are measured against the raised surface they surround rather than against
|
| 308 |
313 |
|
/// the page, and running them through [`indexed_against`] would push both
|
| 309 |
314 |
|
/// onto the same entry and invert the bevel on one side. At
|
| 310 |
|
- |
/// [`ColorDepth::Ansi16`] the palette cannot hold the pair at all and one
|
|
315 |
+ |
/// [`Fidelity::Ansi16`] the palette cannot hold the pair at all and one
|
| 311 |
316 |
|
/// edge lands back on its face, which is a property of sixteen colors rather
|
| 312 |
|
- |
/// than something this can fix: a caller drawing there should spend the edge
|
| 313 |
|
- |
/// that survives on a single-tone shadow.
|
|
317 |
+ |
/// than something this can fix. A caller drawing there does not have to
|
|
318 |
+ |
/// handle that itself: [`Theme::palette`] carries the fidelity through to
|
|
319 |
+ |
/// `makeover-tui`, which answers it with glyphs instead of tones.
|
| 314 |
320 |
|
#[must_use]
|
| 315 |
|
- |
pub fn for_terminal(self, depth: ColorDepth) -> Theme {
|
| 316 |
|
- |
let Some((palette, offset)) = depth.palette() else {
|
|
321 |
+ |
pub fn for_terminal(self, fidelity: Fidelity) -> Theme {
|
|
322 |
+ |
let Some((palette, offset)) = palette_for(fidelity) else {
|
| 317 |
323 |
|
return self;
|
| 318 |
324 |
|
};
|
| 319 |
325 |
|
|
| 327 |
333 |
|
surface_raised: plain(self.surface_raised),
|
| 328 |
334 |
|
surface_sunken: plain(self.surface_sunken),
|
| 329 |
335 |
|
surface_overlay: plain(self.surface_overlay),
|
|
336 |
+ |
// Plainly, like the other surfaces and for the same reason as the
|
|
337 |
+ |
// bevel pair: a well is measured against the raised face it is cut
|
|
338 |
+ |
// into, not against the page, so quantizing it against the page
|
|
339 |
+ |
// would push it toward contrast it is not supposed to have.
|
|
340 |
+ |
surface_well: self.surface_well.map(plain),
|
| 330 |
341 |
|
|
| 331 |
342 |
|
content_primary: on_page(self.content_primary),
|
| 332 |
343 |
|
content_secondary: on_page(self.content_secondary),
|
| 351 |
362 |
|
}
|
| 352 |
363 |
|
}
|
| 353 |
364 |
|
|
|
365 |
+ |
impl Theme {
|
|
366 |
+ |
/// This theme as `makeover-tui`'s renderer wants it.
|
|
367 |
+ |
///
|
|
368 |
+ |
/// The one place a [`Palette`] is assembled. Every widget that draws through
|
|
369 |
+ |
/// the family renderer asks here rather than filling the struct itself,
|
|
370 |
+ |
/// because two of the fields are decisions rather than lookups — which token
|
|
371 |
+ |
/// serves as the well, and whether `fidelity` matches what the colors were
|
|
372 |
+ |
/// actually quantized to — and a per-widget copy is a per-widget chance to
|
|
373 |
+ |
/// answer them differently.
|
|
374 |
+ |
///
|
|
375 |
+ |
/// `fidelity` has to be the same value passed to [`Theme::for_terminal`].
|
|
376 |
+ |
/// The renderer takes its colors already quantized and cannot recover the
|
|
377 |
+ |
/// depth from them afterwards, so it is told; telling it something else is
|
|
378 |
+ |
/// how a frame ends up drawing a glyph fallback over colors that did not
|
|
379 |
+ |
/// need one, or skipping the fallback over colors that did.
|
|
380 |
+ |
#[must_use]
|
|
381 |
+ |
pub fn palette(&self, fidelity: Fidelity) -> Palette {
|
|
382 |
+ |
Palette {
|
|
383 |
+ |
page: self.surface_page,
|
|
384 |
+ |
raised: self.surface_raised,
|
|
385 |
+ |
overlay: self.surface_overlay,
|
|
386 |
+ |
well: self.surface_well,
|
|
387 |
+ |
bevel_light: self.bevel_light,
|
|
388 |
+ |
bevel_dark: self.bevel_dark,
|
|
389 |
+ |
fidelity,
|
|
390 |
+ |
}
|
|
391 |
+ |
}
|
|
392 |
+ |
}
|
|
393 |
+ |
|
| 354 |
394 |
|
/// Alloy's decorative divider: the authored border pulled toward the page.
|
| 355 |
395 |
|
///
|
| 356 |
396 |
|
/// Public because the console is not the only thing that renders this token.
|
| 475 |
515 |
|
surface_raised: page,
|
| 476 |
516 |
|
surface_sunken: page,
|
| 477 |
517 |
|
surface_overlay: page,
|
|
518 |
+ |
// As makeover derives it from Akari Dawn's real raised surface,
|
|
519 |
+ |
// #ede7de, which this fixture flattens onto the page: the theme's
|
|
520 |
+ |
// text is dark, so the well goes the other way and darkens.
|
|
521 |
+ |
surface_well: Some(Color::Rgb(0xd6, 0xd0, 0xc7)),
|
| 478 |
522 |
|
content_primary: Color::Rgb(0x1a, 0x18, 0x16),
|
| 479 |
523 |
|
content_secondary: Color::Rgb(0x1a, 0x18, 0x16),
|
| 480 |
524 |
|
content_muted: Color::Rgb(0x7f, 0x78, 0x6d),
|
| 497 |
541 |
|
|
| 498 |
542 |
|
#[test]
|
| 499 |
543 |
|
fn a_capable_terminal_gets_the_theme_as_authored() {
|
| 500 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Full);
|
|
544 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::TrueColor);
|
| 501 |
545 |
|
assert_eq!(theme.border_strong, Color::Rgb(0x7f, 0x78, 0x6d));
|
| 502 |
546 |
|
}
|
| 503 |
547 |
|
|
| 505 |
549 |
|
// the approximating to the terminal, which is where the collapse happened.
|
| 506 |
550 |
|
#[test]
|
| 507 |
551 |
|
fn a_sixteen_color_terminal_gets_indices() {
|
| 508 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Ansi16);
|
|
552 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::Ansi16);
|
| 509 |
553 |
|
for color in [
|
| 510 |
554 |
|
theme.surface_page,
|
| 511 |
555 |
|
theme.content_primary,
|
| 521 |
565 |
|
// and the face they surround have to reach three separate entries.
|
| 522 |
566 |
|
#[test]
|
| 523 |
567 |
|
fn a_256_color_terminal_keeps_both_bevel_edges() {
|
| 524 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Ansi256);
|
|
568 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::Ansi256);
|
| 525 |
569 |
|
assert_ne!(theme.bevel_light, theme.surface_raised);
|
| 526 |
570 |
|
assert_ne!(theme.bevel_dark, theme.surface_raised);
|
| 527 |
571 |
|
assert_ne!(theme.bevel_light, theme.bevel_dark);
|
| 532 |
576 |
|
// instead of drawing a bevel that resolves on two sides.
|
| 533 |
577 |
|
#[test]
|
| 534 |
578 |
|
fn a_sixteen_color_terminal_loses_one_bevel_edge() {
|
| 535 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Ansi16);
|
|
579 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::Ansi16);
|
| 536 |
580 |
|
let light_survives = theme.bevel_light != theme.surface_raised;
|
| 537 |
581 |
|
let dark_survives = theme.bevel_dark != theme.surface_raised;
|
| 538 |
582 |
|
assert!(
|
| 546 |
590 |
|
// Forgetting the offset would silently address the repaintable low sixteen.
|
| 547 |
591 |
|
#[test]
|
| 548 |
592 |
|
fn the_256_indices_land_outside_the_repaintable_low_sixteen() {
|
| 549 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Ansi256);
|
|
593 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::Ansi256);
|
| 550 |
594 |
|
for color in [
|
| 551 |
595 |
|
theme.surface_page,
|
| 552 |
596 |
|
theme.content_primary,
|
| 561 |
605 |
|
}
|
| 562 |
606 |
|
}
|
| 563 |
607 |
|
|
|
608 |
+ |
// Detection itself is `makeover-tui`'s and tested there. What is still this
|
|
609 |
+ |
// crate's problem is that the answer it gives is the one this quantization
|
|
610 |
+ |
// was built for, so the two cases with a bug behind them are pinned here as
|
|
611 |
+ |
// well: the VT, whose approximation cost the console its frame, and a
|
|
612 |
+ |
// terminal that named itself nothing in particular, which must not be
|
|
613 |
+ |
// flattened to sixteen colors on no evidence.
|
| 564 |
614 |
|
#[test]
|
| 565 |
|
- |
fn a_256_color_term_is_detected_from_its_name() {
|
| 566 |
|
- |
let depth = depth_from_env;
|
| 567 |
|
- |
assert_eq!(depth("", "xterm-256color"), ColorDepth::Ansi256);
|
| 568 |
|
- |
assert_eq!(depth("", "screen-256color"), ColorDepth::Ansi256);
|
| 569 |
|
- |
// A terminal claiming 24-bit is believed over its name.
|
| 570 |
|
- |
assert_eq!(depth("truecolor", "xterm-256color"), ColorDepth::Full);
|
| 571 |
|
- |
// The VT is still the VT.
|
| 572 |
|
- |
assert_eq!(depth("", "linux"), ColorDepth::Ansi16);
|
| 573 |
|
- |
assert_eq!(depth("", "foot"), ColorDepth::Full);
|
|
615 |
+ |
fn the_fidelity_this_quantizes_for_is_the_one_the_family_detects() {
|
|
616 |
+ |
assert_eq!(Fidelity::from_env("", "linux"), Fidelity::Ansi16);
|
|
617 |
+ |
assert_eq!(Fidelity::from_env("", "foot"), Fidelity::TrueColor);
|
|
618 |
+ |
assert_eq!(Fidelity::from_env("", "xterm-256color"), Fidelity::Ansi256);
|
|
619 |
+ |
}
|
|
620 |
+ |
|
|
621 |
+ |
// Every intent the renderer asks for has to be carried across, or a widget
|
|
622 |
+ |
// drawing through `frame` gets a hole where a surface should be. `well` is
|
|
623 |
+ |
// the one that can legitimately be absent, and it is absent as `None` rather
|
|
624 |
+ |
// than as some other surface standing in for it.
|
|
625 |
+ |
#[test]
|
|
626 |
+ |
fn the_renderer_palette_carries_the_theme_across_unsubstituted() {
|
|
627 |
+ |
let theme = akari_dawn();
|
|
628 |
+ |
let p = theme.palette(Fidelity::Ansi256);
|
|
629 |
+ |
assert_eq!(p.page, theme.surface_page);
|
|
630 |
+ |
assert_eq!(p.raised, theme.surface_raised);
|
|
631 |
+ |
assert_eq!(p.overlay, theme.surface_overlay);
|
|
632 |
+ |
assert_eq!(p.bevel_light, theme.bevel_light);
|
|
633 |
+ |
assert_eq!(p.bevel_dark, theme.bevel_dark);
|
|
634 |
+ |
assert_eq!(p.fidelity, Fidelity::Ansi256);
|
|
635 |
+ |
assert_eq!(p.well, theme.surface_well);
|
|
636 |
+ |
|
|
637 |
+ |
let no_well = Theme {
|
|
638 |
+ |
surface_well: None,
|
|
639 |
+ |
..theme
|
|
640 |
+ |
};
|
|
641 |
+ |
assert_eq!(no_well.palette(Fidelity::TrueColor).well, None);
|
|
642 |
+ |
assert_ne!(
|
|
643 |
+ |
no_well.palette(Fidelity::TrueColor).well,
|
|
644 |
+ |
Some(no_well.surface_sunken)
|
|
645 |
+ |
);
|
|
646 |
+ |
}
|
|
647 |
+ |
|
|
648 |
+ |
// A well is measured against the face it is cut into, so it quantizes
|
|
649 |
+ |
// plainly like the other surfaces. Run through `indexed_against` it would be
|
|
650 |
+ |
// pushed toward contrast with the page, which is the one thing a well is not
|
|
651 |
+ |
// supposed to have.
|
|
652 |
+ |
#[test]
|
|
653 |
+ |
fn a_well_survives_quantization_as_a_surface() {
|
|
654 |
+ |
let theme = Theme {
|
|
655 |
+ |
surface_raised: Color::Rgb(0xed, 0xe7, 0xde),
|
|
656 |
+ |
surface_well: Some(Color::Rgb(0xd6, 0xd0, 0xc7)),
|
|
657 |
+ |
..akari_dawn()
|
|
658 |
+ |
};
|
|
659 |
+ |
let quantized = theme.for_terminal(Fidelity::Ansi256);
|
|
660 |
+ |
let well = quantized.surface_well.expect("a well went missing");
|
|
661 |
+ |
assert!(matches!(well, Color::Indexed(_)), "{well:?}");
|
|
662 |
+ |
assert_ne!(
|
|
663 |
+ |
well, quantized.surface_raised,
|
|
664 |
+ |
"the well collapsed onto its face"
|
|
665 |
+ |
);
|
| 574 |
666 |
|
}
|
| 575 |
667 |
|
|
| 576 |
668 |
|
// The bug, as a test: the installer's frame drew in border_strong on
|
| 577 |
669 |
|
// surface_page and could not be seen.
|
| 578 |
670 |
|
#[test]
|
| 579 |
671 |
|
fn the_frame_stays_visible_against_the_page() {
|
| 580 |
|
- |
let theme = akari_dawn().for_terminal(ColorDepth::Ansi16);
|
|
672 |
+ |
let theme = akari_dawn().for_terminal(Fidelity::Ansi16);
|
| 581 |
673 |
|
assert_ne!(theme.border_strong, theme.surface_page);
|
| 582 |
674 |
|
assert_ne!(theme.border_subtle, theme.surface_page);
|
| 583 |
675 |
|
assert_ne!(theme.content_primary, theme.surface_page);
|