diff --git a/rio-window/src/platform_impl/macos/app_delegate.rs b/rio-window/src/platform_impl/macos/app_delegate.rs index 0fb06c54..3d387968 100644 --- a/rio-window/src/platform_impl/macos/app_delegate.rs +++ b/rio-window/src/platform_impl/macos/app_delegate.rs @@ -561,6 +561,7 @@ impl ApplicationDelegate { } } + #[allow(unused)] pub fn queue_redraw(&self, window_id: WindowId) { let mut pending_redraw = self.ivars().pending_redraw.borrow_mut(); if !pending_redraw.contains(&window_id) { diff --git a/rio-window/src/platform_impl/macos/observer.rs b/rio-window/src/platform_impl/macos/observer.rs index 7a3e82bc..db13a0bf 100644 --- a/rio-window/src/platform_impl/macos/observer.rs +++ b/rio-window/src/platform_impl/macos/observer.rs @@ -94,6 +94,7 @@ impl RunLoop { RunLoop(unsafe { CFRunLoopGetMain() }) } + #[allow(unused)] pub fn wakeup(&self) { unsafe { CFRunLoopWakeUp(self.0) } } diff --git a/rio-window/src/platform_impl/macos/view.rs b/rio-window/src/platform_impl/macos/view.rs index 1db6b0bb..e0163365 100644 --- a/rio-window/src/platform_impl/macos/view.rs +++ b/rio-window/src/platform_impl/macos/view.rs @@ -468,6 +468,9 @@ declare_class!( fn key_down(&self, event: &NSEvent) { trace_scope!("keyDown:"); + // Mark input received for 1-second presentation window + self.mark_input_received(); + // Set flag to indicate we're in a key event self.ivars().in_key_event.set(true); @@ -620,6 +623,7 @@ declare_class!( #[method(mouseDown:)] fn mouse_down(&self, event: &NSEvent) { trace_scope!("mouseDown:"); + self.mark_input_received(); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } @@ -634,6 +638,7 @@ declare_class!( #[method(rightMouseDown:)] fn right_mouse_down(&self, event: &NSEvent) { trace_scope!("rightMouseDown:"); + self.mark_input_received(); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } @@ -648,6 +653,7 @@ declare_class!( #[method(otherMouseDown:)] fn other_mouse_down(&self, event: &NSEvent) { trace_scope!("otherMouseDown:"); + self.mark_input_received(); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } @@ -663,6 +669,7 @@ declare_class!( #[method(mouseMoved:)] fn mouse_moved(&self, event: &NSEvent) { + self.mark_input_received(); self.mouse_motion(event); } @@ -702,6 +709,7 @@ declare_class!( fn scroll_wheel(&self, event: &NSEvent) { trace_scope!("scrollWheel:"); + self.mark_input_received(); self.mouse_motion(event); let delta = { @@ -888,6 +896,17 @@ impl WinitView { self.ivars().app_delegate.queue_device_event(event); } + /// Mark that input was received for 1-second presentation window + #[inline] + fn mark_input_received(&self) { + unsafe { + if let Some(delegate) = self.window().delegate() { + let delegate_ptr = Retained::as_ptr(&delegate) as *const WindowDelegate; + (*delegate_ptr).mark_input_received(); + } + } + } + fn scale_factor(&self) -> f64 { self.window().backingScaleFactor() as f64 } diff --git a/rio-window/src/platform_impl/macos/window_delegate.rs b/rio-window/src/platform_impl/macos/window_delegate.rs index 74b2e86a..4903d548 100644 --- a/rio-window/src/platform_impl/macos/window_delegate.rs +++ b/rio-window/src/platform_impl/macos/window_delegate.rs @@ -131,6 +131,8 @@ pub(crate) struct State { display_link: RefCell>, // Track when rendering is needed (dirty state) needs_redraw: Cell, + // Track last input timestamp for 1-second presentation window + last_input_timestamp: Cell, } declare_class!( @@ -750,6 +752,7 @@ impl WindowDelegate { background_color: unsafe { NSColor::blackColor().into() }, display_link: RefCell::new(None), needs_redraw: Cell::new(false), + last_input_timestamp: Cell::new(std::time::Instant::now()), }); let delegate: Retained = unsafe { msg_send_id![super(delegate), init] }; @@ -953,6 +956,27 @@ impl WindowDelegate { #[inline] pub fn pre_present_notify(&self) {} + /// Mark that input was received. + /// + /// This updates the timestamp used for the 1-second presentation window + /// to prevent display downclocking. + #[inline] + pub(crate) fn mark_input_received(&self) { + self.ivars() + .last_input_timestamp + .set(std::time::Instant::now()); + } + + /// Check if we should keep presenting frames after input. + /// + /// After ANY input, keeps presenting frames for 1 second to prevent + /// the display from downclocking the refresh rate. + #[inline] + pub fn should_present_after_input(&self) -> bool { + self.ivars().last_input_timestamp.get().elapsed() + < std::time::Duration::from_secs(1) + } + pub fn outer_position(&self) -> Result, NotSupportedError> { let position = flip_window_screen_coordinates(self.window().frame()); Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())) @@ -2070,17 +2094,24 @@ impl DisplayLinkSupport for WindowDelegate { // Get window delegate from the view (similar to Zed's approach) use super::view::get_window_delegate; if let Some(window_delegate) = get_window_delegate(view) { - // Check if window needs redraw (dirty state) - if window_delegate.ivars().needs_redraw.get() { + // Check if window needs redraw (dirty state) OR if we're within + // the 1-second presentation window after input to prevent display downclocking + let needs_redraw = window_delegate.ivars().needs_redraw.get(); + let present_after_input = + window_delegate.should_present_after_input(); + + if needs_redraw || present_after_input { // Clear dirty flag and trigger redraw window_delegate.ivars().needs_redraw.set(false); window_delegate .ivars() .app_delegate - .queue_redraw(user_data.window_id); + .handle_redraw(user_data.window_id); + tracing::trace!( - "VSync redraw triggered for dirty window {:?}", - user_data.window_id + "VSync redraw triggered {:?} - needs_redraw {:?}", + user_data.window_id, + needs_redraw ); } else { tracing::trace!(