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