//! A container for capturing mouse events. use iced_renderer::core::mouse::Click; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::touch; use crate::core::widget::{Operation, Tree, tree}; use crate::core::{ Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; /// Emit messages on mouse events. pub struct MouseArea< 'a, Message, Theme = crate::Theme, Renderer = crate::Renderer, > { content: Element<'a, Message, Theme, Renderer>, on_drag: Option, on_press: Option, on_double_press: Option, on_release: Option, on_double_click: Option, on_right_press: Option, on_right_release: Option, on_middle_press: Option, on_middle_release: Option, on_scroll: Option Message + 'a>>, on_enter: Option, on_move: Option Message + 'a>>, on_exit: Option, interaction: Option, } impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { /// The message to emit when a drag is initiated. #[must_use] pub fn on_drag(mut self, message: Message) -> Self { self.on_drag = Some(message); self } /// The message to emit on a left button press. #[must_use] pub fn on_press(mut self, message: Message) -> Self { self.on_press = Some(message); self } /// The message to emit on a left double button press. #[must_use] pub fn on_double_press(mut self, message: Message) -> Self { self.on_double_press = Some(message); self } /// The message to emit on a left button release. #[must_use] pub fn on_release(mut self, message: Message) -> Self { self.on_release = Some(message); self } /// The message to emit on a double click. /// /// If you use this with [`on_press`]/[`on_release`], those /// event will be emit as normal. /// /// The events stream will be: on_press -> on_release -> on_press /// -> on_double_click -> on_release -> on_press ... /// /// [`on_press`]: Self::on_press /// [`on_release`]: Self::on_release #[must_use] pub fn on_double_click(mut self, message: Message) -> Self { self.on_double_click = Some(message); self } /// The message to emit on a right button press. #[must_use] pub fn on_right_press(mut self, message: Message) -> Self { self.on_right_press = Some(message); self } /// The message to emit on a right button release. #[must_use] pub fn on_right_release(mut self, message: Message) -> Self { self.on_right_release = Some(message); self } /// The message to emit on a middle button press. #[must_use] pub fn on_middle_press(mut self, message: Message) -> Self { self.on_middle_press = Some(message); self } /// The message to emit on a middle button release. #[must_use] pub fn on_middle_release(mut self, message: Message) -> Self { self.on_middle_release = Some(message); self } /// The message to emit when scroll wheel is used #[must_use] pub fn on_scroll( mut self, on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a, ) -> Self { self.on_scroll = Some(Box::new(on_scroll)); self } /// The message to emit when the mouse enters the area. #[must_use] pub fn on_enter(mut self, message: Message) -> Self { self.on_enter = Some(message); self } /// The message to emit when the mouse moves in the area. #[must_use] pub fn on_move(mut self, on_move: impl Fn(Point) -> Message + 'a) -> Self { self.on_move = Some(Box::new(on_move)); self } /// The message to emit when the mouse exits the area. #[must_use] pub fn on_exit(mut self, message: Message) -> Self { self.on_exit = Some(message); self } /// The [`mouse::Interaction`] to use when hovering the area. #[must_use] pub fn interaction(mut self, interaction: mouse::Interaction) -> Self { self.interaction = Some(interaction); self } } /// Local state of the [`MouseArea`]. struct State { is_hovered: bool, bounds: Rectangle, cursor_position: Option, previous_click: Option, // TODO: Support on_enter and on_exit drag_initiated: Option, is_out_of_bounds: bool, last_press: Option, } impl Default for State { fn default() -> Self { Self { is_hovered: Default::default(), drag_initiated: None, is_out_of_bounds: true, last_press: None, cursor_position: None, bounds: Rectangle::default(), previous_click: None, } } } impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { /// Creates a [`MouseArea`] with the given content. pub fn new( content: impl Into>, ) -> Self { MouseArea { content: content.into(), on_drag: None, on_press: None, on_double_press: None, on_release: None, on_double_click: None, on_right_press: None, on_right_release: None, on_middle_press: None, on_middle_release: None, on_scroll: None, on_enter: None, on_move: None, on_exit: None, interaction: None, } } } impl Widget for MouseArea<'_, Message, Theme, Renderer> where Renderer: renderer::Renderer, Message: Clone, { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(State::default()) } fn children(&self) -> Vec { vec![Tree::new(&self.content)] } fn diff(&mut self, tree: &mut Tree) { tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> Size { self.content.as_widget().size() } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content.as_widget_mut().layout( &mut tree.children[0], renderer, limits, ) } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { self.content.as_widget_mut().operate( &mut tree.children[0], layout, renderer, operation, ); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.content.as_widget_mut().update( &mut tree.children[0], event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() { return; } update(self, tree, event, layout, cursor, shell); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { let content_interaction = self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor, viewport, renderer, ); match (self.interaction, content_interaction) { (Some(interaction), mouse::Interaction::None) if cursor.is_over(layout.bounds()) => { interaction } _ => content_interaction, } } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.content.as_widget().draw( &tree.children[0], renderer, theme, renderer_style, layout, cursor, viewport, ); } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'b>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, viewport, translation, ) } fn drag_destinations( &self, state: &Tree, layout: Layout<'_>, renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { if let Some(state) = state.children.first() { self.content.as_widget().drag_destinations( state, layout, renderer, dnd_rectangles, ); } } #[cfg(feature = "a11y")] fn a11y_nodes( &self, layout: Layout<'_>, state: &Tree, cursor: mouse::Cursor, ) -> iced_accessibility::A11yTree { let c_state = state.children.get(0); let ret = self.content.as_widget().a11y_nodes( layout, c_state.unwrap_or(&Tree::empty()), cursor, ); return ret; } } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a + Clone, Theme: 'a, Renderer: 'a + renderer::Renderer, { fn from( area: MouseArea<'a, Message, Theme, Renderer>, ) -> Element<'a, Message, Theme, Renderer> { Element::new(area) } } /// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`] /// accordingly. fn update( widget: &mut MouseArea<'_, Message, Theme, Renderer>, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, ) { let state: &mut State = tree.state.downcast_mut(); let cursor_position = cursor.position(); if cursor_position.is_none() { state.drag_initiated = None; return; } if let Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) | Event::Mouse(mouse::Event::CursorLeft) | Event::Touch(touch::Event::FingerLost { .. }) = event { let was_hovered = state.is_hovered; let bounds = layout.bounds(); state.is_hovered = cursor.is_over(bounds); state.cursor_position = cursor_position; state.bounds = bounds; match ( widget.on_enter.as_ref(), widget.on_move.as_ref(), widget.on_exit.as_ref(), ) { (Some(on_enter), _, _) if state.is_hovered && !was_hovered => { shell.publish(on_enter.clone()); } (_, Some(on_move), _) if state.is_hovered => { if let Some(position) = cursor.position_in(layout.bounds()) { shell.publish(on_move(position)); } } (_, _, Some(on_exit)) if !state.is_hovered && was_hovered => { shell.publish(on_exit.clone()); } _ => {} } } if !cursor.is_over(layout.bounds()) { if !state.is_out_of_bounds && widget .on_enter .as_ref() .or(widget.on_exit.as_ref()) .is_some() { if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { state.is_out_of_bounds = true; if let Some(message) = widget.on_exit.as_ref() { shell.publish(message.clone()); } return; } } return; } if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = event { if let Some(position) = cursor_position { let new_click = mouse::Click::new( position, mouse::Button::Left, state.last_press, ); if new_click.kind() == mouse::click::Kind::Double && let Some(double_press) = widget.on_double_press.as_ref() { state.drag_initiated = None; shell.publish(double_press.clone()); shell.capture_event(); state.last_press = Some(new_click); return; } else if let Some(on_press_message) = widget.on_press.as_ref() { shell.publish(on_press_message.clone()); } state.last_press = Some(new_click); // Even if this is not a double click, but the press is nevertheless // processed by us and should not be popup to parent widgets. shell.capture_event(); } } if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event { state.drag_initiated = None; if let Some(position) = cursor_position { let new_click = mouse::Click::new( position, mouse::Button::Left, state.previous_click, ); if new_click.kind() == mouse::click::Kind::Double && let Some(double_press) = widget.on_double_click.as_ref() { shell.publish(double_press.clone()); shell.capture_event(); state.previous_click = Some(new_click); return; } else if let Some(on_press_message) = widget.on_release.as_ref() { shell.publish(on_press_message.clone()); } state.previous_click = Some(new_click); // Even if this is not a double click, but the press is nevertheless // processed by us and should not be popup to parent widgets. shell.capture_event(); } } match event { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => { if let Some(message) = widget.on_right_release.as_ref() { shell.publish(message.clone()); } } Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => { if let Some(message) = widget.on_right_press.as_ref() { shell.publish(message.clone()); } } Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => { if let Some(message) = widget.on_middle_press.as_ref() { shell.publish(message.clone()); shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => { if let Some(message) = widget.on_middle_release.as_ref() { shell.publish(message.clone()); } } Event::Mouse(mouse::Event::WheelScrolled { delta }) => { if let Some(on_scroll) = widget.on_scroll.as_ref() { shell.publish(on_scroll(*delta)); shell.capture_event(); } } _ => {} }; if let Some(on_scroll) = widget.on_scroll.as_ref() { if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { shell.publish(on_scroll(*delta)); shell.capture_event(); return; } } if let Some(message) = widget.on_enter.as_ref().or(widget.on_exit.as_ref()) { if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { if state.is_out_of_bounds { state.is_out_of_bounds = false; if widget.on_enter.is_some() { shell.publish(message.clone()); } shell.capture_event(); return; } } } if state.drag_initiated.is_none() && widget.on_drag.is_some() { if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = event { state.drag_initiated = cursor.position(); } } else if let Some((message, drag_source)) = widget.on_drag.as_ref().zip(state.drag_initiated) { if let Some(position) = cursor.position() { if position.distance(drag_source) > 1.0 { state.drag_initiated = None; shell.publish(message.clone()); return; } } } }