gpui_component/
hover_card.rs1use std::rc::Rc;
2
3use gpui::{
4 Anchor, AnyElement, App, Context, ElementId, InteractiveElement as _, IntoElement,
5 ParentElement, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _,
6};
7use gpui_base::HoverCard as BaseHoverCard;
8pub use gpui_base::HoverCardState;
9use instant::Duration;
10
11use crate::{StyledExt as _, popover::Popover};
12
13#[derive(IntoElement)]
19pub struct HoverCard {
20 id: ElementId,
21 style: StyleRefinement,
22 anchor: Anchor,
23 trigger: Option<Box<dyn FnOnce(&mut Window, &App) -> AnyElement + 'static>>,
24 content: Option<
25 Rc<
26 dyn Fn(&mut HoverCardState, &mut Window, &mut Context<HoverCardState>) -> AnyElement
27 + 'static,
28 >,
29 >,
30 children: Vec<AnyElement>,
31 open_delay: Duration,
32 close_delay: Duration,
33 appearance: bool,
34 on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
35}
36
37impl HoverCard {
38 pub fn new(id: impl Into<ElementId>) -> Self {
40 Self {
41 id: id.into(),
42 style: StyleRefinement::default(),
43 anchor: Anchor::TopCenter,
44 trigger: None,
45 content: None,
46 children: vec![],
47 open_delay: Duration::from_secs_f64(0.6),
48 close_delay: Duration::from_secs_f64(0.3),
49 appearance: true,
50 on_open_change: None,
51 }
52 }
53
54 pub fn anchor(mut self, anchor: impl Into<Anchor>) -> Self {
56 self.anchor = anchor.into();
57 self
58 }
59
60 pub fn trigger<T>(mut self, trigger: T) -> Self
62 where
63 T: IntoElement + 'static,
64 {
65 self.trigger = Some(Box::new(|_, _| trigger.into_any_element()));
66 self
67 }
68
69 pub fn content<F, E>(mut self, content: F) -> Self
71 where
72 F: Fn(&mut HoverCardState, &mut Window, &mut Context<HoverCardState>) -> E + 'static,
73 E: IntoElement + 'static,
74 {
75 self.content = Some(Rc::new(move |state, window, cx| {
76 content(state, window, cx).into_any_element()
77 }));
78 self
79 }
80
81 pub fn open_delay(mut self, duration: Duration) -> Self {
83 self.open_delay = duration;
84 self
85 }
86
87 pub fn close_delay(mut self, duration: Duration) -> Self {
89 self.close_delay = duration;
90 self
91 }
92
93 pub fn appearance(mut self, appearance: bool) -> Self {
95 self.appearance = appearance;
96 self
97 }
98
99 pub fn on_open_change<F>(mut self, callback: F) -> Self
101 where
102 F: Fn(&bool, &mut Window, &mut App) + 'static,
103 {
104 self.on_open_change = Some(Rc::new(callback));
105 self
106 }
107}
108
109impl Styled for HoverCard {
110 fn style(&mut self) -> &mut StyleRefinement {
111 &mut self.style
112 }
113}
114
115impl ParentElement for HoverCard {
116 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
117 self.children.extend(elements);
118 }
119}
120
121impl RenderOnce for HoverCard {
122 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
123 let Some(trigger) = self.trigger else {
124 return div().id("empty").into_any_element();
125 };
126
127 let anchor = self.anchor;
128 let appearance = self.appearance;
129 let content = self.content;
130 let children = self.children;
131 let style = self.style;
132
133 BaseHoverCard::new(self.id)
134 .anchor(anchor)
135 .open_delay(self.open_delay)
136 .close_delay(self.close_delay)
137 .trigger((trigger)(window, cx))
138 .content(move |state, window, cx| {
139 Popover::render_popover_content(anchor, appearance, window, cx)
140 .overflow_hidden()
141 .when_some(content, |this, content| {
142 this.child((content)(state, window, cx))
143 })
144 .children(children)
145 .refine_style(&style)
146 })
147 .when_some(self.on_open_change, |this, callback| {
148 this.on_open_change(move |open, window, cx| callback(open, window, cx))
149 })
150 .into_any_element()
151 }
152}