Skip to main content

pfx_gpu/screens/
mode.rs

1use serde::{Deserialize, Serialize};
2
3use super::device::Device;
4use crate::window::{
5    DisplayMode, ModePlan, MonitorChoice, Monitors, PlanError, PlatformCaps, Point, Size,
6    VideoModeChoice, WindowOps, apply_plan, plan_display_mode,
7};
8
9pub const DEFAULT_WINDOWED: Size = Size {
10    width: 1600,
11    height: 900,
12};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum WindowMode {
17    Windowed,
18    #[default]
19    Borderless,
20    Fullscreen,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct DisplaySettings {
26    #[serde(default)]
27    pub mode: WindowMode,
28    #[serde(default = "default_windowed")]
29    pub size: Size,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub monitor: Option<String>,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub position: Option<Point>,
34}
35
36fn default_windowed() -> Size {
37    DEFAULT_WINDOWED
38}
39
40impl Default for DisplaySettings {
41    fn default() -> Self {
42        Self {
43            mode: WindowMode::Borderless,
44            size: DEFAULT_WINDOWED,
45            monitor: None,
46            position: None,
47        }
48    }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Change {
53    Switched { from: WindowMode, to: WindowMode },
54    Same,
55    FixedOnDeck,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct Display {
60    device: Device,
61    settings: DisplaySettings,
62}
63
64impl Display {
65    pub fn new(device: Device, settings: DisplaySettings) -> Self {
66        let mut settings = settings;
67        if settings.size.width == 0 || settings.size.height == 0 {
68            settings.size = DEFAULT_WINDOWED;
69        }
70        Self { device, settings }
71    }
72
73    pub fn device(&self) -> Device {
74        self.device
75    }
76
77    pub fn settings(&self) -> &DisplaySettings {
78        &self.settings
79    }
80
81    pub fn mode(&self) -> WindowMode {
82        if self.device.is_deck() {
83            WindowMode::Fullscreen
84        } else {
85            self.settings.mode
86        }
87    }
88
89    pub fn display_mode(&self) -> DisplayMode {
90        self.mode_with(None)
91    }
92
93    pub fn display_mode_on(&self, monitors: &Monitors) -> DisplayMode {
94        self.mode_with(self.windowed_at(monitors))
95    }
96
97    pub fn windowed_at(&self, monitors: &Monitors) -> Option<Point> {
98        let relative = self.settings.position?;
99        let index = match &self.settings.monitor {
100            Some(name) => monitors
101                .list
102                .iter()
103                .position(|monitor| monitor.name.as_deref() == Some(name.as_str()))?,
104            None => monitors.current.or(monitors.primary)?,
105        };
106        let monitor = monitors.list.get(index)?;
107        let room = |span: u32, window: u32| i32::try_from(span.saturating_sub(window)).unwrap_or(0);
108        Some(Point {
109            x: monitor.position.x
110                + relative
111                    .x
112                    .clamp(0, room(monitor.size.width, self.settings.size.width)),
113            y: monitor.position.y
114                + relative
115                    .y
116                    .clamp(0, room(monitor.size.height, self.settings.size.height)),
117        })
118    }
119
120    fn mode_with(&self, position: Option<Point>) -> DisplayMode {
121        let monitor = match &self.settings.monitor {
122            Some(name) => MonitorChoice::Name(name.clone()),
123            None => MonitorChoice::Current,
124        };
125        if self.device.is_deck() {
126            return DisplayMode::BorderlessFullscreen {
127                monitor: MonitorChoice::Current,
128            };
129        }
130        match self.settings.mode {
131            WindowMode::Windowed => DisplayMode::Windowed {
132                size: self.settings.size,
133                position,
134            },
135            WindowMode::Borderless => DisplayMode::BorderlessFullscreen { monitor },
136            WindowMode::Fullscreen => DisplayMode::ExclusiveFullscreen {
137                monitor,
138                video_mode: VideoModeChoice::Native,
139            },
140        }
141    }
142
143    pub fn set_mode(&mut self, mode: WindowMode) -> Change {
144        if self.device.is_deck() {
145            return Change::FixedOnDeck;
146        }
147        let from = self.settings.mode;
148        if from == mode {
149            return Change::Same;
150        }
151        self.settings.mode = mode;
152        Change::Switched { from, to: mode }
153    }
154
155    pub fn resized(&mut self, size: Size) {
156        if !self.device.is_deck()
157            && self.settings.mode == WindowMode::Windowed
158            && size.width > 0
159            && size.height > 0
160        {
161            self.settings.size = size;
162        }
163    }
164
165    pub fn moved_to(&mut self, monitor: Option<String>) {
166        if !self.device.is_deck() {
167            self.settings.monitor = monitor;
168        }
169    }
170
171    pub fn placed(&mut self, monitor: Option<String>, position: Point) {
172        if !self.device.is_deck() && self.settings.mode == WindowMode::Windowed {
173            self.settings.monitor = monitor;
174            self.settings.position = Some(position);
175        }
176    }
177
178    pub fn plan(&self, platform: PlatformCaps, monitors: &Monitors) -> Result<ModePlan, PlanError> {
179        plan_display_mode(&self.display_mode_on(monitors), platform, monitors)
180    }
181
182    pub fn apply(
183        &self,
184        platform: PlatformCaps,
185        monitors: &Monitors,
186        window: &mut impl WindowOps,
187    ) -> Result<ModePlan, PlanError> {
188        let plan = self.plan(platform, monitors)?;
189        apply_plan(&plan, window);
190        Ok(plan)
191    }
192
193    pub fn switch(
194        &mut self,
195        mode: WindowMode,
196        platform: PlatformCaps,
197        monitors: &Monitors,
198        window: &mut impl WindowOps,
199    ) -> Result<(Change, Option<ModePlan>), PlanError> {
200        let change = self.set_mode(mode);
201        match change {
202            Change::Switched { from, .. } => match self.apply(platform, monitors, window) {
203                Ok(plan) => Ok((change, Some(plan))),
204                Err(error) => {
205                    self.settings.mode = from;
206                    Err(error)
207                }
208            },
209            Change::Same | Change::FixedOnDeck => Ok((change, None)),
210        }
211    }
212}
213
214#[cfg(feature = "window")]
215pub fn apply_display(
216    window: &winit::window::Window,
217    display: &Display,
218) -> Result<ModePlan, PlanError> {
219    let monitors = crate::window::monitors(window);
220    crate::window::apply_display_mode(window, &display.display_mode_on(&monitors))
221}