1use iced::widget;
7use iced_core::event::{self, Event};
8use iced_core::layout;
9use iced_core::mouse;
10use iced_core::overlay;
11use iced_core::renderer;
12use iced_core::touch;
13use iced_core::widget::{Operation, Tree};
14use iced_core::{
15 Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
16};
17
18pub use iced_widget::container::{Catalog, Style};
19
20pub fn popover<'a, Message, Renderer>(
21 content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
22) -> Popover<'a, Message, Renderer> {
23 Popover::new(content)
24}
25
26#[derive(Clone, Copy, Debug, Default)]
27pub enum Position {
28 #[default]
29 Center,
30 Bottom,
31 Point(Point),
32}
33
34#[must_use]
36pub struct Popover<'a, Message, Renderer> {
37 id: widget::Id,
38 content: Element<'a, Message, crate::Theme, Renderer>,
39 modal: bool,
40 popup: Option<Element<'a, Message, crate::Theme, Renderer>>,
41 position: Position,
42 on_close: Option<Message>,
43}
44
45impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
46 pub fn new(content: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
47 Self {
48 id: widget::Id::unique(),
49 content: content.into(),
50 modal: false,
51 popup: None,
52 position: Position::Center,
53 on_close: None,
54 }
55 }
56
57 #[inline]
59 pub fn id(mut self, id: widget::Id) -> Self {
60 self.id = id;
61 self
62 }
63
64 #[inline]
66 pub fn modal(mut self, modal: bool) -> Self {
67 self.modal = modal;
68 self
69 }
70
71 #[inline]
73 pub fn on_close(mut self, on_close: Message) -> Self {
74 self.on_close = Some(on_close);
75 self
76 }
77
78 #[inline]
79 pub fn popup(mut self, popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
80 self.popup = Some(popup.into());
81 self
82 }
83
84 #[inline]
85 pub fn position(mut self, position: Position) -> Self {
86 self.position = position;
87 self
88 }
89}
90
91impl<Message: Clone, Renderer> Widget<Message, crate::Theme, Renderer>
92 for Popover<'_, Message, Renderer>
93where
94 Renderer: iced_core::Renderer,
95{
96 fn id(&self) -> Option<widget::Id> {
97 Some(self.id.clone())
98 }
99
100 fn set_id(&mut self, id: widget::Id) {
101 self.id = id;
102 }
103
104 fn children(&self) -> Vec<Tree> {
105 if let Some(popup) = &self.popup {
106 vec![Tree::new(&self.content), Tree::new(popup)]
107 } else {
108 vec![Tree::new(&self.content)]
109 }
110 }
111
112 fn diff(&mut self, tree: &mut Tree) {
113 if let Some(popup) = &mut self.popup {
114 tree.diff_children(&mut [&mut self.content, popup]);
115 } else {
116 tree.diff_children(&mut [&mut self.content]);
117 }
118 }
119
120 fn size(&self) -> Size<Length> {
121 self.content.as_widget().size()
122 }
123
124 fn layout(
125 &mut self,
126 tree: &mut Tree,
127 renderer: &Renderer,
128 limits: &layout::Limits,
129 ) -> layout::Node {
130 let tree = &mut tree.children[0];
131 self.content.as_widget_mut().layout(tree, renderer, limits)
132 }
133
134 fn operate(
135 &mut self,
136 tree: &mut Tree,
137 layout: Layout<'_>,
138 renderer: &Renderer,
139 operation: &mut dyn Operation,
140 ) {
141 if self.modal && self.popup.is_some() {
143 return;
144 }
145 self.content
146 .as_widget_mut()
147 .operate(content_tree_mut(tree), layout, renderer, operation);
148 }
149
150 fn update(
151 &mut self,
152 tree: &mut Tree,
153 event: &Event,
154 layout: Layout<'_>,
155 cursor_position: mouse::Cursor,
156 renderer: &Renderer,
157 clipboard: &mut dyn Clipboard,
158 shell: &mut Shell<'_, Message>,
159 viewport: &Rectangle,
160 ) {
161 if self.popup.is_some() {
162 if self.modal {
163 if matches!(event, Event::Mouse(_) | Event::Touch(_)) {
164 shell.capture_event();
165 return;
166 }
167 } else if let Some(on_close) = self.on_close.as_ref() {
168 if matches!(
169 event,
170 Event::Mouse(mouse::Event::ButtonPressed(_))
171 | Event::Touch(touch::Event::FingerPressed { .. })
172 ) && !cursor_position.is_over(layout.bounds())
173 {
174 shell.publish(on_close.clone());
175 }
176 }
177 }
178
179 let cursor = if self.modal && self.popup.is_some() {
181 mouse::Cursor::Unavailable
182 } else {
183 cursor_position
184 };
185 self.content.as_widget_mut().update(
186 &mut tree.children[0],
187 event,
188 layout,
189 cursor,
190 renderer,
191 clipboard,
192 shell,
193 viewport,
194 )
195 }
196
197 fn mouse_interaction(
198 &self,
199 tree: &Tree,
200 layout: Layout<'_>,
201 cursor_position: mouse::Cursor,
202 viewport: &Rectangle,
203 renderer: &Renderer,
204 ) -> mouse::Interaction {
205 if self.modal && self.popup.is_some() && cursor_position.is_over(layout.bounds()) {
206 return mouse::Interaction::None;
207 }
208 self.content.as_widget().mouse_interaction(
209 content_tree(tree),
210 layout,
211 cursor_position,
212 viewport,
213 renderer,
214 )
215 }
216
217 fn draw(
218 &self,
219 tree: &Tree,
220 renderer: &mut Renderer,
221 theme: &crate::Theme,
222 renderer_style: &renderer::Style,
223 layout: Layout<'_>,
224 cursor_position: mouse::Cursor,
225 viewport: &Rectangle,
226 ) {
227 let cursor = if self.modal && self.popup.is_some() {
229 mouse::Cursor::Unavailable
230 } else {
231 cursor_position
232 };
233 self.content.as_widget().draw(
234 content_tree(tree),
235 renderer,
236 theme,
237 renderer_style,
238 layout,
239 cursor,
240 viewport,
241 );
242 }
243
244 fn overlay<'b>(
245 &'b mut self,
246 tree: &'b mut Tree,
247 layout: Layout<'b>,
248 renderer: &Renderer,
249 viewport: &Rectangle,
250 mut translation: Vector,
251 ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
252 if let Some(popup) = &mut self.popup {
253 let bounds = layout.bounds();
254
255 let mut overlay_position = match self.position {
257 Position::Center => Point::new(
258 bounds.x + bounds.width / 2.0,
259 bounds.y + bounds.height / 2.0,
260 ),
261 Position::Bottom => {
262 Point::new(bounds.x + bounds.width / 2.0, bounds.y + bounds.height)
263 }
264 Position::Point(relative) => {
265 bounds.position() + Vector::new(relative.x, relative.y)
266 }
267 };
268
269 overlay_position.x = overlay_position.x.round();
271 overlay_position.y = overlay_position.y.round();
272 translation.x += overlay_position.x;
273 translation.y += overlay_position.y;
274 Some(overlay::Element::new(Box::new(Overlay {
275 tree: &mut tree.children[1],
276 content: popup,
277 position: self.position,
278 pos: Point::new(translation.x, translation.y),
279 modal: self.modal,
280 })))
281 } else {
282 self.content.as_widget_mut().overlay(
283 &mut tree.children[0],
284 layout,
285 renderer,
286 viewport,
287 translation,
288 )
289 }
290 }
291
292 fn drag_destinations(
293 &self,
294 tree: &Tree,
295 layout: Layout<'_>,
296 renderer: &Renderer,
297 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
298 ) {
299 self.content.as_widget().drag_destinations(
300 content_tree(tree),
301 layout,
302 renderer,
303 dnd_rectangles,
304 );
305 }
306
307 #[cfg(feature = "a11y")]
308 fn a11y_nodes(
310 &self,
311 layout: Layout<'_>,
312 state: &Tree,
313 p: mouse::Cursor,
314 ) -> iced_accessibility::A11yTree {
315 self.content
316 .as_widget()
317 .a11y_nodes(layout, content_tree(state), p)
318 }
319}
320
321impl<'a, Message, Renderer> From<Popover<'a, Message, Renderer>>
322 for Element<'a, Message, crate::Theme, Renderer>
323where
324 Message: 'static + Clone,
325 Renderer: iced_core::Renderer + 'static,
326{
327 fn from(popover: Popover<'a, Message, Renderer>) -> Self {
328 Self::new(popover)
329 }
330}
331
332pub struct Overlay<'a, 'b, Message, Renderer> {
333 tree: &'a mut Tree,
334 content: &'a mut Element<'b, Message, crate::Theme, Renderer>,
335 position: Position,
336 pos: Point,
337 modal: bool,
338}
339
340impl<Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
341 for Overlay<'_, '_, Message, Renderer>
342where
343 Message: Clone,
344 Renderer: iced_core::Renderer,
345{
346 fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
347 let mut position = self.pos;
348 let limits = layout::Limits::new(Size::UNIT, bounds);
349 let node = self
350 .content
351 .as_widget_mut()
352 .layout(self.tree, renderer, &limits);
353 match self.position {
354 Position::Center => {
355 let width = node.size().width;
357 let height = node.size().height;
358 position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width);
359 position.y = (position.y - height / 2.0).clamp(0.0, bounds.height - height);
360 }
361 Position::Bottom => {
362 let width = node.size().width;
364 let height = node.size().height;
365 position.x = (position.x - width / 2.0).clamp(0.0, bounds.width - width);
366 position.y = position.y.clamp(0.0, bounds.height - height);
367 }
368 Position::Point(_) => {
369 let size = node.size();
371 position.x = position.x.clamp(0.0, bounds.width - size.width);
372 if position.y + size.height > bounds.height {
373 position.y = (position.y - size.height).clamp(0.0, bounds.height - size.height);
374 }
375 }
376 }
377
378 position.x = position.x.round();
380 position.y = position.y.round();
381
382 node.move_to(position)
383 }
384
385 fn operate(
386 &mut self,
387 layout: Layout<'_>,
388 renderer: &Renderer,
389 operation: &mut dyn Operation<()>,
390 ) {
391 self.content
392 .as_widget_mut()
393 .operate(self.tree, layout, renderer, operation);
394 }
395
396 fn update(
397 &mut self,
398 event: &Event,
399 layout: Layout<'_>,
400 cursor_position: mouse::Cursor,
401 renderer: &Renderer,
402 clipboard: &mut dyn Clipboard,
403 shell: &mut Shell<'_, Message>,
404 ) {
405 if self.modal
406 && matches!(event, Event::Mouse(_) | Event::Touch(_))
407 && !cursor_position.is_over(layout.bounds())
408 {
409 shell.capture_event();
410 return;
411 }
412
413 self.content.as_widget_mut().update(
414 self.tree,
415 event,
416 layout,
417 cursor_position,
418 renderer,
419 clipboard,
420 shell,
421 &layout.bounds(),
422 )
423 }
424
425 fn mouse_interaction(
426 &self,
427 layout: Layout<'_>,
428 cursor_position: mouse::Cursor,
429 renderer: &Renderer,
430 ) -> mouse::Interaction {
431 if self.modal && !cursor_position.is_over(layout.bounds()) {
432 return mouse::Interaction::None;
433 }
434
435 self.content.as_widget().mouse_interaction(
436 self.tree,
437 layout,
438 cursor_position,
439 &layout.bounds(),
440 renderer,
441 )
442 }
443
444 fn draw(
445 &self,
446 renderer: &mut Renderer,
447 theme: &crate::Theme,
448 style: &renderer::Style,
449 layout: Layout<'_>,
450 cursor_position: mouse::Cursor,
451 ) {
452 let bounds = layout.bounds();
453 self.content.as_widget().draw(
454 self.tree,
455 renderer,
456 theme,
457 style,
458 layout,
459 cursor_position,
460 &bounds,
461 );
462 }
463
464 fn overlay<'c>(
465 &'c mut self,
466 layout: Layout<'c>,
467 renderer: &Renderer,
468 ) -> Option<overlay::Element<'c, Message, crate::Theme, Renderer>> {
469 self.content.as_widget_mut().overlay(
470 self.tree,
471 layout,
472 renderer,
473 &layout.bounds(),
474 Default::default(),
475 )
476 }
477}
478
479#[derive(Debug, Default)]
481struct State {
482 is_open: bool,
483}
484
485fn content_tree(tree: &Tree) -> &Tree {
487 &tree.children[0]
488}
489
490fn content_tree_mut(tree: &mut Tree) -> &mut Tree {
492 &mut tree.children[0]
493}