Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/windowing/winit/window.rs
1#[cfg(target_os = "linux")]
2mod x11;
3 
4#[cfg(windows)]
5mod windows_wm;
6 
7use std::collections::HashMap;
8use std::sync::Arc;
9#[cfg(windows)]
10use std::sync::LazyLock;
11use std::{
12 cell::{Cell, OnceCell, RefCell},
13 rc::Rc,
14};
15 
16use anyhow::{Context as _, Result};
17use itertools::Itertools;
18use lazy_static::lazy_static;
19use parking_lot::Mutex;
20use pathfinder_geometry::rect::RectF;
21use pathfinder_geometry::vector::{vec2f, Vector2F};
22use wgpu::rwh::HasDisplayHandle;
23use wgpu::{AdapterInfo, CompositeAlphaMode};
24use winit::dpi::PhysicalPosition;
25use winit::error::ExternalError;
26use winit::event_loop::{ActiveEventLoop, EventLoopProxy, OwnedDisplayHandle};
27#[cfg(not(target_family = "wasm"))]
28use winit::monitor::MonitorHandle;
29#[cfg(windows)]
30use winit::platform::windows::{BackdropType, WindowExtWindows};
31use winit::window::{CursorIcon, ResizeDirection, UserAttentionType, WindowLevel};
32use winit::{
33 dpi::{LogicalPosition, LogicalSize, PhysicalSize, Position, Size},
34 window::Fullscreen,
35};
36 
37#[cfg(not(target_family = "wasm"))]
38use crate::platform::WindowBounds;
39use crate::platform::{
40 self, Cursor, FullscreenState, GraphicsBackend, TerminationMode, WindowFocusBehavior,
41 WindowOptions, WindowStyle,
42};
43use crate::rendering::{
44 wgpu::{
45 adapter_has_rendering_offset_bug, from_wgpu_backend, renderer, to_wgpu_backend, Renderer,
46 Resources,
47 },
48 GPUPowerPreference, GlyphConfig, OnGPUDeviceSelected,
49};
50use crate::windowing::WindowCallbacks;
51use crate::{fonts, geometry, Scene};
52use crate::{DisplayId, DisplayIdx, OptionalPlatformWindow, WindowId};
53 
54use super::app::CustomEvent;
55 
56#[cfg(windows)]
57use super::windows::{get_system_caption_button_bounds, set_window_attribute, WindowAttributeErr};
58#[cfg(windows)]
59use windows::Win32::Graphics::Dwm;
60 
61/// The inner margin from the edges of the window within which the mouse can drag to resize the
62/// window. Note that this value is a logical size, not a physical size. It can be converted to a
63/// physical size by multiplying by the scale factor.
64const DRAG_RESIZE_MARGIN: f32 = 4.0;
65 
66/// This must match the ID in the embedded resource file in `app\build.rs`
67#[cfg(windows)]
68const IDI_ICON: u16 = 0x101;
69 
70cfg_if::cfg_if! {
71 if #[cfg(any(test, feature = "integration_tests"))] {
72 /// The window cannot be resized smaller than this.
73 /// TODO(CORE-1891) Instead of being hard-coded, this should be configurable by the user via
74 /// [`crate::platform::WindowOptions`].
75 #[cfg_attr(target_family = "wasm", allow(dead_code))]
76 pub(in crate::windowing::winit) const MIN_WINDOW_SIZE: LogicalSize<f64> =
77 LogicalSize::new(124., 34.);
78 } else {
79 #[cfg_attr(target_family = "wasm", allow(dead_code))]
80 pub(in crate::windowing::winit) const MIN_WINDOW_SIZE: LogicalSize<f64> =
81 LogicalSize::new(480., 192.);
82 }
83}
84 
85lazy_static! {
86 static ref DEFAULT_WINDOW_SIZE: Vector2F = Vector2F::new(1280., 800.);
87}
88 
89pub(crate) struct WindowManager {
90 windows: HashMap<WindowId, Rc<Window>>,
91 event_loop_proxy: EventLoopProxy<CustomEvent>,
92 /// We assume this won't change throughout the life of the Warp process.
93 os_window_manager_name: OnceCell<Option<String>>,
94 /// This is a client for talking to the Xorg server directly instead of through winit.
95 #[cfg(target_os = "linux")]
96 x11_manager: Option<x11::X11Manager>,
97 display_handle: OwnedDisplayHandle,
98}
99 
100impl WindowManager {
101 pub(crate) fn new(
102 event_loop_proxy: EventLoopProxy<CustomEvent>,
103 display_handle: OwnedDisplayHandle,
104 ) -> Self {
105 Self {
106 windows: Default::default(),
107 event_loop_proxy,
108 os_window_manager_name: Default::default(),
109 #[cfg(target_os = "linux")]
110 x11_manager: match x11::X11Manager::new() {
111 Ok(x11_manager) => Some(x11_manager),
112 Err(err) => {
113 log::error!("error creating connection to Xorg server: {err:?}");
114 None
115 }
116 },
117 display_handle,
118 }
119 }
120 
121 /// Get winit's determination of the scale factor.
122 ///
123 /// In X11, the scale factor is a per-screen setting. Note that a "screen" in X11 is not the
124 /// same thing as a physical monitor, but a grouping of monitors into a single coordinate
125 /// space. All our app's windows must be on the same screen, and hence will have the same scale
126 /// factor. For more in-depth explanation:
127 /// https://github.com/warpdotdev/warp-internal/pull/8431#discussion_r1460629912
128 #[cfg(target_os = "linux")]
129 fn get_x11_backing_scale_factor(&self) -> f32 {
130 use crate::platform::WindowContext;
131 
132 self.windows
133 .values()
134 .next()
135 .map(|window| window.backing_scale_factor())
136 .unwrap_or(1.)
137 }
138}
139 
140impl platform::WindowManager for WindowManager {
141 fn open_window(
142 &mut self,
143 window_id: WindowId,
144 window_options: WindowOptions,
145 callbacks: WindowCallbacks,
146 ) -> Result<()> {
147 self.event_loop_proxy.send_event(CustomEvent::OpenWindow {
148 window_id,
149 window_options,
150 })?;
151 self.windows
152 .insert(window_id, Rc::new(super::window::Window::new(callbacks)));
153 Ok(())
154 }
155 
156 fn platform_window(&self, window_id: WindowId) -> OptionalPlatformWindow {
157 self.windows
158 .get(&window_id)
159 .map(Rc::clone)
160 .map(|inner| inner as Rc<dyn crate::platform::Window>)
161 }
162 
163 fn remove_window(&mut self, window_id: WindowId) {
164 self.windows.remove(&window_id);
165 }
166 
167 fn active_window_id(&self) -> Option<WindowId> {
168 // Weirdly, it is possible for "has_focus" to return `true` for hidden windows, so also
169 // check "is_visible".
170 self.windows
171 .iter()
172 .find_map(|(id, window)| (window.has_focus() && window.is_visible()).then_some(*id))
173 }
174 
175 fn key_window_is_modal_panel(&self) -> bool {
176 false
177 }
178 
179 fn app_is_active(&self) -> bool {
180 self.active_window_id().is_some()
181 }
182 
183 /// Activate the app as defined in MacOS.
184 ///
185 /// The concept of "activating" an app doesn't really exist on non-Mac platforms. This is a
186 /// concept which we've borrowed from MacOS AppKit. Activating means:
187 ///
188 /// 1. Make all windows visible if they were hidden.
189 /// 2. Stack all windows on top of other apps' windows.
190 /// 3. Give focus to the frontmost window in this app.
191 ///
192 /// See the AppKit docs for more details:
193 /// https://developer.apple.com/documentation/appkit/nsapplication/1428468-activateignoringotherapps?language=objc
194 fn activate_app(&self, last_active_window: Option<WindowId>) -> Option<WindowId> {
195 let mut next_active_window: Option<WindowId> = None;
196 
197 // We want to loop through all windows and stack each one on top of other windows.
198 for (id, window) in &self.windows {
199 // We want to be sure that the window which ends up with the focus in the end is the
200 // "frontmost" window, or the one which was most recently active/focused. Therefore,
201 // the last active window needs to be focused last. So, skip that window in this loop.
202 if last_active_window.is_some_and(|window_id| window_id == *id) {
203 continue;
204 }
205 
206 window.focus();
207 next_active_window = Some(*id);
208 }
209 
210 // Finally, go back and focus the last active window to make sure it ends up having the
211 // focus.
212 if let Some(window_id) = last_active_window {
213 if let Some(window) = self.windows.get(&window_id) {
214 window.focus();
215 next_active_window = Some(window_id);
216 }
217 }
218 
219 next_active_window
220 }
221 
222 fn show_window_and_focus_app(&self, window_id: WindowId, behavior: WindowFocusBehavior) {
223 debug_assert!(matches!(behavior, WindowFocusBehavior::BringToFront));
224 if let Some(window) = self.windows.get(&window_id) {
225 window.focus();
226 }
227 }
228 
229 fn hide_app(&self) {
230 for window in self.windows.values() {
231 window.set_visible(false);
232 }
233 }
234 
235 fn hide_window(&self, window_id: WindowId) {
236 if let Some(window) = self.windows.get(&window_id) {
237 window.set_visible(false);
238 }
239 }
240 
241 fn set_window_bounds(&self, window_id: WindowId, bound: RectF) {
242 if let Some(window) = self.windows.get(&window_id) {
243 window.set_bounds(bound);
244 }
245 }
246 
247 fn set_all_windows_background_blur_radius(&self, _blur_radius_pixels: u8) {
248 // unsupported on Linux and Windows
249 // https://docs.rs/winit/latest/winit/window/struct.Window.html#method.set_blur
250 }
251 
252 #[cfg_attr(not(windows), allow(unused_variables))]
253 fn set_all_windows_background_blur_texture(&self, use_blur_texture: bool) {
254 #[cfg(windows)]
255 {
256 let new_backdrop_texture = if use_blur_texture {
257 BackdropType::TransientWindow
258 } else {
259 BackdropType::None
260 };
261 for window in self.windows.values() {
262 if let Some(inner) = window.inner.borrow().as_ref() {
263 inner.window.set_system_backdrop(new_backdrop_texture);
264 }
265 }
266 }
267 }
268 
269 fn set_window_title(&self, window_id: WindowId, title: &str) {
270 if let Some(window) = self.windows.get(&window_id) {
271 window.set_title(title);
272 }
273 }
274 
275 fn close_window_async(&self, window_id: WindowId, termination_mode: TerminationMode) {
276 self.event_loop_proxy
277 .send_event(CustomEvent::CloseWindow {
278 window_id,
279 termination_mode,
280 })
281 .expect("event loop should still exist");
282 }
283 
284 fn active_display_bounds(&self) -> RectF {
285 cfg_if::cfg_if! {
286 if #[cfg(target_os = "linux")] {
287 self.x11_manager
288 .as_ref()
289 .and_then(|x11_manager| match x11_manager.get_active_monitor() {
290 Ok(result) => Some(result),
291 Err(err) => {
292 log::warn!("Error getting active display bounds: {err:?}");
293 None
294 }
295 })
296 .map(|(_, monitor_bounds)| x11::physical_bounds_to_rect(&monitor_bounds, self.get_x11_backing_scale_factor()))
297 .unwrap_or(RectF::new(Vector2F::zero(), *DEFAULT_WINDOW_SIZE))
298 } else if #[cfg(windows)] {
299 self.get_active_monitor_logical_bounds().unwrap_or(RectF::new(Vector2F::zero(), *DEFAULT_WINDOW_SIZE))
300 } else {
301 RectF::new(Vector2F::zero(), *DEFAULT_WINDOW_SIZE)
302 }
303 }
304 }
305 
306 fn active_display_id(&self) -> DisplayId {
307 cfg_if::cfg_if! {
308 if #[cfg(target_os = "linux")] {
309 self.x11_manager
310 .as_ref()
311 .and_then(|x11_manager| match x11_manager.get_active_monitor() {
312 Ok(result) => Some(result),
313 Err(err) => {
314 log::warn!("Error getting active display ID: {err:?}");
315 None
316 }
317 })
318 .map(|(i, _)| DisplayId::from(i))
319 .unwrap_or(0.into())
320 } else if #[cfg(windows)] {
321 self.get_current_monitor_id().unwrap_or(0.into())
322 } else {
323 0.into()
324 }
325 }
326 }
327 
328 fn display_count(&self) -> usize {
329 // Although winit provides a `Window::available_monitors` method, it caches the result and
330 // never invalidates the cache. We need to drop down to X11 directly to ensure we read a
331 // fresh value.
332 cfg_if::cfg_if! {
333 if #[cfg(target_os = "linux")] {
334 self.x11_manager
335 .as_ref()
336 .and_then(|x11_manager| x11_manager.list_monitor_bounds().ok())
337 .as_ref()
338 .map(|monitors| monitors.len())
339 .unwrap_or(1)
340 } else if #[cfg(windows)] {
341 self.get_available_monitor_count().unwrap_or(1_usize)
342 } else {
343 1
344 }
345 }
346 }
347 
348 fn bounds_for_display_idx(&self, display_idx: DisplayIdx) -> Option<RectF> {
349 cfg_if::cfg_if! {
350 if #[cfg(target_os = "linux")] {
351 let idx = match display_idx {
352 DisplayIdx::Primary => 0,
353 DisplayIdx::External(idx) => idx + 1,
354 };
355 self.x11_manager
356 .as_ref()
357 .and_then(|x11_manager| x11_manager.list_monitor_bounds().ok())
358 .as_ref()
359 .and_then(|monitors| monitors.get(idx))
360 .map(|monitor| x11::physical_bounds_to_rect(monitor, self.get_x11_backing_scale_factor()))
361 } else if #[cfg(windows)] {
362 self.get_monitor_bounds_for_display_idx(display_idx).ok()
363 } else {
364 let _ = display_idx;
365 None
366 }
367 }
368 }
369 
370 fn active_cursor_position_updated(&self) {
371 self.event_loop_proxy
372 .send_event(CustomEvent::ActiveCursorPositionUpdated)
373 .expect("event loop should still exist");
374 }
375 
376 fn windowing_system(&self) -> Option<crate::windowing::System> {
377 self.display_handle
378 .display_handle()
379 .ok()?
380 .as_raw()
381 .try_into()
382 .ok()
383 }
384 
385 fn is_tiling_window_manager(&self) -> bool {
386 self.os_window_manager_name()
387 .map(|name| is_tiling_window_manager(name.as_str()))
388 .unwrap_or(false)
389 }
390 
391 fn os_window_manager_name(&self) -> Option<String> {
392 self.os_window_manager_name
393 .get_or_init(|| {
394 cfg_if::cfg_if! {
395 if #[cfg(target_os = "linux")] {
396 get_os_window_manager_name_internal(self.x11_manager.as_ref())
397 } else {
398 None
399 }
400 }
401 })
402 .clone()
403 }
404}
405 
406#[cfg(target_os = "linux")]
407pub fn get_os_window_manager_name() -> Option<String> {
408 get_os_window_manager_name_internal(x11::X11Manager::new().ok().as_ref())
409}
410 
411#[cfg(target_os = "linux")]
412fn get_os_window_manager_name_internal(x11_manager: Option<&x11::X11Manager>) -> Option<String> {
413 super::linux::look_for_wayland_compositor()
414 .or_else(|| x11_manager.and_then(|manager| manager.os_window_manager_name().ok()))
415}
416 
417fn is_tiling_window_manager(name: &str) -> bool {
418 cfg_if::cfg_if! {
419 if #[cfg(target_os = "linux")] {
420 super::linux::is_tiling_window_manager(name)
421 } else {
422 let _ = name;
423 false
424 }
425 }
426}
427 
428/// Some additional state we need to track in memory for integration tests.
429struct IntegrationTestAppState {
430 /// A list of window IDs representing the order of visible windows, with
431 /// the frontmost window at the end of the list.
432 window_id_stack: Vec<WindowId>,
433}
434 
435/// Running the integration tests cases in parallel causes some conflicts in the window state which
436/// breaks the tests. In order to keep the state for each "instance" of the
437/// [`platform::current::App`] separate, we track it separately here to scope it down.
438pub(crate) struct IntegrationTestWindowManager {
439 window_manager: WindowManager,
440 app_state: Mutex<IntegrationTestAppState>,
441}
442 
443impl IntegrationTestWindowManager {
444 pub(crate) fn new(
445 event_loop_proxy: EventLoopProxy<CustomEvent>,
446 display_handle: OwnedDisplayHandle,
447 ) -> Self {
448 Self {
449 window_manager: WindowManager::new(event_loop_proxy, display_handle),
450 app_state: Mutex::new(IntegrationTestAppState {
451 window_id_stack: Default::default(),
452 }),
453 }
454 }
455}
456 
457impl platform::WindowManager for IntegrationTestWindowManager {
458 fn open_window(
459 &mut self,
460 window_id: WindowId,
461 window_options: WindowOptions,
462 callbacks: WindowCallbacks,
463 ) -> Result<()> {
464 let window_will_be_focused = window_options.style != platform::WindowStyle::NotStealFocus;
465 self.window_manager
466 .open_window(window_id, window_options, callbacks)?;
467 if window_will_be_focused {
468 let mut app_state = self.app_state.lock();
469 app_state.window_id_stack.push(window_id);
470 }
471 Ok(())
472 }
473 
474 fn platform_window(&self, window_id: WindowId) -> OptionalPlatformWindow {
475 self.window_manager.platform_window(window_id)
476 }
477 
478 fn remove_window(&mut self, window_id: WindowId) {
479 self.window_manager.remove_window(window_id)
480 }
481 
482 fn active_window_id(&self) -> Option<WindowId> {
483 self.app_is_active()
484 .then(|| self.app_state.lock().window_id_stack.last().cloned())
485 .flatten()
486 }
487 
488 fn key_window_is_modal_panel(&self) -> bool {
489 self.window_manager.key_window_is_modal_panel()
490 }
491 
492 fn app_is_active(&self) -> bool {
493 // always assume active for tests.
494 true
495 }
496 
497 fn activate_app(&self, last_active_window: Option<WindowId>) -> Option<WindowId> {
498 self.window_manager.activate_app(last_active_window)
499 }
500 
501 fn show_window_and_focus_app(&self, window_id: WindowId, behavior: WindowFocusBehavior) {
502 debug_assert!(matches!(behavior, WindowFocusBehavior::BringToFront));
503 self.window_manager
504 .show_window_and_focus_app(window_id, behavior);
505 
506 let mut app_state = self.app_state.lock();
507 
508 // Move the window to the top of the stack.
509 app_state.window_id_stack.retain(|id| *id != window_id);
510 app_state.window_id_stack.push(window_id);
511 }
512 
513 fn hide_app(&self) {
514 self.window_manager.hide_app();
515 }
516 
517 fn hide_window(&self, window_id: WindowId) {
518 self.window_manager.hide_window(window_id);
519 // Remove the hidden window from the window stack.
520 self.app_state
521 .lock()
522 .window_id_stack
523 .retain(|id| *id != window_id);
524 }
525 
526 fn set_window_bounds(&self, window_id: WindowId, bound: RectF) {
527 self.window_manager.set_window_bounds(window_id, bound)
528 }
529 
530 fn set_all_windows_background_blur_radius(&self, blur_radius_pixels: u8) {
531 self.window_manager
532 .set_all_windows_background_blur_radius(blur_radius_pixels)
533 }
534 
535 fn set_all_windows_background_blur_texture(&self, use_blur_texture: bool) {
536 self.window_manager
537 .set_all_windows_background_blur_texture(use_blur_texture)
538 }
539 
540 fn set_window_title(&self, window_id: WindowId, title: &str) {
541 self.window_manager.set_window_title(window_id, title)
542 }
543 
544 fn close_window_async(&self, window_id: WindowId, termination_mode: TerminationMode) {
545 self.window_manager
546 .close_window_async(window_id, termination_mode);
547 // Remove the closed window from the window stack.
548 self.app_state
549 .lock()
550 .window_id_stack
551 .retain(|id| *id != window_id);
552 }
553 
554 fn active_display_bounds(&self) -> geometry::rect::RectF {
555 self.window_manager.active_display_bounds()
556 }
557 
558 fn active_display_id(&self) -> DisplayId {
559 self.window_manager.active_display_id()
560 }
561 
562 fn display_count(&self) -> usize {
563 1
564 }
565 
566 fn bounds_for_display_idx(&self, idx: DisplayIdx) -> Option<RectF> {
567 self.window_manager.bounds_for_display_idx(idx)
568 }
569 
570 fn active_cursor_position_updated(&self) {
571 // no-op
572 }
573 
574 fn windowing_system(&self) -> Option<crate::windowing::System> {
575 self.window_manager.windowing_system()
576 }
577 
578 fn os_window_manager_name(&self) -> Option<String> {
579 self.window_manager.os_window_manager_name()
580 }
581 
582 fn is_tiling_window_manager(&self) -> bool {
583 self.window_manager.is_tiling_window_manager()
584 }
585}
586 
587fn window_level_for_style(style: WindowStyle) -> WindowLevel {
588 match style {
589 WindowStyle::NotStealFocus => WindowLevel::AlwaysOnBottom,
590 WindowStyle::Pin => WindowLevel::AlwaysOnTop,
591 _ => WindowLevel::Normal,
592 }
593}
594 
595/// If the selected adapter has a known rendering offset bug, enable native window decorations
596/// to work around it. See: https://github.com/warpdotdev/Warp/issues/6120
597fn enable_decorations_if_needed(window: &winit::window::Window, adapter_info: &AdapterInfo) {
598 if adapter_has_rendering_offset_bug(adapter_info) {
599 log::warn!(
600 "Enabling native window decorations to work around a rendering offset bug in the \
601 selected GPU adapter ({}). See: https://github.com/warpdotdev/Warp/issues/6120",
602 adapter_info.name,
603 );
604 window.set_decorations(true);
605 }
606}
607 
608struct RenderingResources {
609 resources: Resources,
610 renderer: Renderer,
611}
612 
613struct Inner {
614 window: Arc<winit::window::Window>,
615 #[cfg(windows)]
616 is_cloaked: bool,
617 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
618 gpu_power_preference: GPUPowerPreference,
619 backend_preference: Option<wgpu::Backend>,
620 rendering_resources: Option<RenderingResources>,
621 /// Callback that reports when a GPU device is selected. We need to store this on the window
622 /// because we may attempt to recreate resources (which in turns reselects a GPU device) to
623 /// handle cases where wgpu treats the device as "lost" when the system is waking up from sleep.
624 on_gpu_device_selected: Box<OnGPUDeviceSelected>,
625 active_drag_resize_direction: Option<ResizeDirection>,
626 surface_size: Vector2F,
627 surface_requires_reconfiguration: bool,
628 /// The window level isn't just needed at window creation. The window level may get un-set in
629 /// some desktop environments, e.g. when the window is hidden and un-hidden. We don't want this
630 /// behavior, and so we must re-set the window level. In order to re-set it, we must store the
631 /// intended window level.
632 level: WindowLevel,
633}
634 
635impl Inner {
636 /// Returns the physical size of the current window.
637 fn physical_size(&self) -> PhysicalSize<u32> {
638 self.window.inner_size()
639 }
640}
641 
642pub(super) const DEFAULT_TITLEBAR_HEIGHT: f32 = 35.0;
643 
644/// A one-shot callback invoked with a captured frame. See [`platform::WindowContext::request_frame_capture`].
645type FrameCaptureCallback = Box<dyn FnOnce(platform::CapturedFrame) + Send + 'static>;
646 
647pub(super) struct Window {
648 pub(super) callbacks: WindowCallbacks,
649 inner: RefCell<Option<Inner>>,
650 scene: RefCell<Option<Rc<Scene>>>,
651 /// The height, in logical pixels, of the "titlebar region" at the top of the window.
652 ///
653 /// The "titlebar region" is a fixed region at the top of the window which is treated as an
654 /// invisible title bar insofar as it triggers window dragging when clicked-and-dragged, or
655 /// maximize/restore when double-clicked.
656 titlebar_height: Cell<f32>,
657 capture_callback: RefCell<Option<FrameCaptureCallback>>,
658}
659 
660impl Window {
661 pub fn new(callbacks: WindowCallbacks) -> Self {
662 Self {
663 callbacks,
664 inner: Default::default(),
665 scene: Default::default(),
666 titlebar_height: Cell::new(DEFAULT_TITLEBAR_HEIGHT),
667 capture_callback: RefCell::new(None),
668 }
669 }
670 
671 pub fn titlebar_height(&self) -> f32 {
672 self.titlebar_height.get()
673 }
674 
675 pub fn open_window(
676 &self,
677 window_target: &ActiveEventLoop,
678 window_options: WindowOptions,
679 window_class: &Option<String>,
680 tiling_window_manager: bool,
681 downrank_non_nvidia_vulkan_adapters: bool,
682 ) -> Result<winit::window::WindowId> {
683 let window = create_window(
684 window_target,
685 &window_options,
686 window_class,
687 tiling_window_manager,
688 )?;
689 
690 let window = Arc::new(window);
691 
692 // Use the window's size as the initial surface size, ensuring that the
693 // surface has a minimum size of 1 along each dimension.
694 let initial_surface_size = window.inner_size().to_vec2f().max(Vector2F::splat(1.));
695 
696 let gpu_power_preference = window_options.gpu_power_preference;
697 let backend_preference = window_options.backend_preference.map(to_wgpu_backend);
698 let resources = Resources::new(
699 window.clone(),
700 gpu_power_preference,
701 backend_preference,
702 &window_options.on_gpu_device_info_reported,
703 initial_surface_size,
704 downrank_non_nvidia_vulkan_adapters,
705 )?;
706 
707 enable_decorations_if_needed(&window, &resources.adapter.get_info());
708 
709 let renderer = Renderer::new(&resources, GlyphConfig::default());
710 
711 let window_id = window.id();
712 self.inner.replace(Some(Inner {
713 window,
714 #[cfg(windows)]
715 is_cloaked: true,
716 gpu_power_preference,
717 backend_preference,
718 rendering_resources: Some(RenderingResources {
719 resources,
720 renderer,
721 }),
722 on_gpu_device_selected: window_options.on_gpu_device_info_reported,
723 active_drag_resize_direction: None,
724 surface_size: initial_surface_size,
725 surface_requires_reconfiguration: false,
726 level: window_level_for_style(window_options.style),
727 }));
728 Ok(window_id)
729 }
730 
731 pub fn update_size_if_needed(&self) -> Result<(), renderer::Error> {
732 let mut inner = self.inner.borrow_mut();
733 let Some(inner) = inner.as_mut() else {
734 log::warn!("Tried to render a window before it had been fully initialized");
735 return Ok(());
736 };
737 
738 let window_size = inner.physical_size().to_vec2f();
739 
740 let Some(RenderingResources { resources, .. }) = inner.rendering_resources.as_mut() else {
741 return Ok(());
742 };
743 
744 // This log exists to let us know if we can eliminate the size
745 // comparison and rely solely on the boolean flag, which is set to true
746 // when we receive a window resize event. We're logging this in debug
747 // builds only so that we can (hopefully) understand when this occurs
748 // and why, but it's not something we need to log in production.
749 #[cfg(debug_assertions)]
750 if inner.surface_size != window_size && !inner.surface_requires_reconfiguration {
751 log::info!("surface size changed but does not require reconfiguration!");
752 }
753 
754 // If the window size has changed since we last configured the
755 // underlying surface, reconfigure the surface with the new size.
756 if inner.surface_requires_reconfiguration || inner.surface_size != window_size {
757 resources.update_surface_size(window_size)?;
758 inner.surface_size = window_size;
759 inner.surface_requires_reconfiguration = false;
760 }
761 
762 Ok(())
763 }
764 
765 pub fn render(
766 &self,
767 new_scene: Option<Rc<Scene>>,
768 font_cache: &fonts::Cache,
769 ) -> Result<(), renderer::Error> {
770 let mut scene = self.scene.borrow_mut();
771 
772 if scene.is_none() {
773 *scene = new_scene;
774 }
775 
776 let Some(scene) = scene.clone() else {
777 log::error!(
778 "A redraw of the window was requested but no scene was available to render"
779 );
780 return Ok(());
781 };
782 
783 let mut inner = self.inner.borrow_mut();
784 let Some(inner) = inner.as_mut() else {
785 log::warn!("Tried to render a window before it had been fully initialized");
786 return Ok(());
787 };
788 
789 let Some(RenderingResources {
790 resources,
791 renderer,
792 }) = inner.rendering_resources.as_mut()
793 else {
794 return Ok(());
795 };
796 
797 let capture_callback = self.capture_callback.borrow_mut().take();
798 let window = &inner.window;
799 renderer.render(
800 scene.as_ref(),
801 resources,
802 &|glyph_key, scale, subpixel_alignment, glyph_config, format| {
803 font_cache.rasterized_glyph(
804 glyph_key,
805 scale,
806 subpixel_alignment,
807 glyph_config,
808 format,
809 )
810 },
811 &|glyph_key, scale, alignment| {
812 font_cache.glyph_raster_bounds(glyph_key, scale, alignment)
813 },
814 inner.surface_size,
815 Some(Box::new(|| {
816 window.pre_present_notify();
817 })),
818 capture_callback,
819 )?;
820 
821 #[cfg(windows)]
822 {
823 use crate::windowing::winit::windows::WindowExt;
824 
825 // Uncloak the window upon successfully drawing a frame.
826 if inner.is_cloaked {
827 match inner.window.set_cloaked(false) {
828 Ok(_) => {
829 inner.is_cloaked = false;
830 }
831 Err(e) => {
832 log::warn!("Failed to uncloak window: {e:#?}");
833 }
834 }
835 }
836 }
837 Ok(())
838 }
839 
840 /// Drops the window's renderer and all associated resources.
841 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
842 pub fn drop_renderer(&self, display_handle: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) {
843 let mut inner = self.inner.borrow_mut();
844 let Some(inner) = inner.as_mut() else {
845 log::warn!("Tried to drop a window's renderer before it had been fully initialized");
846 return;
847 };
848 
849 let _ = inner.rendering_resources.take();
850 
851 // Forceably drop and recreate our cached `wgpu::Instance` after dropping the renderer.
852 // Without this, certain NVIDIA drivers deadlock upon recreating the resources with the
853 // existing `Instance`.
854 crate::rendering::wgpu::reset_wgpu_instance(display_handle);
855 }
856 
857 /// Recreates the window's renderer and all associated resources.
858 #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
859 pub fn recreate_renderer(&self, downrank_non_nvidia_vulkan_adapters: bool) {
860 let mut inner = self.inner.borrow_mut();
861 let Some(inner) = inner.as_mut() else {
862 log::warn!(
863 "Tried to recreate a window's renderer before it had been fully initialized"
864 );
865 return;
866 };
867 
868 let resources = match Resources::new(
869 inner.window.clone(),
870 inner.gpu_power_preference,
871 inner.backend_preference,
872 &inner.on_gpu_device_selected,
873 inner.surface_size,
874 downrank_non_nvidia_vulkan_adapters,
875 )
876 .context("Failed to recreate window renderer")
877 {
878 Ok(resources) => resources,
879 Err(err) => {
880 log::error!("{err:#}");
881 return;
882 }
883 };
884 
885 enable_decorations_if_needed(&inner.window, &resources.adapter.get_info());
886 
887 let renderer = Renderer::new(&resources, GlyphConfig::default());
888 
889 let _ = inner.rendering_resources.insert(RenderingResources {
890 resources,
891 renderer,
892 });
893 }
894 
895 pub fn has_scene(&self) -> bool {
896 self.scene.borrow().is_some()
897 }
898 
899 pub fn handle_resize(&self) {
900 let mut inner = self.inner.borrow_mut();
901 let Some(inner) = inner.as_mut() else {
902 return;
903 };
904 
905 // Force a recreation of the swap chain, in case it became outdated by
906 // the resize.
907 inner.surface_requires_reconfiguration = true;
908 }
909 
910 /// This method updates window state derived from the cursor position. It keeps track of
911 /// whether the cursor is in a position to initiate drag-resizing.
912 pub fn update_drag_resize_state(&self, position: LogicalPosition<f32>) {
913 let mut inner = self.inner.borrow_mut();
914 let Some(inner) = inner.as_mut() else {
915 return;
916 };
917 
918 // Windows are not drag-resizable when maximized.
919 let drag_resize_direction = if inner.window.is_maximized() {
920 None
921 } else {
922 let scale_factor = inner.window.scale_factor();
923 Self::drag_resize_direction_at_position(
924 inner.physical_size().to_logical(scale_factor),
925 position,
926 DRAG_RESIZE_MARGIN,
927 )
928 };
929 
930 if let Some(resize_direction) = &drag_resize_direction {
931 inner
932 .window
933 .set_cursor(winit::window::CursorIcon::from(*resize_direction));
934 }
935 
936 // Reset the cursor icon if we stopped resizing.
937 if inner.active_drag_resize_direction.is_some() && drag_resize_direction.is_none() {
938 inner.window.set_cursor(winit::window::Cursor::default());
939 }
940 
941 inner.active_drag_resize_direction = drag_resize_direction;
942 }
943 
944 /// Start a drag-resize iff the cursor is in a position to do this. That positioning should
945 /// already have been determined by [`Self::update_drag_resize_state`]. Returns `true` if
946 /// drag-resizing was successfully initiated.
947 pub fn try_drag_resize(&self) -> bool {
948 let mut inner = self.inner.borrow_mut();
949 let Some(inner) = inner.as_mut() else {
950 return false;
951 };
952 let Some(direction) = inner.active_drag_resize_direction else {
953 return false;
954 };
955 inner.window.drag_resize_window(direction).is_ok()
956 }
957 
958 fn drag_resize_direction_at_position(
959 window_size: LogicalSize<u32>,
960 cursor_position: LogicalPosition<f32>,
961 margin: f32,
962 ) -> Option<ResizeDirection> {
963 enum XDirection {
964 West,
965 East,
966 None,
967 }
968 
969 enum YDirection {
970 North,
971 South,
972 None,
973 }
974 
975 let xdir = if cursor_position.x < margin {
976 XDirection::West
977 } else if cursor_position.x > (window_size.width as f32 - margin) {
978 XDirection::East
979 } else {
980 XDirection::None
981 };
982 
983 let ydir = if cursor_position.y < margin {
984 YDirection::North
985 } else if cursor_position.y > (window_size.height as f32 - margin) {
986 YDirection::South
987 } else {
988 YDirection::None
989 };
990 
991 let dir = match (ydir, xdir) {
992 (YDirection::North, XDirection::West) => ResizeDirection::NorthWest,
993 (YDirection::North, XDirection::East) => ResizeDirection::NorthEast,
994 (YDirection::North, XDirection::None) => ResizeDirection::North,
995 (YDirection::South, XDirection::West) => ResizeDirection::SouthWest,
996 (YDirection::South, XDirection::East) => ResizeDirection::SouthEast,
997 (YDirection::South, XDirection::None) => ResizeDirection::South,
998 (YDirection::None, XDirection::West) => ResizeDirection::West,
999 (YDirection::None, XDirection::East) => ResizeDirection::East,
1000 (YDirection::None, XDirection::None) => return None,
1001 };
1002 Some(dir)
1003 }
1004 
1005 pub fn is_decorated(&self) -> bool {
1006 self.inner
1007 .borrow()
1008 .as_ref()
1009 .map(|inner| inner.window.is_decorated())
1010 .unwrap_or(false)
1011 }
1012 
1013 /// Requests user attention. If the window is in focus, this is a noop.
1014 pub fn request_user_attention(&self) {
1015 // Determine which level of user attention urgency to request from the OS.
1016 // On Windows, we don't use the OS-provided title bar, and the
1017 // Informational level doesn't seem to flash the taskbar icon,
1018 // so we use the Critical level instead.
1019 let user_attention_urgency = if cfg!(windows) {
1020 UserAttentionType::Critical
1021 } else {
1022 UserAttentionType::Informational
1023 };
1024 
1025 if let Some(inner) = self.inner.borrow().as_ref() {
1026 inner
1027 .window
1028 .request_user_attention(Some(user_attention_urgency));
1029 };
1030 }
1031 
1032 /// Stops requesting user attention for the current window. If window has not previously requested user attention,
1033 /// this is a noop.
1034 pub fn stop_requesting_user_attention(&self) {
1035 if let Some(inner) = self.inner.borrow().as_ref() {
1036 inner.window.request_user_attention(None);
1037 };
1038 }
1039 
1040 pub fn set_ime_position<P, S>(&self, position: P, size: S)
1041 where
1042 P: Into<Position>,
1043 S: Into<Size>,
1044 {
1045 let inner = self.inner.borrow_mut();
1046 if let Some(inner) = inner.as_ref() {
1047 inner.window.set_ime_cursor_area(position, size);
1048 }
1049 }
1050 
1051 pub fn drag_window(&self) -> Result<(), ExternalError> {
1052 let inner = self.inner.borrow();
1053 let Some(inner) = inner.as_ref() else {
1054 return Ok(());
1055 };
1056 inner.window.drag_window()
1057 }
1058 
1059 pub fn outer_position(&self) -> Option<PhysicalPosition<i32>> {
1060 self.inner
1061 .borrow()
1062 .as_ref()
1063 .and_then(|inner| inner.window.outer_position().ok())
1064 }
1065 
1066 pub fn set_outer_position(&self, position: PhysicalPosition<i32>) {
1067 if let Some(inner) = self.inner.borrow().as_ref() {
1068 inner.window.set_outer_position(position);
1069 }
1070 }
1071 
1072 pub fn has_focus(&self) -> bool {
1073 self.inner
1074 .borrow()
1075 .as_ref()
1076 .map(|inner| inner.window.has_focus())
1077 .unwrap_or(false)
1078 }
1079 
1080 pub fn focus(&self) {
1081 if let Some(Inner { window, level, .. }) = self.inner.borrow().as_ref() {
1082 // Winit is a bit quirky here. Trying to focus a window which isn't visible will not
1083 // make it visible. So, call `focus_window` if the window is visible, otherwise make it
1084 // visible.
1085 if window.is_visible().unwrap_or(true) {
1086 window.set_minimized(false);
1087 window.focus_window();
1088 } else {
1089 // Setting visible to `true` will also focus it.
1090 window.set_visible(true);
1091 }
1092 window.set_window_level(*level);
1093 }
1094 }
1095 
1096 /// Sets whether or not the window is visible.
1097 ///
1098 /// The definition of "visibility" depends on the platform. On X11 this is referring to the
1099 /// concept of "mapping" a window. If a window is "unmapped" it is hidden, i.e. unviewable. The
1100 /// window is removed from the screen and, in some desktop environments, removed from window-
1101 /// switchers like alt-tab.
1102 /// See the Xlib docs:
1103 /// https://tronche.com/gui/x/xlib/window/XMapWindow.html
1104 ///
1105 /// On Wayland, setting visibility is unsupported.
1106 fn set_visible(&self, visible: bool) {
1107 if let Some(Inner { window, level, .. }) = self.inner.borrow().as_ref() {
1108 window.set_visible(visible);
1109 window.set_window_level(*level);
1110 }
1111 }
1112 
1113 /// Reads whether or not the window is visible.
1114 ///
1115 /// See [`Self::set_visible`] for an explanation of what "visible" means in winit. For
1116 /// platforms where setting visibility is unsupported, e.g. Wayland, always return `true`.
1117 #[cfg(not(target_family = "wasm"))]
1118 fn is_visible(&self) -> bool {
1119 self.inner
1120 .borrow()
1121 .as_ref()
1122 .and_then(|inner| inner.window.is_visible())
1123 .unwrap_or(true)
1124 }
1125 
1126 /// Intended for reading whether or not the window is visible. Always returns true.
1127 ///
1128 /// winit does not support is_visible on wasm. See: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.is_visible
1129 #[cfg(target_family = "wasm")]
1130 fn is_visible(&self) -> bool {
1131 true
1132 }
1133 
1134 fn set_bounds(&self, bounds: RectF) {
1135 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1136 let origin = bounds.origin();
1137 window.set_outer_position(LogicalPosition::new(origin.x(), origin.y()));
1138 let size = bounds.size();
1139 let requested_size = LogicalSize::new(size.x() as u32, size.y() as u32);
1140 let resize_result = window.request_inner_size(requested_size);
1141 match resize_result {
1142 Some(resulting_size) => {
1143 if resulting_size == requested_size.to_physical(window.scale_factor()) {
1144 log::debug!("resize request fulfilled synchronously");
1145 } else {
1146 log::info!(
1147 "resizing unallowed by windowing system. resize request ignored"
1148 );
1149 }
1150 }
1151 None => log::debug!("resize request sent asynchronously"),
1152 };
1153 }
1154 }
1155 
1156 fn set_title(&self, title: &str) {
1157 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1158 window.set_title(title)
1159 }
1160 }
1161 
1162 pub(super) fn set_cursor_icon(&self, cursor: Cursor) {
1163 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1164 let icon = match cursor {
1165 Cursor::Arrow => CursorIcon::Default,
1166 Cursor::IBeam => CursorIcon::Text,
1167 Cursor::Crosshair => CursorIcon::Crosshair,
1168 Cursor::OpenHand => CursorIcon::Grab,
1169 Cursor::ClosedHand => CursorIcon::Grabbing,
1170 Cursor::NotAllowed => CursorIcon::NotAllowed,
1171 Cursor::PointingHand => CursorIcon::Pointer,
1172 Cursor::ResizeLeftRight => CursorIcon::ColResize,
1173 Cursor::ResizeUpDown => CursorIcon::RowResize,
1174 Cursor::DragCopy => CursorIcon::Copy,
1175 };
1176 
1177 window.set_cursor(winit::window::Cursor::Icon(icon));
1178 }
1179 }
1180}
1181 
1182#[cfg(target_family = "wasm")]
1183fn create_window(
1184 window_target: &ActiveEventLoop,
1185 _window_options: &WindowOptions,
1186 _window_class: &Option<String>,
1187 _tiling_window_manager: bool,
1188) -> Result<winit::window::Window> {
1189 use winit::platform::web::WindowAttributesExtWebSys;
1190 use winit::platform::web::WindowExtWebSys;
1191 
1192 use crate::platform::current::add_prevent_default_listener;
1193 
1194 let window_attributes = winit::window::WindowAttributes::default().with_prevent_default(false);
1195 
1196 let window = window_target.create_window(window_attributes)?;
1197 let canvas = window
1198 .canvas()
1199 .ok_or(anyhow::anyhow!("Failed to find canvas element"))?;
1200 
1201 if let Some(element) = gloo::utils::document().get_element_by_id("wasm-container") {
1202 log::info!("Attaching canvas element \"{canvas:?}\" to the wasm-container element");
1203 element.replace_children_with_node_1(&canvas);
1204 } else {
1205 log::info!("Attaching canvas element \"{canvas:?}\" to the document body");
1206 gloo::utils::body()
1207 .append_child(&canvas)
1208 .map_err(|_| anyhow::anyhow!("Failed to append canvas element to <body>"))?;
1209 }
1210 
1211 add_prevent_default_listener(&canvas);
1212 let _ = canvas.focus();
1213 
1214 Ok(window)
1215}
1216 
1217#[cfg(not(target_family = "wasm"))]
1218fn create_window(
1219 window_target: &ActiveEventLoop,
1220 window_options: &WindowOptions,
1221 _window_class: &Option<String>,
1222 tiling_window_manager: bool,
1223) -> Result<winit::window::Window> {
1224 let decorations = !window_options.hide_title_bar;
1225 
1226 let mut window_attributes = winit::window::WindowAttributes::default()
1227 .with_min_inner_size(MIN_WINDOW_SIZE)
1228 .with_decorations(decorations)
1229 .with_window_level(window_level_for_style(window_options.style))
1230 .with_transparent(true);
1231 
1232 if let Some(title) = &window_options.title {
1233 window_attributes.title = title.to_owned();
1234 }
1235 
1236 #[cfg_attr(
1237 not(windows),
1238 expect(
1239 unused_mut,
1240 reason = "Windows may need to ignore the requested bounds if they are off any of the \
1241 displays. Linux will adjust the bounds automatically."
1242 )
1243 )]
1244 let mut window_bounds = window_options.bounds;
1245 
1246 // The monitor which has the most overlap with the new window. Will only be Some if
1247 // `window_bounds` is a WindowBounds::ExactPosition.
1248 #[cfg_attr(
1249 not(windows),
1250 expect(
1251 unused,
1252 reason = "Both uses of this variable are for Windows-only workarounds."
1253 )
1254 )]
1255 let mut most_overlapping_monitor: Option<MonitorHandle> = None;
1256 
1257 #[cfg(windows)]
1258 {
1259 // WARNING: Do not use [`WindowAttributes::with_no_redirection_bitmap`] as that caused:
1260 // https://github.com/warpdotdev/Warp/issues/8935
1261 
1262 use winit::platform::windows::{IconExtWindows, WindowAttributesExtWindows};
1263 
1264 let background_texture = if window_options.background_blur_texture {
1265 BackdropType::TransientWindow
1266 } else {
1267 BackdropType::None
1268 };
1269 window_attributes = window_attributes.with_system_backdrop(background_texture);
1270 
1271 // On Windows, don't set the window to be visible until after it has been marked as
1272 // "cloaked". Winit doesn't support initializing a window as cloaked--so we temporarily set
1273 // the window to be invisible to ensure the window doesn't flash in between the window being
1274 // created and the window being marked as cloaked.
1275 window_attributes.visible = false;
1276 
1277 let icon = winit::window::Icon::from_resource(IDI_ICON, None);
1278 window_attributes.window_icon = icon.as_ref().ok().cloned();
1279 window_attributes = window_attributes.with_taskbar_icon(icon.ok());
1280 
1281 // This is to make sure the bounds the caller is requesting intersects with any of the
1282 // monitors. We only do this on Windows b/c Windows happily renders a window outside of any
1283 // monitor, whereas Linux window managers automatically correct this.
1284 if let WindowBounds::ExactPosition(bound_rect) = window_options.bounds {
1285 most_overlapping_monitor = window_target
1286 .available_monitors()
1287 .filter_map(|monitor| {
1288 get_monitor_logical_bounds(&monitor)
1289 .intersection(bound_rect)
1290 .map(|rect| (monitor, rect.width() * rect.height()))
1291 })
1292 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
1293 .map(|pair| pair.0);
1294 if most_overlapping_monitor.is_none() {
1295 window_bounds = WindowBounds::Default;
1296 }
1297 }
1298 }
1299 
1300 let (size, origin) = if tiling_window_manager && window_options.style != WindowStyle::Pin {
1301 (None, None)
1302 } else {
1303 match window_bounds {
1304 // If we weren't passed specific bounds for the new window, use a
1305 // reasonable initial size but don't specify an origin - let the window
1306 // manager decide the window's position.
1307 WindowBounds::Default => (Some(*DEFAULT_WINDOW_SIZE), None),
1308 WindowBounds::ExactSize(size) => (Some(size), None),
1309 WindowBounds::ExactPosition(bounds) => (Some(bounds.size()), Some(bounds.origin())),
1310 }
1311 };
1312 
1313 if let Some(origin) = origin {
1314 let mut position =
1315 Position::Logical(LogicalPosition::new(origin.x() as f64, origin.y() as f64));
1316 // Manually convert logical position to physical. Normally, winit does this for us.
1317 // However, this conversion is failing on Windows so we do it ourselves.
1318 if let (Some(monitor), true) = (most_overlapping_monitor, cfg!(windows)) {
1319 position = Position::Physical(position.to_physical(monitor.scale_factor()));
1320 }
1321 window_attributes.position = Some(position);
1322 }
1323 
1324 if let Some(size) = size {
1325 window_attributes.inner_size = Some(Size::Logical(LogicalSize::new(
1326 size.x() as f64,
1327 size.y() as f64,
1328 )));
1329 }
1330 
1331 match window_options.fullscreen_state {
1332 FullscreenState::Fullscreen => {
1333 window_attributes.fullscreen = Some(Fullscreen::Borderless(None));
1334 }
1335 FullscreenState::Maximized => {
1336 window_attributes.maximized = true;
1337 }
1338 FullscreenState::Normal => {}
1339 }
1340 
1341 #[cfg(target_os = "linux")]
1342 if let Some(window_class) = _window_class.as_deref() {
1343 use winit::platform::x11::{WindowAttributesExtX11, WindowType};
1344 
1345 window_attributes = window_attributes.with_name(
1346 window_class,
1347 window_options
1348 .window_instance
1349 .as_deref()
1350 .unwrap_or(window_class),
1351 );
1352 
1353 if tiling_window_manager && window_options.style == WindowStyle::Pin {
1354 window_attributes = window_attributes.with_x11_window_type(vec![WindowType::Dialog]);
1355 }
1356 }
1357 
1358 #[allow(clippy::let_and_return)]
1359 let created_window = window_target
1360 .create_window(window_attributes)
1361 .map_err(Into::into);
1362 
1363 #[cfg(windows)]
1364 {
1365 use super::windows::WindowExt;
1366 if let Ok(window) = created_window.as_ref() {
1367 // Mark the window as cloaked when created to prevent a white flash from occurring in
1368 // the time between the window being created and us drawing to the window. On Windows, a
1369 // "cloaked" window is one that is not visible but can still be composited / drawn to.
1370 // This differs from `visible` which is not composited.
1371 // The window is uncloaked after the drawing the first frame.
1372 if let Err(e) = window.set_cloaked(true) {
1373 log::error!("Failed to mark window as cloaked: {e:#?}");
1374 };
1375 
1376 if let Some(adjustment) = maybe_adjust_window_vertically(window) {
1377 let direction = if adjustment > 0 { "down" } else { "up" };
1378 log::info!(
1379 "Window launched partially offsceen. Moving the window {direction} by {} pixels",
1380 adjustment.abs()
1381 );
1382 }
1383 
1384 window.set_visible(true);
1385 window.set_ime_allowed(true);
1386 
1387 // When launching a window from windows file explorer, it isn't given focus. We're considering
1388 // this a winit quirk and forcing it to be focused.
1389 if window_options.style != WindowStyle::NotStealFocus {
1390 window.focus_window();
1391 }
1392 
1393 let rounded_corner_result = set_window_attribute(
1394 window,
1395 Dwm::DWMWA_WINDOW_CORNER_PREFERENCE,
1396 Dwm::DWMWCP_ROUND,
1397 );
1398 
1399 static WINDOWS_VERSION: LazyLock<windows_version::OsVersion> =
1400 LazyLock::new(windows_version::OsVersion::current);
1401 
1402 if let Err(err) = rounded_corner_result {
1403 match err {
1404 WindowAttributeErr::Win32Error(_) if WINDOWS_VERSION.build < 22000 => {
1405 log::info!("Rounded window corners not supported on Windows 10");
1406 }
1407 _ => {
1408 log::error!("Error setting rounded window corners: {err:#}");
1409 }
1410 }
1411 }
1412 
1413 let caption_button_result = get_system_caption_button_bounds(window);
1414 match caption_button_result {
1415 Ok(_caption_button_location) => {
1416 // TODO: use location to actually draw buttons
1417 }
1418 Err(err) => {
1419 log::warn!("Couldn't retrieve system caption button bounds: {err:?}");
1420 }
1421 };
1422 }
1423 }
1424 
1425 created_window
1426}
1427 
1428#[cfg(windows)]
1429/// Moves the new window up if it was positioned vertically offscreen. This only checks for the window
1430/// being too low vertically. We have this additional check because winit doesn't handle the case of us
1431/// adjusting the default window size (DEFAULT_WINDOW_SIZE) without setting a window position particularly
1432/// well.
1433///
1434/// Returns the vertical difference of the adjustment, or None.
1435fn maybe_adjust_window_vertically(window: &winit::window::Window) -> Option<i32> {
1436 let window_position = window.outer_position().ok()?;
1437 let window_size = window.outer_size();
1438 let bottom_of_window = window_position.y + window_size.height as i32;
1439 
1440 let current_monitor = window.current_monitor()?;
1441 let monitor_position = current_monitor.position();
1442 let bottom_of_monitor = monitor_position.y + current_monitor.size().height as i32;
1443 
1444 let mut adjustment = 0;
1445 if window_position.y < monitor_position.y {
1446 adjustment = monitor_position.y - window_position.y;
1447 } else if bottom_of_window > bottom_of_monitor {
1448 adjustment = bottom_of_monitor - bottom_of_window;
1449 }
1450 
1451 if adjustment.unsigned_abs() <= current_monitor.size().height {
1452 window.set_outer_position(winit::dpi::PhysicalPosition::new(
1453 window_position.x,
1454 window_position.y + adjustment,
1455 ));
1456 Some(adjustment)
1457 } else {
1458 None
1459 }
1460}
1461 
1462impl crate::platform::Window for Window {
1463 fn callbacks(&self) -> &WindowCallbacks {
1464 &self.callbacks
1465 }
1466 
1467 fn minimize(&self) {
1468 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1469 window.set_minimized(true);
1470 }
1471 }
1472 
1473 fn toggle_maximized(&self) {
1474 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1475 window.set_maximized(!window.is_maximized());
1476 }
1477 }
1478 
1479 fn toggle_fullscreen(&self) {
1480 if let Some(Inner { window, .. }) = self.inner.borrow().as_ref() {
1481 match window.fullscreen() {
1482 Some(_) => window.set_fullscreen(None),
1483 None => window.set_fullscreen(Some(Fullscreen::Borderless(None))),
1484 };
1485 }
1486 }
1487 
1488 fn fullscreen_state(&self) -> FullscreenState {
1489 self.inner
1490 .borrow()
1491 .as_ref()
1492 .and_then(|Inner { window, .. }| {
1493 window
1494 .fullscreen()
1495 .map(|_| FullscreenState::Fullscreen)
1496 .or(window.is_maximized().then_some(FullscreenState::Maximized))
1497 })
1498 .unwrap_or_default()
1499 }
1500 
1501 fn supports_transparency(&self) -> bool {
1502 self.inner
1503 .borrow()
1504 .as_ref()
1505 .and_then(|inner| inner.rendering_resources.as_ref())
1506 .is_some_and(|resources| {
1507 let surface = &resources.resources.surface;
1508 let adapter = &resources.resources.adapter;
1509 surface
1510 .get_capabilities(adapter)
1511 .alpha_modes
1512 .iter()
1513 .any(|&mode| {
1514 matches!(
1515 mode,
1516 CompositeAlphaMode::PreMultiplied
1517 | CompositeAlphaMode::PostMultiplied
1518 | CompositeAlphaMode::Inherit
1519 )
1520 })
1521 })
1522 }
1523 
1524 fn graphics_backend(&self) -> GraphicsBackend {
1525 self.inner
1526 .borrow()
1527 .as_ref()
1528 .and_then(|inner| inner.rendering_resources.as_ref())
1529 .map(|resources| from_wgpu_backend(resources.resources.adapter.get_info().backend))
1530 .unwrap_or(GraphicsBackend::Empty)
1531 }
1532 
1533 fn supported_backends(&self) -> Vec<GraphicsBackend> {
1534 self.inner
1535 .borrow()
1536 .as_ref()
1537 .and_then(|inner| inner.rendering_resources.as_ref())
1538 .map(|resources| {
1539 resources
1540 .resources
1541 .supported_backends
1542 .iter()
1543 .map(|backend| from_wgpu_backend(*backend))
1544 .collect_vec()
1545 })
1546 .unwrap_or_default()
1547 }
1548 
1549 fn uses_native_window_decorations(&self) -> bool {
1550 self.is_decorated()
1551 }
1552 
1553 fn as_ctx(&self) -> &dyn platform::WindowContext {
1554 self
1555 }
1556 
1557 fn as_any(&self) -> &dyn std::any::Any {
1558 self
1559 }
1560 
1561 fn set_titlebar_height(&self, height: f64) {
1562 self.titlebar_height.set(height as f32);
1563 }
1564}
1565 
1566impl platform::WindowContext for Window {
1567 fn size(&self) -> Vector2F {
1568 let scale_factor = self.backing_scale_factor() as f64;
1569 self.inner
1570 .borrow()
1571 .as_ref()
1572 .map_or(Vector2F::zero(), |inner| {
1573 let size = inner.window.inner_size().to_logical::<f32>(scale_factor);
1574 Vector2F::new(size.width, size.height)
1575 })
1576 }
1577 
1578 fn origin(&self) -> Vector2F {
1579 let scale_factor = self.backing_scale_factor() as f64;
1580 self.inner
1581 .borrow()
1582 .as_ref()
1583 .map_or(Vector2F::zero(), |inner| {
1584 // [`winit::window::Window::outer_position`] is returning weird results on Windows.
1585 // When maximized, it shows (physical) position (-13, -13) as the origin.
1586 // `inner_position` seems to be correct on Windows, whereas `outer_position` seems
1587 // correct on Linux.
1588 let position_res = if cfg!(windows) {
1589 inner.window.inner_position()
1590 } else {
1591 inner.window.outer_position()
1592 };
1593 let Ok(position) = position_res else {
1594 return Vector2F::zero();
1595 };
1596 let position = position.to_logical::<f32>(scale_factor);
1597 Vector2F::new(position.x, position.y)
1598 })
1599 }
1600 
1601 fn backing_scale_factor(&self) -> f32 {
1602 self.inner
1603 .borrow()
1604 .as_ref()
1605 .map_or(1., |inner| inner.window.scale_factor() as f32)
1606 }
1607 
1608 fn max_texture_dimension_2d(&self) -> Option<u32> {
1609 self.inner
1610 .borrow()
1611 .as_ref()
1612 .and_then(|inner| inner.rendering_resources.as_ref())
1613 .map(|resources| resources.resources.device.limits().max_texture_dimension_2d)
1614 }
1615 
1616 fn render_scene(&self, scene: Rc<Scene>) {
1617 self.scene.borrow_mut().replace(scene);
1618 if let Some(inner) = self.inner.borrow_mut().as_mut() {
1619 inner.window.request_redraw();
1620 }
1621 }
1622 
1623 fn request_redraw(&self) {
1624 let _ = self.scene.borrow_mut().take();
1625 if let Some(inner) = self.inner.borrow_mut().as_mut() {
1626 inner.window.request_redraw();
1627 }
1628 }
1629 
1630 fn request_frame_capture(
1631 &self,
1632 callback: Box<dyn FnOnce(platform::CapturedFrame) + Send + 'static>,
1633 ) {
1634 *self.capture_callback.borrow_mut() = Some(callback);
1635 if let Some(inner) = self.inner.borrow_mut().as_mut() {
1636 inner.window.request_redraw();
1637 }
1638 }
1639}
1640 
1641/// An extension trait to add helpful methods to [`PhysicalSize`].
1642trait PhysicalSizeExt {
1643 fn to_vec2f(&self) -> Vector2F;
1644}
1645 
1646impl PhysicalSizeExt for PhysicalSize<u32> {
1647 fn to_vec2f(&self) -> Vector2F {
1648 let physical_size = self.cast::<f32>();
1649 vec2f(physical_size.width, physical_size.height)
1650 }
1651}
1652 
1653#[cfg(windows)]
1654fn get_monitor_logical_bounds(monitor: &MonitorHandle) -> RectF {
1655 let scale_factor = monitor.scale_factor();
1656 let logical_size = monitor.size().to_logical(scale_factor);
1657 let logical_position = monitor.position().to_logical(scale_factor);
1658 RectF::new(
1659 Vector2F::new(logical_position.x, logical_position.y),
1660 Vector2F::new(logical_size.width, logical_size.height),
1661 )
1662}
1663