iced_aw/widget/overlay/
context_menu.rs

1//! A modal for showing elements as an overlay on top of another.
2//!
3//! *This API requires the following crate features to be activated: ``context_menu``*
4use crate::context_menu;
5pub use crate::style::{
6    context_menu::{Catalog, Style},
7    status::{self, StyleFn},
8};
9
10use iced_core::{
11    Border, Clipboard, Color, Element, Event, Layout, Point, Shell, Size, keyboard,
12    layout::{Limits, Node},
13    mouse::{self, Cursor},
14    overlay, renderer, touch,
15    widget::Tree,
16    window,
17};
18
19/// The overlay of the [`ContextMenu`](crate::widget::ContextMenu).
20#[allow(missing_debug_implementations)]
21pub struct ContextMenuOverlay<
22    'a,
23    'b,
24    Message,
25    Theme = iced_widget::Theme,
26    Renderer = iced_widget::Renderer,
27> where
28    Message: 'a + Clone,
29    Renderer: 'a + renderer::Renderer,
30    Theme: Catalog,
31    'b: 'a,
32{
33    // The position of the element
34    position: Point,
35    /// The state of the [`ContextMenuOverlay`].
36    tree: &'a mut Tree,
37    /// The content of the [`ContextMenuOverlay`].
38    content: Element<'a, Message, Theme, Renderer>,
39    /// The style of the [`ContextMenuOverlay`].
40    class: &'a Theme::Class<'b>,
41    /// The state shared between [`ContextMenu`](crate::widget::ContextMenu) and [`ContextMenuOverlay`].
42    state: &'a mut context_menu::State,
43}
44
45impl<'a, 'b, Message, Theme, Renderer> ContextMenuOverlay<'a, 'b, Message, Theme, Renderer>
46where
47    Message: Clone,
48    Renderer: renderer::Renderer,
49    Theme: 'a + Catalog,
50    'b: 'a,
51{
52    /// Creates a new [`ContextMenuOverlay`].
53    pub(crate) fn new<C>(
54        position: Point,
55        tree: &'a mut Tree,
56        content: C,
57        class: &'a <Theme as Catalog>::Class<'b>,
58        state: &'a mut context_menu::State,
59    ) -> Self
60    where
61        C: Into<Element<'a, Message, Theme, Renderer>>,
62    {
63        ContextMenuOverlay {
64            position,
65            tree,
66            content: content.into(),
67            class,
68            state,
69        }
70    }
71
72    /// Turn this [`ContextMenuOverlay`] into an overlay [`Element`](overlay::Element).
73    #[must_use]
74    pub fn overlay(self) -> overlay::Element<'a, Message, Theme, Renderer> {
75        overlay::Element::new(Box::new(self))
76    }
77}
78
79impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
80    for ContextMenuOverlay<'a, 'b, Message, Theme, Renderer>
81where
82    Message: 'a + Clone,
83    Renderer: 'a + renderer::Renderer,
84    Theme: 'a + Catalog,
85    'b: 'a,
86{
87    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
88        let limits = Limits::new(Size::ZERO, bounds);
89        let max_size = limits.max();
90
91        let mut content = self
92            .content
93            .as_widget_mut()
94            .layout(self.tree, renderer, &limits);
95
96        // Try to stay inside borders
97        let mut position = self.position;
98        if position.x + content.size().width > bounds.width {
99            position.x = f32::max(0.0, position.x - content.size().width);
100        }
101        if position.y + content.size().height > bounds.height {
102            position.y = f32::max(0.0, position.y - content.size().height);
103        }
104
105        content.move_to_mut(position);
106
107        Node::with_children(max_size, vec![content])
108    }
109
110    fn draw(
111        &self,
112        renderer: &mut Renderer,
113        theme: &Theme,
114        style: &renderer::Style,
115        layout: Layout<'_>,
116        cursor: Cursor,
117    ) {
118        let bounds = layout.bounds();
119
120        let style_sheet = theme.style(self.class, status::Status::Active);
121
122        // Background
123
124        renderer.fill_quad(
125            renderer::Quad {
126                bounds,
127                border: Border {
128                    radius: (0.0).into(),
129                    width: 0.0,
130                    color: Color::TRANSPARENT,
131                },
132                ..Default::default()
133            },
134            style_sheet.background,
135        );
136
137        let content_layout = layout
138            .children()
139            .next()
140            .expect("widget: Layout should have a content layout.");
141
142        // Modal
143        self.content.as_widget().draw(
144            self.tree,
145            renderer,
146            theme,
147            style,
148            content_layout,
149            cursor,
150            &bounds,
151        );
152    }
153
154    fn update(
155        &mut self,
156        event: &Event,
157        layout: Layout<'_>,
158        cursor: iced_core::mouse::Cursor,
159        renderer: &Renderer,
160        clipboard: &mut dyn Clipboard,
161        shell: &mut Shell<'_, Message>,
162    ) {
163        let layout_children = layout
164            .children()
165            .next()
166            .expect("widget: Layout should have a content layout.");
167
168        let mut forward_event_to_children = true;
169        let mut capture_event = false;
170
171        match &event {
172            Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
173                if *key == keyboard::Key::Named(keyboard::key::Named::Escape) {
174                    self.state.show = false;
175                    forward_event_to_children = false;
176                    shell.capture_event();
177                }
178            }
179
180            Event::Mouse(mouse::Event::ButtonPressed(
181                mouse::Button::Left | mouse::Button::Right,
182            ))
183            | Event::Touch(touch::Event::FingerPressed { .. }) => {
184                if cursor.is_over(layout_children.bounds()) {
185                    capture_event = true;
186                } else {
187                    self.state.show = false;
188                    forward_event_to_children = false;
189                    shell.request_redraw();
190                }
191            }
192
193            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
194                // close when released because because button send message on release
195                self.state.show = false;
196
197                capture_event = true;
198            }
199
200            Event::Window(window::Event::Resized { .. }) => {
201                self.state.show = false;
202                forward_event_to_children = false;
203                capture_event = true;
204            }
205
206            _ => {}
207        }
208
209        if forward_event_to_children {
210            self.content.as_widget_mut().update(
211                self.tree,
212                event,
213                layout_children,
214                cursor,
215                renderer,
216                clipboard,
217                shell,
218                &layout.bounds(),
219            );
220        }
221        if capture_event {
222            shell.capture_event();
223        }
224    }
225
226    fn mouse_interaction(
227        &self,
228        layout: Layout<'_>,
229
230        cursor: mouse::Cursor,
231        renderer: &Renderer,
232    ) -> mouse::Interaction {
233        let bounds = layout.bounds();
234
235        self.content.as_widget().mouse_interaction(
236            self.tree,
237            layout
238                .children()
239                .next()
240                .expect("widget: Layout should have a content layout."),
241            cursor,
242            &bounds,
243            renderer,
244        )
245    }
246}