1use crate::event;
3use crate::layout;
4use crate::mouse;
5use crate::renderer;
6use crate::text;
7use crate::widget;
8use crate::widget::container;
9use crate::widget::overlay;
10use crate::widget::{Text, Tree};
11use crate::{
12 Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point,
13 Rectangle, Shell, Size, Vector, Widget,
14};
15
16use std::borrow::Cow;
17
18#[allow(missing_debug_implementations)]
20pub struct Tooltip<'a, Message, Renderer: text::Renderer>
21where
22 Renderer: text::Renderer,
23 Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
24{
25 content: Element<'a, Message, Renderer>,
26 tooltip: Text<'a, Renderer>,
27 position: Position,
28 gap: f32,
29 padding: f32,
30 snap_within_viewport: bool,
31 style: <Renderer::Theme as container::StyleSheet>::Style,
32}
33
34impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
35where
36 Renderer: text::Renderer,
37 Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
38{
39 const DEFAULT_PADDING: f32 = 5.0;
41
42 pub fn new(
46 content: impl Into<Element<'a, Message, Renderer>>,
47 tooltip: impl Into<Cow<'a, str>>,
48 position: Position,
49 ) -> Self {
50 Tooltip {
51 content: content.into(),
52 tooltip: Text::new(tooltip),
53 position,
54 gap: 0.0,
55 padding: Self::DEFAULT_PADDING,
56 snap_within_viewport: true,
57 style: Default::default(),
58 }
59 }
60
61 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
63 self.tooltip = self.tooltip.size(size);
64 self
65 }
66
67 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
71 self.tooltip = self.tooltip.font(font);
72 self
73 }
74
75 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
77 self.gap = gap.into().0;
78 self
79 }
80
81 pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
83 self.padding = padding.into().0;
84 self
85 }
86
87 pub fn snap_within_viewport(mut self, snap: bool) -> Self {
89 self.snap_within_viewport = snap;
90 self
91 }
92
93 pub fn style(
95 mut self,
96 style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>,
97 ) -> Self {
98 self.style = style.into();
99 self
100 }
101}
102
103impl<'a, Message, Renderer> Widget<Message, Renderer>
104 for Tooltip<'a, Message, Renderer>
105where
106 Renderer: text::Renderer,
107 Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
108{
109 fn children(&self) -> Vec<Tree> {
110 vec![Tree::new(&self.content)]
111 }
112
113 fn diff(&self, tree: &mut Tree) {
114 tree.diff_children(std::slice::from_ref(&self.content))
115 }
116
117 fn width(&self) -> Length {
118 self.content.as_widget().width()
119 }
120
121 fn height(&self) -> Length {
122 self.content.as_widget().height()
123 }
124
125 fn layout(
126 &self,
127 renderer: &Renderer,
128 limits: &layout::Limits,
129 ) -> layout::Node {
130 self.content.as_widget().layout(renderer, limits)
131 }
132
133 fn on_event(
134 &mut self,
135 tree: &mut Tree,
136 event: Event,
137 layout: Layout<'_>,
138 cursor_position: Point,
139 renderer: &Renderer,
140 clipboard: &mut dyn Clipboard,
141 shell: &mut Shell<'_, Message>,
142 ) -> event::Status {
143 self.content.as_widget_mut().on_event(
144 &mut tree.children[0],
145 event,
146 layout,
147 cursor_position,
148 renderer,
149 clipboard,
150 shell,
151 )
152 }
153
154 fn mouse_interaction(
155 &self,
156 tree: &Tree,
157 layout: Layout<'_>,
158 cursor_position: Point,
159 viewport: &Rectangle,
160 renderer: &Renderer,
161 ) -> mouse::Interaction {
162 self.content.as_widget().mouse_interaction(
163 &tree.children[0],
164 layout,
165 cursor_position,
166 viewport,
167 renderer,
168 )
169 }
170
171 fn draw(
172 &self,
173 tree: &Tree,
174 renderer: &mut Renderer,
175 theme: &Renderer::Theme,
176 inherited_style: &renderer::Style,
177 layout: Layout<'_>,
178 cursor_position: Point,
179 viewport: &Rectangle,
180 ) {
181 self.content.as_widget().draw(
182 &tree.children[0],
183 renderer,
184 theme,
185 inherited_style,
186 layout,
187 cursor_position,
188 viewport,
189 );
190
191 let tooltip = &self.tooltip;
192
193 draw(
194 renderer,
195 theme,
196 inherited_style,
197 layout,
198 cursor_position,
199 viewport,
200 self.position,
201 self.gap,
202 self.padding,
203 self.snap_within_viewport,
204 &self.style,
205 |renderer, limits| {
206 Widget::<(), Renderer>::layout(tooltip, renderer, limits)
207 },
208 |renderer, defaults, layout, cursor_position, viewport| {
209 Widget::<(), Renderer>::draw(
210 tooltip,
211 &Tree::empty(),
212 renderer,
213 theme,
214 defaults,
215 layout,
216 cursor_position,
217 viewport,
218 );
219 },
220 );
221 }
222
223 fn overlay<'b>(
224 &'b mut self,
225 tree: &'b mut Tree,
226 layout: Layout<'_>,
227 renderer: &Renderer,
228 ) -> Option<overlay::Element<'b, Message, Renderer>> {
229 self.content.as_widget_mut().overlay(
230 &mut tree.children[0],
231 layout,
232 renderer,
233 )
234 }
235}
236
237impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
238 for Element<'a, Message, Renderer>
239where
240 Message: 'a,
241 Renderer: 'a + text::Renderer,
242 Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
243{
244 fn from(
245 tooltip: Tooltip<'a, Message, Renderer>,
246 ) -> Element<'a, Message, Renderer> {
247 Element::new(tooltip)
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum Position {
254 FollowCursor,
256 Top,
258 Bottom,
260 Left,
262 Right,
264}
265
266pub fn draw<Renderer>(
268 renderer: &mut Renderer,
269 theme: &Renderer::Theme,
270 inherited_style: &renderer::Style,
271 layout: Layout<'_>,
272 cursor_position: Point,
273 viewport: &Rectangle,
274 position: Position,
275 gap: f32,
276 padding: f32,
277 snap_within_viewport: bool,
278 style: &<Renderer::Theme as container::StyleSheet>::Style,
279 layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
280 draw_text: impl FnOnce(
281 &mut Renderer,
282 &renderer::Style,
283 Layout<'_>,
284 Point,
285 &Rectangle,
286 ),
287) where
288 Renderer: crate::Renderer,
289 Renderer::Theme: container::StyleSheet,
290{
291 use container::StyleSheet;
292
293 let bounds = layout.bounds();
294
295 if bounds.contains(cursor_position) {
296 let style = theme.appearance(style);
297
298 let defaults = renderer::Style {
299 text_color: style.text_color.unwrap_or(inherited_style.text_color),
300 };
301
302 let text_layout = layout_text(
303 renderer,
304 &layout::Limits::new(
305 Size::ZERO,
306 snap_within_viewport
307 .then(|| viewport.size())
308 .unwrap_or(Size::INFINITY),
309 )
310 .pad(Padding::new(padding)),
311 );
312
313 let text_bounds = text_layout.bounds();
314 let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
315 let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0;
316
317 let mut tooltip_bounds = {
318 let offset = match position {
319 Position::Top => Vector::new(
320 x_center,
321 bounds.y - text_bounds.height - gap - padding,
322 ),
323 Position::Bottom => Vector::new(
324 x_center,
325 bounds.y + bounds.height + gap + padding,
326 ),
327 Position::Left => Vector::new(
328 bounds.x - text_bounds.width - gap - padding,
329 y_center,
330 ),
331 Position::Right => Vector::new(
332 bounds.x + bounds.width + gap + padding,
333 y_center,
334 ),
335 Position::FollowCursor => Vector::new(
336 cursor_position.x,
337 cursor_position.y - text_bounds.height,
338 ),
339 };
340
341 Rectangle {
342 x: offset.x - padding,
343 y: offset.y - padding,
344 width: text_bounds.width + padding * 2.0,
345 height: text_bounds.height + padding * 2.0,
346 }
347 };
348
349 if snap_within_viewport {
350 if tooltip_bounds.x < viewport.x {
351 tooltip_bounds.x = viewport.x;
352 } else if viewport.x + viewport.width
353 < tooltip_bounds.x + tooltip_bounds.width
354 {
355 tooltip_bounds.x =
356 viewport.x + viewport.width - tooltip_bounds.width;
357 }
358
359 if tooltip_bounds.y < viewport.y {
360 tooltip_bounds.y = viewport.y;
361 } else if viewport.y + viewport.height
362 < tooltip_bounds.y + tooltip_bounds.height
363 {
364 tooltip_bounds.y =
365 viewport.y + viewport.height - tooltip_bounds.height;
366 }
367 }
368
369 renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| {
370 container::draw_background(renderer, &style, tooltip_bounds);
371
372 draw_text(
373 renderer,
374 &defaults,
375 Layout::with_offset(
376 Vector::new(
377 tooltip_bounds.x + padding,
378 tooltip_bounds.y + padding,
379 ),
380 &text_layout,
381 ),
382 cursor_position,
383 viewport,
384 )
385 });
386 }
387}