Skip to main content

rosace_widgets/tree/
badge.rs

1use rosace_core::types::{Point, Size};
2use rosace_render::Color;
3use super::{Widget, LayoutCtx, PaintCtx};
4use super::container::draw_rounded_rect_pub;
5
6/// A small count badge or status dot.
7pub struct Badge {
8    pub label: String,
9    pub dot: bool,
10    pub color: Color,
11    pub text_color: Color,
12    /// `None` = read from the active theme's `typography.label_small`
13    /// (D127 "environment" track — see `Checkbox::resolved_font_size`'s doc
14    /// for the reasoning).
15    pub font_size: Option<f32>,
16}
17
18impl Badge {
19    /// D093: new() must exist wherever named constructors exist.
20    pub fn new(text: impl Into<String>) -> Self {
21        Self::label(text)
22    }
23
24    pub fn count(n: u32) -> Self {
25        Self::label(n.to_string())
26    }
27
28    pub fn label(text: impl Into<String>) -> Self {
29        Self {
30            label: text.into(),
31            dot: false,
32            color: Color::rgb(110, 75, 210),
33            text_color: Color::rgb(230, 232, 245),
34            font_size: None,
35        }
36    }
37
38    pub fn dot() -> Self {
39        Self { dot: true, label: String::new(), color: Color::rgb(235, 75, 75),
40               text_color: Color::rgb(255,255,255), font_size: None }
41    }
42
43    pub fn color(mut self, c: Color) -> Self { self.color = c; self }
44    pub fn text_color(mut self, c: Color) -> Self { self.text_color = c; self }
45
46    fn resolved_font_size(&self, theme: &rosace_theme::ThemeData) -> f32 {
47        self.font_size.unwrap_or(theme.typography.label_small.size)
48    }
49}
50
51impl Widget for Badge {
52    fn layout(&self, ctx: &LayoutCtx) -> Size {
53        if self.dot {
54            return Size { width: 8.0, height: 8.0 };
55        }
56        let font_size = self.resolved_font_size(ctx.theme);
57        let w = self.label.len() as f32 * font_size * 0.6 + 12.0;
58        Size { width: w.max(16.0), height: 16.0 }
59    }
60
61    fn paint(&self, ctx: &mut PaintCtx) {
62        if self.dot {
63            // A bare status dot carries no text — nothing to announce.
64            let cx = ctx.rect.origin.x + 4.0;
65            let cy = ctx.rect.origin.y + 4.0;
66            ctx.fill_circle(Point { x: cx, y: cy }, 4.0, self.color);
67            return;
68        }
69        ctx.semantics(super::Semantics::new(rosace_core::Role::Text).label(&self.label));
70        let font_size = self.resolved_font_size(&ctx.theme);
71        let r = ctx.rect;
72        draw_rounded_rect_pub(ctx, r, self.color, r.size.height / 2.0);
73        let text_w = ctx.font.measure_text(&self.label, font_size);
74        let tx = ((r.size.width - text_w) / 2.0).max(0.0);
75        let line_h = ctx.font.line_height(font_size);
76        let ty = ((r.size.height - line_h) / 2.0).max(0.0);
77        ctx.text(&self.label, tx, ty, self.text_color, font_size);
78    }
79}