use engine::Engine; fn main() { Engine::new() .set_render_fn(|mut ctx| { // draw three colored rectagles(tm) ctx.rect_unchecked(0, 0, 100, 100, 0xff0000); ctx.rect_unchecked(100, 0, 100, 100, 0x00ff00); ctx.rect_unchecked(0, 100, 100, 100, 0x0000ff); }) .run(); } mod engine { const GAME_SIZE: (u32, u32) = (500, 800); use std::{ num::NonZeroU32, process, time::{Duration, Instant}, }; use softbuffer::Buffer; use winit::{ dpi::PhysicalSize, event::{Event, KeyEvent, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{Key, NamedKey}, raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle, WindowHandle}, window::WindowBuilder, }; type RenderFn = fn(&mut RenderCtx<'_, '_>); // core game engine struct pub struct Engine { event_loop: EventLoop<()>, render_fn: RenderFn, } impl Engine { pub fn new() -> Self { let event_loop = EventLoop::new().unwrap(); Self { event_loop, render_fn: |_| {}, } } // sets the render function for the game pub fn set_render_fn(mut self, f: RenderFn) -> Self { self.render_fn = f; self } // runs the game and consumes self, this will finish the process pub fn run(self) -> ! { let Self { event_loop, render_fn, } = self; // set up window let window = WindowBuilder::new() .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) .build(&event_loop) .unwrap(); // set up softbuffer let context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap(); let mut surface = softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap(); event_loop .run(|event, elwt| { // shoddy vsync elwt.set_control_flow(ControlFlow::WaitUntil( Instant::now() + Duration::from_millis(1000 / 60), )); match event { // redraw Event::NewEvents(StartCause::ResumeTimeReached { start: _start, requested_resume: _requested_resume, }) => { if let (Some(width), Some(height)) = { let size = window.inner_size(); (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) } { surface.resize(width, height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); buffer.fill(0); let mut ctx = RenderCtx::new(buffer, (width, height), GAME_SIZE); // render render_fn(&mut ctx); ctx.force_present(); } } // crash game to exit lol Event::WindowEvent { window_id: _window_id, event: WindowEvent::CloseRequested | WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. }, } => todo!(), // potential future keyboard handling Event::WindowEvent { window_id: _window_id, event: WindowEvent::KeyboardInput { device_id: _device_id, event: _event, is_synthetic: _is_synthetic, }, } => {} _ => {} } }) .unwrap(); process::exit(0); } } // render context pub struct RenderCtx<'buf, 'win> { buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>, win_size: (u32, u32), context_size: (u32, u32), context_pos: (u32, u32), } impl<'buf, 'win> RenderCtx<'buf, 'win> { // create new render context pub fn new( buffer: Buffer<'buf, DisplayHandle<'win>, WindowHandle<'win>>, win_size: (NonZeroU32, NonZeroU32), context_size: (u32, u32), ) -> Self { Self { buffer, win_size: (win_size.0.get(), win_size.1.get()), context_size, context_pos: ( (win_size.0.get() / 2).saturating_sub(context_size.0 / 2), (win_size.1.get() / 2).saturating_sub(context_size.1 / 2), ), } } // draw a rectangle in the context // // coordinates are relative to the context pub fn rect_unchecked(&mut self, x: u32, y: u32, width: u32, height: u32, color: u32) { // position in buffer coordinates and not relative coordinates let x_buf_pos = self.context_pos.0 + x; let y_buf_pos = self.context_pos.1 + y; for y in y_buf_pos..(y_buf_pos + height) { for x in x_buf_pos..(x_buf_pos + width) { let index = y as usize * self.win_size.0 as usize + x as usize; if let Some(px) = self.buffer.get_mut(index) { *px = color } } } } pub fn force_present(self) { self.buffer.present().unwrap() } } }