1use std::fmt::{self, Display, Formatter};
2
3use crate::{
4 scroll::{Scrollable, ScrollbarAxis},
5 ActiveTheme,
6};
7use gpui::{
8 div, point, px, AbsoluteLength, App, Axis, BoxShadow, Corners, DefiniteLength, Div, Edges,
9 Element, FocusHandle, Hsla, Length, ParentElement, Pixels, Refineable, StyleRefinement, Styled,
10 Window,
11};
12use serde::{Deserialize, Serialize};
13
14#[inline(always)]
16pub fn h_flex() -> Div {
17 div().h_flex()
18}
19
20#[inline(always)]
22pub fn v_flex() -> Div {
23 div().v_flex()
24}
25
26#[inline(always)]
34pub fn box_shadow(
35 x: impl Into<Pixels>,
36 y: impl Into<Pixels>,
37 blur: impl Into<Pixels>,
38 spread: impl Into<Pixels>,
39 color: Hsla,
40) -> BoxShadow {
41 BoxShadow {
42 offset: point(x.into(), y.into()),
43 blur_radius: blur.into(),
44 spread_radius: spread.into(),
45 color,
46 }
47}
48
49macro_rules! font_weight {
50 ($fn:ident, $const:ident) => {
51 #[inline]
53 fn $fn(self) -> Self {
54 self.font_weight(gpui::FontWeight::$const)
55 }
56 };
57}
58
59#[cfg_attr(
61 any(feature = "inspector", debug_assertions),
62 gpui_macros::derive_inspector_reflection
63)]
64pub trait StyledExt: Styled + Sized {
65 fn refine_style(mut self, style: &StyleRefinement) -> Self {
67 self.style().refine(style);
68 self
69 }
70
71 #[inline(always)]
73 fn h_flex(self) -> Self {
74 self.flex().flex_row().items_center()
75 }
76
77 #[inline(always)]
79 fn v_flex(self) -> Self {
80 self.flex().flex_col()
81 }
82
83 fn paddings<L>(self, paddings: impl Into<Edges<L>>) -> Self
85 where
86 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
87 {
88 let paddings = paddings.into();
89 self.pt(paddings.top.into())
90 .pb(paddings.bottom.into())
91 .pl(paddings.left.into())
92 .pr(paddings.right.into())
93 }
94
95 fn margins<L>(self, margins: impl Into<Edges<L>>) -> Self
97 where
98 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
99 {
100 let margins = margins.into();
101 self.mt(margins.top.into())
102 .mb(margins.bottom.into())
103 .ml(margins.left.into())
104 .mr(margins.right.into())
105 }
106
107 fn debug_red(self) -> Self {
109 if cfg!(debug_assertions) {
110 self.border_1().border_color(crate::red_500())
111 } else {
112 self
113 }
114 }
115
116 fn debug_blue(self) -> Self {
118 if cfg!(debug_assertions) {
119 self.border_1().border_color(crate::blue_500())
120 } else {
121 self
122 }
123 }
124
125 fn debug_yellow(self) -> Self {
127 if cfg!(debug_assertions) {
128 self.border_1().border_color(crate::yellow_500())
129 } else {
130 self
131 }
132 }
133
134 fn debug_green(self) -> Self {
136 if cfg!(debug_assertions) {
137 self.border_1().border_color(crate::green_500())
138 } else {
139 self
140 }
141 }
142
143 fn debug_pink(self) -> Self {
145 if cfg!(debug_assertions) {
146 self.border_1().border_color(crate::pink_500())
147 } else {
148 self
149 }
150 }
151
152 fn debug_focused(self, focus_handle: &FocusHandle, window: &Window, cx: &App) -> Self {
154 if cfg!(debug_assertions) {
155 if focus_handle.contains_focused(window, cx) {
156 self.debug_blue()
157 } else {
158 self
159 }
160 } else {
161 self
162 }
163 }
164
165 #[inline]
167 fn focused_border(self, cx: &App) -> Self {
168 self.border_1().border_color(cx.theme().ring)
169 }
170
171 #[inline]
175 fn scrollable(self, axis: impl Into<ScrollbarAxis>) -> Scrollable<Self>
176 where
177 Self: Element,
178 {
179 Scrollable::new(axis, self)
180 }
181
182 font_weight!(font_thin, THIN);
183 font_weight!(font_extralight, EXTRA_LIGHT);
184 font_weight!(font_light, LIGHT);
185 font_weight!(font_normal, NORMAL);
186 font_weight!(font_medium, MEDIUM);
187 font_weight!(font_semibold, SEMIBOLD);
188 font_weight!(font_bold, BOLD);
189 font_weight!(font_extrabold, EXTRA_BOLD);
190 font_weight!(font_black, BLACK);
191
192 #[inline]
194 fn popover_style(self, cx: &App) -> Self {
195 self.bg(cx.theme().popover)
196 .text_color(cx.theme().popover_foreground)
197 .border_1()
198 .border_color(cx.theme().border)
199 .shadow_lg()
200 .rounded(cx.theme().radius)
201 }
202
203 fn corner_radii(self, radius: Corners<Pixels>) -> Self {
205 self.rounded_tl(radius.top_left)
206 .rounded_tr(radius.top_right)
207 .rounded_bl(radius.bottom_left)
208 .rounded_br(radius.bottom_right)
209 }
210}
211
212impl<E: Styled> StyledExt for E {}
213
214#[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
216pub enum Size {
217 Size(Pixels),
218 XSmall,
219 Small,
220 #[default]
221 Medium,
222 Large,
223}
224
225impl Size {
226 fn as_f32(&self) -> f32 {
227 match self {
228 Size::Size(val) => val.as_f32(),
229 Size::XSmall => 0.,
230 Size::Small => 1.,
231 Size::Medium => 2.,
232 Size::Large => 3.,
233 }
234 }
235
236 #[inline]
238 pub fn table_row_height(&self) -> Pixels {
239 match self {
240 Size::XSmall => px(26.),
241 Size::Small => px(30.),
242 Size::Large => px(40.),
243 _ => px(32.),
244 }
245 }
246
247 #[inline]
249 pub fn table_cell_padding(&self) -> Edges<Pixels> {
250 match self {
251 Size::XSmall => Edges {
252 top: px(2.),
253 bottom: px(2.),
254 left: px(4.),
255 right: px(4.),
256 },
257 Size::Small => Edges {
258 top: px(3.),
259 bottom: px(3.),
260 left: px(6.),
261 right: px(6.),
262 },
263 Size::Large => Edges {
264 top: px(8.),
265 bottom: px(8.),
266 left: px(12.),
267 right: px(12.),
268 },
269 _ => Edges {
270 top: px(4.),
271 bottom: px(4.),
272 left: px(8.),
273 right: px(8.),
274 },
275 }
276 }
277
278 pub fn smaller(&self) -> Self {
280 match self {
281 Size::XSmall => Size::XSmall,
282 Size::Small => Size::XSmall,
283 Size::Medium => Size::Small,
284 Size::Large => Size::Medium,
285 Size::Size(val) => Size::Size(*val * 0.2),
286 }
287 }
288
289 pub fn larger(&self) -> Self {
291 match self {
292 Size::XSmall => Size::Small,
293 Size::Small => Size::Medium,
294 Size::Medium => Size::Large,
295 Size::Large => Size::Large,
296 Size::Size(val) => Size::Size(*val * 1.2),
297 }
298 }
299
300 pub fn max(&self, other: Self) -> Self {
304 match (self, other) {
305 (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().min(b.as_f32()))),
306 (Size::Size(a), _) => Size::Size(*a),
307 (_, Size::Size(b)) => Size::Size(b),
308 (a, b) if a.as_f32() < b.as_f32() => *a,
309 _ => other,
310 }
311 }
312
313 pub fn min(&self, other: Self) -> Self {
317 match (self, other) {
318 (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().max(b.as_f32()))),
319 (Size::Size(a), _) => Size::Size(*a),
320 (_, Size::Size(b)) => Size::Size(b),
321 (a, b) if a.as_f32() > b.as_f32() => *a,
322 _ => other,
323 }
324 }
325
326 pub fn input_px(&self) -> Pixels {
328 match self {
329 Self::Large => px(20.),
330 Self::Medium => px(12.),
331 Self::Small => px(8.),
332 Self::XSmall => px(4.),
333 _ => px(8.),
334 }
335 }
336
337 pub fn input_py(&self) -> Pixels {
339 match self {
340 Size::Large => px(10.),
341 Size::Medium => px(5.),
342 Size::Small => px(2.),
343 Size::XSmall => px(0.),
344 _ => px(2.),
345 }
346 }
347}
348
349impl From<Pixels> for Size {
350 fn from(size: Pixels) -> Self {
351 Size::Size(size)
352 }
353}
354
355pub trait Selectable: Sized {
357 fn selected(self, selected: bool) -> Self;
359
360 fn is_selected(&self) -> bool;
362
363 fn secondary_selected(self, _: bool) -> Self {
365 self
366 }
367}
368
369pub trait Disableable {
371 fn disabled(self, disabled: bool) -> Self;
373}
374
375pub trait Sizable: Sized {
378 fn with_size(self, size: impl Into<Size>) -> Self;
383
384 #[inline(always)]
386 fn xsmall(self) -> Self {
387 self.with_size(Size::XSmall)
388 }
389
390 #[inline(always)]
392 fn small(self) -> Self {
393 self.with_size(Size::Small)
394 }
395
396 #[inline(always)]
398 fn large(self) -> Self {
399 self.with_size(Size::Large)
400 }
401}
402
403#[allow(unused)]
404pub trait StyleSized<T: Styled> {
405 fn input_text_size(self, size: Size) -> Self;
406 fn input_size(self, size: Size) -> Self;
407 fn input_pl(self, size: Size) -> Self;
408 fn input_pr(self, size: Size) -> Self;
409 fn input_px(self, size: Size) -> Self;
410 fn input_py(self, size: Size) -> Self;
411 fn input_h(self, size: Size) -> Self;
412 fn list_size(self, size: Size) -> Self;
413 fn list_px(self, size: Size) -> Self;
414 fn list_py(self, size: Size) -> Self;
415 fn size_with(self, size: Size) -> Self;
417 fn table_cell_size(self, size: Size) -> Self;
419 fn button_text_size(self, size: Size) -> Self;
420}
421
422impl<T: Styled> StyleSized<T> for T {
423 #[inline]
424 fn input_text_size(self, size: Size) -> Self {
425 match size {
426 Size::XSmall => self.text_xs(),
427 Size::Small => self.text_sm(),
428 Size::Medium => self.text_base(),
429 Size::Large => self.text_lg(),
430 Size::Size(size) => self.text_size(size),
431 }
432 }
433
434 #[inline]
435 fn input_size(self, size: Size) -> Self {
436 self.input_px(size).input_py(size).input_h(size)
437 }
438
439 #[inline]
440 fn input_pl(self, size: Size) -> Self {
441 self.pl(size.input_px())
442 }
443
444 #[inline]
445 fn input_pr(self, size: Size) -> Self {
446 self.pr(size.input_px())
447 }
448
449 #[inline]
450 fn input_px(self, size: Size) -> Self {
451 self.px(size.input_px())
452 }
453
454 #[inline]
455 fn input_py(self, size: Size) -> Self {
456 self.py(size.input_py())
457 }
458
459 #[inline]
460 fn input_h(self, size: Size) -> Self {
461 match size {
462 Size::Large => self.h_11(),
463 Size::Medium => self.h_8(),
464 Size::Small => self.h(px(24.)),
465 Size::XSmall => self.h(px(20.)),
466 _ => self.h(px(24.)),
467 }
468 .input_text_size(size)
469 }
470
471 #[inline]
472 fn list_size(self, size: Size) -> Self {
473 self.list_px(size).list_py(size).input_text_size(size)
474 }
475
476 #[inline]
477 fn list_px(self, size: Size) -> Self {
478 match size {
479 Size::Small => self.px_2(),
480 _ => self.px_3(),
481 }
482 }
483
484 #[inline]
485 fn list_py(self, size: Size) -> Self {
486 match size {
487 Size::Large => self.py_2(),
488 Size::Medium => self.py_1(),
489 Size::Small => self.py_0p5(),
490 _ => self.py_1(),
491 }
492 }
493
494 #[inline]
495 fn size_with(self, size: Size) -> Self {
496 match size {
497 Size::Large => self.size_11(),
498 Size::Medium => self.size_8(),
499 Size::Small => self.size_5(),
500 Size::XSmall => self.size_4(),
501 Size::Size(size) => self.size(size),
502 }
503 }
504
505 #[inline]
506 fn table_cell_size(self, size: Size) -> Self {
507 let padding = size.table_cell_padding();
508 match size {
509 Size::XSmall => self.text_sm(),
510 Size::Small => self.text_sm(),
511 _ => self,
512 }
513 .pl(padding.left)
514 .pr(padding.right)
515 .pt(padding.top)
516 .pb(padding.bottom)
517 }
518
519 fn button_text_size(self, size: Size) -> Self {
520 match size {
521 Size::XSmall => self.text_xs(),
522 Size::Small => self.text_sm(),
523 _ => self.text_base(),
524 }
525 }
526}
527
528pub(crate) trait FocusableExt<T: ParentElement + Styled + Sized> {
529 fn focus_ring(self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self;
531}
532
533impl<T: ParentElement + Styled + Sized> FocusableExt<T> for T {
534 fn focus_ring(mut self, is_focused: bool, margins: Pixels, window: &Window, cx: &App) -> Self {
535 if !is_focused {
536 return self;
537 }
538
539 const RING_BORDER_WIDTH: Pixels = px(1.5);
540 let rem_size = window.rem_size();
541 let style = self.style();
542
543 let border_widths = Edges::<Pixels> {
544 top: style
545 .border_widths
546 .top
547 .map(|v| v.to_pixels(rem_size))
548 .unwrap_or_default(),
549 bottom: style
550 .border_widths
551 .bottom
552 .map(|v| v.to_pixels(rem_size))
553 .unwrap_or_default(),
554 left: style
555 .border_widths
556 .left
557 .map(|v| v.to_pixels(rem_size))
558 .unwrap_or_default(),
559 right: style
560 .border_widths
561 .right
562 .map(|v| v.to_pixels(rem_size))
563 .unwrap_or_default(),
564 };
565
566 let radius = Corners::<Pixels> {
568 top_left: style
569 .corner_radii
570 .top_left
571 .map(|v| v.to_pixels(rem_size))
572 .unwrap_or_default(),
573 top_right: style
574 .corner_radii
575 .top_right
576 .map(|v| v.to_pixels(rem_size))
577 .unwrap_or_default(),
578 bottom_left: style
579 .corner_radii
580 .bottom_left
581 .map(|v| v.to_pixels(rem_size))
582 .unwrap_or_default(),
583 bottom_right: style
584 .corner_radii
585 .bottom_right
586 .map(|v| v.to_pixels(rem_size))
587 .unwrap_or_default(),
588 }
589 .map(|v| *v + RING_BORDER_WIDTH);
590
591 let mut inner_style = StyleRefinement::default();
592 inner_style.corner_radii.top_left = Some(radius.top_left.into());
593 inner_style.corner_radii.top_right = Some(radius.top_right.into());
594 inner_style.corner_radii.bottom_left = Some(radius.bottom_left.into());
595 inner_style.corner_radii.bottom_right = Some(radius.bottom_right.into());
596
597 let inset = RING_BORDER_WIDTH + margins;
598
599 self.child(
600 div()
601 .flex_none()
602 .absolute()
603 .top(-(inset + border_widths.top))
604 .left(-(inset + border_widths.left))
605 .right(-(inset + border_widths.right))
606 .bottom(-(inset + border_widths.bottom))
607 .border(RING_BORDER_WIDTH)
608 .border_color(cx.theme().ring.alpha(0.2))
609 .refine_style(&inner_style),
610 )
611 }
612}
613
614pub trait AxisExt {
615 fn is_horizontal(self) -> bool;
616 fn is_vertical(self) -> bool;
617}
618
619impl AxisExt for Axis {
620 #[inline]
621 fn is_horizontal(self) -> bool {
622 self == Axis::Horizontal
623 }
624
625 #[inline]
626 fn is_vertical(self) -> bool {
627 self == Axis::Vertical
628 }
629}
630
631#[derive(Clone, Copy, PartialEq, Eq, Debug)]
632pub enum Placement {
633 Top,
634 Bottom,
635 Left,
636 Right,
637}
638
639impl Display for Placement {
640 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
641 match self {
642 Placement::Top => write!(f, "Top"),
643 Placement::Bottom => write!(f, "Bottom"),
644 Placement::Left => write!(f, "Left"),
645 Placement::Right => write!(f, "Right"),
646 }
647 }
648}
649
650impl Placement {
651 #[inline]
652 pub fn is_horizontal(&self) -> bool {
653 match self {
654 Placement::Left | Placement::Right => true,
655 _ => false,
656 }
657 }
658
659 #[inline]
660 pub fn is_vertical(&self) -> bool {
661 match self {
662 Placement::Top | Placement::Bottom => true,
663 _ => false,
664 }
665 }
666
667 #[inline]
668 pub fn axis(&self) -> Axis {
669 match self {
670 Placement::Top | Placement::Bottom => Axis::Vertical,
671 Placement::Left | Placement::Right => Axis::Horizontal,
672 }
673 }
674}
675
676#[derive(Clone, Copy, PartialEq, Eq, Debug)]
678pub enum Side {
679 Left,
680 Right,
681}
682
683impl Side {
684 #[inline]
686 pub fn is_left(&self) -> bool {
687 matches!(self, Self::Left)
688 }
689
690 #[inline]
692 pub fn is_right(&self) -> bool {
693 matches!(self, Self::Right)
694 }
695}
696
697pub trait Collapsible {
699 fn collapsed(self, collapsed: bool) -> Self;
700 fn is_collapsed(&self) -> bool;
701}
702
703pub trait PixelsExt {
705 fn as_f32(&self) -> f32;
706 fn as_f64(self) -> f64;
707}
708impl PixelsExt for Pixels {
709 fn as_f32(&self) -> f32 {
710 f32::from(self)
711 }
712
713 fn as_f64(self) -> f64 {
714 f64::from(self)
715 }
716}
717
718pub trait LengthExt {
719 fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Option<Pixels>;
723}
724
725impl LengthExt for Length {
726 fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Option<Pixels> {
727 match self {
728 Length::Auto => None,
729 Length::Definite(len) => Some(len.to_pixels(base_size, rem_size)),
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use gpui::px;
737
738 use crate::Size;
739
740 #[test]
741 fn test_size_max_min() {
742 assert_eq!(Size::Small.min(Size::XSmall), Size::Small);
743 assert_eq!(Size::XSmall.min(Size::Small), Size::Small);
744 assert_eq!(Size::Small.min(Size::Medium), Size::Medium);
745 assert_eq!(Size::Medium.min(Size::Large), Size::Large);
746 assert_eq!(Size::Large.min(Size::Small), Size::Large);
747
748 assert_eq!(
749 Size::Size(px(10.)).min(Size::Size(px(20.))),
750 Size::Size(px(20.))
751 );
752
753 assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall);
755 assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall);
756 assert_eq!(Size::Small.max(Size::Medium), Size::Small);
757 assert_eq!(Size::Medium.max(Size::Large), Size::Medium);
758 assert_eq!(Size::Large.max(Size::Small), Size::Small);
759
760 assert_eq!(
761 Size::Size(px(10.)).max(Size::Size(px(20.))),
762 Size::Size(px(10.))
763 );
764 }
765}