1use crate::theme::get_global_color;
2use egui::{
3 pos2, Area, FontId, Id, Order, Rect, Response, Sense, Stroke, Ui, Vec2,
4};
5
6#[derive(Clone, Copy, PartialEq, Debug)]
8pub enum TooltipPosition {
9 Top,
11 Bottom,
13 Left,
15 Right,
17 Auto,
19}
20
21pub struct MaterialTooltip {
36 text: String,
37 position: TooltipPosition,
38 max_width: f32,
39 padding: Vec2,
40 font_size: f32,
41}
42
43impl MaterialTooltip {
44 pub fn new(text: impl Into<String>) -> Self {
46 Self {
47 text: text.into(),
48 position: TooltipPosition::Auto,
49 max_width: 200.0,
50 padding: Vec2::new(8.0, 6.0),
51 font_size: 12.0,
52 }
53 }
54
55 pub fn position(mut self, position: TooltipPosition) -> Self {
57 self.position = position;
58 self
59 }
60
61 pub fn max_width(mut self, width: f32) -> Self {
63 self.max_width = width;
64 self
65 }
66
67 pub fn padding(mut self, padding: Vec2) -> Self {
69 self.padding = padding;
70 self
71 }
72
73 pub fn font_size(mut self, size: f32) -> Self {
75 self.font_size = size;
76 self
77 }
78
79 pub fn show(&self, ui: &mut Ui, target_rect: Rect) {
81 let inverse_surface = get_global_color("inverseSurface");
82 let inverse_on_surface = get_global_color("inverseOnSurface");
83
84 let text_galley = ui.painter().layout(
86 self.text.clone(),
87 FontId::proportional(self.font_size),
88 inverse_on_surface,
89 self.max_width - self.padding.x * 2.0,
90 );
91
92 let tooltip_size = Vec2::new(
93 text_galley.size().x + self.padding.x * 2.0,
94 text_galley.size().y + self.padding.y * 2.0,
95 );
96
97 let screen_rect = ui.ctx().screen_rect();
99 let tooltip_pos = self.calculate_position(target_rect, tooltip_size, screen_rect);
100
101 let tooltip_id = Id::new("tooltip").with(&self.text);
103
104 Area::new(tooltip_id)
106 .fixed_pos(tooltip_pos)
107 .order(Order::Tooltip)
108 .interactable(false)
109 .show(ui.ctx(), |ui| {
110 let (rect, _) = ui.allocate_exact_size(tooltip_size, Sense::hover());
111
112 ui.painter().rect_filled(rect, 4.0, inverse_surface);
114
115 ui.painter().rect_stroke(
117 rect,
118 4.0,
119 Stroke::new(1.0, inverse_on_surface.linear_multiply(0.2)),
120 egui::epaint::StrokeKind::Outside,
121 );
122
123 let text_pos = pos2(
125 rect.min.x + self.padding.x,
126 rect.min.y + self.padding.y,
127 );
128 ui.painter().galley(text_pos, text_galley, inverse_on_surface);
129 });
130 }
131
132 fn calculate_position(
134 &self,
135 target_rect: Rect,
136 tooltip_size: Vec2,
137 screen_rect: Rect,
138 ) -> egui::Pos2 {
139 let spacing = 8.0; let position = match self.position {
142 TooltipPosition::Auto => {
143 self.auto_position(target_rect, tooltip_size, screen_rect)
145 }
146 pos => pos,
147 };
148
149 match position {
150 TooltipPosition::Top => pos2(
151 target_rect.center().x - tooltip_size.x / 2.0,
152 target_rect.min.y - tooltip_size.y - spacing,
153 ),
154 TooltipPosition::Bottom => pos2(
155 target_rect.center().x - tooltip_size.x / 2.0,
156 target_rect.max.y + spacing,
157 ),
158 TooltipPosition::Left => pos2(
159 target_rect.min.x - tooltip_size.x - spacing,
160 target_rect.center().y - tooltip_size.y / 2.0,
161 ),
162 TooltipPosition::Right => pos2(
163 target_rect.max.x + spacing,
164 target_rect.center().y - tooltip_size.y / 2.0,
165 ),
166 TooltipPosition::Auto => {
167 pos2(target_rect.max.x + spacing, target_rect.min.y)
169 }
170 }
171 }
172
173 fn auto_position(
175 &self,
176 target_rect: Rect,
177 tooltip_size: Vec2,
178 screen_rect: Rect,
179 ) -> TooltipPosition {
180 let spacing = 8.0;
181
182 let space_above = target_rect.min.y - screen_rect.min.y;
184 let space_below = screen_rect.max.y - target_rect.max.y;
185 let space_left = target_rect.min.x - screen_rect.min.x;
186 let space_right = screen_rect.max.x - target_rect.max.x;
187
188 if space_below >= tooltip_size.y + spacing {
190 TooltipPosition::Bottom
191 } else if space_above >= tooltip_size.y + spacing {
192 TooltipPosition::Top
193 } else if space_right >= tooltip_size.x + spacing {
194 TooltipPosition::Right
195 } else if space_left >= tooltip_size.x + spacing {
196 TooltipPosition::Left
197 } else {
198 TooltipPosition::Bottom
200 }
201 }
202}
203
204pub fn show_tooltip_on_hover(
218 ui: &mut Ui,
219 target_response: &Response,
220 text: impl Into<String>,
221 position: TooltipPosition,
222) {
223 if target_response.hovered() {
224 MaterialTooltip::new(text).position(position).show(ui, target_response.rect);
225 }
226}
227
228pub fn show_tooltip_on_hover_custom(
230 ui: &mut Ui,
231 target_response: &Response,
232 tooltip: MaterialTooltip,
233) {
234 if target_response.hovered() {
235 tooltip.show(ui, target_response.rect);
236 }
237}
238
239pub fn with_tooltip<R>(
254 ui: &mut Ui,
255 _text: impl Into<String>,
256 _position: TooltipPosition,
257 add_contents: impl FnOnce(&mut Ui) -> R,
258) -> R {
259 add_contents(ui)
263}
264
265pub fn tooltip(text: impl Into<String>) -> MaterialTooltip {
267 MaterialTooltip::new(text)
268}