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