#![allow(clippy::unusual_byte_groupings)]
use crate::motion::StyledSlot;
use crate::theme::{ActiveTheme, Theme};
use gpui::{
div, prelude::*, px, App, FontWeight, IntoElement, RenderOnce, SharedString, StyleRefinement,
Styled, Window,
};
use crate::button::ButtonVariant;
use crate::chrome::{box_shadow, button_chrome, ButtonChrome};
const BADGE_HEIGHT: f32 = 22.0;
const BADGE_RADIUS: f32 = 6.0;
const BADGE_PAD_X: f32 = 8.0;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BadgeVariant {
#[default]
Default,
Muted,
Destructive,
}
impl BadgeVariant {
fn button_variant(self) -> ButtonVariant {
match self {
Self::Default => ButtonVariant::Primary,
Self::Muted => ButtonVariant::Ghost,
Self::Destructive => ButtonVariant::Destructive,
}
}
}
fn badge_chrome(theme: Theme, variant: BadgeVariant) -> ButtonChrome {
let mut chrome = button_chrome(theme, variant.button_variant());
if variant == BadgeVariant::Muted {
chrome.fg = theme.muted_fg();
}
chrome
}
#[derive(IntoElement)]
pub struct Badge {
label: SharedString,
variant: BadgeVariant,
style: StyleRefinement,
}
impl Badge {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
variant: BadgeVariant::Default,
style: StyleRefinement::default(),
}
}
pub fn variant(mut self, variant: BadgeVariant) -> Self {
self.variant = variant;
self
}
}
impl Styled for Badge {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Badge {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let chrome = badge_chrome(theme, self.variant);
let mut shadows = vec![box_shadow(0., 1., chrome.inset, 0., 0.)];
if chrome.shadow_blur > 0.0 {
shadows.push(box_shadow(
0.,
chrome.shadow_y,
chrome.shadow,
chrome.shadow_blur,
0.,
));
}
div()
.flex()
.items_center()
.justify_center()
.h(px(BADGE_HEIGHT))
.flex_shrink_0()
.px(px(BADGE_PAD_X))
.rounded(px(BADGE_RADIUS))
.border_1()
.border_color(chrome.border)
.bg(chrome.bg)
.shadow(shadows)
.cursor_default()
.refine_style(&self.style)
.child(
div()
.font_family(theme.font_family)
.font_weight(FontWeight::MEDIUM)
.text_size(px(12.))
.line_height(px(16.))
.text_color(chrome.fg)
.child(self.label),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::{paint, rgb};
#[test]
fn metrics_match_paper() {
assert_eq!(BADGE_HEIGHT, 22.0);
assert_eq!(BADGE_RADIUS, 6.0);
assert_eq!(BADGE_PAD_X, 8.0);
}
#[test]
fn light_variants_match_paper() {
let theme = Theme::light();
let default = badge_chrome(theme, BadgeVariant::Default);
assert_eq!(default.bg, paint(0x18181B_B8));
assert_eq!(default.border, paint(0xFFFFFF_29));
assert_eq!(default.fg, rgb(0xFAFAFA));
let muted = badge_chrome(theme, BadgeVariant::Muted);
assert_eq!(muted.bg, paint(0xFFFFFF_29));
assert_eq!(muted.border, paint(0xFFFFFF_47));
assert_eq!(muted.fg, rgb(0xA1A1AA));
let destructive = badge_chrome(theme, BadgeVariant::Destructive);
assert_eq!(destructive.bg, paint(0xDC2626_C7));
assert_eq!(destructive.border, paint(0xFFFFFF_47));
assert_eq!(destructive.fg, rgb(0xFFFFFF));
}
#[test]
fn dark_variants_match_paper() {
let theme = Theme::dark();
let default = badge_chrome(theme, BadgeVariant::Default);
assert_eq!(default.bg, paint(0xFFFFFF_29));
assert_eq!(default.border, paint(0xFFFFFF_38));
assert_eq!(default.fg, rgb(0xFAFAFA));
let muted = badge_chrome(theme, BadgeVariant::Muted);
assert_eq!(muted.bg, paint(0xFFFFFF_08));
assert_eq!(muted.border, paint(0xFFFFFF_0F));
assert_eq!(muted.fg, rgb(0x71717A));
let destructive = badge_chrome(theme, BadgeVariant::Destructive);
assert_eq!(destructive.bg, paint(0xDC2626_9E));
assert_eq!(destructive.border, paint(0xFFFFFF_29));
assert_eq!(destructive.fg, rgb(0xFAFAFA));
}
}