use core::fmt;
use std::collections::VecDeque;
use crate::PhysicalRect;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MonitorId(pub u32);
impl fmt::Display for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "monitor {}", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MonitorInfo {
pub id: MonitorId,
pub name: String,
pub bounds: PhysicalRect,
pub work_area: PhysicalRect,
pub scale_factor: f64,
pub refresh_millihertz: u32,
pub is_primary: bool,
}
impl MonitorInfo {
#[inline]
#[must_use]
pub fn refresh_hz(&self) -> f64 {
f64::from(self.refresh_millihertz) / 1000.0
}
#[must_use]
pub fn frame_period(&self) -> Option<core::time::Duration> {
if self.refresh_millihertz == 0 {
return None;
}
Some(core::time::Duration::from_secs_f64(
1000.0 / f64::from(self.refresh_millihertz),
))
}
#[inline]
#[must_use]
pub const fn size(&self) -> crate::PhysicalSize {
self.bounds.size()
}
}
pub(crate) fn queue_removed(
old: &[MonitorInfo],
new: &[MonitorInfo],
queue: &mut VecDeque<crate::ShellEvent>,
) -> Vec<MonitorId> {
let removed: Vec<MonitorId> = old
.iter()
.map(|monitor| monitor.id)
.filter(|id| !new.iter().any(|monitor| monitor.id == *id))
.collect();
queue.extend(
removed
.iter()
.map(|&monitor| crate::ShellEvent::MonitorRemoved { monitor }),
);
removed
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PhysicalSize;
fn monitor() -> MonitorInfo {
MonitorInfo {
id: MonitorId(1),
name: "DP-2".to_string(),
bounds: PhysicalRect::new(0, 0, 3840, 2160),
work_area: PhysicalRect::new(0, 32, 3840, 2128),
scale_factor: 1.5,
refresh_millihertz: 143_998,
is_primary: true,
}
}
#[test]
fn refresh_rates_that_are_not_integers_survive() {
let mut info = monitor();
info.refresh_millihertz = 59_940;
assert!((info.refresh_hz() - 59.94).abs() < 1e-9);
let period = info.frame_period().expect("known rate");
assert!((period.as_secs_f64() - 1.0 / 59.94).abs() < 1e-9);
assert!((monitor().refresh_hz() - 143.998).abs() < 1e-9);
}
#[test]
fn an_unknown_refresh_rate_is_absent_not_zero_hz() {
let mut info = monitor();
info.refresh_millihertz = 0;
assert_eq!(info.frame_period(), None);
assert_eq!(info.refresh_hz(), 0.0);
}
#[test]
fn a_removal_is_an_id_the_new_list_lacks() {
let with_id = |id| MonitorInfo {
id: MonitorId(id),
..monitor()
};
let mut queue = VecDeque::new();
let removed = queue_removed(
&[with_id(1), with_id(2), with_id(3)],
&[with_id(2), with_id(4)],
&mut queue,
);
assert_eq!(removed, [MonitorId(1), MonitorId(3)]);
assert_eq!(
Vec::from(queue),
[1, 3].map(|id| crate::ShellEvent::MonitorRemoved {
monitor: MonitorId(id)
})
);
let mut queue = VecDeque::new();
let mut moved = with_id(1);
moved.bounds = PhysicalRect::new(100, 0, 3840, 2160);
assert!(queue_removed(&[with_id(1)], &[moved], &mut queue).is_empty());
assert!(queue.is_empty());
}
#[test]
fn geometry_accessors_agree_with_the_bounds() {
let info = monitor();
assert_eq!(info.size(), PhysicalSize::new(3840, 2160));
assert_eq!(info.work_area.size(), PhysicalSize::new(3840, 2128));
assert_eq!(info.id.to_string(), "monitor 1");
assert_eq!(
monitor().frame_period().map(|period| period.as_millis()),
Some(6)
);
}
}