use galileo_types::cartesian::{CartesianPoint2d, Point2};
use web_time::SystemTime;
use crate::control::{
EventPropagation, MouseButton, MouseButtonsState, MouseEvent, RawUserEvent, TouchId, UserEvent,
UserEventHandler,
};
use crate::map::Map;
const DRAG_THRESHOLD: f64 = 3.0;
const CLICK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(200);
const DBL_CLICK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
struct TouchInfo {
id: TouchId,
start_position: Point2,
_start_time: SystemTime,
prev_position: Point2,
}
pub struct EventProcessor {
handlers: Vec<Box<dyn UserEventHandler>>,
pointer_position: Point2,
pointer_pressed_position: Point2,
touches: Vec<TouchInfo>,
buttons_state: MouseButtonsState,
last_pressed_time: SystemTime,
last_click_time: SystemTime,
drag_target: Option<usize>,
}
impl Default for EventProcessor {
fn default() -> Self {
Self {
handlers: vec![],
pointer_position: Default::default(),
pointer_pressed_position: Default::default(),
touches: Vec::new(),
buttons_state: Default::default(),
last_pressed_time: SystemTime::UNIX_EPOCH,
last_click_time: SystemTime::UNIX_EPOCH,
drag_target: None,
}
}
}
impl EventProcessor {
pub fn add_handler(&mut self, handler: impl UserEventHandler + 'static) {
self.handlers.push(Box::new(handler));
}
pub fn add_handler_boxed(&mut self, handler: Box<dyn UserEventHandler>) {
self.handlers.push(handler);
}
pub fn is_dragging(&self) -> bool {
self.drag_target.is_some()
}
pub fn handle(&mut self, event: RawUserEvent, map: &mut Map) {
if let Some(user_events) = self.process(event) {
for user_event in user_events {
log::trace!("Handling user event: {user_event:?}");
let mut drag_start_target = None;
for (index, handler) in self.handlers.iter_mut().enumerate() {
if matches!(user_event, UserEvent::Drag(..) | UserEvent::DragEnded(..)) {
if let Some(target) = &self.drag_target {
if index != *target {
continue;
}
} else {
continue;
}
}
match handler.handle(&user_event, map) {
EventPropagation::Propagate => {}
EventPropagation::Stop => break,
EventPropagation::Consume => {
if let UserEvent::DragStarted(..) = user_event {
drag_start_target = Some(index);
}
break;
}
}
}
if drag_start_target.is_some() {
self.drag_target = drag_start_target;
}
if matches!(user_event, UserEvent::DragEnded(..)) {
self.drag_target = None;
}
}
}
}
fn process(&mut self, event: RawUserEvent) -> Option<Vec<UserEvent>> {
let now = SystemTime::now();
match event {
RawUserEvent::ButtonPressed(button) => {
self.buttons_state.set_pressed(button);
self.last_pressed_time = now;
self.pointer_pressed_position = self.pointer_position;
Some(vec![UserEvent::ButtonPressed(
button,
self.get_mouse_event(),
)])
}
RawUserEvent::ButtonReleased(button) => {
self.buttons_state.set_released(button);
let mut events = vec![UserEvent::ButtonReleased(button, self.get_mouse_event())];
if (now.duration_since(self.last_pressed_time)).unwrap_or_default() < CLICK_TIMEOUT
{
events.push(UserEvent::Click(button, self.get_mouse_event()));
if (now.duration_since(self.last_click_time)).unwrap_or_default()
< DBL_CLICK_TIMEOUT
{
events.push(UserEvent::DoubleClick(button, self.get_mouse_event()));
}
self.last_click_time = now;
}
if self.drag_target.is_some() {
events.push(UserEvent::DragEnded(button, self.get_mouse_event()));
}
Some(events)
}
RawUserEvent::PointerMoved(position) => {
let prev_position = self.pointer_position;
self.pointer_position = position;
let mut events = vec![UserEvent::PointerMoved(self.get_mouse_event())];
if let Some(button) = self.buttons_state.single_pressed() {
let mut is_dragging = self.drag_target.is_some();
if self.drag_target.is_none()
&& position.taxicab_distance(&self.pointer_pressed_position)
> DRAG_THRESHOLD
{
events.push(UserEvent::DragStarted(
button,
self.get_mouse_event_pos(self.pointer_pressed_position),
));
is_dragging = true;
}
if is_dragging {
events.push(UserEvent::Drag(
button,
self.pointer_position - prev_position,
self.get_mouse_event(),
));
}
}
Some(events)
}
RawUserEvent::Scroll(delta) => {
Some(vec![UserEvent::Scroll(delta, self.get_mouse_event())])
}
RawUserEvent::TouchStart(touch) => {
for i in 0..self.touches.len() {
if self.touches[i].id == touch.touch_id {
self.touches.remove(i);
break;
}
}
self.touches.push(TouchInfo {
id: touch.touch_id,
start_position: touch.position,
_start_time: now,
prev_position: touch.position,
});
None
}
RawUserEvent::TouchMove(touch) => {
let touch_info = self.touches.iter().find(|t| t.id == touch.touch_id)?;
let position = touch.position;
let mut events = vec![];
if self.touches.len() == 1 {
let mut is_dragging = self.drag_target.is_some();
if self.drag_target.is_none()
&& position.taxicab_distance(&touch_info.start_position) > DRAG_THRESHOLD
{
events.push(UserEvent::DragStarted(
MouseButton::Other,
self.get_mouse_event_pos(touch_info.start_position),
));
is_dragging = true
}
if is_dragging {
events.push(UserEvent::Drag(
MouseButton::Other,
position - touch_info.prev_position,
self.get_mouse_event_pos(position),
));
}
} else if self.touches.len() == 2 {
let Some(other_touch) = self.touches.iter().find(|t| t.id != touch_info.id)
else {
log::warn!("Unexpected touch id");
return None;
};
let distance = (other_touch.prev_position - position).magnitude();
let prev_distance =
(other_touch.prev_position - touch_info.prev_position).magnitude();
let zoom = prev_distance / distance;
events.push(UserEvent::Zoom(zoom, other_touch.prev_position))
}
for touch_info in &mut self.touches {
if touch_info.id == touch.touch_id {
touch_info.prev_position = position;
}
}
Some(events)
}
RawUserEvent::TouchEnd(touch) => {
for i in 0..self.touches.len() {
if self.touches[i].id == touch.touch_id {
self.touches.remove(i);
break;
}
}
let mut events = vec![];
if self.drag_target.is_some() && self.touches.is_empty() {
self.drag_target = None;
events.push(UserEvent::DragEnded(
MouseButton::Other,
self.get_mouse_event_pos(touch.position),
));
}
Some(events)
}
}
}
fn get_mouse_event(&self) -> MouseEvent {
self.get_mouse_event_pos(self.pointer_position)
}
fn get_mouse_event_pos(&self, screen_pointer_position: Point2) -> MouseEvent {
MouseEvent {
screen_pointer_position,
buttons: self.buttons_state,
}
}
}