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