feat(k-ui): glass look: orb wallpaper + blurred, tinted glass panels (half-res backdrop per theme+size, integer code, frames only copy), soft analytic shadow, top gloss; contrast checked over wallpaper/glass per orb (light accent -> #0a5cb0); partial frames drawn in place; Rect geometry, tree lookups split out; settings/tally 6 MiB
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
//! Contrast as WCAG 2 measures it: the ratio of two colours' relative
|
||||
//! luminance, from 1 (the same) to 21 (black on white). Text needs 4.5,
|
||||
//! other things the eye must find (the focus ring, a toggle's track) 3.
|
||||
//! What text sits on is not one colour: the wallpaper glows with the orbs,
|
||||
//! and glass panels let it through, so each is checked over the plain
|
||||
//! background and over every orb.
|
||||
|
||||
use crate::parse::Error;
|
||||
use crate::{Rgba, Theme};
|
||||
@@ -22,25 +25,48 @@ pub fn ratio(a: Rgba, b: Rgba) -> f32 {
|
||||
const TEXT: f32 = 4.5;
|
||||
const SHAPE: f32 = 3.0;
|
||||
|
||||
/// Every pairing K-UI draws, each with what it must reach.
|
||||
fn pairings(t: &Theme) -> [(&'static str, Rgba, Rgba, f32); 10] {
|
||||
/// Pairings drawn on solid colours, each with what it must reach.
|
||||
fn solid(t: &Theme) -> [(&'static str, Rgba, Rgba, f32); 4] {
|
||||
[
|
||||
("text_primary on background", t.text_primary, t.background, TEXT),
|
||||
("text_primary on surface", t.text_primary, t.surface, TEXT),
|
||||
("text_primary on surface_raised", t.text_primary, t.surface_raised, TEXT),
|
||||
("text_secondary on background", t.text_secondary, t.background, TEXT),
|
||||
("text_secondary on surface", t.text_secondary, t.surface, TEXT),
|
||||
("accent on background", t.accent, t.background, TEXT),
|
||||
("on_accent on accent", t.on_accent, t.accent, TEXT),
|
||||
("on_destructive on destructive", t.on_destructive, t.destructive, TEXT),
|
||||
("accent on surface", t.accent, t.surface, SHAPE),
|
||||
("focus on background", t.focus, t.background, SHAPE),
|
||||
]
|
||||
}
|
||||
|
||||
/// Pairings drawn on the wallpaper (`w`) and on glass over it (`g`).
|
||||
fn over(t: &Theme, w: Rgba, g: Rgba) -> [(&'static str, Rgba, Rgba, f32); 6] {
|
||||
[
|
||||
("text_primary on the wallpaper", t.text_primary, w, TEXT),
|
||||
("text_secondary on the wallpaper", t.text_secondary, w, TEXT),
|
||||
("accent on the wallpaper", t.accent, w, TEXT),
|
||||
("focus on the wallpaper", t.focus, w, SHAPE),
|
||||
("text_primary on glass", t.text_primary, g, TEXT),
|
||||
("text_secondary on glass", t.text_secondary, g, TEXT),
|
||||
]
|
||||
}
|
||||
|
||||
fn mix(a: Rgba, b: Rgba, t: f32) -> Rgba {
|
||||
let m = |x: u8, y: u8| (x as f32 + (y as f32 - x as f32) * t) as u8;
|
||||
Rgba(m(a.0, b.0), m(a.1, b.1), m(a.2, b.2), 255)
|
||||
}
|
||||
|
||||
/// The wallpaper where an orb is strongest.
|
||||
pub fn wallpaper(t: &Theme, orb: Rgba) -> Rgba {
|
||||
mix(t.background, orb, crate::ORB_STRENGTH)
|
||||
}
|
||||
|
||||
/// A glass panel over `under`.
|
||||
pub fn glass(t: &Theme, under: Rgba) -> Rgba {
|
||||
mix(under, t.surface, t.panel_opacity as f32 / 100.0)
|
||||
}
|
||||
|
||||
/// The theme, if everything drawn with it can be read.
|
||||
pub fn check(t: Theme) -> Result<Theme, Error> {
|
||||
for (pair, fg, bg, min) in pairings(&t) {
|
||||
let walls = [t.background].into_iter().chain(t.orbs.iter().map(|o| wallpaper(&t, *o)));
|
||||
let pairs = walls.flat_map(|w| over(&t, w, glass(&t, w))).chain(solid(&t));
|
||||
for (pair, fg, bg, min) in pairs {
|
||||
let r = ratio(fg, bg);
|
||||
if r < min {
|
||||
return Err(Error::Contrast { pair, ratio_x100: (r * 100.0) as u32 });
|
||||
|
||||
@@ -17,7 +17,7 @@ pub mod wire;
|
||||
mod tests;
|
||||
|
||||
pub use accent::with_accent;
|
||||
pub use contrast::{check, ratio};
|
||||
pub use contrast::{check, glass, ratio, wallpaper};
|
||||
pub use parse::{Error, parse};
|
||||
|
||||
/// A colour as the theme names it: straight (not premultiplied) RGBA.
|
||||
@@ -83,6 +83,9 @@ pub enum Weight {
|
||||
SemiBold,
|
||||
}
|
||||
|
||||
/// How strongly the orbs show through the background at their hearts.
|
||||
pub const ORB_STRENGTH: f32 = 0.5;
|
||||
|
||||
/// A theme's name: what Settings lists and a user's choice records.
|
||||
pub const NAME_BYTES: usize = types::name::BYTES;
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ fn a_theme_survives_the_wire_but_its_name() {
|
||||
#[test]
|
||||
fn unreadable_pairings_are_refused() {
|
||||
let grey = DARK.replace("text_secondary = \"#9e9eb8\"", "text_secondary = \"#3a3a4a\"");
|
||||
assert!(matches!(check(parse(&grey).unwrap()), Err(Error::Contrast { pair: "text_secondary on background", .. })));
|
||||
assert!(matches!(check(parse(&grey).unwrap()), Err(Error::Contrast { pair: "text_secondary on the wallpaper", .. })));
|
||||
// An orb too bright for the text over it.
|
||||
let glare = DARK.replace("\"#0f4c5c\"", "\"#f0f0f0\"");
|
||||
assert!(matches!(check(parse(&glare).unwrap()), Err(Error::Contrast { .. })));
|
||||
assert!((ratio(Rgba::rgb(0), Rgba::rgb(0xffffff)) - 21.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user