1use gpui::{AlignContent, AlignItems, Div, Hsla, Styled};
6
7use crate::theme::{ColorName, Size, Theme};
8
9pub trait StyleExt: Sized {
12 fn apply(self, f: impl FnOnce(Self) -> Self) -> Self {
15 f(self)
16 }
17}
18
19impl<T: Styled> StyleExt for T {}
20
21pub(crate) trait FlexExt: Sized {
26 fn grow(self, factor: f32) -> Self;
28 fn shrink(self, factor: f32) -> Self;
30 fn items_stretch(self) -> Self;
32 fn justify_evenly(self) -> Self;
34}
35
36impl FlexExt for Div {
37 fn grow(mut self, factor: f32) -> Self {
38 self.style().flex_grow = Some(factor);
39 self
40 }
41
42 fn shrink(mut self, factor: f32) -> Self {
43 self.style().flex_shrink = Some(factor);
44 self
45 }
46
47 fn items_stretch(mut self) -> Self {
48 self.style().align_items = Some(AlignItems::Stretch);
49 self
50 }
51
52 fn justify_evenly(mut self) -> Self {
53 self.style().justify_content = Some(AlignContent::SpaceEvenly);
54 self
55 }
56}
57
58pub(crate) fn icon_size(size: Size) -> f32 {
61 match size {
62 Size::Xs => 18.0,
63 Size::Sm => 22.0,
64 Size::Md => 28.0,
65 Size::Lg => 34.0,
66 Size::Xl => 44.0,
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub enum Variant {
73 #[default]
75 Filled,
76 Light,
78 Outline,
80 Subtle,
82 Default,
84 Transparent,
86 White,
88}
89
90#[derive(Debug, Clone, Copy)]
92pub struct Surface {
93 pub bg: Hsla,
94 pub bg_hover: Hsla,
95 pub fg: Hsla,
96 pub border: Option<Hsla>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq)]
106pub enum ColorValue {
107 Named(ColorName),
109 Custom(Hsla),
111}
112
113impl Default for ColorValue {
114 fn default() -> Self {
115 ColorValue::Named(ColorName::Blue)
116 }
117}
118
119impl From<ColorName> for ColorValue {
120 fn from(name: ColorName) -> Self {
121 ColorValue::Named(name)
122 }
123}
124
125impl From<Hsla> for ColorValue {
126 fn from(color: Hsla) -> Self {
127 ColorValue::Custom(color)
128 }
129}
130
131impl ColorValue {
132 pub fn accent(self, theme: &Theme) -> Hsla {
134 match self {
135 ColorValue::Named(name) => theme.color(name, theme.primary_shade()).hsla(),
136 ColorValue::Custom(c) => c,
137 }
138 }
139
140 pub fn soft(self, theme: &Theme) -> Hsla {
142 let dark = theme.scheme.is_dark();
143 match self {
144 ColorValue::Named(name) if dark => theme.color(name, 5).alpha(0.20),
145 ColorValue::Named(name) => theme.color(name, 0).hsla(),
146 ColorValue::Custom(c) => with_alpha(c, if dark { 0.22 } else { 0.12 }),
147 }
148 }
149}
150
151fn shift_l(c: Hsla, delta: f32) -> Hsla {
152 Hsla {
153 l: (c.l + delta).clamp(0.0, 1.0),
154 ..c
155 }
156}
157
158fn with_alpha(c: Hsla, a: f32) -> Hsla {
159 Hsla { a, ..c }
160}
161
162fn readable_on(c: Hsla) -> Hsla {
164 let v = if c.l > 0.6 { 0.0 } else { 1.0 };
165 Hsla {
166 h: 0.0,
167 s: 0.0,
168 l: v,
169 a: 1.0,
170 }
171}
172
173pub fn surface(theme: &Theme, color: impl Into<ColorValue>, variant: Variant) -> Surface {
176 match color.into() {
177 ColorValue::Named(name) => surface_named(theme, name, variant),
178 ColorValue::Custom(c) => surface_custom(theme, c, variant),
179 }
180}
181
182fn surface_custom(theme: &Theme, c: Hsla, variant: Variant) -> Surface {
185 let dark = theme.scheme.is_dark();
186 let transparent = gpui::transparent_black();
187 let hover_fill = if dark {
188 shift_l(c, 0.06)
189 } else {
190 shift_l(c, -0.06)
191 };
192 match variant {
193 Variant::Filled => Surface {
194 bg: c,
195 bg_hover: hover_fill,
196 fg: readable_on(c),
197 border: None,
198 },
199 Variant::Light => Surface {
200 bg: with_alpha(c, if dark { 0.20 } else { 0.12 }),
201 bg_hover: with_alpha(c, if dark { 0.28 } else { 0.20 }),
202 fg: c,
203 border: None,
204 },
205 Variant::Outline => Surface {
206 bg: transparent,
207 bg_hover: with_alpha(c, 0.08),
208 fg: c,
209 border: Some(c),
210 },
211 Variant::Subtle => Surface {
212 bg: transparent,
213 bg_hover: with_alpha(c, if dark { 0.15 } else { 0.08 }),
214 fg: c,
215 border: None,
216 },
217 Variant::Default => Surface {
218 bg: theme.surface().hsla(),
219 bg_hover: theme.surface_hover().hsla(),
220 fg: theme.text().hsla(),
221 border: Some(theme.border().hsla()),
222 },
223 Variant::Transparent => Surface {
224 bg: transparent,
225 bg_hover: transparent,
226 fg: c,
227 border: None,
228 },
229 Variant::White => Surface {
230 bg: theme.white.hsla(),
231 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
232 fg: c,
233 border: None,
234 },
235 }
236}
237
238fn surface_named(theme: &Theme, name: ColorName, variant: Variant) -> Surface {
240 let dark = theme.scheme.is_dark();
241 let transparent = gpui::transparent_black();
242 let shade = theme.primary_shade();
243 let filled = theme.color(name, shade);
244 let filled_hover = theme.color(name, (shade + 1).min(9));
245 let accent = theme.color(name, if dark { 4 } else { 6 });
247
248 match variant {
249 Variant::Filled => Surface {
250 bg: filled.hsla(),
251 bg_hover: filled_hover.hsla(),
252 fg: filled.contrasting().hsla(),
253 border: None,
254 },
255 Variant::Light => {
256 let (bg, bg_hover, fg) = if dark {
257 (
258 theme.color(name, 5).alpha(0.20),
259 theme.color(name, 5).alpha(0.30),
260 theme.color(name, 2).hsla(),
261 )
262 } else {
263 (
264 theme.color(name, 0).hsla(),
265 theme.color(name, 1).hsla(),
266 theme.color(name, 8).hsla(),
267 )
268 };
269 Surface {
270 bg,
271 bg_hover,
272 fg,
273 border: None,
274 }
275 }
276 Variant::Outline => Surface {
277 bg: transparent,
278 bg_hover: accent.alpha(0.08),
279 fg: accent.hsla(),
280 border: Some(accent.hsla()),
281 },
282 Variant::Subtle => Surface {
283 bg: transparent,
284 bg_hover: accent.alpha(if dark { 0.15 } else { 0.08 }),
285 fg: accent.hsla(),
286 border: None,
287 },
288 Variant::Default => Surface {
289 bg: theme.surface().hsla(),
290 bg_hover: theme.surface_hover().hsla(),
291 fg: theme.text().hsla(),
292 border: Some(theme.border().hsla()),
293 },
294 Variant::Transparent => Surface {
295 bg: transparent,
296 bg_hover: transparent,
297 fg: accent.hsla(),
298 border: None,
299 },
300 Variant::White => Surface {
301 bg: theme.white.hsla(),
302 bg_hover: theme.color(ColorName::Gray, 0).hsla(),
303 fg: theme.color(name, shade).hsla(),
304 border: None,
305 },
306 }
307}