1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! The app theme: one token set, two concrete instances.
use gpui::{Global, Hsla, SharedString};
use crate::{Appearance, paint};
mod glass;
mod install;
mod layout;
mod palettes;
mod syntax;
pub use install::set_palette;
pub use syntax::{HighlightKind, SyntaxPalette};
/// The app theme. Two concrete instances — [`Theme::dark`] and [`Theme::light`].
#[derive(Debug, Clone)]
pub struct Theme {
/// Which appearance these tokens were built for.
pub appearance: Appearance,
// ---- paint: neutral surfaces ----
/// Main content panel. Dark: the deepest plane (#060606). Light: pure white —
/// long-form content reads best on an unbroken white field.
pub bg: Hsla,
/// Shell / sidebar surface. Dark: one step *up* from `bg`. Light: one step
/// *down* (grey) — chrome recedes from the content plane in both, which is
/// the direction a naive invert gets backwards.
pub surface: Hsla,
/// Raised surface: opaque pills and chips that sit proud of the panel.
/// Dark: lighter than `surface`. Light: white, separated by `border` +
/// shadow rather than by lightness.
pub surface_raised: Hsla,
// ---- paint: elevation ladder ----
//
// Dark mode distinguishes floating planes by lightness, and the steps are
// *small* (#0e → #10 → #16 → #1e). They are not interchangeable: collapsing
// them onto one token visibly lifts popovers off their intended plane.
//
// Light mode cannot use the same trick, because the content plane is already
// white and there is nothing lighter to climb to. All three land on white and
// let `border` + shadow carry the separation instead — the standard light-UI
// answer, and the reason this is a ladder of tokens rather than an arithmetic
// offset applied to one.
/// Inline card resting on the main panel (auth gate, empty-state cards).
pub surface_card: Hsla,
/// Modal dialog, floating over a [`Theme::scrim`].
pub surface_dialog: Hsla,
/// Popover, menu and command-palette surface — the highest plane.
pub surface_overlay: Hsla,
/// Hover wash for interactive rows/buttons.
pub element_hover: Hsla,
/// Active/selected wash.
pub element_active: Hsla,
/// Hairline border.
pub border: Hsla,
/// Stronger border for focused/raised edges.
pub border_strong: Hsla,
// ---- paint: text ----
/// Primary text. ~17.5:1 on its own background in both appearances.
pub text: Hsla,
/// Muted text: timestamps, secondary labels. ~7.5–8:1.
pub text_muted: Hsla,
/// Faint text: placeholders, disabled. ~4.5:1 — AA for body copy.
pub text_faint: Hsla,
/// One notch below `text_muted` — the diff file-path tone. It exists as its
/// own token rather than being folded into `text_muted` because the dark
/// value was sampled (#989898) and folding it would shift that label, which
/// is a palette change dressed up as a refactor.
pub text_dim: Hsla,
// ---- paint: high-contrast solid (primary buttons) ----
/// The maximum-contrast solid fill: near-white on dark, near-black on light.
/// This is the primary button plate.
pub solid: Hsla,
/// Label/icon color on top of [`Self::solid`] — its inverse.
pub on_solid: Hsla,
// ---- paint: accents ----
/// Accent — the emphasis weight for text and icons.
///
/// **Neutral by default, and deliberately.** A component library that ships
/// a hue puts that hue in every app that installs it, and bezel had an
/// indigo running through spinners, pagination, date selection and list
/// markers before anyone chose it. The default is now the same lightness
/// with the chroma at zero.
///
/// This is the token to brand:
///
/// ```ignore
/// let mut theme = Theme::for_appearance(appearance);
/// theme.accent = my_brand_accent(appearance);
/// ```
///
/// See [`set_palette`](crate::theme::install::set_palette), which is what
/// makes an override survive an appearance switch.
pub accent: Hsla,
/// Stronger accent for fills that carry [`Self::on_accent`] text. Neutral by
/// default, it is the maximum-contrast plate — a mid grey would not carry a
/// label the way the indigo it replaced did.
pub accent_strong: Hsla,
/// Label color on top of [`Self::accent_strong`].
pub on_accent: Hsla,
/// Danger — red (errors, stop button).
pub danger: Hsla,
/// Softer danger for secondary/inline error copy.
pub danger_muted: Hsla,
/// Warning — amber (offline notices, awaiting-input).
pub warning: Hsla,
/// Softer warning for secondary copy.
pub warning_muted: Hsla,
/// Success / online — emerald.
pub success: Hsla,
/// Working / streaming indicator — pink.
pub busy: Hsla,
/// Softer success for text on a success-tinted chip.
pub success_muted: Hsla,
// ---- paint: components ----
/// Hover tone for an *opaque* raised pill. Hover must brighten the plate in
/// dark mode, never swap it for a translucent wash (that made pills go
/// see-through — user-reported); in light mode it darkens instead, same idea.
pub surface_raised_hover: Hsla,
/// Recessed band behind a palette/picker header or footer strip. Translucent
/// so the glass still reads through.
pub band: Hsla,
/// The composer pill and other input plates.
///
/// Its own token because "lifted" inverts between appearances. On dark, a
/// faint *white* wash over near-black reads as raised. The literal light
/// translation — a faint *black* wash on white — reads as **recessed**, a dent
/// rather than a plate, which is why the prompt looked like bare text on a
/// smudge. Light mode lifts the way light UIs actually do: pure white, with
/// the border and shadow carrying the elevation.
pub input_bg: Hsla,
/// Text-selection highlight in the composer and inputs.
pub selection: Hsla,
/// Terminal block cursor.
pub cursor: Hsla,
/// Text caret, and the focus ring that shares its weight.
///
/// The body text colour, because that is what a caret is: the next glyph,
/// before you type it. It was a sampled blue once — carried over from the
/// app this library was extracted from, derived from nothing here — which
/// is why a caret in a plain paragraph arrived tinted.
pub caret: Hsla,
/// Destructive-action button fill (danger plate, carries [`Self::on_accent`]).
pub danger_strong: Hsla,
// ---- paint: code & diff ----
/// Inline-code text. Neutral: code is already set apart by the mono face
/// and its wash, and a hue on top reads as a link rather than as code.
pub code_text: Hsla,
/// Inline-code wash behind [`Self::code_text`].
pub code_wash: Hsla,
/// Shared paint-only syntax palette.
pub syntax: SyntaxPalette,
/// Diff: added lines.
pub diff_add: Hsla,
/// Diff: deleted lines.
pub diff_del: Hsla,
/// Diff: hunk-header wash (bluish grey).
pub diff_hunk_bg: Hsla,
// ---- fonts ----
/// UI font family — the name the text system resolves, not the bytes. Point
/// it at your own family once you have registered that font with the text
/// system; a family nothing registered falls through to the fallback below.
pub font_sans: SharedString,
/// Monospace family for code/terminal.
pub font_mono: SharedString,
/// Explicit system fallbacks, for callers that want to skip the lookup.
pub font_sans_fallback: SharedString,
pub font_mono_fallback: SharedString,
}
impl Theme {
/// Overlay ink at `alpha` — see [`ink`](crate::paint::ink).
pub fn ink(&self, alpha: f32) -> Hsla {
paint::ink_for(self.appearance, alpha)
}
/// Hairline ink at `alpha` — see [`hairline`](crate::paint::hairline).
pub fn hairline(&self, alpha: f32) -> Hsla {
paint::hairline_for(self.appearance, alpha)
}
/// State wash at `alpha` — see [`wash`](crate::paint::wash).
pub fn wash(&self, alpha: f32) -> Hsla {
paint::wash_for(self.appearance, alpha)
}
}
impl Default for Theme {
fn default() -> Self {
Self::dark()
}
}
impl Global for Theme {}