gpui: Fix window cursor style flickering (#32596)

Closes #32592
Follow-up to #31965 

This PR fixes the cursor style flickering on Linux systems. The issue
arose since the window cursor style was not reused anymore for
subsequent frames after the changes in #31965. This works on MacOS for
hiding cursors, since they are hidden until the next mouse movement
occurs, which is not the case for other systems.

This PR re-adds this whilst keeping the fixes applied in #31965. We now
determine the first cursor style that is hovered and continue searching
for a cursor style that should be applied globally. If one to apply for
the whole window is found, we return that cursor style early instead.

Alternatively, we could store window cursor style request in a vector
similar to normal cursor styles. That would require more memory in
exchange for fewer checks which cursor style to apply. I preferred the
approach here, though, but can change this should the other method be
preferred.

CC @smitbarmase since you assigned yourself that issue.

Release Notes:

- Fixed an issue where the cursor would flicker whilst typing.
This commit is contained in:
Finn Evers 2025-06-12 11:54:44 +02:00 committed by GitHub
parent 2d4e427b45
commit 7ecad2bef9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -24,6 +24,8 @@ use core_video::pixel_buffer::CVPixelBuffer;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use futures::FutureExt; use futures::FutureExt;
use futures::channel::oneshot; use futures::channel::oneshot;
use itertools::FoldWhile::{Continue, Done};
use itertools::Itertools;
use parking_lot::RwLock; use parking_lot::RwLock;
use raw_window_handle::{HandleError, HasDisplayHandle, HasWindowHandle}; use raw_window_handle::{HandleError, HasDisplayHandle, HasWindowHandle};
use refineable::Refineable; use refineable::Refineable;
@ -408,7 +410,7 @@ pub(crate) type AnyMouseListener =
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct CursorStyleRequest { pub(crate) struct CursorStyleRequest {
pub(crate) hitbox_id: HitboxId, pub(crate) hitbox_id: Option<HitboxId>,
pub(crate) style: CursorStyle, pub(crate) style: CursorStyle,
} }
@ -622,7 +624,6 @@ pub(crate) struct Frame {
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>, pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>, pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
pub(crate) cursor_styles: Vec<CursorStyleRequest>, pub(crate) cursor_styles: Vec<CursorStyleRequest>,
window_cursor_style: Option<CursorStyle>,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>, pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
#[cfg(any(feature = "inspector", debug_assertions))] #[cfg(any(feature = "inspector", debug_assertions))]
@ -667,7 +668,6 @@ impl Frame {
input_handlers: Vec::new(), input_handlers: Vec::new(),
tooltip_requests: Vec::new(), tooltip_requests: Vec::new(),
cursor_styles: Vec::new(), cursor_styles: Vec::new(),
window_cursor_style: None,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(), debug_bounds: FxHashMap::default(),
@ -693,7 +693,6 @@ impl Frame {
self.window_control_hitboxes.clear(); self.window_control_hitboxes.clear();
self.deferred_draws.clear(); self.deferred_draws.clear();
self.focus = None; self.focus = None;
self.window_cursor_style = None;
#[cfg(any(feature = "inspector", debug_assertions))] #[cfg(any(feature = "inspector", debug_assertions))]
{ {
@ -703,14 +702,16 @@ impl Frame {
} }
pub(crate) fn cursor_style(&self, window: &Window) -> Option<CursorStyle> { pub(crate) fn cursor_style(&self, window: &Window) -> Option<CursorStyle> {
self.window_cursor_style.or_else(|| { self.cursor_styles
self.cursor_styles.iter().rev().find_map(|request| { .iter()
request .rev()
.hitbox_id .fold_while(None, |style, request| match request.hitbox_id {
.is_hovered(window) None => Done(Some(request.style)),
.then_some(request.style) Some(hitbox_id) => Continue(
}) style.or_else(|| hitbox_id.is_hovered(window).then_some(request.style)),
),
}) })
.into_inner()
} }
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest { pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
@ -2174,7 +2175,7 @@ impl Window {
pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) { pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) {
self.invalidator.debug_assert_paint(); self.invalidator.debug_assert_paint();
self.next_frame.cursor_styles.push(CursorStyleRequest { self.next_frame.cursor_styles.push(CursorStyleRequest {
hitbox_id: hitbox.id, hitbox_id: Some(hitbox.id),
style, style,
}); });
} }
@ -2185,7 +2186,10 @@ impl Window {
/// phase of element drawing. /// phase of element drawing.
pub fn set_window_cursor_style(&mut self, style: CursorStyle) { pub fn set_window_cursor_style(&mut self, style: CursorStyle) {
self.invalidator.debug_assert_paint(); self.invalidator.debug_assert_paint();
self.next_frame.window_cursor_style = Some(style); self.next_frame.cursor_styles.push(CursorStyleRequest {
hitbox_id: None,
style,
})
} }
/// Sets a tooltip to be rendered for the upcoming frame. This method should only be called /// Sets a tooltip to be rendered for the upcoming frame. This method should only be called