Skip to main content

iced_widget/
mouse_area.rs

1//! A container for capturing mouse events.
2use crate::core::layout;
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::touch;
7use crate::core::widget::{Operation, Tree, tree};
8use crate::core::{Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget};
9
10/// Emit messages on mouse events.
11pub struct MouseArea<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
12    content: Element<'a, Message, Theme, Renderer>,
13    on_press: Option<Message>,
14    on_release: Option<Message>,
15    on_double_click: Option<Message>,
16    on_right_press: Option<Message>,
17    on_right_release: Option<Message>,
18    on_middle_press: Option<Message>,
19    on_middle_release: Option<Message>,
20    on_scroll: Option<Box<dyn Fn(mouse::ScrollDelta) -> Message + 'a>>,
21    on_enter: Option<Message>,
22    on_move: Option<Box<dyn Fn(Point) -> Message + 'a>>,
23    on_exit: Option<Message>,
24    interaction: Option<mouse::Interaction>,
25}
26
27impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
28    /// The message to emit on a left button press.
29    #[must_use]
30    pub fn on_press(mut self, message: Message) -> Self {
31        self.on_press = Some(message);
32        self
33    }
34
35    /// The message to emit on a left button release.
36    #[must_use]
37    pub fn on_release(mut self, message: Message) -> Self {
38        self.on_release = Some(message);
39        self
40    }
41
42    /// The message to emit on a double click.
43    ///
44    /// If you use this with [`on_press`]/[`on_release`], those
45    /// event will be emit as normal.
46    ///
47    /// The events stream will be: on_press -> on_release -> on_press
48    /// -> on_double_click -> on_release -> on_press ...
49    ///
50    /// [`on_press`]: Self::on_press
51    /// [`on_release`]: Self::on_release
52    #[must_use]
53    pub fn on_double_click(mut self, message: Message) -> Self {
54        self.on_double_click = Some(message);
55        self
56    }
57
58    /// The message to emit on a right button press.
59    #[must_use]
60    pub fn on_right_press(mut self, message: Message) -> Self {
61        self.on_right_press = Some(message);
62        self
63    }
64
65    /// The message to emit on a right button release.
66    #[must_use]
67    pub fn on_right_release(mut self, message: Message) -> Self {
68        self.on_right_release = Some(message);
69        self
70    }
71
72    /// The message to emit on a middle button press.
73    #[must_use]
74    pub fn on_middle_press(mut self, message: Message) -> Self {
75        self.on_middle_press = Some(message);
76        self
77    }
78
79    /// The message to emit on a middle button release.
80    #[must_use]
81    pub fn on_middle_release(mut self, message: Message) -> Self {
82        self.on_middle_release = Some(message);
83        self
84    }
85
86    /// The message to emit when scroll wheel is used
87    #[must_use]
88    pub fn on_scroll(mut self, on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a) -> Self {
89        self.on_scroll = Some(Box::new(on_scroll));
90        self
91    }
92
93    /// The message to emit when the mouse enters the area.
94    #[must_use]
95    pub fn on_enter(mut self, message: Message) -> Self {
96        self.on_enter = Some(message);
97        self
98    }
99
100    /// The message to emit when the mouse moves in the area.
101    #[must_use]
102    pub fn on_move(mut self, on_move: impl Fn(Point) -> Message + 'a) -> Self {
103        self.on_move = Some(Box::new(on_move));
104        self
105    }
106
107    /// The message to emit when the mouse exits the area.
108    #[must_use]
109    pub fn on_exit(mut self, message: Message) -> Self {
110        self.on_exit = Some(message);
111        self
112    }
113
114    /// The [`mouse::Interaction`] to use when hovering the area.
115    #[must_use]
116    pub fn interaction(mut self, interaction: mouse::Interaction) -> Self {
117        self.interaction = Some(interaction);
118        self
119    }
120}
121
122/// Local state of the [`MouseArea`].
123#[derive(Default)]
124struct State {
125    is_hovered: bool,
126    bounds: Rectangle,
127    cursor_position: Option<Point>,
128    previous_click: Option<mouse::Click>,
129}
130
131impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
132    /// Creates a [`MouseArea`] with the given content.
133    pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
134        MouseArea {
135            content: content.into(),
136            on_press: None,
137            on_release: None,
138            on_double_click: None,
139            on_right_press: None,
140            on_right_release: None,
141            on_middle_press: None,
142            on_middle_release: None,
143            on_scroll: None,
144            on_enter: None,
145            on_move: None,
146            on_exit: None,
147            interaction: None,
148        }
149    }
150}
151
152impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
153    for MouseArea<'_, Message, Theme, Renderer>
154where
155    Renderer: renderer::Renderer,
156    Message: Clone,
157{
158    fn tag(&self) -> tree::Tag {
159        tree::Tag::of::<State>()
160    }
161
162    fn state(&self) -> tree::State {
163        tree::State::new(State::default())
164    }
165
166    fn diff(&mut self, tree: &mut Tree) {
167        tree.diff_children(std::slice::from_mut(&mut self.content));
168    }
169
170    fn size(&self) -> Size<Length> {
171        self.content.as_widget().size()
172    }
173
174    fn layout(
175        &mut self,
176        tree: &mut Tree,
177        renderer: &Renderer,
178        limits: &layout::Limits,
179    ) -> layout::Node {
180        self.content
181            .as_widget_mut()
182            .layout(&mut tree.children[0], renderer, limits)
183    }
184
185    fn operate(
186        &mut self,
187        tree: &mut Tree,
188        layout: Layout<'_>,
189        renderer: &Renderer,
190        operation: &mut dyn Operation,
191    ) {
192        self.content
193            .as_widget_mut()
194            .operate(&mut tree.children[0], layout, renderer, operation);
195    }
196
197    fn update(
198        &mut self,
199        tree: &mut Tree,
200        event: &Event,
201        layout: Layout<'_>,
202        cursor: mouse::Cursor,
203        renderer: &Renderer,
204        shell: &mut Shell<'_, Message>,
205        viewport: &Rectangle,
206    ) {
207        self.content.as_widget_mut().update(
208            &mut tree.children[0],
209            event,
210            layout,
211            cursor,
212            renderer,
213            shell,
214            viewport,
215        );
216
217        if shell.is_event_captured() {
218            return;
219        }
220
221        update(self, tree, event, layout, cursor, shell);
222    }
223
224    fn mouse_interaction(
225        &self,
226        tree: &Tree,
227        layout: Layout<'_>,
228        cursor: mouse::Cursor,
229        viewport: &Rectangle,
230        renderer: &Renderer,
231    ) -> mouse::Interaction {
232        let content_interaction = self.content.as_widget().mouse_interaction(
233            &tree.children[0],
234            layout,
235            cursor,
236            viewport,
237            renderer,
238        );
239
240        match (self.interaction, content_interaction) {
241            (Some(interaction), mouse::Interaction::None) if cursor.is_over(layout.bounds()) => {
242                interaction
243            }
244            _ => content_interaction,
245        }
246    }
247
248    fn draw(
249        &self,
250        tree: &Tree,
251        renderer: &mut Renderer,
252        theme: &Theme,
253        renderer_style: &renderer::Style,
254        layout: Layout<'_>,
255        cursor: mouse::Cursor,
256        viewport: &Rectangle,
257    ) {
258        self.content.as_widget().draw(
259            &tree.children[0],
260            renderer,
261            theme,
262            renderer_style,
263            layout,
264            cursor,
265            viewport,
266        );
267    }
268
269    fn overlay<'b>(
270        &'b mut self,
271        tree: &'b mut Tree,
272        layout: Layout<'b>,
273        renderer: &Renderer,
274        viewport: &Rectangle,
275        translation: Vector,
276    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
277        self.content.as_widget_mut().overlay(
278            &mut tree.children[0],
279            layout,
280            renderer,
281            viewport,
282            translation,
283        )
284    }
285}
286
287impl<'a, Message, Theme, Renderer> From<MouseArea<'a, Message, Theme, Renderer>>
288    for Element<'a, Message, Theme, Renderer>
289where
290    Message: 'a + Clone,
291    Theme: 'a,
292    Renderer: 'a + renderer::Renderer,
293{
294    fn from(
295        area: MouseArea<'a, Message, Theme, Renderer>,
296    ) -> Element<'a, Message, Theme, Renderer> {
297        Element::new(area)
298    }
299}
300
301/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
302/// accordingly.
303fn update<Message: Clone, Theme, Renderer>(
304    widget: &mut MouseArea<'_, Message, Theme, Renderer>,
305    tree: &mut Tree,
306    event: &Event,
307    layout: Layout<'_>,
308    cursor: mouse::Cursor,
309    shell: &mut Shell<'_, Message>,
310) {
311    let state: &mut State = tree.state.downcast_mut();
312
313    let cursor_position = cursor.position();
314    let bounds = layout.bounds();
315
316    if state.cursor_position != cursor_position || state.bounds != bounds {
317        let was_hovered = state.is_hovered;
318
319        state.is_hovered = cursor.is_over(layout.bounds());
320        state.cursor_position = cursor_position;
321        state.bounds = bounds;
322
323        match (
324            widget.on_enter.as_ref(),
325            widget.on_move.as_ref(),
326            widget.on_exit.as_ref(),
327        ) {
328            (Some(on_enter), _, _) if state.is_hovered && !was_hovered => {
329                shell.publish(on_enter.clone());
330            }
331            (_, Some(on_move), _) if state.is_hovered => {
332                if let Some(position) = cursor.position_in(layout.bounds()) {
333                    shell.publish(on_move(position));
334                }
335            }
336            (_, _, Some(on_exit)) if !state.is_hovered && was_hovered => {
337                shell.publish(on_exit.clone());
338            }
339            _ => {}
340        }
341    }
342
343    if !cursor.is_over(layout.bounds()) {
344        return;
345    }
346
347    match event {
348        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
349        | Event::Touch(touch::Event::FingerPressed { .. }) => {
350            if let Some(message) = widget.on_press.as_ref() {
351                shell.publish(message.clone());
352                shell.capture_event();
353            }
354
355            if let Some(position) = cursor_position
356                && let Some(message) = widget.on_double_click.as_ref()
357            {
358                let new_click =
359                    mouse::Click::new(position, mouse::Button::Left, state.previous_click);
360
361                if new_click.kind() == mouse::click::Kind::Double {
362                    shell.publish(message.clone());
363                }
364
365                state.previous_click = Some(new_click);
366
367                // Even if this is not a double click, but the press is nevertheless
368                // processed by us and should not be popup to parent widgets.
369                shell.capture_event();
370            }
371        }
372        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
373        | Event::Touch(touch::Event::FingerLifted { .. }) => {
374            if let Some(message) = widget.on_release.as_ref() {
375                shell.publish(message.clone());
376            }
377        }
378        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
379            if let Some(message) = widget.on_right_press.as_ref() {
380                shell.publish(message.clone());
381                shell.capture_event();
382            }
383        }
384        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
385            if let Some(message) = widget.on_right_release.as_ref() {
386                shell.publish(message.clone());
387            }
388        }
389        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => {
390            if let Some(message) = widget.on_middle_press.as_ref() {
391                shell.publish(message.clone());
392                shell.capture_event();
393            }
394        }
395        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => {
396            if let Some(message) = widget.on_middle_release.as_ref() {
397                shell.publish(message.clone());
398            }
399        }
400        Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
401            if let Some(on_scroll) = widget.on_scroll.as_ref() {
402                shell.publish(on_scroll(*delta));
403                shell.capture_event();
404            }
405        }
406        _ => {}
407    }
408}