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