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}