1use iced_widget::core::text as core_text;
4use iced_widget::core::widget::{Operation, Tree};
5use iced_widget::core::{
6 Clipboard, Element, Event, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector,
7 Widget, alignment, layout, mouse, overlay, renderer,
8};
9use iced_widget::text;
10use iced_widget::{Container, Text};
11
12use super::absolute_line_height;
13use crate::{Theme, style::badge as badge_style, tokens};
14
15use std::fmt;
16
17pub fn small<'a, Message, Renderer>() -> Container<'a, Message, Theme, Renderer>
18where
19 Message: 'a,
20 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
21{
22 Container::new(Text::new(""))
23 .width(Length::Fixed(tokens::component::badge::SMALL_SIZE))
24 .height(Length::Fixed(tokens::component::badge::SMALL_SIZE))
25 .style(badge_style::default)
26}
27
28pub fn large<'a, Message, Renderer>(
29 label: impl text::IntoFragment<'a>,
30) -> Container<'a, Message, Theme, Renderer>
31where
32 Message: 'a,
33 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
34{
35 let label_text = tokens::component::badge::LABEL_TEXT;
36
37 Container::new(
38 Text::new(label)
39 .size(label_text.size)
40 .line_height(absolute_line_height(label_text.line_height)),
41 )
42 .height(Length::Fixed(
43 tokens::component::badge::LARGE_CONTAINER_HEIGHT,
44 ))
45 .max_width(tokens::component::badge::LARGE_CONTAINER_MAX_WIDTH)
46 .padding(Padding::from([
47 0.0,
48 tokens::component::badge::LARGE_HORIZONTAL_SPACE,
49 ]))
50 .align_x(alignment::Horizontal::Center)
51 .align_y(alignment::Vertical::Center)
52 .style(badge_style::default)
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum BadgedBoxPlacement {
57 IconOnly,
58 WithContent,
59}
60
61impl BadgedBoxPlacement {
62 fn horizontal_offset(self) -> f32 {
63 match self {
64 Self::IconOnly => tokens::component::badge::ICON_ONLY_OFFSET,
65 Self::WithContent => tokens::component::badge::WITH_CONTENT_HORIZONTAL_OFFSET,
66 }
67 }
68
69 fn vertical_offset(self) -> f32 {
70 match self {
71 Self::IconOnly => tokens::component::badge::ICON_ONLY_OFFSET,
72 Self::WithContent => tokens::component::badge::WITH_CONTENT_VERTICAL_OFFSET,
73 }
74 }
75}
76
77pub fn badged_box<'a, Message, Renderer>(
78 anchor: impl Into<Element<'a, Message, Theme, Renderer>>,
79 badge: impl Into<Element<'a, Message, Theme, Renderer>>,
80 placement: BadgedBoxPlacement,
81) -> BadgedBox<'a, Message, Renderer>
82where
83 Renderer: iced_widget::core::Renderer,
84{
85 BadgedBox::new(anchor, badge, placement)
86}
87
88pub struct BadgedBox<'a, Message, Renderer = iced_widget::Renderer>
89where
90 Renderer: iced_widget::core::Renderer,
91{
92 width: Length,
93 height: Length,
94 placement: BadgedBoxPlacement,
95 children: Vec<Element<'a, Message, Theme, Renderer>>,
96}
97
98impl<'a, Message, Renderer> BadgedBox<'a, Message, Renderer>
99where
100 Renderer: iced_widget::core::Renderer,
101{
102 pub fn new(
103 anchor: impl Into<Element<'a, Message, Theme, Renderer>>,
104 badge: impl Into<Element<'a, Message, Theme, Renderer>>,
105 placement: BadgedBoxPlacement,
106 ) -> Self {
107 let anchor = anchor.into();
108 let size_hint = anchor.as_widget().size_hint();
109
110 Self {
111 width: size_hint.width,
112 height: size_hint.height,
113 placement,
114 children: vec![anchor, badge.into()],
115 }
116 }
117}
118
119impl<Message, Renderer> fmt::Debug for BadgedBox<'_, Message, Renderer>
120where
121 Renderer: iced_widget::core::Renderer,
122{
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("BadgedBox")
125 .field("width", &self.width)
126 .field("height", &self.height)
127 .field("placement", &self.placement)
128 .finish_non_exhaustive()
129 }
130}
131
132impl<Message, Renderer> Widget<Message, Theme, Renderer> for BadgedBox<'_, Message, Renderer>
133where
134 Renderer: iced_widget::core::Renderer,
135{
136 fn children(&self) -> Vec<Tree> {
137 self.children.iter().map(Tree::new).collect()
138 }
139
140 fn diff(&self, tree: &mut Tree) {
141 tree.diff_children(&self.children);
142 }
143
144 fn size(&self) -> Size<Length> {
145 Size {
146 width: self.width,
147 height: self.height,
148 }
149 }
150
151 fn layout(
152 &mut self,
153 tree: &mut Tree,
154 renderer: &Renderer,
155 limits: &layout::Limits,
156 ) -> layout::Node {
157 let limits = limits.width(self.width).height(self.height);
158 let anchor =
159 self.children[0]
160 .as_widget_mut()
161 .layout(&mut tree.children[0], renderer, &limits);
162 let size = limits.resolve(self.width, self.height, anchor.size());
163 let badge = self.children[1].as_widget_mut().layout(
164 &mut tree.children[1],
165 renderer,
166 &layout::Limits::NONE,
167 );
168 let badge_position = badged_box_badge_position(&anchor, &badge, self.placement);
169
170 layout::Node::with_children(
171 size,
172 vec![anchor.move_to(Point::ORIGIN), badge.move_to(badge_position)],
173 )
174 }
175
176 fn operate(
177 &mut self,
178 tree: &mut Tree,
179 layout: Layout<'_>,
180 renderer: &Renderer,
181 operation: &mut dyn Operation,
182 ) {
183 operation.container(None, layout.bounds());
184 operation.traverse(&mut |operation| {
185 self.children
186 .iter_mut()
187 .zip(&mut tree.children)
188 .zip(layout.children())
189 .for_each(|((child, state), layout)| {
190 child
191 .as_widget_mut()
192 .operate(state, layout, renderer, operation);
193 });
194 });
195 }
196
197 fn update(
198 &mut self,
199 tree: &mut Tree,
200 event: &Event,
201 layout: Layout<'_>,
202 cursor: mouse::Cursor,
203 renderer: &Renderer,
204 clipboard: &mut dyn Clipboard,
205 shell: &mut Shell<'_, Message>,
206 viewport: &Rectangle,
207 ) {
208 for ((child, tree), layout) in self
209 .children
210 .iter_mut()
211 .zip(&mut tree.children)
212 .zip(layout.children())
213 .rev()
214 {
215 child.as_widget_mut().update(
216 tree, event, layout, cursor, renderer, clipboard, shell, viewport,
217 );
218
219 if shell.is_event_captured() {
220 return;
221 }
222 }
223 }
224
225 fn mouse_interaction(
226 &self,
227 tree: &Tree,
228 layout: Layout<'_>,
229 cursor: mouse::Cursor,
230 viewport: &Rectangle,
231 renderer: &Renderer,
232 ) -> mouse::Interaction {
233 self.children
234 .iter()
235 .rev()
236 .zip(tree.children.iter().rev())
237 .zip(layout.children().rev())
238 .map(|((child, tree), layout)| {
239 child
240 .as_widget()
241 .mouse_interaction(tree, layout, cursor, viewport, renderer)
242 })
243 .find(|interaction| *interaction != mouse::Interaction::None)
244 .unwrap_or_default()
245 }
246
247 fn draw(
248 &self,
249 tree: &Tree,
250 renderer: &mut Renderer,
251 theme: &Theme,
252 style: &renderer::Style,
253 layout: Layout<'_>,
254 cursor: mouse::Cursor,
255 viewport: &Rectangle,
256 ) {
257 if layout.bounds().intersection(viewport).is_some() {
258 for (index, ((child, tree), layout)) in self
259 .children
260 .iter()
261 .zip(&tree.children)
262 .zip(layout.children())
263 .enumerate()
264 {
265 if index == 0 {
266 child
267 .as_widget()
268 .draw(tree, renderer, theme, style, layout, cursor, viewport);
269 } else {
270 renderer.with_layer(*viewport, |renderer| {
271 child
272 .as_widget()
273 .draw(tree, renderer, theme, style, layout, cursor, viewport);
274 });
275 }
276 }
277 }
278 }
279
280 fn overlay<'b>(
281 &'b mut self,
282 tree: &'b mut Tree,
283 layout: Layout<'b>,
284 renderer: &Renderer,
285 viewport: &Rectangle,
286 translation: Vector,
287 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
288 overlay::from_children(
289 &mut self.children,
290 tree,
291 layout,
292 renderer,
293 viewport,
294 translation,
295 )
296 }
297}
298
299impl<'a, Message, Renderer> From<BadgedBox<'a, Message, Renderer>>
300 for Element<'a, Message, Theme, Renderer>
301where
302 Message: 'a,
303 Renderer: iced_widget::core::Renderer + 'a,
304{
305 fn from(badged_box: BadgedBox<'a, Message, Renderer>) -> Self {
306 Self::new(badged_box)
307 }
308}
309
310fn badged_box_badge_position(
311 anchor: &layout::Node,
312 badge: &layout::Node,
313 placement: BadgedBoxPlacement,
314) -> Point {
315 let anchor_measured = anchor.size();
316 let badge_measured = badge.size();
317
318 Point::new(
319 anchor_measured.width - placement.horizontal_offset(),
320 -badge_measured.height + placement.vertical_offset(),
321 )
322}
323
324#[cfg(test)]
325#[path = "../../../tests/widget/component/badge.rs"]
326mod tests;