use crate::{image::Icon, prelude::*, renderer::RendererSettings};
use log::{debug, error, info};
use std::{
num::NonZeroUsize,
thread,
time::{Duration, Instant},
};
#[allow(unused_variables)]
pub trait PixEngine {
fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
Ok(())
}
fn on_update(&mut self, s: &mut PixState) -> PixResult<()>;
fn on_stop(&mut self, s: &mut PixState) -> PixResult<()> {
Ok(())
}
fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
Ok(false)
}
fn on_key_released(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
Ok(false)
}
fn on_key_typed(&mut self, s: &mut PixState, text: &str) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_dragged(
&mut self,
s: &mut PixState,
pos: Point<i32>,
rel_pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_controller_pressed(
&mut self,
s: &mut PixState,
event: ControllerEvent,
) -> PixResult<bool> {
Ok(false)
}
fn on_controller_released(
&mut self,
s: &mut PixState,
event: ControllerEvent,
) -> PixResult<bool> {
Ok(false)
}
fn on_controller_axis_motion(
&mut self,
s: &mut PixState,
controller_id: ControllerId,
axis: Axis,
value: i32,
) -> PixResult<bool> {
Ok(false)
}
fn on_controller_update(
&mut self,
s: &mut PixState,
controller_id: ControllerId,
update: ControllerUpdate,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_pressed(
&mut self,
s: &mut PixState,
btn: Mouse,
pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_released(
&mut self,
s: &mut PixState,
btn: Mouse,
pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_clicked(
&mut self,
s: &mut PixState,
btn: Mouse,
pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_dbl_clicked(
&mut self,
s: &mut PixState,
btn: Mouse,
pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_motion(
&mut self,
s: &mut PixState,
pos: Point<i32>,
rel_pos: Point<i32>,
) -> PixResult<bool> {
Ok(false)
}
fn on_mouse_wheel(&mut self, s: &mut PixState, pos: Point<i32>) -> PixResult<bool> {
Ok(false)
}
fn on_window_event(
&mut self,
s: &mut PixState,
window_id: WindowId,
event: WindowEvent,
) -> PixResult<()> {
Ok(())
}
fn on_event(&mut self, s: &mut PixState, event: &Event) -> PixResult<bool> {
Ok(false)
}
}
#[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 {
pub fn new() -> Self {
Self::default()
}
pub fn title<S>(&mut self, title: S) -> &mut Self
where
S: Into<String>,
{
self.settings.title = title.into();
self
}
pub fn font(&mut self, font: Font) -> &mut Self {
self.theme.fonts.body = font;
self
}
pub fn font_size(&mut self, size: u32) -> &mut Self {
self.theme.font_size = size;
self
}
pub fn theme(&mut self, theme: Theme) -> &mut Self {
self.theme = theme;
self
}
pub fn icon<I>(&mut self, icon: I) -> &mut Self
where
I: Into<Icon>,
{
self.settings.icon = Some(icon.into());
self
}
pub fn position(&mut self, x: i32, y: i32) -> &mut Self {
self.settings.x = Position::Positioned(x);
self.settings.y = Position::Positioned(y);
self
}
pub fn position_centered(&mut self) -> &mut Self {
self.settings.x = Position::Centered;
self.settings.y = Position::Centered;
self
}
pub fn dimensions(&mut self, width: u32, height: u32) -> &mut Self {
self.settings.width = width;
self.settings.height = height;
self
}
pub fn scale(&mut self, x: f32, y: f32) -> &mut Self {
self.settings.scale_x = x;
self.settings.scale_y = y;
self
}
pub fn audio_sample_rate(&mut self, sample_rate: i32) -> &mut Self {
self.settings.audio_sample_rate = Some(sample_rate);
self
}
pub fn audio_channels(&mut self, channels: u8) -> &mut Self {
self.settings.audio_channels = Some(channels);
self
}
pub fn audio_buffer_size(&mut self, buffer_size: u16) -> &mut Self {
self.settings.audio_buffer_size = Some(buffer_size);
self
}
pub fn fullscreen(&mut self) -> &mut Self {
self.settings.fullscreen = true;
self
}
pub fn vsync_enabled(&mut self) -> &mut Self {
self.settings.vsync = true;
self
}
pub fn resizable(&mut self) -> &mut Self {
self.settings.resizable = true;
self
}
pub fn borderless(&mut self) -> &mut Self {
self.settings.borderless = true;
self
}
pub fn deadzone(&mut self, value: i32) -> &mut Self {
self.joystick_deadzone = value;
self
}
pub fn allow_highdpi(&mut self) -> &mut Self {
self.settings.allow_highdpi = true;
self
}
pub fn hidden(&mut self) -> &mut Self {
self.settings.hidden = true;
self
}
pub fn show_frame_rate(&mut self) -> &mut Self {
self.settings.show_frame_rate = true;
self
}
pub fn target_frame_rate(&mut self, rate: usize) -> &mut Self {
self.settings.target_frame_rate = Some(rate);
self
}
pub fn texture_cache(&mut self, size: NonZeroUsize) -> &mut Self {
self.settings.texture_cache_size = size;
self
}
pub fn text_cache(&mut self, size: NonZeroUsize) -> &mut Self {
self.settings.text_cache_size = size;
self
}
pub fn build(&self) -> PixResult<Engine> {
Ok(Engine {
state: PixState::new(self.settings.clone(), self.theme.clone())?,
joystick_deadzone: self.joystick_deadzone,
})
}
}
#[must_use]
#[derive(Debug)]
pub struct Engine {
state: PixState,
joystick_deadzone: i32,
}
impl Engine {
pub fn builder() -> EngineBuilder {
EngineBuilder::default()
}
pub fn run<A>(&mut self, app: &mut A) -> PixResult<()>
where
A: PixEngine,
{
info!("Starting `Engine`...");
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 {
debug!("Starting `Engine::on_update` loop.");
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 {
#[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
{
} 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(())
}
}