gpui_component/
group_box.rs1use gpui::{
2 div, prelude::FluentBuilder, relative, AnyElement, App, ElementId, InteractiveElement as _,
3 IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window,
4};
5use smallvec::SmallVec;
6
7use crate::{v_flex, ActiveTheme, StyledExt as _};
8
9#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
10pub enum GroupBoxVariant {
11 #[default]
12 Normal,
13 Fill,
14 Outline,
15}
16
17#[derive(IntoElement)]
20pub struct GroupBox {
21 id: Option<ElementId>,
22 variant: GroupBoxVariant,
23 style: StyleRefinement,
24 title_style: StyleRefinement,
25 title: Option<AnyElement>,
26 content_style: StyleRefinement,
27 children: SmallVec<[AnyElement; 1]>,
28}
29
30impl GroupBox {
31 pub fn new() -> Self {
32 Self {
33 id: None,
34 variant: GroupBoxVariant::default(),
35 style: StyleRefinement::default(),
36 title_style: StyleRefinement::default(),
37 content_style: StyleRefinement::default(),
38 title: None,
39 children: SmallVec::new(),
40 }
41 }
42
43 pub fn variant(mut self, variant: GroupBoxVariant) -> Self {
45 self.variant = variant;
46 self
47 }
48
49 pub fn fill(mut self) -> Self {
51 self.variant = GroupBoxVariant::Fill;
52 self
53 }
54
55 pub fn outline(mut self) -> Self {
59 self.variant = GroupBoxVariant::Outline;
60 self
61 }
62
63 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
65 self.id = Some(id.into());
66 self
67 }
68
69 pub fn title(mut self, title: impl IntoElement) -> Self {
71 self.title = Some(title.into_any_element());
72 self
73 }
74
75 pub fn title_style(mut self, style: StyleRefinement) -> Self {
77 self.title_style = style;
78 self
79 }
80
81 pub fn content_style(mut self, style: StyleRefinement) -> Self {
83 self.content_style = style;
84 self
85 }
86}
87
88impl ParentElement for GroupBox {
89 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
90 self.children.extend(elements);
91 }
92}
93
94impl Styled for GroupBox {
95 fn style(&mut self) -> &mut StyleRefinement {
96 &mut self.style
97 }
98}
99
100impl RenderOnce for GroupBox {
101 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
102 let (bg, border, has_paddings) = match self.variant {
103 GroupBoxVariant::Normal => (None, None, false),
104 GroupBoxVariant::Fill => (Some(cx.theme().group_box), None, true),
105 GroupBoxVariant::Outline => (None, Some(cx.theme().border), true),
106 };
107
108 v_flex()
109 .id(self.id.unwrap_or("group-box".into()))
110 .w_full()
111 .when(has_paddings, |this| this.gap_3())
112 .when(!has_paddings, |this| this.gap_4())
113 .refine_style(&self.style)
114 .when_some(self.title, |this, title| {
115 this.child(
116 div()
117 .text_color(cx.theme().muted_foreground)
118 .line_height(relative(1.))
119 .refine_style(&self.title_style)
120 .child(title),
121 )
122 })
123 .child(
124 v_flex()
125 .when_some(bg, |this, bg| this.bg(bg))
126 .when_some(border, |this, border| this.border_color(border).border_1())
127 .text_color(cx.theme().group_box_foreground)
128 .when(has_paddings, |this| this.p_4())
129 .gap_4()
130 .rounded(cx.theme().radius)
131 .refine_style(&self.content_style)
132 .children(self.children),
133 )
134 }
135}