use std::io;
use winit::dpi::PhysicalSize;
use crate::error::{GameError, GameResult};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum FullscreenType {
Windowed,
True,
Desktop,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct WindowMode {
pub width: f32,
pub height: f32,
pub maximized: bool,
pub fullscreen_type: FullscreenType,
pub borderless: bool,
pub transparent: bool,
pub min_width: f32,
pub min_height: f32,
pub max_width: f32,
pub max_height: f32,
pub resizable: bool,
pub visible: bool,
pub resize_on_scale_factor_change: bool,
pub logical_size: Option<winit::dpi::LogicalSize<f32>>,
}
impl Default for WindowMode {
fn default() -> Self {
Self {
width: 800.0,
height: 600.0,
maximized: false,
fullscreen_type: FullscreenType::Windowed,
borderless: false,
transparent: false,
min_width: 1.0,
min_height: 1.0,
max_width: 0.0,
max_height: 0.0,
resizable: false,
visible: true,
resize_on_scale_factor_change: false,
logical_size: None,
}
}
}
impl WindowMode {
#[must_use]
pub fn dimensions(mut self, width: f32, height: f32) -> Self {
if width >= 1.0 {
self.width = width;
}
if height >= 1.0 {
self.height = height;
}
self
}
#[must_use]
pub fn maximized(mut self, maximized: bool) -> Self {
self.maximized = maximized;
self
}
#[must_use]
pub fn fullscreen_type(mut self, fullscreen_type: FullscreenType) -> Self {
self.fullscreen_type = fullscreen_type;
self
}
#[must_use]
pub fn borderless(mut self, borderless: bool) -> Self {
self.borderless = borderless;
self
}
#[must_use]
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
#[must_use]
pub fn min_dimensions(mut self, width: f32, height: f32) -> Self {
if width >= 1.0 {
self.min_width = width;
}
if height >= 1.0 {
self.min_height = height;
}
self
}
#[must_use]
pub fn max_dimensions(mut self, width: f32, height: f32) -> Self {
self.max_width = width;
self.max_height = height;
self
}
#[must_use]
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
#[must_use]
pub fn visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
#[must_use]
pub fn resize_on_scale_factor_change(mut self, resize_on_scale_factor_change: bool) -> Self {
self.resize_on_scale_factor_change = resize_on_scale_factor_change;
self
}
pub(crate) fn actual_size(&self) -> GameResult<winit::dpi::Size> {
let actual_size: winit::dpi::Size = if let Some(logical_size) = self.logical_size {
logical_size.into()
} else {
winit::dpi::PhysicalSize::<f64>::from((self.width, self.height)).into()
};
let physical_size: PhysicalSize<f64> = actual_size.to_physical(1.0);
if physical_size.width >= 1.0 && physical_size.height >= 1.0 {
Ok(actual_size)
} else {
Err(GameError::WindowError(format!(
"window width and height need to be at least 1; actual values: {}, {}",
physical_size.width, physical_size.height
)))
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct WindowSetup {
pub title: String,
pub samples: NumSamples,
pub vsync: bool,
pub icon: String,
pub srgb: bool,
}
impl Default for WindowSetup {
fn default() -> Self {
Self {
title: String::from("An easy, good game"),
samples: NumSamples::One,
vsync: true,
icon: String::new(),
srgb: true,
}
}
}
impl WindowSetup {
#[must_use]
pub fn title(mut self, title: &str) -> Self {
title.clone_into(&mut self.title);
self
}
#[must_use]
pub fn samples(mut self, samples: NumSamples) -> Self {
self.samples = samples;
self
}
#[must_use]
pub fn vsync(mut self, vsync: bool) -> Self {
self.vsync = vsync;
self
}
#[must_use]
pub fn icon(mut self, icon: &str) -> Self {
icon.clone_into(&mut self.icon);
self
}
#[must_use]
pub fn srgb(mut self, active: bool) -> Self {
self.srgb = active;
self
}
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default)]
#[serde(tag = "type")]
pub enum Backend {
#[default]
All,
OnlyPrimary,
Vulkan,
Metal,
Dx12,
Gl,
BrowserWebGpu,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum NumSamples {
One = 1,
Four = 4,
}
impl TryFrom<u8> for NumSamples {
type Error = GameError;
fn try_from(i: u8) -> Result<Self, Self::Error> {
match i {
1 => Ok(NumSamples::One),
4 => Ok(NumSamples::Four),
_ => Err(GameError::ConfigError(String::from(
"Invalid number of samples",
))),
}
}
}
impl From<NumSamples> for u8 {
fn from(ns: NumSamples) -> u8 {
ns as u8
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Default, Clone)]
pub struct Conf {
pub window_mode: WindowMode,
pub window_setup: WindowSetup,
pub backend: Backend,
}
impl Conf {
pub fn new() -> Self {
Self::default()
}
pub fn from_toml_file<R: io::Read>(file: &mut R) -> GameResult<Conf> {
let mut s = String::new();
let _ = file.read_to_string(&mut s)?;
let decoded = toml::from_str(&s)?;
Ok(decoded)
}
pub fn to_toml_file<W: io::Write>(&self, file: &mut W) -> GameResult {
let s = toml::to_string(self)?;
file.write_all(s.as_bytes())?;
Ok(())
}
#[must_use]
pub fn window_mode(mut self, window_mode: WindowMode) -> Self {
self.window_mode = window_mode;
self
}
#[must_use]
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
}
#[cfg(test)]
mod tests {
use crate::conf;
#[test]
fn headless_encode_round_trip() {
let c1 = conf::Conf::new();
let mut writer = Vec::new();
c1.to_toml_file(&mut writer).unwrap();
let mut reader = writer.as_slice();
let c2 = conf::Conf::from_toml_file(&mut reader).unwrap();
assert_eq!(c1, c2);
}
}