From 2b4e8ef9164e3b01709e6b0009dce54cdfad3c5d Mon Sep 17 00:00:00 2001 From: aloucks Date: Sun, 16 Mar 2025 21:27:27 -0400 Subject: [PATCH] Fix a pause in the event loop when clicking the title bar on windows (#4136) * Fix a pause in the event loop when clicking the title bar on windows When clicking the title bar on Windows, to drag the window, there is a noticible pause in continuous redraw requests. This was fixed in #839 and then regressed in #1852. The cursor blinks in both cases and is unrelated. The regression made the blink happen after the pause instead of immediately. * Update the event loop pause note on the WM_NCLBUTTONDOWN handler The application example was also updated to optionally animate the fill color in order to demonstrate continuous redraw without pauses in the event loop. --- examples/application.rs | 35 +++++++++++++++++++++++++ examples/util/fill.rs | 21 +++++++++++++++ src/changelog/unreleased.md | 1 + src/platform_impl/windows/event_loop.rs | 25 ++++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/examples/application.rs b/examples/application.rs index 4c58d4c1..8b63a089 100644 --- a/examples/application.rs +++ b/examples/application.rs @@ -42,6 +42,9 @@ use winit::window::{ #[path = "util/tracing.rs"] mod tracing; +#[path = "util/fill.rs"] +mod fill; + /// The amount of points to around the window for drag resize direction calculations. const BORDER_SIZE: f64 = 20.; @@ -314,6 +317,13 @@ impl Application { self.sender.send(Action::Message).unwrap(); event_loop.create_proxy().wake_up(); }, + Action::ToggleAnimatedFillColor => { + window.animated_fill_color = !window.animated_fill_color; + }, + Action::ToggleContinuousRedraw => { + window.continuous_redraw = !window.continuous_redraw; + window.window.request_redraw(); + }, } } @@ -441,6 +451,9 @@ impl ApplicationHandler for Application { if let Err(err) = window.draw() { error!("Error drawing window: {err}"); } + if window.continuous_redraw { + window.window.request_redraw(); + } }, WindowEvent::Occluded(occluded) => { window.set_occluded(occluded); @@ -616,6 +629,13 @@ struct WindowState { window: Arc, /// The window theme we're drawing with. theme: Theme, + /// Fill the window with animated color + animated_fill_color: bool, + /// The application start time. Used for color fill animation + #[cfg(not(android_platform))] + start_time: std::time::Instant, + /// Redraw continuously + continuous_redraw: bool, /// Cursor position over the window. cursor_position: Option>, /// Window modifiers state. @@ -669,6 +689,10 @@ impl WindowState { surface, window, theme, + animated_fill_color: false, + continuous_redraw: false, + #[cfg(not(android_platform))] + start_time: std::time::Instant::now(), ime, cursor_position: Default::default(), cursor_hidden: Default::default(), @@ -947,6 +971,11 @@ impl WindowState { return Ok(()); } + if self.animated_fill_color { + fill::fill_window_with_animated_color(&*self.window, self.start_time); + return Ok(()); + } + let mut buffer = self.surface.buffer_mut()?; // Draw a different color inside the safe area @@ -1038,6 +1067,8 @@ enum Action { RequestResize, DumpMonitors, Message, + ToggleAnimatedFillColor, + ToggleContinuousRedraw, } impl Action { @@ -1082,6 +1113,8 @@ impl Action { information" }, Action::Message => "Prints a message through a user wake up", + Action::ToggleAnimatedFillColor => "Toggle animated fill color", + Action::ToggleContinuousRedraw => "Toggle continuous redraw", } } } @@ -1196,6 +1229,7 @@ const CURSORS: &[CursorIcon] = &[ const KEY_BINDINGS: &[Binding<&'static str>] = &[ Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow), Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp), + Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor), Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen), #[cfg(macos_platform)] Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen), @@ -1205,6 +1239,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[ Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::ALT, Action::RequestResize), + Binding::new("R", ModifiersState::SHIFT, Action::ToggleContinuousRedraw), // M. Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors), Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), diff --git a/examples/util/fill.rs b/examples/util/fill.rs index dab4a1d7..f7a6a4e2 100644 --- a/examples/util/fill.rs +++ b/examples/util/fill.rs @@ -12,6 +12,8 @@ pub use platform::cleanup_window; #[allow(unused_imports)] pub use platform::fill_window; #[allow(unused_imports)] +pub use platform::fill_window_with_animated_color; +#[allow(unused_imports)] pub use platform::fill_window_with_color; #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -102,6 +104,16 @@ mod platform { fill_window_with_color(window, 0xff181818); } + #[allow(dead_code)] + pub fn fill_window_with_animated_color(window: &dyn Window, start: std::time::Instant) { + let time = start.elapsed().as_secs_f32() * 1.5; + let blue = (time.sin() * 255.0) as u32; + let green = ((time.cos() * 255.0) as u32) << 8; + let red = ((1.0 - time.sin() * 255.0) as u32) << 16; + let color = red | green | blue; + fill_window_with_color(window, color); + } + #[allow(dead_code)] pub fn cleanup_window(window: &dyn Window) { GC.with(|gc| { @@ -115,6 +127,7 @@ mod platform { #[cfg(any(target_os = "android", target_os = "ios"))] mod platform { + #[allow(dead_code)] pub fn fill_window(_window: &dyn winit::window::Window) { // No-op on mobile platforms. } @@ -124,6 +137,14 @@ mod platform { // No-op on mobile platforms. } + #[allow(dead_code)] + pub fn fill_window_with_animated_color( + _window: &dyn winit::window::Window, + _start: std::time::Instant, + ) { + // No-op on mobile platforms. + } + #[allow(dead_code)] pub fn cleanup_window(_window: &dyn winit::window::Window) { // No-op on mobile platforms. diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index d1ce8b80..26988a59 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -236,3 +236,4 @@ changelog entry. - On macOS, fixed the scancode conversion for audio volume keys. - On macOS, fixed the scancode conversion for `IntlBackslash`. - On macOS, fixed redundant `SurfaceResized` event at window creation. +- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index fb843d09..95019a71 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1135,6 +1135,31 @@ unsafe fn public_window_callback_inner( WM_NCLBUTTONDOWN => { if wparam == HTCAPTION as _ { + // Prevent the user event loop from pausing when left clicking the title bar. + // + // When the user interacts with the title bar, Windows enters the modal event + // loop. Currently, a left click causes a pause for about 500ms. Sending a dummy + // mouse-move event seems to cancel the modal loop early, preventing the pause. + // The application will never see this dummy event. + // + // The mouse coordinates are encoded into the lparam value, however the WM_MOUSEMOVE + // event is not using the same coordinate system of the WM_NCLBUTTONDOWN event. + // One uses client-area coordinates and the other is screen-coordinates. In any + // case, passing the lparam as-is with the dummy event does not seem the cancel + // the modal loop. + // + // However, passing in a value of 0 has been observed to always cancel the pause. + // + // Other notes: + // + // For some unknown reason, the cursor will blink when clicking the title bar. + // Cancelling the modal loop early causes the blink to happen *immediately*. + // Otherwise, the blank happens *after* the pause. + // + // When right-click the title bar, the system window menu is presented to the user, + // and the modal event loop begins. This dummy event does *not* prevent the freeze + // in the main event loop caused by that popup menu. + let lparam = 0; unsafe { PostMessageW(window, WM_MOUSEMOVE, 0, lparam) }; } result = ProcResult::DefWindowProc(wparam);