Skip to main content

repose_material/material3/
badge.rs

1#![allow(non_snake_case)]
2
3use repose_core::*;
4use repose_ui::{Box, ViewExt};
5
6use super::*;
7
8/// Configuration for [`Badge`].
9#[derive(Clone, Debug)]
10pub struct BadgeConfig {
11    pub modifier: Modifier,
12    pub container_color: Color,
13    pub content_color: Color,
14}
15
16impl Default for BadgeConfig {
17    fn default() -> Self {
18        Self {
19            modifier: Modifier::new(),
20            container_color: BadgeDefaults::container_color(),
21            content_color: BadgeDefaults::content_color(),
22        }
23    }
24}
25
26/// M3 Badge - a small notification indicator. If `content` is `None`, shows a
27/// small 6dp dot; otherwise shows the content inside a 16dp pill.
28pub fn Badge(content: Option<View>, config: BadgeConfig) -> View {
29    match content {
30        None => Box(Modifier::new()
31            .size(BadgeDefaults::DOT_SIZE, BadgeDefaults::DOT_SIZE)
32            .background(config.container_color)
33            .clip_rounded(BadgeDefaults::DOT_SIZE * 0.5)
34            .flex_shrink(0.0)
35            .then(config.modifier)),
36        Some(view) => Box(Modifier::new()
37            .min_width(BadgeDefaults::LABEL_MIN_WIDTH)
38            .height(BadgeDefaults::LABEL_HEIGHT)
39            .background(config.container_color)
40            .clip_rounded(BadgeDefaults::LABEL_HEIGHT * 0.5)
41            .padding_values(PaddingValues {
42                left: 4.0,
43                right: 4.0,
44                top: 0.0,
45                bottom: 0.0,
46            })
47            .align_items(AlignItems::CENTER)
48            .justify_content(JustifyContent::CENTER)
49            .flex_shrink(0.0)
50            .then(config.modifier))
51        .child(with_content_color(config.content_color, move || view)),
52    }
53}
54
55/// Configuration for [`BadgedBox`].
56#[derive(Clone, Debug)]
57pub struct BadgedBoxConfig {
58    pub modifier: Modifier,
59    /// Horizontal offset for the badge when it's a small dot.
60    pub dot_offset_x: f32,
61    /// Vertical offset for the badge when it's a small dot.
62    pub dot_offset_y: f32,
63    /// Horizontal offset for the badge when it has content.
64    pub content_offset_x: f32,
65    /// Vertical offset for the badge when it has content.
66    pub content_offset_y: f32,
67    /// When true, use `content_offset_*` (labeled badge). When false, use `dot_offset_*`.
68    pub has_content: bool,
69}
70
71impl Default for BadgedBoxConfig {
72    fn default() -> Self {
73        Self {
74            modifier: Modifier::new(),
75            dot_offset_x: BadgeDefaults::DOT_OFFSET_X,
76            dot_offset_y: BadgeDefaults::DOT_OFFSET_Y,
77            content_offset_x: BadgeDefaults::CONTENT_OFFSET_X,
78            content_offset_y: BadgeDefaults::CONTENT_OFFSET_Y,
79            has_content: false,
80        }
81    }
82}
83
84/// Wraps `content` and shows a `badge` anchored to the top-end corner.
85pub fn BadgedBox(badge: View, content: View, config: BadgedBoxConfig) -> View {
86    let (top, right) = if config.has_content {
87        (
88            config.content_offset_y - BadgeDefaults::LABEL_HEIGHT, // 14 - 16 = -2
89            config.content_offset_x - BadgeDefaults::LABEL_MIN_WIDTH, // 12 - 16 = -4
90        )
91    } else {
92        (
93            config.dot_offset_y - BadgeDefaults::DOT_SIZE, // 6 - 6 = 0
94            config.dot_offset_x - BadgeDefaults::DOT_SIZE, // 6 - 6 = 0
95        )
96    };
97
98    Box(config.modifier.flex_shrink(0.0)).child((
99        content,
100        Box(Modifier::new()
101            .absolute()
102            .offset(None, Some(top), Some(right), None)
103            .flex_shrink(0.0)
104            .hit_passthrough())
105        .child(badge),
106    ))
107}