gpui_component/
group_box.rs

1use 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/// GroupBox is a styled container element that with
18/// an optional title to groups related content together.
19#[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    /// Set the variant of the group box.
44    pub fn variant(mut self, variant: GroupBoxVariant) -> Self {
45        self.variant = variant;
46        self
47    }
48
49    /// Set to use Fill variant.
50    pub fn fill(mut self) -> Self {
51        self.variant = GroupBoxVariant::Fill;
52        self
53    }
54
55    /// Set use outline style of the group box.
56    ///
57    /// If true, the group box will have a border around it, and no background color.
58    pub fn outline(mut self) -> Self {
59        self.variant = GroupBoxVariant::Outline;
60        self
61    }
62
63    /// Set the id of the group box, default is None.
64    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
65        self.id = Some(id.into());
66        self
67    }
68
69    /// Set the title of the group box, default is None.
70    pub fn title(mut self, title: impl IntoElement) -> Self {
71        self.title = Some(title.into_any_element());
72        self
73    }
74
75    /// Set the style of the title of the group box to override the default style, default is None.
76    pub fn title_style(mut self, style: StyleRefinement) -> Self {
77        self.title_style = style;
78        self
79    }
80
81    /// Set the style of the content of the group box to override the default style, default is None.
82    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}