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}