1use crate::{ActiveTheme, PixelsExt as _};
2use gpui::{
3 App, BoxShadow, Corners, DefiniteLength, Div, Edges, FocusHandle, Hsla, ParentElement, Pixels,
4 Refineable, StyleRefinement, Styled, Window, div, point, px,
5};
6use serde::{Deserialize, Serialize};
7
8#[inline(always)]
10pub fn h_flex() -> Div {
11 div().h_flex()
12}
13
14#[inline(always)]
16pub fn v_flex() -> Div {
17 div().v_flex()
18}
19
20#[inline(always)]
28pub fn box_shadow(
29 x: impl Into<Pixels>,
30 y: impl Into<Pixels>,
31 blur: impl Into<Pixels>,
32 spread: impl Into<Pixels>,
33 color: Hsla,
34) -> BoxShadow {
35 BoxShadow {
36 offset: point(x.into(), y.into()),
37 blur_radius: blur.into(),
38 spread_radius: spread.into(),
39 color,
40 }
41}
42
43macro_rules! font_weight {
44 ($fn:ident, $const:ident) => {
45 #[inline]
47 fn $fn(self) -> Self {
48 self.font_weight(gpui::FontWeight::$const)
49 }
50 };
51}
52
53#[cfg_attr(
55 any(feature = "inspector", debug_assertions),
56 gpui_macros::derive_inspector_reflection
57)]
58pub trait StyledExt: Styled + Sized {
59 fn refine_style(mut self, style: &StyleRefinement) -> Self {
61 self.style().refine(style);
62 self
63 }
64
65 #[inline(always)]
67 fn h_flex(self) -> Self {
68 self.flex().flex_row().items_center()
69 }
70
71 #[inline(always)]
73 fn v_flex(self) -> Self {
74 self.flex().flex_col()
75 }
76
77 fn paddings<L>(self, paddings: impl Into<Edges<L>>) -> Self
79 where
80 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
81 {
82 let paddings = paddings.into();
83 self.pt(paddings.top.into())
84 .pb(paddings.bottom.into())
85 .pl(paddings.left.into())
86 .pr(paddings.right.into())
87 }
88
89 fn margins<L>(self, margins: impl Into<Edges<L>>) -> Self
91 where
92 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
93 {
94 let margins = margins.into();
95 self.mt(margins.top.into())
96 .mb(margins.bottom.into())
97 .ml(margins.left.into())
98 .mr(margins.right.into())
99 }
100
101 fn debug_red(self) -> Self {
103 if cfg!(debug_assertions) {
104 self.border_1().border_color(crate::red_500())
105 } else {
106 self
107 }
108 }
109
110 fn debug_blue(self) -> Self {
112 if cfg!(debug_assertions) {
113 self.border_1().border_color(crate::blue_500())
114 } else {
115 self
116 }
117 }
118
119 fn debug_yellow(self) -> Self {
121 if cfg!(debug_assertions) {
122 self.border_1().border_color(crate::yellow_500())
123 } else {
124 self
125 }
126 }
127
128 fn debug_green(self) -> Self {
130 if cfg!(debug_assertions) {
131 self.border_1().border_color(crate::green_500())
132 } else {
133 self
134 }
135 }
136
137 fn debug_pink(self) -> Self {
139 if cfg!(debug_assertions) {
140 self.border_1().border_color(crate::pink_500())
141 } else {
142 self
143 }
144 }
145
146 fn debug_focused(self, focus_handle: &FocusHandle, window: &Window, cx: &App) -> Self {
148 if cfg!(debug_assertions) {
149 if focus_handle.contains_focused(window, cx) {
150 self.debug_blue()
151 } else {
152 self
153 }
154 } else {
155 self
156 }
157 }
158
159 #[inline]
161 fn focused_border(self, cx: &App) -> Self {
162 self.border_1().border_color(cx.theme().ring)
163 }
164
165 font_weight!(font_thin, THIN);
166 font_weight!(font_extralight, EXTRA_LIGHT);
167 font_weight!(font_light, LIGHT);
168 font_weight!(font_normal, NORMAL);
169 font_weight!(font_medium, MEDIUM);
170 font_weight!(font_semibold, SEMIBOLD);
171 font_weight!(font_bold, BOLD);
172 font_weight!(font_extrabold, EXTRA_BOLD);
173 font_weight!(font_black, BLACK);
174
175 #[inline]
177 fn popover_style(self, cx: &App) -> Self {
178 self.bg(cx.theme().popover)
179 .text_color(cx.theme().popover_foreground)
180 .border_1()
181 .border_color(cx.theme().border)
182 .shadow_lg()
183 .rounded(cx.theme().radius)
184 }
185
186 fn corner_radii(self, radius: Corners<Pixels>) -> Self {
188 self.rounded_tl(radius.top_left)
189 .rounded_tr(radius.top_right)
190 .rounded_bl(radius.bottom_left)
191 .rounded_br(radius.bottom_right)
192 }
193}
194
195impl<E: Styled> StyledExt for E {}
196
197#[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
199pub enum Size {
200 Size(Pixels),
201 XSmall,
202 Small,
203 #[default]
204 Medium,
205 Large,
206}
207
208impl Size {
209 fn as_f32(&self) -> f32 {
210 match self {
211 Size::Size(val) => val.as_f32(),
212 Size::XSmall => 0.,
213 Size::Small => 1.,
214 Size::Medium => 2.,
215 Size::Large => 3.,
216 }
217 }
218
219 pub fn as_str(&self) -> &'static str {
221 match self {
222 Size::XSmall => "xs",
223 Size::Small => "sm",
224 Size::Medium => "md",
225 Size::Large => "lg",
226 Size::Size(_) => "custom",
227 }
228 }
229
230 pub fn from_str(size: &str) -> Self {
239 match size.to_lowercase().as_str() {
240 "xs" | "xsmall" => Size::XSmall,
241 "sm" | "small" => Size::Small,
242 "md" | "medium" => Size::Medium,
243 "lg" | "large" => Size::Large,
244 _ => Size::Medium,
245 }
246 }
247
248 #[inline]
250 pub fn table_row_height(&self) -> Pixels {
251 match self {
252 Size::XSmall => px(26.),
253 Size::Small => px(30.),
254 Size::Large => px(40.),
255 _ => px(32.),
256 }
257 }
258
259 #[inline]
261 pub fn table_cell_padding(&self) -> Edges<Pixels> {
262 match self {
263 Size::XSmall => Edges {
264 top: px(2.),
265 bottom: px(2.),
266 left: px(4.),
267 right: px(4.),
268 },
269 Size::Small => Edges {
270 top: px(3.),
271 bottom: px(3.),
272 left: px(6.),
273 right: px(6.),
274 },
275 Size::Large => Edges {
276 top: px(8.),
277 bottom: px(8.),
278 left: px(12.),
279 right: px(12.),
280 },
281 _ => Edges {
282 top: px(4.),
283 bottom: px(4.),
284 left: px(8.),
285 right: px(8.),
286 },
287 }
288 }
289
290 pub fn smaller(&self) -> Self {
292 match self {
293 Size::XSmall => Size::XSmall,
294 Size::Small => Size::XSmall,
295 Size::Medium => Size::Small,
296 Size::Large => Size::Medium,
297 Size::Size(val) => Size::Size(*val * 0.2),
298 }
299 }
300
301 pub fn larger(&self) -> Self {
303 match self {
304 Size::XSmall => Size::Small,
305 Size::Small => Size::Medium,
306 Size::Medium => Size::Large,
307 Size::Large => Size::Large,
308 Size::Size(val) => Size::Size(*val * 1.2),
309 }
310 }
311
312 pub fn max(&self, other: Self) -> Self {
316 match (self, other) {
317 (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().min(b.as_f32()))),
318 (Size::Size(a), _) => Size::Size(*a),
319 (_, Size::Size(b)) => Size::Size(b),
320 (a, b) if a.as_f32() < b.as_f32() => *a,
321 _ => other,
322 }
323 }
324
325 pub fn min(&self, other: Self) -> Self {
329 match (self, other) {
330 (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().max(b.as_f32()))),
331 (Size::Size(a), _) => Size::Size(*a),
332 (_, Size::Size(b)) => Size::Size(b),
333 (a, b) if a.as_f32() > b.as_f32() => *a,
334 _ => other,
335 }
336 }
337
338 pub fn input_px(&self) -> Pixels {
340 match self {
341 Self::Large => px(16.),
342 Self::Medium => px(12.),
343 Self::Small => px(8.),
344 Self::XSmall => px(4.),
345 _ => px(8.),
346 }
347 }
348
349 pub fn input_py(&self) -> Pixels {
351 match self {
352 Size::Large => px(10.),
353 Size::Medium => px(8.),
354 Size::Small => px(2.),
355 Size::XSmall => px(0.),
356 _ => px(2.),
357 }
358 }
359}
360
361impl From<Pixels> for Size {
362 fn from(size: Pixels) -> Self {
363 Size::Size(size)
364 }
365}
366
367pub trait Selectable: Sized {
369 fn selected(self, selected: bool) -> Self;
371
372 fn is_selected(&self) -> bool;
374
375 fn secondary_selected(self, _: bool) -> Self {
377 self
378 }
379}
380
381pub trait Disableable {
383 fn disabled(self, disabled: bool) -> Self;
385}
386
387pub trait Sizable: Sized {
390 fn with_size(self, size: impl Into<Size>) -> Self;
395
396 #[inline(always)]
398 fn xsmall(self) -> Self {
399 self.with_size(Size::XSmall)
400 }
401
402 #[inline(always)]
404 fn small(self) -> Self {
405 self.with_size(Size::Small)
406 }
407
408 #[inline(always)]
410 fn large(self) -> Self {
411 self.with_size(Size::Large)
412 }
413}
414
415#[allow(unused)]
416pub trait StyleSized<T: Styled> {
417 fn input_text_size(self, size: Size) -> Self;
418 fn input_size(self, size: Size) -> Self;
419 fn input_pl(self, size: Size) -> Self;
420 fn input_pr(self, size: Size) -> Self;
421 fn input_px(self, size: Size) -> Self;
422 fn input_py(self, size: Size) -> Self;
423 fn input_h(self, size: Size) -> Self;
424 fn list_size(self, size: Size) -> Self;
425 fn list_px(self, size: Size) -> Self;
426 fn list_py(self, size: Size) -> Self;
427 fn size_with(self, size: Size) -> Self;
429 fn table_cell_size(self, size: Size) -> Self;
431 fn button_text_size(self, size: Size) -> Self;
432}
433
434impl<T: Styled> StyleSized<T> for T {
435 #[inline]
436 fn input_text_size(self, size: Size) -> Self {
437 match size {
438 Size::XSmall => self.text_xs(),
439 Size::Small => self.text_sm(),
440 Size::Medium => self.text_sm(),
441 Size::Large => self.text_base(),
442 Size::Size(size) => self.text_size(size * 0.875),
443 }
444 }
445
446 #[inline]
447 fn input_size(self, size: Size) -> Self {
448 self.input_px(size).input_py(size).input_h(size)
449 }
450
451 #[inline]
452 fn input_pl(self, size: Size) -> Self {
453 self.pl(size.input_px())
454 }
455
456 #[inline]
457 fn input_pr(self, size: Size) -> Self {
458 self.pr(size.input_px())
459 }
460
461 #[inline]
462 fn input_px(self, size: Size) -> Self {
463 self.px(size.input_px())
464 }
465
466 #[inline]
467 fn input_py(self, size: Size) -> Self {
468 self.py(size.input_py())
469 }
470
471 #[inline]
472 fn input_h(self, size: Size) -> Self {
473 match size {
474 Size::Large => self.h_11(),
475 Size::Medium => self.h_8(),
476 Size::Small => self.h_6(),
477 Size::XSmall => self.h_5(),
478 _ => self.h_6(),
479 }
480 }
481
482 #[inline]
483 fn list_size(self, size: Size) -> Self {
484 self.list_px(size).list_py(size).input_text_size(size)
485 }
486
487 #[inline]
488 fn list_px(self, size: Size) -> Self {
489 match size {
490 Size::Small => self.px_2(),
491 _ => self.px_3(),
492 }
493 }
494
495 #[inline]
496 fn list_py(self, size: Size) -> Self {
497 match size {
498 Size::Large => self.py_2(),
499 Size::Medium => self.py_1(),
500 Size::Small => self.py_0p5(),
501 _ => self.py_1(),
502 }
503 }
504
505 #[inline]
506 fn size_with(self, size: Size) -> Self {
507 match size {
508 Size::Large => self.size_11(),
509 Size::Medium => self.size_8(),
510 Size::Small => self.size_5(),
511 Size::XSmall => self.size_4(),
512 Size::Size(size) => self.size(size),
513 }
514 }
515
516 #[inline]
517 fn table_cell_size(self, size: Size) -> Self {
518 let padding = size.table_cell_padding();
519 match size {
520 Size::XSmall => self.text_sm(),
521 Size::Small => self.text_sm(),
522 _ => self,
523 }
524 .pl(padding.left)
525 .pr(padding.right)
526 .pt(padding.top)
527 .pb(padding.bottom)
528 }
529
530 fn button_text_size(self, size: Size) -> Self {
531 match size {
532 Size::XSmall => self.text_xs(),
533 Size::Small => self.text_sm(),
534 _ => self.text_base(),
535 }
536 }
537}
538
539pub(crate) trait FocusableExt<T: ParentElement + Styled + Sized> {
540 fn focus_ring(self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self;
542}
543
544impl<T: ParentElement + Styled + Sized> FocusableExt<T> for T {
545 fn focus_ring(mut self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self {
546 if !is_focused {
547 return self;
548 }
549
550 const RING_BORDER_WIDTH: Pixels = px(1.5);
551 let rem_size = window.rem_size();
552 let style = self.style();
553
554 let border_widths = Edges::<Pixels> {
555 top: style
556 .border_widths
557 .top
558 .map(|v| v.to_pixels(rem_size))
559 .unwrap_or_default(),
560 bottom: style
561 .border_widths
562 .bottom
563 .map(|v| v.to_pixels(rem_size))
564 .unwrap_or_default(),
565 left: style
566 .border_widths
567 .left
568 .map(|v| v.to_pixels(rem_size))
569 .unwrap_or_default(),
570 right: style
571 .border_widths
572 .right
573 .map(|v| v.to_pixels(rem_size))
574 .unwrap_or_default(),
575 };
576
577 let radius = Corners::<Pixels> {
579 top_left: style
580 .corner_radii
581 .top_left
582 .map(|v| v.to_pixels(rem_size))
583 .unwrap_or_default(),
584 top_right: style
585 .corner_radii
586 .top_right
587 .map(|v| v.to_pixels(rem_size))
588 .unwrap_or_default(),
589 bottom_left: style
590 .corner_radii
591 .bottom_left
592 .map(|v| v.to_pixels(rem_size))
593 .unwrap_or_default(),
594 bottom_right: style
595 .corner_radii
596 .bottom_right
597 .map(|v| v.to_pixels(rem_size))
598 .unwrap_or_default(),
599 }
600 .map(|v| *v + RING_BORDER_WIDTH);
601
602 let mut inner_style = StyleRefinement::default();
603 inner_style.corner_radii.top_left = Some(radius.top_left.into());
604 inner_style.corner_radii.top_right = Some(radius.top_right.into());
605 inner_style.corner_radii.bottom_left = Some(radius.bottom_left.into());
606 inner_style.corner_radii.bottom_right = Some(radius.bottom_right.into());
607
608 let inset = RING_BORDER_WIDTH + margins;
609
610 self.child(
611 div()
612 .flex_none()
613 .absolute()
614 .top(-(inset + border_widths.top))
615 .left(-(inset + border_widths.left))
616 .right(-(inset + border_widths.right))
617 .bottom(-(inset + border_widths.bottom))
618 .border(RING_BORDER_WIDTH)
619 .border_color(cx.theme().ring.alpha(0.2))
620 .refine_style(&inner_style),
621 )
622 }
623}
624
625pub trait Collapsible {
627 fn collapsed(self, collapsed: bool) -> Self;
628 fn is_collapsed(&self) -> bool;
629}
630
631#[cfg(test)]
632mod tests {
633 use gpui::px;
634
635 use crate::Size;
636
637 #[test]
638 fn test_size_max_min() {
639 assert_eq!(Size::Small.min(Size::XSmall), Size::Small);
640 assert_eq!(Size::XSmall.min(Size::Small), Size::Small);
641 assert_eq!(Size::Small.min(Size::Medium), Size::Medium);
642 assert_eq!(Size::Medium.min(Size::Large), Size::Large);
643 assert_eq!(Size::Large.min(Size::Small), Size::Large);
644
645 assert_eq!(
646 Size::Size(px(10.)).min(Size::Size(px(20.))),
647 Size::Size(px(20.))
648 );
649
650 assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall);
652 assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall);
653 assert_eq!(Size::Small.max(Size::Medium), Size::Small);
654 assert_eq!(Size::Medium.max(Size::Large), Size::Medium);
655 assert_eq!(Size::Large.max(Size::Small), Size::Small);
656
657 assert_eq!(
658 Size::Size(px(10.)).max(Size::Size(px(20.))),
659 Size::Size(px(10.))
660 );
661 }
662
663 #[test]
664 fn test_size_as_str() {
665 assert_eq!(Size::XSmall.as_str(), "xs");
666 assert_eq!(Size::Small.as_str(), "sm");
667 assert_eq!(Size::Medium.as_str(), "md");
668 assert_eq!(Size::Large.as_str(), "lg");
669 assert_eq!(Size::Size(px(15.)).as_str(), "custom");
670 }
671
672 #[test]
673 fn test_size_from_str() {
674 assert_eq!(Size::from_str("xs"), Size::XSmall);
675 assert_eq!(Size::from_str("xsmall"), Size::XSmall);
676 assert_eq!(Size::from_str("sm"), Size::Small);
677 assert_eq!(Size::from_str("small"), Size::Small);
678 assert_eq!(Size::from_str("md"), Size::Medium);
679 assert_eq!(Size::from_str("medium"), Size::Medium);
680 assert_eq!(Size::from_str("lg"), Size::Large);
681 assert_eq!(Size::from_str("large"), Size::Large);
682 assert_eq!(Size::from_str("unknown"), Size::Medium);
683
684 assert_eq!(Size::from_str("XS"), Size::XSmall);
686 assert_eq!(Size::from_str("SMALL"), Size::Small);
687 assert_eq!(Size::from_str("Md"), Size::Medium);
688 }
689}