tear8 0.1.78

Tear8 is a Rust library that enables you to create your own games or fantasy consoles using Winit and Pixels.
Documentation
use std::time::Duration;
use std::time::Instant;

// Pixels
use pixels::Pixels;
use pixels::SurfaceTexture;

// Winit
use winit::{
    event::Event, 
    event_loop::{
        EventLoop, ControlFlow
    }, 
    dpi::LogicalSize,
    window::{WindowBuilder, Window, Fullscreen},
};

// Crate
use crate::{
    resources::Buffer,
    commands::Commands,
    color::YELLOW,
};

const MIN_WINDOW_SIZE: (u32, u32) = (10, 10);

/// Create pixels graphics
fn create_pixels(window: &Window, width: u32, height: u32) -> Pixels {
    let window_size = window.inner_size();
    let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
    Pixels::new(width, height, surface_texture).expect("Unable to initialize Pixels library")
}

#[derive(Clone, Copy)]
/// Window settings struct
pub(crate) struct WindowSettings {
    pub(crate) width: u32, 
    pub(crate) height: u32, 
    pub(crate) resizable: bool, 
    pub(crate) fullscreen: bool,
    pub(crate) maximized: bool,
}

impl WindowSettings {
    /// Create new window settings
    pub(crate) fn new(width: u32, height: u32, resizable: bool, fullscreen: bool, maximized: bool) -> Self {
        Self {
            width, height,
            resizable,
            fullscreen,
            maximized,
        }
    }
}

/// Create basic winit window
fn create_window(title: &str, settings: WindowSettings, fullscreen: Option<Fullscreen>, event_loop: &EventLoop<()>) -> Window {
    let size = LogicalSize::new(settings.width as f32, settings.height as f32);
    let min_size = LogicalSize::new(MIN_WINDOW_SIZE.0 as f32, MIN_WINDOW_SIZE.1 as f32);
    
    WindowBuilder::new()
        .with_title(title)
        .with_inner_size(size)
        .with_min_inner_size(min_size)
        .with_resizable(settings.resizable)
        .with_fullscreen(fullscreen)
        .with_maximized(settings.maximized)
        .build(event_loop)
        .expect("Cannot create window with winit")
}

/// A trait for basic Tear Program State
/// 
/// # Example
/// ```no_run 
/// impl System for MyWorld {
///     fn sys_loop(&mut self, cmd: &mut Commands) {
///         // Render enemies
///         for e in self.enemies {
///             println!("Render is drawing '{}' enemy", e.name)
///             cmd.render(&mut e)
///         } 
/// 
///         if cmd.key_pressed(Key::Escape) {
///             cmd.quit_program()
///         }
///     }
/// 
///     fn raw_sys(&mut self, window: &mut Window, event: &mut Event<'_, ()>) {
///         // Check if window is resizable
///         if window.is_resizable() { println!("The window is resizable") }
///         // Update events
///         match event {
///             // Update Device Events
///             Event::DeviceEvent { event, .. } => {
///                 match event {
///                     // Mouse events
///                     DeviceEvent::MouseWheel { delta } => { println!("scroll wheel {:?}", delta); }
///                     DeviceEvent::MouseMotion { delta } => println!("Mouse is moved! Delta: {delta:?}"),
///                     DeviceEvent::Button { button, state } => match state {
///                         ElementState::Pressed => println!("Mouse button '{button}' pressed"), ElementState::Released => println!("Mouse button '{button}' released"),
///                     },
///                     _ => ()
///                 }
///             }
///             _ => ()
///         }
///     }
/// }
pub trait System {
    /// A function to handle main program logic
    /// 
    /// # Example
    /// ```no_run 
    /// cmd.render(&mut self.sprite) 
    /// 
    /// if cmd.key_pressed(Key::Escape) || cmd.mouse_pressed(MouseButton::Left) {
    ///     cmd.quit_program()
    /// }
    /// ```
    fn sys_loop(&mut self, cmd: &mut Commands) {
        cmd.clear_buffer(YELLOW);
    }

