diff options
author | 2022-12-12 01:53:45 +0100 | |
---|---|---|
committer | 2023-04-13 06:19:42 +0200 | |
commit | dfc1868179d96236ddf2a9eb590832d810afb6c3 (patch) | |
tree | 3adc4fd2eb1e6bd183ed1df1d32b32a970d09a46 | |
parent | cf35c85f8cb16fb3c02f1eeb27b9a7736d35ef59 (diff) | |
download | iced-dfc1868179d96236ddf2a9eb590832d810afb6c3.tar.gz iced-dfc1868179d96236ddf2a9eb590832d810afb6c3.tar.bz2 iced-dfc1868179d96236ddf2a9eb590832d810afb6c3.zip |
feat(native): Add MouseListener widget
Diffstat (limited to '')
-rw-r--r-- | native/src/widget.rs | 3 | ||||
-rw-r--r-- | native/src/widget/helpers.rs | 10 | ||||
-rw-r--r-- | native/src/widget/mouse_listener.rs | 403 | ||||
-rw-r--r-- | src/widget.rs | 8 |
4 files changed, 424 insertions, 0 deletions
diff --git a/native/src/widget.rs b/native/src/widget.rs index 2b3ca7be..4eb3d1ba 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -17,6 +17,7 @@ pub mod column; pub mod container; pub mod helpers; pub mod image; +pub mod mouse_listener; pub mod operation; pub mod pane_grid; pub mod pick_list; @@ -51,6 +52,8 @@ pub use helpers::*; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] +pub use mouse_listener::MouseListener; +#[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use pick_list::PickList; diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index b25e064d..88986d60 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -314,3 +314,13 @@ where { widget::Svg::new(handle) } + +/// A container intercepting mouse events. +pub fn mouse_listener<'a, Message, Renderer>( + widget: impl Into<Element<'a, Message, Renderer>>, +) -> widget::MouseListener<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + widget::MouseListener::new(widget) +} diff --git a/native/src/widget/mouse_listener.rs b/native/src/widget/mouse_listener.rs new file mode 100644 index 00000000..0add7c71 --- /dev/null +++ b/native/src/widget/mouse_listener.rs @@ -0,0 +1,403 @@ +//! A container for capturing mouse events. + +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::renderer; +use crate::touch; +use crate::widget::{tree, Operation, Tree}; +use crate::{ + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget, +}; + +use std::u32; + +/// Emit messages on mouse events. +#[allow(missing_debug_implementations)] +pub struct MouseListener<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + + /// Sets the message to emit on a left mouse button press. + on_press: Option<Message>, + + /// Sets the message to emit on a left mouse button release. + on_release: Option<Message>, + + /// Sets the message to emit on a right mouse button press. + on_right_press: Option<Message>, + + /// Sets the message to emit on a right mouse button release. + on_right_release: Option<Message>, + + /// Sets the message to emit on a middle mouse button press. + on_middle_press: Option<Message>, + + /// Sets the message to emit on a middle mouse button release. + on_middle_release: Option<Message>, + + /// Sets the message to emit when the mouse enters the widget. + on_mouse_enter: Option<Message>, + + /// Sets the messsage to emit when the mouse exits the widget. + on_mouse_exit: Option<Message>, +} + +impl<'a, Message, Renderer> MouseListener<'a, Message, Renderer> { + /// 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 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 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 the mouse enters the widget. + #[must_use] + pub fn on_mouse_enter(mut self, message: Message) -> Self { + self.on_mouse_enter = Some(message); + self + } + + /// The messsage to emit when the mouse exits the widget. + #[must_use] + pub fn on_mouse_exit(mut self, message: Message) -> Self { + self.on_mouse_exit = Some(message); + self + } +} + +/// Local state of the [`MouseListener`]. +#[derive(Default)] +struct State { + hovered: bool, +} + +impl<'a, Message, Renderer> MouseListener<'a, Message, Renderer> { + /// Creates an empty [`MouseListener`]. + pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { + MouseListener { + content: content.into(), + on_press: None, + on_release: None, + on_right_press: None, + on_right_release: None, + on_middle_press: None, + on_middle_release: None, + on_mouse_enter: None, + on_mouse_exit: None, + } + } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> + for MouseListener<'a, Message, Renderer> +where + Renderer: crate::Renderer, + Message: Clone, +{ + fn children(&self) -> Vec<Tree> { + vec![Tree::new(&self.content)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)); + } + + fn width(&self) -> Length { + self.content.as_widget().width() + } + + fn height(&self) -> Length { + self.content.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout( + renderer, + limits, + Widget::<Message, Renderer>::width(self), + Widget::<Message, Renderer>::height(self), + u32::MAX, + u32::MAX, + |renderer, limits| { + self.content.as_widget().layout(renderer, limits) + }, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation<Message>, + ) { + operation.container(None, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + if let event::Status::Captured = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) { + return event::Status::Captured; + } + + update( + self, + &event, + layout, + cursor_position, + shell, + tree.state.downcast_mut::<State>(), + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + layout.children().next().unwrap(), + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option<overlay::Element<'b, Message, Renderer>> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::<State>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } +} + +impl<'a, Message, Renderer> From<MouseListener<'a, Message, Renderer>> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + crate::Renderer, +{ + fn from( + listener: MouseListener<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(listener) + } +} + +/// Processes the given [`Event`] and updates the [`State`] of an [`MouseListener`] +/// accordingly. +fn update<Message: Clone, Renderer>( + widget: &mut MouseListener<'_, Message, Renderer>, + event: &Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + state: &mut State, +) -> event::Status { + let hovered = state.hovered; + + if !layout.bounds().contains(cursor_position) { + if hovered { + state.hovered = false; + if let Some(message) = widget.on_mouse_exit.clone() { + shell.publish(message); + return event::Status::Captured; + } + } + + return event::Status::Ignored; + } + + state.hovered = true; + + if !hovered { + if let Some(message) = widget.on_mouse_enter.clone() { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = + event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_right_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Right, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_press.clone() { + if let Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Middle, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + if let Some(message) = widget.on_middle_release.clone() { + if let Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Middle, + )) = event + { + shell.publish(message); + return event::Status::Captured; + } + } + + event::Status::Ignored +} + +/// Computes the layout of a [`MouseListener`]. +pub fn layout<Renderer>( + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + max_height: u32, + max_width: u32, + layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { + let limits = limits + .loose() + .max_height(max_height) + .max_width(max_width) + .width(width) + .height(height); + + let content = layout_content(renderer, &limits); + let size = limits.resolve(content.size()); + + layout::Node::with_children(size, vec![content]) +} diff --git a/src/widget.rs b/src/widget.rs index c0ac716f..15b34ffa 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -47,6 +47,14 @@ pub mod container { iced_native::widget::Container<'a, Message, Renderer>; } +pub mod mouse_listener { + //! Intercept mouse events on a widget. + + /// A container intercepting mouse events. + pub type MouseListener<'a, Message, Renderer = crate::Renderer> = + iced_native::widget::MouseListener<'a, Message, Renderer>; +} + pub mod pane_grid { //! Let your users split regions of your application and organize layout dynamically. //! |