Skip to main content

graphix_package_gui/widgets/
context_menu_widget.rs

1use iced_core::{
2    keyboard, layout, mouse, overlay, renderer, touch, widget, Clipboard, Element, Event,
3    Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
4};
5
6use super::menu_bar_widget::{MenuGroupDesc, MenuItemDesc, MenuOverlay};
7use super::{Message, Renderer};
8use crate::theme::GraphixTheme;
9
10#[derive(Default)]
11struct State {
12    open: bool,
13    position: Point,
14}
15
16/// An iced widget that wraps a child and shows a dropdown menu on
17/// right-click (context menu). Uses `MenuOverlay` for rendering.
18pub(crate) struct OwnedContextMenu<'a> {
19    child: Element<'a, Message, GraphixTheme, Renderer>,
20    desc: MenuGroupDesc,
21}
22
23impl<'a> OwnedContextMenu<'a> {
24    pub fn new(
25        child: Element<'a, Message, GraphixTheme, Renderer>,
26        items: Vec<MenuItemDesc>,
27    ) -> Self {
28        Self {
29            child,
30            desc: MenuGroupDesc { label: String::new(), items },
31        }
32    }
33}
34
35impl<'a> Widget<Message, GraphixTheme, Renderer> for OwnedContextMenu<'a> {
36    fn tag(&self) -> widget::tree::Tag {
37        widget::tree::Tag::of::<State>()
38    }
39
40    fn state(&self) -> widget::tree::State {
41        widget::tree::State::new(State::default())
42    }
43
44    fn children(&self) -> Vec<widget::Tree> {
45        vec![widget::Tree::new(&self.child)]
46    }
47
48    fn diff(&self, tree: &mut widget::Tree) {
49        tree.diff_children(std::slice::from_ref(&self.child));
50    }
51
52    fn size(&self) -> Size<Length> {
53        self.child.as_widget().size()
54    }
55
56    fn layout(
57        &mut self,
58        tree: &mut widget::Tree,
59        renderer: &Renderer,
60        limits: &layout::Limits,
61    ) -> layout::Node {
62        self.child.as_widget_mut().layout(
63            &mut tree.children[0],
64            renderer,
65            limits,
66        )
67    }
68
69    fn draw(
70        &self,
71        tree: &widget::Tree,
72        renderer: &mut Renderer,
73        theme: &GraphixTheme,
74        style: &renderer::Style,
75        layout: Layout<'_>,
76        cursor: mouse::Cursor,
77        viewport: &Rectangle,
78    ) {
79        self.child.as_widget().draw(
80            &tree.children[0],
81            renderer,
82            theme,
83            style,
84            layout,
85            cursor,
86            viewport,
87        );
88    }
89
90    fn update(
91        &mut self,
92        tree: &mut widget::Tree,
93        event: &Event,
94        layout: Layout<'_>,
95        cursor: mouse::Cursor,
96        renderer: &Renderer,
97        clipboard: &mut dyn Clipboard,
98        shell: &mut Shell<'_, Message>,
99        viewport: &Rectangle,
100    ) {
101        // Forward events to child first
102        self.child.as_widget_mut().update(
103            &mut tree.children[0],
104            event,
105            layout,
106            cursor,
107            renderer,
108            clipboard,
109            shell,
110            viewport,
111        );
112        let state = tree.state.downcast_mut::<State>();
113        match event {
114            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
115                if cursor.is_over(layout.bounds()) {
116                    if let Some(pos) = cursor.position() {
117                        state.open = true;
118                        state.position = pos;
119                        shell.capture_event();
120                    }
121                }
122            }
123            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
124                if state.open {
125                    // Close on left-click outside the overlay
126                    // (clicks on overlay items are handled by MenuOverlay)
127                    state.open = false;
128                }
129            }
130            Event::Touch(touch::Event::FingerPressed { .. }) => {
131                if state.open {
132                    state.open = false;
133                }
134            }
135            Event::Keyboard(keyboard::Event::KeyPressed {
136                key: keyboard::Key::Named(keyboard::key::Named::Escape),
137                ..
138            }) => {
139                if state.open {
140                    state.open = false;
141                    shell.capture_event();
142                }
143            }
144            _ => {}
145        }
146    }
147
148    fn overlay<'b>(
149        &'b mut self,
150        tree: &'b mut widget::Tree,
151        layout: Layout<'b>,
152        renderer: &Renderer,
153        viewport: &Rectangle,
154        translation: Vector,
155    ) -> Option<overlay::Element<'b, Message, GraphixTheme, Renderer>> {
156        // First check for child overlays
157        let child_overlay = self.child.as_widget_mut().overlay(
158            &mut tree.children[0],
159            layout,
160            renderer,
161            viewport,
162            translation,
163        );
164        if child_overlay.is_some() {
165            return child_overlay;
166        }
167        let state = tree.state.downcast_mut::<State>();
168        if !state.open || self.desc.items.is_empty() {
169            return None;
170        }
171        Some(overlay::Element::new(Box::new(MenuOverlay {
172            menu: &self.desc,
173            position: state.position,
174            open: Some(&mut state.open),
175        })))
176    }
177
178    fn mouse_interaction(
179        &self,
180        tree: &widget::Tree,
181        layout: Layout<'_>,
182        cursor: mouse::Cursor,
183        viewport: &Rectangle,
184        renderer: &Renderer,
185    ) -> mouse::Interaction {
186        self.child.as_widget().mouse_interaction(
187            &tree.children[0],
188            layout,
189            cursor,
190            viewport,
191            renderer,
192        )
193    }
194}
195
196impl<'a> From<OwnedContextMenu<'a>>
197    for Element<'a, Message, GraphixTheme, Renderer>
198{
199    fn from(w: OwnedContextMenu<'a>) -> Self {
200        Self::new(w)
201    }
202}