iced_aw/widget/
context_menu.rs

1//! A context menu for showing actions on right click.
2//!
3use iced_core::{
4    Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Vector, Widget,
5    layout::{Limits, Node},
6    mouse::{self, Button, Cursor},
7    overlay, renderer,
8    widget::{Operation, Tree, tree},
9};
10
11pub use crate::style::{
12    context_menu::{Catalog, Style},
13    status::{Status, StyleFn},
14};
15
16use crate::widget::overlay::ContextMenuOverlay;
17
18/// A context menu
19///
20///
21/// # Example
22/// ```ignore
23/// # use iced_widget::{Text, Button};
24/// # use iced_aw::ContextMenu;
25/// #
26/// #[derive(Debug, Clone)]
27/// enum Message {
28///     Action1,
29/// }
30///
31/// let underlay = Text::new("right click me");
32///
33/// let cm = ContextMenu::new(
34///     underlay,
35///     || Button::new("action1").on_press(Message::Action1).into()
36/// );
37/// ```
38#[allow(missing_debug_implementations)]
39pub struct ContextMenu<
40    'a,
41    Overlay,
42    Message,
43    Theme = iced_widget::Theme,
44    Renderer = iced_widget::Renderer,
45> where
46    Overlay: Fn() -> Element<'a, Message, Theme, Renderer>,
47    Message: Clone,
48    Renderer: renderer::Renderer,
49    Theme: Catalog,
50{
51    /// The underlying element.
52    underlay: Element<'a, Message, Theme, Renderer>,
53    /// The content of [`ContextMenuOverlay`].
54    overlay: Overlay,
55    /// The style of the [`ContextMenu`].
56    class: Theme::Class<'a>,
57}
58
59impl<'a, Overlay, Message, Theme, Renderer> ContextMenu<'a, Overlay, Message, Theme, Renderer>
60where
61    Overlay: Fn() -> Element<'a, Message, Theme, Renderer>,
62    Message: Clone,
63    Renderer: renderer::Renderer,
64    Theme: Catalog,
65{
66    /// Creates a new [`ContextMenu`]
67    ///
68    /// `underlay`: The underlying element.
69    ///
70    /// `overlay`: The content of [`ContextMenuOverlay`] which will be displayed when `underlay` is clicked.
71    pub fn new<U>(underlay: U, overlay: Overlay) -> Self
72    where
73        U: Into<Element<'a, Message, Theme, Renderer>>,
74    {
75        ContextMenu {
76            underlay: underlay.into(),
77            overlay,
78            class: Theme::default(),
79        }
80    }
81
82    /// Sets the style of the [`ContextMenu`].
83    #[must_use]
84    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
85    where
86        Theme::Class<'a>: From<StyleFn<'a, Theme, Style>>,
87    {
88        self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
89        self
90    }
91
92    /// Sets the class of the input of the [`ContextMenu`].
93    #[must_use]
94    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
95        self.class = class.into();
96        self
97    }
98}
99
100impl<'a, Content, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
101    for ContextMenu<'a, Content, Message, Theme, Renderer>
102where
103    Content: 'a + Fn() -> Element<'a, Message, Theme, Renderer>,
104    Message: 'a + Clone,
105    Renderer: 'a + renderer::Renderer,
106    Theme: Catalog,
107{
108    fn size(&self) -> iced_core::Size<Length> {
109        self.underlay.as_widget().size()
110    }
111
112    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
113        self.underlay
114            .as_widget_mut()
115            .layout(&mut tree.children[0], renderer, limits)
116    }
117
118    fn draw(
119        &self,
120        state: &Tree,
121        renderer: &mut Renderer,
122        theme: &Theme,
123        style: &renderer::Style,
124        layout: Layout<'_>,
125        cursor: Cursor,
126        viewport: &Rectangle,
127    ) {
128        self.underlay.as_widget().draw(
129            &state.children[0],
130            renderer,
131            theme,
132            style,
133            layout,
134            cursor,
135            viewport,
136        );
137    }
138
139    fn tag(&self) -> tree::Tag {
140        tree::Tag::of::<State>()
141    }
142
143    fn state(&self) -> tree::State {
144        tree::State::new(State::new())
145    }
146
147    fn children(&self) -> Vec<Tree> {
148        vec![Tree::new(&self.underlay), Tree::new((self.overlay)())]
149    }
150
151    fn diff(&self, tree: &mut Tree) {
152        tree.diff_children(&[&self.underlay, &(self.overlay)()]);
153    }
154
155    fn operate<'b>(
156        &'b mut self,
157        state: &'b mut Tree,
158        layout: Layout<'_>,
159        renderer: &Renderer,
160        operation: &mut dyn Operation<()>,
161    ) {
162        let s: &mut State = state.state.downcast_mut();
163
164        if s.show {
165            let mut content = (self.overlay)();
166            content.as_widget_mut().diff(&mut state.children[1]);
167
168            content
169                .as_widget_mut()
170                .operate(&mut state.children[1], layout, renderer, operation);
171        } else {
172            self.underlay.as_widget_mut().operate(
173                &mut state.children[0],
174                layout,
175                renderer,
176                operation,
177            );
178        }
179    }
180
181    fn update(
182        &mut self,
183        state: &mut Tree,
184        event: &Event,
185        layout: Layout<'_>,
186        cursor: Cursor,
187        renderer: &Renderer,
188        clipboard: &mut dyn Clipboard,
189        shell: &mut Shell<'_, Message>,
190        viewport: &Rectangle,
191    ) {
192        if *event == Event::Mouse(mouse::Event::ButtonPressed(Button::Right)) {
193            let bounds = layout.bounds();
194
195            if cursor.is_over(bounds) {
196                let s: &mut State = state.state.downcast_mut();
197                s.cursor_position = cursor.position().unwrap_or_default();
198                s.show = !s.show;
199                shell.capture_event();
200            }
201        }
202
203        self.underlay.as_widget_mut().update(
204            &mut state.children[0],
205            event,
206            layout,
207            cursor,
208            renderer,
209            clipboard,
210            shell,
211            viewport,
212        );
213    }
214
215    fn mouse_interaction(
216        &self,
217        state: &Tree,
218        layout: Layout<'_>,
219        cursor: Cursor,
220        viewport: &Rectangle,
221        renderer: &Renderer,
222    ) -> mouse::Interaction {
223        self.underlay.as_widget().mouse_interaction(
224            &state.children[0],
225            layout,
226            cursor,
227            viewport,
228            renderer,
229        )
230    }
231
232    fn overlay<'b>(
233        &'b mut self,
234        tree: &'b mut Tree,
235        layout: Layout<'b>,
236        renderer: &Renderer,
237        viewport: &Rectangle,
238        translation: Vector,
239    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
240        let s: &mut State = tree.state.downcast_mut();
241
242        if !s.show {
243            return self.underlay.as_widget_mut().overlay(
244                &mut tree.children[0],
245                layout,
246                renderer,
247                viewport,
248                translation,
249            );
250        }
251
252        let position = s.cursor_position;
253        let mut content = (self.overlay)();
254        content.as_widget_mut().diff(&mut tree.children[1]);
255        Some(
256            ContextMenuOverlay::new(
257                position + translation,
258                &mut tree.children[1],
259                content,
260                &self.class,
261                s,
262            )
263            .overlay(),
264        )
265    }
266}
267
268impl<'a, Content, Message, Theme, Renderer> From<ContextMenu<'a, Content, Message, Theme, Renderer>>
269    for Element<'a, Message, Theme, Renderer>
270where
271    Content: 'a + Fn() -> Self,
272    Message: 'a + Clone,
273    Renderer: 'a + renderer::Renderer,
274    Theme: 'a + Catalog,
275{
276    fn from(modal: ContextMenu<'a, Content, Message, Theme, Renderer>) -> Self {
277        Element::new(modal)
278    }
279}
280
281/// The state of the ``context_menu``.
282#[derive(Debug, Default)]
283pub(crate) struct State {
284    /// The visibility of the [`ContextMenu`] overlay.
285    pub show: bool,
286    /// Use for showing the overlay where the click was made.
287    pub cursor_position: Point,
288}
289
290impl State {
291    /// Creates a new [`State`] containing the given state data.
292    pub const fn new() -> Self {
293        Self {
294            show: false,
295            cursor_position: Point::ORIGIN,
296        }
297    }
298}