1use 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
20const BORDER_RADIUS_RATIO: f32 = 34.0 / 15.0;
22
23#[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 padding: u16,
44 width: Length,
46 height: Length,
48 horizontal_alignment: Alignment,
50 vertical_alignment: Alignment,
52 class: Theme::Class<'a>,
54 content: Element<'a, Message, Theme, Renderer>,
56 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 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 #[must_use]
87 pub fn align_x(mut self, alignment: Alignment) -> Self {
88 self.horizontal_alignment = alignment;
89 self
90 }
91
92 #[must_use]
94 pub fn align_y(mut self, alignment: Alignment) -> Self {
95 self.vertical_alignment = alignment;
96 self
97 }
98
99 #[must_use]
101 pub fn height(mut self, height: impl Into<Length>) -> Self {
102 self.height = height.into();
103 self
104 }
105
106 #[must_use]
108 pub fn padding(mut self, units: u16) -> Self {
109 self.padding = units;
110 self
111 }
112
113 #[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 #[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 #[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}