434 lines
12 KiB
Rust
434 lines
12 KiB
Rust
//! A container for capturing mouse events.
|
|
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.
|
|
#[allow(missing_debug_implementations)]
|
|
pub struct MouseArea<
|
|
'a,
|
|
Message,
|
|
Theme = crate::Theme,
|
|
Renderer = crate::Renderer,
|
|
> {
|
|
content: Element<'a, Message, Theme, Renderer>,
|
|
on_press: Option<Message>,
|
|
on_release: Option<Message>,
|
|
on_double_click: Option<Message>,
|
|
on_right_press: Option<Message>,
|
|
on_right_release: Option<Message>,
|
|
on_middle_press: Option<Message>,
|
|
on_middle_release: Option<Message>,
|
|
on_scroll: Option<Box<dyn Fn(mouse::ScrollDelta) -> Message + 'a>>,
|
|
on_enter: Option<Message>,
|
|
on_move: Option<Box<dyn Fn(Point) -> Message + 'a>>,
|
|
on_exit: Option<Message>,
|
|
interaction: Option<mouse::Interaction>,
|
|
}
|
|
|
|
impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, 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 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`].
|
|
#[derive(Default)]
|
|
struct State {
|
|
is_hovered: bool,
|
|
bounds: Rectangle,
|
|
cursor_position: Option<Point>,
|
|
previous_click: Option<mouse::Click>,
|
|
}
|
|
|
|
impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|
/// Creates a [`MouseArea`] with the given content.
|
|
pub fn new(
|
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
|
) -> Self {
|
|
MouseArea {
|
|
content: content.into(),
|
|
on_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<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
|
for MouseArea<'_, Message, Theme, Renderer>
|
|
where
|
|
Renderer: renderer::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 size(&self) -> Size<Length> {
|
|
self.content.as_widget().size()
|
|
}
|
|
|
|
fn layout(
|
|
&self,
|
|
tree: &mut Tree,
|
|
renderer: &Renderer,
|
|
limits: &layout::Limits,
|
|
) -> layout::Node {
|
|
self.content
|
|
.as_widget()
|
|
.layout(&mut tree.children[0], renderer, limits)
|
|
}
|
|
|
|
fn operate(
|
|
&self,
|
|
tree: &mut Tree,
|
|
layout: Layout<'_>,
|
|
renderer: &Renderer,
|
|
operation: &mut dyn Operation,
|
|
) {
|
|
self.content.as_widget().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<'_>,
|
|
renderer: &Renderer,
|
|
translation: Vector,
|
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
|
self.content.as_widget_mut().overlay(
|
|
&mut tree.children[0],
|
|
layout,
|
|
renderer,
|
|
translation,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a, Message, Theme, Renderer> From<MouseArea<'a, Message, Theme, Renderer>>
|
|
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<Message: Clone, Theme, Renderer>(
|
|
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();
|
|
let bounds = layout.bounds();
|
|
|
|
if state.cursor_position != cursor_position || state.bounds != bounds {
|
|
let was_hovered = state.is_hovered;
|
|
|
|
state.is_hovered = cursor.is_over(layout.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()) {
|
|
return;
|
|
}
|
|
|
|
match event {
|
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
|
if let Some(message) = widget.on_press.as_ref() {
|
|
shell.publish(message.clone());
|
|
shell.capture_event();
|
|
}
|
|
|
|
if let Some(position) = cursor_position {
|
|
if let Some(message) = widget.on_double_click.as_ref() {
|
|
let new_click = mouse::Click::new(
|
|
position,
|
|
mouse::Button::Left,
|
|
state.previous_click,
|
|
);
|
|
|
|
if new_click.kind() == mouse::click::Kind::Double {
|
|
shell.publish(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();
|
|
}
|
|
}
|
|
}
|
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
|
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
|
if let Some(message) = widget.on_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());
|
|
shell.capture_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::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();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|