Files
coop/crates/ui/src/group_box.rs
T
2026-10-17 17:04:50 +07:00

127 lines
3.5 KiB
Rust

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<ElementId>,
variant: GroupBoxVariant,
style: StyleRefinement,
title: Option<AnyElement>,
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<ElementId>) -> 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<Item = AnyElement>) {
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_1p5())
.when(!has_paddings, |this| this.gap_2())
.refine_style(&self.style)
.when_some(self.title, |this, title| {
this.child(
div()
.text_color(cx.theme().text_muted)
.text_style(TextStyle::Subheadline)
.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),
)
}
}