Mostly fix hover tooltips not respecting occlusion (#24319)

Regression in #22644

Unfortunately not a full fix, In the case where a tooltip gets displayed
and then gets occluded after display, it will stick around until the
mouse exits the hover bounds.

Release Notes:

- N/A

Co-authored-by: Ben <ben@zed.dev>
This commit is contained in:
Michael Sloan 2025-02-05 16:08:56 -07:00 committed by GitHub
parent e1919b4121
commit 1dbca5d9a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 13 deletions

View file

@ -2008,18 +2008,27 @@ impl Interactivity {
let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| {
Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable))
});
// Use bounds instead of testing hitbox since check_is_hovered is also called
// during prepaint.
let source_bounds = hitbox.bounds;
let check_is_hovered = Rc::new(move |window: &Window| {
pending_mouse_down.borrow().is_none()
&& source_bounds.contains(&window.mouse_position())
// Use bounds instead of testing hitbox since this is called during prepaint.
let check_is_hovered_during_prepaint = Rc::new({
let pending_mouse_down = pending_mouse_down.clone();
let source_bounds = hitbox.bounds;
move |window: &Window| {
pending_mouse_down.borrow().is_none()
&& source_bounds.contains(&window.mouse_position())
}
});
let check_is_hovered = Rc::new({
let hitbox = hitbox.clone();
move |window: &Window| {
pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window)
}
});
register_tooltip_mouse_handlers(
&active_tooltip,
self.tooltip_id,
build_tooltip,
check_is_hovered,
check_is_hovered_during_prepaint,
window,
);
}
@ -2361,6 +2370,7 @@ pub(crate) fn register_tooltip_mouse_handlers(
tooltip_id: Option<TooltipId>,
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>,
window: &mut Window,
) {
window.on_mouse_event({
@ -2372,6 +2382,7 @@ pub(crate) fn register_tooltip_mouse_handlers(
&active_tooltip,
&build_tooltip,
&check_is_hovered,
&check_is_hovered_during_prepaint,
phase,
window,
cx,
@ -2398,10 +2409,22 @@ pub(crate) fn register_tooltip_mouse_handlers(
});
}
/// Handles displaying tooltips when an element is hovered.
///
/// The mouse hovering logic also relies on being called from window prepaint in order to handle the
/// case where the element the tooltip is on is not rendered - in that case its mouse listeners are
/// also not registered. During window prepaint, the hitbox information is not available, so
/// `check_is_hovered_during_prepaint` is used which bases the check off of the absolute bounds of
/// the element.
///
/// TODO: There's a minor bug due to the use of absolute bounds while checking during prepaint - it
/// does not know if the hitbox is occluded. In the case where a tooltip gets displayed and then
/// gets occluded after display, it will stick around until the mouse exits the hover bounds.
fn handle_tooltip_mouse_move(
active_tooltip: &Rc<RefCell<Option<ActiveTooltip>>>,
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>,
phase: DispatchPhase,
window: &mut Window,
cx: &mut App,
@ -2447,7 +2470,7 @@ fn handle_tooltip_mouse_move(
let delayed_show_task = window.spawn(cx, {
let active_tooltip = active_tooltip.clone();
let build_tooltip = build_tooltip.clone();
let check_is_hovered = check_is_hovered.clone();
let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_SHOW_DELAY).await;
cx.update(|window, cx| {
@ -2463,7 +2486,7 @@ fn handle_tooltip_mouse_move(
handle_tooltip_check_visible_and_update(
&active_tooltip,
tooltip_is_hoverable,
&check_is_hovered,
&check_is_hovered_during_prepaint,
tooltip_bounds,
window,
cx,

View file

@ -734,7 +734,6 @@ impl Element for InteractiveText {
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let active_tooltip = interactive_state.active_tooltip.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
let build_tooltip = Rc::new({
let tooltip_is_hoverable = false;
let text_layout = text_layout.clone();
@ -746,11 +745,12 @@ impl Element for InteractiveText {
.map(|view| (view, tooltip_is_hoverable))
}
});
// Use bounds instead of testing hitbox since check_is_hovered is also
// called during prepaint.
let source_bounds = hitbox.bounds;
let check_is_hovered = Rc::new({
// Use bounds instead of testing hitbox since this is called during prepaint.
let check_is_hovered_during_prepaint = Rc::new({
let source_bounds = hitbox.bounds;
let text_layout = text_layout.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
move |window: &Window| {
text_layout
.index_for_position(window.mouse_position())
@ -759,11 +759,26 @@ impl Element for InteractiveText {
&& pending_mouse_down.get().is_none()
}
});
let check_is_hovered = Rc::new({
let hitbox = hitbox.clone();
let text_layout = text_layout.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
move |window: &Window| {
text_layout
.index_for_position(window.mouse_position())
.is_ok()
&& hitbox.is_hovered(window)
&& pending_mouse_down.get().is_none()
}
});
register_tooltip_mouse_handlers(
&active_tooltip,
self.tooltip_id,
build_tooltip,
check_is_hovered,
check_is_hovered_during_prepaint,
window,
);
}