diff --git a/desktop/src/app.rs b/desktop/src/app.rs index f72f84c8e8..9cab625675 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -495,6 +495,10 @@ impl ApplicationHandler for App { let Some(render_state) = &mut self.render_state else { return }; if let Some(window) = &self.window { + if !window.can_render() { + return; + } + match render_state.render(window) { Ok(_) => {} Err(RenderError::OutdatedUITextureError) => { diff --git a/desktop/src/window.rs b/desktop/src/window.rs index d61b1afa5e..1b049787b6 100644 --- a/desktop/src/window.rs +++ b/desktop/src/window.rs @@ -12,6 +12,9 @@ pub(crate) trait NativeWindow { fn init() {} fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes; fn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self; + fn can_render(&self) -> bool { + true + } fn update_menu(&self, _entries: Vec) {} fn hide(&self) {} fn hide_others(&self) {} @@ -85,6 +88,10 @@ impl Window { self.winit_window.pre_present_notify(); } + pub(crate) fn can_render(&self) -> bool { + self.native_handle.can_render() + } + pub(crate) fn surface_size(&self) -> winit::dpi::PhysicalSize { self.winit_window.surface_size() } diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs index 70a229a7e4..788f0c5b99 100644 --- a/desktop/src/window/win.rs +++ b/desktop/src/window/win.rs @@ -28,6 +28,10 @@ impl super::NativeWindow for NativeWindowImpl { let native_handle = native_handle::NativeWindowHandle::new(window); NativeWindowImpl { native_handle } } + + fn can_render(&self) -> bool { + self.native_handle.can_render() + } } impl Drop for NativeWindowImpl { diff --git a/desktop/src/window/win/native_handle.rs b/desktop/src/window/win/native_handle.rs index 61250000ed..84fd9a3bc1 100644 --- a/desktop/src/window/win/native_handle.rs +++ b/desktop/src/window/win/native_handle.rs @@ -9,7 +9,8 @@ //! - The helper window is a invisible window that never activates, so it doesn't steal focus from the main window. //! - The main window needs to update the helper window's position and size whenever it moves or resizes. -use std::sync::OnceLock; +use std::sync::{Arc, Mutex, OnceLock}; +use std::time::Instant; use wgpu::rwh::{HasWindowHandle, RawWindowHandle}; use windows::Win32::Foundation::*; use windows::Win32::Graphics::Dwm::*; @@ -21,11 +22,18 @@ use windows::Win32::UI::WindowsAndMessaging::*; use windows::core::PCWSTR; use winit::window::Window; +#[derive(Default)] +struct NativeWindowState { + can_render: bool, + can_render_since: Option, +} + #[derive(Clone)] pub(super) struct NativeWindowHandle { main: HWND, helper: HWND, prev_window_message_handler: isize, + state: Arc>, } impl NativeWindowHandle { pub(super) fn new(window: &dyn Window) -> NativeWindowHandle { @@ -74,6 +82,7 @@ impl NativeWindowHandle { main, helper, prev_window_message_handler, + state: Arc::new(Mutex::new(NativeWindowState::default())), }; registry::insert(&native_handle); @@ -123,6 +132,32 @@ impl NativeWindowHandle { let _ = unsafe { DestroyWindow(self.helper) }; } } + + // Rendering should be disabled when window is minimized + // Rendering also needs to be disabled during minimize and restore animations + // Reenabling rendering is done after a small delay to account for restore animation + // TODO: Find a cleaner solution that doesn't depend on a timeout + pub(super) fn can_render(&self) -> bool { + let can_render = !unsafe { IsIconic(self.main).into() } && unsafe { IsWindowVisible(self.main).into() }; + let Ok(mut state) = self.state.lock() else { + tracing::error!("Failed to lock NativeWindowState"); + return true; + }; + match (can_render, state.can_render, state.can_render_since) { + (true, false, None) => { + state.can_render_since = Some(Instant::now()); + } + (true, false, Some(can_render_since)) if can_render_since.elapsed().as_millis() > 50 => { + state.can_render = true; + state.can_render_since = None; + } + (false, true, _) => { + state.can_render = false; + } + _ => {} + } + state.can_render + } } mod registry { @@ -226,7 +261,7 @@ unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wpara // Call the previous window message handler, this is a standard subclassing pattern. let prev_window_message_handler_fn_ptr: *const () = std::ptr::without_provenance(handle.prev_window_message_handler as usize); let prev_window_message_handler_fn = unsafe { std::mem::transmute::<_, _>(prev_window_message_handler_fn_ptr) }; - return unsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) }; + unsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) } } // Helper window message handler, called on the UI thread for every message the helper window receives. @@ -290,7 +325,7 @@ unsafe fn position_helper(main: HWND, helper: HWND) { let w = (r.right - r.left) + RESIZE_BAND_THICKNESS * 2; let h = (r.bottom - r.top) + RESIZE_BAND_THICKNESS * 2; - let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) }; + let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING) }; } unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 {