use pixelcoords_core::session::MonitorRecord;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Space {
Auto,
Physical,
Logical,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ResolvedPoint {
pub x: f64,
pub y: f64,
pub space: Space,
pub monitor: usize,
pub scale: f64,
}
pub const fn native_space() -> Space {
if cfg!(target_os = "macos") {
return Space::Logical;
}
Space::Physical
}
pub fn monitor_at(
monitors: &[MonitorRecord],
global_x: i32,
global_y: i32,
) -> Option<&MonitorRecord> {
monitors.iter().find(|m| {
let right = m.origin_px.x + m.size_px.w;
let bottom = m.origin_px.y + m.size_px.h;
global_x >= m.origin_px.x
&& global_x < right
&& global_y >= m.origin_px.y
&& global_y < bottom
})
}
pub fn to_space(
monitors: &[MonitorRecord],
global_x: i32,
global_y: i32,
space: Space,
) -> Option<ResolvedPoint> {
let monitor = monitor_at(monitors, global_x, global_y)?;
let resolved = match space {
Space::Auto => native_space(),
other => other,
};
let divisor = match resolved {
Space::Logical => monitor.scale,
_ => 1.0,
};
Some(ResolvedPoint {
x: f64::from(global_x) / divisor,
y: f64::from(global_y) / divisor,
space: resolved,
monitor: monitor.index,
scale: monitor.scale,
})
}
pub fn near_screen_corner(
monitors: &[MonitorRecord],
space: Space,
x: f64,
y: f64,
margin: f64,
) -> bool {
corners(monitors, space)
.iter()
.any(|corner| (corner.x - x).abs() <= margin && (corner.y - y).abs() <= margin)
}
pub fn corners(monitors: &[MonitorRecord], space: Space) -> Vec<ResolvedPoint> {
let mut corners = Vec::with_capacity(monitors.len() * 4);
for monitor in monitors {
let left = monitor.origin_px.x;
let top = monitor.origin_px.y;
let right = left + monitor.size_px.w - 1;
let bottom = top + monitor.size_px.h - 1;
for (x, y) in [(left, top), (right, top), (left, bottom), (right, bottom)] {
if let Some(point) = to_space(monitors, x, y, space) {
corners.push(point);
}
}
}
corners
}
#[cfg(test)]
mod tests {
use pixelcoords_core::geometry::{Point, Size};
use super::*;
fn monitor(index: usize, origin: (i32, i32), size: (i32, i32), scale: f64) -> MonitorRecord {
MonitorRecord {
index,
name: format!("display {index}"),
primary: index == 0,
origin_px: Point::new(origin.0, origin.1),
size_px: Size::new(size.0, size.1),
scale,
}
}
#[test]
fn every_monitor_contributes_four_corners() {
let monitors = retina_plus_external();
assert_eq!(
corners(&monitors, Space::Physical).len(),
monitors.len() * 4
);
}
#[test]
fn a_cursor_slammed_into_a_corner_trips_the_kill_switch() {
let monitors = vec![monitor(0, (0, 0), (3024, 1964), 2.0)];
assert!(near_screen_corner(
&monitors,
Space::Logical,
1511.5,
981.5,
10.0
));
assert!(near_screen_corner(
&monitors,
Space::Logical,
0.0,
0.0,
10.0
));
assert!(near_screen_corner(
&monitors,
Space::Logical,
1503.0,
8.0,
10.0
));
}
#[test]
fn the_middle_of_the_screen_is_not_a_corner() {
let monitors = vec![monitor(0, (0, 0), (3024, 1964), 2.0)];
assert!(!near_screen_corner(
&monitors,
Space::Logical,
700.0,
500.0,
10.0
));
assert!(!near_screen_corner(
&monitors,
Space::Logical,
700.0,
0.0,
10.0
));
}
#[test]
fn a_corner_of_any_monitor_counts_not_just_the_primary() {
let monitors = retina_plus_external();
let external = &monitors[1];
let corner = to_space(
&monitors,
external.origin_px.x + external.size_px.w - 1,
external.origin_px.y + external.size_px.h - 1,
Space::Physical,
)
.expect("a monitor contains its own corner");
assert!(near_screen_corner(
&monitors,
Space::Physical,
corner.x,
corner.y,
2.0
));
}
fn retina_plus_external() -> Vec<MonitorRecord> {
vec![
monitor(0, (0, 0), (3024, 1964), 2.0),
monitor(1, (3024, 0), (1920, 1080), 1.0),
]
}
#[test]
fn logical_conversion_divides_by_the_containing_monitors_scale() {
let monitors = retina_plus_external();
let point = to_space(&monitors, 1624, 880, Space::Logical).expect("inside monitor 0");
assert_eq!(point.monitor, 0);
assert!((point.x - 812.0).abs() < f64::EPSILON);
assert!((point.y - 440.0).abs() < f64::EPSILON);
}
#[test]
fn a_mixed_dpi_layout_converts_per_monitor_not_per_desktop() {
let monitors = retina_plus_external();
let point = to_space(&monitors, 4000, 500, Space::Logical).expect("inside monitor 1");
assert_eq!(point.monitor, 1);
assert!((point.x - 4000.0).abs() < f64::EPSILON);
assert!((point.y - 500.0).abs() < f64::EPSILON);
}
#[test]
fn physical_space_is_the_identity() {
let monitors = retina_plus_external();
let point = to_space(&monitors, 1624, 880, Space::Physical).expect("inside monitor 0");
assert!((point.x - 1624.0).abs() < f64::EPSILON);
assert!((point.y - 880.0).abs() < f64::EPSILON);
assert_eq!(point.space, Space::Physical);
}
#[test]
fn a_point_in_a_gap_between_monitors_resolves_to_nothing() {
let monitors = vec![
monitor(0, (0, 0), (1920, 1080), 1.0),
monitor(1, (2000, 0), (1920, 1080), 1.0),
];
assert!(monitor_at(&monitors, 1960, 500).is_none());
assert!(to_space(&monitors, 1960, 500, Space::Auto).is_none());
}
#[test]
fn monitor_bounds_are_half_open_so_edges_belong_to_exactly_one() {
let monitors = retina_plus_external();
assert_eq!(monitor_at(&monitors, 3023, 0).expect("m0").index, 0);
assert_eq!(monitor_at(&monitors, 3024, 0).expect("m1").index, 1);
}
#[test]
fn auto_resolves_to_the_platforms_own_space() {
let monitors = retina_plus_external();
let point = to_space(&monitors, 1624, 880, Space::Auto).expect("inside monitor 0");
assert_eq!(point.space, native_space());
assert_ne!(point.space, Space::Auto);
}
#[test]
fn negative_origins_are_handled() {
let monitors = vec![
monitor(0, (0, 0), (1920, 1080), 1.0),
monitor(1, (-1920, 0), (1920, 1080), 2.0),
];
let point = to_space(&monitors, -960, 500, Space::Logical).expect("inside monitor 1");
assert_eq!(point.monitor, 1);
assert!((point.x - -480.0).abs() < f64::EPSILON);
}
}