pix-engine 0.7.0

A cross-platform graphics/UI engine framework for simple games, visualizations, and graphics demos.
Documentation
//! Primary [`PiEngine`] trait and functions which drive your application.
//!
//! This is the core module of the `pix-engine` crate and is responsible for building and running
//! any application using it.
//!
//! [`EngineBuilder`] allows you to customize various engine features and, once built, can
//! [run][`Engine::run`] your application which must implement [`PixEngine::on_update`].
//!
//!
//!
//! # Example
//!
//! ```no_run
//! use pix_engine::prelude::*;
//!
//! struct MyApp;
//!
//! impl PixEngine for MyApp {
//!     fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
//!         // Setup App state. `PixState` contains engine specific state and
//!         // utility functions for things like getting mouse coordinates,
//!         // drawing shapes, etc.
//!         s.background(220);
//!         s.font_family(Font::NOTO)?;
//!         s.font_size(16);
//!         Ok(())
//!     }
//!
//!     fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
//!         // Main render loop. Called as often as possible, or based on `target frame rate`.
//!         if s.mouse_pressed() {
//!             s.fill(color!(0));
//!         } else {
//!             s.fill(color!(255));
//!         }
//!         let m = s.mouse_pos();
//!         s.circle([m.x(), m.y(), 80])?;
//!         Ok(())
//!     }
//!
//!     fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
//!         // Teardown any state or resources before exiting.
//!         Ok(())
//!     }
//! }
//!
//! fn main() -> PixResult<()> {
//!     let mut app = MyApp;
//!     let mut engine = Engine::builder()
//!       .dimensions(800, 600)
//!       .title("MyApp")
//!       .build()?;
//!     engine.run(&mut app)
//! }
//! ```

use crate::{image::Icon, prelude::*, renderer::RendererSettings};
use log::{debug, error, info};
use std::{
    num::NonZeroUsize,
    thread,
    time::{Duration, Instant},
};

