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