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