Skip to main content

gpui_cn/
tooltip_host.rs

1use std::collections::HashMap;
2
3use gpui_kit::{
4    App, ElementId, Entity, Global, WeakEntity, Window, WindowId, base::TooltipOverlay,
5};
6
7/// The per-window registry of tooltip overlays.
8///
9/// gpui-cn registers the overlay it adds to each `Root` here. A host with its own
10/// root view registers the overlay it owns the same way, and gpui-cn
11/// components then show their tooltips through it. With no registration a
12/// component falls back to GPUI's native tooltip.
13#[derive(Default)]
14pub struct TooltipHost {
15    overlays: HashMap<WindowId, WeakEntity<TooltipOverlay>>,
16    /// The trigger that last asked the window's overlay to show. GPUI
17    /// delivers the next trigger's enter before the previous trigger's
18    /// leave, and a leave from a trigger that is not the requester must not
19    /// cancel the pending show.
20    requesters: HashMap<WindowId, ElementId>,
21}
22
23impl Global for TooltipHost {}
24
25impl TooltipHost {
26    /// Registers `overlay` as the tooltip overlay of `window`.
27    ///
28    /// The registry holds a weak handle, so dropping the overlay (closing
29    /// the window, replacing the root) unregisters it.
30    pub fn register(window: &Window, overlay: &Entity<TooltipOverlay>, cx: &mut App) {
31        let id = window.window_handle().window_id();
32        let overlays = &mut cx.default_global::<Self>().overlays;
33        overlays.retain(|_, overlay| overlay.upgrade().is_some());
34        overlays.insert(id, overlay.downgrade());
35    }
36
37    /// The tooltip overlay registered for `window`, if it is still alive.
38    pub fn overlay(window: &Window, cx: &App) -> Option<Entity<TooltipOverlay>> {
39        let id = window.window_handle().window_id();
40        cx.try_global::<Self>()?.overlays.get(&id)?.upgrade()
41    }
42
43    pub(crate) fn note_requester(window: &Window, trigger: &ElementId, cx: &mut App) {
44        let id = window.window_handle().window_id();
45        cx.default_global::<Self>()
46            .requesters
47            .insert(id, trigger.clone());
48    }
49
50    pub(crate) fn is_requester(window: &Window, trigger: &ElementId, cx: &App) -> bool {
51        let id = window.window_handle().window_id();
52        cx.try_global::<Self>()
53            .and_then(|host| host.requesters.get(&id))
54            .is_some_and(|requester| requester == trigger)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use gpui_kit::{AppContext as _, Context, IntoElement, Render, TestAppContext, div};
62
63    struct Empty;
64
65    impl Render for Empty {
66        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
67            div()
68        }
69    }
70
71    #[gpui_kit::test]
72    fn registry_is_per_window_and_weak(cx: &mut TestAppContext) {
73        cx.update(|cx| {
74            gpui_kit::init(cx);
75            crate::init(cx);
76        });
77        let (_, cx) = cx.add_window_view(|_, _| Empty);
78        cx.update(|window, cx| {
79            assert!(TooltipHost::overlay(window, cx).is_none());
80            let overlay = cx.new(|_| TooltipOverlay::new());
81            TooltipHost::register(window, &overlay, cx);
82            assert_eq!(
83                TooltipHost::overlay(window, cx).map(|o| o.entity_id()),
84                Some(overlay.entity_id())
85            );
86            drop(overlay);
87        });
88        // The entity is released once the update that dropped the handle ends.
89        cx.update(|window, cx| {
90            assert!(TooltipHost::overlay(window, cx).is_none());
91        });
92    }
93}