text.rs

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