use gpui::{App, Global, Hsla};
use crate::{Appearance, color, theme::Theme};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Tint {
pub hue: f32,
pub chroma: f32,
}
impl Tint {
pub const NONE: Self = Self {
hue: 0.0,
chroma: 0.0,
};
pub const fn new(hue: f32, chroma: f32) -> Self {
Self { hue, chroma }
}
}
pub const BASE_COLORS: [(&str, Tint); 5] = [
("Neutral", Tint::NONE),
("Stone", Tint::new(58.071, 0.013)),
("Zinc", Tint::new(285.938, 0.016)),
("Gray", Tint::new(264.364, 0.027)),
("Slate", Tint::new(257.417, 0.046)),
];
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TextInk {
pub text: f32,
pub muted: f32,
pub faint: f32,
}
impl TextInk {
pub fn color(appearance: Appearance, alpha: f32) -> Hsla {
match appearance {
Appearance::Dark => gpui::hsla(0.0, 0.0, 1.0, alpha),
Appearance::Light => gpui::hsla(0.0, 0.0, 0.0, alpha),
}
}
pub fn paint(&self, theme: &mut Theme) {
let ink = theme.text;
theme.text = ink.alpha(self.text);
theme.text_muted = ink.alpha(self.muted);
theme.text_faint = ink.alpha(self.faint);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Ink {
pub dark: TextInk,
pub light: TextInk,
}
impl Ink {
pub const APPKIT: Self = Self {
dark: TextInk {
text: 0.85,
muted: 0.55,
faint: 0.25,
},
light: TextInk {
text: 0.85,
muted: 0.50,
faint: 0.26,
},
};
pub const ACCESSIBLE: Self = Self {
dark: TextInk {
text: 0.85,
muted: 0.52,
faint: 0.40,
},
light: TextInk {
text: 0.85,
muted: 0.56,
faint: 0.43,
},
};
pub fn for_appearance(&self, appearance: Appearance) -> TextInk {
match appearance {
Appearance::Dark => self.dark,
Appearance::Light => self.light,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Vibrancy {
#[default]
Auto,
On,
Off,
}
impl Vibrancy {
pub fn on(self, appearance: Appearance) -> bool {
match self {
Self::Auto => crate::frosted_window() && matches!(appearance, Appearance::Dark),
Self::On => true,
Self::Off => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Brand {
pub tint: Tint,
pub accent: Tint,
pub radius: f32,
pub vibrancy_alpha: f32,
pub window_blur: f32,
pub vibrancy: Vibrancy,
pub glass: bool,
pub ink: Ink,
}
impl Global for Brand {}
impl Default for Brand {
fn default() -> Self {
Self {
tint: Tint::NONE,
accent: Tint::NONE,
radius: Theme::BASE_RADIUS,
vibrancy_alpha: Theme::VIBRANCY_ALPHA,
window_blur: Theme::WINDOW_BLUR,
vibrancy: Vibrancy::Auto,
glass: crate::LENSED,
ink: Ink::APPKIT,
}
}
}
const ACCENT_L: (f32, f32) = (0.673, 0.511);
const PLATE_L: (f32, f32) = (0.58, 0.51);
impl Theme {
pub fn branded(brand: &Brand, appearance: Appearance) -> Self {
let mut theme = Self::for_appearance(appearance);
brand.apply(&mut theme);
theme
}
}
impl Brand {
pub fn apply(&self, theme: &mut Theme) {
theme.vibrancy = self.vibrancy.on(theme.appearance);
theme.vibrancy_alpha = self.vibrancy_alpha;
theme.window_blur = self.window_blur;
theme.glass = self.glass;
self.ink.for_appearance(theme.appearance).paint(theme);
let tint = match theme.family {
None => self.tint,
Some(_) => Tint::NONE,
};
for (_, slot) in theme.tokens_mut() {
if slot.a == 1.0 && slot.s <= f32::EPSILON {
*slot = color::tint(*slot, tint.hue, tint.chroma);
}
}
if self.accent.chroma > 0.0 {
let light = theme.appearance == Appearance::Light;
let (accent_l, plate_l) = if light {
(ACCENT_L.1, PLATE_L.1)
} else {
(ACCENT_L.0, PLATE_L.0)
};
theme.accent = color::oklch(accent_l, self.accent.chroma, self.accent.hue);
theme.accent_strong = color::oklch(plate_l, self.accent.chroma, self.accent.hue);
theme.on_accent = label_on(theme.accent_strong, theme);
}
}
}
fn label_on(plate: Hsla, theme: &Theme) -> Hsla {
let (a, b) = (theme.solid, theme.on_solid);
if color::contrast_ratio(plate, a) >= color::contrast_ratio(plate, b) {
a
} else {
b
}
}
pub(crate) fn brand(cx: &App) -> Brand {
cx.try_global::<Brand>().copied().unwrap_or_default()
}
pub(crate) fn set_brand(brand: Brand, cx: &mut App) {
cx.set_global(brand);
Theme::install(crate::paint::current_appearance(), cx);
crate::appearance::reapply_window_background(cx);
cx.refresh_windows();
}