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