iced_widget/
mouse_area.rs

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