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}