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