#![deny(missing_docs)]
use std::collections::HashMap;
#[cfg(feature = "wgpu")]
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};
#[cfg(target_arch = "wasm32")]
use web_time::{Duration, Instant};
pub use gapp::{Render, Update};
#[cfg(feature = "opengl")]
#[cfg(not(target_arch = "wasm32"))]
use glutin::{
context::PossiblyCurrentContext,
prelude::*,
surface::{Surface, WindowSurface},
};
#[cfg(not(target_arch = "wasm32"))]
use winit::dpi::PhysicalSize;
use winit::{
event::WindowEvent,
event_loop::ActiveEventLoop,
window::{Window, WindowId},
};
pub trait WindowInput<C, R> {
fn input(
&mut self,
window_id: WindowId,
event: &WindowEvent,
event_loop: &ActiveEventLoop,
context: &mut C,
windows: &mut Windows<R>,
);
}
#[cfg(feature = "opengl")]
pub struct WindowData {
pub window: Window,
#[cfg(not(target_arch = "wasm32"))]
pub context: PossiblyCurrentContext,
#[cfg(not(target_arch = "wasm32"))]
pub surface: Surface<WindowSurface>,
}
#[cfg(feature = "wgpu")]
pub struct WindowData {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface_config: wgpu::SurfaceConfiguration,
pub surface: wgpu::Surface<'static>,
pub window: Arc<Window>,
}
pub struct Windows<R> {
map: HashMap<WindowId, (WindowData, R)>,
}
impl<R> Windows<R> {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn insert(&mut self, window_data: WindowData, renderer: R) -> WindowId {
let id = window_data.window.id();
self.map.insert(id, (window_data, renderer));
id
}
pub fn remove(&mut self, id: &WindowId) -> Option<(WindowData, R)> {
self.map.remove(id)
}
pub fn get(&self, id: &WindowId) -> Option<&(WindowData, R)> {
self.map.get(id)
}
pub fn get_mut(&mut self, id: &WindowId) -> Option<&mut (WindowData, R)> {
self.map.get_mut(id)
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&WindowId, &(WindowData, R))> {
self.map.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&WindowId, &mut (WindowData, R))> {
self.map.iter_mut()
}
}
impl<R> Default for Windows<R> {
fn default() -> Self {
Self::new()
}
}
pub trait Present<R> {
fn present(&self, renderer: &mut R, window_data: &WindowData);
}
type WindowFactory<R> = Box<dyn FnOnce(&ActiveEventLoop) -> Windows<R>>;
struct AppState<I, R, E> {
application: E,
timestep: Duration,
create_windows: Option<WindowFactory<R>>,
windows: Option<Windows<R>>,
input_context: I,
prev_time: Instant,
}
impl<I: 'static, R: 'static, E> winit::application::ApplicationHandler for AppState<I, R, E>
where
E: WindowInput<I, R> + Present<R> + Render<R> + Update,
{
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_some() {
return;
}
if let Some(create_windows) = self.create_windows.take() {
self.windows = Some(create_windows(event_loop));
self.prev_time = Instant::now();
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let Some(windows) = &mut self.windows else {
return;
};
#[cfg(not(target_arch = "wasm32"))]
if let WindowEvent::Resized(PhysicalSize { width, height }) = &event {
let width = (*width).max(1);
let height = (*height).max(1);
if let Some((window_data, _)) = windows.get_mut(&window_id) {
#[cfg(feature = "opengl")]
if let (Ok(non_zero_width), Ok(non_zero_height)) =
(width.try_into(), height.try_into())
{
window_data.surface.resize(
&window_data.context,
non_zero_width,
non_zero_height,
);
}
#[cfg(feature = "wgpu")]
{
window_data.surface_config.width = width;
window_data.surface_config.height = height;
window_data
.surface
.configure(&window_data.device, &window_data.surface_config);
}
}
}
if let WindowEvent::RedrawRequested = &event {
self.application.input(
window_id,
&event,
event_loop,
&mut self.input_context,
windows,
);
if let Some((window_data, renderer)) = windows.get_mut(&window_id) {
self.application.render(renderer);
self.application.present(renderer, window_data);
}
} else {
self.application.input(
window_id,
&event,
event_loop,
&mut self.input_context,
windows,
);
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let Some(windows) = &self.windows else {
return;
};
self.application.update(self.timestep.as_secs_f32());
#[cfg(not(target_arch = "wasm32"))]
{
let frame_duration = self.prev_time.elapsed();
if frame_duration < self.timestep {
std::thread::sleep(self.timestep - frame_duration);
}
}
for (_, (window_data, _)) in windows.iter() {
window_data.window.request_redraw();
}
self.prev_time = Instant::now();
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {}
}
pub fn run<
I: 'static,
R: 'static,
E: WindowInput<I, R> + Present<R> + Render<R> + Update + 'static,
>(
application: E,
event_loop: winit::event_loop::EventLoop<()>,
fps: u64,
create_windows: impl FnOnce(&ActiveEventLoop) -> Windows<R> + 'static,
input_context: I,
) {
let timestep = Duration::from_nanos(1_000_000_000 / fps);
let mut state = AppState {
application,
timestep,
create_windows: Some(Box::new(create_windows)),
windows: None,
input_context,
prev_time: Instant::now(),
};
let _ = event_loop.run_app(&mut state);
}