#![warn(missing_docs)]
pub mod audio;
pub use glm;
pub mod error;
pub mod graphics;
pub mod input;
pub mod time;
pub mod window;
use std::time::{Duration, Instant};
use std::collections::VecDeque;
use sdl2::event::{Event, WindowEvent};
use sdl2::video::{FullscreenType, GLProfile, Window};
use sdl2::Sdl;
use crate::audio::AudioContext;
pub use crate::error::{Result, TetraError};
use crate::graphics::opengl::GLDevice;
use crate::graphics::GraphicsContext;
use crate::graphics::ScreenScaling;
use crate::input::InputContext;
#[allow(unused_variables)]
pub trait State {
fn update(&mut self, ctx: &mut Context) -> Result {
Ok(())
}
fn draw(&mut self, ctx: &mut Context, dt: f64) -> Result {
Ok(())
}
}
pub struct Context {
sdl: Sdl,
window: Window,
gl: GLDevice,
graphics: GraphicsContext,
input: InputContext,
audio: AudioContext,
window_width: i32,
window_height: i32,
fullscreen: bool,
running: bool,
quit_on_escape: bool,
tick_rate: Duration,
fps_tracker: VecDeque<f64>
}
impl Context {
pub fn run<S>(&mut self, state: &mut S) -> Result
where
S: State,
{
self.window.show();
let mut events = self.sdl.event_pump().map_err(TetraError::Sdl)?;
let mut last_time = Instant::now();
let mut lag = Duration::from_secs(0);
self.running = true;
while self.running {
let current_time = Instant::now();
let elapsed = current_time - last_time;
last_time = current_time;
lag += elapsed;
self.fps_tracker.pop_front();
self.fps_tracker.push_back(time::duration_to_f64(elapsed));
for event in events.poll_iter() {
if let Err(e) = self
.handle_event(event)
.and_then(|event| input::handle_event(self, event))
{
self.running = false;
return Err(e);
}
}
while lag >= self.tick_rate {
if let Err(e) = state.update(self) {
self.running = false;
return Err(e);
}
input::cleanup_after_state_update(self);
lag -= self.tick_rate;
}
let dt = time::duration_to_f64(lag) / time::duration_to_f64(self.tick_rate);
if let Err(e) = state.draw(self, dt) {
self.running = false;
return Err(e);
}
graphics::present(self);
std::thread::yield_now();
}
self.window.hide();
Ok(())
}
pub fn run_with<S, F>(&mut self, init: F) -> Result
where
S: State,
F: FnOnce(&mut Context) -> Result<S>,
{
let state = &mut init(self)?;
self.run(state)
}
fn handle_event(&mut self, event: Event) -> Result<Event> {
match event {
Event::Quit { .. } => self.running = false, Event::Window { win_event, .. } => {
if let WindowEvent::SizeChanged(x, y) = win_event {
window::set_size_ex(self, x, y, true)
}
}
_ => {}
}
Ok(event)
}
}
#[derive(Debug, Clone)]
pub struct ContextBuilder<'a> {
title: &'a str,
internal_width: i32,
internal_height: i32,
window_size: Option<(i32, i32)>,
window_scale: Option<i32>,
scaling: ScreenScaling,
vsync: bool,
tick_rate: f64,
fullscreen: bool,
maximized: bool,
minimized: bool,
resizable: bool,
borderless: bool,
show_mouse: bool,
quit_on_escape: bool,
}
impl<'a> ContextBuilder<'a> {
pub fn new(title: &'a str, width: i32, height: i32) -> ContextBuilder<'a> {
ContextBuilder {
title,
internal_width: width,
internal_height: height,
..ContextBuilder::default()
}
}
pub fn title(&mut self, title: &'a str) -> &mut ContextBuilder<'a> {
self.title = title;
self
}
pub fn size(&mut self, width: i32, height: i32) -> &mut ContextBuilder<'a> {
self.internal_width = width;
self.internal_height = height;
self
}
pub fn scaling(&mut self, scaling: ScreenScaling) -> &mut ContextBuilder<'a> {
self.scaling = scaling;
self
}
pub fn window_size(&mut self, width: i32, height: i32) -> &mut ContextBuilder<'a> {
self.window_size = Some((width, height));
self
}
pub fn window_scale(&mut self, scale: i32) -> &mut ContextBuilder<'a> {
self.window_scale = Some(scale);
self
}
pub fn vsync(&mut self, vsync: bool) -> &mut ContextBuilder<'a> {
self.vsync = vsync;
self
}
pub fn tick_rate(&mut self, tick_rate: f64) -> &mut ContextBuilder<'a> {
self.tick_rate = 1.0 / tick_rate;
self
}
pub fn fullscreen(&mut self, fullscreen: bool) -> &mut ContextBuilder<'a> {
self.fullscreen = fullscreen;
self
}
pub fn maximized(&mut self, maximized: bool) -> &mut ContextBuilder<'a> {
self.maximized = maximized;
self
}
pub fn minimized(&mut self, minimized: bool) -> &mut ContextBuilder<'a> {
self.minimized = minimized;
self
}
pub fn resizable(&mut self, resizable: bool) -> &mut ContextBuilder<'a> {
self.resizable = resizable;
self
}
pub fn borderless(&mut self, borderless: bool) -> &mut ContextBuilder<'a> {
self.borderless = borderless;
self
}
pub fn show_mouse(&mut self, show_mouse: bool) -> &mut ContextBuilder<'a> {
self.show_mouse = show_mouse;
self
}
pub fn quit_on_escape(&mut self, quit_on_escape: bool) -> &mut ContextBuilder<'a> {
self.quit_on_escape = quit_on_escape;
self
}
pub fn build(&self) -> Result<Context> {
let audio = AudioContext::new();
let sdl = sdl2::init().map_err(TetraError::Sdl)?;
let video = sdl.video().map_err(TetraError::Sdl)?;
let gl_attr = video.gl_attr();
gl_attr.set_context_profile(GLProfile::Core);
gl_attr.set_context_version(3, 2);
gl_attr.set_red_size(8);
gl_attr.set_green_size(8);
gl_attr.set_blue_size(8);
gl_attr.set_alpha_size(8);
gl_attr.set_double_buffer(true);
let (mut window_width, mut window_height) = if let Some(size) = self.window_size {
size
} else if let Some(scale) = self.window_scale {
(self.internal_width * scale, self.internal_height * scale)
} else {
(self.internal_width, self.internal_height)
};
let mut window_builder =
video.window(self.title, window_width as u32, window_height as u32);
window_builder.hidden().position_centered().opengl();
if self.resizable {
window_builder.resizable();
}
if self.borderless {
window_builder.borderless();
}
sdl.mouse().show_cursor(self.show_mouse);
let mut window = window_builder.build()?;
if self.maximized {
window.maximize();
let size = window.drawable_size();
window_width = size.0 as i32;
window_height = size.1 as i32;
} else if self.minimized {
window.minimize();
let size = window.drawable_size();
window_width = size.0 as i32;
window_height = size.1 as i32;
}
if self.fullscreen {
window
.display_mode()
.and_then(|m| {
window_width = m.w;
window_height = m.h;
window.set_fullscreen(FullscreenType::Desktop)
})
.map_err(TetraError::Sdl)?;
}
let mut gl = GLDevice::new(&video, &window, self.vsync)?;
let graphics = GraphicsContext::new(
&mut gl,
window_width,
window_height,
self.internal_width,
self.internal_height,
self.scaling,
)?;
let input = InputContext::new(&sdl)?;
let mut fps_tracker = VecDeque::with_capacity(200);
fps_tracker.resize(200, 1.0 / 60.0);
Ok(Context {
sdl,
window,
gl,
graphics,
input,
audio,
window_width,
window_height,
fullscreen: self.fullscreen,
running: false,
quit_on_escape: self.quit_on_escape,
tick_rate: time::f64_to_duration(self.tick_rate),
fps_tracker,
})
}
}
impl<'a> Default for ContextBuilder<'a> {
fn default() -> ContextBuilder<'a> {
ContextBuilder {
title: "Tetra",
internal_width: 1280,
internal_height: 720,
window_size: None,
window_scale: None,
scaling: ScreenScaling::ShowAllPixelPerfect,
vsync: true,
tick_rate: 1.0 / 60.0,
fullscreen: false,
maximized: false,
minimized: false,
resizable: false,
borderless: false,
show_mouse: false,
quit_on_escape: false,
}
}
}