egui_winit/
window_settings.rs1use egui::ViewportBuilder;
2
3const PLACE_AFTER_CREATION: bool = cfg!(any(target_os = "macos", target_os = "windows"));
6
7#[derive(Clone, Copy, Debug)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15pub struct WindowGeometry {
16 outer_position_pixels: Option<egui::Pos2>,
18
19 scale_factor: f32,
26
27 inner_size_points: egui::Vec2,
29}
30
31impl WindowGeometry {
32 pub fn from_window(egui_zoom_factor: f32, window: &winit::window::Window) -> Self {
33 let scale_factor = window.scale_factor() as f32;
34 let inner_size = window.inner_size();
35 Self {
36 outer_position_pixels: window
37 .outer_position()
38 .ok()
39 .map(|p| egui::pos2(p.x as f32, p.y as f32)),
40 scale_factor,
41 inner_size_points: egui::vec2(inner_size.width as f32, inner_size.height as f32)
42 / (egui_zoom_factor * scale_factor),
43 }
44 }
45}
46
47#[derive(Clone, Copy, Debug)]
49#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
50pub struct WindowSettings {
51 normal_geometry: WindowGeometry,
53
54 fullscreen: bool,
55
56 maximized: bool,
57}
58
59impl WindowSettings {
60 pub fn from_window(
62 egui_zoom_factor: f32,
63 window: &winit::window::Window,
64 last_normal_geometry: Option<WindowGeometry>,
65 ) -> Self {
66 let fullscreen = window.fullscreen().is_some();
67 let maximized = window.is_maximized();
68 let normal_geometry = last_normal_geometry
69 .filter(|_| fullscreen || maximized)
70 .unwrap_or_else(|| WindowGeometry::from_window(egui_zoom_factor, window));
71 Self {
72 normal_geometry,
73 fullscreen,
74 maximized,
75 }
76 }
77
78 pub fn inner_size_points(&self) -> egui::Vec2 {
79 self.normal_geometry.inner_size_points
80 }
81
82 pub fn normal_geometry(&self) -> WindowGeometry {
84 self.normal_geometry
85 }
86
87 pub fn initialize_viewport_builder(
88 &self,
89 egui_zoom_factor: f32,
90 mut viewport_builder: ViewportBuilder,
91 ) -> ViewportBuilder {
92 profiling::function_scope!();
93
94 let geometry = &self.normal_geometry;
95
96 if let Some(pos) = geometry.outer_position_pixels {
97 viewport_builder =
101 viewport_builder.with_position(pos / (egui_zoom_factor * geometry.scale_factor));
102 }
103
104 viewport_builder = viewport_builder.with_inner_size(geometry.inner_size_points);
105 if PLACE_AFTER_CREATION {
106 viewport_builder
109 .with_fullscreen(false)
110 .with_maximized(false)
111 } else {
112 viewport_builder
113 .with_fullscreen(self.fullscreen)
114 .with_maximized(self.maximized)
115 }
116 }
117
118 pub fn initialize_window(&self, window: &winit::window::Window) {
119 if !PLACE_AFTER_CREATION {
120 return;
121 }
122
123 let geometry = &self.normal_geometry;
129 if let Some(pos) = geometry.outer_position_pixels {
130 if cfg!(target_os = "macos") {
131 let pos = pos / geometry.scale_factor;
133 #[expect(
134 clippy::disallowed_types,
135 reason = "these are OS points, unaffected by egui zoom"
136 )]
137 window.set_outer_position(winit::dpi::LogicalPosition::new(pos.x, pos.y));
138 } else {
139 window.set_outer_position(winit::dpi::PhysicalPosition::new(pos.x, pos.y));
140 }
141 }
142
143 if self.fullscreen && cfg!(target_os = "macos") {
144 window.set_maximized(true);
148 } else if self.fullscreen {
149 window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
150 } else if self.maximized {
151 crate::set_maximized(window, true);
152 }
153 }
154
155 pub fn before_first_show(&self, window: &winit::window::Window) {
157 if cfg!(target_os = "macos") && self.fullscreen {
158 window.set_maximized(false);
161 window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
162 }
163 }
164
165 pub fn after_first_show(&self, window: &winit::window::Window) {
167 if is_x11(window) {
170 if self.fullscreen {
171 window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
172 } else if self.maximized {
173 window.set_maximized(true);
174 }
175 }
176 }
177
178 pub fn clamp_size_to_sane_values(&mut self, largest_monitor_size_points: egui::Vec2) {
179 use egui::NumExt as _;
180
181 let size = &mut self.normal_geometry.inner_size_points;
182
183 let min_size = egui::Vec2::splat(64.0);
185 *size = size.at_least(min_size);
186
187 *size = size.at_most(largest_monitor_size_points);
190 }
191
192 pub fn clamp_position_to_monitors(
193 &mut self,
194 egui_zoom_factor: f32,
195 event_loop: &winit::event_loop::ActiveEventLoop,
196 ) {
197 let geometry = &mut self.normal_geometry;
203 let Some(pos_px) = geometry.outer_position_pixels else {
204 return;
205 };
206
207 if cfg!(target_os = "macos") {
208 let pos = pos_px / geometry.scale_factor;
210 let is_on_a_monitor = event_loop.available_monitors().any(|monitor| {
211 (monitor_rect_px(&monitor) / monitor.scale_factor() as f32).contains(pos)
212 });
213 if !is_on_a_monitor {
214 geometry.outer_position_pixels = None;
215 }
216 } else if cfg!(target_os = "windows") {
217 let mut pos_px = pos_px;
218 clamp_pos_to_monitors(
219 egui_zoom_factor,
220 event_loop,
221 geometry.inner_size_points,
222 &mut pos_px,
223 );
224 geometry.outer_position_pixels = Some(pos_px);
225 }
226 }
227}
228
229#[cfg(feature = "x11")]
230fn is_x11(window: &winit::window::Window) -> bool {
231 use raw_window_handle::{HasWindowHandle as _, RawWindowHandle};
232 matches!(
233 window.window_handle().map(|h| h.as_raw()),
234 Ok(RawWindowHandle::Xlib(_) | RawWindowHandle::Xcb(_))
235 )
236}
237
238#[cfg(not(feature = "x11"))]
239fn is_x11(_window: &winit::window::Window) -> bool {
240 false
241}
242
243fn find_active_monitor(
244 egui_zoom_factor: f32,
245 event_loop: &winit::event_loop::ActiveEventLoop,
246 window_size_pts: egui::Vec2,
247 position_px: &egui::Pos2,
248) -> Option<winit::monitor::MonitorHandle> {
249 profiling::function_scope!();
250 let monitors = event_loop.available_monitors();
251
252 let Some(mut active_monitor) = event_loop
254 .primary_monitor()
255 .or_else(|| event_loop.available_monitors().next())
256 else {
257 return None; };
259
260 let mut active_monitor_overlap = 0.0;
261 for monitor in monitors {
262 let window_size_px = window_size_pts * (egui_zoom_factor * monitor.scale_factor() as f32);
263 let window_rect = egui::Rect::from_min_size(*position_px, window_size_px);
264 let overlap = window_rect.intersect(monitor_rect_px(&monitor)).area();
265
266 if active_monitor_overlap < overlap {
267 active_monitor = monitor;
268 active_monitor_overlap = overlap;
269 }
270 }
271
272 Some(active_monitor)
273}
274
275fn monitor_rect_px(monitor: &winit::monitor::MonitorHandle) -> egui::Rect {
276 let pos = monitor.position();
277 let size = monitor.size();
278 egui::Rect::from_min_size(
279 egui::pos2(pos.x as f32, pos.y as f32),
280 egui::vec2(size.width as f32, size.height as f32),
281 )
282}
283
284fn clamp_pos_to_monitors(
285 egui_zoom_factor: f32,
286 event_loop: &winit::event_loop::ActiveEventLoop,
287 window_size_pts: egui::Vec2,
288 position_px: &mut egui::Pos2,
289) {
290 profiling::function_scope!();
291
292 let Some(active_monitor) =
293 find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px)
294 else {
295 return; };
297
298 let mut window_size_px =
299 window_size_pts * (egui_zoom_factor * active_monitor.scale_factor() as f32);
300 if cfg!(target_os = "windows") {
302 window_size_px += egui::Vec2::new(
303 0.0,
304 32.0 * egui_zoom_factor * active_monitor.scale_factor() as f32,
305 );
306 }
307 let monitor_rect = monitor_rect_px(&active_monitor);
308
309 let window_size = (monitor_rect.size() - window_size_px).max(egui::Vec2::ZERO);
311 *position_px = position_px.clamp(monitor_rect.min, monitor_rect.min + window_size);
315}