nightshade 0.14.1

A cross-platform data-oriented game engine.
Documentation
/// Snapshot of a single monitor as known to the engine.
///
/// `position` is the monitor's top-left corner in winit's virtual desktop
/// space, in physical pixels. `size` is the monitor's resolution in
/// physical pixels. `scale_factor` is the OS-reported DPI scale at the
/// moment the snapshot was captured.
#[derive(Clone, Debug, Default)]
pub struct Monitor {
    pub index: usize,
    pub name: Option<String>,
    pub position: (i32, i32),
    pub size: (u32, u32),
    pub scale_factor: f32,
    pub refresh_rate_millihertz: Option<u32>,
    pub is_primary: bool,
}

/// Engine-wide registry of every monitor reported by the OS, plus the
/// last-known monitor of every window so windows can be restored to
/// the same display across sessions.
#[derive(Clone, Debug, Default)]
pub struct Monitors {
    pub monitors: Vec<Monitor>,
    pub primary_index: Option<usize>,
}

impl Monitor {
    pub fn min_x(&self) -> i32 {
        self.position.0
    }

    pub fn min_y(&self) -> i32 {
        self.position.1
    }

    pub fn max_x(&self) -> i32 {
        self.position.0 + self.size.0 as i32
    }

    pub fn max_y(&self) -> i32 {
        self.position.1 + self.size.1 as i32
    }

    pub fn contains_point(&self, x: i32, y: i32) -> bool {
        x >= self.min_x() && x < self.max_x() && y >= self.min_y() && y < self.max_y()
    }
}

impl Monitors {
    pub fn primary(&self) -> Option<&Monitor> {
        self.primary_index
            .and_then(|index| self.monitors.iter().find(|monitor| monitor.index == index))
    }

    pub fn at_index(&self, index: usize) -> Option<&Monitor> {
        self.monitors.iter().find(|monitor| monitor.index == index)
    }

    pub fn at_position(&self, x: i32, y: i32) -> Option<&Monitor> {
        self.monitors
            .iter()
            .find(|monitor| monitor.contains_point(x, y))
    }
}

/// Reads the OS monitor list from winit and writes it into `Monitors`.
/// The engine refreshes automatically at app start and on every
/// window `Resized` and `ScaleFactorChanged` event, which catches the
/// common monitor-config changes (resolution change, monitor
/// hotplug surfaces as a window resize, window crossing displays).
/// Apps can also call this directly if they have an out-of-band
/// signal that the display layout changed.
pub fn refresh_monitors_from_event_loop(
    monitors: &mut Monitors,
    event_loop: &winit::event_loop::ActiveEventLoop,
) {
    monitors.monitors.clear();
    let primary_handle = event_loop.primary_monitor();
    let primary_handle_ref = primary_handle.as_ref();
    let mut primary_index = None;
    for (index, handle) in event_loop.available_monitors().enumerate() {
        let position = handle.position();
        let size = handle.size();
        let is_primary = primary_handle_ref.is_some_and(|primary| primary == &handle);
        if is_primary {
            primary_index = Some(index);
        }
        monitors.monitors.push(Monitor {
            index,
            name: handle.name(),
            position: (position.x, position.y),
            size: (size.width, size.height),
            scale_factor: handle.scale_factor() as f32,
            refresh_rate_millihertz: handle.refresh_rate_millihertz(),
            is_primary,
        });
    }
    monitors.primary_index = primary_index;
}