liora_components/
tooltip.rs1use gpui::{
2 AnyElement, App, Bounds, Component, ElementId, GlobalElementId, InspectorElementId,
3 IntoElement, LayoutId, Pixels, RenderOnce, SharedString, Window, div, prelude::*, px,
4};
5use liora_core::{Placement, TooltipData, clear_tooltip, set_active_tooltip, stable_unique_id};
6use std::cell::Cell;
7use std::rc::Rc;
8
9pub struct Tooltip {
10 trigger: AnyElement,
11 content: SharedString,
12 placement: Placement,
13 offset: Pixels,
14 id: Option<SharedString>,
15}
16
17impl Tooltip {
18 pub fn new(trigger: impl IntoElement) -> Self {
19 Self {
20 trigger: trigger.into_any_element(),
21 content: SharedString::default(),
22 placement: Placement::Top,
23 offset: px(8.0),
24 id: None,
25 }
26 }
27
28 pub fn content(mut self, content: impl Into<SharedString>) -> Self {
29 self.content = content.into();
30 self
31 }
32
33 pub fn placement(mut self, placement: Placement) -> Self {
34 self.placement = placement;
35 self
36 }
37
38 pub fn offset(mut self, offset: impl Into<Pixels>) -> Self {
39 self.offset = offset.into();
40 self
41 }
42
43 pub fn id(mut self, id: impl Into<SharedString>) -> Self {
44 self.id = Some(id.into());
45 self
46 }
47}
48
49impl RenderOnce for Tooltip {
50 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
51 let content = self.content.clone();
52 let placement = self.placement;
53 let offset = self.offset;
54 let id = self.id.clone().unwrap_or_else(|| {
55 stable_unique_id(
56 format!("tooltip:{}:{:?}", self.content, self.placement),
57 "tooltip",
58 _window,
59 _cx,
60 )
61 });
62
63 let bounds_cell = Rc::new(Cell::new(Bounds::default()));
64 let bounds_cell_clone = bounds_cell.clone();
65 let hover_id = id.clone();
66 let move_id = id.clone();
67
68 div()
69 .id(id.clone())
70 .child(TooltipBoundsTracker {
71 trigger: self.trigger,
72 bounds: bounds_cell,
73 })
74 .on_hover(move |hovered, _window, cx| {
75 if !hovered {
76 clear_tooltip(&hover_id, cx);
77 }
78 })
79 .on_mouse_move(move |_event, _window, cx| {
80 let anchor_bounds = bounds_cell_clone.get();
81 if anchor_bounds.size.width > px(0.0) {
82 set_active_tooltip(
83 TooltipData {
84 id: move_id.clone(),
85 content: content.clone(),
86 anchor_bounds,
87 placement,
88 offset,
89 },
90 cx,
91 );
92 }
93 })
94 }
95}
96
97impl IntoElement for Tooltip {
98 type Element = Component<Self>;
99 fn into_element(self) -> Self::Element {
100 Component::new(self)
101 }
102}
103
104struct TooltipBoundsTracker {
105 trigger: AnyElement,
106 bounds: Rc<Cell<Bounds<Pixels>>>,
107}
108
109impl IntoElement for TooltipBoundsTracker {
110 type Element = Self;
111 fn into_element(self) -> Self::Element {
112 self
113 }
114}
115
116impl gpui::Element for TooltipBoundsTracker {
117 type RequestLayoutState = ();
118 type PrepaintState = ();
119
120 fn id(&self) -> Option<ElementId> {
121 None
122 }
123 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
124 None
125 }
126
127 fn request_layout(
128 &mut self,
129 _id: Option<&GlobalElementId>,
130 _id2: Option<&InspectorElementId>,
131 window: &mut Window,
132 cx: &mut App,
133 ) -> (LayoutId, ()) {
134 (self.trigger.request_layout(window, cx), ())
135 }
136
137 fn prepaint(
138 &mut self,
139 _id: Option<&GlobalElementId>,
140 _id2: Option<&InspectorElementId>,
141 _bounds: Bounds<Pixels>,
142 _rl: &mut (),
143 window: &mut Window,
144 cx: &mut App,
145 ) -> () {
146 self.trigger.prepaint(window, cx);
147 }
148
149 fn paint(
150 &mut self,
151 _id: Option<&GlobalElementId>,
152 _id2: Option<&InspectorElementId>,
153 bounds: Bounds<Pixels>,
154 _rl: &mut (),
155 _ps: &mut (),
156 window: &mut Window,
157 cx: &mut App,
158 ) {
159 self.bounds.set(bounds);
160 self.trigger.paint(window, cx);
161 }
162}