166 lines
5.2 KiB
Rust
166 lines
5.2 KiB
Rust
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<Path>) -> Result<Self, Box<dyn Error>> {
|
|
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<Path>) -> Result<(), Box<dyn Error>> {
|
|
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<Path>) -> Result<Self, Box<dyn Error>> {
|
|
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<VideoModeHandle> {
|
|
let target_size = monitor.size();
|
|
|
|
let mut best_native: Option<VideoModeHandle> = None;
|
|
let mut best_any: Option<VideoModeHandle> = 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())
|
|
}
|
|
}
|
|
}
|