use winit::monitor::{MonitorHandle, VideoModeHandle}; use winit::window::{Fullscreen, Window}; use serde::{Serialize, Deserialize}; use std::error::Error; use std::fs; use std::io; use std::path::{Path, PathBuf}; pub fn default_config_path() -> PathBuf { let exe_path = std::env::current_exe().unwrap(); let exe_dir = exe_path .parent() .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "executable has no parent")).unwrap(); exe_dir.join("settings.toml") } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum WindowMode { BorderlessFullscreen, ExclusiveFullscreen, #[default] Windowed, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct Settings { pub display_settings: DisplaySettings, } impl Settings { pub fn load_from_file(path: impl AsRef) -> Result> { let path = path.as_ref(); let text = fs::read_to_string(path)?; let settings: Settings = toml::from_str(&text)?; Ok(settings) } pub fn save_to_file(&self, path: impl AsRef) -> Result<(), Box> { let path = path.as_ref(); if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let toml_str = toml::to_string_pretty(self)?; fs::write(path, toml_str)?; Ok(()) } pub fn load_or_default(path: impl AsRef) -> Result> { let path = path.as_ref(); match fs::read_to_string(path) { Ok(text) => { let settings: Settings = toml::from_str(&text)?; Ok(settings) } Err(err) if err.kind() == io::ErrorKind::NotFound => { let settings = Settings::default(); if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let toml_str = toml::to_string_pretty(&settings)?; fs::write(path, toml_str)?; Ok(settings) } Err(err) => Err(Box::new(err)), } } } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct DisplaySettings { pub fullscreen_mode: WindowMode, #[serde(skip)] pub dirty: bool, } impl Default for DisplaySettings { fn default() -> Self { Self { fullscreen_mode: WindowMode::Windowed, dirty: false, } } } impl DisplaySettings { pub fn apply(&self, window: &Window) { // apply fullscreen mode match self.fullscreen_mode { WindowMode::BorderlessFullscreen => { let monitor = window.current_monitor().or_else(|| window.primary_monitor()); window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } WindowMode::ExclusiveFullscreen => { let monitor = window.current_monitor().or_else(|| window.primary_monitor()); match monitor { Some(monitor) => { if let Some(video_mode) = pick_best_video_mode(&monitor) { window.set_fullscreen(Some(Fullscreen::Exclusive(video_mode))); } else { // fallback to borderless window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor)))); } } None => { // no monitor info, fallback to windowed window.set_fullscreen(None); } } } WindowMode::Windowed => { window.set_fullscreen(None); }, } } } fn pick_best_video_mode(monitor: &MonitorHandle) -> Option { let target_size = monitor.size(); let mut best_native: Option = None; let mut best_any: Option = None; for mode in monitor.video_modes() { if mode.size() == target_size { let replace = match best_native.as_ref() { None => true, Some(best) => { (mode.refresh_rate_millihertz(), mode.bit_depth()) > (best.refresh_rate_millihertz(), best.bit_depth()) } }; if replace { best_native = Some(mode.clone()); } } let replace = match best_any.as_ref() { None => true, Some(best) => is_better_video_mode(&mode, best), }; if replace { best_any = Some(mode); } } best_native.or(best_any) } fn is_better_video_mode(a: &VideoModeHandle, b: &VideoModeHandle) -> bool { let a_size = a.size(); let b_size = b.size(); let a_area = u64::from(a_size.width) * u64::from(a_size.height); let b_area = u64::from(b_size.width) * u64::from(b_size.height); match a_area.cmp(&b_area) { std::cmp::Ordering::Greater => true, std::cmp::Ordering::Less => false, std::cmp::Ordering::Equal => { (a.refresh_rate_millihertz(), a.bit_depth()) > (b.refresh_rate_millihertz(), b.bit_depth()) } } }