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