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