iced_aw/widget/
badge.rs

1//! Use a badge for color highlighting important information.
2//!
3//! *This API requires the following crate features to be activated: badge*
4
5use iced_core::{
6    Alignment, Border, Clipboard, Color, Element, Event, Layout, Length, Padding, Point, Rectangle,
7    Shadow, Shell, Size, Widget,
8    layout::{Limits, Node},
9    mouse::{self, Cursor},
10    renderer,
11    widget::Tree,
12    window,
13};
14
15pub use crate::style::{
16    badge::{Catalog, Style},
17    status::{Status, StyleFn},
18};
19
20/// The ratio of the border radius.
21const BORDER_RADIUS_RATIO: f32 = 34.0 / 15.0;
22
23/// A badge for color highlighting small information.
24///
25/// # Example
26/// ```ignore
27/// # use iced_widget::Text;
28/// # use iced_aw::Badge;
29/// #
30/// #[derive(Debug, Clone)]
31/// enum Message {
32/// }
33///
34/// let badge = Badge::<Message>::new(Text::new("Text"));
35/// ```
36#[allow(missing_debug_implementations)]
37pub struct Badge<'a, Message, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
38where
39    Renderer: renderer::Renderer,
40    Theme: Catalog,
41{
42    /// The padding of the [`Badge`].
43    padding: u16,
44    /// The width of the [`Badge`].
45    width: Length,
46    /// The height of the [`Badge`].
47    height: Length,
48    /// The horizontal alignment of the [`Badge`].
49    horizontal_alignment: Alignment,
50    /// The vertical alignment of the [`Badge`].
51    vertical_alignment: Alignment,
52    /// The style of the [`Badge`].
53    class: Theme::Class<'a>,
54    /// The content [`Element`] of the [`Badge`].
55    content: Element<'a, Message, Theme, Renderer>,
56    /// The [`Status`] of the [`Badge`]
57    status: Option<Status>,
58}
59
60impl<'a, Message, Theme, Renderer> Badge<'a, Message, Theme, Renderer>
61where
62    Renderer: renderer::Renderer,
63    Theme: Catalog,
64{
65    /// Creates a new [`Badge`] with the given content.
66    ///
67    /// It expects:
68    ///     * the content [`Element`] to display in the [`Badge`].
69    pub fn new<T>(content: T) -> Self
70    where
71        T: Into<Element<'a, Message, Theme, Renderer>>,
72    {
73        Badge {
74            padding: 7,
75            width: Length::Shrink,
76            height: Length::Shrink,
77            horizontal_alignment: Alignment::Center,
78            vertical_alignment: Alignment::Center,
79            class: Theme::default(),
80            content: content.into(),
81            status: None,
82        }
83    }
84
85    /// Sets the horizontal alignment of the content of the [`Badge`].
86    #[must_use]
87    pub fn align_x(mut self, alignment: Alignment) -> Self {
88        self.horizontal_alignment = alignment;
89        self
90    }
91
92    /// Sets the vertical alignment of the content of the [`Badge`].
93    #[must_use]
94    pub fn align_y(mut self, alignment: Alignment) -> Self {
95        self.vertical_alignment = alignment;
96        self
97    }
98
99    /// Sets the height of the [`Badge`].
100    #[must_use]
101    pub fn height(mut self, height: impl Into<Length>) -> Self {
102        self.height = height.into();
103        self
104    }
105
106    /// Sets the padding of the [`Badge`].
107    #[must_use]
108    pub fn padding(mut self, units: u16) -> Self {
109        self.padding = units;
110        self
111    }
112
113    /// Sets the style of the [`Badge`].
114    #[must_use]
115    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
116    where
117        Theme::Class<'a>: From<StyleFn<'a, Theme, Style>>,
118    {
119        self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
120        self
121    }
122
123    /// Sets the class of the input of the [`Badge`].
124    #[must_use]
125    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
126        self.class = class.into();
127        self
128    }
129
130    /// Sets the width of the [`Badge`].
131    #[must_use]
132    pub fn width(mut self, width: impl Into<Length>) -> Self {
133        self.width = width.into();
134        self
135    }
136}
137
138impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
139    for Badge<'a, Message, Theme, Renderer>
140where
141    Message: 'a + Clone,
142    Renderer: 'a + renderer::Renderer,
143    Theme: Catalog,
144{
145    fn children(&self) -> Vec<Tree> {
146        vec![Tree::new(&self.content)]
147    }
148
149    fn diff(&self, tree: &mut Tree) {
150        tree.diff_children(std::slice::from_ref(&self.content));
151    }
152
153    fn size(&self) -> Size<Length> {
154        Size {
155            width: self.width,
156            height: self.height,
157        }
158    }
159
160    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
161        let padding: Padding = self.padding.into();
162        let limits = limits
163            .loose()
164            .width(self.width)
165            .height(self.height)
166            .shrink(padding);
167
168        let mut content =
169            self.content
170                .as_widget_mut()
171                .layout(&mut tree.children[0], renderer, &limits.loose());
172        let size = limits.resolve(self.width, self.height, content.size());
173
174        content = content
175            .move_to(Point::new(padding.left, padding.top))
176            .align(self.horizontal_alignment, self.vertical_alignment, size);
177
178        Node::with_children(size.expand(padding), vec![content])
179    }
180
181    fn update(
182        &mut self,
183        state: &mut Tree,
184        event: &Event,
185        layout: Layout<'_>,
186        cursor: Cursor,
187        renderer: &Renderer,
188        clipboard: &mut dyn Clipboard,
189        shell: &mut Shell<Message>,
190        viewport: &Rectangle,
191    ) {
192        self.content.as_widget_mut().update(
193            &mut state.children[0],
194            event,
195            layout
196                .children()
197                .next()
198                .expect("widget: Layout should have a children layout for a badge."),
199            cursor,
200            renderer,
201            clipboard,
202            shell,
203            viewport,
204        );
205
206        let current_status = if cursor.is_over(layout.bounds()) {
207            Status::Hovered
208        } else {
209            Status::Active
210        };
211
212        if let Event::Window(window::Event::RedrawRequested(_now)) = event {
213            self.status = Some(current_status);
214        } else if self.status.is_some_and(|status| status != current_status) {
215            shell.request_redraw();
216        }
217    }
218
219    fn mouse_interaction(
220        &self,
221        state: &Tree,
222        layout: Layout<'_>,
223        cursor: Cursor,
224        viewport: &Rectangle,
225        renderer: &Renderer,
226    ) -> mouse::Interaction {
227        self.content.as_widget().mouse_interaction(
228            &state.children[0],
229            layout,
230            cursor,
231            viewport,
232            renderer,
233        )
234    }
235
236    fn draw(
237        &self,
238        tree: &Tree,
239        renderer: &mut Renderer,
240        theme: &Theme,
241        _style: &renderer::Style,
242        layout: Layout<'_>,
243        cursor: Cursor,
244        viewport: &Rectangle,
245    ) {
246        let bounds = layout.bounds();
247        let mut children = layout.children();
248
249        let style_sheet = theme.style(&self.class, self.status.unwrap_or(Status::Active));
250
251        let border_radius = style_sheet
252            .border_radius
253            .unwrap_or(bounds.height / BORDER_RADIUS_RATIO);
254
255        if bounds.intersects(viewport) {
256            renderer.fill_quad(
257                renderer::Quad {
258                    bounds,
259                    border: Border {
260                        radius: border_radius.into(),
261                        width: style_sheet.border_width,
262                        color: style_sheet.border_color.unwrap_or(Color::BLACK),
263                    },
264                    shadow: Shadow::default(),
265                    ..renderer::Quad::default()
266                },
267                style_sheet.background,
268            );
269        }
270
271        self.content.as_widget().draw(
272            &tree.children[0],
273            renderer,
274            theme,
275            &renderer::Style {
276                text_color: style_sheet.text_color,
277            },
278            children
279                .next()
280                .expect("Graphics: Layout should have a children layout for Badge"),
281            cursor,
282            viewport,
283        );
284    }
285}
286
287impl<'a, Message, Theme, Renderer> From<Badge<'a, Message, Theme, Renderer>>
288    for Element<'a, Message, Theme, Renderer>
289where
290    Message: 'a + Clone,
291    Renderer: 'a + renderer::Renderer,
292    Theme: 'a + Catalog,
293{
294    fn from(badge: Badge<'a, Message, Theme, Renderer>) -> Self {
295        Self::new(badge)
296    }
297}