Account for all hover heights & prevent un-hover between popovers (#9257)

Fixes https://github.com/zed-industries/zed/issues/5227
Fixes https://github.com/zed-industries/zed/issues/7304

Release Notes:

- Fixed an issue where not all editor hover popovers would be accounted
for when choosing to hover above or below the mouse cursor
([#5227](https://github.com/zed-industries/zed/issues/5227)).
- Fixed an issue where editor hover could be dismissed by moving the
mouse between hover popovers
([#7304](https://github.com/zed-industries/zed/issues/7304)).

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
This commit is contained in:
Julia 2024-03-13 12:55:15 -04:00 committed by GitHub
parent 139bb3275a
commit 88e33a1dbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1650,6 +1650,12 @@ impl EditorElement {
em_width: Pixels, em_width: Pixels,
cx: &mut ElementContext, cx: &mut ElementContext,
) { ) {
struct MeasuredHoverPopover {
element: AnyElement,
size: Size<Pixels>,
horizontal_offset: Pixels,
}
let max_size = size( let max_size = size(
(120. * em_width) // Default size (120. * em_width) // Default size
.min(hitbox.size.width / 2.) // Shrink to half of the editor width .min(hitbox.size.width / 2.) // Shrink to half of the editor width
@ -1669,7 +1675,7 @@ impl EditorElement {
cx, cx,
) )
}); });
let Some((position, mut hover_popovers)) = hover_popovers else { let Some((position, hover_popovers)) = hover_popovers else {
return; return;
}; };
@ -1679,48 +1685,70 @@ impl EditorElement {
let hovered_row_layout = let hovered_row_layout =
&line_layouts[(position.row() - visible_display_row_range.start) as usize].line; &line_layouts[(position.row() - visible_display_row_range.start) as usize].line;
// Minimum required size: Take the first popover, and add 1.5 times the minimum popover
// height. This is the size we will use to decide whether to render popovers above or below
// the hovered line.
let first_size = hover_popovers[0].measure(available_space, cx);
let height_to_reserve = first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * line_height;
// Compute Hovered Point // Compute Hovered Point
let x = let x =
hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x; hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
let y = position.row() as f32 * line_height - scroll_pixel_position.y; let y = position.row() as f32 * line_height - scroll_pixel_position.y;
let hovered_point = content_origin + point(x, y); let hovered_point = content_origin + point(x, y);
if hovered_point.y - height_to_reserve > Pixels::ZERO { let mut overall_height = Pixels::ZERO;
let mut measured_hover_popovers = Vec::new();
for mut hover_popover in hover_popovers {
let size = hover_popover.measure(available_space, cx);
let horizontal_offset =
(text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
overall_height += HOVER_POPOVER_GAP + size.height;
measured_hover_popovers.push(MeasuredHoverPopover {
element: hover_popover,
size,
horizontal_offset,
});
}
overall_height += HOVER_POPOVER_GAP;
fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
let mut occlusion = div()
.size_full()
.occlude()
.on_mouse_move(|_, cx| cx.stop_propagation())
.into_any_element();
occlusion.measure(size(width, HOVER_POPOVER_GAP).into(), cx);
cx.defer_draw(occlusion, origin, 2);
}
if hovered_point.y > overall_height {
// There is enough space above. Render popovers above the hovered point // There is enough space above. Render popovers above the hovered point
let mut current_y = hovered_point.y; let mut current_y = hovered_point.y;
for mut hover_popover in hover_popovers { for (position, popover) in measured_hover_popovers.into_iter().with_position() {
let size = hover_popover.measure(available_space, cx); let size = popover.size;
let mut popover_origin = point(hovered_point.x, current_y - size.height); let popover_origin = point(
hovered_point.x + popover.horizontal_offset,
current_y - size.height,
);
let x_out_of_bounds = text_hitbox.upper_right().x - (popover_origin.x + size.width); cx.defer_draw(popover.element, popover_origin, 2);
if x_out_of_bounds < Pixels::ZERO { if position != itertools::Position::Last {
popover_origin.x = popover_origin.x + x_out_of_bounds; let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
draw_occluder(size.width, origin, cx);
} }
cx.defer_draw(hover_popover, popover_origin, 2);
current_y = popover_origin.y - HOVER_POPOVER_GAP; current_y = popover_origin.y - HOVER_POPOVER_GAP;
} }
} else { } else {
// There is not enough space above. Render popovers below the hovered point // There is not enough space above. Render popovers below the hovered point
let mut current_y = hovered_point.y + line_height; let mut current_y = hovered_point.y + line_height;
for mut hover_popover in hover_popovers { for (position, popover) in measured_hover_popovers.into_iter().with_position() {
let size = hover_popover.measure(available_space, cx); let size = popover.size;
let mut popover_origin = point(hovered_point.x, current_y); let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
let x_out_of_bounds = text_hitbox.upper_right().x - (popover_origin.x + size.width); cx.defer_draw(popover.element, popover_origin, 2);
if x_out_of_bounds < Pixels::ZERO { if position != itertools::Position::Last {
popover_origin.x = popover_origin.x + x_out_of_bounds; let origin = point(popover_origin.x, popover_origin.y + size.height);
draw_occluder(size.width, origin, cx);
} }
cx.defer_draw(hover_popover, popover_origin, 2);
current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
} }
} }