use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TouchPhase {
Started,
Moved,
Ended,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TouchContact {
pub id: u64,
pub phase: TouchPhase,
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum InputEvent {
Pan {
dx: f64,
dy: f64,
x: Option<f64>,
y: Option<f64>,
},
Zoom {
factor: f64,
x: Option<f64>,
y: Option<f64>,
},
Rotate {
delta_yaw: f64,
delta_pitch: f64,
},
Resize {
width: u32,
height: u32,
},
Touch(TouchContact),
}
impl InputEvent {
#[inline]
pub fn pan(dx: f64, dy: f64) -> Self {
Self::Pan {
dx,
dy,
x: None,
y: None,
}
}
#[inline]
pub fn pan_at(dx: f64, dy: f64, x: f64, y: f64) -> Self {
Self::Pan {
dx,
dy,
x: Some(x),
y: Some(y),
}
}
#[inline]
pub fn zoom_in(factor: f64) -> Self {
Self::Zoom {
factor,
x: None,
y: None,
}
}
#[inline]
pub fn zoom_at(factor: f64, x: f64, y: f64) -> Self {
Self::Zoom {
factor,
x: Some(x),
y: Some(y),
}
}
#[inline]
pub fn zoom_out(factor: f64) -> Self {
Self::Zoom {
factor: if factor > 0.0 { 1.0 / factor } else { 0.0 },
x: None,
y: None,
}
}
#[inline]
pub fn rotate(delta_yaw: f64, delta_pitch: f64) -> Self {
Self::Rotate {
delta_yaw,
delta_pitch,
}
}
#[inline]
pub fn resize(width: u32, height: u32) -> Self {
Self::Resize { width, height }
}
#[inline]
pub fn touch(id: u64, phase: TouchPhase, x: f64, y: f64) -> Self {
Self::Touch(TouchContact { id, phase, x, y })
}
}
impl InputEvent {
#[inline]
pub fn is_pan(&self) -> bool {
matches!(self, Self::Pan { .. })
}
#[inline]
pub fn is_zoom(&self) -> bool {
matches!(self, Self::Zoom { .. })
}
#[inline]
pub fn is_rotate(&self) -> bool {
matches!(self, Self::Rotate { .. })
}
#[inline]
pub fn is_resize(&self) -> bool {
matches!(self, Self::Resize { .. })
}
#[inline]
pub fn is_touch(&self) -> bool {
matches!(self, Self::Touch(_))
}
}
impl fmt::Display for InputEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pan { dx, dy, x, y } => {
if let (Some(px), Some(py)) = (x, y) {
write!(f, "Pan(dx={dx:.1}, dy={dy:.1}, at={px:.1},{py:.1})")
} else {
write!(f, "Pan(dx={dx:.1}, dy={dy:.1})")
}
}
Self::Zoom { factor, x, y } => {
if let (Some(px), Some(py)) = (x, y) {
write!(f, "Zoom(factor={factor:.3}, at={px:.1},{py:.1})")
} else {
write!(f, "Zoom(factor={factor:.3})")
}
}
Self::Rotate {
delta_yaw,
delta_pitch,
} => write!(f, "Rotate(yaw={delta_yaw:.4}, pitch={delta_pitch:.4})"),
Self::Resize { width, height } => {
write!(f, "Resize({width}x{height})")
}
Self::Touch(c) => {
write!(
f,
"Touch(id={}, {:?}, {:.1},{:.1})",
c.id, c.phase, c.x, c.y
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pan_constructor() {
let e = InputEvent::pan(10.0, -5.0);
assert_eq!(
e,
InputEvent::Pan {
dx: 10.0,
dy: -5.0,
x: None,
y: None,
}
);
}
#[test]
fn zoom_in_constructor() {
let e = InputEvent::zoom_in(2.0);
assert_eq!(
e,
InputEvent::Zoom {
factor: 2.0,
x: None,
y: None,
}
);
}
#[test]
fn zoom_at_constructor() {
let e = InputEvent::zoom_at(2.0, 10.0, 20.0);
assert_eq!(
e,
InputEvent::Zoom {
factor: 2.0,
x: Some(10.0),
y: Some(20.0),
}
);
}
#[test]
fn zoom_out_constructor() {
let e = InputEvent::zoom_out(2.0);
assert_eq!(
e,
InputEvent::Zoom {
factor: 0.5,
x: None,
y: None,
}
);
}
#[test]
fn zoom_out_zero_factor() {
let e = InputEvent::zoom_out(0.0);
assert_eq!(
e,
InputEvent::Zoom {
factor: 0.0,
x: None,
y: None,
}
);
}
#[test]
fn rotate_constructor() {
let e = InputEvent::rotate(0.1, 0.2);
assert_eq!(
e,
InputEvent::Rotate {
delta_yaw: 0.1,
delta_pitch: 0.2
}
);
}
#[test]
fn resize_constructor() {
let e = InputEvent::resize(1920, 1080);
assert_eq!(
e,
InputEvent::Resize {
width: 1920,
height: 1080
}
);
}
#[test]
fn is_pan() {
assert!(InputEvent::pan(1.0, 2.0).is_pan());
assert!(!InputEvent::zoom_in(1.0).is_pan());
}
#[test]
fn is_zoom() {
assert!(InputEvent::zoom_in(1.0).is_zoom());
assert!(!InputEvent::pan(0.0, 0.0).is_zoom());
}
#[test]
fn is_rotate() {
assert!(InputEvent::rotate(0.0, 0.0).is_rotate());
assert!(!InputEvent::resize(0, 0).is_rotate());
}
#[test]
fn is_resize() {
assert!(InputEvent::resize(800, 600).is_resize());
assert!(!InputEvent::rotate(0.0, 0.0).is_resize());
}
#[test]
fn display_pan() {
let s = format!("{}", InputEvent::pan(10.0, -5.0));
assert!(s.contains("Pan"));
assert!(s.contains("10.0"));
}
#[test]
fn display_zoom() {
let s = format!("{}", InputEvent::zoom_in(1.5));
assert!(s.contains("Zoom"));
assert!(s.contains("1.5"));
}
#[test]
fn display_rotate() {
let s = format!("{}", InputEvent::rotate(0.1, 0.2));
assert!(s.contains("Rotate"));
}
#[test]
fn display_resize() {
let s = format!("{}", InputEvent::resize(1920, 1080));
assert!(s.contains("1920"));
assert!(s.contains("1080"));
}
#[test]
fn copy_semantics() {
let a = InputEvent::pan(1.0, 2.0);
let b = a; assert_eq!(a, b);
}
#[test]
fn clone_eq() {
let a = InputEvent::zoom_in(3.0);
#[allow(clippy::clone_on_copy)]
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn different_variants_not_equal() {
assert_ne!(InputEvent::pan(0.0, 0.0), InputEvent::zoom_in(1.0));
}
}