use std::sync::Arc;
use game_loop::winit::{dpi::LogicalSize, window::WindowBuilder};
use miette::{IntoDiagnostic, Result};
use pixels::{wgpu::BlendState, PixelsBuilder, SurfaceTexture};
use vek::{Extent2, Vec2};
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, TouchPhase, VirtualKeyCode, WindowEvent,
},
event_loop::EventLoop,
};
use crate::input::Input;
pub async fn run<G, U, R>(
game_state: G,
size: Extent2<usize>,
updates_per_second: u32,
mut update: U,
mut render: R,
) -> Result<()>
where
G: 'static,
U: FnMut(&mut G, &Input) + 'static,
R: FnMut(&mut G, &mut [u32]) + 'static,
{
#[cfg(target_arch = "wasm32")]
let canvas = wasm::setup_canvas();
let event_loop = EventLoop::new();
let logical_size = LogicalSize::new(size.w as f64, size.h as f64);
#[allow(unused_mut)]
let mut window_builder = WindowBuilder::new()
.with_title("Sprite")
.with_inner_size(logical_size)
.with_min_inner_size(logical_size);
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowBuilderExtWebSys;
window_builder = window_builder.with_canvas(Some(canvas));
}
let window = window_builder.build(&event_loop).into_diagnostic()?;
let pixels = {
let surface_texture = SurfaceTexture::new(size.w as u32 * 2, size.h as u32 * 2, &window);
PixelsBuilder::new(size.w as u32, size.h as u32, surface_texture)
.clear_color(pixels::wgpu::Color::WHITE)
.blend_state(BlendState::REPLACE)
.build_async()
.await
}
.into_diagnostic()?;
#[cfg(target_arch = "wasm32")]
wasm::update_canvas(size);
let mut buffer = vec![0u32; size.w * size.h];
game_loop::game_loop(
event_loop,
Arc::new(window),
(game_state, pixels, Input::default()),
updates_per_second,
0.1,
move |g| {
update(&mut g.game.0, &g.game.2);
g.game.2.update();
},
move |g| {
render(&mut g.game.0, &mut buffer);
{
g.game
.1
.frame_mut()
.chunks_exact_mut(4)
.zip(buffer.iter())
.for_each(|(target, source)| {
let source = source.to_ne_bytes();
target[0] = source[2];
target[1] = source[1];
target[2] = source[0];
target[3] = source[3];
});
}
if let Err(err) = g.game.1.render() {
dbg!(err);
g.exit();
}
},
move |g, ev| {
match ev {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => g.exit(),
Event::WindowEvent {
event: WindowEvent::Resized(new_size),
..
} => {
g.game
.1
.resize_surface(new_size.width, new_size.height)
.into_diagnostic()
.unwrap();
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode,
state,
..
},
..
},
..
} => match virtual_keycode {
Some(VirtualKeyCode::Up | VirtualKeyCode::W) => {
g.game.2.up.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::Down | VirtualKeyCode::S) => {
g.game.2.down.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::Left | VirtualKeyCode::A) => {
g.game.2.left.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::Right | VirtualKeyCode::D) => {
g.game.2.right.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::Space) => {
g.game.2.space.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::R) => {
g.game.2.r.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::G) => {
g.game.2.g.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::C) => {
g.game.2.c.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::O) => {
g.game.2.o.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::N) => {
g.game.2.n.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::X) => {
g.game.2.x.handle_bool(state == &ElementState::Pressed)
}
Some(VirtualKeyCode::Escape) => g.exit(),
_ => (),
},
Event::WindowEvent {
event: WindowEvent::MouseInput { button, state, .. },
..
} => {
if *button == MouseButton::Left {
g.game
.2
.left_mouse
.handle_bool(*state == ElementState::Pressed);
} else if *button == MouseButton::Right {
g.game
.2
.right_mouse
.handle_bool(*state == ElementState::Pressed);
}
}
Event::WindowEvent {
event: WindowEvent::Touch(touch),
..
} => match touch.phase {
TouchPhase::Started => g.game.2.left_mouse.handle_bool(true),
TouchPhase::Moved => (),
TouchPhase::Ended | TouchPhase::Cancelled => {
g.game.2.left_mouse.handle_bool(false)
}
},
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
g.game.2.mouse_pos = g
.game
.1
.window_pos_to_pixel((position.x as f32, position.y as f32))
.map(|(x, y)| Vec2::new(x as i32, y as i32))
.unwrap_or_else(|(x, y)| Vec2::new(x as i32, y as i32))
}
_ => (),
}
},
);
}
#[cfg(target_arch = "wasm32")]
mod wasm {
use vek::Extent2;
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
pub fn setup_canvas() -> HtmlCanvasElement {
log::debug!("Binding window to HTML canvas");
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.style().set_css_text("text-align: center");
let canvas = document
.create_element("canvas")
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap();
canvas.set_id("canvas");
body.append_child(&canvas).unwrap();
let header = document.create_element("h2").unwrap();
header.set_text_content(Some("Sprite"));
body.append_child(&header).unwrap();
canvas
}
pub fn update_canvas(size: Extent2<usize>) {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
.get_element_by_id("canvas")
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap();
canvas.style().set_css_text(&format!(
"display:block; margin: auto; image-rendering: pixelated; width: {}px; height: {}px",
size.w * 2,
size.h * 2
));
canvas.set_width(size.w as u32 * 2);
canvas.set_height(size.h as u32 * 2);
}
}