use gpui_kit::base::StyledExt as _;
use gpui_kit::{
AnyElement, App, ElementId, FontWeight, InteractiveElement as _, IntoElement, ParentElement,
RenderOnce, StyleRefinement, Styled, TestSupportExt as _, Window, div, relative,
};
use crate::{ActiveTheme as _, Icon, TagVariant};
#[derive(Clone, Default)]
enum Mark {
#[default]
Count,
Dot,
Icon(Box<Icon>),
}
#[derive(IntoElement)]
pub struct Badge {
id: ElementId,
style: StyleRefinement,
mark: Mark,
count: usize,
max: usize,
variant: TagVariant,
children: Vec<AnyElement>,
}
impl Badge {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
mark: Mark::Count,
count: 0,
max: 99,
variant: TagVariant::Primary,
children: Vec::new(),
}
}
pub fn count(mut self, count: usize) -> Self {
self.mark = Mark::Count;
self.count = count;
self
}
pub fn max(mut self, max: usize) -> Self {
self.max = max;
self
}
pub fn dot(mut self) -> Self {
self.mark = Mark::Dot;
self
}
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.mark = Mark::Icon(Box::new(icon.into()));
self
}
pub fn variant(mut self, variant: TagVariant) -> Self {
self.variant = variant;
self
}
pub fn secondary(self) -> Self {
self.variant(TagVariant::Secondary)
}
pub fn danger(self) -> Self {
self.variant(TagVariant::Danger)
}
pub fn success(self) -> Self {
self.variant(TagVariant::Success)
}
pub fn warning(self) -> Self {
self.variant(TagVariant::Warning)
}
pub fn info(self) -> Self {
self.variant(TagVariant::Info)
}
}
impl Styled for Badge {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl ParentElement for Badge {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Badge {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let metrics = &theme.metrics;
let (fill, ink) = match self.variant {
TagVariant::Primary => (theme.primary(), theme.primary_foreground()),
TagVariant::Secondary => (theme.selected, theme.foreground()),
TagVariant::Danger => (theme.destructive_solid, theme.solid_foreground),
TagVariant::Success => (theme.success_solid, theme.solid_foreground),
TagVariant::Warning => (theme.warning_solid, theme.solid_foreground),
TagVariant::Info => (theme.info_solid, theme.solid_foreground),
};
let ring = theme.background();
let offset = -metrics.badge_count_offset;
let (count, count_text, dot, icon_size) = (
metrics.badge_count,
metrics.badge_count_text,
metrics.badge_dot,
metrics.badge_icon,
);
let mark = div()
.id(self.id)
.test_support()
.absolute()
.flex()
.items_center()
.justify_center()
.rounded_full()
.bg(fill)
.text_color(ink);
let mark = match self.mark {
Mark::Count if self.count == 0 => None,
Mark::Count => {
let text = if self.count > self.max {
format!("{}+", self.max)
} else {
self.count.to_string()
};
Some(
mark.top(offset)
.right(offset)
.h(count)
.min_w(count)
.px_1()
.text_size(count_text)
.line_height(relative(1.))
.font_weight(FontWeight::MEDIUM)
.child(text),
)
}
Mark::Dot => Some(mark.top_0().right_0().size(dot)),
Mark::Icon(icon) => Some(
mark.bottom_0()
.right_0()
.size(icon_size)
.border_1()
.border_color(ring)
.child(icon.size(icon_size / 2.)),
),
};
div()
.relative()
.flex_none()
.children(self.children)
.children(mark)
.refine_style(&self.style)
}
}