//! Color sets for the window chrome and the backdrop behind it.
//!
//! Two families share one shape:
//!
//! * **KDE** — the classic schemes, one per era. The Breeze trio is copied
//! from the `[WM]` / `[Colors:Window]` groups of the `.colors` files Plasma
//! ships (`/usr/share/color-schemes/Breeze{Classic,Light,Dark}.colors`).
//! The KDE 1–4 sets are reconstructions of the defaults of their day; they
//! aim to read as the era at a glance, not to be pixel-exact.
//! * **neiam** — the daisyUI schemes shipped by the `neiam-htmx`
//! cargo-generate template (her … stones, light, dark, lofi, black), plus
//! Diogramos' `blueprint`. Names match presentato's and Diogramos', so a
//! deck's `set-theme` message can name one directly.
//!
//! The chrome only ever reads the derived roles on [`Palette`]; the code area
//! takes its colors from the highlight theme instead.
use std::fmt;
use std::sync::LazyLock;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
pub const fn hex(v: u32) -> Self {
Rgb((v >> 16) as u8, (v >> 8) as u8, v as u8)
}
/// Linear blend towards `other`; `t = 0` is `self`, `t = 1` is `other`.
pub fn mix(self, other: Rgb, t: f32) -> Rgb {
let f = |a: u8, b: u8| (a as f32 + (b as f32 - a as f32) * t).round().clamp(0.0, 255.0) as u8;
Rgb(f(self.0, other.0), f(self.1, other.1), f(self.2, other.2))
}
pub fn lighten(self, t: f32) -> Rgb {
self.mix(Rgb(255, 255, 255), t)
}
pub fn darken(self, t: f32) -> Rgb {
self.mix(Rgb(0, 0, 0), t)
}
/// Rough perceived lightness in 0..=1, enough to pick a contrasting glyph.
pub fn luma(self) -> f32 {
(0.299 * self.0 as f32 + 0.587 * self.1 as f32 + 0.114 * self.2 as f32) / 255.0
}
}
impl fmt::Display for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Family {
Kde,
Neiam,
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct Palette {
pub id: &'static str,
pub name: &'static str,
pub family: Family,
/// Decides the `solarized` highlight's light/dark variant.
pub dark: bool,
/// Active titlebar fill, and the far end of its gradient (KDE's
/// `activeBlend`). Equal when the era drew a flat titlebar.
#[serde(skip)]
pub title_bg: Rgb,
#[serde(skip)]
pub title_blend: Rgb,
#[serde(skip)]
pub title_fg: Rgb,
/// Window face: frame, bevels, button faces.
#[serde(skip)]
pub window: Rgb,
#[serde(skip)]
pub window_fg: Rgb,
/// Selection / focus accent.
#[serde(skip)]
pub focus: Rgb,
/// Close-button red.
#[serde(skip)]
pub negative: Rgb,
/// Backdrop gradient, top-left to bottom-right.
#[serde(skip)]
pub backdrop: (Rgb, Rgb),
/// The scheme's own code surface (background, text). When set and the
/// highlight follows the palette, the code area is filled with it instead
/// of the highlight theme's background. The neiam schemes carry one so a
/// card sits in the same colors as the deck or page around it.
#[serde(skip)]
pub surface: Option<(Rgb, Rgb)>,
}
#[allow(clippy::too_many_arguments)]
const fn kde(
id: &'static str,
name: &'static str,
dark: bool,
title_bg: u32,
title_blend: u32,
title_fg: u32,
window: u32,
window_fg: u32,
focus: u32,
negative: u32,
backdrop: (u32, u32),
) -> Palette {
Palette {
id,
name,
family: Family::Kde,
dark,
title_bg: Rgb::hex(title_bg),
title_blend: Rgb::hex(title_blend),
title_fg: Rgb::hex(title_fg),
window: Rgb::hex(window),
window_fg: Rgb::hex(window_fg),
focus: Rgb::hex(focus),
negative: Rgb::hex(negative),
backdrop: (Rgb::hex(backdrop.0), Rgb::hex(backdrop.1)),
surface: None,
}
}
/// Map a daisyUI scheme onto the chrome roles: the titlebar wears `neutral`
/// (what daisy uses for bars and chrome), the frame `base-200`, and the
/// backdrop runs from `primary` into `base-100`, and the code area is
/// `base-100` with `base-content` text.
#[allow(clippy::too_many_arguments)]
fn neiam(
id: &'static str,
name: &'static str,
dark: bool,
base100: u32,
base200: u32,
base300: u32,
content: u32,
primary: u32,
neutral: u32,
neutral_content: u32,
error: u32,
) -> Palette {
let neutral = Rgb::hex(neutral);
Palette {
id,
name,
family: Family::Neiam,
dark,
title_bg: neutral,
title_blend: neutral.mix(Rgb::hex(base300), 0.35),
title_fg: Rgb::hex(neutral_content),
window: Rgb::hex(base200),
window_fg: Rgb::hex(content),
focus: Rgb::hex(primary),
negative: Rgb::hex(error),
backdrop: (Rgb::hex(primary), Rgb::hex(base100)),
surface: Some((Rgb::hex(base100), Rgb::hex(content))),
}
}
pub static PALETTES: LazyLock<Vec<Palette>> = LazyLock::new(|| {
vec![
// ── KDE, oldest first ────────────────────────────────────────────
// kwm's shaded-horizontal titlebar on the stock grey.
kde("kde1", "KDE 1", false, 0x000080, 0x1084d0, 0xffffff, 0xc0c0c0, 0x000000, 0x000080, 0xc00000, (0x3a7ca5, 0x0e3050)),
kde("kde2", "KDE 2", false, 0x0a5f89, 0x9daaba, 0xffffff, 0xdcdcdc, 0x000000, 0x0a5f89, 0xc03030, (0x5a7fa8, 0x22446e)),
kde("keramik", "KDE 3 · Keramik", false, 0x418edc, 0x6b91b8, 0xffffff, 0xefefef, 0x000000, 0x678db2, 0xd04040, (0x7aa6d8, 0x2b5a96)),
kde("plastik", "KDE 3.5 · Plastik", false, 0x3e7fd0, 0x6aa0e0, 0xffffff, 0xefefef, 0x000000, 0x678db2, 0xd04040, (0x5d8fcf, 0x1b3b6f)),
kde("oxygen", "KDE 4 · Oxygen", false, 0xe0dfde, 0xf0efee, 0x141312, 0xe0dfde, 0x141312, 0x43ace8, 0xbf0303, (0x7fb2e3, 0x1c4a86)),
kde("obsidian-coast", "KDE 4 · Obsidian Coast", true, 0x302f2f, 0x3a3939, 0xe0dedb, 0x302f2f, 0xe0dedb, 0x184880, 0xed4b4b, (0x3b4c5e, 0x11161c)),
// Straight from BreezeClassic.colors: the dark titlebar on a light window.
kde("breeze", "Plasma 5 · Breeze", false, 0x475057, 0x475057, 0xfcfcfc, 0xeff0f1, 0x232627, 0x3daee9, 0xda4453, (0x1d99f3, 0x1b2a3a)),
kde("breeze-light", "Plasma 6 · Breeze Light", false, 0xe3e5e7, 0xe3e5e7, 0x232629, 0xeff0f1, 0x232629, 0x3daee9, 0xda4453, (0x8ec9f5, 0x3d6e9e)),
kde("breeze-dark", "Plasma 6 · Breeze Dark", true, 0x272c31, 0x272c31, 0xfcfcfc, 0x202326, 0xfcfcfc, 0x3daee9, 0xda4453, (0x2c5a7c, 0x101418)),
// ── neiam (template daisyUI schemes + Diogramos' blueprint) ─────
neiam("her", "her", true, 0x661e1e, 0x570e12, 0x470005, 0xffece8, 0xb6797a, 0x7f3535, 0xfff6f5, 0xfda6a5),
neiam("afterdark", "afterdark", true, 0x201e66, 0x160e57, 0x0e0048, 0xeff6ff, 0x7b78b4, 0x393680, 0xf7f8ff, 0xfda6a5),
neiam("forest", "forest", true, 0x062f17, 0x00220b, 0x001603, 0xe0ffe9, 0x48de81, 0x186634, 0xf4faf5, 0xfda6a5),
neiam("sky", "sky", true, 0x072e48, 0x00213a, 0x00152d, 0xdcfdff, 0x37bcf7, 0x0b4a6e, 0xf3f9ff, 0xfda6a5),
neiam("clays", "clays", true, 0x451a03, 0x370d00, 0x290200, 0xffefdd, 0xda780e, 0x913f0d, 0xfff7f3, 0xfda6a5),
neiam("stones", "stones", true, 0x292524, 0x1d1a19, 0x120f0e, 0xffede3, 0x6b727f, 0x56524d, 0xfdf8f1, 0xfda6a5),
neiam("light", "light", false, 0xfef6f6, 0xfbeeed, 0xf3dfdd, 0x361716, 0xb6797a, 0x51515c, 0xf8f8f8, 0xea003e),
neiam("dark", "dark", true, 0x292f37, 0x1e2329, 0x13171c, 0xecf9ff, 0xb6797a, 0x314157, 0xf7f9fa, 0xea003e),
neiam("blueprint", "blueprint", true, 0x002661, 0x001c51, 0x001241, 0xc2e7fe, 0x0e9dff, 0x00449a, 0xc2e7fe, 0xfda6a5),
// daisyUI v4 built-ins the template also lists.
neiam("lofi", "lofi", false, 0xffffff, 0xf2f2f2, 0xe6e5e5, 0x000000, 0x0d0d0d, 0x000000, 0xffffff, 0xff9fa0),
neiam("black", "black", true, 0x000000, 0x0d0d0d, 0x1a1919, 0xd6d6d6, 0x373737, 0x373737, 0xd6d6d6, 0xff6f6f),
]
});
pub fn get(id: &str) -> Option<&'static Palette> {
PALETTES.iter().find(|p| p.id == id)
}
/// Every palette id; the embed page uses this to vet `set-theme` names.
pub fn ids() -> Vec<&'static str> {
PALETTES.iter().map(|p| p.id).collect()
}
neiam
/bismuth
MOTHER-FUCKING-BISMUTH
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
8.4 KiB · text
History
487bb12