use crate::core::{Point, Size};
pub type TouchId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScreenOrientation {
Portrait,
Landscape,
ReversePortrait,
ReverseLandscape,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GestureClass {
Single,
Multi,
#[cfg(feature = "holographic")]
Holographic,
}
#[derive(Debug, Clone)]
pub enum Event {
MouseDown((Point, u32)),
MouseUp((Point, u32)),
MouseMoveLegacy((Point, u32)),
KeyDown((u32, u32)),
KeyUp((u32, u32)),
FocusGained,
FocusLost,
MouseMove { pos: Point },
MousePress { pos: Point, button: u32 },
MouseDoubleClick { pos: Point, button: u32 },
MouseRelease { pos: Point, button: u32 },
MouseEnter { pos: Point },
MouseLeave { pos: Point },
KeyPress { key: u32, modifiers: u32 },
KeyRelease { key: u32, modifiers: u32 },
Paint,
Resize { size: Size },
Timer { id: u32 },
Wheel { delta: Point, modifiers: u32 },
Custom { name: String, payload: Vec<u8> },
OrientationChanged { orientation: ScreenOrientation },
Quit,
#[cfg(feature = "touch")]
TouchBegin { pos: Point, touch_id: TouchId },
#[cfg(feature = "touch")]
TouchEnd { pos: Point, touch_id: TouchId },
#[cfg(feature = "touch")]
TouchMove { pos: Point, touch_id: TouchId },
#[cfg(feature = "touch")]
Tap { pos: Point },
#[cfg(feature = "touch")]
DoubleTap { pos: Point },
#[cfg(feature = "touch")]
LongPress { pos: Point },
#[cfg(feature = "touch")]
Swipe { start: Point, end: Point, velocity: f32 },
#[cfg(feature = "touch")]
Pinch { scale: f32 },
#[cfg(feature = "touch")]
Rotate { angle: f32 },
#[cfg(feature = "touch")]
Drag { pos: Point, touch_id: TouchId, delta: Point },
#[cfg(feature = "touch")]
TwoFingerTap { pos: Point },
#[cfg(feature = "touch")]
TwoFingerSwipe { centroid_start: Point, centroid_end: Point, velocity: f32 },
#[cfg(feature = "touch")]
Fling { pos: Point, velocity: Point, touch_id: TouchId },
#[cfg(feature = "holographic")]
HolographicTouch { pos: Point, depth: f32, touch_id: TouchId },
PointerPress { pos: Point, button: u32, pressure: f32, tilt_x: f32, tilt_y: f32 },
PointerMove { pos: Point, pressure: f32, tilt_x: f32, tilt_y: f32 },
PointerRelease { pos: Point, button: u32, pressure: f32 },
GamepadPress { button: u32 },
GamepadRelease { button: u32 },
GamepadAxis { axis: u32, value: f32 },
GamepadConnected { id: u32 },
GamepadDisconnected { id: u32 },
}
impl Event {
pub fn mouse_press(x: i32, y: i32, button: u32) -> Self {
Self::MousePress { pos: Point::new(x, y), button }
}
pub fn mouse_release(x: i32, y: i32, button: u32) -> Self {
Self::MouseRelease { pos: Point::new(x, y), button }
}
pub fn mouse_double_click(x: i32, y: i32, button: u32) -> Self {
Self::MouseDoubleClick { pos: Point::new(x, y), button }
}
pub fn mouse_move(x: i32, y: i32) -> Self {
Self::MouseMove { pos: Point::new(x, y) }
}
pub fn mouse_enter(x: i32, y: i32) -> Self {
Self::MouseEnter { pos: Point::new(x, y) }
}
pub fn mouse_leave(x: i32, y: i32) -> Self {
Self::MouseLeave { pos: Point::new(x, y) }
}
pub fn key_press(key: u32, modifiers: u32) -> Self {
Self::KeyPress { key, modifiers }
}
pub fn key_release(key: u32, modifiers: u32) -> Self {
Self::KeyRelease { key, modifiers }
}
pub fn paint() -> Self {
Self::Paint
}
pub fn resize(width: u32, height: u32) -> Self {
Self::Resize { size: Size::new(width, height) }
}
pub fn timer(id: u32) -> Self {
Self::Timer { id }
}
pub fn wheel(delta_x: i32, delta_y: i32, modifiers: u32) -> Self {
Self::Wheel { delta: Point::new(delta_x, delta_y), modifiers }
}
#[cfg(feature = "touch")]
pub fn touch_begin(x: i32, y: i32, touch_id: TouchId) -> Self {
Self::TouchBegin { pos: Point::new(x, y), touch_id }
}
#[cfg(feature = "touch")]
pub fn touch_end(x: i32, y: i32, touch_id: TouchId) -> Self {
Self::TouchEnd { pos: Point::new(x, y), touch_id }
}
#[cfg(feature = "touch")]
pub fn touch_move(x: i32, y: i32, touch_id: TouchId) -> Self {
Self::TouchMove { pos: Point::new(x, y), touch_id }
}
#[cfg(feature = "touch")]
pub fn tap(x: i32, y: i32) -> Self {
Self::Tap { pos: Point::new(x, y) }
}
#[cfg(feature = "touch")]
pub fn double_tap(x: i32, y: i32) -> Self {
Self::DoubleTap { pos: Point::new(x, y) }
}
#[cfg(feature = "touch")]
pub fn long_press(x: i32, y: i32) -> Self {
Self::LongPress { pos: Point::new(x, y) }
}
#[cfg(feature = "touch")]
pub fn swipe(start_x: i32, start_y: i32, end_x: i32, end_y: i32, velocity: f32) -> Self {
Self::Swipe { start: Point::new(start_x, start_y), end: Point::new(end_x, end_y), velocity }
}
#[cfg(feature = "touch")]
pub fn pinch(scale: f32) -> Self {
Self::Pinch { scale }
}
#[cfg(feature = "touch")]
pub fn rotate(angle: f32) -> Self {
Self::Rotate { angle }
}
#[cfg(feature = "touch")]
pub fn drag(x: i32, y: i32, touch_id: TouchId, delta_x: i32, delta_y: i32) -> Self {
Self::Drag { pos: Point::new(x, y), touch_id, delta: Point::new(delta_x, delta_y) }
}
#[cfg(feature = "touch")]
pub fn two_finger_tap(x: i32, y: i32) -> Self {
Self::TwoFingerTap { pos: Point::new(x, y) }
}
#[cfg(feature = "touch")]
pub fn two_finger_swipe(
start_x: i32,
start_y: i32,
end_x: i32,
end_y: i32,
velocity: f32,
) -> Self {
Self::TwoFingerSwipe {
centroid_start: Point::new(start_x, start_y),
centroid_end: Point::new(end_x, end_y),
velocity,
}
}
#[cfg(feature = "touch")]
pub fn fling(x: i32, y: i32, vx: i32, vy: i32, touch_id: TouchId) -> Self {
Self::Fling { pos: Point::new(x, y), velocity: Point::new(vx, vy), touch_id }
}
#[cfg(feature = "holographic")]
pub fn holographic_touch(x: i32, y: i32, depth: f32, touch_id: TouchId) -> Self {
Self::HolographicTouch { pos: Point::new(x, y), depth, touch_id }
}
pub fn pointer_press(pos: Point, button: u32, pressure: f32, tilt_x: f32, tilt_y: f32) -> Self {
Event::PointerPress { pos, button, pressure, tilt_x, tilt_y }
}
pub fn pointer_move(pos: Point, pressure: f32, tilt_x: f32, tilt_y: f32) -> Self {
Event::PointerMove { pos, pressure, tilt_x, tilt_y }
}
pub fn pointer_release(pos: Point, button: u32, pressure: f32) -> Self {
Event::PointerRelease { pos, button, pressure }
}
pub fn gamepad_press(button: u32) -> Self {
Event::GamepadPress { button }
}
pub fn gamepad_release(button: u32) -> Self {
Event::GamepadRelease { button }
}
pub fn gamepad_axis(axis: u32, value: f32) -> Self {
Event::GamepadAxis { axis, value }
}
pub fn gamepad_connected(id: u32) -> Self {
Event::GamepadConnected { id }
}
pub fn gamepad_disconnected(id: u32) -> Self {
Event::GamepadDisconnected { id }
}
pub fn orientation_changed(orientation: ScreenOrientation) -> Self {
Self::OrientationChanged { orientation }
}
pub fn quit() -> Self {
Self::Quit
}
pub fn gesture_class(&self) -> Option<GestureClass> {
match self {
#[cfg(feature = "touch")]
Self::Tap { .. }
| Self::DoubleTap { .. }
| Self::LongPress { .. }
| Self::Swipe { .. }
| Self::Fling { .. } => Some(GestureClass::Single),
#[cfg(feature = "touch")]
Self::Pinch { .. }
| Self::Rotate { .. }
| Self::TwoFingerTap { .. }
| Self::TwoFingerSwipe { .. } => Some(GestureClass::Multi),
#[cfg(feature = "holographic")]
Self::HolographicTouch { .. } => Some(GestureClass::Holographic),
_ => None,
}
}
pub fn is_touch(&self) -> bool {
#[cfg(feature = "touch")]
{
matches!(
self,
Self::TouchBegin { .. }
| Self::TouchEnd { .. }
| Self::TouchMove { .. }
| Self::Tap { .. }
| Self::DoubleTap { .. }
| Self::LongPress { .. }
| Self::Swipe { .. }
| Self::Pinch { .. }
| Self::Rotate { .. }
| Self::Drag { .. }
| Self::TwoFingerTap { .. }
| Self::TwoFingerSwipe { .. }
| Self::Fling { .. }
) || {
#[cfg(feature = "holographic")]
{
matches!(self, Self::HolographicTouch { .. })
}
#[cfg(not(feature = "holographic"))]
{
false
}
}
}
#[cfg(not(feature = "touch"))]
{
#[cfg(feature = "holographic")]
{
matches!(self, Self::HolographicTouch { .. })
}
#[cfg(not(feature = "holographic"))]
{
false
}
}
}
}
pub trait EventHandler {
fn handle_event(&mut self, event: &Event);
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "touch")]
#[test]
fn touch_begin_creation() {
let e = Event::touch_begin(10, 20, 1);
match e {
Event::TouchBegin { pos, touch_id } => {
assert_eq!(pos.x, 10);
assert_eq!(pos.y, 20);
assert_eq!(touch_id, 1);
}
_ => panic!("expected TouchBegin"),
}
}
#[cfg(feature = "touch")]
#[test]
fn touch_end_creation() {
let e = Event::touch_end(30, 40, 2);
match e {
Event::TouchEnd { pos, touch_id } => {
assert_eq!(pos.x, 30);
assert_eq!(pos.y, 40);
assert_eq!(touch_id, 2);
}
_ => panic!("expected TouchEnd"),
}
}
#[cfg(feature = "touch")]
#[test]
fn tap_gesture_class_is_single() {
let e = Event::tap(5, 10);
assert_eq!(e.gesture_class(), Some(GestureClass::Single));
}
#[cfg(feature = "touch")]
#[test]
fn pinch_gesture_class_is_multi() {
let e = Event::pinch(0.5);
assert_eq!(e.gesture_class(), Some(GestureClass::Multi));
}
#[test]
fn mouse_event_gesture_class_is_none() {
let e = Event::mouse_press(1, 2, 0);
assert_eq!(e.gesture_class(), None);
}
#[cfg(feature = "touch")]
#[test]
fn is_touch_true_for_touch_variants() {
assert!(Event::touch_begin(0, 0, 0).is_touch());
assert!(Event::touch_end(0, 0, 0).is_touch());
assert!(Event::touch_move(0, 0, 0).is_touch());
assert!(Event::tap(0, 0).is_touch());
assert!(Event::double_tap(0, 0).is_touch());
assert!(Event::long_press(0, 0).is_touch());
assert!(Event::swipe(0, 0, 10, 10, 100.0).is_touch());
assert!(Event::pinch(1.0).is_touch());
assert!(Event::rotate(0.5).is_touch());
assert!(Event::drag(0, 0, 0, 1, 1).is_touch());
assert!(Event::two_finger_tap(0, 0).is_touch());
assert!(Event::two_finger_swipe(0, 0, 10, 10, 50.0).is_touch());
assert!(Event::fling(0, 0, 1, 1, 0).is_touch());
}
#[test]
fn is_touch_false_for_mouse_variants() {
assert!(!Event::mouse_press(1, 2, 0).is_touch());
assert!(!Event::mouse_move(1, 2).is_touch());
assert!(!Event::key_press(32, 0).is_touch());
assert!(!Event::paint().is_touch());
assert!(!Event::quit().is_touch());
}
#[cfg(feature = "touch")]
#[test]
fn swipe_fields_correct() {
let e = Event::swipe(0, 0, 100, 200, 500.0);
match e {
Event::Swipe { start, end, velocity } => {
assert_eq!(start.x, 0);
assert_eq!(start.y, 0);
assert_eq!(end.x, 100);
assert_eq!(end.y, 200);
assert!((velocity - 500.0).abs() < f32::EPSILON);
}
_ => panic!("expected Swipe"),
}
}
#[cfg(feature = "touch")]
#[test]
fn two_finger_tap_creation() {
let e = Event::two_finger_tap(15, 25);
match e {
Event::TwoFingerTap { pos } => {
assert_eq!(pos.x, 15);
assert_eq!(pos.y, 25);
}
_ => panic!("expected TwoFingerTap"),
}
}
#[cfg(feature = "touch")]
#[test]
fn two_finger_tap_gesture_class_is_multi() {
let e = Event::two_finger_tap(10, 20);
assert_eq!(e.gesture_class(), Some(GestureClass::Multi));
}
#[cfg(feature = "touch")]
#[test]
fn two_finger_swipe_fields_correct() {
let e = Event::two_finger_swipe(0, 0, 100, 200, 300.0);
match e {
Event::TwoFingerSwipe { centroid_start, centroid_end, velocity } => {
assert_eq!(centroid_start.x, 0);
assert_eq!(centroid_start.y, 0);
assert_eq!(centroid_end.x, 100);
assert_eq!(centroid_end.y, 200);
assert!((velocity - 300.0).abs() < f32::EPSILON);
}
_ => panic!("expected TwoFingerSwipe"),
}
}
#[cfg(feature = "touch")]
#[test]
fn two_finger_swipe_gesture_class_is_multi() {
let e = Event::two_finger_swipe(0, 0, 10, 10, 50.0);
assert_eq!(e.gesture_class(), Some(GestureClass::Multi));
}
#[cfg(feature = "touch")]
#[test]
fn fling_creation() {
let e = Event::fling(50, 60, 10, -5, 3);
match e {
Event::Fling { pos, velocity, touch_id } => {
assert_eq!(pos.x, 50);
assert_eq!(pos.y, 60);
assert_eq!(velocity.x, 10);
assert_eq!(velocity.y, -5);
assert_eq!(touch_id, 3);
}
_ => panic!("expected Fling"),
}
}
#[cfg(feature = "touch")]
#[test]
fn fling_gesture_class_is_single() {
let e = Event::fling(0, 0, 100, 50, 0);
assert_eq!(e.gesture_class(), Some(GestureClass::Single));
}
#[cfg(feature = "touch")]
#[test]
fn is_touch_true_for_new_gesture_variants() {
assert!(Event::two_finger_tap(0, 0).is_touch());
assert!(Event::two_finger_swipe(0, 0, 10, 10, 50.0).is_touch());
assert!(Event::fling(0, 0, 1, 1, 0).is_touch());
}
#[test]
fn orientation_changed_creation() {
let e = Event::orientation_changed(ScreenOrientation::Landscape);
match e {
Event::OrientationChanged { orientation } => {
assert_eq!(orientation, ScreenOrientation::Landscape);
}
_ => panic!("expected OrientationChanged"),
}
}
#[test]
fn orientation_changed_not_touch() {
let e = Event::orientation_changed(ScreenOrientation::Portrait);
assert!(!e.is_touch());
}
#[cfg(feature = "touch")]
#[test]
fn drag_fields_correct() {
let e = Event::drag(50, 60, 1, 5, -3);
match e {
Event::Drag { pos, touch_id, delta } => {
assert_eq!(pos.x, 50);
assert_eq!(pos.y, 60);
assert_eq!(touch_id, 1);
assert_eq!(delta.x, 5);
assert_eq!(delta.y, -3);
}
_ => panic!("expected Drag"),
}
}
#[test]
fn pointer_press_constructor() {
let e = Event::pointer_press(Point::new(10, 20), 0, 0.5, 0.1, 0.2);
match e {
Event::PointerPress { pos, button, pressure, tilt_x, tilt_y } => {
assert_eq!(pos, Point::new(10, 20));
assert_eq!(button, 0);
assert!((pressure - 0.5).abs() < 1e-6);
assert!((tilt_x - 0.1).abs() < 1e-6);
assert!((tilt_y - 0.2).abs() < 1e-6);
}
_ => panic!("Expected PointerPress"),
}
}
#[test]
fn pointer_move_constructor() {
let e = Event::pointer_move(Point::new(30, 40), 0.8, -0.1, 0.3);
match e {
Event::PointerMove { pos, .. } => {
assert_eq!(pos, Point::new(30, 40));
}
_ => panic!("Expected PointerMove"),
}
}
#[test]
fn pointer_release_constructor() {
let e = Event::pointer_release(Point::new(50, 60), 1, 0.0);
match e {
Event::PointerRelease { pos, button, .. } => {
assert_eq!(pos, Point::new(50, 60));
assert_eq!(button, 1);
}
_ => panic!("Expected PointerRelease"),
}
}
#[test]
fn gamepad_press_constructor() {
let e = Event::gamepad_press(3);
match e {
Event::GamepadPress { button } => assert_eq!(button, 3),
_ => panic!("Expected GamepadPress"),
}
}
#[test]
fn gamepad_release_constructor() {
let e = Event::gamepad_release(7);
match e {
Event::GamepadRelease { button } => assert_eq!(button, 7),
_ => panic!("Expected GamepadRelease"),
}
}
#[test]
fn gamepad_axis_constructor() {
let e = Event::gamepad_axis(1, -0.5);
match e {
Event::GamepadAxis { axis, value } => {
assert_eq!(axis, 1);
assert!((value - (-0.5)).abs() < 1e-6);
}
_ => panic!("Expected GamepadAxis"),
}
}
#[test]
fn gamepad_connected_constructor() {
let e = Event::gamepad_connected(42);
match e {
Event::GamepadConnected { id } => assert_eq!(id, 42),
_ => panic!("Expected GamepadConnected"),
}
}
#[test]
fn gamepad_disconnected_constructor() {
let e = Event::gamepad_disconnected(99);
match e {
Event::GamepadDisconnected { id } => assert_eq!(id, 99),
_ => panic!("Expected GamepadDisconnected"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventPriority {
High,
Normal,
Idle,
}
pub struct AsyncTask {
pub id: u64,
pub task: Box<dyn FnOnce() + Send>,
}
impl AsyncTask {
pub fn new<F>(id: u64, f: F) -> Self
where
F: FnOnce() + Send + 'static,
{
Self { id, task: Box::new(f) }
}
}
pub fn schedule_task<F>(id: u64, f: F)
where
F: FnOnce() + Send + 'static,
{
let task = AsyncTask::new(id, f);
TASK_QUEUE.with(|q| {
q.borrow_mut().push(task);
});
}
thread_local! {
static TASK_QUEUE: std::cell::RefCell<Vec<AsyncTask>> = const { std::cell::RefCell::new(Vec::new()) };
}
pub fn drain_tasks() {
TASK_QUEUE.with(|q| {
let mut tasks = q.borrow_mut();
while let Some(task) = tasks.pop() {
(task.task)();
}
});
}