1use gpui::{px, 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 TextOverflowExt: Sized {
34 fn truncate_text(self) -> Self;
35}
36
37impl TextOverflowExt for Div {
38 fn truncate_text(self) -> Self {
39 self.min_w(px(0.0)).whitespace_nowrap().text_ellipsis()
40 }
41}
42
43pub(crate) trait FlexExt: Sized {
47 fn grow(self, factor: f32) -> Self;
49 fn shrink(self, factor: f32) -> Self;
51 fn items_stretch(self) -> Self;
53 fn justify_evenly(self) -> Self;
55}
56
57impl FlexExt for Div {
58 fn grow(mut self, factor: f32) -> Self {
59 self.style().flex_grow = Some(factor);
60 self
61 }
62
63 fn shrink(mut self, factor: f32) -> Self {
64 self.style().flex_shrink = Some(factor);
65 self
66 }
67
68 fn items_stretch(mut self) -> Self {
69 self.style().align_items = Some(gpui::AlignItems::Stretch);
70 self
71 }
72
73 fn justify_evenly(mut self) -> Self {
74 self.style().justify_content = Some(gpui::JustifyContent::SpaceEvenly);
75 self
76 }
77}
78
79pub(crate) fn icon_size(size: Size) -> f32 {
82 match size {
83 Size::Xs => 18.0,
84 Size::Sm => 22.0,
85 Size::Md => 28.0,
86 Size::Lg => 34.0,
87 Size::Xl => 44.0,
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum Variant {
94 #[default]
96 Filled,
97 Light,
99 Outline,
101 Subtle,
103 Default,
105 Transparent,
107 White,
109}
110
111impl Variant {
112 pub fn label(self) -> &'static str {
114 match self {
115 Variant::Filled => "filled",
116 Variant::Light => "light",
117 Variant::Outline => "outline",
118 Variant::Subtle => "subtle",
119 Variant::Default => "default",
120 Variant::Transparent => "transparent",
121 Variant::White => "white",
122 }
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
128pub struct Surface {
129 pub bg: Hsla,
130 pub bg_hover: Hsla,
131 pub fg: Hsla,
132 pub border: Option<Hsla>,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq)]
142pub enum ColorValue {
143 Named(ColorName),
145 Custom(Hsla),
147}
148
149impl Default for ColorValue {
150 fn default() -> Self {
151 ColorValue::Named(ColorName::Blue)
152 }
153}
154
155impl From<ColorName> for ColorValue {
156 fn from(name: ColorName) -> Self {
157 ColorValue::Named(name)
158 }
159}
160
161impl From<Hsla> for ColorValue {
162 fn from(color: Hsla) -> Self {
163 ColorValue::Custom(color)
164 }
165}
166
167impl ColorValue {
168 pub fn accent(self, theme: &Theme) -> Hsla {
170 match self {
171 ColorValue::Named(name) => theme.color(name, theme.primary_shade()).hsla(),
172 ColorValue::Custom(c) => c,
173 }
174 }
175
176 pub fn soft(self, theme: &Theme) -> Hsla {
178 let dark = theme.scheme.is_dark();
179 match self {
180 ColorValue::Named(name) if dark => theme.color(name, 5).alpha(0.20),
181 ColorValue::Named(name) => theme.color(name, 0).hsla(),
182 ColorValue::Custom(c) => with_alpha(c, if dark { 0.22 } else { 0.12 }),
183 }
184 }
185}
186
187fn shift_l(c: Hsla, delta: f32) -> Hsla {
188 Hsla {
189 l: (c.l + delta).clamp(0.0, 1.0),
190 ..c
191 }
192}
193
194fn with_alpha(c: Hsla, a: f32) -> Hsla {
195 Hsla { a, ..c }
196}
197
198fn readable_on(c: Hsla) -> Hsla {
200 let v = if c.l > 0.6 { 0.0 } else { 1.0 };
201 Hsla {
202 h: 0.0,
203 s: 0.0,
204 l: v,
205 a: 1.0,
206 }
207}
208
209pub fn surface(theme: &Theme, color: impl Into<ColorValue>, variant: Variant) -> Surface {
212 match color.into() {
213 ColorValue::Named(name) => surface_named(theme, name, variant),
214 ColorValue::Custom(c) => surface_custom(theme, c, variant),
215 }
216}
217
218pub(crate) fn solid(theme: &Theme, color: ColorValue) -> Hsla {
221 match color {
222 ColorValue::Named(name) => theme.color(name, theme.primary_shade()).hsla(),
223 ColorValue::Custom(c) => c,
224 }
225}
226
227fn surface_custom(theme: &Theme, c: Hsla, variant: Variant) -> Surface {
230 let dark = theme.scheme.is_dark();
231 let transparent = gpui::transparent_black();
232 let hover_fill = if dark {
233 shift_l(c, 0.06)
234 } else {
235 shift_l(c, -0.06)
236 };
237 match variant {
238 Variant::Filled => Surface {
239 bg: c,
240 bg_hover: hover_fill,
241 fg: readable_on(c),
242 border: None,
243 },
244 Variant::Light => Surface {
245 bg: with_alpha(c, if dark { 0.20 } else { 0.12 }),
246 bg_hover: with_alpha(c, if dark { 0.28 } else { 0.20 }),
247 fg: c,
248 border: None,
249 },
250 Variant::Outline => Surface {
251 bg: transparent,
252 bg_hover: with_alpha(c, 0.08),
253 fg: c,
254 border: Some(c),
255 },
256 Variant::Subtle => Surface {
257 bg: transparent,
258 bg_hover: with_alpha(c, if dark { 0.15 } else { 0.08 }),
259 fg: c,
260 border: None,
261 },
262 Variant::Default => Surface {
263 bg: theme.surface().hsla(),
264 bg_hover: theme.surface_hover().hsla(),
265 fg: theme.text().hsla(),
266 border: Some(theme.border().hsla()),
267 },
268 Variant::Transparent => Surface {
269 bg: transparent,
270 bg_hover: transparent,
271 fg: c,
272 border: None,
273 },
274 Variant::White => Surface {
275 bg: theme.white.hsla(),
276 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
277 fg: c,
278 border: None,
279 },
280 }
281}
282
283fn surface_named(theme: &Theme, name: ColorName, variant: Variant) -> Surface {
285 let dark = theme.scheme.is_dark();
286 let transparent = gpui::transparent_black();
287 let shade = theme.primary_shade();
288 let filled = theme.color(name, shade);
289 let filled_hover = theme.color(name, (shade + 1).min(9));
290 let accent = theme.color(name, if dark { 4 } else { 6 });
292
293 match variant {
294 Variant::Filled => Surface {
295 bg: filled.hsla(),
296 bg_hover: filled_hover.hsla(),
297 fg: filled.contrasting().hsla(),
298 border: None,
299 },
300 Variant::Light => {
301 let (bg, bg_hover, fg) = if dark {
302 (
303 theme.color(name, 5).alpha(0.20),
304 theme.color(name, 5).alpha(0.30),
305 theme.color(name, 2).hsla(),
306 )
307 } else {
308 (
309 theme.color(name, 0).hsla(),
310 theme.color(name, 1).hsla(),
311 theme.color(name, 8).hsla(),
312 )
313 };
314 Surface {
315 bg,
316 bg_hover,
317 fg,
318 border: None,
319 }
320 }
321 Variant::Outline => Surface {
322 bg: transparent,
323 bg_hover: accent.alpha(0.08),
324 fg: accent.hsla(),
325 border: Some(accent.hsla()),
326 },
327 Variant::Subtle => Surface {
328 bg: transparent,
329 bg_hover: accent.alpha(if dark { 0.15 } else { 0.08 }),
330 fg: accent.hsla(),
331 border: None,
332 },
333 Variant::Default => Surface {
334 bg: theme.surface().hsla(),
335 bg_hover: theme.surface_hover().hsla(),
336 fg: theme.text().hsla(),
337 border: Some(theme.border().hsla()),
338 },
339 Variant::Transparent => Surface {
340 bg: transparent,
341 bg_hover: transparent,
342 fg: accent.hsla(),
343 border: None,
344 },
345 Variant::White => Surface {
346 bg: theme.white.hsla(),
347 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
348 fg: theme.color(name, shade).hsla(),
349 border: None,
350 },
351 }
352}