iced_widget/
mouse_area.rs

1//! A container for capturing mouse events.
2
3use iced_renderer::core::mouse::Click;
4
5use crate::core::event::{self, Event};
6use crate::core::layout;
7use crate::core::mouse;
8use crate::core::overlay;
9use crate::core::renderer;
10use crate::core::touch;
11use crate::core::widget::{tree, Operation, Tree};
12use crate::core::{
13    Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
14    Widget,
15};
16
17/// Emit messages on mouse events.
18#[allow(missing_debug_implementations)]
19pub struct MouseArea<
20    'a,
21    Message,
22    Theme = crate::Theme,
23    Renderer = crate::Renderer,
24> {
25    content: Element<'a, Message, Theme, Renderer>,
26    on_drag: Option<Message>,
27    on_press: Option<Message>,
28    on_double_press: Option<Message>,
29    on_release: Option<Message>,
30    on_double_click: Option<Message>,
31    on_right_press: Option<Message>,
32    on_right_release: Option<Message>,
33    on_middle_press: Option<Message>,
34    on_middle_release: Option<Message>,
35    on_scroll: Option<Box<dyn Fn(mouse::ScrollDelta) -> Message + 'a>>,
36    on_enter: Option<Message>,
37    on_move: Option<Box<dyn Fn(Point) -> Message + 'a>>,
38    on_exit: Option<Message>,
39    interaction: Option<mouse::Interaction>,
40}
41
42impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
43    /// The message to emit when a drag is initiated.
44    #[must_use]
45    pub fn on_drag(mut self, message: Message) -> Self {
46        self.on_drag = Some(message);
47        self
48    }
49
50    /// The message to emit on a left button press.
51    #[must_use]
52    pub fn on_press(mut self, message: Message) -> Self {
53        self.on_press = Some(message);
54        self
55    }
56    /// The message to emit on a left double button press.
57    #[must_use]
58    pub fn on_double_press(mut self, message: Message) -> Self {
59        self.on_double_press = Some(message);
60        self
61    }
62
63    /// The message to emit on a left button release.
64    #[must_use]
65    pub fn on_release(mut self, message: Message) -> Self {
66        self.on_release = Some(message);
67        self
68    }
69
70    /// The message to emit on a double click.
71    ///
72    /// If you use this with [`on_press`]/[`on_release`], those
73    /// event will be emit as normal.
74    ///
75    /// The events stream will be: on_press -> on_release -> on_press
76    /// -> on_double_click -> on_release -> on_press ...
77    ///
78    /// [`on_press`]: Self::on_press
79    /// [`on_release`]: Self::on_release
80    #[must_use]
81    pub fn on_double_click(mut self, message: Message) -> Self {
82        self.on_double_click = Some(message);
83        self
84    }
85
86    /// The message to emit on a right button press.
87    #[must_use]
88    pub fn on_right_press(mut self, message: Message) -> Self {
89        self.on_right_press = Some(message);
90        self
91    }
92
93    /// The message to emit on a right button release.
94    #[must_use]
95    pub fn on_right_release(mut self, message: Message) -> Self {
96        self.on_right_release = Some(message);
97        self
98    }
99
100    /// The message to emit on a middle button press.
101    #[must_use]
102    pub fn on_middle_press(mut self, message: Message) -> Self {
103        self.on_middle_press = Some(message);
104        self
105    }
106
107    /// The message to emit on a middle button release.
108    #[must_use]
109    pub fn on_middle_release(mut self, message: Message) -> Self {
110        self.on_middle_release = Some(message);
111        self
112    }
113
114    /// The message to emit when scroll wheel is used
115    #[must_use]
116    pub fn on_scroll(
117        mut self,
118        on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a,
119    ) -> Self {
120        self.on_scroll = Some(Box::new(on_scroll));
121        self
122    }
123
124    /// The message to emit when the mouse enters the area.
125    #[must_use]
126    pub fn on_enter(mut self, message: Message) -> Self {
127        self.on_enter = Some(message);
128        self
129    }
130
131    /// The message to emit when the mouse moves in the area.
132    #[must_use]
133    pub fn on_move(mut self, on_move: impl Fn(Point) -> Message + 'a) -> Self {
134        self.on_move = Some(Box::new(on_move));
135        self
136    }
137
138    /// The message to emit when the mouse exits the area.
139    #[must_use]
140    pub fn on_exit(mut self, message: Message) -> Self {
141        self.on_exit = Some(message);
142        self
143    }
144
145    /// The [`mouse::Interaction`] to use when hovering the area.
146    #[must_use]
147    pub fn interaction(mut self, interaction: mouse::Interaction) -> Self {
148        self.interaction = Some(interaction);
149        self
150    }
151}
152
153/// Local state of the [`MouseArea`].
154struct State {
155    is_hovered: bool,
156    bounds: Rectangle,
157    cursor_position: Option<Point>,
158    previous_click: Option<mouse::Click>,
159    // TODO: Support on_enter and on_exit
160    drag_initiated: Option<Point>,
161    is_out_of_bounds: bool,
162    last_click: Option<Click>,
163}
164impl Default for State {
165    fn default() -> Self {
166        Self {
167            is_hovered: Default::default(),
168            drag_initiated: None,
169            is_out_of_bounds: true,
170            last_click: None,
171            cursor_position: None,
172            bounds: Rectangle::default(),
173            previous_click: None,
174        }
175    }
176}
177
178impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
179    /// Creates a [`MouseArea`] with the given content.
180    pub fn new(
181        content: impl Into<Element<'a, Message, Theme, Renderer>>,
182    ) -> Self {
183        MouseArea {
184            content: content.into(),
185            on_drag: None,
186            on_press: None,
187            on_double_press: None,
188            on_release: None,
189            on_double_click: None,
190            on_right_press: None,
191            on_right_release: None,
192            on_middle_press: None,
193            on_middle_release: None,
194            on_scroll: None,
195            on_enter: None,
196            on_move: None,
197            on_exit: None,
198            interaction: None,
199        }
200    }
201}
202
203impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
204    for MouseArea<'a, Message, Theme, Renderer>
205where
206    Renderer: renderer::Renderer,
207    Message: Clone,
208{
209    fn tag(&self) -> tree::Tag {
210        tree::Tag::of::<State>()
211    }
212
213    fn state(&self) -> tree::State {
214        tree::State::new(State::default())
215    }
216
217    fn children(&self) -> Vec<Tree> {
218        vec![Tree::new(&self.content)]
219    }
220
221    fn diff(&mut self, tree: &mut Tree) {
222        tree.diff_children(std::slice::from_mut(&mut self.content));
223    }
224
225    fn size(&self) -> Size<Length> {
226        self.content.as_widget().size()
227    }
228
229    fn layout(
230        &self,
231        tree: &mut Tree,
232        renderer: &Renderer,
233        limits: &layout::Limits,
234    ) -> layout::Node {
235        self.content
236            .as_widget()
237            .layout(&mut tree.children[0], renderer, limits)
238    }
239
240    fn operate(
241        &self,
242        tree: &mut Tree,
243        layout: Layout<'_>,
244        renderer: &Renderer,
245        operation: &mut dyn Operation,
246    ) {
247        self.content.as_widget().operate(
248            &mut tree.children[0],
249            layout,
250            renderer,
251            operation,
252        );
253    }
254
255    fn on_event(
256        &mut self,
257        tree: &mut Tree,
258        event: Event,
259        layout: Layout<'_>,
260        cursor: mouse::Cursor,
261        renderer: &Renderer,
262        clipboard: &mut dyn Clipboard,
263        shell: &mut Shell<'_, Message>,
264        viewport: &Rectangle,
265    ) -> event::Status {
266        if let event::Status::Captured = self.content.as_widget_mut().on_event(
267            &mut tree.children[0],
268            event.clone(),
269            layout,
270            cursor,
271            renderer,
272            clipboard,
273            shell,
274            viewport,
275        ) {
276            return event::Status::Captured;
277        }
278
279        update(self, tree, event, layout, cursor, shell)
280    }
281
282    fn mouse_interaction(
283        &self,
284        tree: &Tree,
285        layout: Layout<'_>,
286        cursor: mouse::Cursor,
287        viewport: &Rectangle,
288        renderer: &Renderer,
289    ) -> mouse::Interaction {
290        let content_interaction = self.content.as_widget().mouse_interaction(
291            &tree.children[0],
292            layout,
293            cursor,
294            viewport,
295            renderer,
296        );
297
298        match (self.interaction, content_interaction) {
299            (Some(interaction), mouse::Interaction::None)
300                if cursor.is_over(layout.bounds()) =>
301            {
302                interaction
303            }
304            _ => content_interaction,
305        }
306    }
307
308    fn draw(
309        &self,
310        tree: &Tree,
311        renderer: &mut Renderer,
312        theme: &Theme,
313        renderer_style: &renderer::Style,
314        layout: Layout<'_>,
315        cursor: mouse::Cursor,
316        viewport: &Rectangle,
317    ) {
318        self.content.as_widget().draw(
319            &tree.children[0],
320            renderer,
321            theme,
322            renderer_style,
323            layout,
324            cursor,
325            viewport,
326        );
327    }
328    fn overlay<'b>(
329        &'b mut self,
330        tree: &'b mut Tree,
331        layout: Layout<'_>,
332        renderer: &Renderer,
333        translation: Vector,
334    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
335        self.content.as_widget_mut().overlay(
336            &mut tree.children[0],
337            layout,
338            renderer,
339            translation,
340        )
341    }
342    fn drag_destinations(
343        &self,
344        state: &Tree,
345        layout: Layout<'_>,
346        renderer: &Renderer,
347        dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles,
348    ) {
349        if let Some(state) = state.children.first() {
350            self.content.as_widget().drag_destinations(
351                state,
352                layout,
353                renderer,
354                dnd_rectangles,
355            );
356        }
357    }
358
359    #[cfg(feature = "a11y")]
360    fn a11y_nodes(
361        &self,
362        layout: Layout<'_>,
363        state: &Tree,
364        cursor: mouse::Cursor,
365    ) -> iced_accessibility::A11yTree {
366        let c_state = state.children.get(0);
367
368        let ret = self.content.as_widget().a11y_nodes(
369            layout,
370            c_state.unwrap_or(&Tree::empty()),
371            cursor,
372        );
373        return ret;
374    }
375}
376
377impl<'a, Message, Theme, Renderer> From<MouseArea<'a, Message, Theme, Renderer>>
378    for Element<'a, Message, Theme, Renderer>
379where
380    Message: 'a + Clone,
381    Theme: 'a,
382    Renderer: 'a + renderer::Renderer,
383{
384    fn from(
385        area: MouseArea<'a, Message, Theme, Renderer>,
386    ) -> Element<'a, Message, Theme, Renderer> {
387        Element::new(area)
388    }
389}
390
391/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
392/// accordingly.
393fn update<Message: Clone, Theme, Renderer>(
394    widget: &mut MouseArea<'_, Message, Theme, Renderer>,
395    tree: &mut Tree,
396    event: Event,
397    layout: Layout<'_>,
398    cursor: mouse::Cursor,
399    shell: &mut Shell<'_, Message>,
400) -> event::Status {
401    let state: &mut State = tree.state.downcast_mut();
402    let cursor_position = cursor.position();
403
404    if let Event::Mouse(mouse::Event::CursorMoved { .. })
405    | Event::Touch(touch::Event::FingerMoved { .. }) = event
406    {
407        let was_hovered = state.is_hovered;
408        let bounds = layout.bounds();
409
410        state.is_hovered = cursor.is_over(bounds);
411        state.cursor_position = cursor_position;
412        state.bounds = bounds;
413
414        match (
415            widget.on_enter.as_ref(),
416            widget.on_move.as_ref(),
417            widget.on_exit.as_ref(),
418        ) {
419            (Some(on_enter), _, _) if state.is_hovered && !was_hovered => {
420                shell.publish(on_enter.clone());
421            }
422            (_, Some(on_move), _) if state.is_hovered => {
423                if let Some(position) = cursor.position_in(layout.bounds()) {
424                    shell.publish(on_move(position));
425                }
426            }
427            (_, _, Some(on_exit)) if !state.is_hovered && was_hovered => {
428                shell.publish(on_exit.clone());
429            }
430            _ => {}
431        }
432    }
433
434    if !cursor.is_over(layout.bounds()) {
435        if !state.is_out_of_bounds
436            && widget
437                .on_enter
438                .as_ref()
439                .or(widget.on_exit.as_ref())
440                .is_some()
441        {
442            if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event {
443                state.is_out_of_bounds = true;
444                if let Some(message) = widget.on_exit.as_ref() {
445                    shell.publish(message.clone());
446                }
447                return event::Status::Captured;
448            }
449        }
450
451        return event::Status::Ignored;
452    }
453
454    if let Some(message) = widget.on_double_press.as_ref() {
455        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) =
456            event
457        {
458            if let Some(cursor_position) = cursor.position() {
459                let click = mouse::Click::new(
460                    cursor_position,
461                    mouse::Button::Left,
462                    state.last_click,
463                );
464                state.last_click = Some(click);
465                if let mouse::click::Kind::Double = click.kind() {
466                    shell.publish(message.clone());
467                    state.drag_initiated = None;
468                    return event::Status::Captured;
469                }
470            }
471        }
472    }
473
474    if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
475    | Event::Touch(touch::Event::FingerPressed { .. }) = event
476    {
477        let mut captured = false;
478
479        if let Some(message) = widget.on_press.as_ref() {
480            captured = true;
481            shell.publish(message.clone());
482        }
483
484        if let Some(position) = cursor_position {
485            if let Some(message) = widget.on_double_click.as_ref() {
486                let new_click = mouse::Click::new(
487                    position,
488                    mouse::Button::Left,
489                    state.previous_click,
490                );
491
492                if matches!(new_click.kind(), mouse::click::Kind::Double) {
493                    shell.publish(message.clone());
494                }
495
496                state.previous_click = Some(new_click);
497
498                // Even if this is not a double click, but the press is nevertheless
499                // processed by us and should not be popup to parent widgets.
500                captured = true;
501            }
502        }
503
504        if captured {
505            return event::Status::Captured;
506        }
507    }
508
509    if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
510    | Event::Touch(touch::Event::FingerLifted { .. }) = event
511    {
512        state.drag_initiated = None;
513        if let Some(message) = widget.on_release.as_ref() {
514            shell.publish(message.clone());
515
516            return event::Status::Captured;
517        }
518    }
519
520    if let Some(message) = widget.on_right_press.as_ref() {
521        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
522            event
523        {
524            shell.publish(message.clone());
525
526            return event::Status::Captured;
527        }
528    }
529
530    if let Some(message) = widget.on_right_release.as_ref() {
531        if let Event::Mouse(mouse::Event::ButtonReleased(
532            mouse::Button::Right,
533        )) = event
534        {
535            shell.publish(message.clone());
536
537            return event::Status::Captured;
538        }
539    }
540
541    if let Some(message) = widget.on_middle_press.as_ref() {
542        if let Event::Mouse(mouse::Event::ButtonPressed(
543            mouse::Button::Middle,
544        )) = event
545        {
546            shell.publish(message.clone());
547
548            return event::Status::Captured;
549        }
550    }
551
552    if let Some(message) = widget.on_middle_release.as_ref() {
553        if let Event::Mouse(mouse::Event::ButtonReleased(
554            mouse::Button::Middle,
555        )) = event
556        {
557            shell.publish(message.clone());
558
559            return event::Status::Captured;
560        }
561    }
562
563    if let Some(on_scroll) = widget.on_scroll.as_ref() {
564        if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
565            shell.publish(on_scroll(delta));
566
567            return event::Status::Captured;
568        }
569    }
570
571    if let Some(message) = widget.on_enter.as_ref().or(widget.on_exit.as_ref())
572    {
573        if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event {
574            if state.is_out_of_bounds {
575                state.is_out_of_bounds = false;
576                if widget.on_enter.is_some() {
577                    shell.publish(message.clone());
578                }
579                return event::Status::Captured;
580            }
581        }
582    }
583
584    if state.drag_initiated.is_none() && widget.on_drag.is_some() {
585        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
586        | Event::Touch(touch::Event::FingerPressed { .. }) = event
587        {
588            state.drag_initiated = cursor.position();
589        }
590    } else if let Some((message, drag_source)) =
591        widget.on_drag.as_ref().zip(state.drag_initiated)
592    {
593        if let Some(position) = cursor.position() {
594            if position.distance(drag_source) > 1.0 {
595                state.drag_initiated = None;
596                shell.publish(message.clone());
597
598                return event::Status::Captured;
599            }
600        }
601    }
602
603    event::Status::Ignored
604}