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