text.rs

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