pub mod input;
pub mod time;
use crate::{input::Input, time::FrameTimer};
use std::sync::Arc;
pub use winit::{event::WindowEvent, window::Window};
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event::MouseScrollDelta,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProxy},
window::WindowId,
};
pub struct AppConfig {
pub title: String,
pub width: Option<u32>,
pub height: Option<u32>,
pub resizable: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
title: "Egor App".to_string(),
width: None,
height: None,
resizable: true,
}
}
}
#[allow(async_fn_in_trait)]
pub trait AppHandler<R> {
fn on_window_event(&mut self, _window: &Window, _event: &WindowEvent) {}
async fn with_resource(&mut self, _window: Arc<Window>) -> R;
fn on_ready(&mut self, _window: &Window, _resource: &mut R) {}
fn frame(&mut self, _window: &Window, _resource: &mut R, _input: &Input, _timer: &FrameTimer) {}
fn resize(&mut self, _w: u32, _h: u32, _resource: &mut R) {}
}
pub struct AppRunner<R: 'static, H: AppHandler<R> + 'static> {
handler: Option<H>,
resource: Option<R>,
window: Option<Arc<Window>>,
proxy: Option<EventLoopProxy<(R, H)>>,
input: Input,
timer: FrameTimer,
config: AppConfig,
}
#[doc(hidden)]
impl<R, H: AppHandler<R> + 'static> ApplicationHandler<(R, H)> for AppRunner<R, H> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let Some(proxy) = self.proxy.take() else {
return;
};
let mut win_attrs = Window::default_attributes()
.with_visible(false)
.with_title(&self.config.title)
.with_resizable(self.config.resizable);
if let (Some(w), Some(h)) = (self.config.width, self.config.height) {
win_attrs = win_attrs.with_inner_size(PhysicalSize::new(w, h));
}
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowAttributesExtWebSys;
win_attrs = win_attrs.with_append(true);
}
let window = Arc::new(event_loop.create_window(win_attrs).unwrap());
self.window = Some(window.clone());
let mut handler = self.handler.take().unwrap();
#[cfg(target_arch = "wasm32")]
{
wasm_bindgen_futures::spawn_local(async move {
let resource = handler.with_resource(window).await;
_ = proxy.send_event((resource, handler));
});
}
#[cfg(not(target_arch = "wasm32"))]
{
let resource = pollster::block_on(handler.with_resource(window));
_ = proxy.send_event((resource, handler));
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
if let Some(handler) = &mut self.handler {
handler.on_window_event(self.window.as_ref().unwrap(), &event);
}
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
let Some(window) = &self.window else { return };
let (Some(resource), Some(handler)) = (&mut self.resource, &mut self.handler)
else {
return;
};
self.timer.update();
handler.frame(window, resource, &self.input, &self.timer);
self.input.end_frame();
window.request_redraw();
}
WindowEvent::Resized(size) => {
if size.width == 0 || size.height == 0 {
return;
}
if let (Some(resource), Some(handler)) =
(self.resource.as_mut(), self.handler.as_mut())
{
handler.resize(size.width, size.height, resource);
}
}
WindowEvent::KeyboardInput { event, .. } => self.input.update_key(event),
WindowEvent::MouseInput { button, state, .. } => {
self.input.update_mouse_button(button, state)
}
WindowEvent::CursorMoved { position, .. } => self.input.update_cursor(position),
WindowEvent::MouseWheel { delta, .. } => {
let wheel_delta = match delta {
MouseScrollDelta::LineDelta(_, y) => y,
MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 100.0,
};
self.input.update_scroll(wheel_delta);
}
_ => {}
}
}
fn user_event(&mut self, _: &ActiveEventLoop, (mut resource, mut handler): (R, H)) {
let Some(window) = &self.window else { return };
handler.on_ready(window, &mut resource);
handler.frame(window, &mut resource, &self.input, &self.timer);
window.set_visible(true);
window.request_redraw();
self.resource = Some(resource);
self.handler = Some(handler);
}
}
impl<R, H: AppHandler<R> + 'static> AppRunner<R, H> {
pub fn new(handler: H, config: AppConfig) -> Self {
Self {
handler: Some(handler),
resource: None,
window: None,
proxy: None,
input: Input::default(),
timer: FrameTimer::default(),
config,
}
}
pub fn run(mut self) {
let event_loop = EventLoop::<(R, H)>::with_user_event().build().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
self.proxy = Some(event_loop.create_proxy());
#[cfg(target_arch = "wasm32")]
{
#[cfg(feature = "log")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Error).unwrap();
}
use winit::platform::web::EventLoopExtWebSys;
wasm_bindgen_futures::spawn_local(async move {
event_loop.spawn_app(self);
});
}
#[cfg(not(target_arch = "wasm32"))]
{
#[cfg(feature = "log")]
env_logger::init_from_env(env_logger::Env::default().default_filter_or("error"));
event_loop.run_app(&mut self).unwrap();
}
}
}