1use iced_renderer::core::mouse::Click;
4
5use crate::core::event::{self, Event};
6use crate::core::layout;
7use crate::core::mouse;
8use crate::core::overlay;
9use crate::core::renderer;
10use crate::core::touch;
11use crate::core::widget::{tree, Operation, Tree};
12use crate::core::{
13 Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
14 Widget,
15};
16
17#[allow(missing_debug_implementations)]
19pub struct MouseArea<
20 'a,
21 Message,
22 Theme = crate::Theme,
23 Renderer = crate::Renderer,
24> {
25 content: Element<'a, Message, Theme, Renderer>,
26 on_drag: Option<Message>,
27 on_press: Option<Message>,
28 on_double_press: Option<Message>,
29 on_release: Option<Message>,
30 on_double_click: Option<Message>,
31 on_right_press: Option<Message>,
32 on_right_release: Option<Message>,
33 on_middle_press: Option<Message>,
34 on_middle_release: Option<Message>,
35 on_scroll: Option<Box<dyn Fn(mouse::ScrollDelta) -> Message + 'a>>,
36 on_enter: Option<Message>,
37 on_move: Option<Box<dyn Fn(Point) -> Message + 'a>>,
38 on_exit: Option<Message>,
39 interaction: Option<mouse::Interaction>,
40}
41
42impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
43 #[must_use]
45 pub fn on_drag(mut self, message: Message) -> Self {
46 self.on_drag = Some(message);
47 self
48 }
49
50 #[must_use]
52 pub fn on_press(mut self, message: Message) -> Self {
53 self.on_press = Some(message);
54 self
55 }
56 #[must_use]
58 pub fn on_double_press(mut self, message: Message) -> Self {
59 self.on_double_press = Some(message);
60 self
61 }
62
63 #[must_use]
65 pub fn on_release(mut self, message: Message) -> Self {
66 self.on_release = Some(message);
67 self
68 }
69
70 #[must_use]
81 pub fn on_double_click(mut self, message: Message) -> Self {
82 self.on_double_click = Some(message);
83 self
84 }
85
86 #[must_use]
88 pub fn on_right_press(mut self, message: Message) -> Self {
89 self.on_right_press = Some(message);
90 self
91 }
92
93 #[must_use]
95 pub fn on_right_release(mut self, message: Message) -> Self {
96 self.on_right_release = Some(message);
97 self
98 }
99
100 #[must_use]
102 pub fn on_middle_press(mut self, message: Message) -> Self {
103 self.on_middle_press = Some(message);
104 self
105 }
106
107 #[must_use]
109 pub fn on_middle_release(mut self, message: Message) -> Self {
110 self.on_middle_release = Some(message);
111 self
112 }
113
114 #[must_use]
116 pub fn on_scroll(
117 mut self,
118 on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a,
119 ) -> Self {
120 self.on_scroll = Some(Box::new(on_scroll));
121 self
122 }
123
124 #[must_use]
126 pub fn on_enter(mut self, message: Message) -> Self {
127 self.on_enter = Some(message);
128 self
129 }
130
131 #[must_use]
133 pub fn on_move(mut self, on_move: impl Fn(Point) -> Message + 'a) -> Self {
134 self.on_move = Some(Box::new(on_move));
135 self
136 }
137
138 #[must_use]
140 pub fn on_exit(mut self, message: Message) -> Self {
141 self.on_exit = Some(message);
142 self
143 }
144
145 #[must_use]
147 pub fn interaction(mut self, interaction: mouse::Interaction) -> Self {
148 self.interaction = Some(interaction);
149 self
150 }
151}
152
153struct State {
155 is_hovered: bool,
156 bounds: Rectangle,
157 cursor_position: Option<Point>,
158 previous_click: Option<mouse::Click>,
159 drag_initiated: Option<Point>,
161 is_out_of_bounds: bool,
162 last_click: Option<Click>,
163}
164impl Default for State {
165 fn default() -> Self {
166 Self {
167 is_hovered: Default::default(),
168 drag_initiated: None,
169 is_out_of_bounds: true,
170 last_click: None,
171 cursor_position: None,
172 bounds: Rectangle::default(),
173 previous_click: None,
174 }
175 }
176}
177
178impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
179 pub fn new(
181 content: impl Into<Element<'a, Message, Theme, Renderer>>,
182 ) -> Self {
183 MouseArea {
184 content: content.into(),
185 on_drag: None,
186 on_press: None,
187 on_double_press: None,
188 on_release: None,
189 on_double_click: None,
190 on_right_press: None,
191 on_right_release: None,
192 on_middle_press: None,
193 on_middle_release: None,
194 on_scroll: None,
195 on_enter: None,
196 on_move: None,
197 on_exit: None,
198 interaction: None,
199 }
200 }
201}
202
203impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
204 for MouseArea<'a, Message, Theme, Renderer>
205where
206 Renderer: renderer::Renderer,
207 Message: Clone,
208{
209 fn tag(&self) -> tree::Tag {
210 tree::Tag::of::<State>()
211 }
212
213 fn state(&self) -> tree::State {
214 tree::State::new(State::default())
215 }
216
217 fn children(&self) -> Vec<Tree> {
218 vec![Tree::new(&self.content)]
219 }
220
221 fn diff(&mut self, tree: &mut Tree) {
222 tree.diff_children(std::slice::from_mut(&mut self.content));
223 }
224
225 fn size(&self) -> Size<Length> {
226 self.content.as_widget().size()
227 }
228
229 fn layout(
230 &self,
231 tree: &mut Tree,
232 renderer: &Renderer,
233 limits: &layout::Limits,
234 ) -> layout::Node {
235 self.content
236 .as_widget()
237 .layout(&mut tree.children[0], renderer, limits)
238 }
239
240 fn operate(
241 &self,
242 tree: &mut Tree,
243 layout: Layout<'_>,
244 renderer: &Renderer,
245 operation: &mut dyn Operation,
246 ) {
247 self.content.as_widget().operate(
248 &mut tree.children[0],
249 layout,
250 renderer,
251 operation,
252 );
253 }
254
255 fn on_event(
256 &mut self,
257 tree: &mut Tree,
258 event: Event,
259 layout: Layout<'_>,
260 cursor: mouse::Cursor,
261 renderer: &Renderer,
262 clipboard: &mut dyn Clipboard,
263 shell: &mut Shell<'_, Message>,
264 viewport: &Rectangle,
265 ) -> event::Status {
266 if let event::Status::Captured = self.content.as_widget_mut().on_event(
267 &mut tree.children[0],
268 event.clone(),
269 layout,
270 cursor,
271 renderer,
272 clipboard,
273 shell,
274 viewport,
275 ) {
276 return event::Status::Captured;
277 }
278
279 update(self, tree, event, layout, cursor, shell)
280 }
281
282 fn mouse_interaction(
283 &self,
284 tree: &Tree,
285 layout: Layout<'_>,
286 cursor: mouse::Cursor,
287 viewport: &Rectangle,
288 renderer: &Renderer,
289 ) -> mouse::Interaction {
290 let content_interaction = self.content.as_widget().mouse_interaction(
291 &tree.children[0],
292 layout,
293 cursor,
294 viewport,
295 renderer,
296 );
297
298 match (self.interaction, content_interaction) {
299 (Some(interaction), mouse::Interaction::None)
300 if cursor.is_over(layout.bounds()) =>
301 {
302 interaction
303 }
304 _ => content_interaction,
305 }
306 }
307
308 fn draw(
309 &self,
310 tree: &Tree,
311 renderer: &mut Renderer,
312 theme: &Theme,
313 renderer_style: &renderer::Style,
314 layout: Layout<'_>,
315 cursor: mouse::Cursor,
316 viewport: &Rectangle,
317 ) {
318 self.content.as_widget().draw(
319 &tree.children[0],
320 renderer,
321 theme,
322 renderer_style,
323 layout,
324 cursor,
325 viewport,
326 );
327 }
328 fn overlay<'b>(
329 &'b mut self,
330 tree: &'b mut Tree,
331 layout: Layout<'_>,
332 renderer: &Renderer,
333 translation: Vector,
334 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
335 self.content.as_widget_mut().overlay(
336 &mut tree.children[0],
337 layout,
338 renderer,
339 translation,
340 )
341 }
342 fn drag_destinations(
343 &self,
344 state: &Tree,
345 layout: Layout<'_>,
346 renderer: &Renderer,
347 dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles,
348 ) {
349 if let Some(state) = state.children.first() {
350 self.content.as_widget().drag_destinations(
351 state,
352 layout,
353 renderer,
354 dnd_rectangles,
355 );
356 }
357 }
358
359 #[cfg(feature = "a11y")]
360 fn a11y_nodes(
361 &self,
362 layout: Layout<'_>,
363 state: &Tree,
364 cursor: mouse::Cursor,
365 ) -> iced_accessibility::A11yTree {
366 let c_state = state.children.get(0);
367
368 let ret = self.content.as_widget().a11y_nodes(
369 layout,
370 c_state.unwrap_or(&Tree::empty()),
371 cursor,
372 );
373 return ret;
374 }
375}
376
377impl<'a, Message, Theme, Renderer> From<MouseArea<'a, Message, Theme, Renderer>>
378 for Element<'a, Message, Theme, Renderer>
379where
380 Message: 'a + Clone,
381 Theme: 'a,
382 Renderer: 'a + renderer::Renderer,
383{
384 fn from(
385 area: MouseArea<'a, Message, Theme, Renderer>,
386 ) -> Element<'a, Message, Theme, Renderer> {
387 Element::new(area)
388 }
389}
390
391fn update<Message: Clone, Theme, Renderer>(
394 widget: &mut MouseArea<'_, Message, Theme, Renderer>,
395 tree: &mut Tree,
396 event: Event,
397 layout: Layout<'_>,
398 cursor: mouse::Cursor,
399 shell: &mut Shell<'_, Message>,
400) -> event::Status {
401 let state: &mut State = tree.state.downcast_mut();
402 let cursor_position = cursor.position();
403
404 if let Event::Mouse(mouse::Event::CursorMoved { .. })
405 | Event::Touch(touch::Event::FingerMoved { .. }) = event
406 {
407 let was_hovered = state.is_hovered;
408 let bounds = layout.bounds();
409
410 state.is_hovered = cursor.is_over(bounds);
411 state.cursor_position = cursor_position;
412 state.bounds = bounds;
413
414 match (
415 widget.on_enter.as_ref(),
416 widget.on_move.as_ref(),
417 widget.on_exit.as_ref(),
418 ) {
419 (Some(on_enter), _, _) if state.is_hovered && !was_hovered => {
420 shell.publish(on_enter.clone());
421 }
422 (_, Some(on_move), _) if state.is_hovered => {
423 if let Some(position) = cursor.position_in(layout.bounds()) {
424 shell.publish(on_move(position));
425 }
426 }
427 (_, _, Some(on_exit)) if !state.is_hovered && was_hovered => {
428 shell.publish(on_exit.clone());
429 }
430 _ => {}
431 }
432 }
433
434 if !cursor.is_over(layout.bounds()) {
435 if !state.is_out_of_bounds
436 && widget
437 .on_enter
438 .as_ref()
439 .or(widget.on_exit.as_ref())
440 .is_some()
441 {
442 if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event {
443 state.is_out_of_bounds = true;
444 if let Some(message) = widget.on_exit.as_ref() {
445 shell.publish(message.clone());
446 }
447 return event::Status::Captured;
448 }
449 }
450
451 return event::Status::Ignored;
452 }
453
454 if let Some(message) = widget.on_double_press.as_ref() {
455 if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) =
456 event
457 {
458 if let Some(cursor_position) = cursor.position() {
459 let click = mouse::Click::new(
460 cursor_position,
461 mouse::Button::Left,
462 state.last_click,
463 );
464 state.last_click = Some(click);
465 if let mouse::click::Kind::Double = click.kind() {
466 shell.publish(message.clone());
467 state.drag_initiated = None;
468 return event::Status::Captured;
469 }
470 }
471 }
472 }
473
474 if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
475 | Event::Touch(touch::Event::FingerPressed { .. }) = event
476 {
477 let mut captured = false;
478
479 if let Some(message) = widget.on_press.as_ref() {
480 captured = true;
481 shell.publish(message.clone());
482 }
483
484 if let Some(position) = cursor_position {
485 if let Some(message) = widget.on_double_click.as_ref() {
486 let new_click = mouse::Click::new(
487 position,
488 mouse::Button::Left,
489 state.previous_click,
490 );
491
492 if matches!(new_click.kind(), mouse::click::Kind::Double) {
493 shell.publish(message.clone());
494 }
495
496 state.previous_click = Some(new_click);
497
498 captured = true;
501 }
502 }
503
504 if captured {
505 return event::Status::Captured;
506 }
507 }
508
509 if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
510 | Event::Touch(touch::Event::FingerLifted { .. }) = event
511 {
512 state.drag_initiated = None;
513 if let Some(message) = widget.on_release.as_ref() {
514 shell.publish(message.clone());
515
516 return event::Status::Captured;
517 }
518 }
519
520 if let Some(message) = widget.on_right_press.as_ref() {
521 if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
522 event
523 {
524 shell.publish(message.clone());
525
526 return event::Status::Captured;
527 }
528 }
529
530 if let Some(message) = widget.on_right_release.as_ref() {
531 if let Event::Mouse(mouse::Event::ButtonReleased(
532 mouse::Button::Right,
533 )) = event
534 {
535 shell.publish(message.clone());
536
537 return event::Status::Captured;
538 }
539 }
540
541 if let Some(message) = widget.on_middle_press.as_ref() {
542 if let Event::Mouse(mouse::Event::ButtonPressed(
543 mouse::Button::Middle,
544 )) = event
545 {
546 shell.publish(message.clone());
547
548 return event::Status::Captured;
549 }
550 }
551
552 if let Some(message) = widget.on_middle_release.as_ref() {
553 if let Event::Mouse(mouse::Event::ButtonReleased(
554 mouse::Button::Middle,
555 )) = event
556 {
557 shell.publish(message.clone());
558
559 return event::Status::Captured;
560 }
561 }
562
563 if let Some(on_scroll) = widget.on_scroll.as_ref() {
564 if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
565 shell.publish(on_scroll(delta));
566
567 return event::Status::Captured;
568 }
569 }
570
571 if let Some(message) = widget.on_enter.as_ref().or(widget.on_exit.as_ref())
572 {
573 if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event {
574 if state.is_out_of_bounds {
575 state.is_out_of_bounds = false;
576 if widget.on_enter.is_some() {
577 shell.publish(message.clone());
578 }
579 return event::Status::Captured;
580 }
581 }
582 }
583
584 if state.drag_initiated.is_none() && widget.on_drag.is_some() {
585 if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
586 | Event::Touch(touch::Event::FingerPressed { .. }) = event
587 {
588 state.drag_initiated = cursor.position();
589 }
590 } else if let Some((message, drag_source)) =
591 widget.on_drag.as_ref().zip(state.drag_initiated)
592 {
593 if let Some(position) = cursor.position() {
594 if position.distance(drag_source) > 1.0 {
595 state.drag_initiated = None;
596 shell.publish(message.clone());
597
598 return event::Status::Captured;
599 }
600 }
601 }
602
603 event::Status::Ignored
604}