1use crate::Opaque;
51use crate::core::layout::{self, Layout};
52use crate::core::mouse;
53use crate::core::overlay;
54use crate::core::renderer;
55use crate::core::touch;
56use crate::core::widget::{self, Widget};
57use crate::core::{Event, Length, Pixels, Point, Rectangle, Shell, Size, Vector};
58
59pub struct Popover<W, V, Message> {
105 content: W,
106 popup: Option<V>,
107 position: Position,
108 gap: f32,
109 snap_within_viewport: bool,
110 on_close: Option<Message>,
111}
112
113impl<W, V, Message> Popover<W, V, Message> {
114 pub fn new(content: W, popup: Option<V>) -> Self {
126 Popover {
127 content,
128 popup,
129 position: Position::default(),
130 gap: 0.0,
131 snap_within_viewport: true,
132 on_close: None,
133 }
134 }
135
136 pub fn on_close(mut self, message: Message) -> Self {
142 self.on_close = Some(message);
143 self
144 }
145
146 pub fn position(mut self, position: Position) -> Self {
151 self.position = position;
152 self
153 }
154
155 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
157 self.gap = gap.into().0;
158 self
159 }
160
161 pub fn snap_within_viewport(mut self, snap: bool) -> Self {
163 self.snap_within_viewport = snap;
164 self
165 }
166}
167
168impl<W, V, Message> Popover<W, Opaque<V>, Message> {
169 pub fn passthrough(self) -> Popover<W, V, Message> {
172 Popover {
173 content: self.content,
174 popup: self.popup.map(Opaque::into_inner),
175 position: self.position,
176 gap: self.gap,
177 snap_within_viewport: self.snap_within_viewport,
178 on_close: self.on_close,
179 }
180 }
181
182 pub fn capture_scroll(mut self, capture: bool) -> Self {
189 self.popup = self
190 .popup
191 .take()
192 .map(|popup| Opaque::new(popup.into_inner()).capture_scroll(capture));
193 self
194 }
195}
196
197impl<W, V, Message> widget::Meta for Popover<W, V, Message> {}
198
199impl<W, V, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Popover<W, V, Message>
200where
201 W: Widget<Message, Theme, Renderer>,
202 V: Widget<Message, Theme, Renderer>,
203 Message: Clone,
204 Renderer: crate::core::Renderer,
205{
206 fn tag(&self) -> widget::tree::Tag {
207 widget::tree::Tag::of::<State>()
208 }
209
210 fn state(&self) -> widget::tree::State {
211 widget::tree::State::new(State {
212 needs_relayout: true,
213 })
214 }
215
216 fn diff(&mut self, tree: &mut widget::Tree) {
217 tree.state.downcast_mut::<State>().needs_relayout = true;
220
221 if tree.children.len() != 2 {
222 tree.children = vec![
223 widget::Tree::new(&self.content),
224 widget::Tree::new(&self.popup),
225 ];
226 }
227
228 tree.children[0].diff(&mut self.content);
229 tree.children[1].diff(&mut self.popup);
230 }
231
232 fn size(&self) -> Size<Length> {
233 self.content.size()
234 }
235
236 fn layout(&mut self, tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits) {
237 self.content.layout(&mut tree.children[0], renderer, limits);
238
239 tree.size = tree.children[0].size;
240 }
241
242 fn update(
243 &mut self,
244 tree: &mut widget::Tree,
245 event: &Event,
246 layout: Layout,
247 cursor: mouse::Cursor,
248 renderer: &Renderer,
249 shell: &mut Shell<'_, Message>,
250 viewport: &Rectangle,
251 ) {
252 self.content.update(
253 &mut tree.children[0],
254 event,
255 layout,
256 cursor,
257 renderer,
258 shell,
259 viewport,
260 );
261 }
262
263 fn draw(
264 &self,
265 tree: &widget::Tree,
266 renderer: &mut Renderer,
267 theme: &Theme,
268 style: &renderer::Style,
269 layout: Layout,
270 cursor: mouse::Cursor,
271 viewport: &Rectangle,
272 ) {
273 self.content.draw(
274 &tree.children[0],
275 renderer,
276 theme,
277 style,
278 layout,
279 cursor,
280 viewport,
281 );
282 }
283
284 fn mouse_interaction(
285 &self,
286 tree: &widget::Tree,
287 layout: Layout,
288 cursor: mouse::Cursor,
289 viewport: &Rectangle,
290 renderer: &Renderer,
291 ) -> mouse::Interaction {
292 self.content
293 .mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer)
294 }
295
296 fn operate(
297 &mut self,
298 tree: &mut widget::Tree,
299 layout: Layout,
300 viewport: &Rectangle,
301 renderer: &Renderer,
302 operation: &mut dyn widget::Operation,
303 ) {
304 operation.container(None, layout.bounds(), viewport);
305 operation.traverse(&mut |operation| {
306 self.content
307 .operate(&mut tree.children[0], layout, viewport, renderer, operation);
308 });
309 }
310
311 fn overlay<'b>(
312 &'b mut self,
313 tree: &'b mut widget::Tree,
314 layout: Layout,
315 renderer: &Renderer,
316 viewport: &Rectangle,
317 translation: Vector,
318 window: Size,
319 ) -> Vec<overlay::Element<'b, Message, Theme, Renderer>> {
320 let mut children = tree.children.iter_mut();
321
322 let base = children.next().unwrap();
323 let popup_tree = children.next();
324
325 let mut overlays =
326 self.content
327 .overlay(base, layout, renderer, viewport, translation, window);
328
329 let viewport = *viewport + translation;
330
331 if let (Some(popup), Some(popup_tree)) = (self.popup.as_mut(), popup_tree) {
332 let state = tree.state.downcast_mut::<State>();
333
334 if state.needs_relayout {
337 popup.layout(
338 popup_tree,
339 renderer,
340 &layout::Limits::new(
341 Size::ZERO,
342 if self.snap_within_viewport {
343 viewport.size()
344 } else {
345 Size::INFINITE
346 },
347 ),
348 );
349
350 state.needs_relayout = false;
351 }
352
353 let popup_size = popup_tree.size;
354
355 let content_bounds = layout.bounds() + translation;
356
357 let popup_bounds = crate::overlay::Position::from(self.position).resolve(
358 content_bounds,
359 popup_size,
360 self.gap,
361 Point::ORIGIN,
362 viewport,
363 self.snap_within_viewport,
364 );
365
366 overlays.push(overlay::Element::new(Box::new(Overlay {
367 content: popup,
368 tree: popup_tree,
369 layout: Layout::new(popup_size).move_to(popup_bounds.position()),
370 content_bounds,
371 on_close: self.on_close.clone(),
372 viewport,
373 window,
374 })));
375 }
376
377 overlays
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
383pub enum Position {
384 #[default]
387 Auto,
388 Top,
390 Bottom,
392 Left,
394 Right,
396}
397
398impl From<Position> for crate::overlay::Position {
399 fn from(position: Position) -> Self {
400 match position {
401 Position::Auto => Self::Auto {
402 preference: crate::overlay::Side::default(),
403 },
404 Position::Top => Self::Top,
405 Position::Bottom => Self::Bottom,
406 Position::Left => Self::Left,
407 Position::Right => Self::Right,
408 }
409 }
410}
411
412struct State {
413 needs_relayout: bool,
414}
415
416struct Overlay<'b, V, Message> {
417 content: &'b mut V,
418 tree: &'b mut widget::Tree,
419 layout: Layout,
420 content_bounds: Rectangle,
421 on_close: Option<Message>,
422 viewport: Rectangle,
423 window: Size,
424}
425
426impl<V, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
427 for Overlay<'_, V, Message>
428where
429 V: Widget<Message, Theme, Renderer>,
430 Renderer: crate::core::Renderer,
431{
432 fn update(
433 &mut self,
434 event: &Event,
435 cursor: mouse::Cursor,
436 renderer: &Renderer,
437 shell: &mut Shell<'_, Message>,
438 ) {
439 let cursor_position = cursor.position();
440
441 let is_inside =
442 cursor_position.is_some_and(|position| self.layout.bounds().contains(position));
443
444 let is_over_base =
445 cursor_position.is_some_and(|position| self.content_bounds.contains(position));
446
447 if matches!(
448 event,
449 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
450 | Event::Touch(touch::Event::FingerPressed { .. })
451 ) && !is_inside
452 {
453 if is_over_base {
454 return;
457 }
458
459 if let Some(on_close) = self.on_close.take() {
462 shell.publish(on_close);
463 }
464
465 return;
466 }
467
468 self.content.update(
469 self.tree,
470 event,
471 self.layout,
472 cursor,
473 renderer,
474 shell,
475 &self.viewport,
476 );
477 }
478
479 fn draw(
480 &self,
481 renderer: &mut Renderer,
482 theme: &Theme,
483 style: &renderer::Style,
484 cursor_position: mouse::Cursor,
485 ) {
486 renderer.with_layer(self.viewport, |renderer| {
487 self.content.draw(
488 self.tree,
489 renderer,
490 theme,
491 style,
492 self.layout,
493 cursor_position,
494 &self.viewport,
495 );
496 });
497 }
498
499 fn mouse_interaction(&self, cursor: mouse::Cursor, renderer: &Renderer) -> mouse::Interaction {
500 if !cursor.is_over(self.layout.bounds()) {
501 return mouse::Interaction::None;
502 }
503
504 self.content
505 .mouse_interaction(self.tree, self.layout, cursor, &self.viewport, renderer)
506 }
507
508 fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
509 self.content
510 .operate(self.tree, self.layout, &self.viewport, renderer, operation);
511 }
512
513 fn overlay<'c>(
514 &'c mut self,
515 renderer: &Renderer,
516 ) -> Vec<overlay::Element<'c, Message, Theme, Renderer>> {
517 self.content.overlay(
518 self.tree,
519 self.layout,
520 renderer,
521 &self.viewport,
522 Vector::ZERO,
523 self.window,
524 )
525 }
526
527 fn index(&self) -> f32 {
529 2.0
530 }
531}