Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MouseArea widget #1594

Merged
merged 10 commits into from
Apr 13, 2023
3 changes: 3 additions & 0 deletions native/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod column;
pub mod container;
pub mod helpers;
pub mod image;
pub mod mouse_area;
pub mod operation;
pub mod pane_grid;
pub mod pick_list;
Expand Down Expand Up @@ -51,6 +52,8 @@ pub use helpers::*;
#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use mouse_area::MouseArea;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
Expand Down
10 changes: 10 additions & 0 deletions native/src/widget/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,13 @@ where
{
widget::Svg::new(handle)
}

/// A container intercepting mouse events.
pub fn mouse_area<'a, Message, Renderer>(
widget: impl Into<Element<'a, Message, Renderer>>,
) -> widget::MouseArea<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
widget::MouseArea::new(widget)
}
311 changes: 311 additions & 0 deletions native/src/widget/mouse_area.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
//! 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,
};

/// Emit messages on mouse events.
#[allow(missing_debug_implementations)]
pub struct MouseArea<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
on_release: Option<Message>,
on_right_press: Option<Message>,
on_right_release: Option<Message>,
on_middle_press: Option<Message>,
on_middle_release: Option<Message>,
}

impl<'a, Message, Renderer> MouseArea<'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
}
}

/// Local state of the [`MouseArea`].
#[derive(Default)]
struct State {
// TODO: Support on_mouse_enter and on_mouse_exit
}

impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
/// Creates a [`MouseArea`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
MouseArea {
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,
}
}
}

impl<'a, Message, Renderer> Widget<Message, Renderer>
for MouseArea<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Message: Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}

fn state(&self) -> tree::State {
tree::State::new(State::default())
}

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 {
self.content.as_widget().layout(renderer, limits)
}

fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
self.content.as_widget().operate(
&mut tree.children[0],
layout,
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,
cursor_position,
renderer,
clipboard,
shell,
) {
return event::Status::Captured;
}

update(self, &event, layout, cursor_position, shell)
}

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,
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,
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,
renderer,
)
}
}

impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + crate::Renderer,
{
fn from(
area: MouseArea<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(area)
}
}

/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
/// accordingly.
fn update<Message: Clone, Renderer>(
widget: &mut MouseArea<'_, Message, Renderer>,
event: &Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
) -> event::Status {
if !layout.bounds().contains(cursor_position) {
return event::Status::Ignored;
}

if let Some(message) = widget.on_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) = event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

if let Some(message) = widget.on_release.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

if let Some(message) = widget.on_right_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

if let Some(message) = widget.on_right_release.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Right,
)) = event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

if let Some(message) = widget.on_middle_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Middle,
)) = event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

if let Some(message) = widget.on_middle_release.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Middle,
)) = event
{
shell.publish(message.clone());

return event::Status::Captured;
}
}

event::Status::Ignored
}
8 changes: 8 additions & 0 deletions src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ pub mod container {
iced_native::widget::Container<'a, Message, Renderer>;
}

pub mod mouse_area {
//! Intercept mouse events on a widget.

/// A container intercepting mouse events.
pub type MouseArea<'a, Message, Renderer = crate::Renderer> =
iced_native::widget::MouseArea<'a, Message, Renderer>;
}

pub mod pane_grid {
//! Let your users split regions of your application and organize layout dynamically.
//!
Expand Down