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        let text_style = window.text_style();
419        for line in &element_state.lines {
420            line.paint(
421                line_origin,
422                line_height,
423                text_style.text_align,
424                Some(bounds),
425                window,
426                cx,
427            )
428            .log_err();
429            line_origin.y += line.size(line_height).height;
430        }
431    }
432
433    /// Get the byte index into the input of the pixel position.
434    pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
435        let element_state = self.0.borrow();
436        let element_state = element_state
437            .as_ref()
438            .expect("measurement has not been performed");
439        let bounds = element_state
440            .bounds
441            .expect("prepaint has not been performed");
442
443        if position.y < bounds.top() {
444            return Err(0);
445        }
446
447        let line_height = element_state.line_height;
448        let mut line_origin = bounds.origin;
449        let mut line_start_ix = 0;
450        for line in &element_state.lines {
451            let line_bottom = line_origin.y + line.size(line_height).height;
452            if position.y > line_bottom {
453                line_origin.y = line_bottom;
454                line_start_ix += line.len() + 1;
455            } else {
456                let position_within_line = position - line_origin;
457                match line.index_for_position(position_within_line, line_height) {
458                    Ok(index_within_line) => return Ok(line_start_ix + index_within_line),
459                    Err(index_within_line) => return Err(line_start_ix + index_within_line),
460                }
461            }
462        }
463
464        Err(line_start_ix.saturating_sub(1))
465    }
466
467    /// Get the pixel position for the given byte index.
468    pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
469        let element_state = self.0.borrow();
470        let element_state = element_state
471            .as_ref()
472            .expect("measurement has not been performed");
473        let bounds = element_state
474            .bounds
475            .expect("prepaint has not been performed");
476        let line_height = element_state.line_height;
477
478        let mut line_origin = bounds.origin;
479        let mut line_start_ix = 0;
480
481        for line in &element_state.lines {
482            let line_end_ix = line_start_ix + line.len();
483            if index < line_start_ix {
484                break;
485            } else if index > line_end_ix {
486                line_origin.y += line.size(line_height).height;
487                line_start_ix = line_end_ix + 1;
488                continue;
489            } else {
490                let ix_within_line = index - line_start_ix;
491                return Some(line_origin + line.position_for_index(ix_within_line, line_height)?);
492            }
493        }
494
495        None
496    }
497
498    /// Retrieve the layout for the line containing the given byte index.
499    pub fn line_layout_for_index(&self, index: usize) -> Option<Arc<WrappedLineLayout>> {
500        let element_state = self.0.borrow();
501        let element_state = element_state
502            .as_ref()
503            .expect("measurement has not been performed");
504        let bounds = element_state
505            .bounds
506            .expect("prepaint has not been performed");
507        let line_height = element_state.line_height;
508
509        let mut line_origin = bounds.origin;
510        let mut line_start_ix = 0;
511
512        for line in &element_state.lines {
513            let line_end_ix = line_start_ix + line.len();
514            if index < line_start_ix {
515                break;
516            } else if index > line_end_ix {
517                line_origin.y += line.size(line_height).height;
518                line_start_ix = line_end_ix + 1;
519                continue;
520            } else {
521                return Some(line.layout.clone());
522            }
523        }
524
525        None
526    }
527
528    /// The bounds of this layout.
529    pub fn bounds(&self) -> Bounds<Pixels> {
530        self.0.borrow().as_ref().unwrap().bounds.unwrap()
531    }
532
533    /// The line height for this layout.
534    pub fn line_height(&self) -> Pixels {
535        self.0.borrow().as_ref().unwrap().line_height
536    }
537
538    /// The text for this layout.
539    pub fn text(&self) -> String {
540        self.0
541            .borrow()
542            .as_ref()
543            .unwrap()
544            .lines
545            .iter()
546            .map(|s| s.text.to_string())
547            .collect::<Vec<_>>()
548            .join("\n")
549    }
550}
551
552/// A text element that can be interacted with.
553pub struct InteractiveText {
554    element_id: ElementId,
555    text: StyledText,
556    click_listener:
557        Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut Window, &mut App)>>,
558    hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App)>>,
559    tooltip_builder: Option<Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyView>>>,
560    tooltip_id: Option<TooltipId>,
561    clickable_ranges: Vec<Range<usize>>,
562}
563
564struct InteractiveTextClickEvent {
565    mouse_down_index: usize,
566    mouse_up_index: usize,
567}
568
569#[doc(hidden)]
570#[derive(Default)]
571pub struct InteractiveTextState {
572    mouse_down_index: Rc<Cell<Option<usize>>>,
573    hovered_index: Rc<Cell<Option<usize>>>,
574    active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
575}
576
577/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
578impl InteractiveText {
579    /// Creates a new InteractiveText from the given text.
580    pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
581        Self {
582            element_id: id.into(),
583            text,
584            click_listener: None,
585            hover_listener: None,
586            tooltip_builder: None,
587            tooltip_id: None,
588            clickable_ranges: Vec::new(),
589        }
590    }
591
592    /// on_click is called when the user clicks on one of the given ranges, passing the index of
593    /// the clicked range.
594    pub fn on_click(
595        mut self,
596        ranges: Vec<Range<usize>>,
597        listener: impl Fn(usize, &mut Window, &mut App) + 'static,
598    ) -> Self {
599        self.click_listener = Some(Box::new(move |ranges, event, window, cx| {
600            for (range_ix, range) in ranges.iter().enumerate() {
601                if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
602                {
603                    listener(range_ix, window, cx);
604                }
605            }
606        }));
607        self.clickable_ranges = ranges;
608        self
609    }
610
611    /// on_hover is called when the mouse moves over a character within the text, passing the
612    /// index of the hovered character, or None if the mouse leaves the text.
613    pub fn on_hover(
614        mut self,
615        listener: impl Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App) + 'static,
616    ) -> Self {
617        self.hover_listener = Some(Box::new(listener));
618        self
619    }
620
621    /// tooltip lets you specify a tooltip for a given character index in the string.
622    pub fn tooltip(
623        mut self,
624        builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyView> + 'static,
625    ) -> Self {
626        self.tooltip_builder = Some(Rc::new(builder));
627        self
628    }
629}
630
631impl Element for InteractiveText {
632    type RequestLayoutState = ();
633    type PrepaintState = Hitbox;
634
635    fn id(&self) -> Option<ElementId> {
636        Some(self.element_id.clone())
637    }
638
639    fn request_layout(
640        &mut self,
641        _id: Option<&GlobalElementId>,
642        window: &mut Window,
643        cx: &mut App,
644    ) -> (LayoutId, Self::RequestLayoutState) {
645        self.text.request_layout(None, window, cx)
646    }
647
648    fn prepaint(
649        &mut self,
650        global_id: Option<&GlobalElementId>,
651        bounds: Bounds<Pixels>,
652        state: &mut Self::RequestLayoutState,
653        window: &mut Window,
654        cx: &mut App,
655    ) -> Hitbox {
656        window.with_optional_element_state::<InteractiveTextState, _>(
657            global_id,
658            |interactive_state, window| {
659                let mut interactive_state = interactive_state
660                    .map(|interactive_state| interactive_state.unwrap_or_default());
661
662                if let Some(interactive_state) = interactive_state.as_mut() {
663                    if self.tooltip_builder.is_some() {
664                        self.tooltip_id =
665                            set_tooltip_on_window(&interactive_state.active_tooltip, window);
666                    } else {
667                        // If there is no longer a tooltip builder, remove the active tooltip.
668                        interactive_state.active_tooltip.take();
669                    }
670                }
671
672                self.text.prepaint(None, bounds, state, window, cx);
673                let hitbox = window.insert_hitbox(bounds, false);
674                (hitbox, interactive_state)
675            },
676        )
677    }
678
679    fn paint(
680        &mut self,
681        global_id: Option<&GlobalElementId>,
682        bounds: Bounds<Pixels>,
683        _: &mut Self::RequestLayoutState,
684        hitbox: &mut Hitbox,
685        window: &mut Window,
686        cx: &mut App,
687    ) {
688        let current_view = window.current_view();
689        let text_layout = self.text.layout().clone();
690        window.with_element_state::<InteractiveTextState, _>(
691            global_id.unwrap(),
692            |interactive_state, window| {
693                let mut interactive_state = interactive_state.unwrap_or_default();
694                if let Some(click_listener) = self.click_listener.take() {
695                    let mouse_position = window.mouse_position();
696                    if let Ok(ix) = text_layout.index_for_position(mouse_position) {
697                        if self
698                            .clickable_ranges
699                            .iter()
700                            .any(|range| range.contains(&ix))
701                        {
702                            window.set_cursor_style(crate::CursorStyle::PointingHand, Some(hitbox))
703                        }
704                    }
705
706                    let text_layout = text_layout.clone();
707                    let mouse_down = interactive_state.mouse_down_index.clone();
708                    if let Some(mouse_down_index) = mouse_down.get() {
709                        let hitbox = hitbox.clone();
710                        let clickable_ranges = mem::take(&mut self.clickable_ranges);
711                        window.on_mouse_event(
712                            move |event: &MouseUpEvent, phase, window: &mut Window, cx| {
713                                if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
714                                    if let Ok(mouse_up_index) =
715                                        text_layout.index_for_position(event.position)
716                                    {
717                                        click_listener(
718                                            &clickable_ranges,
719                                            InteractiveTextClickEvent {
720                                                mouse_down_index,
721                                                mouse_up_index,
722                                            },
723                                            window,
724                                            cx,
725                                        )
726                                    }
727
728                                    mouse_down.take();
729                                    window.refresh();
730                                }
731                            },
732                        );
733                    } else {
734                        let hitbox = hitbox.clone();
735                        window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| {
736                            if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
737                                if let Ok(mouse_down_index) =
738                                    text_layout.index_for_position(event.position)
739                                {
740                                    mouse_down.set(Some(mouse_down_index));
741                                    window.refresh();
742                                }
743                            }
744                        });
745                    }
746                }
747
748                window.on_mouse_event({
749                    let mut hover_listener = self.hover_listener.take();
750                    let hitbox = hitbox.clone();
751                    let text_layout = text_layout.clone();
752                    let hovered_index = interactive_state.hovered_index.clone();
753                    move |event: &MouseMoveEvent, phase, window, cx| {
754                        if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
755                            let current = hovered_index.get();
756                            let updated = text_layout.index_for_position(event.position).ok();
757                            if current != updated {
758                                hovered_index.set(updated);
759                                if let Some(hover_listener) = hover_listener.as_ref() {
760                                    hover_listener(updated, event.clone(), window, cx);
761                                }
762                                cx.notify(current_view);
763                            }
764                        }
765                    }
766                });
767
768                if let Some(tooltip_builder) = self.tooltip_builder.clone() {
769                    let active_tooltip = interactive_state.active_tooltip.clone();
770                    let build_tooltip = Rc::new({
771                        let tooltip_is_hoverable = false;
772                        let text_layout = text_layout.clone();
773                        move |window: &mut Window, cx: &mut App| {
774                            text_layout
775                                .index_for_position(window.mouse_position())
776                                .ok()
777                                .and_then(|position| tooltip_builder(position, window, cx))
778                                .map(|view| (view, tooltip_is_hoverable))
779                        }
780                    });
781
782                    // Use bounds instead of testing hitbox since this is called during prepaint.
783                    let check_is_hovered_during_prepaint = Rc::new({
784                        let source_bounds = hitbox.bounds;
785                        let text_layout = text_layout.clone();
786                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
787                        move |window: &Window| {
788                            text_layout
789                                .index_for_position(window.mouse_position())
790                                .is_ok()
791                                && source_bounds.contains(&window.mouse_position())
792                                && pending_mouse_down.get().is_none()
793                        }
794                    });
795
796                    let check_is_hovered = Rc::new({
797                        let hitbox = hitbox.clone();
798                        let text_layout = text_layout.clone();
799                        let pending_mouse_down = interactive_state.mouse_down_index.clone();
800                        move |window: &Window| {
801                            text_layout
802                                .index_for_position(window.mouse_position())
803                                .is_ok()
804                                && hitbox.is_hovered(window)
805                                && pending_mouse_down.get().is_none()
806                        }
807                    });
808
809                    register_tooltip_mouse_handlers(
810                        &active_tooltip,
811                        self.tooltip_id,
812                        build_tooltip,
813                        check_is_hovered,
814                        check_is_hovered_during_prepaint,
815                        window,
816                    );
817                }
818
819                self.text.paint(None, bounds, &mut (), &mut (), window, cx);
820
821                ((), interactive_state)
822            },
823        );
824    }
825}
826
827impl IntoElement for InteractiveText {
828    type Element = Self;
829
830    fn into_element(self) -> Self::Element {
831        self
832    }
833}