gpui: Avoid redundant group hover redraws (#61118)

Anonymous elements with group hover styles have no persistent hover
state. The
prepaint mouse handler treated missing state as unhovered and notified
the
current view for every pointer movement within the group. This made
controls
such as switches redraw continuously while the pointer moved over them.

Let anonymous elements use the existing paint-time hover transition
handler.
Only elements with persistent hover state now update that state and
notify from
prepaint. Add regression coverage for entering, moving within, and
leaving an
anonymous hover group.

Signed-off-by: Daan De Meyer <daan@amutable.com>

Release Notes:

- N/A

Signed-off-by: Daan De Meyer <daan@amutable.com>
This commit is contained in:
Daan De Meyer authored and GitHub committed 2026-07-16 19:26:49 +00:00
1 parent e6efd6fddc
commit b6a1c2706e
1 file changed
+102 -3
+102 -3
View File
@@ -2669,8 +2669,8 @@ impl Interactivity {
if phase == DispatchPhase::Capture && group_hovered != was_group_hovered {
if let Some(hover_state) = &hover_state {
hover_state.borrow_mut().group = group_hovered;
cx.notify(current_view);
}
cx.notify(current_view);
}
});
}
@@ -4114,9 +4114,108 @@ mod tests {
use super::*;
use crate::{
AnyWindowHandle, AppContext as _, Context, InputEvent, Keystroke, MouseMoveEvent,
TestAppContext, util::FluentBuilder as _,
TestAppContext, canvas, util::FluentBuilder as _,
};
use std::rc::Weak;
use std::{cell::Cell, rc::Weak};
struct GroupHoverTestView {
render_count: Rc<Cell<usize>>,
anonymous_paint_count: Rc<Cell<usize>>,
stateful_width: Rc<Cell<Pixels>>,
}
impl Render for GroupHoverTestView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.render_count.set(self.render_count.get() + 1);
let anonymous_paint_count = self.anonymous_paint_count.clone();
let stateful_width = self.stateful_width.clone();
div().size_full().child(
div()
.ml(px(20.))
.mt(px(20.))
.size(px(50.))
.relative()
.group("hover-group")
.child(
div()
.absolute()
.size_full()
.invisible()
.group_hover("hover-group", |style| style.visible())
.child(canvas(
|_, _, _| {},
move |_, _, _, _| {
anonymous_paint_count.set(anonymous_paint_count.get() + 1)
},
)),
)
.child(
div()
.id("stateful-group-hover-target")
.absolute()
.top_0()
.left_0()
.size(px(10.))
.group_hover("hover-group", |style| style.size(px(20.)))
.child(canvas(
move |bounds, _, _| stateful_width.set(bounds.size.width),
|_, _, _, _| {},
)),
),
)
}
}
#[gpui::test]
fn group_hover_styles_update_only_on_transitions(cx: &mut TestAppContext) {
let render_count = Rc::new(Cell::new(0));
let anonymous_paint_count = Rc::new(Cell::new(0));
let stateful_width = Rc::new(Cell::new(px(0.)));
let window = cx.add_window({
let render_count = render_count.clone();
let anonymous_paint_count = anonymous_paint_count.clone();
let stateful_width = stateful_width.clone();
move |_, _| GroupHoverTestView {
render_count,
anonymous_paint_count,
stateful_width,
}
});
let window = AnyWindowHandle::from(window);
cx.update_window(window, |_, window, cx| window.draw(cx).clear())
.unwrap();
assert_eq!(anonymous_paint_count.get(), 0);
assert_eq!(stateful_width.get(), px(10.));
let move_mouse = |cx: &mut TestAppContext, position| {
cx.update_window(window, |_, window, cx| {
window.simulate_mouse_move(position, cx)
})
.unwrap();
};
let initial_render_count = render_count.get();
move_mouse(cx, point(px(25.), px(25.)));
assert_eq!(render_count.get(), initial_render_count + 1);
assert_eq!(anonymous_paint_count.get(), 1);
assert_eq!(stateful_width.get(), px(20.));
move_mouse(cx, point(px(30.), px(30.)));
assert_eq!(render_count.get(), initial_render_count + 1);
assert_eq!(anonymous_paint_count.get(), 1);
assert_eq!(stateful_width.get(), px(20.));
move_mouse(cx, point(px(5.), px(5.)));
assert_eq!(render_count.get(), initial_render_count + 2);
assert_eq!(anonymous_paint_count.get(), 1);
assert_eq!(stateful_width.get(), px(10.));
move_mouse(cx, point(px(10.), px(10.)));
assert_eq!(render_count.get(), initial_render_count + 2);
assert_eq!(anonymous_paint_count.get(), 1);
assert_eq!(stateful_width.get(), px(10.));
}
struct TestTooltipView;