1use gpui::{Div, Hsla, Styled};
6
7use crate::theme::{ColorName, Size, Theme};
8
9pub(crate) const MONO_FAMILY: &str = "Menlo";
14
15pub trait StyleExt: Sized {
18 fn apply(self, f: impl FnOnce(Self) -> Self) -> Self {
21 f(self)
22 }
23}
24
25impl<T: Styled> StyleExt for T {}
26
27pub(crate) trait FlexExt: Sized {
31 fn grow(self, factor: f32) -> Self;
33 fn shrink(self, factor: f32) -> Self;
35 fn items_stretch(self) -> Self;
37 fn justify_evenly(self) -> Self;
39}
40
41impl FlexExt for Div {
42 fn grow(mut self, factor: f32) -> Self {
43 self.style().flex_grow = Some(factor);
44 self
45 }
46
47 fn shrink(mut self, factor: f32) -> Self {
48 self.style().flex_shrink = Some(factor);
49 self
50 }
51
52 fn items_stretch(mut self) -> Self {
53 self.style().align_items = Some(gpui::AlignItems::Stretch);
54 self
55 }
56
57 fn justify_evenly(mut self) -> Self {
58 self.style().justify_content = Some(gpui::JustifyContent::SpaceEvenly);
59 self
60 }
61}
62
63pub(crate) fn icon_size(size: Size) -> f32 {
66 match size {
67 Size::Xs => 18.0,
68 Size::Sm => 22.0,
69 Size::Md => 28.0,
70 Size::Lg => 34.0,
71 Size::Xl => 44.0,
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub enum Variant {
78 #[default]
80 Filled,
81 Light,
83 Outline,
85 Subtle,
87 Default,
89 Transparent,
91 White,
93}
94
95impl Variant {
96 pub fn label(self) -> &'static str {
98 match self {
99 Variant::Filled => "filled",
100 Variant::Light => "light",
101 Variant::Outline => "outline",
102 Variant::Subtle => "subtle",
103 Variant::Default => "default",
104 Variant::Transparent => "transparent",
105 Variant::White => "white",
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy)]
112pub struct Surface {
113 pub bg: Hsla,
114 pub bg_hover: Hsla,
115 pub fg: Hsla,
116 pub border: Option<Hsla>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq)]
126pub enum ColorValue {
127 Named(ColorName),
129 Custom(Hsla),
131}
132
133impl Default for ColorValue {
134 fn default() -> Self {
135 ColorValue::Named(ColorName::Blue)
136 }
137}
138
139impl From<ColorName> for ColorValue {
140 fn from(name: ColorName) -> Self {
141 ColorValue::Named(name)
142 }
143}
144
145impl From<Hsla> for ColorValue {
146 fn from(color: Hsla) -> Self {
147 ColorValue::Custom(color)
148 }
149}
150
151impl ColorValue {
152 pub fn accent(self, theme: &Theme) -> Hsla {
154 match self {
155 ColorValue::Named(name) => theme.color(name, theme.primary_shade()).hsla(),
156 ColorValue::Custom(c) => c,
157 }
158 }
159
160 pub fn soft(self, theme: &Theme) -> Hsla {
162 let dark = theme.scheme.is_dark();
163 match self {
164 ColorValue::Named(name) if dark => theme.color(name, 5).alpha(0.20),
165 ColorValue::Named(name) => theme.color(name, 0).hsla(),
166 ColorValue::Custom(c) => with_alpha(c, if dark { 0.22 } else { 0.12 }),
167 }
168 }
169}
170
171fn shift_l(c: Hsla, delta: f32) -> Hsla {
172 Hsla {
173 l: (c.l + delta).clamp(0.0, 1.0),
174 ..c
175 }
176}
177
178fn with_alpha(c: Hsla, a: f32) -> Hsla {
179 Hsla { a, ..c }
180}
181
182fn readable_on(c: Hsla) -> Hsla {
184 let v = if c.l > 0.6 { 0.0 } else { 1.0 };
185 Hsla {
186 h: 0.0,
187 s: 0.0,
188 l: v,
189 a: 1.0,
190 }
191}
192
193pub fn surface(theme: &Theme, color: impl Into<ColorValue>, variant: Variant) -> Surface {
196 match color.into() {
197 ColorValue::Named(name) => surface_named(theme, name, variant),
198 ColorValue::Custom(c) => surface_custom(theme, c, variant),
199 }
200}
201
202pub(crate) fn solid(theme: &Theme, color: ColorValue) -> Hsla {
205 match color {
206 ColorValue::Named(name) => theme.color(name, theme.primary_shade()).hsla(),
207 ColorValue::Custom(c) => c,
208 }
209}
210
211fn surface_custom(theme: &Theme, c: Hsla, variant: Variant) -> Surface {
214 let dark = theme.scheme.is_dark();
215 let transparent = gpui::transparent_black();
216 let hover_fill = if dark {
217 shift_l(c, 0.06)
218 } else {
219 shift_l(c, -0.06)
220 };
221 match variant {
222 Variant::Filled => Surface {
223 bg: c,
224 bg_hover: hover_fill,
225 fg: readable_on(c),
226 border: None,
227 },
228 Variant::Light => Surface {
229 bg: with_alpha(c, if dark { 0.20 } else { 0.12 }),
230 bg_hover: with_alpha(c, if dark { 0.28 } else { 0.20 }),
231 fg: c,
232 border: None,
233 },
234 Variant::Outline => Surface {
235 bg: transparent,
236 bg_hover: with_alpha(c, 0.08),
237 fg: c,
238 border: Some(c),
239 },
240 Variant::Subtle => Surface {
241 bg: transparent,
242 bg_hover: with_alpha(c, if dark { 0.15 } else { 0.08 }),
243 fg: c,
244 border: None,
245 },
246 Variant::Default => Surface {
247 bg: theme.surface().hsla(),
248 bg_hover: theme.surface_hover().hsla(),
249 fg: theme.text().hsla(),
250 border: Some(theme.border().hsla()),
251 },
252 Variant::Transparent => Surface {
253 bg: transparent,
254 bg_hover: transparent,
255 fg: c,
256 border: None,
257 },
258 Variant::White => Surface {
259 bg: theme.white.hsla(),
260 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
261 fg: c,
262 border: None,
263 },
264 }
265}
266
267fn surface_named(theme: &Theme, name: ColorName, variant: Variant) -> Surface {
269 let dark = theme.scheme.is_dark();
270 let transparent = gpui::transparent_black();
271 let shade = theme.primary_shade();
272 let filled = theme.color(name, shade);
273 let filled_hover = theme.color(name, (shade + 1).min(9));
274 let accent = theme.color(name, if dark { 4 } else { 6 });
276
277 match variant {
278 Variant::Filled => Surface {
279 bg: filled.hsla(),
280 bg_hover: filled_hover.hsla(),
281 fg: filled.contrasting().hsla(),
282 border: None,
283 },
284 Variant::Light => {
285 let (bg, bg_hover, fg) = if dark {
286 (
287 theme.color(name, 5).alpha(0.20),
288 theme.color(name, 5).alpha(0.30),
289 theme.color(name, 2).hsla(),
290 )
291 } else {
292 (
293 theme.color(name, 0).hsla(),
294 theme.color(name, 1).hsla(),
295 theme.color(name, 8).hsla(),
296 )
297 };
298 Surface {
299 bg,
300 bg_hover,
301 fg,
302 border: None,
303 }
304 }
305 Variant::Outline => Surface {
306 bg: transparent,
307 bg_hover: accent.alpha(0.08),
308 fg: accent.hsla(),
309 border: Some(accent.hsla()),
310 },
311 Variant::Subtle => Surface {
312 bg: transparent,
313 bg_hover: accent.alpha(if dark { 0.15 } else { 0.08 }),
314 fg: accent.hsla(),
315 border: None,
316 },
317 Variant::Default => Surface {
318 bg: theme.surface().hsla(),
319 bg_hover: theme.surface_hover().hsla(),
320 fg: theme.text().hsla(),
321 border: Some(theme.border().hsla()),
322 },
323 Variant::Transparent => Surface {
324 bg: transparent,
325 bg_hover: transparent,
326 fg: accent.hsla(),
327 border: None,
328 },
329 Variant::White => Surface {
330 bg: theme.white.hsla(),
331 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
332 fg: theme.color(name, shade).hsla(),
333 border: None,
334 },
335 }
336}