use std::str::FromStr; use gpui::prelude::FluentBuilder; use gpui::{ div, relative, AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, }; use smallvec::SmallVec; use theme::ActiveTheme; use crate::{v_flex, StyledExt as _}; /// 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::Normal`] to GroupBox. #[must_use] fn normal(self) -> Self { self.with_variant(GroupBoxVariant::Normal) } /// Set to use [`GroupBoxVariant::Fill`] to GroupBox. #[must_use] fn fill(self) -> Self { self.with_variant(GroupBoxVariant::Fill) } } impl GroupBoxVariant { /// Convert the GroupBoxVariant to a string. pub const fn as_str(&self) -> &str { match self { GroupBoxVariant::Normal => "normal", GroupBoxVariant::Fill => "fill", } } } impl FromStr for GroupBoxVariant { type Err = (); fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "fill" => Ok(GroupBoxVariant::Fill), _ => Ok(GroupBoxVariant::Normal), } } } /// GroupBox is a styled container element that with /// an optional title to groups related content together. #[derive(IntoElement)] pub struct GroupBox { id: Option, variant: GroupBoxVariant, style: StyleRefinement, title_style: StyleRefinement, title: Option, content_style: StyleRefinement, children: SmallVec<[AnyElement; 1]>, } impl GroupBox { /// Create a new GroupBox. pub fn new() -> Self { Self { id: None, variant: GroupBoxVariant::default(), style: StyleRefinement::default(), title_style: StyleRefinement::default(), content_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 } /// Set the style of the title of the group box to override the default style, default is None. #[must_use] pub fn title_style(mut self, style: StyleRefinement) -> Self { self.title_style = style; self } /// Set the style of the content of the group box to override the default style, default is None. #[must_use] pub fn content_style(mut self, style: StyleRefinement) -> Self { self.content_style = style; 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_background), 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) .line_height(relative(1.)) .refine_style(&self.title_style) .text_sm() .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(cx.theme().radius_lg) .refine_style(&self.content_style) .children(self.children), ) } }