1use crate::{
2 register_tooltip_mouse_handlers, set_tooltip_on_window, ActiveTooltip, AnyView, App, Bounds,
3 DispatchPhase, Element, ElementId, GlobalElementId, HighlightStyle, Hitbox, IntoElement,
4 LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size,
5 TextOverflow, TextRun, TextStyle, TooltipId, WhiteSpace, Window, WrappedLine,
6 WrappedLineLayout,
7};
8use anyhow::anyhow;
9use smallvec::SmallVec;
10use std::{
11 cell::{Cell, RefCell},
12 mem,
13 ops::Range,
14 rc::Rc,
15 sync::Arc,
16};
17use util::ResultExt;
18
19impl Element for &'static str {
20 type RequestLayoutState = TextLayout;
21 type PrepaintState = ();
22
23 fn id(&self) -> Option<ElementId> {
24 None
25 }
26
27 fn request_layout(
28 &mut self,
29 _id: Option<&GlobalElementId>,
30 window: &mut Window,
31 cx: &mut App,
32 ) -> (LayoutId, Self::RequestLayoutState) {
33 let mut state = TextLayout::default();
34 let layout_id = state.layout(SharedString::from(*self), None, window, cx);
35 (layout_id, state)
36 }
37
38 fn prepaint(
39 &mut self,
40 _id: Option<&GlobalElementId>,
41 bounds: Bounds<Pixels>,
42 text_layout: &mut Self::RequestLayoutState,
43 _window: &mut Window,
44 _cx: &mut App,
45 ) {
46 text_layout.prepaint(bounds, self)
47 }
48
49 fn paint(
50 &mut self,
51 _id: Option<&GlobalElementId>,
52 _bounds: Bounds<Pixels>,
53 text_layout: &mut TextLayout,
54 _: &mut (),
55 window: &mut Window,
56 cx: &mut App,
57 ) {
58 text_layout.paint(self, window, cx)
59 }
60}
61
62impl IntoElement for &'static str {
63 type Element = Self;
64
65 fn into_element(self) -> Self::Element {
66 self
67 }
68}
69
70impl IntoElement for String {
71 type Element = SharedString;
72
73 fn into_element(self) -> Self::Element {
74 self.into()
75 }
76}
77
78impl Element for SharedString {
79 type RequestLayoutState = TextLayout;
80 type PrepaintState = ();
81
82 fn id(&self) -> Option<ElementId> {
83 None
84 }
85
86 fn request_layout(
87 &mut self,
88
89 _id: Option<&GlobalElementId>,
90
91 window: &mut Window,
92 cx: &mut App,
93 ) -> (LayoutId, Self::RequestLayoutState) {
94 let mut state = TextLayout::default();
95 let layout_id = state.layout(self.clone(), None, window, cx);
96 (layout_id, state)
97 }
98
99 fn prepaint(
100 &mut self,
101 _id: Option<&GlobalElementId>,
102 bounds: Bounds<Pixels>,
103 text_layout: &mut Self::RequestLayoutState,
104 _window: &mut Window,
105 _cx: &mut App,
106 ) {
107 text_layout.prepaint(bounds, self.as_ref())
108 }
109
110 fn paint(
111 &mut self,
112 _id: Option<&GlobalElementId>,
113 _bounds: Bounds<Pixels>,
114 text_layout: &mut Self::RequestLayoutState,
115 _: &mut Self::PrepaintState,
116 window: &mut Window,
117 cx: &mut App,
118 ) {
119 text_layout.paint(self.as_ref(), window, cx)
120 }
121}
122
123impl IntoElement for SharedString {
124 type Element = Self;
125
126 fn into_element(self) -> Self::Element {
127 self
128 }
129}
130
131/// Renders text with runs of different styles.
132///
133/// Callers are responsible for setting the correct style for each run.
134/// For text with a uniform style, you can usually avoid calling this constructor
135/// and just pass text directly.
136pub struct StyledText {
137 text: SharedString,
138 runs: Option<Vec<TextRun>>,
139 delayed_highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
140 layout: TextLayout,
141}
142
143impl StyledText {
144 /// Construct a new styled text element from the given string.
145 pub fn new(text: impl Into<SharedString>) -> Self {
146 StyledText {
147 text: text.into(),
148 runs: None,
149 delayed_highlights: None,
150 layout: TextLayout::default(),
151 }
152 }
153
154 /// Get the layout for this element. This can be used to map indices to pixels and vice versa.
155 pub fn layout(&self) -> &TextLayout {
156 &self.layout
157 }
158
159 /// Set the styling attributes for the given text, as well as
160 /// as any ranges of text that have had their style customized.
161 pub fn with_default_highlights(
162 mut self,
163 default_style: &TextStyle,
164 highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
165 ) -> Self {
166 debug_assert!(
167 self.delayed_highlights.is_none(),
168 "Can't use `with_default_highlights` and `with_highlights`"
169 );
170 let runs = Self::compute_runs(&self.text, default_style, highlights);
171 self.runs = Some(runs);
172 self
173 }
174
175 /// Set the styling attributes for the given text, as well as
176 /// as any ranges of text that have had their style customized.
177 pub fn with_highlights(
178 mut self,
179 highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
180 ) -> Self {
181 debug_assert!(
182 self.runs.is_none(),
183 "Can't use `with_highlights` and `with_default_highlights`"
184 );
185 self.delayed_highlights = Some(highlights.into_iter().collect::<Vec<_>>());
186 self
187 }
188
189 fn compute_runs(
190 text: &str,
191 default_style: &TextStyle,
192 highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
193 ) -> Vec<TextRun> {
194 let mut runs = Vec::new();
195 let mut ix = 0;
196 for (range, highlight) in highlights {
197 if ix < range.start {
198 runs.push(default_style.clone().to_run(range.start - ix));
199 }
200 runs.push(
201 default_style
202 .clone()
203 .highlight(highlight)
204 .to_run(range.len()),
205 );
206 ix = range.end;
207 }
208 if ix < text.len() {
209 runs.push(default_style.to_run(text.len() - ix));
210 }
211 runs
212 }
213
214 /// Set the text runs for this piece of text.
215 pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
216 self.runs = Some(runs);
217 self
218 }
219}
220
221impl Element for StyledText {
222 type RequestLayoutState = ();
223 type PrepaintState = ();
224
225 fn id(&self) -> Option<ElementId> {
226 None
227 }
228
229 fn request_layout(
230 &mut self,
231 _id: Option<&GlobalElementId>,
232 window: &mut Window,
233 cx: &mut App,
234 ) -> (LayoutId, Self::RequestLayoutState) {
235 let runs = self.runs.take().or_else(|| {
236 self.delayed_highlights.take().map(|delayed_highlights| {
237 Self::compute_runs(&self.text, &window.text_style(), delayed_highlights)
238 })
239 });
240
241 let layout_id = self.layout.layout(self.text.clone(), runs, window, cx);
242 (layout_id, ())
243 }
244
245 fn prepaint(
246 &mut self,
247 _id: Option<&GlobalElementId>,
248 bounds: Bounds<Pixels>,
249 _: &mut Self::RequestLayoutState,
250 _window: &mut Window,
251 _cx: &mut App,
252 ) {
253 self.layout.prepaint(bounds, &self.text)
254 }
255
256 fn paint(
257 &mut self,
258 _id: Option<&GlobalElementId>,
259 _bounds: Bounds<Pixels>,
260 _: &mut Self::RequestLayoutState,
261 _: &mut Self::PrepaintState,
262 window: &mut Window,
263 cx: &mut App,
264 ) {
265 self.layout.paint(&self.text, window, cx)
266 }
267}
268
269impl IntoElement for StyledText {
270 type Element = Self;
271
272 fn into_element(self) -> Self::Element {
273 self
274 }
275}
276
277/// The Layout for TextElement. This can be used to map indices to pixels and vice versa.
278#[derive(Default, Clone)]
279pub struct TextLayout(Rc<RefCell<Option<TextLayoutInner>>>);
280
281struct TextLayoutInner {
282 lines: SmallVec<[WrappedLine; 1]>,
283 line_height: Pixels,
284 wrap_width: Option<Pixels>,
285 size: Option<Size<Pixels>>,
286 bounds: Option<Bounds<Pixels>>,
287}
288
289impl TextLayout {
290 fn layout(
291 &self,
292 text: SharedString,
293 runs: Option<Vec<TextRun>>,
294 window: &mut Window,
295 _: &mut App,
296 ) -> LayoutId {
297 let text_style = window.text_style();
298 let font_size = text_style.font_size.to_pixels(window.rem_size());
299 let line_height = text_style
300 .line_height
301 .to_pixels(font_size.into(), window.rem_size());
302
303 let mut runs = if let Some(runs) = runs {
304 runs
305 } else {
306 vec![text_style.to_run(text.len())]
307 };
308
309 let layout_id = window.request_measured_layout(Default::default(), {
310 let element_state = self.clone();
311
312 move |known_dimensions, available_space, window, cx| {
313 let wrap_width = if text_style.white_space == WhiteSpace::Normal {
314 known_dimensions.width.or(match available_space.width {
315 crate::AvailableSpace::Definite(x) => Some(x),
316 _ => None,
317 })
318 } else {
319 None
320 };
321
322 let (truncate_width, ellipsis) =
323 if let Some(text_overflow) = text_style.text_overflow {
324 let width = known_dimensions.width.or(match available_space.width {
325 crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
326 Some(max_lines) => Some(x * max_lines),
327 None => Some(x),
328 },
329 _ => None,
330 });
331
332 match text_overflow {
333 TextOverflow::Ellipsis(s) => (width, Some(s)),
334 }
335 } else {
336 (None, None)
337 };
338
339 if let Some(text_layout) = element_state.0.borrow().as_ref() {
340 if text_layout.size.is_some()
341 && (wrap_width.is_none() || wrap_width == text_layout.wrap_width)
342 {
343 return text_layout.size.unwrap();
344 }
345 }
346
347 let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
348 let text = if let Some(truncate_width) = truncate_width {
349 line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis, &mut runs)
350 } else {
351 text.clone()
352 };
353
354 let Some(lines) = window
355 .text_system()
356 .shape_text(
357 text,
358 font_size,
359 &runs,
360 wrap_width, // Wrap if we know the width.
361 text_style.line_clamp, // Limit the number of lines if line_clamp is set.
362 )
363 .log_err()
364 else {
365 element_state.0.borrow_mut().replace(TextLayoutInner {
366 lines: Default::default(),
367 line_height,
368 wrap_width,
369 size: Some(Size::default()),
370 bounds: None,
371 });
372 return Size::default();
373 };
374
375 let mut size: Size<Pixels> = Size::default();
376 for line in &lines {
377 let line_size = line.size(line_height);
378 size.height += line_size.height;
379 size.width = size.width.max(line_size.width).ceil();
380 }
381
382 element_state.0.borrow_mut().replace(TextLayoutInner {
383 lines,
384 line_height,
385 wrap_width,
386 size: Some(size),
387 bounds: None,
388 });
389
390 size
391 }
392 });
393
394 layout_id
395 }
396
397 fn prepaint(&self, bounds: Bounds<Pixels>, text: &str) {
398 let mut element_state = self.0.borrow_mut();
399 let element_state = element_state
400 .as_mut()
401 .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
402 .unwrap();
403 element_state.bounds = Some(bounds);
404 }
405
406 fn paint(&self, text: &str, window: &mut Window, cx: &mut App) {
407 let element_state = self.0.borrow();
408 let element_state = element_state
409 .as_ref()
410 .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
411 .unwrap();
412 let bounds = element_state
413 .bounds
414 .ok_or_else(|| anyhow!("prepaint has not been performed on {:?}", text))
415 .unwrap();
416
417 let line_height = element_state.line_height;
418 let mut line_origin = bounds.origin;
419 let text_style = window.text_style();
420 for line in &element_state.lines {
421 line.paint(
422 line_origin,
423 line_height,
424 text_style.text_align,
425 Some(bounds),
426 window,
427 cx,
428 )
429 .log_err();
430 line_origin.y += line.size(line_height).height;
431 }
432 }
433
434 /// Get the byte index into the input of the pixel position.
435 pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
436 let element_state = self.0.borrow();
437 let element_state = element_state
438 .as_ref()
439 .expect("measurement has not been performed");
440 let bounds = element_state
441 .bounds
442 .expect("prepaint has not been performed");
443
444 if position.y < bounds.top() {
445 return Err(0);
446 }
447
448 let line_height = element_state.line_height;
449 let mut line_origin = bounds.origin;
450 let mut line_start_ix = 0;
451 for line in &element_state.lines {
452 let line_bottom = line_origin.y + line.size(line_height).height;
453 if position.y > line_bottom {
454 line_origin.y = line_bottom;
455 line_start_ix += line.len() + 1;
456 } else {
457 let position_within_line = position - line_origin;
458 match line.index_for_position(position_within_line, line_height) {
459 Ok(index_within_line) => return Ok(line_start_ix + index_within_line),
460 Err(index_within_line) => return Err(line_start_ix + index_within_line),
461 }
462 }
463 }
464
465 Err(line_start_ix.saturating_sub(1))
466 }
467
468 /// Get the pixel position for the given byte index.
469 pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
470 let element_state = self.0.borrow();
471 let element_state = element_state
472 .as_ref()
473 .expect("measurement has not been performed");
474 let bounds = element_state
475 .bounds
476 .expect("prepaint has not been performed");
477 let line_height = element_state.line_height;
478
479 let mut line_origin = bounds.origin;
480 let mut line_start_ix = 0;
481
482 for line in &element_state.lines {
483 let line_end_ix = line_start_ix + line.len();
484 if index < line_start_ix {
485 break;
486 } else if index > line_end_ix {
487 line_origin.y += line.size(line_height).height;
488 line_start_ix = line_end_ix + 1;
489 continue;
490 } else {
491 let ix_within_line = index - line_start_ix;
492 return Some(line_origin + line.position_for_index(ix_within_line, line_height)?);
493 }
494 }
495
496 None
497 }
498
499 /// Retrieve the layout for the line containing the given byte index.
500 pub fn line_layout_for_index(&self, index: usize) -> Option<Arc<WrappedLineLayout>> {
501 let element_state = self.0.borrow();
502 let element_state = element_state
503 .as_ref()
504 .expect("measurement has not been performed");
505 let bounds = element_state
506 .bounds
507 .expect("prepaint has not been performed");
508 let line_height = element_state.line_height;
509
510 let mut line_origin = bounds.origin;
511 let mut line_start_ix = 0;
512
513 for line in &element_state.lines {
514 let line_end_ix = line_start_ix + line.len();
515 if index < line_start_ix {
516 break;
517 } else if index > line_end_ix {
518 line_origin.y += line.size(line_height).height;
519 line_start_ix = line_end_ix + 1;
520 continue;
521 } else {
522 return Some(line.layout.clone());
523 }
524 }
525
526 None
527 }
528
529 /// The bounds of this layout.
530 pub fn bounds(&self) -> Bounds<Pixels> {
531 self.0.borrow().as_ref().unwrap().bounds.unwrap()
532 }
533
534 /// The line height for this layout.
535 pub fn line_height(&self) -> Pixels {
536 self.0.borrow().as_ref().unwrap().line_height
537 }
538
539 /// The text for this layout.
540 pub fn text(&self) -> String {
541 self.0
542 .borrow()
543 .as_ref()
544 .unwrap()
545 .lines
546 .iter()
547 .map(|s| s.text.to_string())
548 .collect::<Vec<_>>()
549 .join("\n")
550 }
551}
552
553/// A text element that can be interacted with.
554pub struct InteractiveText {
555 element_id: ElementId,
556 text: StyledText,
557 click_listener:
558 Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut Window, &mut App)>>,
559 hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App)>>,
560 tooltip_builder: Option<Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyView>>>,
561 tooltip_id: Option<TooltipId>,
562 clickable_ranges: Vec<Range<usize>>,
563}
564
565struct InteractiveTextClickEvent {
566 mouse_down_index: usize,
567 mouse_up_index: usize,
568}
569
570#[doc(hidden)]
571#[derive(Default)]
572pub struct InteractiveTextState {
573 mouse_down_index: Rc<Cell<Option<usize>>>,
574 hovered_index: Rc<Cell<Option<usize>>>,
575 active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
576}
577
578/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
579impl InteractiveText {
580 /// Creates a new InteractiveText from the given text.
581 pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
582 Self {
583 element_id: id.into(),
584 text,
585 click_listener: None,
586 hover_listener: None,
587 tooltip_builder: None,
588 tooltip_id: None,
589 clickable_ranges: Vec::new(),
590 }
591 }
592
593 /// on_click is called when the user clicks on one of the given ranges, passing the index of
594 /// the clicked range.
595 pub fn on_click(
596 mut self,
597 ranges: Vec<Range<usize>>,
598 listener: impl Fn(usize, &mut Window, &mut App) + 'static,
599 ) -> Self {
600 self.click_listener = Some(Box::new(move |ranges, event, window, cx| {
601 for (range_ix, range) in ranges.iter().enumerate() {
602 if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
603 {
604 listener(range_ix, window, cx);
605 }
606 }
607 }));
608 self.clickable_ranges = ranges;
609 self
610 }
611
612 /// on_hover is called when the mouse moves over a character within the text, passing the
613 /// index of the hovered character, or None if the mouse leaves the text.
614 pub fn on_hover(
615 mut self,
616 listener: impl Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App) + 'static,
617 ) -> Self {
618 self.hover_listener = Some(Box::new(listener));
619 self
620 }
621
622 /// tooltip lets you specify a tooltip for a given character index in the string.
623 pub fn tooltip(
624 mut self,
625 builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyView> + 'static,
626 ) -> Self {
627 self.tooltip_builder = Some(Rc::new(builder));
628 self
629 }
630}
631
632impl Element for InteractiveText {
633 type RequestLayoutState = ();
634 type PrepaintState = Hitbox;
635
636 fn id(&self) -> Option<ElementId> {
637 Some(self.element_id.clone())
638 }
639
640 fn request_layout(
641 &mut self,
642 _id: Option<&GlobalElementId>,
643 window: &mut Window,
644 cx: &mut App,
645 ) -> (LayoutId, Self::RequestLayoutState) {
646 self.text.request_layout(None, window, cx)
647 }
648
649 fn prepaint(
650 &mut self,
651 global_id: Option<&GlobalElementId>,
652 bounds: Bounds<Pixels>,
653 state: &mut Self::RequestLayoutState,
654 window: &mut Window,
655 cx: &mut App,
656 ) -> Hitbox {
657 window.with_optional_element_state::<InteractiveTextState, _>(
658 global_id,
659 |interactive_state, window| {
660 let mut interactive_state = interactive_state
661 .map(|interactive_state| interactive_state.unwrap_or_default());
662
663 if let Some(interactive_state) = interactive_state.as_mut() {
664 if self.tooltip_builder.is_some() {
665 self.tooltip_id =
666 set_tooltip_on_window(&interactive_state.active_tooltip, window);
667 } else {
668 // If there is no longer a tooltip builder, remove the active tooltip.
669 interactive_state.active_tooltip.take();
670 }
671 }
672
673 self.text.prepaint(None, bounds, state, window, cx);
674 let hitbox = window.insert_hitbox(bounds, false);
675 (hitbox, interactive_state)
676 },
677 )
678 }
679
680 fn paint(
681 &mut self,
682 global_id: Option<&GlobalElementId>,
683 bounds: Bounds<Pixels>,
684 _: &mut Self::RequestLayoutState,
685 hitbox: &mut Hitbox,
686 window: &mut Window,
687 cx: &mut App,
688 ) {
689 let current_view = window.current_view();
690 let text_layout = self.text.layout().clone();
691 window.with_element_state::<InteractiveTextState, _>(
692 global_id.unwrap(),
693 |interactive_state, window| {
694 let mut interactive_state = interactive_state.unwrap_or_default();
695 if let Some(click_listener) = self.click_listener.take() {
696 let mouse_position = window.mouse_position();
697 if let Ok(ix) = text_layout.index_for_position(mouse_position) {
698 if self
699 .clickable_ranges
700 .iter()
701 .any(|range| range.contains(&ix))
702 {
703 window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
704 }
705 }
706
707 let text_layout = text_layout.clone();
708 let mouse_down = interactive_state.mouse_down_index.clone();
709 if let Some(mouse_down_index) = mouse_down.get() {
710 let hitbox = hitbox.clone();
711 let clickable_ranges = mem::take(&mut self.clickable_ranges);
712 window.on_mouse_event(
713 move |event: &MouseUpEvent, phase, window: &mut Window, cx| {
714 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
715 if let Ok(mouse_up_index) =
716 text_layout.index_for_position(event.position)
717 {
718 click_listener(
719 &clickable_ranges,
720 InteractiveTextClickEvent {
721 mouse_down_index,
722 mouse_up_index,
723 },
724 window,
725 cx,
726 )
727 }
728
729 mouse_down.take();
730 window.refresh();
731 }
732 },
733 );
734 } else {
735 let hitbox = hitbox.clone();
736 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| {
737 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
738 if let Ok(mouse_down_index) =
739 text_layout.index_for_position(event.position)
740 {
741 mouse_down.set(Some(mouse_down_index));
742 window.refresh();
743 }
744 }
745 });
746 }
747 }
748
749 window.on_mouse_event({
750 let mut hover_listener = self.hover_listener.take();
751 let hitbox = hitbox.clone();
752 let text_layout = text_layout.clone();
753 let hovered_index = interactive_state.hovered_index.clone();
754 move |event: &MouseMoveEvent, phase, window, cx| {
755 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
756 let current = hovered_index.get();
757 let updated = text_layout.index_for_position(event.position).ok();
758 if current != updated {
759 hovered_index.set(updated);
760 if let Some(hover_listener) = hover_listener.as_ref() {
761 hover_listener(updated, event.clone(), window, cx);
762 }
763 cx.notify(current_view);
764 }
765 }
766 }
767 });
768
769 if let Some(tooltip_builder) = self.tooltip_builder.clone() {
770 let active_tooltip = interactive_state.active_tooltip.clone();
771 let build_tooltip = Rc::new({
772 let tooltip_is_hoverable = false;
773 let text_layout = text_layout.clone();
774 move |window: &mut Window, cx: &mut App| {
775 text_layout
776 .index_for_position(window.mouse_position())
777 .ok()
778 .and_then(|position| tooltip_builder(position, window, cx))
779 .map(|view| (view, tooltip_is_hoverable))
780 }
781 });
782
783 // Use bounds instead of testing hitbox since this is called during prepaint.
784 let check_is_hovered_during_prepaint = Rc::new({
785 let source_bounds = hitbox.bounds;
786 let text_layout = text_layout.clone();
787 let pending_mouse_down = interactive_state.mouse_down_index.clone();
788 move |window: &Window| {
789 text_layout
790 .index_for_position(window.mouse_position())
791 .is_ok()
792 && source_bounds.contains(&window.mouse_position())
793 && pending_mouse_down.get().is_none()
794 }
795 });
796
797 let check_is_hovered = Rc::new({
798 let hitbox = hitbox.clone();
799 let text_layout = text_layout.clone();
800 let pending_mouse_down = interactive_state.mouse_down_index.clone();
801 move |window: &Window| {
802 text_layout
803 .index_for_position(window.mouse_position())
804 .is_ok()
805 && hitbox.is_hovered(window)
806 && pending_mouse_down.get().is_none()
807 }
808 });
809
810 register_tooltip_mouse_handlers(
811 &active_tooltip,
812 self.tooltip_id,
813 build_tooltip,
814 check_is_hovered,
815 check_is_hovered_during_prepaint,
816 window,
817 );
818 }
819
820 self.text.paint(None, bounds, &mut (), &mut (), window, cx);
821
822 ((), interactive_state)
823 },
824 );
825 }
826}
827
828impl IntoElement for InteractiveText {
829 type Element = Self;
830
831 fn into_element(self) -> Self::Element {
832 self
833 }
834}