    /// A function to handle program logic with raw window & events
    /// 
    /// # Example
    /// ```no_run
    /// // Check if window is resizable
    /// if window.is_resizable() { println!("The window is resizable") }
    /// // Update events
    /// match event {
    ///     // Update Device Events
    ///     Event::DeviceEvent { event, .. } => {
    ///         match event {
    ///             // Mouse events
    ///             DeviceEvent::MouseWheel { delta } => { println!("scroll wheel {:?}", delta); }
    ///             DeviceEvent::MouseMotion { delta } => println!("Mouse is moved! Delta: {delta:?}"),
    ///             DeviceEvent::Button { button, state } => match state {
    ///                 ElementState::Pressed => println!("Mouse button '{button}' pressed"), ElementState::Released => println!("Mouse button '{button}' released"),
    ///             },
    ///             _ => ()
    ///         }
    ///     }
    ///     _ => ()
    /// } 
    /// ```
    fn raw_sys(&mut self, _window: &mut Window, _event: &mut Event<'_, ()>, _pixels: &mut Pixels) { }
}

/// Tear library main struct
/// # Example 
/// ```no_run
/// struct World;
/// 
/// TearProgram::new().run("Hello World from Tear8!", World);
/// 
/// impl System for World {}
/// ```
pub struct TearProgram {
    window_settings: WindowSettings, buffer_size: (u32, u32),
}

impl TearProgram {
    /// Create tear basic program
    pub fn new() -> Self {
        Self {  
            window_settings: WindowSettings::new(300, 300, true, false, false),
            buffer_size: (300, 300),
        }
    }
    // # Program Settings
    /// Set window size (default is 300x300)
    pub fn window_size(&mut self, width: u32, height: u32) -> &mut Self { self.window_settings.width = width; self.window_settings.height = height; self }
    /// Set buffer size (default is 300x300)
    pub fn buffer_size(&mut self, width: u32, height: u32) -> &mut Self { self.buffer_size = (width, height); self }
    /// Set window as resizable (default is true)
    pub fn resizable(&mut self, value: bool) -> &mut Self { self.window_settings.resizable = value; self }
    /// Set window to fullscreen (default is false)
    pub fn fullscreen(&mut self, value: bool) -> &mut Self { self.window_settings.fullscreen = value; self }
    /// Maximize the window (default is false)
    pub fn maximized(&mut self, value: bool) -> &mut Self { self.window_settings.maximized = value; self }
    /// Config all window & buffer settings
    pub fn config_program(&mut self, win_size: (u32, u32), buf_size: (u32, u32), resizable: bool, fullscreen: bool, maximized: bool) -> &mut Self {
        self.window_settings = WindowSettings::new(win_size.0, win_size.1, resizable, fullscreen, maximized);
        self.buffer_size = buf_size; self
    }
    // # Run & Build Program 
    /// Runs and build the program
    pub fn run<W: System + 'static>(&mut self, title: &str, mut world: W) {
        // # Window
        // Init event loop
        let event_loop = EventLoop::new();
        // Init window
        let mut window = if self.window_settings.fullscreen { 
            // Get monitor for fullscreen
            let monitor = event_loop
                .available_monitors()
                .next()
                .expect("no monitor found!");
            let mode = monitor.video_modes().next().expect("no monitor mode found!");
            // Create window
            create_window(title, self.window_settings, Some(Fullscreen::Exclusive(mode)), &event_loop)
        } else { create_window(title, self.window_settings, None, &event_loop) };

        // # Graphics & Commands
        // Init Pixels Graphics & Buffer
        let mut pixels = create_pixels(&window, self.buffer_size.0, self.buffer_size.1);
        let buffer = Buffer::new(self.buffer_size.0, self.buffer_size.1);
        // Init FPS & DELTA TIME
        let fps = Fps::new();
        let delta = DeltaTime::new();
        // Init Commands
        let mut cmd = Commands::new(buffer, fps, delta);
        
        // # Run Window
        event_loop.run(move |mut event, _, control_flow| {
            // # Render 
            // Render the current frame
            if let Event::RedrawRequested(_) = event {
                // Draw render on main pixels buffer
                pixels.frame_mut().copy_from_slice(cmd.buffer.raw_buffer());

                if let Err(err) = pixels.render() {
                    println!("Pixels render error: {}", err.to_string());
                    *control_flow = ControlFlow::Exit; return;
                }
            } 
            
            // # Update Raw System
            world.raw_sys(&mut window, &mut event, &mut pixels);

            // # Handle Input
            if cmd.winit_input.update(&event) {
                // Close events
                if cmd.winit_input.close_requested() || cmd.quit {
                    *control_flow = ControlFlow::Exit; return;
                }
                // Resize the window
                if let Some(size) = cmd.winit_input.window_resized() {
                    if size.width != 0 && size.height != 0 {
                        if let Err(err) = pixels.resize_surface(size.width, size.height) {
                            println!("Pixels resize surface error: {}", err.to_string());
                            *control_flow = ControlFlow::Exit; return;
                        }
                    }
                }
                // Update world
                world.sys_loop(&mut cmd);
                // Update window
                window.request_redraw();
            }
        });
    }
    
}

/// A struct for calculate Delta Time
pub(crate) struct DeltaTime {
    last_frame_time: Instant
}

impl DeltaTime {
    /// Create new Delta Timer calculator
    fn new() -> Self {
        Self {
            last_frame_time: Instant::now(),
        }
    }

    /// Return Delta Time 
    pub(crate) fn get(&mut self) -> Duration { 
        let current_time = Instant::now();
        let delta_time = current_time.duration_since(self.last_frame_time);
        self.last_frame_time = current_time;
        return delta_time;
    }
}

/// A struct for calculate FPS
pub(crate) struct Fps {
    fps_counter: i32,
    fps_timer: Instant,
}

impl Fps {
    /// Create new Fps Counter
    fn new() -> Self { 
        Self {
            fps_counter: 0,
            fps_timer: Instant::now(),
        }
    }

    /// Return FPS
    pub(crate) fn get(&mut self) -> f32 {
        // Calcola gli FPS
        self.fps_counter += 1;
        if self.fps_timer.elapsed().as_secs_f32() >= 1.0 {
            // Resetta il contatore e il timer
            self.fps_counter = 0;
            self.fps_timer = Instant::now();
        }
        let fps = self.fps_counter as f32 / self.fps_timer.elapsed().as_secs_f32();
        return fps;
    }
}