fix(popover): modal popover handling broken

This commit is contained in:
Michael Aaron Murphy 2025-03-26 16:13:32 +01:00 committed by Michael Murphy
parent 2753941aad
commit 61c0fc7543

View file

@ -1,7 +1,7 @@
// Copyright 2022 System76 <info@system76.com> // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
//! A widget showing a popup in an overlay positioned relative to another widget. //! A container which displays an overlay when a popup widget is attached.
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::layout; use iced_core::layout;
@ -9,7 +9,7 @@ use iced_core::mouse;
use iced_core::overlay; use iced_core::overlay;
use iced_core::renderer; use iced_core::renderer;
use iced_core::touch; use iced_core::touch;
use iced_core::widget::{Operation, Tree, tree}; use iced_core::widget::{Operation, Tree};
use iced_core::{ use iced_core::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
}; };
@ -30,6 +30,7 @@ pub enum Position {
Point(Point), Point(Point),
} }
/// A container which displays overlays when a popup widget is assigned.
#[must_use] #[must_use]
pub struct Popover<'a, Message, Renderer> { pub struct Popover<'a, Message, Renderer> {
content: Element<'a, Message, crate::Theme, Renderer>, content: Element<'a, Message, crate::Theme, Renderer>,
@ -50,29 +51,31 @@ impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
} }
} }
/// A modal popup interrupts user inputs and demands action. /// A modal popup intercepts user inputs while a popup is active.
#[inline]
pub fn modal(mut self, modal: bool) -> Self { pub fn modal(mut self, modal: bool) -> Self {
self.modal = modal; self.modal = modal;
self self
} }
/// Emitted when the popup is closed. /// Emitted when the popup is closed.
#[inline]
pub fn on_close(mut self, on_close: Message) -> Self { pub fn on_close(mut self, on_close: Message) -> Self {
self.on_close = Some(on_close); self.on_close = Some(on_close);
self self
} }
#[inline]
pub fn popup(mut self, popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self { pub fn popup(mut self, popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
self.popup = Some(popup.into()); self.popup = Some(popup.into());
self self
} }
#[inline]
pub fn position(mut self, position: Position) -> Self { pub fn position(mut self, position: Position) -> Self {
self.position = position; self.position = position;
self self
} }
// TODO More options for positioning similar to GdkPopup, xdg_popup
} }
impl<Message: Clone, Renderer> Widget<Message, crate::Theme, Renderer> impl<Message: Clone, Renderer> Widget<Message, crate::Theme, Renderer>
@ -80,14 +83,6 @@ impl<Message: Clone, Renderer> Widget<Message, crate::Theme, Renderer>
where where
Renderer: iced_core::Renderer, Renderer: iced_core::Renderer,
{ {
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State { is_open: true })
}
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
if let Some(popup) = &self.popup { if let Some(popup) = &self.popup {
vec![Tree::new(&self.content), Tree::new(popup)] vec![Tree::new(&self.content), Tree::new(popup)]
@ -114,7 +109,7 @@ where
renderer: &Renderer, renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let tree = &mut tree.children[0]; let tree = content_tree_mut(tree);
self.content.as_widget().layout(tree, renderer, limits) self.content.as_widget().layout(tree, renderer, limits)
} }
@ -127,7 +122,7 @@ where
) { ) {
self.content self.content
.as_widget() .as_widget()
.operate(&mut tree.children[0], layout, renderer, operation); .operate(content_tree_mut(tree), layout, renderer, operation);
} }
fn on_event( fn on_event(
@ -141,26 +136,25 @@ where
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
if !self.modal if self.popup.is_some() {
&& matches!( if self.modal {
event, if matches!(event, Event::Mouse(_) | Event::Touch(_)) {
Event::Mouse(mouse::Event::ButtonPressed(_)) return event::Status::Captured;
| Event::Touch(touch::Event::FingerPressed { .. }) }
) } else if let Some(on_close) = self.on_close.clone() {
{ if matches!(
let state = tree.state.downcast_mut::<State>(); event,
let was_open = state.is_open; Event::Mouse(mouse::Event::ButtonPressed(_))
state.is_open = cursor_position.is_over(layout.bounds()); | Event::Touch(touch::Event::FingerPressed { .. })
) && !cursor_position.is_over(layout.bounds())
if let Some(on_close) = self.on_close.clone() { {
if was_open && !state.is_open {
shell.publish(on_close); shell.publish(on_close);
} }
} }
} }
self.content.as_widget_mut().on_event( self.content.as_widget_mut().on_event(
&mut tree.children[0], content_tree_mut(tree),
event, event,
layout, layout,
cursor_position, cursor_position,
@ -179,8 +173,11 @@ where
viewport: &Rectangle, viewport: &Rectangle,
renderer: &Renderer, renderer: &Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
if self.modal && self.popup.is_some() && cursor_position.is_over(layout.bounds()) {
return mouse::Interaction::None;
}
self.content.as_widget().mouse_interaction( self.content.as_widget().mouse_interaction(
&tree.children[0], content_tree(tree),
layout, layout,
cursor_position, cursor_position,
viewport, viewport,
@ -199,7 +196,7 @@ where
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
self.content.as_widget().draw( self.content.as_widget().draw(
&tree.children[0], content_tree(tree),
renderer, renderer,
theme, theme,
renderer_style, renderer_style,
@ -216,10 +213,6 @@ where
renderer: &Renderer, renderer: &Renderer,
mut translation: Vector, mut translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
if !tree.state.downcast_mut::<State>().is_open {
return None;
}
if let Some(popup) = &mut self.popup { if let Some(popup) = &mut self.popup {
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -248,10 +241,11 @@ where
content: popup, content: popup,
position: self.position, position: self.position,
pos: Point::new(translation.x, translation.y), pos: Point::new(translation.x, translation.y),
modal: self.modal,
}))) })))
} else { } else {
self.content.as_widget_mut().overlay( self.content.as_widget_mut().overlay(
&mut tree.children[0], content_tree_mut(tree),
layout, layout,
renderer, renderer,
translation, translation,
@ -267,7 +261,7 @@ where
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
) { ) {
self.content.as_widget().drag_destinations( self.content.as_widget().drag_destinations(
&tree.children[0], content_tree(tree),
layout, layout,
renderer, renderer,
dnd_rectangles, dnd_rectangles,
@ -282,8 +276,9 @@ where
state: &Tree, state: &Tree,
p: mouse::Cursor, p: mouse::Cursor,
) -> iced_accessibility::A11yTree { ) -> iced_accessibility::A11yTree {
let c_state = &state.children[0]; self.content
self.content.as_widget().a11y_nodes(layout, c_state, p) .as_widget()
.a11y_nodes(layout, content_tree(state), p)
} }
} }
@ -303,6 +298,7 @@ pub struct Overlay<'a, 'b, Message, Renderer> {
content: &'a mut Element<'b, Message, crate::Theme, Renderer>, content: &'a mut Element<'b, Message, crate::Theme, Renderer>,
position: Position, position: Position,
pos: Point, pos: Point,
modal: bool,
} }
impl<Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer> impl<Message, Renderer> overlay::Overlay<Message, crate::Theme, Renderer>
@ -370,6 +366,13 @@ where
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) -> event::Status { ) -> event::Status {
if self.modal
&& matches!(event, Event::Mouse(_) | Event::Touch(_))
&& !cursor_position.is_over(layout.bounds())
{
return event::Status::Captured;
}
self.content.as_widget_mut().on_event( self.content.as_widget_mut().on_event(
self.tree, self.tree,
event, event,
@ -389,6 +392,10 @@ where
viewport: &Rectangle, viewport: &Rectangle,
renderer: &Renderer, renderer: &Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
if self.modal && !cursor_position.is_over(layout.bounds()) {
return mouse::Interaction::None;
}
self.content.as_widget().mouse_interaction( self.content.as_widget().mouse_interaction(
self.tree, self.tree,
layout, layout,
@ -434,3 +441,13 @@ where
struct State { struct State {
is_open: bool, is_open: bool,
} }
/// The first child in [`Popover::children`] is always the wrapped content.
fn content_tree(tree: &Tree) -> &Tree {
&tree.children[0]
}
/// The first child in [`Popover::children`] is always the wrapped content.
fn content_tree_mut(tree: &mut Tree) -> &mut Tree {
&mut tree.children[0]
}