use crate::coordinate_space::{
ClientSpace, ElementSpace, Lines, PageSpace, Pages, Pixels, ScreenSpace,
};
use euclid::num::Zero;
use euclid::*;
pub type ScreenPoint = Point2D<f64, ScreenSpace>;
pub type PagePoint = Point2D<f64, PageSpace>;
pub type ClientPoint = Point2D<f64, ClientSpace>;
pub type ElementPoint = Point2D<f64, ElementSpace>;
pub type PixelsVector = Vector3D<f64, Pixels>;
pub type LinesVector = Vector3D<f64, Lines>;
pub type PagesVector = Vector3D<f64, Pages>;
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum WheelDelta {
Pixels(PixelsVector),
Lines(LinesVector),
Pages(PagesVector),
}
impl Zero for WheelDelta {
fn zero() -> Self {
WheelDelta::pixels(0., 0., 0.)
}
}
impl WheelDelta {
pub fn pixels(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Pixels(PixelsVector::new(x, y, z))
}
pub fn lines(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Lines(LinesVector::new(x, y, z))
}
pub fn pages(x: f64, y: f64, z: f64) -> Self {
WheelDelta::Pages(PagesVector::new(x, y, z))
}
pub fn is_zero(&self) -> bool {
self.strip_units() == Vector3D::zero()
}
pub fn strip_units(&self) -> Vector3D<f64, UnknownUnit> {
match self {
WheelDelta::Pixels(v) => v.cast_unit(),
WheelDelta::Lines(v) => v.cast_unit(),
WheelDelta::Pages(v) => v.cast_unit(),
}
}
}
#[cfg(test)]
mod wheel_delta {
use super::*;
#[test]
fn zero() {
let d = WheelDelta::zero();
assert_eq!(d, WheelDelta::Pixels(PixelsVector::new(0., 0., 0.,)));
assert!(d.is_zero());
}
#[test]
fn construct_pixels() {
let d = WheelDelta::pixels(1., 2., 3.);
assert_eq!(d, WheelDelta::Pixels(PixelsVector::new(1., 2., 3.,)));
}
#[test]
fn construct_lines() {
let d = WheelDelta::lines(1., 2., 3.);
assert_eq!(d, WheelDelta::Lines(LinesVector::new(1., 2., 3.,)));
}
#[test]
fn construct_pages() {
let d = WheelDelta::pages(1., 2., 3.);
assert_eq!(d, WheelDelta::Pages(PagesVector::new(1., 2., 3.,)));
}
#[test]
fn strip_units() {
let d = WheelDelta::pixels(1., 2., 3.);
assert_eq!(d.strip_units(), Vector3D::new(1., 2., 3.));
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Coordinates {
screen: ScreenPoint,
page: PagePoint,
client: ClientPoint,
element: ElementPoint,
}
impl Coordinates {
pub fn new(
screen: ScreenPoint,
page: PagePoint,
client: ClientPoint,
element: ElementPoint,
) -> Self {
Self {
screen,
page,
client,
element,
}
}
pub fn screen(&self) -> ScreenPoint {
self.screen
}
pub fn page(&self) -> PagePoint {
self.page
}
pub fn client(&self) -> ClientPoint {
self.client
}
pub fn element(&self) -> ElementPoint {
self.element
}
}
impl Zero for Coordinates {
fn zero() -> Self {
Self::new(
ScreenPoint::zero(),
PagePoint::zero(),
ClientPoint::zero(),
ElementPoint::zero(),
)
}
}
#[cfg(test)]
mod coordinates {
use super::*;
#[test]
fn getters() {
let c = Coordinates::new(
ScreenPoint::new(1., 1.),
PagePoint::new(2., 2.),
ClientPoint::new(3., 3.),
ElementPoint::new(4., 4.),
);
assert_eq!(c.screen(), ScreenPoint::new(1., 1.));
assert_eq!(c.page(), PagePoint::new(2., 2.));
assert_eq!(c.client(), ClientPoint::new(3., 3.));
assert_eq!(c.element(), ElementPoint::new(4., 4.));
}
}
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointerOrientation {
tilt_x: Angle<f64>,
tilt_y: Angle<f64>,
altitude: Angle<f64>,
azimuth: Angle<f64>,
twist: Angle<f64>,
}
impl PointerOrientation {
pub fn new(
tilt_x: Angle<f64>,
tilt_y: Angle<f64>,
altitude: Angle<f64>,
azimuth: Angle<f64>,
twist: Angle<f64>,
) -> Self {
Self {
tilt_x,
tilt_y,
altitude,
azimuth,
twist,
}
}
pub fn from_tilt_and_twist(tilt_x: Angle<f64>, tilt_y: Angle<f64>, twist: Angle<f64>) -> Self {
let (altitude, azimuth) = tilt_to_spherical(tilt_x, tilt_y);
Self {
tilt_x,
tilt_y,
altitude,
azimuth,
twist,
}
}
pub fn from_spherical_and_twist(
altitude: Angle<f64>,
azimuth: Angle<f64>,
twist: Angle<f64>,
) -> Self {
let (tilt_x, tilt_y) = spherical_to_tilt(altitude, azimuth);
Self {
tilt_x,
tilt_y,
altitude,
azimuth,
twist,
}
}
pub fn tilt_x(&self) -> Angle<f64> {
self.tilt_x
}
pub fn tilt_y(&self) -> Angle<f64> {
self.tilt_y
}
pub fn altitude(&self) -> Angle<f64> {
self.altitude
}
pub fn azimuth(&self) -> Angle<f64> {
self.azimuth
}
pub fn twist(&self) -> Angle<f64> {
self.twist
}
}
impl Default for PointerOrientation {
fn default() -> Self {
Self::new(
Angle::zero(),
Angle::zero(),
Angle::frac_pi_2(),
Angle::zero(),
Angle::zero(),
)
}
}
#[cfg(test)]
mod pointer_orientation {
use super::*;
use assert2::assert;
use euclid::approxeq::ApproxEq;
#[test]
fn default() {
let p = PointerOrientation::default();
assert!(p.azimuth() == Angle::zero());
assert!(p.altitude() == Angle::frac_pi_2());
assert!(p.tilt_x() == Angle::zero());
assert!(p.tilt_y() == Angle::zero());
assert!(p.twist() == Angle::zero());
}
#[test]
fn twist() {
let p = PointerOrientation::from_tilt_and_twist(
Angle::zero(),
Angle::zero(),
Angle::degrees(42.),
);
assert!(p.twist() == Angle::degrees(42.));
let p = PointerOrientation::from_spherical_and_twist(
Angle::zero(),
Angle::zero(),
Angle::degrees(42.),
);
assert!(p.twist() == Angle::degrees(42.));
}
#[test]
fn from_tilt() {
let p =
PointerOrientation::from_tilt_and_twist(Angle::zero(), Angle::zero(), Angle::default());
assert!(p.azimuth() == Angle::zero());
assert!(p.altitude() == Angle::frac_pi_2());
assert!(p.tilt_x() == Angle::zero());
assert!(p.tilt_y() == Angle::zero());
let p = PointerOrientation::from_tilt_and_twist(
Angle::frac_pi_4(),
Angle::zero(),
Angle::default(),
);
assert!(p.azimuth() == Angle::zero());
assert!(p.altitude() == Angle::frac_pi_4());
assert!(p.tilt_x() == Angle::frac_pi_4());
assert!(p.tilt_y() == Angle::zero());
let p = PointerOrientation::from_tilt_and_twist(
Angle::zero(),
Angle::frac_pi_4(),
Angle::default(),
);
assert!(p.azimuth() == Angle::frac_pi_2());
assert!(p.altitude() == Angle::frac_pi_4());
assert!(p.tilt_x() == Angle::zero());
assert!(p.tilt_y() == Angle::frac_pi_4());
let p = PointerOrientation::from_tilt_and_twist(
-Angle::frac_pi_4(),
Angle::zero(),
Angle::default(),
);
assert!(p.azimuth() == Angle::pi());
assert!(p.altitude() == Angle::frac_pi_4());
assert!(p.tilt_x() == -Angle::frac_pi_4());
assert!(p.tilt_y() == Angle::zero());
let p = PointerOrientation::from_tilt_and_twist(
Angle::degrees(70.),
Angle::degrees(30.),
Angle::default(),
);
assert!(p.altitude().to_degrees().approx_eq_eps(&19.61, &0.1));
assert!(p.azimuth().to_degrees().approx_eq_eps(&11.17, &1.));
}
#[test]
fn from_spherical() {
let p = PointerOrientation::from_spherical_and_twist(
Angle::frac_pi_4(),
Angle::zero(),
Angle::default(),
);
assert!(p.tilt_x().approx_eq(&Angle::frac_pi_4()));
assert!(p.tilt_y().approx_eq(&Angle::zero()));
let p = PointerOrientation::from_spherical_and_twist(
Angle::frac_pi_4(),
Angle::frac_pi_2(),
Angle::default(),
);
assert!(p.tilt_x().approx_eq(&Angle::zero()));
assert!(p.tilt_y().approx_eq(&Angle::frac_pi_4()));
let p = PointerOrientation::from_spherical_and_twist(
Angle::frac_pi_4(),
Angle::pi(),
Angle::default(),
);
assert!(p.tilt_x().approx_eq(&-Angle::frac_pi_4()));
assert!(p.tilt_y().approx_eq(&Angle::zero()));
let p = PointerOrientation::from_spherical_and_twist(
Angle::degrees(19.61),
Angle::degrees(11.17),
Angle::default(),
);
assert!(p.tilt_x().to_degrees().approx_eq_eps(&70., &0.1));
assert!(p.tilt_y().to_degrees().approx_eq_eps(&30., &2.));
}
}
fn spherical_to_tilt(altitude: Angle<f64>, azimuth: Angle<f64>) -> (Angle<f64>, Angle<f64>) {
use std::f64::consts::{FRAC_PI_2 as HALF_PI, PI};
const THREE_HALF_PI: f64 = PI / 2. * 3.;
const TWO_PI: f64 = 2. * PI;
if altitude == Angle::zero() {
let a = azimuth.radians;
let (tilt_x, tilt_y) = if a == 0. || a == TWO_PI {
(HALF_PI, 0.)
} else if 0. < a && a < HALF_PI {
(HALF_PI, HALF_PI)
} else if a == HALF_PI {
(0., HALF_PI)
} else if HALF_PI < a && a < PI {
(-HALF_PI, HALF_PI)
} else if a == PI {
(-HALF_PI, 0.)
} else if PI < a && a < THREE_HALF_PI {
(-HALF_PI, -HALF_PI)
} else if a == THREE_HALF_PI {
(0., -HALF_PI)
} else if THREE_HALF_PI < a && a < TWO_PI {
(HALF_PI, -HALF_PI)
} else {
panic!("invalid value for azimuth {azimuth:?}")
};
(Angle::radians(tilt_x), Angle::radians(tilt_y))
} else {
let altitude_tan = altitude.radians.tan();
let (azimuth_sin, azimuth_cos) = azimuth.sin_cos();
let tilt_x = (azimuth_cos / altitude_tan).atan();
let tilt_y = (azimuth_sin / altitude_tan).atan();
(Angle::radians(tilt_x), Angle::radians(tilt_y))
}
}
fn tilt_to_spherical(tilt_x: Angle<f64>, tilt_y: Angle<f64>) -> (Angle<f64>, Angle<f64>) {
use std::f64::consts::{FRAC_PI_2 as HALF_PI, PI};
const THREE_HALF_PI: f64 = PI / 2. * 3.;
const TWO_PI: f64 = 2. * PI;
let x = tilt_x.radians;
let y = tilt_y.radians;
let azimuth = if x == 0. {
if y < 0. {
THREE_HALF_PI
} else if y > 0. {
HALF_PI
} else {
0.
}
} else if y == 0. {
if x < 0. {
PI
} else {
0.
}
} else if x.abs() == HALF_PI || y.abs() == HALF_PI {
0.
} else {
let tan_x = x.tan();
let tan_y = y.tan();
let a = tan_y.atan2(tan_x);
if a < 0. {
a + TWO_PI
} else {
a
}
};
let altitude = if x.abs() == HALF_PI || y.abs() == HALF_PI {
0.
} else if x == 0. {
HALF_PI - y.abs()
} else if y == 0. {
HALF_PI - x.abs()
} else {
let x_tan_square = x.tan().powi(2);
let y_tan_square = y.tan().powi(2);
(1. / (x_tan_square + y_tan_square).sqrt()).atan()
};
(Angle::radians(altitude), Angle::radians(azimuth))
}