use super::decals;
use super::inputs::{self, Input, KeySet, Mouse, MouseBtn, MouseWheel};
use super::Sprite;
use pixel_engine_draw::traits::SmartDrawingTrait;
use px_draw::graphics::DrawingSprite;
use pixel_engine_draw::vector2::Vu2d;
use px_backend::winit::{
self,
event::{Event, WindowEvent},
};
#[derive(Debug)]
pub struct EngineWrapper(Option<Engine>);
impl std::ops::Deref for EngineWrapper {
type Target = Engine;
fn deref(&self) -> &Self::Target {
self.0.as_ref().expect("Pannic while deref EngineWrapper")
}
}
impl std::ops::DerefMut for EngineWrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
.as_mut()
.expect("Panic while deref Mut EngineWrapper")
}
}
impl EngineWrapper {
pub async fn new(title: String, size: (u32, u32, u32)) -> Self {
Self(Some(Engine::new(title, size).await))
}
#[cfg(not(target_arch = "wasm32"))]
#[must_use]
pub fn new_sync(title: String, size: (u32, u32, u32)) -> Self {
Self(Some(Engine::new_sync(title, size)))
}
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
pub fn run<F>(mut self, mut main_func: F) -> !
where
F: (FnMut(&mut Engine) -> Result<bool, Box<dyn std::error::Error>>) + 'static,
{
let mut engine = self.0.take().unwrap();
let mut force_exit = false;
let event_loop = engine.event_loop.take().unwrap();
let mut redraw = true;
let mut redraw_last_frame = false;
event_loop.run(move |e, _, control_flow| {
if redraw_last_frame {
for key in &engine.k_pressed {
engine.k_held.insert(*key);
}
engine.k_pressed.clear();
engine.k_released.clear();
for i in 0..3 {
if engine.mouse.buttons[i].released {
engine.mouse.buttons[i].released = false;
}
if engine.mouse.buttons[i].pressed {
engine.mouse.buttons[i].pressed = false;
engine.mouse.buttons[i].held = true;
}
}
engine.mouse.wheel = MouseWheel::None;
redraw_last_frame = false;
}
match e {
Event::WindowEvent {
event: e,
window_id,
} if window_id == engine.window.id() => match e {
WindowEvent::KeyboardInput { input: inp, .. } => {
if let Some(k) = inp.virtual_keycode {
if inp.state == winit::event::ElementState::Released {
engine.k_pressed.remove(&inputs::Key::from(inp));
engine.k_held.remove(&inputs::Key::from(inp));
engine.k_released.insert(inputs::Key::from(inp));
} else if !engine.k_held.has(k) {
engine.k_pressed.insert(inputs::Key::from(inp));
}
}
}
WindowEvent::CloseRequested => {
force_exit = true;
}
WindowEvent::CursorMoved { position, .. } => {
let (x, y): (f64, f64) = position.into();
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
{
engine.mouse.pos = (
(x / f64::from(engine.size.2)).trunc().abs() as u32,
(y / f64::from(engine.size.2)).trunc().abs() as u32,
);
};
}
WindowEvent::MouseWheel { delta, .. } => match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
engine.mouse.wheel = if x.abs() > y.abs() {
if x > 0.0 {
MouseWheel::Right
} else if x < 0.0 {
MouseWheel::Left
} else {
MouseWheel::None
}
} else if y > 0.0 {
MouseWheel::Down
} else if y < 0.0 {
MouseWheel::Up
} else {
MouseWheel::None
};
}
winit::event::MouseScrollDelta::PixelDelta(lp) => {
let (x, y): (f64, f64) = lp.into();
engine.mouse.wheel = if x.abs() > y.abs() {
if x > 0.0 {
MouseWheel::Right
} else if x < 0.0 {
MouseWheel::Left
} else {
MouseWheel::None
}
} else if y > 0.0 {
MouseWheel::Down
} else if y < 0.0 {
MouseWheel::Up
} else {
MouseWheel::None
};
}
},
WindowEvent::MouseInput { button, state, .. } => {
if std::mem::discriminant(&button)
!= std::mem::discriminant(&winit::event::MouseButton::Other(0))
{
let btn = match button {
winit::event::MouseButton::Left => MouseBtn::Left,
winit::event::MouseButton::Right => MouseBtn::Right,
winit::event::MouseButton::Middle => MouseBtn::Middle,
winit::event::MouseButton::Other(_) => {
unreachable!("MouseButton::Other()")
}
};
if state == winit::event::ElementState::Pressed {
engine.mouse.buttons[match btn {
MouseBtn::Left => 0,
MouseBtn::Right => 1,
MouseBtn::Middle => 2,
}]
.pressed = true;
} else {
engine.mouse.buttons[match btn {
MouseBtn::Left => 0,
MouseBtn::Right => 1,
MouseBtn::Middle => 2,
}]
.released = true;
engine.mouse.buttons[match btn {
MouseBtn::Left => 0,
MouseBtn::Right => 1,
MouseBtn::Middle => 2,
}]
.held = false;
}
}
}
_ => {}
},
Event::RedrawRequested(_) => {
redraw = true;
}
Event::MainEventsCleared => {
engine.window.request_redraw();
}
_ => {}
}
if redraw {
engine.elapsed = (instant::Instant::now()
.checked_duration_since(engine.timer)
.expect("Error with timer"))
.as_secs_f64();
engine.timer = instant::Instant::now();
engine.frame_timer += engine.elapsed;
engine.frame_count += 1;
if engine.frame_timer > 1.0 {
engine.frame_timer -= 1.0;
engine
.window
.set_title(&format!("{} - {}fps", engine.title, engine.frame_count));
engine.frame_count = 0;
}
let r = (main_func)(&mut engine);
if r.is_err() || r.as_ref().ok() == Some(&false) || force_exit {
if let Err(e) = r {
if cfg!(debug_assertions) {
println!("Game Stopped:\n{:?}", e);
} else {
println!("Game Stopped:\n{}", e);
}
}
*control_flow = winit::event_loop::ControlFlow::Exit;
}
engine.handler.render(&engine.screen.get_ref().get_raw());
redraw = false;
redraw_last_frame = true;
}
});
}
}
pub struct Engine {
pub title: String,
pub size: (u32, u32, u32),
pub elapsed: f64,
timer: instant::Instant,
frame_count: u64,
frame_timer: f64,
pub(crate) screen: DrawingSprite<Sprite>,
pub(crate) handler: px_backend::Context,
k_pressed: std::collections::HashSet<inputs::Key>,
k_held: std::collections::HashSet<inputs::Key>,
k_released: std::collections::HashSet<inputs::Key>,
mouse: Mouse,
event_loop: Option<winit::event_loop::EventLoop<()>>,
window: winit::window::Window,
}
impl std::fmt::Debug for Engine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Engine")
.field("title", &self.title)
.field("size", &self.size)
.field("elapsed", &self.elapsed)
.field("screen", &self.screen)
.finish()
}
}
impl Engine {
async fn new(title: String, size: (u32, u32, u32)) -> Self {
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(
#[allow(clippy::cast_precision_loss)]
{
winit::dpi::PhysicalSize::new(
(size.0 * size.2) as f32,
(size.1 * size.2) as f32,
)
},
)
.with_title(&title)
.with_resizable(false)
.build(&event_loop)
.expect("Error when constructing window");
window.set_visible(false);
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let env = option_env!("PIXEL_ENGINE_CANVAS");
match env {
Some(id) => {
let window_sys = web_sys::window().unwrap();
let document = window_sys.document().unwrap();
let parent_canvas = document
.query_selector(&format!("#{}", id))
.expect("The given ID does not exist")
.unwrap();
parent_canvas
.append_child(&canvas)
.expect("Append canvas to HTML body");
}
None => {
let window_sys = web_sys::window().unwrap();
let document = window_sys.document().unwrap();
let body = document.body().unwrap();
body.append_child(&canvas)
.expect("Append canvas to HTML body");
}
}
}
let handler = px_backend::Context::new(&window, size).await;
Engine {
size,
title,
timer: instant::Instant::now(),
frame_count: 0u64,
frame_timer: 0f64,
elapsed: 0f64,
handler,
screen: DrawingSprite::new(Sprite::new(size.0, size.1)),
k_pressed: std::collections::HashSet::new(),
k_held: std::collections::HashSet::new(),
k_released: std::collections::HashSet::new(),
mouse: Mouse::new(),
window: {
window.set_visible(true);
window
},
event_loop: Some(event_loop),
}
}
#[cfg(not(target_arch = "wasm32"))]
fn new_sync(title: String, size: (u32, u32, u32)) -> Self {
futures::executor::block_on(Self::new(title, size))
}
pub fn size(&self) -> Vu2d {
self.screen.get_size()
}
#[inline]
pub fn get_key(&self, keycode: inputs::Keycodes) -> Input {
Input::new(
self.k_pressed.has(keycode),
self.k_held.has(keycode),
self.k_released.has(keycode),
)
}
pub fn get_mouse_btn(&self, btn: MouseBtn) -> Input {
self.mouse.buttons[match btn {
MouseBtn::Left => 0,
MouseBtn::Right => 1,
MouseBtn::Middle => 2,
}]
}
pub fn get_mouse_location(&self) -> (u32, u32) {
self.mouse.pos
}
pub fn get_mouse_wheel(&self) -> MouseWheel {
self.mouse.wheel
}
pub fn get_pressed(&self) -> std::collections::HashSet<inputs::Keycodes> {
self.k_pressed.clone().iter().map(|k| k.key).collect()
}
pub fn create_decal(&mut self, sprite: &Sprite) -> decals::Decal {
decals::Decal::new(&mut self.handler, sprite)
}
pub fn destroy_decal(&mut self, decal: decals::Decal) {
decal.0.destroy(&mut self.handler);
}
}