/// Trait for allowing the [`Engine`] to drive your application and send notification of events,
/// passing along a [`&mut PixState`](PixState) to allow interacting with the [`Engine`].
///
/// Please see the [module-level documentation] for more examples.
///
/// [module-level documentation]: crate::appstate
#[allow(unused_variables)]
pub trait PixEngine {
    /// Called once upon engine start when [`Engine::run`] is called.
    ///
    /// This can be used to set up initial state like creating objects, loading files or [Image]s, or
    /// any additional application state that's either dynamic or relies on runtime values from
    /// [`PixState`].
    ///
    /// # Errors
    ///
    /// Returning an error will immediately exit the application and call [`PixEngine::on_stop`].
    /// [`Engine::run`] will return the original error or any error returned from
    /// [`PixEngine::on_stop`].
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.background(220);
    ///     s.font_family(Font::NOTO)?;
    ///     s.font_size(16);
    ///     Ok(())
    /// }
    /// # }
    /// ```
    fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
        Ok(())
    }

    /// Called after [`PixEngine::on_start`], every frame based on the [target frame rate].
    ///
    /// By default, this is called as often as possible but can be controlled by changing the
    /// [target frame rate]. It will continue to be executed until the application is terminated,
    /// or [`PixState::run(false)`] is called.
    ///
    /// After [`PixState::run(false)`] is called, you can call [`PixState::redraw`] or
    /// [`PixState::run_times`] to control the execution.
    ///
    /// [target frame rate]: PixState::frame_rate
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`].
    /// [`Engine::run`] will return the original error or any error returned from
    /// [`PixEngine::on_stop`]. Calling [`PixState::abort_quit`] during [`PixEngine::on_stop`] will
    /// allow program execution to resume. Care should be taken as this could result in a loop if
    /// the cause of the original error is not resolved.
    ///
    /// Any errors encountered from methods on [`PixState`] can either be handled by the
    /// implementor, or propagated with the [?] operator.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     if s.mouse_pressed() {
    ///         s.fill(color!(0));
    ///     } else {
    ///         s.fill(color!(255));
    ///     }
    ///     let m = s.mouse_pos();
    ///     s.circle([m.x(), m.y(), 80])?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    fn on_update(&mut self, s: &mut PixState) -> PixResult<()>;

    /// Called when the engine detects a close/exit event such as calling [`PixState::quit`] or if an
    /// error is returned during program execution by any [`PixEngine`] methods.
    ///
    /// This can be used to clean up files or resources on appliation quit.
    ///
    /// # Errors
    ///
    /// Returning an error will immediately exit the application by propagating the error and returning from
    /// [`Engine::run`]. Calling [`PixState::abort_quit`] will allow program execution to
    /// resume. Care should be taken as this could result in a loop if the cause of the original
    /// error is not resolved.
    ///
    /// Any errors encountered from methods on [`PixState`] can either be handled by the
    /// implementor, or propagated with the [?] operator.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { resources: std::path::PathBuf }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     std::fs::remove_file(&self.resources)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
        Ok(())
    }

    /// Called each time a [`Key`] is pressed with the [`KeyEvent`] indicating which key and modifiers
    /// are pressed as well as whether this is a repeat event where the key is being held down.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
    ///     match event.key {
    ///         Key::Return if event.keymod == KeyMod::CTRL => {
    ///             s.fullscreen(true);
    ///             Ok(true)
    ///         },
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`Key`] is pressed with the [`KeyEvent`] indicating which key and modifiers
    /// are released.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl App { fn fire_bullet(&mut self, s: &mut PixState) {} }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_key_released(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
    ///     match event.key {
    ///         Key::Space => {
    ///             self.fire_bullet(s);
    ///             Ok(true)
    ///         }
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_key_released(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time text input is received.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { text: String };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_key_typed(&mut self, s: &mut PixState, text: &str) -> PixResult<bool> {
    ///     self.text.push_str(text);
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_key_typed(&mut self, s: &mut PixState, text: &str) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time the [`Mouse`] is moved while any mouse button is being held.
    ///
    /// You can inspect which button is being held by calling [`PixState::mouse_down`] with the desired
    /// [Mouse] button. See also: [`PixEngine::on_mouse_motion`].
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { pos: Point<i32> };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_dragged(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     pos: Point<i32>,
    ///     rel_pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     self.pos = pos;
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_dragged(
        &mut self,
        s: &mut PixState,
        pos: Point<i32>,
        rel_pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`ControllerButton`] is pressed with the [`ControllerEvent`] indicating
    /// which button is pressed.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl App { fn pause(&mut self) {} }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_controller_pressed(&mut self, s: &mut PixState, event: ControllerEvent) -> PixResult<bool> {
    ///     match event.button {
    ///         ControllerButton::Start => {
    ///             self.pause();
    ///             Ok(true)
    ///         },
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_controller_pressed(
        &mut self,
        s: &mut PixState,
        event: ControllerEvent,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`ControllerButton`] is pressed with the [`ControllerEvent`] indicating
    /// which key and modifiers are released.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl App { fn fire_bullet(&mut self, s: &mut PixState) {} }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_controller_released(&mut self, s: &mut PixState, event: ControllerEvent) -> PixResult<bool> {
    ///     match event.button {
    ///         ControllerButton::X => {
    ///             self.fire_bullet(s);
    ///             Ok(true)
    ///         }
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_controller_released(
        &mut self,
        s: &mut PixState,
        event: ControllerEvent,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a `Controller` `Axis` is moved with the delta since last frame.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { player1: ControllerId }
    /// # impl App {
    /// #   fn move_right(&self) {}
    /// #   fn move_left(&self) {}
    /// #   fn move_up(&self) {}
    /// #   fn move_down(&self) {}
    /// # }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_controller_axis_motion(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     controller_id: ControllerId,
    ///     axis: Axis,
    ///     value: i32,
    /// ) -> PixResult<bool> {
    ///     if controller_id == self.player1 {
    ///         match axis {
    ///             Axis::LeftX => {
    ///                 if value > 0 {
    ///                     self.move_right();
    ///                 } else if value < 0 {
    ///                     self.move_left();
    ///                 }
    ///                 Ok(true)
    ///             }
    ///             Axis::LeftY => {
    ///                 if value > 0 {
    ///                     self.move_up();
    ///                 } else if value < 0 {
    ///                     self.move_down();
    ///                 }
    ///                 Ok(true)
    ///             }
    ///             _ => Ok(false)
    ///         }
    ///     } else {
    ///         Ok(false)
    ///     }
    /// }
    /// # }
    /// ```
    fn on_controller_axis_motion(
        &mut self,
        s: &mut PixState,
        controller_id: ControllerId,
        axis: Axis,
        value: i32,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a `Controller` is added, removed, or remapped.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl App {
    /// # fn add_gamepad(&mut self, _: ControllerId) {}
    /// # fn remove_gamepad(&mut self, _: ControllerId) {}
    /// # }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_controller_update(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     controller_id: ControllerId,
    ///     update: ControllerUpdate,
    /// ) -> PixResult<bool> {
    ///     match update {
    ///         ControllerUpdate::Added => {
    ///             self.add_gamepad(controller_id);
    ///             Ok(true)
    ///         }
    ///         ControllerUpdate::Removed => {
    ///             self.remove_gamepad(controller_id);
    ///             Ok(true)
    ///         }
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_controller_update(
        &mut self,
        s: &mut PixState,
        controller_id: ControllerId,
        update: ControllerUpdate,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`Mouse`] button is pressed.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { canvas: Rect<i32>, drawing: bool };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_pressed(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     btn: Mouse,
    ///     pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     if let Mouse::Left = btn {
    ///         if self.canvas.contains(pos) {
    ///             self.drawing = true;
    ///         }
    ///     }
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_pressed(
        &mut self,
        s: &mut PixState,
        btn: Mouse,
        pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`Mouse`] button is released.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { drawing: bool, canvas: Rect<i32> };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_released(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     btn: Mouse,
    ///     pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     if let Mouse::Left = btn {
    ///         if self.canvas.contains(pos) {
    ///             self.drawing = false;
    ///         }
    ///     }
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_released(
        &mut self,
        s: &mut PixState,
        btn: Mouse,
        pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`Mouse`] button is clicked (a press followed by a release).
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { item: Rect<i32>, selected: bool };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_clicked(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     btn: Mouse,
    ///     pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     if let Mouse::Left = btn {
    ///         if self.item.contains(pos) {
    ///             self.selected = true;
    ///         }
    ///     }
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_clicked(
        &mut self,
        s: &mut PixState,
        btn: Mouse,
        pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a [`Mouse`] button is clicked twice within 500ms.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { item: Rect<i32> };
    /// # impl App { fn execute_item(&mut self) {} }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_dbl_clicked(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     btn: Mouse,
    ///     pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     if let Mouse::Left = btn {
    ///         if self.item.contains(pos) {
    ///             self.execute_item()
    ///         }
    ///     }
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_dbl_clicked(
        &mut self,
        s: &mut PixState,
        btn: Mouse,
        pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time the [`Mouse`] is moved with the `(x, y)` screen coordinates and relative
    /// `(xrel, yrel)` positions since last frame.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { pos: Point<i32> };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_motion(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     pos: Point<i32>,
    ///     rel_pos: Point<i32>,
    /// ) -> PixResult<bool> {
    ///     self.pos = pos;
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_motion(
        &mut self,
        s: &mut PixState,
        pos: Point<i32>,
        rel_pos: Point<i32>,
    ) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time the [`Mouse`] wheel is scrolled with the `(x, y)` delta since last frame.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { scroll: Point<i32> };
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_mouse_wheel(&mut self, s: &mut PixState, pos: Point<i32>) -> PixResult<bool> {
    ///     self.scroll += pos;
    ///     Ok(true)
    /// }
    /// # }
    /// ```
    fn on_mouse_wheel(&mut self, s: &mut PixState, pos: Point<i32>) -> PixResult<bool> {
        Ok(false)
    }

    /// Called each time a window event occurs.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App { window_id: WindowId };
    /// # impl App { fn pause(&mut self) {} fn unpause(&mut self) {} }
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_window_event(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     window_id: WindowId,
    ///     event: WindowEvent,
    /// ) -> PixResult<()> {
    ///     if window_id == self.window_id {
    ///         match event {
    ///             WindowEvent::Minimized => self.pause(),
    ///             WindowEvent::Restored => self.unpause(),
    ///             _ => (),
    ///         }
    ///     }
    ///     Ok(())
    /// }
    /// # }
    /// ```
    fn on_window_event(
        &mut self,
        s: &mut PixState,
        window_id: WindowId,
        event: WindowEvent,
    ) -> PixResult<()> {
        Ok(())
    }

    /// Called for any system or user event. This is a catch-all for handling any events not
    /// covered by other [`PixEngine`] methods.
    ///
    /// Returning `true` consumes this event, preventing any further event triggering.
    ///
    /// # Errors
    ///
    /// Returning an error will start exiting the application and call [`PixEngine::on_stop`]. See
    /// the `Errors` section in [`PixEngine::on_update`] for more details.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_event(
    ///     &mut self,
    ///     s: &mut PixState,
    ///     event: &Event,
    /// ) -> PixResult<bool> {
    ///     match event {
    ///         Event::ControllerDown { controller_id, button } => {
    ///             // Handle controller down event
    ///             Ok(true)
    ///         }
    ///         Event::ControllerUp { controller_id, button } => {
    ///             // Handle controller up event
    ///             Ok(true)
    ///         }
    ///         _ => Ok(false),
    ///     }
    /// }
    /// # }
    /// ```
    fn on_event(&mut self, s: &mut PixState, event: &Event) -> PixResult<bool> {
        Ok(false)
    }
}

/// Builds a [`Engine`] instance by providing several configration functions.
///
/// # Example
///
/// ```no_run
/// # use pix_engine::prelude::*;
/// # struct MyApp;
/// # impl PixEngine for MyApp {
/// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
/// # }
/// fn main() -> PixResult<()> {
///     let mut engine = Engine::builder()
///         .title("My App")
///         .position(10, 10)
///         .resizable()
///         .show_frame_rate()
///         .icon("myapp.png")
///         .build()?;
///     let mut app = MyApp;
///     engine.run(&mut app)
/// }
/// ```
#[must_use]
#[derive(Debug)]
pub struct EngineBuilder {
    settings: RendererSettings,
    theme: Theme,
    joystick_deadzone: i32,
}

impl Default for EngineBuilder {
    fn default() -> Self {
        Self {
            settings: RendererSettings::default(),
            theme: Theme::default(),
            joystick_deadzone: 8000,
        }
    }
}

impl EngineBuilder {
    /// Constructs a `EngineBuilder`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set a window title.
    pub fn title<S>(&mut self, title: S) -> &mut Self
    where
        S: Into<String>,
    {
        self.settings.title = title.into();
        self
    }

    /// Set font for text rendering.
    pub fn font(&mut self, font: Font) -> &mut Self {
        self.theme.fonts.body = font;
        self
    }

    /// Set font size for text rendering.
    pub fn font_size(&mut self, size: u32) -> &mut Self {
        self.theme.font_size = size;
        self
    }

    /// Set theme for UI rendering.
    pub fn theme(&mut self, theme: Theme) -> &mut Self {
        self.theme = theme;
        self
    }

    /// Set a window icon.
    pub fn icon<I>(&mut self, icon: I) -> &mut Self
    where
        I: Into<Icon>,
    {
        self.settings.icon = Some(icon.into());
        self
    }

    /// Position the window at the given `(x, y)` coordinates of the display.
    pub fn position(&mut self, x: i32, y: i32) -> &mut Self {
        self.settings.x = Position::Positioned(x);
        self.settings.y = Position::Positioned(y);
        self
    }

    /// Position the window in the center of the display. This is the default.
    pub fn position_centered(&mut self) -> &mut Self {
        self.settings.x = Position::Centered;
        self.settings.y = Position::Centered;
        self
    }

    /// Set window dimensions.
    pub fn dimensions(&mut self, width: u32, height: u32) -> &mut Self {
        self.settings.width = width;
        self.settings.height = height;
        self
    }

    /// Set the rendering scale of the current canvas. Drawing coordinates are scaled by x/y
    /// factors before being drawn to the canvas.
    pub fn scale(&mut self, x: f32, y: f32) -> &mut Self {
        self.settings.scale_x = x;
        self.settings.scale_y = y;
        self
    }

    /// Set audio sample rate in Hz (samples per second). Defaults to device fallback sample rate.
    pub fn audio_sample_rate(&mut self, sample_rate: i32) -> &mut Self {
        self.settings.audio_sample_rate = Some(sample_rate);
        self
    }

    /// Set number of audio channels (1 for Mono, 2 for Stereo, etc). Defaults to device fallback
    /// number of channels.
    pub fn audio_channels(&mut self, channels: u8) -> &mut Self {
        self.settings.audio_channels = Some(channels);
        self
    }

    /// Set audio buffer size in samples. Defaults to device fallback sample size.
    pub fn audio_buffer_size(&mut self, buffer_size: u16) -> &mut Self {
        self.settings.audio_buffer_size = Some(buffer_size);
        self
    }

    /// Start window in fullscreen mode.
    pub fn fullscreen(&mut self) -> &mut Self {
        self.settings.fullscreen = true;
        self
    }

    /// Set the window to synchronize frame rate to the screens refresh rate ([`VSync`]).
    ///
    /// [`VSync`]: https://en.wikipedia.org/wiki/Screen_tearing#Vertical_synchronization
    pub fn vsync_enabled(&mut self) -> &mut Self {
        self.settings.vsync = true;
        self
    }

    /// Allow window resizing.
    pub fn resizable(&mut self) -> &mut Self {
        self.settings.resizable = true;
        self
    }

    /// Removes the window decoration.
    pub fn borderless(&mut self) -> &mut Self {
        self.settings.borderless = true;
        self
    }

    /// Alter the joystick axis deadzone.
    pub fn deadzone(&mut self, value: i32) -> &mut Self {
        self.joystick_deadzone = value;
        self
    }

    /// Enables high-DPI on displays that support it.
    pub fn allow_highdpi(&mut self) -> &mut Self {
        self.settings.allow_highdpi = true;
        self
    }

    /// Starts engine with window hidden.
    pub fn hidden(&mut self) -> &mut Self {
        self.settings.hidden = true;
        self
    }

    /// Enable average frame rate (FPS) in title.
    pub fn show_frame_rate(&mut self) -> &mut Self {
        self.settings.show_frame_rate = true;
        self
    }

    /// Set a target frame rate to render at, controls how often
    /// [`Engine::on_update`] is called.
    pub fn target_frame_rate(&mut self, rate: usize) -> &mut Self {
        self.settings.target_frame_rate = Some(rate);
        self
    }

    /// Set a custom texture cache size other than the default of `20`.
    /// Affects font family and image rendering caching operations.
    pub fn texture_cache(&mut self, size: NonZeroUsize) -> &mut Self {
        self.settings.texture_cache_size = size;
        self
    }

    /// Set a custom text cache size other than the default of `500`.
    /// Affects text rendering caching operations.
    pub fn text_cache(&mut self, size: NonZeroUsize) -> &mut Self {
        self.settings.text_cache_size = size;
        self
    }

    /// Convert [EngineBuilder] to a [`Engine`] instance.
    ///
    /// # Errors
    ///
    /// If the engine fails to create a new renderer, then an error is returned.
    ///
    /// Possible errors include the title containing a `nul` character, the position or dimensions
    /// being invalid values or overlowing and an internal renderer error such as running out of
    /// memory or a software driver issue.
    pub fn build(&self) -> PixResult<Engine> {
        Ok(Engine {
            state: PixState::new(self.settings.clone(), self.theme.clone())?,
            joystick_deadzone: self.joystick_deadzone,
        })
    }
}

/// The core engine that maintains the render loop, state, drawing functions, event handling, etc.
#[must_use]
#[derive(Debug)]
pub struct Engine {
    state: PixState,
    joystick_deadzone: i32,
}

impl Engine {
    /// Constructs a default [EngineBuilder] which can build a `Engine` instance.
    ///
    /// See [EngineBuilder] for examples.
    pub fn builder() -> EngineBuilder {
        EngineBuilder::default()
    }

    /// Starts the `Engine` application and begins executing the frame loop on a given
    /// application which must implement [`Engine`]. The only required method of which is
    /// [`Engine::on_update`].
    ///
    /// # Errors
    ///
    /// Any error in the entire library can propagate here and terminate the program. See the
    /// [error](crate::error) module for details. Also see [`Engine::on_stop`].
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use pix_engine::prelude::*;
    /// # struct MyApp;
    /// # impl MyApp { fn new() -> Self { Self } }
    /// # impl PixEngine for MyApp {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// # }
    /// fn main() -> PixResult<()> {
    ///     let mut engine = Engine::builder().build()?;
    ///     let mut app = MyApp::new(); // MyApp implements `Engine`
    ///     engine.run(&mut app)
    /// }
    /// ```
    pub fn run<A>(&mut self, app: &mut A) -> PixResult<()>
    where
        A: PixEngine,
    {
        info!("Starting `Engine`...");

        // Handle events before on_start to initialize window
        self.handle_events(app)?;

        debug!("Starting with `Engine::on_start`");
        self.state.clear()?;
        let on_start = app.on_start(&mut self.state);
        if on_start.is_err() || self.state.should_quit() {
            debug!("Quitting during startup with `Engine::on_stop`");
            if let Err(ref err) = on_start {
                error!("Error: {}", err);
            }
            return app.on_stop(&mut self.state).and(on_start);
        }
        self.state.present();

        // on_stop loop enables on_stop to prevent application close if necessary
        'on_stop: loop {
            debug!("Starting `Engine::on_update` loop.");
            // running loop continues until an event or on_update returns false or errors
            let result = 'running: loop {
                let start_time = Instant::now();
                let time_since_last = start_time - self.state.last_frame_time();

                self.handle_events(app)?;
                if self.state.should_quit() {
                    break 'running Ok(());
                }

                if self.state.is_running() {
                    self.state.pre_update();
                    let on_update = app.on_update(&mut self.state);
                    if on_update.is_err() {
                        self.state.quit();
                        break 'running on_update;
                    }
                    self.state.on_update()?;
                    self.state.post_update();
                    self.state.present();
                    self.state.set_delta_time(start_time, time_since_last);
                    self.state.increment_frame(time_since_last)?;
                }

                if !self.state.vsync_enabled() {
                    if let Some(target_delta_time) = self.state.target_delta_time() {
                        let time_to_next_frame = start_time + target_delta_time;
                        let now = Instant::now();
                        if time_to_next_frame > now {
                            thread::sleep(time_to_next_frame - now);
                        }
                    }
                }
            };

            debug!("Quitting with `Engine::on_stop`");
            let on_stop = app.on_stop(&mut self.state);
            if self.state.should_quit() {
                info!("Quitting `Engine`...");
                break 'on_stop on_stop.and(result);
            }
        }
    }
}

impl Engine {
    /// Handle user and system events.
    #[inline]
    fn handle_events<A>(&mut self, app: &mut A) -> PixResult<()>
    where
        A: PixEngine,
    {
        let state = &mut self.state;
        while let Some(event) = state.poll_event() {
            if let Event::ControllerAxisMotion { .. }
            | Event::JoyAxisMotion { .. }
            | Event::MouseMotion { .. }
            | Event::MouseWheel { .. }
            | Event::KeyDown { repeat: true, .. }
            | Event::KeyUp { repeat: true, .. } = event
            {
                // Ignore noisy events
            } else {
                debug!("Polling event {:?}", event);
            }
            let handled = app.on_event(state, &event)?;
            if !handled {
                match event {
                    Event::Quit { .. } | Event::AppTerminating { .. } => state.quit(),
                    Event::Window {
                        window_id,
                        win_event,
                    } => {
                        let window_id = WindowId(window_id);
                        match win_event {
                            WindowEvent::FocusGained => state.focus_window(Some(window_id)),
                            WindowEvent::FocusLost => state.focus_window(None),
                            WindowEvent::Close => state.close_window(window_id)?,
                            _ => (),
                        }
                        app.on_window_event(state, window_id, win_event)?;
                    }
                    Event::KeyDown {
                        key: Some(key),
                        keymod,
                        repeat,
                    } => {
                        let evt = KeyEvent::new(key, keymod, repeat);
                        if !app.on_key_pressed(state, evt)? {
                            state.ui.keys.press(key, keymod);
                        }
                    }
                    Event::KeyUp {
                        key: Some(key),
                        keymod,
                        repeat,
                    } => {
                        let evt = KeyEvent::new(key, keymod, repeat);
                        if !app.on_key_released(state, evt)? {
                            state.ui.keys.release(key, keymod);
                        }
                    }
                    Event::ControllerDown {
                        controller_id,
                        button,
                    } => {
                        let evt = ControllerEvent::new(controller_id, button);
                        app.on_controller_pressed(state, evt)?;
                    }
                    Event::ControllerUp {
                        controller_id,
                        button,
                    } => {
                        let evt = ControllerEvent::new(controller_id, button);
                        app.on_controller_released(state, evt)?;
                    }
                    Event::ControllerAxisMotion {
                        controller_id,
                        axis,
                        value,
                    } => {
                        let value = i32::from(value);
                        let value =
                            if (-self.joystick_deadzone..self.joystick_deadzone).contains(&value) {
                                0
                            } else {
                                value
                            };
                        let id = ControllerId(controller_id);
                        app.on_controller_axis_motion(state, id, axis, value)?;
                    }
                    Event::ControllerAdded { controller_id } => {
                        let id = ControllerId(controller_id);
                        if !app.on_controller_update(state, id, ControllerUpdate::Added)? {
                            state.open_controller(id)?;
                        }
                    }
                    Event::JoyDeviceAdded { joy_id } => {
                        let id = ControllerId(joy_id);
                        if !app.on_controller_update(state, id, ControllerUpdate::Added)? {
                            state.open_controller(id)?;
                        }
                    }
                    Event::ControllerRemoved { controller_id } => {
                        let id = ControllerId(controller_id);
                        if !app.on_controller_update(state, id, ControllerUpdate::Removed)? {
                            state.close_controller(id);
                        }
                    }
                    Event::JoyDeviceRemoved { joy_id } => {
                        let id = ControllerId(joy_id);
                        if !app.on_controller_update(state, id, ControllerUpdate::Removed)? {
                            state.open_controller(id)?;
                        }
                    }
                    Event::ControllerRemapped { controller_id } => {
                        let id = ControllerId(controller_id);
                        app.on_controller_update(state, id, ControllerUpdate::Remapped)?;
                    }
                    Event::TextInput { text, .. } => {
                        if !app.on_key_typed(state, &text)? {
                            state.ui.keys.typed(text);
                        }
                    }
                    Event::MouseMotion { x, y, xrel, yrel } => {
                        let pos = point!(x, y);
                        let rel_pos = point!(xrel, yrel);
                        if state.ui.mouse.is_pressed() {
                            app.on_mouse_dragged(state, pos, rel_pos)?;
                        }
                        if !app.on_mouse_motion(state, pos, rel_pos)? {
                            state.on_mouse_motion(pos);
                        }
                    }
                    Event::MouseDown { button, x, y } => {
                        if !app.on_mouse_pressed(state, button, point!(x, y))? {
                            state.on_mouse_pressed(button);
                        }
                    }
                    Event::MouseUp { button, x, y } => {
                        if state.ui.mouse.is_down(button) {
                            let now = Instant::now();
                            if let Some(clicked) = state.ui.mouse.last_clicked(button) {
                                if now - *clicked < Duration::from_millis(500)
                                    && !app.on_mouse_dbl_clicked(state, button, point!(x, y))?
                                {
                                    state.on_mouse_dbl_click(button, now);
                                }
                            }
                            if !app.on_mouse_clicked(state, button, point!(x, y))? {
                                state.on_mouse_click(button, now);
                            }
                        }
                        if !app.on_mouse_released(state, button, point!(x, y))? {
                            state.on_mouse_released(button);
                        }
                    }
                    Event::MouseWheel { x, y, .. } => {
                        if !app.on_mouse_wheel(state, point!(x, y))? {
                            state.on_mouse_wheel(x, y);
                        }
                    }
                    _ => (),
                }
            }
        }
        Ok(())
    }
}