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 Brand {
pub tint: Tint,
pub accent: Tint,
pub radius: f32,
pub vibrancy_alpha: f32,
pub vibrancy: bool,
pub glass: bool,
}
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,
vibrancy: Theme::VIBRANCY_ALPHA < 1.0,
glass: Theme::VIBRANCY_ALPHA < 1.0,
}
}
}
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_alpha = self.vibrancy_alpha;
theme.vibrancy = self.vibrancy;
theme.glass = self.glass;
let tokens: [&mut Hsla; 39] = [
&mut theme.bg,
&mut theme.surface,
&mut theme.surface_raised,
&mut theme.surface_card,
&mut theme.surface_dialog,
&mut theme.surface_overlay,
&mut theme.element_hover,
&mut theme.element_active,
&mut theme.border,
&mut theme.border_strong,
&mut theme.text,
&mut theme.text_muted,
&mut theme.text_faint,
&mut theme.text_dim,
&mut theme.solid,
&mut theme.on_solid,
&mut theme.accent,
&mut theme.accent_strong,
&mut theme.on_accent,
&mut theme.danger,
&mut theme.danger_muted,
&mut theme.warning,
&mut theme.warning_muted,
&mut theme.success,
&mut theme.busy,
&mut theme.success_muted,
&mut theme.surface_raised_hover,
&mut theme.band,
&mut theme.input_bg,
&mut theme.selection,
&mut theme.cursor,
&mut theme.caret,
&mut theme.ring,
&mut theme.danger_strong,
&mut theme.code_text,
&mut theme.code_wash,
&mut theme.diff_add,
&mut theme.diff_del,
&mut theme.diff_hunk_bg,
];
let syntax: [&mut Hsla; 24] = [
&mut theme.syntax.comment,
&mut theme.syntax.keyword,
&mut theme.syntax.string,
&mut theme.syntax.string_special,
&mut theme.syntax.escape,
&mut theme.syntax.number,
&mut theme.syntax.boolean,
&mut theme.syntax.type_name,
&mut theme.syntax.type_builtin,
&mut theme.syntax.constructor,
&mut theme.syntax.function,
&mut theme.syntax.function_builtin,
&mut theme.syntax.macro_name,
&mut theme.syntax.property,
&mut theme.syntax.constant,
&mut theme.syntax.variable,
&mut theme.syntax.variable_special,
&mut theme.syntax.parameter,
&mut theme.syntax.operator,
&mut theme.syntax.punctuation,
&mut theme.syntax.tag,
&mut theme.syntax.attribute,
&mut theme.syntax.label,
&mut theme.syntax.invalid,
];
for slot in tokens.into_iter().chain(syntax) {
if slot.a == 1.0 && slot.s <= f32::EPSILON {
*slot = color::tint(*slot, self.tint.hue, self.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 fn brand(cx: &App) -> Brand {
cx.try_global::<Brand>().copied().unwrap_or_default()
}
pub 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();
}