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)]
11pub enum GroupBoxVariant {
12 #[default]
13 Normal,
14 Fill,
15 Outline,
16}
17
18pub trait GroupBoxVariants: Sized {
20 fn with_variant(self, variant: GroupBoxVariant) -> Self;
22 fn normal(mut self) -> Self {
24 self = self.with_variant(GroupBoxVariant::Normal);
25 self
26 }
27 fn fill(mut self) -> Self {
29 self = self.with_variant(GroupBoxVariant::Fill);
30 self
31 }
32 fn outline(mut self) -> Self {
34 self = self.with_variant(GroupBoxVariant::Outline);
35 self
36 }
37}
38
39impl GroupBoxVariant {
40 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 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#[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 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 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
88 self.id = Some(id.into());
89 self
90 }
91
92 pub fn title(mut self, title: impl IntoElement) -> Self {
94 self.title = Some(title.into_any_element());
95 self
96 }
97
98 pub fn title_style(mut self, style: StyleRefinement) -> Self {
100 self.title_style = style;
101 self
102 }
103
104 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}