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 children(&self) -> Vec<Tree> {
167        vec![Tree::new(&self.content)]
168    }
169
170    fn diff(&self, tree: &mut Tree) {
171        tree.diff_children(std::slice::from_ref(&self.content));
172    }
173
174    fn size(&self) -> Size<Length> {
175        self.content.as_widget().size()
176    }
177
178    fn layout(
179        &mut self,
180        tree: &mut Tree,
181        renderer: &Renderer,
182        limits: &layout::Limits,
183    ) -> layout::Node {
184        self.content
185            .as_widget_mut()
186            .layout(&mut tree.children[0], renderer, limits)
187    }
188
189    fn operate(
190        &mut self,
191        tree: &mut Tree,
192        layout: Layout<'_>,
193        renderer: &Renderer,
194        operation: &mut dyn Operation,
195    ) {
196        self.content
197            .as_widget_mut()
198            .operate(&mut tree.children[0], layout, renderer, operation);
199    }
200
201    fn update(
202        &mut self,
203        tree: &mut Tree,
204        event: &Event,
205        layout: Layout<'_>,
206        cursor: mouse::Cursor,
207        renderer: &Renderer,
208        shell: &mut Shell<'_, Message>,
209        viewport: &Rectangle,
210    ) {
211        self.content.as_widget_mut().update(
212            &mut tree.children[0],
213            event,
214            layout,
215            cursor,
216            renderer,
217            shell,
218            viewport,
219        );
220
221        if shell.is_event_captured() {
222            return;
223        }
224
225        update(self, tree, event, layout, cursor, shell);
226    }
227
228    fn mouse_interaction(
229        &self,
230        tree: &Tree,
231        layout: Layout<'_>,
232        cursor: mouse::Cursor,
233        viewport: &Rectangle,
234        renderer: &Renderer,
235    ) -> mouse::Interaction {
236        let content_interaction = self.content.as_widget().mouse_interaction(
237            &tree.children[0],
238            layout,
239            cursor,
240            viewport,
241            renderer,
242        );
243
244        match (self.interaction, content_interaction) {
245            (Some(interaction), mouse::Interaction::None) if cursor.is_over(layout.bounds()) => {
246                interaction
247            }
248            _ => content_interaction,
249        }
250    }
251
252    fn draw(
253        &self,
254        tree: &Tree,
255        renderer: &mut Renderer,
256        theme: &Theme,
257        renderer_style: &renderer::Style,
258        layout: Layout<'_>,
259        cursor: mouse::Cursor,
260        viewport: &Rectangle,
261    ) {
262        self.content.as_widget().draw(
263            &tree.children[0],
264            renderer,
265            theme,
266            renderer_style,
267            layout,
268            cursor,
269            viewport,
270        );
271    }
272
273    fn overlay<'b>(
274        &'b mut self,
275        tree: &'b mut Tree,
276        layout: Layout<'b>,
277        renderer: &Renderer,
278        viewport: &Rectangle,
279        translation: Vector,
280    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
281        self.content.as_widget_mut().overlay(
282            &mut tree.children[0],
283            layout,
284            renderer,
285            viewport,
286            translation,
287        )
288    }
289}
290
291impl<'a, Message, Theme, Renderer> From<MouseArea<'a, Message, Theme, Renderer>>
292    for Element<'a, Message, Theme, Renderer>
293where
294    Message: 'a + Clone,
295    Theme: 'a,
296    Renderer: 'a + renderer::Renderer,
297{
298    fn from(
299        area: MouseArea<'a, Message, Theme, Renderer>,
300    ) -> Element<'a, Message, Theme, Renderer> {
301        Element::new(area)
302    }
303}
304
305/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
306/// accordingly.
307fn update<Message: Clone, Theme, Renderer>(
308    widget: &mut MouseArea<'_, Message, Theme, Renderer>,
309    tree: &mut Tree,
310    event: &Event,
311    layout: Layout<'_>,
312    cursor: mouse::Cursor,
313    shell: &mut Shell<'_, Message>,
314) {
315    let state: &mut State = tree.state.downcast_mut();
316
317    let cursor_position = cursor.position();
318    let bounds = layout.bounds();
319
320    if state.cursor_position != cursor_position || state.bounds != bounds {
321        let was_hovered = state.is_hovered;
322
323        state.is_hovered = cursor.is_over(layout.bounds());
324        state.cursor_position = cursor_position;
325        state.bounds = bounds;
326
327        match (
328            widget.on_enter.as_ref(),
329            widget.on_move.as_ref(),
330            widget.on_exit.as_ref(),
331        ) {
332            (Some(on_enter), _, _) if state.is_hovered && !was_hovered => {
333                shell.publish(on_enter.clone());
334            }
335            (_, Some(on_move), _) if state.is_hovered => {
336                if let Some(position) = cursor.position_in(layout.bounds()) {
337                    shell.publish(on_move(position));
338                }
339            }
340            (_, _, Some(on_exit)) if !state.is_hovered && was_hovered => {
341                shell.publish(on_exit.clone());
342            }
343            _ => {}
344        }
345    }
346
347    if !cursor.is_over(layout.bounds()) {
348        return;
349    }
350
351    match event {
352        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
353        | Event::Touch(touch::Event::FingerPressed { .. }) => {
354            if let Some(message) = widget.on_press.as_ref() {
355                shell.publish(message.clone());
356                shell.capture_event();
357            }
358
359            if let Some(position) = cursor_position
360                && let Some(message) = widget.on_double_click.as_ref()
361            {
362                let new_click =
363                    mouse::Click::new(position, mouse::Button::Left, state.previous_click);
364
365                if new_click.kind() == mouse::click::Kind::Double {
366                    shell.publish(message.clone());
367                }
368
369                state.previous_click = Some(new_click);
370
371                // Even if this is not a double click, but the press is nevertheless
372                // processed by us and should not be popup to parent widgets.
373                shell.capture_event();
374            }
375        }
376        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
377        | Event::Touch(touch::Event::FingerLifted { .. }) => {
378            if let Some(message) = widget.on_release.as_ref() {
379                shell.publish(message.clone());
380            }
381        }
382        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
383            if let Some(message) = widget.on_right_press.as_ref() {
384                shell.publish(message.clone());
385                shell.capture_event();
386            }
387        }
388        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
389            if let Some(message) = widget.on_right_release.as_ref() {
390                shell.publish(message.clone());
391            }
392        }
393        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => {
394            if let Some(message) = widget.on_middle_press.as_ref() {
395                shell.publish(message.clone());
396                shell.capture_event();
397            }
398        }
399        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => {
400            if let Some(message) = widget.on_middle_release.as_ref() {
401                shell.publish(message.clone());
402            }
403        }
404        Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
405            if let Some(on_scroll) = widget.on_scroll.as_ref() {
406                shell.publish(on_scroll(*delta));
407                shell.capture_event();
408            }
409        }
410        _ => {}
411    }
412}