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
143pub 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 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 pub fn layout(&self) -> &TextLayout {
168 &self.layout
169 }
170
171 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 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 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#[derive(Default, Clone)]
298pub struct TextLayout(Rc<RefCell<Option<TextLayoutInner>>>);
299
300#[derive(Clone, Copy)]
301enum TextPaintPass {
302 All,
303 Background,
304 Foreground,
305}
306
307struct TextLayoutInner {
308 len: usize,
309 lines: SmallVec<[WrappedLine; 1]>,
310 line_height: Pixels,
311 wrap_width: Option<Pixels>,
312 truncate_width: Option<Pixels>,
313 size: Option<Size<Pixels>>,
314 bounds: Option<Bounds<Pixels>>,
315}
316
317fn cached_size_for_constraints(
318 cached: &TextLayoutInner,
319 wrap_width: Option<Pixels>,
320 truncate_width: Option<Pixels>,
321) -> Option<Size<Pixels>> {
322 if (wrap_width.is_none() || wrap_width == cached.wrap_width)
323 && truncate_width.is_none()
324 && cached.truncate_width.is_none()
325 {
326 cached.size
327 } else {
328 None
329 }
330}
331
332fn all_shaped_line_widths_fit(
333 widths: impl IntoIterator<Item = Pixels>,
334 truncate_width: Pixels,
335) -> bool {
336 widths.into_iter().all(|width| width <= truncate_width)
337}
338
339impl TextLayout {
340 fn layout(
341 &self,
342 text: SharedString,
343 runs: Option<Vec<TextRun>>,
344 window: &mut Window,
345 _: &mut App,
346 ) -> LayoutId {
347 let text_style = window.text_style();
348 let font_size = text_style.font_size.to_pixels(window.rem_size());
349 let line_height = window.pixel_snap(
350 text_style
351 .line_height
352 .to_pixels(font_size.into(), window.rem_size()),
353 );
354
355 let mut runs = if let Some(runs) = runs {
356 runs
357 } else {
358 vec![text_style.to_run(text.len())]
359 };
360
361 window.request_measured_layout(Default::default(), {
362 let element_state = self.clone();
363
364 move |known_dimensions, available_space, window, cx| {
365 let wrap_width = if text_style.white_space == WhiteSpace::Normal {
366 known_dimensions.width.or(match available_space.width {
367 crate::AvailableSpace::Definite(x) => Some(x),
368 _ => None,
369 })
370 } else {
371 None
372 };
373
374 let (truncate_width, truncation_suffix) =
375 if let Some(text_overflow) = text_style.text_overflow.clone() {
376 let width = known_dimensions.width.or(match available_space.width {
377 crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
378 Some(max_lines) => Some(x * max_lines),
379 None => Some(x),
380 },
381 _ => None,
382 });
383
384 match text_overflow {
385 TextOverflow::Truncate(s) => (width, s),
386 }
387 } else {
388 (None, "".into())
389 };
390
391 if let Some(size) = element_state.0.borrow().as_ref().and_then(|cached| {
392 cached_size_for_constraints(cached, wrap_width, truncate_width)
393 }) {
394 return size;
395 }
396
397 let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
398 let text = if let Some(truncate_width) = truncate_width {
399 let unclipped_text_fits = wrap_width.is_none()
400 && window
401 .text_system()
402 .shape_text(text.clone(), font_size, &runs, None, None)
403 .log_err()
404 .is_some_and(|lines| {
405 all_shaped_line_widths_fit(
406 lines.iter().map(|line| line.size(line_height).width),
407 truncate_width,
408 )
409 });
410
411 if unclipped_text_fits {
412 text.clone()
413 } else {
414 line_wrapper.truncate_line(
415 text.clone(),
416 truncate_width,
417 &truncation_suffix,
418 &mut runs,
419 )
420 }
421 } else {
422 text.clone()
423 };
424 let len = text.len();
425
426 let Some(lines) = window
427 .text_system()
428 .shape_text(
429 text,
430 font_size,
431 &runs,
432 wrap_width, text_style.line_clamp, )
435 .log_err()
436 else {
437 element_state.0.borrow_mut().replace(TextLayoutInner {
438 lines: Default::default(),
439 len: 0,
440 line_height,
441 wrap_width,
442 truncate_width,
443 size: Some(Size::default()),
444 bounds: None,
445 });
446 return Size::default();
447 };
448
449 let mut size: Size<Pixels> = Size::default();
450 for line in &lines {
451 let line_size = line.size(line_height);
452 size.height += line_size.height;
453 size.width = size.width.max(line_size.width).ceil();
454 }
455
456 element_state.0.borrow_mut().replace(TextLayoutInner {
457 lines,
458 len,
459 line_height,
460 wrap_width,
461 truncate_width,
462 size: Some(size),
463 bounds: None,
464 });
465
466 size
467 }
468 })
469 }
470
471 fn prepaint(&self, bounds: Bounds<Pixels>, text: &str) {
472 let mut element_state = self.0.borrow_mut();
473 let element_state = element_state
474 .as_mut()
475 .with_context(|| format!("measurement has not been performed on {text}"))
476 .unwrap();
477 element_state.bounds = Some(bounds);
478 }
479
480 fn paint(&self, text: &str, window: &mut Window, cx: &mut App) {
481 self.paint_lines(TextPaintPass::All, window, cx)
482 .with_context(|| format!("failed to paint {text}"))
483 .unwrap();
484 }
485
486 pub fn paint_background(&self, window: &mut Window, cx: &mut App) -> anyhow::Result<()> {
490 self.paint_lines(TextPaintPass::Background, window, cx)
491 }
492
493 pub fn paint_foreground(&self, window: &mut Window, cx: &mut App) -> anyhow::Result<()> {
495 self.paint_lines(TextPaintPass::Foreground, window, cx)
496 }
497
498 fn paint_lines(
499 &self,
500 pass: TextPaintPass,
501 window: &mut Window,
502 cx: &mut App,
503 ) -> anyhow::Result<()> {
504 let element_state = self.0.borrow();
505 let element_state = element_state
506 .as_ref()
507 .context("measurement has not been performed")?;
508 let bounds = element_state
509 .bounds
510 .context("prepaint has not been performed")?;
511
512 let line_height = element_state.line_height;
513 let mut line_origin = bounds.origin;
514 let text_style = window.text_style();
515 for line in &element_state.lines {
516 if matches!(pass, TextPaintPass::All | TextPaintPass::Background) {
517 line.paint_background(
518 line_origin,
519 line_height,
520 text_style.text_align,
521 Some(bounds),
522 window,
523 cx,
524 )
525 .log_err();
526 }
527 if matches!(pass, TextPaintPass::All | TextPaintPass::Foreground) {
528 line.paint(
529 line_origin,
530 line_height,
531 text_style.text_align,
532 Some(bounds),
533 window,
534 cx,
535 )
536 .log_err();
537 }
538 line_origin.y += line.size(line_height).height;
539 }
540 Ok(())
541 }
542
543 pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
545 let element_state = self.0.borrow();
546 let element_state = element_state
547 .as_ref()
548 .expect("measurement has not been performed");
549 let bounds = element_state
550 .bounds
551 .expect("prepaint has not been performed");
552
553 if position.y < bounds.top() {
554 return Err(0);
555 }
556
557 let line_height = element_state.line_height;
558 let mut line_origin = bounds.origin;
559 let mut line_start_ix = 0;
560 for line in &element_state.lines {
561 let line_bottom = line_origin.y + line.size(line_height).height;
562 if position.y > line_bottom {
563 line_origin.y = line_bottom;
564 line_start_ix += line.len() + 1;
565 } else {
566 let position_within_line = position - line_origin;
567 match line.index_for_position(position_within_line, line_height) {
568 Ok(index_within_line) => return Ok(line_start_ix + index_within_line),
569 Err(index_within_line) => return Err(line_start_ix + index_within_line),
570 }
571 }
572 }
573
574 Err(line_start_ix.saturating_sub(1))
575 }
576
577 pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
579 let element_state = self.0.borrow();
580 let element_state = element_state
581 .as_ref()
582 .expect("measurement has not been performed");
583 let bounds = element_state
584 .bounds
585 .expect("prepaint has not been performed");
586 let line_height = element_state.line_height;
587
588 let mut line_origin = bounds.origin;
589 let mut line_start_ix = 0;
590
591 for line in &element_state.lines {
592 let line_end_ix = line_start_ix + line.len();
593 if index < line_start_ix {
594 break;
595 } else if index > line_end_ix {
596 line_origin.y += line.size(line_height).height;
597 line_start_ix = line_end_ix + 1;
598 continue;
599 } else {
600 let ix_within_line = index - line_start_ix;
601 return Some(line_origin + line.position_for_index(ix_within_line, line_height)?);
602 }
603 }
604
605 None
606 }
607
608 pub fn line_layout_for_index(&self, index: usize) -> Option<Arc<WrappedLineLayout>> {
610 let element_state = self.0.borrow();
611 let element_state = element_state
612 .as_ref()
613 .expect("measurement has not been performed");
614 let bounds = element_state
615 .bounds
616 .expect("prepaint has not been performed");
617 let line_height = element_state.line_height;
618
619 let mut line_origin = bounds.origin;
620 let mut line_start_ix = 0;
621
622 for line in &element_state.lines {
623 let line_end_ix = line_start_ix + line.len();
624 if index < line_start_ix {
625 break;
626 } else if index > line_end_ix {
627 line_origin.y += line.size(line_height).height;
628 line_start_ix = line_end_ix + 1;
629 continue;
630 } else {
631 return Some(line.layout.clone());
632 }
633 }
634
635 None
636 }
637
638 pub fn bounds(&self) -> Bounds<Pixels> {
640 self.0.borrow().as_ref().unwrap().bounds.unwrap()
641 }
642
643 pub fn line_height(&self) -> Pixels {
645 self.0.borrow().as_ref().unwrap().line_height
646 }
647
648 pub fn len(&self) -> usize {
650 self.0.borrow().as_ref().unwrap().len
651 }
652
653 pub fn text(&self) -> String {
655 self.0
656 .borrow()
657 .as_ref()
658 .unwrap()
659 .lines
660 .iter()
661 .map(|s| s.text.to_string())
662 .collect::<Vec<_>>()
663 .join("\n")
664 }
665
666 pub fn wrapped_text(&self) -> String {
668 let mut lines = Vec::new();
669 for wrapped in self.0.borrow().as_ref().unwrap().lines.iter() {
670 let mut seen = 0;
671 for boundary in wrapped.layout.wrap_boundaries.iter() {
672 let index = wrapped.layout.unwrapped_layout.runs[boundary.run_ix].glyphs
673 [boundary.glyph_ix]
674 .index;
675
676 lines.push(wrapped.text[seen..index].to_string());
677 seen = index;
678 }
679 lines.push(wrapped.text[seen..].to_string());
680 }
681
682 lines.join("\n")
683 }
684}
685
686pub struct InteractiveText {
688 element_id: ElementId,
689 text: StyledText,
690 click_listener:
691 Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut Window, &mut App)>>,
692 hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App)>>,
693 tooltip_builder: Option<Rc<dyn Fn(usize, &mut Window, &mut App) -> Option<AnyView>>>,
694 tooltip_id: Option<TooltipId>,
695 clickable_ranges: Vec<Range<usize>>,
696}
697
698struct InteractiveTextClickEvent {
699 mouse_down_index: usize,
700 mouse_up_index: usize,
701}
702
703#[doc(hidden)]
704#[derive(Default)]
705pub struct InteractiveTextState {
706 mouse_down_index: Rc<Cell<Option<usize>>>,
707 hovered_index: Rc<Cell<Option<usize>>>,
708 active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
709}
710
711impl InteractiveText {
713 pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
715 Self {
716 element_id: id.into(),
717 text,
718 click_listener: None,
719 hover_listener: None,
720 tooltip_builder: None,
721 tooltip_id: None,
722 clickable_ranges: Vec::new(),
723 }
724 }
725
726 pub fn on_click(
729 mut self,
730 ranges: Vec<Range<usize>>,
731 listener: impl Fn(usize, &mut Window, &mut App) + 'static,
732 ) -> Self {
733 self.click_listener = Some(Box::new(move |ranges, event, window, cx| {
734 for (range_ix, range) in ranges.iter().enumerate() {
735 if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index)
736 {
737 listener(range_ix, window, cx);
738 }
739 }
740 }));
741 self.clickable_ranges = ranges;
742 self
743 }
744
745 pub fn on_hover(
748 mut self,
749 listener: impl Fn(Option<usize>, MouseMoveEvent, &mut Window, &mut App) + 'static,
750 ) -> Self {
751 self.hover_listener = Some(Box::new(listener));
752 self
753 }
754
755 pub fn tooltip(
757 mut self,
758 builder: impl Fn(usize, &mut Window, &mut App) -> Option<AnyView> + 'static,
759 ) -> Self {
760 self.tooltip_builder = Some(Rc::new(builder));
761 self
762 }
763}
764
765impl Element for InteractiveText {
766 type RequestLayoutState = ();
767 type PrepaintState = Hitbox;
768
769 fn id(&self) -> Option<ElementId> {
770 Some(self.element_id.clone())
771 }
772
773 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
774 None
775 }
776
777 fn a11y_role(&self) -> Option<accesskit::Role> {
778 Some(accesskit::Role::Label)
779 }
780
781 fn write_a11y_info(&self, node: &mut accesskit::Node) {
782 node.set_value(self.text.text.to_string());
783 }
784
785 fn request_layout(
786 &mut self,
787 _id: Option<&GlobalElementId>,
788 inspector_id: Option<&InspectorElementId>,
789 window: &mut Window,
790 cx: &mut App,
791 ) -> (LayoutId, Self::RequestLayoutState) {
792 self.text.request_layout(None, inspector_id, window, cx)
793 }
794
795 fn prepaint(
796 &mut self,
797 global_id: Option<&GlobalElementId>,
798 inspector_id: Option<&InspectorElementId>,
799 bounds: Bounds<Pixels>,
800 state: &mut Self::RequestLayoutState,
801 window: &mut Window,
802 cx: &mut App,
803 ) -> Hitbox {
804 window.with_optional_element_state::<InteractiveTextState, _>(
805 global_id,
806 |interactive_state, window| {
807 let mut interactive_state = interactive_state
808 .map(|interactive_state| interactive_state.unwrap_or_default());
809
810 if let Some(interactive_state) = interactive_state.as_mut() {
811 if self.tooltip_builder.is_some() {
812 self.tooltip_id =
813 set_tooltip_on_window(&interactive_state.active_tooltip, window);
814 } else {
815 interactive_state.active_tooltip.take();
817 }
818 }
819
820 self.text
821 .prepaint(None, inspector_id, bounds, state, window, cx);
822 let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
823 (hitbox, interactive_state)
824 },
825 )
826 }
827
828 fn paint(
829 &mut self,
830 global_id: Option<&GlobalElementId>,
831 inspector_id: Option<&InspectorElementId>,
832 bounds: Bounds<Pixels>,
833 _: &mut Self::RequestLayoutState,
834 hitbox: &mut Hitbox,
835 window: &mut Window,
836 cx: &mut App,
837 ) {
838 let current_view = window.current_view();
839 let text_layout = self.text.layout().clone();
840 window.with_element_state::<InteractiveTextState, _>(
841 global_id.unwrap(),
842 |interactive_state, window| {
843 let mut interactive_state = interactive_state.unwrap_or_default();
844 if let Some(click_listener) = self.click_listener.take() {
845 let mouse_position = window.mouse_position();
846 if let Ok(ix) = text_layout.index_for_position(mouse_position)
847 && self
848 .clickable_ranges
849 .iter()
850 .any(|range| range.contains(&ix))
851 {
852 window.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
853 }
854
855 let text_layout = text_layout.clone();
856 let mouse_down = interactive_state.mouse_down_index.clone();
857 if let Some(mouse_down_index) = mouse_down.get() {
858 let hitbox = hitbox.clone();
859 let clickable_ranges = mem::take(&mut self.clickable_ranges);
860 window.on_mouse_event(
861 move |event: &MouseUpEvent, phase, window: &mut Window, cx| {
862 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
863 if let Ok(mouse_up_index) =
864 text_layout.index_for_position(event.position)
865 {
866 click_listener(
867 &clickable_ranges,
868 InteractiveTextClickEvent {
869 mouse_down_index,
870 mouse_up_index,
871 },
872 window,
873 cx,
874 )
875 }
876
877 mouse_down.take();
878 window.refresh();
879 }
880 },
881 );
882 } else {
883 let hitbox = hitbox.clone();
884 window.on_mouse_event(move |event: &MouseDownEvent, phase, window, _| {
885 if phase == DispatchPhase::Bubble
886 && hitbox.is_hovered(window)
887 && let Ok(mouse_down_index) =
888 text_layout.index_for_position(event.position)
889 {
890 mouse_down.set(Some(mouse_down_index));
891 window.refresh();
892 }
893 });
894 }
895 }
896
897 window.on_mouse_event({
898 let mut hover_listener = self.hover_listener.take();
899 let hitbox = hitbox.clone();
900 let text_layout = text_layout.clone();
901 let hovered_index = interactive_state.hovered_index.clone();
902 move |event: &MouseMoveEvent, phase, window, cx| {
903 if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
904 let current = hovered_index.get();
905 let updated = text_layout.index_for_position(event.position).ok();
906 if current != updated {
907 hovered_index.set(updated);
908 if let Some(hover_listener) = hover_listener.as_ref() {
909 hover_listener(updated, event.clone(), window, cx);
910 }
911 cx.notify(current_view);
912 }
913 }
914 }
915 });
916
917 if let Some(tooltip_builder) = self.tooltip_builder.clone() {
918 let active_tooltip = interactive_state.active_tooltip.clone();
919 let build_tooltip = Rc::new({
920 let tooltip_is_hoverable = false;
921 let text_layout = text_layout.clone();
922 move |window: &mut Window, cx: &mut App| {
923 text_layout
924 .index_for_position(window.mouse_position())
925 .ok()
926 .and_then(|position| tooltip_builder(position, window, cx))
927 .map(|view| (view, tooltip_is_hoverable))
928 }
929 });
930
931 let check_is_hovered_during_prepaint = Rc::new({
933 let source_bounds = hitbox.bounds;
934 let text_layout = text_layout.clone();
935 let pending_mouse_down = interactive_state.mouse_down_index.clone();
936 move |window: &Window| {
937 text_layout
938 .index_for_position(window.mouse_position())
939 .is_ok()
940 && source_bounds.contains(&window.mouse_position())
941 && pending_mouse_down.get().is_none()
942 }
943 });
944
945 let check_is_hovered = Rc::new({
946 let hitbox = hitbox.clone();
947 let text_layout = text_layout.clone();
948 let pending_mouse_down = interactive_state.mouse_down_index.clone();
949 move |window: &Window| {
950 text_layout
951 .index_for_position(window.mouse_position())
952 .is_ok()
953 && hitbox.is_hovered(window)
954 && pending_mouse_down.get().is_none()
955 }
956 });
957
958 register_tooltip_mouse_handlers(
959 &active_tooltip,
960 self.tooltip_id,
961 build_tooltip,
962 check_is_hovered,
963 check_is_hovered_during_prepaint,
964 None,
965 window,
966 );
967 }
968
969 self.text
970 .paint(None, inspector_id, bounds, &mut (), &mut (), window, cx);
971
972 ((), interactive_state)
973 },
974 );
975 }
976}
977
978impl IntoElement for InteractiveText {
979 type Element = Self;
980
981 fn into_element(self) -> Self::Element {
982 self
983 }
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use crate::{px, size};
990
991 #[test]
992 fn truncated_layout_is_not_reused_for_an_unconstrained_measurement() {
993 let cached = TextLayoutInner {
994 len: 4,
995 lines: SmallVec::new(),
996 line_height: px(16.0),
997 wrap_width: None,
998 truncate_width: Some(px(24.0)),
999 size: Some(size(px(24.0), px(16.0))),
1000 bounds: None,
1001 };
1002
1003 assert_eq!(cached_size_for_constraints(&cached, None, None), None);
1004 }
1005
1006 #[test]
1007 fn exact_fit_shaped_text_does_not_require_truncation() {
1008 assert!(all_shaped_line_widths_fit([px(72.0), px(80.0)], px(80.0)));
1009 assert!(!all_shaped_line_widths_fit([px(80.5)], px(80.0)));
1010 }
1011}