use slotmap::{new_key_type, SlotMap};
use crate::dpi::DpiScale;
use crate::{Window, WindowId};
new_key_type! {
pub struct WindowKey;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WindowEventOutcome {
None,
CloseRequested,
Destroyed,
ScaleFactorChanged(f64),
RedrawRequested,
}
#[derive(Debug)]
pub struct WindowEntry {
pub window: Box<dyn Window>,
pub dpi_scale: f64,
pub id: WindowId,
}
impl WindowEntry {
#[must_use]
pub fn dpi(&self) -> DpiScale {
DpiScale::new(self.dpi_scale)
}
}
#[derive(Debug)]
pub struct WindowManager {
windows: SlotMap<WindowKey, WindowEntry>,
}
impl WindowManager {
#[must_use]
pub fn new() -> Self {
Self {
windows: SlotMap::with_key(),
}
}
pub fn create_window(
&mut self,
event_loop: &dyn winit::event_loop::ActiveEventLoop,
attributes: winit::window::WindowAttributes,
) -> Result<WindowKey, winit::error::RequestError> {
let window = event_loop.create_window(attributes)?;
let id = window.id();
let raw_scale = window.scale_factor();
let dpi_scale = if DpiScale::is_valid(raw_scale) {
raw_scale
} else {
1.0
};
let key = self.windows.insert(WindowEntry {
window,
dpi_scale,
id,
});
Ok(key)
}
pub fn destroy_window(&mut self, key: WindowKey) -> Option<WindowEntry> {
self.windows.remove(key)
}
#[must_use]
pub fn get_window(&self, key: WindowKey) -> Option<&WindowEntry> {
self.windows.get(key)
}
#[must_use]
pub fn get_window_mut(&mut self, key: WindowKey) -> Option<&mut WindowEntry> {
self.windows.get_mut(key)
}
pub fn iter_windows(&self) -> impl Iterator<Item = (WindowKey, &WindowEntry)> {
self.windows.iter()
}
pub fn iter_windows_mut(&mut self) -> impl Iterator<Item = (WindowKey, &mut WindowEntry)> {
self.windows.iter_mut()
}
#[must_use]
pub fn window_count(&self) -> usize {
self.windows.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.windows.is_empty()
}
#[must_use]
pub fn key_for_id(&self, id: WindowId) -> Option<WindowKey> {
self.windows
.iter()
.find(|(_, entry)| entry.id == id)
.map(|(key, _)| key)
}
pub fn handle_window_event(
&mut self,
id: WindowId,
event: &winit::event::WindowEvent,
) -> WindowEventOutcome {
use winit::event::WindowEvent;
let Some(key) = self.key_for_id(id) else {
return WindowEventOutcome::None;
};
match event {
WindowEvent::CloseRequested => WindowEventOutcome::CloseRequested,
WindowEvent::Destroyed => {
self.destroy_window(key);
WindowEventOutcome::Destroyed
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
if let Some(entry) = self.get_window_mut(key) {
if DpiScale::is_valid(*scale_factor) {
entry.dpi_scale = *scale_factor;
}
}
WindowEventOutcome::ScaleFactorChanged(*scale_factor)
}
WindowEvent::RedrawRequested => WindowEventOutcome::RedrawRequested,
_ => WindowEventOutcome::None,
}
}
}
impl Default for WindowManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{WindowEventOutcome, WindowManager};
use crate::dpi::DpiScale;
#[derive(Debug, Clone, Copy, PartialEq)]
struct FakeEntry {
id: u64,
dpi_scale: f64,
}
#[derive(Debug, Default)]
struct FakeManager {
windows: slotmap::SlotMap<super::WindowKey, FakeEntry>,
}
impl FakeManager {
fn new() -> Self {
Self {
windows: slotmap::SlotMap::with_key(),
}
}
fn insert(&mut self, id: u64, dpi_scale: f64) -> super::WindowKey {
self.windows.insert(FakeEntry { id, dpi_scale })
}
fn remove(&mut self, key: super::WindowKey) -> Option<FakeEntry> {
self.windows.remove(key)
}
fn get(&self, key: super::WindowKey) -> Option<&FakeEntry> {
self.windows.get(key)
}
fn get_mut(&mut self, key: super::WindowKey) -> Option<&mut FakeEntry> {
self.windows.get_mut(key)
}
fn len(&self) -> usize {
self.windows.len()
}
fn is_empty(&self) -> bool {
self.windows.is_empty()
}
fn iter(&self) -> impl Iterator<Item = (super::WindowKey, &FakeEntry)> {
self.windows.iter()
}
fn key_for_id(&self, id: u64) -> Option<super::WindowKey> {
self.windows
.iter()
.find(|(_, entry)| entry.id == id)
.map(|(key, _)| key)
}
}
#[test]
fn new_manager_is_empty() {
let mgr = WindowManager::new();
assert!(mgr.is_empty());
assert_eq!(mgr.window_count(), 0);
}
#[test]
fn default_equals_new() {
let a = WindowManager::new();
let b = WindowManager::default();
assert_eq!(a.window_count(), b.window_count());
assert!(a.is_empty() && b.is_empty());
}
#[test]
fn slotmap_mechanics_insert_and_get() {
let mut mgr = FakeManager::new();
assert!(mgr.is_empty());
let k0 = mgr.insert(10, 1.0);
let k1 = mgr.insert(20, 2.0);
assert_ne!(k0, k1);
assert_eq!(mgr.len(), 2);
assert!(!mgr.is_empty());
assert_eq!(mgr.get(k0).map(|e| e.id), Some(10));
assert_eq!(mgr.get(k1).map(|e| e.id), Some(20));
}
#[test]
fn slotmap_mechanics_remove_returns_entry() {
let mut mgr = FakeManager::new();
let k = mgr.insert(42, 1.5);
assert_eq!(mgr.len(), 1);
let removed = mgr.remove(k);
assert_eq!(removed.map(|e| (e.id, e.dpi_scale)), Some((42, 1.5)));
assert_eq!(mgr.len(), 0);
assert!(mgr.get(k).is_none());
}
#[test]
fn slotmap_mechanics_remove_stale_key_returns_none() {
let mut mgr = FakeManager::new();
let k = mgr.insert(1, 1.0);
let _ = mgr.remove(k);
assert!(mgr.remove(k).is_none());
}
#[test]
fn slotmap_mechanics_get_mut_updates_dpi() {
let mut mgr = FakeManager::new();
let k = mgr.insert(7, 1.0);
{
let entry = mgr.get_mut(k).expect("just-inserted key must resolve");
entry.dpi_scale = 1.5;
}
assert_eq!(mgr.get(k).map(|e| e.dpi_scale), Some(1.5));
}
#[test]
fn slotmap_mechanics_key_for_id_resolves() {
let mut mgr = FakeManager::new();
let k0 = mgr.insert(100, 1.0);
let k1 = mgr.insert(200, 2.0);
assert_eq!(mgr.key_for_id(100), Some(k0));
assert_eq!(mgr.key_for_id(200), Some(k1));
assert_eq!(mgr.key_for_id(999), None);
}
#[test]
fn slotmap_mechanics_iter_visits_all() {
let mut mgr = FakeManager::new();
let k0 = mgr.insert(1, 1.0);
let k1 = mgr.insert(2, 2.0);
let k2 = mgr.insert(3, 3.0);
let mut seen: Vec<(super::WindowKey, u64)> = mgr.iter().map(|(k, e)| (k, e.id)).collect();
seen.sort_by_key(|(_, id)| *id);
assert_eq!(seen, vec![(k0, 1), (k1, 2), (k2, 3)],);
}
#[test]
fn slotmap_mechanics_remove_makes_key_stale_but_others_survive() {
let mut mgr = FakeManager::new();
let k0 = mgr.insert(1, 1.0);
let k1 = mgr.insert(2, 2.0);
assert!(mgr.remove(k0).is_some());
assert!(mgr.get(k0).is_none(), "removed key should be stale");
assert!(
mgr.get(k1).is_some(),
"unrelated key must remain valid after a removal",
);
assert_eq!(mgr.len(), 1);
}
#[test]
fn window_entry_dpi_returns_scale_view() {
let scale = DpiScale::new(2.0);
assert_eq!(scale.to_physical(100.0), 200.0);
assert_eq!(scale.to_logical(200.0), 100.0);
}
#[test]
fn window_event_outcome_variants_are_distinct() {
assert_ne!(WindowEventOutcome::None, WindowEventOutcome::CloseRequested);
assert_ne!(
WindowEventOutcome::CloseRequested,
WindowEventOutcome::Destroyed,
);
assert_ne!(
WindowEventOutcome::ScaleFactorChanged(1.0),
WindowEventOutcome::ScaleFactorChanged(2.0),
);
assert_eq!(
WindowEventOutcome::RedrawRequested,
WindowEventOutcome::RedrawRequested,
);
}
#[test]
#[ignore = "requires a running winit event loop and a windowing system"]
fn create_and_destroy_real_window() {
use winit::application::ApplicationHandler;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::WindowAttributes;
struct App {
mgr: WindowManager,
done: bool,
}
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let attrs = WindowAttributes::default().with_title("martensite-window test");
let key = self
.mgr
.create_window(event_loop, attrs)
.expect("window creation should succeed on a live event loop");
assert_eq!(self.mgr.window_count(), 1);
assert!(self.mgr.get_window(key).is_some());
assert!(self.mgr.destroy_window(key).is_some());
assert_eq!(self.mgr.window_count(), 0);
self.done = true;
event_loop.exit();
}
fn window_event(
&mut self,
_event_loop: &dyn ActiveEventLoop,
_id: winit::window::WindowId,
_event: winit::event::WindowEvent,
) {
}
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.done {
event_loop.exit();
}
}
}
let event_loop = EventLoop::new().expect("event loop creation should succeed");
let app = App {
mgr: WindowManager::new(),
done: false,
};
event_loop
.run_app(app)
.expect("event loop should run cleanly");
}
}