ggez/conf.rs
1//! The `conf` module contains functions for loading and saving game
2//! configurations.
3//!
4//! A [`Conf`](struct.Conf.html) struct is used to create a config file
5//! which specifies hardware setup stuff, mostly video display settings.
6//!
7//! By default a ggez game will search its resource paths for a `/conf.toml`
8//! file and load values from it when the [`Context`](../struct.Context.html) is created. This file
9//! must be complete (ie you cannot just fill in some fields and have the
10//! rest be default) and provides a nice way to specify settings that
11//! can be tweaked such as window resolution, multisampling options, etc.
12//! If no file is found, it will create a `Conf` object from the settings
13//! passed to the [`ContextBuilder`](../struct.ContextBuilder.html).
14
15use std::convert::TryFrom;
16use std::io;
17
18use winit::dpi::PhysicalSize;
19
20use crate::error::{GameError, GameResult};
21
22/// Possible fullscreen modes.
23#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
24pub enum FullscreenType {
25 /// Windowed mode.
26 Windowed,
27 /// True fullscreen, which used to be preferred 'cause it can have
28 /// small performance benefits over windowed fullscreen.
29 ///
30 /// Also it allows us to set different resolutions.
31 True,
32 /// Windowed fullscreen, generally preferred over real fullscreen
33 /// these days 'cause it plays nicer with multiple monitors.
34 Desktop,
35}
36
37/// A builder structure containing window settings
38/// that can be set at runtime and changed with [`graphics::set_mode()`](../graphics/fn.set_mode.html).
39///
40/// Defaults:
41///
42/// ```rust
43/// # use ggez::conf::*;
44/// # fn main() { assert_eq!(
45/// WindowMode {
46/// width: 800.0,
47/// height: 600.0,
48/// maximized: false,
49/// fullscreen_type: FullscreenType::Windowed,
50/// borderless: false,
51/// min_width: 1.0,
52/// max_width: 0.0,
53/// min_height: 1.0,
54/// max_height: 0.0,
55/// resizable: false,
56/// visible: true,
57/// transparent: false,
58/// resize_on_scale_factor_change: false,
59/// logical_size: None,
60/// }
61/// # , WindowMode::default());}
62/// ```
63#[derive(
64 Debug, Copy, Clone, smart_default::SmartDefault, serde::Serialize, serde::Deserialize, PartialEq,
65)]
66pub struct WindowMode {
67 /// Window width in physical pixels
68 #[default = 800.0]
69 pub width: f32,
70 /// Window height in physical pixels
71 #[default = 600.0]
72 pub height: f32,
73 /// Whether or not to maximize the window
74 #[default = false]
75 pub maximized: bool,
76 /// Fullscreen type
77 #[default(FullscreenType::Windowed)]
78 pub fullscreen_type: FullscreenType,
79 /// Whether or not to show window decorations
80 #[default = false]
81 pub borderless: bool,
82 /// Whether or not the window should be transparent
83 #[default = false]
84 pub transparent: bool,
85 /// Minimum width for resizable windows; 1 is the technical minimum,
86 /// as wgpu will panic on a width of 0.
87 #[default = 1.0]
88 pub min_width: f32,
89 /// Minimum height for resizable windows; 1 is the technical minimum,
90 /// as wgpu will panic on a height of 0.
91 #[default = 1.0]
92 pub min_height: f32,
93 /// Maximum width for resizable windows; 0 means no limit
94 #[default = 0.0]
95 pub max_width: f32,
96 /// Maximum height for resizable windows; 0 means no limit
97 #[default = 0.0]
98 pub max_height: f32,
99 /// Whether or not the window is resizable
100 #[default = false]
101 pub resizable: bool,
102 /// Whether this window should displayed (true) or hidden (false)
103 #[default = true]
104 pub visible: bool,
105 /// Whether this window should change its size in physical pixels
106 /// when its hidpi factor changes, i.e. when [`WindowEvent::ScaleFactorChanged`](https://docs.rs/winit/0.25.0/winit/event/enum.WindowEvent.html#variant.ScaleFactorChanged)
107 /// is fired.
108 ///
109 /// You usually want this to be false, since the window suddenly changing size may break your game.
110 /// Setting this to true may be desirable if you plan for it and want your window to behave like
111 /// windows of other programs when being dragged from one screen to another, for example.
112 ///
113 /// For more context on this take a look at [this conversation](https://github.com/ggez/ggez/pull/949#issuecomment-854731226).
114 #[default = false]
115 pub resize_on_scale_factor_change: bool,
116 // logical_size is serialized as a table, so it must be at the end of the struct for toml
117 /// Window height/width but allows LogicalSize for high DPI systems. If Some will be used instead of width/height.
118 #[default(None)]
119 pub logical_size: Option<winit::dpi::LogicalSize<f32>>,
120}
121
122impl WindowMode {
123 /// Set default window size, or screen resolution in true fullscreen mode.
124 #[must_use]
125 pub fn dimensions(mut self, width: f32, height: f32) -> Self {
126 if width >= 1.0 {
127 self.width = width;
128 }
129 if height >= 1.0 {
130 self.height = height;
131 }
132 self
133 }
134
135 /// Set whether the window should be maximized.
136 #[must_use]
137 pub fn maximized(mut self, maximized: bool) -> Self {
138 self.maximized = maximized;
139 self
140 }
141
142 /// Set the fullscreen type.
143 #[must_use]
144 pub fn fullscreen_type(mut self, fullscreen_type: FullscreenType) -> Self {
145 self.fullscreen_type = fullscreen_type;
146 self
147 }
148
149 /// Set whether a window should be borderless in windowed mode.
150 #[must_use]
151 pub fn borderless(mut self, borderless: bool) -> Self {
152 self.borderless = borderless;
153 self
154 }
155
156 /// Set whether a window should be transparent.
157 #[must_use]
158 pub fn transparent(mut self, transparent: bool) -> Self {
159 self.transparent = transparent;
160 self
161 }
162
163 /// Set minimum window dimensions for windowed mode.
164 /// Minimum dimensions will always be >= 1.
165 #[must_use]
166 pub fn min_dimensions(mut self, width: f32, height: f32) -> Self {
167 if width >= 1.0 {
168 self.min_width = width;
169 }
170 if height >= 1.0 {
171 self.min_height = height;
172 }
173 self
174 }
175
176 /// Set maximum window dimensions for windowed mode.
177 #[must_use]
178 pub fn max_dimensions(mut self, width: f32, height: f32) -> Self {
179 self.max_width = width;
180 self.max_height = height;
181 self
182 }
183
184 /// Set resizable.
185 #[must_use]
186 pub fn resizable(mut self, resizable: bool) -> Self {
187 self.resizable = resizable;
188 self
189 }
190
191 /// Set visibility
192 #[must_use]
193 pub fn visible(mut self, visible: bool) -> Self {
194 self.visible = visible;
195 self
196 }
197
198 /// Set whether to resize when the hidpi factor changes
199 #[must_use]
200 pub fn resize_on_scale_factor_change(mut self, resize_on_scale_factor_change: bool) -> Self {
201 self.resize_on_scale_factor_change = resize_on_scale_factor_change;
202 self
203 }
204
205 // Use logical_size if set, else convert width/height to PhysicalSize
206 pub(crate) fn actual_size(&self) -> GameResult<winit::dpi::Size> {
207 let actual_size: winit::dpi::Size = if let Some(logical_size) = self.logical_size {
208 logical_size.into()
209 } else {
210 winit::dpi::PhysicalSize::<f64>::from((self.width, self.height)).into()
211 };
212
213 let physical_size: PhysicalSize<f64> = actual_size.to_physical(1.0);
214 if physical_size.width >= 1.0 && physical_size.height >= 1.0 {
215 Ok(actual_size)
216 } else {
217 Err(GameError::WindowError(format!(
218 "window width and height need to be at least 1; actual values: {}, {}",
219 physical_size.width, physical_size.height
220 )))
221 }
222 }
223}
224
225/// A builder structure containing window settings
226/// that must be set at init time and cannot be changed afterwards.
227///
228/// Defaults:
229///
230/// ```rust
231/// # use ggez::conf::*;
232/// # fn main() { assert_eq!(
233/// WindowSetup {
234/// title: "An easy, good game".to_owned(),
235/// samples: NumSamples::One,
236/// vsync: true,
237/// icon: "".to_owned(),
238/// srgb: true,
239/// }
240/// # , WindowSetup::default()); }
241/// ```
242#[derive(
243 Debug, Clone, smart_default::SmartDefault, serde::Serialize, serde::Deserialize, PartialEq, Eq,
244)]
245pub struct WindowSetup {
246 /// The window title.
247 #[default(String::from("An easy, good game"))]
248 pub title: String,
249 /// Number of samples to use for multisample anti-aliasing.
250 #[default(NumSamples::One)]
251 pub samples: NumSamples,
252 /// Whether or not to enable vsync.
253 #[default = true]
254 pub vsync: bool,
255 /// A file path to the window's icon.
256 /// It takes a path rooted in the `resources` directory (see the [`filesystem`](../filesystem/index.html)
257 /// module for details), and an empty string results in a blank/default icon.
258 #[default(String::new())]
259 pub icon: String,
260 /// Whether or not to enable sRGB (gamma corrected color)
261 /// handling on the display.
262 #[default = true]
263 pub srgb: bool,
264}
265
266impl WindowSetup {
267 /// Set window title.
268 #[must_use]
269 pub fn title(mut self, title: &str) -> Self {
270 self.title = title.to_owned();
271 self
272 }
273
274 /// Set number of samples to use for multisample anti-aliasing.
275 #[must_use]
276 pub fn samples(mut self, samples: NumSamples) -> Self {
277 self.samples = samples;
278 self
279 }
280
281 /// Set whether vsync is enabled.
282 #[must_use]
283 pub fn vsync(mut self, vsync: bool) -> Self {
284 self.vsync = vsync;
285 self
286 }
287
288 /// Set the window's icon.
289 #[must_use]
290 pub fn icon(mut self, icon: &str) -> Self {
291 self.icon = icon.to_owned();
292 self
293 }
294
295 /// Set `sRGB` color mode.
296 #[must_use]
297 pub fn srgb(mut self, active: bool) -> Self {
298 self.srgb = active;
299 self
300 }
301}
302
303/// Possible graphics backends.
304/// The default is `Primary`.
305#[derive(
306 Debug,
307 Copy,
308 Clone,
309 serde::Serialize,
310 serde::Deserialize,
311 PartialEq,
312 Eq,
313 smart_default::SmartDefault,
314)]
315#[serde(tag = "type")]
316pub enum Backend {
317 /// Includes [`Backend::OnlyPrimary`] and also secondary APIs consisting of OpenGL and DX11.
318 ///
319 /// These APIs may have issues and may be deprecated by some platforms.
320 #[default]
321 All,
322 /// Primary APIs consisting of Vulkan, Metal and DX12.
323 ///
324 /// These APIs have first-class support from WGPU and from the platforms that support them.
325 OnlyPrimary,
326 /// Use the Khronos Vulkan API.
327 Vulkan,
328 /// Use the Apple Metal API.
329 Metal,
330 /// Use the Microsoft DirectX 12 API.
331 Dx12,
332 /// Use the Microsoft DirectX 11 API. This is not a recommended backend.
333 Dx11,
334 /// Use the Khronos OpenGL API. This is not a recommended backend.
335 Gl,
336 /// Use the WebGPU API. Targets the web.
337 BrowserWebGpu,
338}
339
340/// The possible number of samples for multisample anti-aliasing.
341#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
342pub enum NumSamples {
343 /// One sample
344 One = 1,
345 /* uncomment when WGPU supports more sample counts
346 /// Two samples
347 Two = 2,
348 */
349 /// Four samples
350 Four = 4,
351 /* uncomment when WGPU supports more sample counts
352 /// Eight samples
353 Eight = 8,
354 /// Sixteen samples
355 Sixteen = 16,
356 */
357}
358
359impl TryFrom<u8> for NumSamples {
360 type Error = GameError;
361 fn try_from(i: u8) -> Result<Self, Self::Error> {
362 match i {
363 1 => Ok(NumSamples::One),
364 //2 => Ok(NumSamples::Two),
365 4 => Ok(NumSamples::Four),
366 //8 => Ok(NumSamples::Eight),
367 //16 => Ok(NumSamples::Sixteen),
368 _ => Err(GameError::ConfigError(String::from(
369 "Invalid number of samples",
370 ))),
371 }
372 }
373}
374
375impl From<NumSamples> for u8 {
376 fn from(ns: NumSamples) -> u8 {
377 ns as u8
378 }
379}
380
381/// A structure containing configuration data
382/// for the game engine.
383///
384/// Defaults:
385///
386/// ```rust
387/// # use ggez::conf::*;
388/// # fn main() { assert_eq!(
389/// Conf {
390/// window_mode: WindowMode::default(),
391/// window_setup: WindowSetup::default(),
392/// backend: Backend::default(),
393/// }
394/// # , Conf::default()); }
395/// ```
396#[derive(
397 serde::Serialize, serde::Deserialize, Debug, PartialEq, smart_default::SmartDefault, Clone,
398)]
399pub struct Conf {
400 /// Window setting information that can be set at runtime
401 pub window_mode: WindowMode,
402 /// Window setting information that must be set at init-time
403 pub window_setup: WindowSetup,
404 /// Graphics backend configuration
405 pub backend: Backend,
406}
407
408impl Conf {
409 /// Same as `Conf::default()`
410 pub fn new() -> Self {
411 Self::default()
412 }
413
414 /// Load a TOML file from the given `Read` and attempts to parse
415 /// a `Conf` from it.
416 pub fn from_toml_file<R: io::Read>(file: &mut R) -> GameResult<Conf> {
417 let mut s = String::new();
418 let _ = file.read_to_string(&mut s)?;
419 let decoded = toml::from_str(&s)?;
420 Ok(decoded)
421 }
422
423 /// Saves the `Conf` to the given `Write` object,
424 /// formatted as TOML.
425 pub fn to_toml_file<W: io::Write>(&self, file: &mut W) -> GameResult {
426 let s = toml::to_vec(self)?;
427 file.write_all(&s)?;
428 Ok(())
429 }
430
431 /// Sets the window mode
432 #[must_use]
433 pub fn window_mode(mut self, window_mode: WindowMode) -> Self {
434 self.window_mode = window_mode;
435 self
436 }
437
438 /// Sets the backend
439 #[must_use]
440 pub fn backend(mut self, backend: Backend) -> Self {
441 self.backend = backend;
442 self
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use crate::conf;
449
450 /// Tries to encode and decode a `Conf` object
451 /// and makes sure it gets the same result it had.
452 #[test]
453 fn headless_encode_round_trip() {
454 let c1 = conf::Conf::new();
455 let mut writer = Vec::new();
456 c1.to_toml_file(&mut writer).unwrap();
457 let mut reader = writer.as_slice();
458 let c2 = conf::Conf::from_toml_file(&mut reader).unwrap();
459 assert_eq!(c1, c2);
460 }
461}