use super::{GrabMode, Press, PressSource, velocity};
use crate::event::{
Event, EventCx, EventState, FocusSource, NavAdvance, PressStart, ScrollDelta, TimerHandle,
};
use crate::geom::{Affine, Coord, DVec2, Vec2};
use crate::window::WindowErased;
use crate::window::WindowWidget;
use crate::{ActionRedraw, Id, Node, Tile, TileExt};
use cast::{CastApprox, CastFloat};
use std::time::{Duration, Instant};
use winit::cursor::CursorIcon;
use winit::event::{ElementState, MouseButton, MouseScrollDelta};
const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1);
#[derive(Clone, Debug)]
struct PanDetails {
c0: DVec2,
c1: DVec2,
moved: bool,
mode: (bool, bool), }
#[derive(Clone, Debug)]
enum GrabDetails {
Click,
Grab,
Pan(PanDetails),
}
impl GrabDetails {
fn is_pan(&self) -> bool {
matches!(self, GrabDetails::Pan(_))
}
fn pan(position: DVec2, mode: (bool, bool)) -> Self {
GrabDetails::Pan(PanDetails {
c0: position,
c1: position,
moved: false,
mode,
})
}
}
#[derive(Clone, Debug)]
pub(super) struct MouseGrab {
button: MouseButton,
repetitions: u32,
pub(super) start_id: Id,
pub(super) depress: Option<Id>,
details: GrabDetails,
cancel: bool,
pub(super) icon: CursorIcon,
}
pub(crate) struct Mouse {
pub(super) over: Option<Id>, pub(super) icon: CursorIcon, old_icon: CursorIcon, last_click_button: Option<MouseButton>,
last_click_repetitions: u32,
last_click_timeout: Instant,
last_pin: Option<(Id, DVec2)>,
pub(super) grab: Option<MouseGrab>,
tooltip_source: Option<Id>,
tooltip_expiry_started: bool,
last_position: DVec2,
last_click_position: DVec2,
pub(super) samples: velocity::Samples,
}
impl Default for Mouse {
fn default() -> Self {
Mouse {
over: None,
icon: CursorIcon::Default,
old_icon: CursorIcon::Default,
last_click_button: None,
last_click_repetitions: 0,
last_click_timeout: Instant::now(),
last_pin: None,
grab: None,
tooltip_source: None,
tooltip_expiry_started: false,
last_position: DVec2::ZERO,
last_click_position: DVec2::ZERO,
samples: Default::default(),
}
}
}
impl Mouse {
pub(crate) const TIMER_TOOLTIP: TimerHandle = TimerHandle::new(1 << 59, false);
pub(in crate::event::cx) fn cancel_event_focus(&mut self, target: &Id) {
if let Some(grab) = self.grab.as_mut()
&& grab.start_id == target
{
grab.cancel = true;
}
}
pub(in crate::event::cx) fn update_pointer_icon(&mut self) -> Option<CursorIcon> {
let icon = self
.grab
.as_ref()
.map(|grab| grab.icon)
.unwrap_or(self.icon);
if icon != self.old_icon {
self.old_icon = icon;
Some(icon)
} else {
None
}
}
pub fn frame_update(&mut self) -> Option<(Id, Affine)> {
if let Some(grab) = self.grab.as_mut()
&& let GrabDetails::Pan(details) = &mut grab.details
{
let (old, new) = (details.c0, details.c1);
details.c0 = details.c1;
let transform = if let Some((_, y)) = self.last_pin.as_ref() {
Affine::pan(old, new, *y, *y, details.mode)
} else {
Affine::translate(new - old)
};
if transform.is_finite() && transform != Affine::IDENTITY {
let id = grab.start_id.clone();
return Some((id, transform));
}
}
None
}
pub(crate) fn over_id(&self) -> Option<Id> {
self.over.clone()
}
fn update_grab(&mut self) -> (bool, Option<ActionRedraw>) {
let (mut cancel, mut redraw) = (false, None);
if let Some(grab) = self.grab.as_mut() {
cancel = grab.cancel;
if let GrabDetails::Click = grab.details {
let over = self.over.as_ref();
if grab.start_id == over {
if grab.depress.as_ref() != over {
grab.depress = over.cloned();
redraw = Some(ActionRedraw);
}
} else if grab.depress.is_some() {
grab.depress = None;
redraw = Some(ActionRedraw);
}
}
}
(cancel, redraw)
}
pub(in crate::event::cx) fn start_grab(
&mut self,
button: MouseButton,
repetitions: u32,
id: Id,
position: DVec2,
mode: GrabMode,
icon: CursorIcon,
) -> bool {
let details = match mode {
GrabMode::Click => GrabDetails::Click,
GrabMode::Grab => GrabDetails::Grab,
GrabMode::Pan { scale, rotate } => {
if matches!(&self.last_pin, Some((id2, _)) if id == *id2) {
GrabDetails::pan(position, (scale, rotate))
} else {
GrabDetails::pan(position, (false, false))
}
}
};
if let Some(ref mut grab) = self.grab {
if grab.start_id != id
|| grab.button != button
|| grab.details.is_pan() != mode.is_pan()
|| grab.cancel
{
return false;
}
debug_assert!(repetitions >= grab.repetitions);
grab.repetitions = repetitions;
grab.depress = Some(id.clone());
grab.details = details;
} else {
self.grab = Some(MouseGrab {
button,
repetitions,
start_id: id.clone(),
depress: Some(id.clone()),
details,
cancel: false,
icon,
});
}
true
}
pub(in crate::event::cx) fn tooltip_popup_close(&mut self, id: &Id) {
if self.tooltip_source.as_ref() == Some(id) {
self.tooltip_source = None;
}
}
}
impl EventState {
pub(crate) fn mouse_pin(&self) -> Option<(DVec2, bool)> {
if let Some((_, position)) = self.mouse.last_pin.as_ref() {
let used = self
.mouse
.grab
.as_ref()
.map(|grab| grab.details.is_pan())
.unwrap_or(false);
Some((*position, used))
} else {
None
}
}
}
fn has_tooltip(window: &dyn Tile, id: Option<&Id>) -> bool {
if let Some(id) = id.as_ref()
&& let Some(tile) = window.find_tile(id)
{
tile.tooltip().is_some()
} else {
false
}
}
impl<'a> EventCx<'a> {
fn set_over(&mut self, mut window: Node<'_>, w_id: Option<Id>) {
if self.mouse.over != w_id {
log::trace!("set_over: w_id={w_id:?}");
if let Some(id) = self.mouse.over.take() {
self.send_event(window.re(), id, Event::MouseOver(false));
}
self.mouse.over = w_id.clone();
self.mouse.icon = Default::default();
let tooltip_expiry_started = self.mouse.tooltip_expiry_started;
let update_timer = if let Some(source) = &mut self.mouse.tooltip_source {
if !tooltip_expiry_started {
if *source != w_id && !has_tooltip(window.as_tile(), w_id.as_ref()) {
self.mouse.tooltip_expiry_started = true;
true
} else {
false
}
} else {
if *source == w_id || has_tooltip(window.as_tile(), w_id.as_ref()) {
self.mouse.tooltip_expiry_started = false;
true
} else {
false
}
}
} else {
self.mouse.tooltip_expiry_started = false;
true
};
if update_timer {
let delay = self.config().event().hover_delay();
self.request_timer(window.id(), Mouse::TIMER_TOOLTIP, delay);
}
if let Some(id) = w_id {
self.send_event(window, id, Event::MouseOver(true));
}
}
}
fn remove_mouse_grab(&mut self, window: Node<'_>, success: bool) {
let mut to_send = None;
let last_pin;
let redraw;
if let Some(grab) = self.mouse.grab.as_ref() {
log::trace!(
"remove_mouse_grab: start_id={}, success={success}",
grab.start_id
);
debug_assert!(self.mouse.last_position.is_finite());
redraw = grab.depress.clone();
if let GrabDetails::Pan(details) = &grab.details {
if success && !details.moved {
last_pin = Some((grab.start_id.clone(), self.mouse.last_position));
} else {
last_pin = None;
}
} else {
last_pin = None;
let press = Press {
source: PressSource::mouse(Some(grab.button), grab.repetitions),
id: self.mouse.over.clone(),
coord: self.mouse.last_position.cast_nearest(),
};
let event = Event::PressEnd { press, success };
to_send = Some((grab.start_id.clone(), event));
}
} else {
return;
}
if let Some((id, event)) = to_send {
self.send_event(window, id, event);
}
self.mouse.last_pin = last_pin;
self.opt_redraw(redraw);
self.mouse.grab = None;
}
pub(in crate::event::cx) fn mouse_handle_pending(&mut self, mut node: Node<'_>) {
let (cancel, redraw) = self.mouse.update_grab();
if cancel {
self.remove_mouse_grab(node.re(), false);
}
self.action_redraw(redraw);
if self.action_moved.is_some() {
let over = node.try_probe(self.mouse.last_position.cast_nearest());
self.set_over(node, over);
}
}
pub(in crate::event::cx) fn handle_pointer_moved<A>(
&mut self,
win: &mut dyn WindowWidget<Data = A>,
data: &A,
position: DVec2,
) {
let coord = position.cast_nearest();
let id = win.try_probe(coord);
self.tooltip_motion(win, &id);
self.handle_pointer_moved_(id, win.as_node(data), coord, position);
}
pub(in crate::event::cx) fn handle_pointer_moved_(
&mut self,
id: Option<Id>,
mut window: Node<'_>,
coord: Coord,
position: DVec2,
) {
let delta: Vec2 = (position - self.mouse.last_position).cast_approx();
if delta.is_finite() {
self.mouse.samples.push_delta(delta);
}
self.mouse.last_position = position;
self.set_over(window.re(), id.clone());
if let Some(grab) = self.mouse.grab.as_mut() {
match &mut grab.details {
GrabDetails::Click => (),
GrabDetails::Grab => {
let target = grab.start_id.clone();
let press = Press {
source: PressSource::mouse(Some(grab.button), grab.repetitions),
id,
coord,
};
debug_assert!(delta.is_finite());
let event = Event::PressMove { press, delta };
self.send_event(window.re(), target, event);
}
GrabDetails::Pan(details) => {
details.c1 = position;
details.moved = true;
self.need_frame_update = true;
}
}
} else if let Some(popup_id) = self
.popups
.last()
.filter(|popup| popup.is_sized)
.map(|state| state.desc.id.clone())
{
let press = Press {
source: PressSource::mouse(None, 0),
id,
coord,
};
let event = Event::PointerMove { press };
self.send_event(window, popup_id, event);
} else {
}
}
#[inline]
pub(in crate::event::cx) fn handle_pointer_entered(&mut self) {}
pub(in crate::event::cx) fn handle_pointer_left(&mut self, window: Node<'_>) {
self.mouse.last_click_button = None;
if self.mouse.grab.is_none() {
self.set_over(window, None);
}
}
pub(in crate::event::cx) fn handle_mouse_wheel(
&mut self,
window: Node<'_>,
delta: MouseScrollDelta,
) {
self.mouse.last_click_button = None;
let event = Event::Scroll(match delta {
MouseScrollDelta::LineDelta(x, y) => ScrollDelta::Lines(x, y),
MouseScrollDelta::PixelDelta(pos) => {
ScrollDelta::PixelDelta(DVec2::from(pos).cast_approx())
}
});
if let Some(id) = self.mouse.over.clone() {
self.send_event(window, id, event);
}
}
pub(in crate::event::cx) fn handle_mouse_input(
&mut self,
mut window: Node<'_>,
state: ElementState,
button: MouseButton,
) {
if state == ElementState::Pressed {
let now = Instant::now();
if Some(button) != self.mouse.last_click_button
|| self.mouse.last_click_timeout < now
|| (self.mouse.last_position - self.mouse.last_click_position).distance_l_inf()
> self.config.event().double_click_dist_thresh().into()
{
self.mouse.last_click_button = Some(button);
self.mouse.last_click_repetitions = 0;
}
self.mouse.last_click_repetitions += 1;
self.mouse.last_click_timeout = now + DOUBLE_CLICK_TIMEOUT;
self.mouse.last_click_position = self.mouse.last_position;
}
if self
.mouse
.grab
.as_ref()
.map(|g| g.button == button)
.unwrap_or(false)
{
self.remove_mouse_grab(window.re(), true);
}
if state == ElementState::Pressed {
let start_id = self.mouse.over.clone();
self.close_non_ancestors_of(start_id.as_ref());
if let Some(id) = start_id {
if matches!(self.mouse.last_pin.as_ref(), Some((pin_id, _)) if *pin_id != id) {
self.mouse.last_pin = None;
}
if self.config.event().mouse_nav_focus()
&& let Some(id) = self.nav_next(window.as_tile(), Some(&id), NavAdvance::None)
{
self.set_nav_focus(id, FocusSource::Pointer);
}
let source = PressSource::mouse(Some(button), self.mouse.last_click_repetitions);
let press = PressStart {
source,
id: Some(id.clone()),
position: self.mouse.last_position,
};
debug_assert!(press.position.is_finite());
let event = Event::PressStart(press);
self.send_event(window, id, event);
}
}
}
pub(crate) fn timer_expiry_tooltip(&mut self, win: &mut dyn WindowErased) {
match (self.mouse.over_id(), &self.mouse.tooltip_source) {
(None, None) => (),
(None, Some(_)) => {
win.close_tooltip(self);
self.mouse.tooltip_source = None;
}
(Some(id), Some(source)) if id == source => (),
(Some(id), _) => {
if let Some(text) = win.as_tile().find_tile(&id).and_then(|tile| tile.tooltip()) {
win.show_tooltip(self, id.clone(), text.to_string());
self.post_recursion();
self.mouse.tooltip_source = Some(id);
} else {
win.close_tooltip(self);
self.mouse.tooltip_source = None;
}
}
}
}
fn tooltip_motion(&mut self, win: &mut dyn WindowErased, id: &Option<Id>) {
if let Some(source) = &mut self.mouse.tooltip_source
&& *source != id
&& let Some(id) = id.as_ref()
&& let Some(text) = win.as_tile().find_tile(id).and_then(|tile| tile.tooltip())
{
win.show_tooltip(self, id.clone(), text.to_string());
self.post_recursion();
self.mouse.tooltip_source = Some(id.clone());
}
}
}