Skip to main content

repose_material/material3/
badge.rs

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