diff --git a/crates/base/src/popup.rs b/crates/base/src/popup.rs index ad189fe236..ef32ca3c8b 100644 --- a/crates/base/src/popup.rs +++ b/crates/base/src/popup.rs @@ -140,6 +140,9 @@ impl RenderOnce for Popup { offset, ))); + // A closed popup places nothing: it notes its trigger's bounds, for when + // it opens, without asking for another frame. + let has_content = self.content.is_some(); let root = self .base .child(self.trigger) @@ -158,7 +161,7 @@ impl RenderOnce for Popup { state.captured = true; first }); - if first { + if first && has_content { window.request_animation_frame(); } } @@ -307,6 +310,53 @@ mod tests { assert!(background_hovered.get()); } + /// A popup that may open later. + struct ClosedHarness { + open: bool, + } + + impl Render for ClosedHarness { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + let popup = Popup::new( + "popup", + div() + .debug_selector(|| "popup-trigger".into()) + .size(px(100.)), + ); + if self.open { + popup.content( + div() + .debug_selector(|| "popup-content".into()) + .size(px(20.)), + ) + } else { + popup + } + } + } + + /// A closed popup notes its trigger's bounds without asking for another + /// frame: a list of rows holding closed popups is not drawn again for + /// each row that shows. Opened, it places its content at once. + #[gpui::test] + fn a_closed_popup_asks_for_no_frame_and_opens_at_once(cx: &mut gpui::TestAppContext) { + let (view, window) = cx.add_window_view(|_, _| ClosedHarness { open: false }); + window.update(|window, cx| { + window.draw(cx).clear(cx); + assert_eq!(window.simulate_next_frame(cx), 0, "no frame was asked for"); + }); + + view.update(window, |harness, cx| { + harness.open = true; + cx.notify(); + }); + window.update(|window, cx| window.draw(cx).clear(cx)); + assert_eq!( + window.debug_bounds("popup-content").unwrap().size, + gpui::Size::new(px(20.), px(20.)) + ); + } + #[gpui::test] fn trigger_capture_enables_deferred_content_on_the_next_frame(cx: &mut gpui::TestAppContext) { let (_, window) = cx.add_window_view(|_, _| Harness); diff --git a/crates/base/src/text/inline.rs b/crates/base/src/text/inline.rs index b184f0b78d..38e8e40508 100644 --- a/crates/base/src/text/inline.rs +++ b/crates/base/src/text/inline.rs @@ -992,9 +992,16 @@ impl Element for Inline { window.set_cursor_style(CursorStyle::IBeam, &hitbox); } - // link cursor pointer - let hovered_link = - Self::link_range_for_position(&text_layout, &self.links, window.mouse_position()); + // link cursor pointer. Only text under the pointer reads where the pointer + // is: under GPUI's retained views, reading it makes the text depend on + // every pointer move, so a transcript of markdown would be drawn again + // whenever the pointer moved anywhere. + let hovered_link = hitbox + .is_hovered(window) + .then(|| { + Self::link_range_for_position(&text_layout, &self.links, window.mouse_position()) + }) + .flatten(); if hovered_link.is_some() { window.set_cursor_style(CursorStyle::PointingHand, &hitbox); }