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/// Trait to add GroupBox variant methods to elements.
19pub trait GroupBoxVariants: Sized {
20    /// Set the variant of the [`GroupBox`].
21    fn with_variant(self, variant: GroupBoxVariant) -> Self;
22    /// Set to use [`GroupBoxVariant::Normal`] to GroupBox.
23    fn normal(mut self) -> Self {
24        self = self.with_variant(GroupBoxVariant::Normal);
25        self
26    }
27    /// Set to use [`GroupBoxVariant::Fill`] to GroupBox.
28    fn fill(mut self) -> Self {
29        self = self.with_variant(GroupBoxVariant::Fill);
30        self
31    }
32    /// Set to use [`GroupBoxVariant::Outline`] to GroupBox.
33    fn outline(mut self) -> Self {
34        self = self.with_variant(GroupBoxVariant::Outline);
35        self
36    }
37}
38
39impl GroupBoxVariant {
40    /// Create a GroupBoxVariant from a string.
41    pub fn from_str(s: &str) -> Self {
42        match s.to_lowercase().as_str() {
43            "fill" => GroupBoxVariant::Fill,
44            "outline" => GroupBoxVariant::Outline,
45            _ => GroupBoxVariant::Normal,
46        }
47    }
48
49    /// Convert the GroupBoxVariant to a string.
50    pub fn as_str(&self) -> &str {
51        match self {
52            GroupBoxVariant::Normal => "normal",
53            GroupBoxVariant::Fill => "fill",
54            GroupBoxVariant::Outline => "outline",
55        }
56    }
57}
58
59/// GroupBox is a styled container element that with
60/// an optional title to groups related content together.
61#[derive(IntoElement)]
62pub struct GroupBox {
63    id: Option<ElementId>,
64    variant: GroupBoxVariant,
65    style: StyleRefinement,
66    title_style: StyleRefinement,
67    title: Option<AnyElement>,
68    content_style: StyleRefinement,
69    children: SmallVec<[AnyElement; 1]>,
70}
71
72impl GroupBox {
73    /// Create a new GroupBox.
74    pub fn new() -> Self {
75        Self {
76            id: None,
77            variant: GroupBoxVariant::default(),
78            style: StyleRefinement::default(),
79            title_style: StyleRefinement::default(),
80            content_style: StyleRefinement::default(),
81            title: None,
82            children: SmallVec::new(),
83        }
84    }
85
86    /// Set the id of the group box, default is None.
87    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
88        self.id = Some(id.into());
89        self
90    }
91
92    /// Set the title of the group box, default is None.
93    pub fn title(mut self, title: impl IntoElement) -> Self {
94        self.title = Some(title.into_any_element());
95        self
96    }
97
98    /// Set the style of the title of the group box to override the default style, default is None.
99    pub fn title_style(mut self, style: StyleRefinement) -> Self {
100        self.title_style = style;
101        self
102    }
103
104    /// Set the style of the content of the group box to override the default style, default is None.
105    pub fn content_style(mut self, style: StyleRefinement) -> Self {
106        self.content_style = style;
107        self
108    }
109}
110
111impl ParentElement for GroupBox {
112    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
113        self.children.extend(elements);
114    }
115}
116
117impl Styled for GroupBox {
118    fn style(&mut self) -> &mut StyleRefinement {
119        &mut self.style
120    }
121}
122
123impl GroupBoxVariants for GroupBox {
124    fn with_variant(mut self, variant: GroupBoxVariant) -> Self {
125        self.variant = variant;
126        self
127    }
128}
129
130impl RenderOnce for GroupBox {
131    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
132        let (bg, border, has_paddings) = match self.variant {
133            GroupBoxVariant::Normal => (None, None, false),
134            GroupBoxVariant::Fill => (Some(cx.theme().group_box), None, true),
135            GroupBoxVariant::Outline => (None, Some(cx.theme().border), true),
136        };
137
138        v_flex()
139            .id(self.id.unwrap_or("group-box".into()))
140            .w_full()
141            .when(has_paddings, |this| this.gap_3())
142            .when(!has_paddings, |this| this.gap_4())
143            .refine_style(&self.style)
144            .when_some(self.title, |this, title| {
145                this.child(
146                    div()
147                        .text_color(cx.theme().muted_foreground)
148                        .line_height(relative(1.))
149                        .refine_style(&self.title_style)
150                        .child(title),
151                )
152            })
153            .child(
154                v_flex()
155                    .when_some(bg, |this, bg| this.bg(bg))
156                    .when_some(border, |this, border| this.border_color(border).border_1())
157                    .text_color(cx.theme().group_box_foreground)
158                    .when(has_paddings, |this| this.p_4())
159                    .gap_4()
160                    .rounded(cx.theme().radius)
161                    .refine_style(&self.content_style)
162                    .children(self.children),
163            )
164    }
165}
166
167#[cfg(test)]
168mod test {
169    #[test]
170    fn test_group_variant_from_str() {
171        use super::GroupBoxVariant;
172
173        assert_eq!(GroupBoxVariant::from_str("normal"), GroupBoxVariant::Normal);
174        assert_eq!(GroupBoxVariant::from_str("fill"), GroupBoxVariant::Fill);
175        assert_eq!(
176            GroupBoxVariant::from_str("outline"),
177            GroupBoxVariant::Outline
178        );
179        assert_eq!(GroupBoxVariant::from_str("other"), GroupBoxVariant::Normal);
180
181        assert_eq!(GroupBoxVariant::from_str("FILL"), GroupBoxVariant::Fill);
182        assert_eq!(
183            GroupBoxVariant::from_str("OutLine"),
184            GroupBoxVariant::Outline
185        );
186
187        assert_eq!(GroupBoxVariant::Normal.as_str(), "normal");
188        assert_eq!(GroupBoxVariant::Fill.as_str(), "fill");
189        assert_eq!(GroupBoxVariant::Outline.as_str(), "outline");
190    }
191}