use std::convert::TryFrom;
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, smart_default::SmartDefault, serde::Serialize, serde::Deserialize, PartialEq,
)]
pub struct WindowMode {
#[default = 800.0]
pub width: f32,
#[default = 600.0]
pub height: f32,
#[default = false]
pub maximized: bool,
#[default(FullscreenType::Windowed)]
pub fullscreen_type: FullscreenType,
#[default = false]
pub borderless: bool,
#[default = false]
pub transparent: bool,
#[default = 1.0]
pub min_width: f32,
#[default = 1.0]
pub min_height: f32,
#[default = 0.0]
pub max_width: f32,
#[default = 0.0]
pub max_height: f32,
#[default = false]
pub resizable: bool,
#[default = true]
pub visible: bool,
#[default = false]
pub resize_on_scale_factor_change: bool,
#[default(None)]
pub logical_size: Option<winit::dpi::LogicalSize<f32>>,
}
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, smart_default::SmartDefault, serde::Serialize, serde::Deserialize, PartialEq, Eq,
)]
pub struct WindowSetup {
#[default(String::from("An easy, good game"))]
pub title: String,
#[default(NumSamples::One)]
pub samples: NumSamples,
#[default = true]
pub vsync: bool,
#[default(String::new())]
pub icon: String,
#[default = true]
pub srgb: bool,
}
impl WindowSetup {
#[must_use]
pub fn title(mut self, title: &str) -> Self {
self.title = title.to_owned();
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 {
self.icon = icon.to_owned();
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,
smart_default::SmartDefault,
)]
#[serde(tag = "type")]
pub enum Backend {
#[default]
All,
OnlyPrimary,
Vulkan,
Metal,
Dx12,
Dx11,
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, smart_default::SmartDefault, 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_vec(self)?;
file.write_all(&s)?;
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);
}
}