gsa 0.2.1

Game development library modelled after an imaginary console
Documentation
use crate::buttons::{button_from_gilrs, button_from_scancode};
use crate::gsa_render_to_screen::{render_to_screen, render_to_window};
use crate::tileset::load_tileset;
use crate::{
    Buttons, Gsa, Sprite, FONT_BOLD, MAX_SPRITES, SCREEN_HEIGHT, SCREEN_WIDTH, TRANSPARENT,
};
use clap::crate_version;
use gilrs::EventType;
use glam::IVec2;
use std::cmp::min;
use std::num::NonZeroU32;
use std::time::{Duration, Instant};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event;
use winit::event::{ElementState, Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;

/// Creates main function, includes gfx.gif, and calls run
///
/// Pass the following two functions to it:
/// - fn init(gsa: &mut Gsa) -> TGame
/// - fn update(game: &mut Game, gsa: &mut Gsa)
///
/// TGame can be any type, usually a struct that contains your game's state
#[macro_export]
macro_rules! run {
    ($init: ident, $update: ident) => {
        fn main() {
            run($init, $update, include_bytes!("gfx.gif"));
        }
    };
}

/// This is called by [run!]
pub fn run<TGame: 'static>(
    init_fn: fn(gsa: &mut Gsa) -> TGame,
    update_fn: fn(game: &mut TGame, gsa: &mut Gsa),
    image_data: &[u8],
) {
    let (tileset, palette) = load_tileset(image_data);

    let mut gsa = Gsa {
        sprites: [Sprite::default(); MAX_SPRITES],
        bgs: Default::default(),
        palette,
        font: FONT_BOLD,
        pressed: 0,
        released: 0,
        down: 0,
    };

    gsa.reset_bgs();
    gsa.reset_sprites();

    let mut game = init_fn(&mut gsa);

    let event_loop = EventLoop::new();
    let size = LogicalSize::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32);
    //todo: custom title
    let window = WindowBuilder::new()
        .with_title(format!("Game Skunk Advance v{}", crate_version!()))
        .with_inner_size(size)
        .with_min_inner_size(size)
        .build(&event_loop)
        .unwrap();

    {
        let monitor_size = window.primary_monitor().unwrap().size();
        let scale_factor = 0.9;
        let window_scale = ((monitor_size.width as f32 * scale_factor) as u32
            / SCREEN_WIDTH as u32)
            .min((monitor_size.height as f32 * scale_factor) as u32 / SCREEN_HEIGHT as u32);
        window.set_inner_size(PhysicalSize {
            width: window_scale * SCREEN_WIDTH as u32,
            height: window_scale * SCREEN_HEIGHT as u32,
        });
        let outer_size = window.outer_size();
        window.set_outer_position(PhysicalPosition {
            x: (monitor_size.width - outer_size.width) / 2,
            y: (monitor_size.height - outer_size.height) / 2,
        });
    }

    let context = unsafe { softbuffer::Context::new(&window) }.unwrap();
    let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap();

    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).div_f64(60.0);

    let mut scale = 1usize;
    let mut off_x = 0usize;
    let mut off_y = 0usize;

    let mut new_buttons = Buttons::default();
    // in case button gets pressed and released within the same frame need to catch them
    // seperately from the last-now comparison to catch it
    let mut new_pressed = Buttons::default();
    let mut new_released = Buttons::default();
    let mut gilrs = gilrs::Gilrs::new().unwrap();

    event_loop.run(move |event, _, control_flow| {
        let scale = &mut scale;
        let off_x = &mut off_x;
        let off_y = &mut off_y;
        let new_buttons = &mut new_buttons;
        let new_pressed = &mut new_pressed;
        let new_released = &mut new_released;
        let gilrs = &mut gilrs;

        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 {
            Event::WindowEvent {
                event:
                    WindowEvent::KeyboardInput {
                        input:
                            event::KeyboardInput {
                                virtual_keycode: Some(VirtualKeyCode::Escape),
                                ..
                            },
                        ..
                    },
                ..
            } => {
                *control_flow = ControlFlow::Exit;
            }

            Event::WindowEvent {
                event: WindowEvent::KeyboardInput { input, .. },
                ..
            } => {
                let button = button_from_scancode(input.scancode);
                match input.state {
                    ElementState::Pressed => {
                        *new_buttons |= button;
                        *new_pressed |= button;
                    }
                    ElementState::Released => {
                        *new_buttons &= !button;
                        *new_released |= button;
                    }
                };
            }

            Event::MainEventsCleared {} => {
                if Instant::now() - last_frame >= target_frame_duration {
                    //input
                    while let Some(event) = gilrs.next_event() {
                        match event.event {
                            EventType::ButtonPressed(button, ..) => {
                                let button = button_from_gilrs(button);
                                *new_buttons |= button;
                                *new_pressed |= button;
                            }
                            EventType::ButtonReleased(button, ..) => {
                                let button = button_from_gilrs(button);
                                *new_buttons &= !button;
                                *new_released |= button;
                            }
                            _ => {}
                        }
                    }
                    gsa.down = *new_buttons;
                    gsa.released = *new_released;
                    gsa.pressed = *new_pressed;
                    *new_released = 0;
                    *new_pressed = 0;

                    //update
                    update_fn(&mut game, &mut gsa);

                    //graphics
                    let size = window.inner_size();
                    surface
                        .resize(
                            NonZeroU32::new(size.width).unwrap(),
                            NonZeroU32::new(size.height).unwrap(),
                        )
                        .unwrap();

                    *scale = min(
                        size.width / SCREEN_WIDTH as u32,
                        size.height / SCREEN_HEIGHT as u32,
                    ) as usize;
                    *off_x = (size.width as usize - SCREEN_WIDTH * *scale) / 2;
                    *off_y = (size.height as usize - SCREEN_HEIGHT * *scale) / 2;

                    let mut screen_buffer = [TRANSPARENT; SCREEN_WIDTH * SCREEN_HEIGHT];
                    let mut window_buffer = surface.buffer_mut().unwrap();
                    render_to_screen(&mut screen_buffer, &gsa, &tileset);
                    render_to_window(
                        &mut window_buffer,
                        &mut screen_buffer,
                        &palette,
                        IVec2 {
                            x: size.width as i32,
                            y: size.height as i32,
                        },
                        *scale,
                        *off_x,
                        *off_y,
                    );
                    window_buffer.present().unwrap();
                    frames_since_last_second += 1;
                    last_frame += target_frame_duration;
                }
            }
            _ => {}
        }
    });
}