use gpui::prelude::FluentBuilder; use gpui::{ AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, div, px, }; use smallvec::SmallVec; use theme::{ActiveTheme, TextStyle, Typeset as _}; use crate::{StyledExt as _, v_flex}; /// The variant of the GroupBox. #[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)] pub enum GroupBoxVariant { #[default] Normal, Fill, } /// Trait to add GroupBox variant methods to elements. pub trait GroupBoxVariants: Sized { /// Set the variant of the [`GroupBox`]. #[must_use] fn with_variant(self, variant: GroupBoxVariant) -> Self; /// Set to use [`GroupBoxVariant::Fill`] to GroupBox. #[must_use] fn fill(self) -> Self { self.with_variant(GroupBoxVariant::Fill) } } /// A styled container with an optional title that groups related content together. #[derive(IntoElement)] pub struct GroupBox { id: Option, variant: GroupBoxVariant, style: StyleRefinement, title: Option, children: SmallVec<[AnyElement; 1]>, } impl GroupBox { /// Create a new GroupBox. pub fn new() -> Self { Self { id: None, variant: GroupBoxVariant::default(), style: StyleRefinement::default(), title: None, children: SmallVec::new(), } } /// Set the id of the group box, default is None. #[must_use] pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } /// Set the title of the group box, default is None. #[must_use] pub fn title(mut self, title: impl IntoElement) -> Self { self.title = Some(title.into_any_element()); self } } impl Default for GroupBox { fn default() -> Self { Self::new() } } impl ParentElement for GroupBox { fn extend(&mut self, elements: impl IntoIterator) { self.children.extend(elements); } } impl Styled for GroupBox { fn style(&mut self) -> &mut StyleRefinement { &mut self.style } } impl GroupBoxVariants for GroupBox { fn with_variant(mut self, variant: GroupBoxVariant) -> Self { self.variant = variant; self } } impl RenderOnce for GroupBox { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { let (bg, has_paddings) = match self.variant { GroupBoxVariant::Normal => (None, false), GroupBoxVariant::Fill => (Some(cx.theme().surface), true), }; v_flex() .id(self.id.unwrap_or("group-box".into())) .w_full() .when(has_paddings, |this| this.gap_3()) .when(!has_paddings, |this| this.gap_4()) .refine_style(&self.style) .when_some(self.title, |this, title| { this.child( div() .text_color(cx.theme().text_muted) .text_style(TextStyle::Callout) .font_semibold() .child(title), ) }) .child( v_flex() .when_some(bg, |this, bg| this.bg(bg)) .text_color(cx.theme().text) .when(has_paddings, |this| this.p_2()) .gap_4() .rounded(px(theme::panel_radius())) .children(self.children), ) } }