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/// The variant of the GroupBox.
10#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
11pub enum GroupBoxVariant {
12    #[default]
13    Normal,
14    Fill,
15    Outline,
16}
17
18/// GroupBox is a styled container element that with
19/// an optional title to groups related content together.
20#[derive(IntoElement)]
21pub struct GroupBox {
22    id: Option<ElementId>,
23    variant: GroupBoxVariant,
24    style: StyleRefinement,
25    title_style: StyleRefinement,
26    title: Option<AnyElement>,
27    content_style: StyleRefinement,
28    children: SmallVec<[AnyElement; 1]>,
29}
30
31impl GroupBox {
32    /// Create a new GroupBox.
33    pub fn new() -> Self {
34        Self {
35            id: None,
36            variant: GroupBoxVariant::default(),
37            style: StyleRefinement::default(),
38            title_style: StyleRefinement::default(),
39            content_style: StyleRefinement::default(),
40            title: None,
41            children: SmallVec::new(),
42        }
43    }
44
45    /// Set the variant of the group box.
46    pub fn with_variant(mut self, variant: GroupBoxVariant) -> Self {
47        self.variant = variant;
48        self
49    }
50
51    /// Set to use Fill variant.
52    pub fn fill(mut self) -> Self {
53        self.variant = GroupBoxVariant::Fill;
54        self
55    }
56
57    /// Set use outline style of the group box.
58    ///
59    /// If true, the group box will have a border around it, and no background color.
60    pub fn outline(mut self) -> Self {
61        self.variant = GroupBoxVariant::Outline;
62        self
63    }
64
65    /// Set the id of the group box, default is None.
66    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
67        self.id = Some(id.into());
68        self
69    }
70
71    /// Set the title of the group box, default is None.
72    pub fn title(mut self, title: impl IntoElement) -> Self {
73        self.title = Some(title.into_any_element());
74        self
75    }
76
77    /// Set the style of the title of the group box to override the default style, default is None.
78    pub fn title_style(mut self, style: StyleRefinement) -> Self {
79        self.title_style = style;
80        self
81    }
82
83    /// Set the style of the content of the group box to override the default style, default is None.
84    pub fn content_style(mut self, style: StyleRefinement) -> Self {
85        self.content_style = style;
86        self
87    }
88}
89
90impl ParentElement for GroupBox {
91    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
92        self.children.extend(elements);
93    }
94}
95
96impl Styled for GroupBox {
97    fn style(&mut self) -> &mut StyleRefinement {
98        &mut self.style
99    }
100}
101
102impl RenderOnce for GroupBox {
103    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
104        let (bg, border, has_paddings) = match self.variant {
105            GroupBoxVariant::Normal => (None, None, false),
106            GroupBoxVariant::Fill => (Some(cx.theme().group_box), None, true),
107            GroupBoxVariant::Outline => (None, Some(cx.theme().border), true),
108        };
109
110        v_flex()
111            .id(self.id.unwrap_or("group-box".into()))
112            .w_full()
113            .when(has_paddings, |this| this.gap_3())
114            .when(!has_paddings, |this| this.gap_4())
115            .refine_style(&self.style)
116            .when_some(self.title, |this, title| {
117                this.child(
118                    div()
119                        .text_color(cx.theme().muted_foreground)
120                        .line_height(relative(1.))
121                        .refine_style(&self.title_style)
122                        .child(title),
123                )
124            })
125            .child(
126                v_flex()
127                    .when_some(bg, |this, bg| this.bg(bg))
128                    .when_some(border, |this, border| this.border_color(border).border_1())
129                    .text_color(cx.theme().group_box_foreground)
130                    .when(has_paddings, |this| this.p_4())
131                    .gap_4()
132                    .rounded(cx.theme().radius)
133                    .refine_style(&self.content_style)
134                    .children(self.children),
135            )
136    }
137}