use std::sync::{Arc, Mutex};
use crate::geometry::{PhysicalSize, ScaleFactor};
use crate::platform::ControlFlow;
use crate::platform::event::{Event, WindowId};
use crate::platform::window::{Window, WindowAttributes};
use crate::render::{Pixmap, Surface};
#[derive(Clone, Debug, Default)]
pub struct FrameProbe {
last: Arc<Mutex<Option<Pixmap>>>,
}
impl FrameProbe {
pub fn last_frame(&self) -> Option<Pixmap> {
self.last.lock().unwrap().clone()
}
pub fn present_count(&self) -> usize {
usize::from(self.last.lock().unwrap().is_some())
}
}
#[derive(Debug)]
pub struct HeadlessSurface {
size: PhysicalSize,
sink: Arc<Mutex<Option<Pixmap>>>,
}
impl HeadlessSurface {
pub fn new(size: PhysicalSize) -> (Self, FrameProbe) {
let sink = Arc::new(Mutex::new(None));
(
Self {
size,
sink: sink.clone(),
},
FrameProbe { last: sink },
)
}
}
impl Surface for HeadlessSurface {
fn resize(&mut self, size: PhysicalSize) {
self.size = size;
}
fn size(&self) -> PhysicalSize {
self.size
}
fn present(&mut self, pixmap: &Pixmap, _damage: &[crate::geometry::Rect]) {
*self.sink.lock().unwrap() = Some(pixmap.clone());
}
}
#[derive(Debug)]
pub struct HeadlessWindow {
id: WindowId,
size: PhysicalSize,
scale: ScaleFactor,
probe: FrameProbe,
}
impl HeadlessWindow {
fn new(attrs: &WindowAttributes) -> Self {
let scale = ScaleFactor::IDENTITY;
let size = scale.to_physical(attrs.logical_size);
Self {
id: WindowId(1),
size,
scale,
probe: FrameProbe::default(),
}
}
pub fn frame_probe(&self) -> FrameProbe {
self.probe.clone()
}
}
impl Window for HeadlessWindow {
fn id(&self) -> WindowId {
self.id
}
fn inner_size(&self) -> PhysicalSize {
self.size
}
fn scale_factor(&self) -> ScaleFactor {
self.scale
}
fn request_redraw(&self) {}
fn set_title(&self, _title: &str) {}
fn create_surface(&self) -> Box<dyn Surface> {
let surface = HeadlessSurface {
size: self.size,
sink: self.probe.last.clone(),
};
Box::new(surface)
}
}
pub fn run<H>(
attrs: WindowAttributes,
events: impl IntoIterator<Item = Event>,
mut handler: H,
) -> HeadlessWindow
where
H: FnMut(Event, &dyn Window) -> ControlFlow,
{
let window = HeadlessWindow::new(&attrs);
for event in events {
if matches!(handler(event, &window), ControlFlow::Exit) {
break;
}
}
window
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Size;
#[test]
fn surface_retains_last_frame() {
let (mut surface, probe) = HeadlessSurface::new(PhysicalSize::new(4, 4));
assert!(probe.last_frame().is_none());
let pm = Pixmap::new(PhysicalSize::new(4, 4));
surface.present(&pm, &[]);
assert_eq!(probe.last_frame().unwrap().size(), PhysicalSize::new(4, 4));
}
#[test]
fn run_stops_on_exit() {
let attrs = WindowAttributes::new().with_logical_size(Size::new(10.0, 10.0));
let mut seen = 0;
run(
attrs,
[
Event::RedrawRequested,
Event::CloseRequested,
Event::RedrawRequested,
],
|ev, _w| {
seen += 1;
if matches!(ev, Event::CloseRequested) {
ControlFlow::Exit
} else {
ControlFlow::Wait
}
},
);
assert_eq!(seen, 2); }
}