Skip to main content

teksilo_widgets/styles/
recipe_badge_style.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Default `BadgeStyle` impl driven by paint-recipe data.
5//!
6//! `RecipeBadgeStyle` ships the IntUI badge chrome: a fully-rounded
7//! (`9999` radius) pill with a soft `AccentSubtle` background by
8//! default, content inset by the badge padding. Pure composition —
9//! no custom paint.
10//!
11//! Apps that want a different badge look (square tag, status-tinted
12//! pill, bordered chip) write their own `impl BadgeStyle` block and
13//! install it per-call (`Badge::style(...)`) or theme-wide
14//! (`theme.style_slots.badge`).
15
16use teksilo_core::build_context::BuildContext;
17use teksilo_core::color_prop::ColorProp;
18use teksilo_core::styles::{BadgeStyle, BadgeStyleConfig};
19use teksilo_core::widget_id::WidgetId;
20use teksilo_tokens::{CornerRadius, InputTokens, SurfaceRole};
21
22use crate::primitives::{Padding, RectWidget, ZStack};
23use teksilo_core::styles::density::spacing;
24
25// IntUI design tokens for Badge. The recipe owns its own dimensions.
26pub const BADGE_PADDING_HORIZONTAL: f32 = 6.0;
27pub const BADGE_PADDING_VERTICAL: f32 = 1.0;
28/// Fully-rounded pill — a large radius the renderer clamps to half the
29/// shorter side.
30pub const BADGE_CORNER_RADIUS: f32 = 9999.0;
31
32/// Dimension bundle for [`RecipeBadgeStyle`]. All fields are `f32` and
33/// the struct is `Copy`, so it can be passed freely.
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub struct BadgeRecipe {
36    pub padding_horizontal: f32,
37    pub padding_vertical: f32,
38    pub corner_radius: f32,
39}
40
41impl BadgeRecipe {
42    /// This recipe's dimensions resolved against a density's [`InputTokens`].
43    ///
44    /// [`Default`] is `for_tokens(&InputTokens::default())` — the Compact
45    /// ladder — so the shipped values below are the Compact column by
46    /// construction and cannot drift from it.
47    pub fn for_tokens(tokens: &InputTokens) -> Self {
48        Self {
49            padding_horizontal: spacing(BADGE_PADDING_HORIZONTAL, tokens),
50            padding_vertical: spacing(BADGE_PADDING_VERTICAL, tokens),
51            corner_radius: BADGE_CORNER_RADIUS,
52        }
53    }
54}
55
56impl Default for BadgeRecipe {
57    fn default() -> Self {
58        Self::for_tokens(&InputTokens::default())
59    }
60}
61
62/// Default `BadgeStyle` shipped with Teksilo. Background defaults to
63/// `SurfaceRole::AccentSubtle` when the caller sets no override.
64#[derive(Debug, Default, Clone, Copy)]
65pub struct RecipeBadgeStyle {
66    pub recipe: BadgeRecipe,
67}
68
69impl RecipeBadgeStyle {
70    pub fn new(recipe: BadgeRecipe) -> Self {
71        Self { recipe }
72    }
73
74    /// This style with every dimension resolved against a density's
75    /// [`InputTokens`], as `RecipeBadgeStyle::for_tokens(&ctx.theme().input)` at
76    /// the widget's own build site.
77    ///
78    /// [`Default`] is the `TargetDensity::Compact` projection, so a Compact
79    /// tree gets exactly the values this module documents.
80    pub fn for_tokens(tokens: &InputTokens) -> Self {
81        Self {
82            recipe: BadgeRecipe::for_tokens(tokens),
83        }
84    }
85}
86
87impl BadgeStyle for RecipeBadgeStyle {
88    fn make_body(&self, cfg: &BadgeStyleConfig, ctx: &mut BuildContext) -> WidgetId {
89        let bg: ColorProp = cfg
90            .background_override
91            .clone()
92            .unwrap_or(ColorProp::SurfaceRole(SurfaceRole::AccentSubtle));
93        let bg_rect = ctx.add(
94            RectWidget::new()
95                .background(bg)
96                .corner_radius(CornerRadius::uniform(self.recipe.corner_radius)),
97        );
98        let padding_id = ctx.add(
99            Padding::symmetric(self.recipe.padding_vertical, self.recipe.padding_horizontal)
100                .child(cfg.content),
101        );
102        ctx.add(ZStack::new().child(bg_rect).child(padding_id))
103    }
104}