Make every shell color token theme-aware
Dark mode was persistent in `AppearanceSettings` but never reached any paint code: every call site in the shell read `colors::INK` etc. as a `pub const u32`, so toggling `ThemeMode::Dark` mutated state nothing sampled. Root-cause fix is to invert the contract — the design-system exports functions that resolve through a thread-local `Mode`, and the GPUI render impl sets that mode each frame. What lands: - `ely_design_system::colors::Mode` + thread-local + `set_mode` / `mode` accessors. Every ink shade, glass surface, stroke, divider, hairline, canvas, success / error chip now picks the warm-dark counterpart when the active mode is `Mode::Dark`. - `ElyShell::render` resolves `ThemeMode::System` against `Window::appearance()` and pushes the mode before traversing the tree, so widgets lower down read the right shade without owning a `Mode` parameter. - `render_wallpaper` + `panel_bg` now branch on `colors::mode()` so the gradient base, panel tint, and overlay highlights flip to warm-graphite when dark mode is active. - Mechanical conversion across 687 call sites in 55 files from `colors::FOO` constants to `colors::foo()` accessors. The `Theme` / `ELY_THEME` const surface (unused outside the design system) is removed; the function surface is the new contract.
This commit is contained in:
@@ -1,46 +1,218 @@
|
||||
// Ink scale — text hierarchy with warm undertones
|
||||
pub const INK: u32 = 0x1d1c1a;
|
||||
pub const INK_2: u32 = 0x3a3733;
|
||||
pub const INK_3: u32 = 0x6b6660;
|
||||
pub const INK_4: u32 = 0x9a948c;
|
||||
pub const INK_5: u32 = 0xc8c2b8;
|
||||
//! Theme-aware color tokens.
|
||||
//!
|
||||
//! Each token is a function (not a `const`) so the same call site can
|
||||
//! resolve to the light or dark value depending on the current mode.
|
||||
//! Mode lives in a thread-local that the GPUI render impl sets once
|
||||
//! per frame via [`set_mode`]; the entire single-threaded render tree
|
||||
//! reads the same value, so there is no per-element parameter to
|
||||
//! thread through.
|
||||
//!
|
||||
//! Semantic accents (Accent, Violet, Rose, Mint) stay constant across
|
||||
//! modes — their hue is the brand and looks correct against both warm
|
||||
//! cream and warm graphite backdrops.
|
||||
|
||||
// Glass surfaces — semi-transparent whites (use with rgba)
|
||||
pub const GLASS: u32 = 0xffffff9e; // 62% white
|
||||
pub const GLASS_2: u32 = 0xffffffc7; // 78% white
|
||||
pub const GLASS_3: u32 = 0xffffffeb; // 92% white
|
||||
pub const TINT: u32 = 0xffffff6b; // 42% white
|
||||
use std::cell::Cell;
|
||||
|
||||
// Stroke & divider — subtle borders (use with rgba)
|
||||
pub const STROKE: u32 = 0x281e1414; // 8% warm dark
|
||||
pub const STROKE_2: u32 = 0x281e140a; // 4% warm dark
|
||||
pub const DIVIDER: u32 = 0x281e140f; // 6% warm dark
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
// Accent — warm clay palette
|
||||
pub const ACCENT: u32 = 0xc96442;
|
||||
pub const ACCENT_LIGHT: u32 = 0xd97757;
|
||||
pub const ACCENT_HOVER: u32 = 0xc9644214; // 8% opacity for row highlights
|
||||
thread_local! {
|
||||
static CURRENT_MODE: Cell<Mode> = const { Cell::new(Mode::Light) };
|
||||
}
|
||||
|
||||
// Secondary accents
|
||||
pub const VIOLET: u32 = 0x7c6cf7;
|
||||
pub const ROSE: u32 = 0xec6a8e;
|
||||
pub const MINT: u32 = 0x4fb59a;
|
||||
/// Resolve the mode used by every subsequent [`Mode`]-keyed function
|
||||
/// call on this thread. The Render impl calls this once at the top of
|
||||
/// every frame so widgets that read e.g. [`ink`] return the right
|
||||
/// shade without owning a `Mode` parameter.
|
||||
pub fn set_mode(mode: Mode) {
|
||||
CURRENT_MODE.with(|cell| cell.set(mode));
|
||||
}
|
||||
|
||||
// Canvas backgrounds (opaque fallbacks)
|
||||
pub const CANVAS: u32 = 0xf0eee9;
|
||||
pub const CANVAS_SOFT: u32 = 0xfafaf7;
|
||||
pub const SURFACE_CARD: u32 = 0xffffff;
|
||||
#[must_use]
|
||||
pub fn mode() -> Mode {
|
||||
CURRENT_MODE.with(Cell::get)
|
||||
}
|
||||
|
||||
// Semantic
|
||||
pub const SUCCESS: u32 = 0x2f7d68;
|
||||
pub const ERROR: u32 = 0xcf2d56;
|
||||
#[must_use]
|
||||
fn pick(light: u32, dark: u32) -> u32 {
|
||||
match mode() {
|
||||
Mode::Light => light,
|
||||
Mode::Dark => dark,
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy aliases — preserved for compatibility during migration
|
||||
pub const PRIMARY: u32 = ACCENT;
|
||||
pub const PRIMARY_ACTIVE: u32 = 0xb55a3c;
|
||||
pub const BODY: u32 = INK_2;
|
||||
pub const MUTED: u32 = INK_3;
|
||||
pub const MUTED_SOFT: u32 = INK_4;
|
||||
pub const HAIRLINE: u32 = 0xe6e5e0;
|
||||
pub const HAIRLINE_STRONG: u32 = 0xcfcdc4;
|
||||
pub const SURFACE_STRONG: u32 = 0xe6e5e0;
|
||||
// Ink scale — text hierarchy with warm undertones. In dark mode the
|
||||
// hierarchy inverts: brightest at the top, darkest at the bottom,
|
||||
// keeping the same step contrast (~12 % luminance gaps) as the light
|
||||
// scale so headings + secondary copy still pull the reader's eye in
|
||||
// the right order.
|
||||
#[must_use]
|
||||
pub fn ink() -> u32 {
|
||||
pick(0x1d1c1a, 0xf2efe9)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ink_2() -> u32 {
|
||||
pick(0x3a3733, 0xcfc8be)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ink_3() -> u32 {
|
||||
pick(0x6b6660, 0xa39b8e)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ink_4() -> u32 {
|
||||
pick(0x9a948c, 0x7c7568)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ink_5() -> u32 {
|
||||
pick(0xc8c2b8, 0x4f4942)
|
||||
}
|
||||
|
||||
// Glass surfaces — semi-transparent panels. Dark mode swaps the white
|
||||
// for warm-graphite so a translucent panel sits naturally on a dark
|
||||
// wallpaper without bleaching the underlying gradient.
|
||||
#[must_use]
|
||||
pub fn glass() -> u32 {
|
||||
pick(0xffffff9e, 0x1f1d1b9e)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn glass_2() -> u32 {
|
||||
pick(0xffffffc7, 0x1f1d1bc7)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn glass_3() -> u32 {
|
||||
pick(0xffffffeb, 0x1f1d1beb)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn tint() -> u32 {
|
||||
pick(0xffffff6b, 0x1f1d1b6b)
|
||||
}
|
||||
|
||||
// Stroke & divider — subtle borders. Dark mode uses warm-white at
|
||||
// matching opacity so panels keep their inner highlight.
|
||||
#[must_use]
|
||||
pub fn stroke() -> u32 {
|
||||
pick(0x281e1414, 0xf2efe914)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn stroke_2() -> u32 {
|
||||
pick(0x281e140a, 0xf2efe90a)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn divider() -> u32 {
|
||||
pick(0x281e140f, 0xf2efe90f)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn hairline() -> u32 {
|
||||
pick(0xe6e5e0, 0x312d28)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn hairline_strong() -> u32 {
|
||||
pick(0xcfcdc4, 0x49443c)
|
||||
}
|
||||
|
||||
// Accent — warm clay palette. Stays constant across modes; the hue is
|
||||
// the brand and reads correctly against both creamy + graphite
|
||||
// backdrops with the same contrast ladder.
|
||||
#[must_use]
|
||||
pub fn accent() -> u32 {
|
||||
0xc96442
|
||||
}
|
||||
#[must_use]
|
||||
pub fn accent_light() -> u32 {
|
||||
0xd97757
|
||||
}
|
||||
#[must_use]
|
||||
pub fn accent_hover() -> u32 {
|
||||
pick(0xc9644214, 0xc9644229)
|
||||
}
|
||||
|
||||
// Secondary accents.
|
||||
#[must_use]
|
||||
pub fn violet() -> u32 {
|
||||
0x7c6cf7
|
||||
}
|
||||
#[must_use]
|
||||
pub fn rose() -> u32 {
|
||||
0xec6a8e
|
||||
}
|
||||
#[must_use]
|
||||
pub fn mint() -> u32 {
|
||||
0x4fb59a
|
||||
}
|
||||
|
||||
// Canvas backgrounds (opaque fallbacks for non-wallpaper surfaces).
|
||||
// Dark canvas uses a warm graphite that holds up against the dark
|
||||
// wallpapers without crushing contrast.
|
||||
#[must_use]
|
||||
pub fn canvas() -> u32 {
|
||||
pick(0xf0eee9, 0x141312)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn canvas_soft() -> u32 {
|
||||
pick(0xfafaf7, 0x1a1816)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn surface_card() -> u32 {
|
||||
pick(0xffffff, 0x24211f)
|
||||
}
|
||||
|
||||
// Semantic — same hue across modes, lightness shifted slightly in
|
||||
// dark mode so the chip reads at the same legibility weight.
|
||||
#[must_use]
|
||||
pub fn success() -> u32 {
|
||||
pick(0x2f7d68, 0x6cc8a8)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn error() -> u32 {
|
||||
pick(0xcf2d56, 0xff7095)
|
||||
}
|
||||
|
||||
// Legacy aliases — preserved during migration from the old constant
|
||||
// surface. Internal call sites still reach for `colors::body()`,
|
||||
// `colors::muted()`, etc., so keep the names available.
|
||||
#[must_use]
|
||||
pub fn primary() -> u32 {
|
||||
accent()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn primary_active() -> u32 {
|
||||
pick(0xb55a3c, 0xe2785a)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn body() -> u32 {
|
||||
ink_2()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn muted() -> u32 {
|
||||
ink_3()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn muted_soft() -> u32 {
|
||||
ink_4()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn surface_strong() -> u32 {
|
||||
hairline()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Mode, ink, mode, set_mode};
|
||||
|
||||
#[test]
|
||||
fn mode_toggle_flips_ink() {
|
||||
set_mode(Mode::Light);
|
||||
let light = ink();
|
||||
set_mode(Mode::Dark);
|
||||
let dark = ink();
|
||||
assert_ne!(light, dark, "dark ink must differ from light ink");
|
||||
assert_eq!(mode(), Mode::Dark);
|
||||
// Restore the default so subsequent tests on this thread aren't
|
||||
// dragged into dark mode.
|
||||
set_mode(Mode::Light);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,3 @@ pub mod colors;
|
||||
pub mod motion;
|
||||
pub mod spacing;
|
||||
pub mod typography;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Theme {
|
||||
pub canvas: u32,
|
||||
pub canvas_soft: u32,
|
||||
pub surface: u32,
|
||||
pub ink: u32,
|
||||
pub body: u32,
|
||||
pub muted: u32,
|
||||
pub hairline: u32,
|
||||
pub accent: u32,
|
||||
pub success: u32,
|
||||
pub error: u32,
|
||||
}
|
||||
|
||||
pub const ELY_THEME: Theme = Theme {
|
||||
canvas: colors::CANVAS,
|
||||
canvas_soft: colors::CANVAS_SOFT,
|
||||
surface: colors::SURFACE_CARD,
|
||||
ink: colors::INK,
|
||||
body: colors::INK_2,
|
||||
muted: colors::INK_3,
|
||||
hairline: colors::HAIRLINE,
|
||||
accent: colors::ACCENT,
|
||||
success: colors::SUCCESS,
|
||||
error: colors::ERROR,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user