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
18#[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 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 pub fn with_variant(mut self, variant: GroupBoxVariant) -> Self {
47 self.variant = variant;
48 self
49 }
50
51 pub fn fill(mut self) -> Self {
53 self.variant = GroupBoxVariant::Fill;
54 self
55 }
56
57 pub fn outline(mut self) -> Self {
61 self.variant = GroupBoxVariant::Outline;
62 self
63 }
64
65 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
67 self.id = Some(id.into());
68 self
69 }
70
71 pub fn title(mut self, title: impl IntoElement) -> Self {
73 self.title = Some(title.into_any_element());
74 self
75 }
76
77 pub fn title_style(mut self, style: StyleRefinement) -> Self {
79 self.title_style = style;
80 self
81 }
82
83 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}