use crate::core::{CursorStyle, HitboxId, InputEvent, MouseButton, Point};
use crate::view::interactivity::{
ClickEvent, DragEvent, Effects, EventContext, ScrollEvent, ScrollState, TextInputEvent,
};
use crate::view::window::Window;
use std::time::{Duration, Instant};
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(450);
const DOUBLE_CLICK_DISTANCE: f32 = 4.0;
impl Window {
pub fn handle_event(&mut self, event: &InputEvent) -> bool {
match event {
InputEvent::MouseMove { position } => self.on_mouse_move(*position),
InputEvent::MouseDown { position, button } => self.on_mouse_down(*position, *button),
InputEvent::MouseUp { position, button } => {
if let Some(redraw) = self.finish_drag(*position) {
self.mouse.pressed = None;
self.pressed_drag = None;
return redraw;
}
let click = ClickEvent {
position: *position,
button: *button,
modifiers: self.modifiers,
click_count: self.click_count(),
};
let redraw = *button == MouseButton::Left && self.dispatch_click(&click, ClickKind::Up);
let was_pressed = self.mouse.pressed.take().is_some();
self.pressed_drag = None;
redraw | was_pressed
}
InputEvent::Scroll { position, delta } => {
self.dispatch_scroll(&ScrollEvent { position: *position, delta: *delta })
}
InputEvent::KeyDown { keystroke, text } => self.dispatch_keystroke(keystroke, text.as_deref()),
InputEvent::ImePreedit { text, cursor } => {
self.dispatch_text_input(TextInputEvent::Preedit { text: text.clone(), cursor: *cursor })
}
InputEvent::ImeCommit(text) => self.dispatch_text_input(TextInputEvent::Insert(text.clone())),
}
}
fn click_count(&self) -> u32 {
self.last_click.map_or(1, |(_, _, count)| count)
}
fn on_mouse_down(&mut self, position: Point, button: MouseButton) -> bool {
let now = Instant::now();
let count = match self.last_click {
Some((at, last, count))
if now.duration_since(at) < DOUBLE_CLICK_INTERVAL
&& (position.x.0 - last.x.0).abs() < DOUBLE_CLICK_DISTANCE
&& (position.y.0 - last.y.0).abs() < DOUBLE_CLICK_DISTANCE =>
{
count % 3 + 1
}
_ => 1,
};
self.last_click = Some((now, position, count));
self.mouse.pressed = self.frame.hitboxes.topmost_at(position);
self.pressed_drag = self
.mouse
.pressed
.and_then(|pressed| self.frame.handlers.iter().find(|entry| entry.hitbox == pressed)?.on_drag.clone());
self.focus_hit(position);
if button == MouseButton::Left {
self.prepare_drag(position);
}
let click = ClickEvent { position, button, modifiers: self.modifiers, click_count: count };
let kind = if button == MouseButton::Right { ClickKind::Right } else { ClickKind::Down };
self.dispatch_click(&click, kind) | self.mouse.pressed.is_some()
}
fn focus_hit(&mut self, position: Point) {
let hits: Vec<HitboxId> = self.frame.hitboxes.hits_at(position).collect();
let focus = hits.iter().find_map(|hit| self.frame.handlers.iter().find(|entry| entry.hitbox == *hit)?.focus);
if let Some(focus) = focus {
self.entities.focus.focus(&focus);
}
}
fn on_mouse_move(&mut self, position: Point) -> bool {
let dragged = self.update_drag(position) || self.dispatch_drag(position);
let moved = self.dispatch_move(position);
let before: Vec<HitboxId> = self.frame.hitboxes.hits_at(self.mouse.position).collect();
self.mouse.position = position;
let after: Vec<HitboxId> = self.frame.hitboxes.hits_at(position).collect();
let tooltip_changed = self.track_tooltip();
dragged | moved | tooltip_changed | (before != after)
}
fn dispatch_move(&mut self, position: Point) -> bool {
if self.mouse.pressed.is_some() {
return false;
}
let handler = self
.frame
.hitboxes
.hits_at(position)
.find_map(|hit| self.frame.handlers.iter().find(|entry| entry.hitbox == hit)?.on_mouse_move.clone());
let Some(handler) = handler else { return false };
let mut context = EventContext { entities: &mut self.entities, effects: Effects::default() };
handler(&DragEvent { position, modifiers: self.modifiers }, &mut context);
let effects = context.effects;
self.finish(effects)
}
fn dispatch_drag(&mut self, position: Point) -> bool {
if self.mouse.pressed.is_none() {
return false;
}
let Some(handler) = self.pressed_drag.clone() else { return false };
let mut context = EventContext { entities: &mut self.entities, effects: Effects::default() };
handler(&DragEvent { position, modifiers: self.modifiers }, &mut context);
let effects = context.effects;
self.finish(effects)
}
fn dispatch_click(&mut self, event: &ClickEvent, kind: ClickKind) -> bool {
let hits: Vec<HitboxId> = self.frame.hitboxes.hits_at(event.position).collect();
let mut context = EventContext { entities: &mut self.entities, effects: Effects::default() };
for hit in hits {
let Some(entry) = self.frame.handlers.iter().find(|entry| entry.hitbox == hit) else { continue };
let handler = match kind {
ClickKind::Down => &entry.on_mouse_down,
ClickKind::Right => &entry.on_right_click,
ClickKind::Up if self.mouse.pressed == Some(hit) => &entry.on_click,
ClickKind::Up => &None,
};
if let Some(handler) = handler.clone() {
context.effects.propagate = false;
handler(event, &mut context);
if !context.effects.propagate {
break;
}
}
}
let effects = context.effects;
self.finish(effects)
}
fn dispatch_scroll(&mut self, event: &ScrollEvent) -> bool {
let hits: Vec<HitboxId> = self.frame.hitboxes.hits_at(event.position).collect();
let mut context = EventContext { entities: &mut self.entities, effects: Effects::default() };
for hit in hits {
let Some(entry) = self.frame.handlers.iter().find(|entry| entry.hitbox == hit) else { continue };
if let Some(handler) = entry.on_scroll.clone() {
handler(event, &mut context);
break;
}
if let Some(scroll) = &entry.scroll {
if ScrollState::scroll_by(scroll, event.delta.y.0) {
context.effects.notify();
break;
}
}
}
let effects = context.effects;
self.finish(effects)
}
pub fn cursor_style(&self) -> CursorStyle {
if self.active_drag_type().is_some() {
return CursorStyle::Grabbing;
}
let cursor_of = |hit: HitboxId| self.frame.handlers.iter().find(|entry| entry.hitbox == hit)?.cursor;
if let Some(cursor) = self.mouse.pressed.and_then(cursor_of) {
return cursor;
}
self.frame.hitboxes.hits_at(self.mouse.position).find_map(cursor_of).unwrap_or_default()
}
pub(crate) fn finish(&mut self, effects: Effects) -> bool {
let mut redraw = effects.redraw;
let mut actions = effects.actions;
actions.append(&mut self.entities.pending_actions);
for action in actions {
redraw |= self.dispatch_action(action);
}
redraw || self.entities.is_dirty()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ClickKind {
Down,
Up,
Right,
}