gpui: Fix stuck tooltips after mouse leaves origin (#58134)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A I encountered this bug while building my app with GPUI where tooltips were getting stuck when i move my mouse away from the origin element. Zed kinda gets away with it because tab close buttons use `visible_on_hover`, so when the mouse leaves the tab, close button disappears. This triggers a redraw which makes GPUI recheck and hide the tooltip. In the "Before" version i had to trigger the recheck by moving my mouse over an element with hover styles to make the tooltip disappear. ### Before https://github.com/user-attachments/assets/f405620f-6d6d-4a24-8992-214180bc7b76 ### After https://github.com/user-attachments/assets/2e41fbca-af61-4f34-bd5d-ab288f34e73a
This commit is contained in:
1 parent
2e5d04e62f
commit
ea4bf744d0
1 file changed
+61
-4
@@ -18,7 +18,7 @@
|
||||
use crate::PinchEvent;
|
||||
use crate::{
|
||||
Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
|
||||
Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox,
|
||||
Display, Element, ElementId, Entity, EntityId, FocusHandle, Global, GlobalElementId, Hitbox,
|
||||
HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
|
||||
KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
|
||||
MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, Overflow,
|
||||
@@ -3195,6 +3195,8 @@ pub(crate) fn register_tooltip_mouse_handlers(
|
||||
check_is_hovered_during_prepaint: Rc<dyn Fn(&Window) -> bool>,
|
||||
window: &mut Window,
|
||||
) {
|
||||
let current_view = window.current_view();
|
||||
|
||||
window.on_mouse_event({
|
||||
let active_tooltip = active_tooltip.clone();
|
||||
let build_tooltip = build_tooltip.clone();
|
||||
@@ -3205,6 +3207,8 @@ pub(crate) fn register_tooltip_mouse_handlers(
|
||||
&build_tooltip,
|
||||
&check_is_hovered,
|
||||
&check_is_hovered_during_prepaint,
|
||||
tooltip_id,
|
||||
current_view,
|
||||
phase,
|
||||
window,
|
||||
cx,
|
||||
@@ -3247,6 +3251,8 @@ fn handle_tooltip_mouse_move(
|
||||
build_tooltip: &Rc<dyn Fn(&mut Window, &mut App) -> Option<(AnyView, bool)>>,
|
||||
check_is_hovered: &Rc<dyn Fn(&Window) -> bool>,
|
||||
check_is_hovered_during_prepaint: &Rc<dyn Fn(&Window) -> bool>,
|
||||
tooltip_id: Option<TooltipId>,
|
||||
current_view: EntityId,
|
||||
phase: DispatchPhase,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -3257,6 +3263,7 @@ fn handle_tooltip_mouse_move(
|
||||
None,
|
||||
CancelShow,
|
||||
ScheduleShow,
|
||||
CheckVisible,
|
||||
}
|
||||
|
||||
let action = match active_tooltip.borrow().as_ref() {
|
||||
@@ -3276,9 +3283,26 @@ fn handle_tooltip_mouse_move(
|
||||
Action::CancelShow
|
||||
}
|
||||
}
|
||||
// These are handled in check_visible_and_update.
|
||||
Some(ActiveTooltip::Visible { .. }) | Some(ActiveTooltip::WaitingForHide { .. }) => {
|
||||
Action::None
|
||||
Some(ActiveTooltip::Visible { is_hoverable, .. }) => {
|
||||
if phase.capture()
|
||||
&& !check_is_hovered(window)
|
||||
&& (!*is_hoverable
|
||||
|| !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
|
||||
{
|
||||
Action::CheckVisible
|
||||
} else {
|
||||
Action::None
|
||||
}
|
||||
}
|
||||
Some(ActiveTooltip::WaitingForHide { .. }) => {
|
||||
if phase.capture()
|
||||
&& (check_is_hovered(window)
|
||||
|| tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)))
|
||||
{
|
||||
Action::CheckVisible
|
||||
} else {
|
||||
Action::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3339,6 +3363,7 @@ fn handle_tooltip_mouse_move(
|
||||
_task: delayed_show_task,
|
||||
});
|
||||
}
|
||||
Action::CheckVisible => cx.notify(current_view),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4055,4 +4080,36 @@ mod tests {
|
||||
|
||||
assert!(weak_active_tooltip.upgrade().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tooltip_hides_after_mouse_leaves_origin() {
|
||||
let (mut test_app, any_window, captured_active_tooltip) = setup_tooltip_owner_test();
|
||||
|
||||
let weak_active_tooltip = captured_active_tooltip.borrow().clone().unwrap();
|
||||
let active_tooltip = weak_active_tooltip.upgrade().unwrap();
|
||||
|
||||
test_app.dispatcher.advance_clock(TOOLTIP_SHOW_DELAY);
|
||||
test_app.run_until_parked();
|
||||
|
||||
assert!(matches!(
|
||||
active_tooltip.borrow().as_ref(),
|
||||
Some(ActiveTooltip::Visible { .. })
|
||||
));
|
||||
|
||||
test_app
|
||||
.update_window(any_window, |_, window, cx| {
|
||||
window.dispatch_event(
|
||||
MouseMoveEvent {
|
||||
position: point(px(75.), px(75.)),
|
||||
modifiers: Default::default(),
|
||||
pressed_button: None,
|
||||
}
|
||||
.to_platform_input(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(active_tooltip.borrow().is_none());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user