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