refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
Re-sincroniza las fuentes desde el monorepo (estaba en vello 0.5/wgpu 24 y con la estructura vieja de eventloop) y suma el 3D: - bump del workspace a vello 0.7 / wgpu 27 / parley 0.6, + accesskit 0.24 / accesskit_winit 0.33 / vello_hybrid 0.0.9. - nuevos crates: llimphi-3d (voxels ray-march + mallas en un depth compartido, montable dentro de un View 2D vía set_viewport+scissor) y llimphi-voxel (world-gen, personajes, director de escenas) + shared/foreign-vox (puente .vox). - README: sección "Not just 2D — a 3D voxel engine" + GIF (docs/llimphi_voxel.gif). - excluido modules/allichay (arrastra deps fuera del alcance del front-door). - cargo check --workspace: verde. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+325
-13
@@ -20,6 +20,37 @@ pub use llimphi_raster::peniko::Color;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
// =====================================================================
|
||||
// Color estable por semilla — avatares, etiquetas, hash-coloring
|
||||
// =====================================================================
|
||||
|
||||
/// Paleta sobria de 8 tonos para colorear entidades por hash (avatares de
|
||||
/// contactos, etiquetas de calendario…). Tonos apagados que conviven con
|
||||
/// cualquier `Theme`. Usada vía [`stable_color`].
|
||||
pub const ENTITY_PALETTE: [(u8, u8, u8); 8] = [
|
||||
(94, 129, 172), // azul acero
|
||||
(163, 109, 156), // malva
|
||||
(122, 162, 110), // verde salvia
|
||||
(191, 138, 92), // terracota
|
||||
(108, 153, 168), // celeste apagado
|
||||
(170, 120, 120), // rosa viejo
|
||||
(130, 140, 175), // lavanda
|
||||
(150, 150, 110), // oliva
|
||||
];
|
||||
|
||||
/// Color estable derivado de una semilla: hash FNV-1a del texto → índice en
|
||||
/// [`ENTITY_PALETTE`]. La misma semilla da siempre el mismo color, sin estado.
|
||||
/// Para avatares (por correo), etiquetas, badges de entidad, etc.
|
||||
pub fn stable_color(seed: &str) -> Color {
|
||||
let mut h: u32 = 2_166_136_261;
|
||||
for b in seed.bytes() {
|
||||
h ^= b as u32;
|
||||
h = h.wrapping_mul(16_777_619);
|
||||
}
|
||||
let (r, g, b) = ENTITY_PALETTE[(h as usize) % ENTITY_PALETTE.len()];
|
||||
Color::from_rgba8(r, g, b, 255)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Tokens transversales — motion, alpha, radius
|
||||
// =====================================================================
|
||||
@@ -32,16 +63,21 @@ use std::time::Duration;
|
||||
// future variante por preset lo requiera.
|
||||
|
||||
/// Duraciones canónicas (segundo nivel: rítmico, no nervioso, no
|
||||
/// soporífero). Los widgets eligen `FAST` para microinteracciones
|
||||
/// (hover, focus), `NORMAL` para transiciones principales (toast entrar,
|
||||
/// modal abrir) y `SLOW` para énfasis o entradas dramáticas (splash de
|
||||
/// boot).
|
||||
/// soporífero). Los widgets eligen `MICRO` para tintes de hover/focus
|
||||
/// que sólo necesitan suavizar el "salto", `FAST` para microinteracciones
|
||||
/// completas (chip que pulsa), `NORMAL` para transiciones principales
|
||||
/// (toast entrar, modal abrir), `SLOW` para énfasis o entradas dramáticas
|
||||
/// (splash de boot, hero shared-element).
|
||||
pub mod motion {
|
||||
use super::Duration;
|
||||
|
||||
/// Tintes hover/focus — apenas perceptible, sólo elimina el "clack".
|
||||
pub const MICRO: Duration = Duration::from_millis(50);
|
||||
pub const FAST: Duration = Duration::from_millis(80);
|
||||
pub const NORMAL: Duration = Duration::from_millis(160);
|
||||
pub const SLOW: Duration = Duration::from_millis(320);
|
||||
/// Entradas dramáticas (splash, hero shared-element).
|
||||
pub const DRAMATIC: Duration = Duration::from_millis(480);
|
||||
|
||||
/// Easing estándar — cubic-out. Energía inicial, asentamiento suave.
|
||||
/// La gran mayoría de transiciones de salida / aparición.
|
||||
@@ -64,6 +100,29 @@ pub mod motion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Easing fuerte — quint-out. Arranca más rápido que cubic-out y
|
||||
/// asienta más suave. Para elementos que aparecen "lanzados" (toast,
|
||||
/// FAB).
|
||||
#[inline]
|
||||
pub fn ease_out_quint(t: f32) -> f32 {
|
||||
let inv = 1.0 - t.clamp(0.0, 1.0);
|
||||
1.0 - inv * inv * inv * inv * inv
|
||||
}
|
||||
|
||||
/// Overshoot suave — back-out con `c1=1.70158` (Material/Penner
|
||||
/// estándar). El valor pasa de 0 al objetivo, lo sobrepasa ~10 % y
|
||||
/// vuelve. Para entradas que necesitan "ping" (modal, snackbar,
|
||||
/// elemento nuevo en una lista). No usar para hover — la oscilación
|
||||
/// se percibe nerviosa.
|
||||
#[inline]
|
||||
pub fn ease_out_back(t: f32) -> f32 {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
const C1: f32 = 1.701_58;
|
||||
const C3: f32 = C1 + 1.0;
|
||||
let u = t - 1.0;
|
||||
1.0 + C3 * u * u * u + C1 * u * u
|
||||
}
|
||||
|
||||
/// Lineal — no es elegante pero a veces es lo correcto (barra de
|
||||
/// progreso, valores numéricos crudos).
|
||||
#[inline]
|
||||
@@ -72,6 +131,29 @@ pub mod motion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokens de **elevación** — sombras escalonadas. Como `Shadow` vive en
|
||||
/// `llimphi-compositor` (y `llimphi-theme` no depende de él para
|
||||
/// quedarse leaf), cada nivel se expone como `(alpha_u8, blur_px,
|
||||
/// dy_px)`. Los widgets construyen su `Shadow` puenteándolo:
|
||||
/// `Shadow { color: Color::from_rgba8(0,0,0, a), blur, dy, dx: 0.0, spread: 0.0 }`.
|
||||
/// Escala perceptual logarítmica: cada nivel ~×2 de blur.
|
||||
pub mod elevation {
|
||||
/// `(alpha 0–255, blur px, dy px)`. dy ≈ blur·0.4 (sombra natural,
|
||||
/// fuente de luz un poco arriba).
|
||||
pub type Elev = (u8, f64, f64);
|
||||
|
||||
/// E1 — chip levantado del fondo (hover button, badge).
|
||||
pub const E1: Elev = (44, 4.0, 1.5);
|
||||
/// E2 — card/tile flotante sobre el panel (default cards).
|
||||
pub const E2: Elev = (60, 10.0, 4.0);
|
||||
/// E3 — superficie destacada (menú contextual, dropdown).
|
||||
pub const E3: Elev = (84, 18.0, 8.0);
|
||||
/// E4 — overlay sobre la app (modal, dialog).
|
||||
pub const E4: Elev = (110, 32.0, 14.0);
|
||||
/// E5 — sello de identidad (FAB, hero, picker activo).
|
||||
pub const E5: Elev = (140, 48.0, 22.0);
|
||||
}
|
||||
|
||||
/// Valores de opacidad alfa (0–255) para capas semánticas. Usar siempre
|
||||
/// que se quiera *transparencia coherente*. El widget que improvisa su
|
||||
/// propio alpha rompe la firma visual.
|
||||
@@ -177,6 +259,48 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tema **"Tawa"** — el LOOK FIRMA de la suite, el que se publica en
|
||||
/// screenshots. Decisiones de paleta:
|
||||
///
|
||||
/// - **Base negro cálido, no azul marino.** A diferencia de `dark()` (un
|
||||
/// navy genérico, R<B) acá el fondo es un casi-negro con temperatura:
|
||||
/// los canales rojo/verde van un punto por encima del azul, así el grafito
|
||||
/// "respira" tibio en vez de frío. Jerarquía de superficies en escalera
|
||||
/// suave: `bg_app` (más profundo) → `bg_panel`/`bg_input` → barras → chips,
|
||||
/// sin saltos bruscos.
|
||||
/// - **Acento teal-eléctrico (#2BD9A6), NO azul.** El mar de unixporn es
|
||||
/// todo azul (Catppuccin/Tokyo Night); elegimos un verde-aguamarina
|
||||
/// vibrante para destacar de inmediato — distintivo pero no chillón, con
|
||||
/// raíz en el teal del CDE / del logo de la suite. `accent` y
|
||||
/// `border_focus` comparten ese tono; la selección lo usa atenuado para
|
||||
/// no encandilar filas enteras.
|
||||
/// - **Texto legible (WCAG AA).** `fg_text` (#E8E6E0, marfil cálido) supera
|
||||
/// 12:1 sobre `bg_app` y ~10:1 sobre `bg_panel`; `fg_muted` ronda 5:1
|
||||
/// (texto secundario cómodo); `fg_placeholder` queda como hint tenue.
|
||||
/// - **Armonía:** todos los grises llevan el mismo tinte cálido y el acento
|
||||
/// es el único color saturado — la paleta se lee como una sola pieza.
|
||||
pub const fn tawa() -> Self {
|
||||
Self {
|
||||
name: "Tawa",
|
||||
bg_app: Color::from_rgba8(20, 19, 17, 255), // grafito cálido casi-negro
|
||||
bg_panel: Color::from_rgba8(30, 28, 26, 255),
|
||||
bg_panel_alt: Color::from_rgba8(25, 24, 22, 255),
|
||||
bg_input: Color::from_rgba8(24, 23, 21, 255),
|
||||
bg_input_focus: Color::from_rgba8(32, 30, 28, 255),
|
||||
bg_button: Color::from_rgba8(42, 40, 37, 255),
|
||||
bg_button_hover: Color::from_rgba8(56, 53, 49, 255),
|
||||
bg_selected: Color::from_rgba8(26, 74, 64, 255), // teal hundido (selección)
|
||||
bg_row_hover: Color::from_rgba8(40, 38, 35, 255),
|
||||
fg_text: Color::from_rgba8(232, 230, 224, 255), // marfil cálido
|
||||
fg_muted: Color::from_rgba8(160, 154, 144, 255),
|
||||
fg_placeholder: Color::from_rgba8(112, 107, 99, 255),
|
||||
fg_destructive: Color::from_rgba8(232, 116, 97, 255), // coral (cálido, no rojo puro)
|
||||
border: Color::from_rgba8(54, 51, 47, 255),
|
||||
border_focus: Color::from_rgba8(43, 217, 166, 255), // teal-eléctrico
|
||||
accent: Color::from_rgba8(43, 217, 166, 255), // #2BD9A6 — la firma
|
||||
}
|
||||
}
|
||||
|
||||
/// Tema claro — contraste revisado para WCAG AA sobre `bg_app`:
|
||||
/// `fg_text` ~12:1, `fg_muted` ~5.4:1 (texto secundario legible),
|
||||
/// `fg_destructive` y `accent` oscurecidos para superar 4.5:1 sobre
|
||||
@@ -278,18 +402,179 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
/// Todos los presets del repo, en el orden canónico de rotación
|
||||
/// (Dark → Light → Aurora → Sunset → Dark…). El theme-switcher
|
||||
/// los consume vía [`Theme::next_after`]. `print()` queda fuera de la
|
||||
/// rotación a propósito — es un modo deliberado (imprimir), no un
|
||||
/// gusto estético que se cicle por accidente.
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![Self::dark(), Self::light(), Self::aurora(), Self::sunset()]
|
||||
/// Skin **Windows XP "Luna"** — escritorio azul-gris claro, selección y
|
||||
/// acento en el azul XP (#316AC5), chrome celeste. Para la vista `windows-xp`.
|
||||
pub const fn xp_blue() -> Self {
|
||||
Self {
|
||||
name: "WinXP",
|
||||
bg_app: Color::from_rgba8(236, 240, 249, 255),
|
||||
bg_panel: Color::from_rgba8(214, 223, 247, 255),
|
||||
bg_panel_alt: Color::from_rgba8(60, 100, 190, 255), // franja azul (taskbar)
|
||||
bg_input: Color::from_rgba8(255, 255, 255, 255),
|
||||
bg_input_focus: Color::from_rgba8(248, 250, 255, 255),
|
||||
bg_button: Color::from_rgba8(222, 230, 246, 255),
|
||||
bg_button_hover: Color::from_rgba8(198, 214, 244, 255),
|
||||
bg_selected: Color::from_rgba8(49, 106, 197, 255), // azul de selección XP
|
||||
bg_row_hover: Color::from_rgba8(214, 226, 248, 255),
|
||||
fg_text: Color::from_rgba8(20, 30, 50, 255),
|
||||
fg_muted: Color::from_rgba8(78, 92, 120, 255),
|
||||
fg_placeholder: Color::from_rgba8(130, 142, 168, 255),
|
||||
fg_destructive: Color::from_rgba8(176, 32, 32, 255),
|
||||
border: Color::from_rgba8(122, 152, 206, 255),
|
||||
border_focus: Color::from_rgba8(49, 106, 197, 255),
|
||||
accent: Color::from_rgba8(36, 94, 220, 255), // Luna blue
|
||||
}
|
||||
}
|
||||
|
||||
/// Busca un preset por nombre exacto.
|
||||
/// Skin **macOS (Big Sur claro)** — casi blanco, grises sutiles, acento
|
||||
/// azul de sistema (#0A84FF). Para la vista `mac`.
|
||||
pub const fn mac_light() -> Self {
|
||||
Self {
|
||||
name: "macOS",
|
||||
bg_app: Color::from_rgba8(246, 246, 248, 255),
|
||||
bg_panel: Color::from_rgba8(236, 236, 240, 255),
|
||||
bg_panel_alt: Color::from_rgba8(242, 242, 245, 235), // menubar translúcida
|
||||
bg_input: Color::from_rgba8(255, 255, 255, 255),
|
||||
bg_input_focus: Color::from_rgba8(252, 252, 255, 255),
|
||||
bg_button: Color::from_rgba8(228, 228, 233, 255),
|
||||
bg_button_hover: Color::from_rgba8(214, 214, 221, 255),
|
||||
bg_selected: Color::from_rgba8(10, 132, 255, 255),
|
||||
bg_row_hover: Color::from_rgba8(232, 234, 240, 255),
|
||||
fg_text: Color::from_rgba8(28, 28, 32, 255),
|
||||
fg_muted: Color::from_rgba8(110, 110, 120, 255),
|
||||
fg_placeholder: Color::from_rgba8(160, 160, 170, 255),
|
||||
fg_destructive: Color::from_rgba8(215, 58, 50, 255),
|
||||
border: Color::from_rgba8(208, 208, 215, 255),
|
||||
border_focus: Color::from_rgba8(10, 132, 255, 255),
|
||||
accent: Color::from_rgba8(10, 132, 255, 255),
|
||||
}
|
||||
}
|
||||
|
||||
/// Skin **KDE Plasma "Breeze" (claro)** — gris papel (#eff0f1), acento
|
||||
/// azul Breeze (#3daee9). Para la vista `kde`.
|
||||
pub const fn kde_breeze() -> Self {
|
||||
Self {
|
||||
name: "Breeze",
|
||||
bg_app: Color::from_rgba8(239, 240, 241, 255),
|
||||
bg_panel: Color::from_rgba8(252, 252, 252, 255),
|
||||
bg_panel_alt: Color::from_rgba8(49, 54, 59, 255), // panel oscuro Breeze
|
||||
bg_input: Color::from_rgba8(255, 255, 255, 255),
|
||||
bg_input_focus: Color::from_rgba8(248, 252, 254, 255),
|
||||
bg_button: Color::from_rgba8(224, 226, 228, 255),
|
||||
bg_button_hover: Color::from_rgba8(208, 211, 214, 255),
|
||||
bg_selected: Color::from_rgba8(61, 174, 233, 255),
|
||||
bg_row_hover: Color::from_rgba8(227, 229, 231, 255),
|
||||
fg_text: Color::from_rgba8(35, 38, 41, 255),
|
||||
fg_muted: Color::from_rgba8(99, 104, 109, 255),
|
||||
fg_placeholder: Color::from_rgba8(150, 155, 160, 255),
|
||||
fg_destructive: Color::from_rgba8(218, 68, 83, 255),
|
||||
border: Color::from_rgba8(188, 192, 196, 255),
|
||||
border_focus: Color::from_rgba8(61, 174, 233, 255),
|
||||
accent: Color::from_rgba8(61, 174, 233, 255),
|
||||
}
|
||||
}
|
||||
|
||||
/// Skin **Windows 3.1**: gris Motif (#c0c0c0) con barra de título azul
|
||||
/// marino (#000080) y escritorio teal. La era de los biseles. Para la vista
|
||||
/// `windows-3.1`.
|
||||
pub const fn win31() -> Self {
|
||||
Self {
|
||||
name: "Win3.1",
|
||||
bg_app: Color::from_rgba8(0, 128, 128, 255), // escritorio teal clásico
|
||||
bg_panel: Color::from_rgba8(192, 192, 192, 255), // gris ventana
|
||||
bg_panel_alt: Color::from_rgba8(0, 0, 128, 255), // barra de título azul marino
|
||||
bg_input: Color::from_rgba8(255, 255, 255, 255),
|
||||
bg_input_focus: Color::from_rgba8(255, 255, 255, 255),
|
||||
bg_button: Color::from_rgba8(192, 192, 192, 255),
|
||||
bg_button_hover: Color::from_rgba8(208, 208, 208, 255),
|
||||
bg_selected: Color::from_rgba8(0, 0, 128, 255),
|
||||
bg_row_hover: Color::from_rgba8(200, 200, 200, 255),
|
||||
fg_text: Color::from_rgba8(0, 0, 0, 255),
|
||||
fg_muted: Color::from_rgba8(64, 64, 64, 255),
|
||||
fg_placeholder: Color::from_rgba8(112, 112, 112, 255),
|
||||
fg_destructive: Color::from_rgba8(128, 0, 0, 255),
|
||||
border: Color::from_rgba8(128, 128, 128, 255),
|
||||
border_focus: Color::from_rgba8(0, 0, 128, 255),
|
||||
accent: Color::from_rgba8(0, 0, 128, 255), // azul Win3.1
|
||||
}
|
||||
}
|
||||
|
||||
/// Skin **Solaris CDE** (era dorada): gris-beige Motif con acento teal —
|
||||
/// el Common Desktop Environment. Para la vista `solaris`.
|
||||
pub const fn cde() -> Self {
|
||||
Self {
|
||||
name: "CDE",
|
||||
bg_app: Color::from_rgba8(45, 70, 90, 255), // fondo azul-gris CDE
|
||||
bg_panel: Color::from_rgba8(174, 178, 195, 255), // gris-lila Motif
|
||||
bg_panel_alt: Color::from_rgba8(120, 130, 150, 255),
|
||||
bg_input: Color::from_rgba8(220, 222, 230, 255),
|
||||
bg_input_focus: Color::from_rgba8(235, 237, 244, 255),
|
||||
bg_button: Color::from_rgba8(160, 166, 185, 255),
|
||||
bg_button_hover: Color::from_rgba8(176, 182, 200, 255),
|
||||
bg_selected: Color::from_rgba8(90, 130, 130, 255),
|
||||
bg_row_hover: Color::from_rgba8(168, 174, 192, 255),
|
||||
fg_text: Color::from_rgba8(20, 24, 32, 255),
|
||||
fg_muted: Color::from_rgba8(64, 72, 84, 255),
|
||||
fg_placeholder: Color::from_rgba8(100, 108, 120, 255),
|
||||
fg_destructive: Color::from_rgba8(140, 40, 40, 255),
|
||||
border: Color::from_rgba8(108, 116, 134, 255),
|
||||
border_focus: Color::from_rgba8(64, 132, 132, 255),
|
||||
accent: Color::from_rgba8(64, 132, 132, 255), // teal CDE
|
||||
}
|
||||
}
|
||||
|
||||
/// Superficie "hundida" — un escalón más profunda que `bg_app`, para
|
||||
/// áreas de lectura intensa (output de terminal, viewports de log,
|
||||
/// IDE-text) que deben recibir el texto con más contraste que el chrome
|
||||
/// y leerse recesadas respecto del marco. En temas oscuros oscurece
|
||||
/// `bg_app` hacia el negro; en claros lo aleja un paso del blanco. Las
|
||||
/// cards/strips (`bg_panel`, `bg_panel_alt`) quedan flotando por encima.
|
||||
/// Derivada de la paleta — no inventa un color suelto.
|
||||
pub fn sunken(&self) -> Color {
|
||||
let c = self.bg_app.components;
|
||||
// Luminancia relativa aproximada en sRGB (sin linealizar — alcanza
|
||||
// para decidir oscuro/claro).
|
||||
let lum = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
||||
let factor = if lum < 0.5 { 0.5 } else { 0.93 };
|
||||
Color::from_rgba8(
|
||||
(c[0] * factor * 255.0).round().clamp(0.0, 255.0) as u8,
|
||||
(c[1] * factor * 255.0).round().clamp(0.0, 255.0) as u8,
|
||||
(c[2] * factor * 255.0).round().clamp(0.0, 255.0) as u8,
|
||||
255,
|
||||
)
|
||||
}
|
||||
|
||||
/// Todos los presets del repo, en el orden canónico de rotación
|
||||
/// (Tawa → Dark → Light → Aurora → Sunset → Tawa…). `tawa()` va **al
|
||||
/// frente**: es el look firma de la suite, el primero que se ve. El
|
||||
/// theme-switcher los consume vía [`Theme::next_after`]. `print()` queda
|
||||
/// fuera de la rotación a propósito — es un modo deliberado (imprimir), no
|
||||
/// un gusto estético que se cicle por accidente.
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
Self::tawa(),
|
||||
Self::dark(),
|
||||
Self::light(),
|
||||
Self::aurora(),
|
||||
Self::sunset(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Busca un preset por nombre exacto. Incluye los modos deliberados que
|
||||
/// quedan fuera de la rotación casual (`print` y los skins de vista
|
||||
/// `WinXP`/`macOS`/`Breeze`), para que `Config::theme` los resuelva.
|
||||
pub fn by_name(name: &str) -> Option<Self> {
|
||||
Self::all().into_iter().find(|t| t.name == name)
|
||||
Self::all()
|
||||
.into_iter()
|
||||
.chain([
|
||||
Self::print(),
|
||||
Self::xp_blue(),
|
||||
Self::mac_light(),
|
||||
Self::kde_breeze(),
|
||||
Self::win31(),
|
||||
Self::cde(),
|
||||
])
|
||||
.find(|t| t.name == name)
|
||||
}
|
||||
|
||||
/// Próximo preset en la rotación de [`Theme::all`]. Si `current` no
|
||||
@@ -358,4 +643,31 @@ mod tests {
|
||||
fn dark_is_the_default() {
|
||||
assert_eq!(Theme::default().name, "Dark");
|
||||
}
|
||||
|
||||
/// "Tawa" — el look firma — entra en la rotación y va al frente, y
|
||||
/// `by_name` lo resuelve.
|
||||
#[test]
|
||||
fn tawa_es_el_primero_y_se_resuelve() {
|
||||
let all = Theme::all();
|
||||
assert_eq!(all[0].name, "Tawa", "Tawa debe ir al frente de la rotación");
|
||||
assert_eq!(Theme::by_name("Tawa").expect("registrado").name, "Tawa");
|
||||
}
|
||||
|
||||
/// En temas oscuros la superficie hundida es más oscura que el chrome
|
||||
/// (`bg_app`); en claros, también desciende (se lee recesada). En ambos
|
||||
/// casos difiere de `bg_app` — no es un no-op.
|
||||
#[test]
|
||||
fn sunken_is_deeper_than_bg_app() {
|
||||
let lum = |c: Color| {
|
||||
let k = c.components;
|
||||
0.2126 * k[0] + 0.7152 * k[1] + 0.0722 * k[2]
|
||||
};
|
||||
for t in Theme::all() {
|
||||
assert!(
|
||||
lum(t.sunken()) < lum(t.bg_app),
|
||||
"{}: sunken debe ser más oscura que bg_app",
|
||||
t.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user