use crate::console::Console; use crate::image::Image; use crate::Tileset; use glam::IVec2; use rand::Rng; use std::cmp::{max, min}; use std::num::NonZeroU32; use std::time::{Duration, Instant}; use winit::dpi::LogicalSize; use winit::event::ElementState::Pressed; use winit::event::VirtualKeyCode::{Escape, F12}; use winit::event::{ ElementState, Event as WinitEvent, MouseButton as WinitMouseButton, WindowEvent, }; use winit::event_loop::{ControlFlow, EventLoop}; use winit::platform::run_return::EventLoopExtRunReturn; use winit::window::WindowBuilder; pub enum MouseButton { Left, Right, Middle, WheelUp, WheelDown, Forward, Back, } pub enum Event { MouseClick(MouseButton, IVec2), } pub trait Game { fn new(window_state: &mut WindowState) -> Self; fn update(&mut self, window_state: &mut WindowState); fn on_event(&mut self, window_state: &mut WindowState, event: Event); fn draw(&self, target: &mut Image); } pub struct WindowState { palette: [u32; 256], mouse_pos: IVec2, console: Console, } impl WindowState { pub fn log(&mut self, msg: &str) { self.console.add(msg); } } impl WindowState { fn new(console_size: IVec2) -> Self { WindowState { palette: [0u32; 256], mouse_pos: IVec2::ZERO, console: Console::new(console_size.y, console_size.x), } } pub fn set_palette(&mut self, index: u8, r: u8, g: u8, b: u8) { self.palette[index as usize] = (r as u32) | ((g as u32) << 8) | ((b as u32) << 16) | 0xff000000; } pub fn scramble_palette(&mut self) { let mut rng = rand::thread_rng(); for i in 0..=255 { self.set_palette(i, rng.gen(), rng.gen(), rng.gen()); } } pub fn mouse_pos(&self) -> IVec2 { self.mouse_pos } } pub fn run<T: Game + 'static>(width: i32, height: i32, target_fps: u32) { let mut event_loop = EventLoop::new(); let window = { let size = LogicalSize::new(width as f64, height as f64); WindowBuilder::new() .with_title("Skunk 2D") .with_inner_size(size) .with_min_inner_size(size) .with_decorations(false) .with_maximized(true) .build(&event_loop) .unwrap() }; //todo: replace Pixels with custom thingie (startup time slow because wgpu?) let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); let mut screen = Image::new(IVec2 { x: width, y: height, }); let internal_font = Tileset::load_data(include_bytes!("ega-8x14.gif"), IVec2 { x: 16, y: 16 }); let mut window_state = WindowState::new(IVec2 { x: width / internal_font.tile_size().x, y: height / internal_font.tile_size().y / 3, }); let mut game = T::new(&mut window_state); window_state.console.add("Initialising Skunk2d"); let mut display_console = true; let mut frames_since_last_second = 0; let mut last_second = Instant::now(); let mut last_frame = last_second; let target_frame_duration = Duration::from_secs(1) / target_fps; let mut scale = 1usize; let mut off_x = 0usize; let mut off_y = 0usize; event_loop.run_return(move |event, _, control_flow| { let scale = &mut scale; let off_x = &mut off_x; let off_y = &mut off_y; if Instant::now() - last_second > Duration::from_secs(1) { println!("fps: {}", frames_since_last_second); frames_since_last_second = 0; last_second += Duration::from_secs(1); } match event { WinitEvent::NewEvents(_) => {} WinitEvent::WindowEvent { event: window_event, .. } => { match window_event { WindowEvent::Resized(_) => {} WindowEvent::Moved(_) => {} WindowEvent::CloseRequested => {} WindowEvent::Destroyed => {} WindowEvent::DroppedFile(_) => {} WindowEvent::HoveredFile(_) => {} WindowEvent::HoveredFileCancelled => {} WindowEvent::ReceivedCharacter(_) => {} WindowEvent::Focused(_) => {} WindowEvent::KeyboardInput { input, .. } => { //todo: actually do input handling if let Some(k) = input.virtual_keycode { if k == Escape { *control_flow = ControlFlow::Exit; } if k == F12 && input.state == Pressed { display_console = !display_console; } }; } WindowEvent::ModifiersChanged(_) => {} WindowEvent::Ime(_) => {} WindowEvent::CursorMoved { position, .. } => { let x = (position.x as i32 - *off_x as i32) / *scale as i32; let y = (position.y as i32 - *off_y as i32) / *scale as i32; window_state.mouse_pos = IVec2 { x: min(width - 1, max(0, x)), y: min(height - 1, max(0, y)), } } WindowEvent::CursorEntered { .. } => {} WindowEvent::CursorLeft { .. } => {} WindowEvent::MouseWheel { .. } => {} WindowEvent::MouseInput { button, state, .. } => { let b = match button { WinitMouseButton::Left => MouseButton::Left, WinitMouseButton::Right => MouseButton::Right, WinitMouseButton::Middle => MouseButton::Middle, //todo: handle other mousebuttons WinitMouseButton::Other(_) => MouseButton::Back, }; match state { ElementState::Pressed => { let e = Event::MouseClick(b, window_state.mouse_pos); game.on_event(&mut window_state, e); } ElementState::Released => {} } } WindowEvent::TouchpadMagnify { .. } => {} WindowEvent::SmartMagnify { .. } => {} WindowEvent::TouchpadRotate { .. } => {} WindowEvent::TouchpadPressure { .. } => {} WindowEvent::AxisMotion { .. } => {} WindowEvent::Touch(_) => {} WindowEvent::ScaleFactorChanged { .. } => {} WindowEvent::ThemeChanged(_) => {} WindowEvent::Occluded(_) => {} } } WinitEvent::DeviceEvent { .. } => {} WinitEvent::UserEvent(_) => {} WinitEvent::Suspended => {} WinitEvent::Resumed => {} WinitEvent::MainEventsCleared => { if Instant::now() - last_frame >= target_frame_duration { game.update(&mut window_state); game.draw(&mut screen); if display_console { window_state.console.draw_to_image( &mut screen, IVec2::ZERO, &internal_font, ); } let size = window.inner_size(); surface .resize( NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap(), ) .unwrap(); *scale = min(size.width / width as u32, size.height / height as u32) as usize; *off_x = (size.width as usize - width as usize * *scale) / 2; *off_y = (size.height as usize - height as usize * *scale) / 2; let mut buf = surface.buffer_mut().unwrap(); //for y in 0..height as usize { { let width = width as usize; let window_width = size.width as usize; let scale = *scale; let chunk_size = scale * window_width; let screen_data = screen.data(); buf.chunks_exact_mut(chunk_size) .enumerate() .for_each(|(y, chunk)| { for x in 0..width { let p = window_state.palette[screen_data[x + y * width] as usize]; //let p = 0; for scaley in 0..scale { for scalex in 0..scale { let sx = x * scale + scalex; chunk[sx + scaley * window_width] = p; } } } }); } buf.present().unwrap(); frames_since_last_second += 1; last_frame += target_frame_duration; } else { std::thread::sleep(Duration::from_millis(1)); } } WinitEvent::RedrawRequested(_) => {} WinitEvent::RedrawEventsCleared => {} WinitEvent::LoopDestroyed => {} } }); }