1use iced_widget::graphics::text::Paragraph;
2
3use crate::core::alignment;
4use crate::core::clipboard;
5use crate::core::keyboard;
6use crate::core::keyboard::key;
7use crate::core::layout;
8use crate::core::mouse;
9use crate::core::renderer;
10use crate::core::text::{Paragraph as _, Span};
11use crate::core::touch;
12use crate::core::widget::text::{Alignment, LineHeight, Shaping, Wrapping};
13use crate::core::widget::tree::{self, Tree};
14use crate::core::{
15 self, Clipboard, Element, Event, Font, Layout, Length, Pixels, Point,
16 Rectangle, Shell, Size, Vector, Widget,
17};
18use crate::selection::{Selection, SelectionEnd};
19use crate::text::{Catalog, Dragging, Style, StyleFn};
20
21pub struct Rich<
23 'a,
24 Link,
25 Message,
26 Theme = iced_widget::Theme,
27 Renderer = iced_widget::Renderer,
28> where
29 Link: Clone + 'static,
30 Theme: Catalog,
31 Renderer: core::text::Renderer,
32{
33 spans: Box<dyn AsRef<[Span<'a, Link, Renderer::Font>]> + 'a>,
34 size: Option<Pixels>,
35 line_height: LineHeight,
36 width: Length,
37 height: Length,
38 font: Option<Renderer::Font>,
39 align_x: Alignment,
40 align_y: alignment::Vertical,
41 wrapping: Wrapping,
42 class: Theme::Class<'a>,
43 on_link_click: Option<Box<dyn Fn(Link) -> Message + 'a>>,
44 on_link_hover: Option<Box<dyn Fn(Link) -> Message + 'a>>,
45 on_hover_lost: Option<Box<dyn Fn() -> Message + 'a>>,
46}
47
48impl<'a, Link, Message, Theme, Renderer>
49 Rich<'a, Link, Message, Theme, Renderer>
50where
51 Link: Clone + 'static,
52 Theme: Catalog,
53 Renderer: core::text::Renderer,
54 Renderer::Font: 'a,
55{
56 pub fn new() -> Self {
58 Self {
59 spans: Box::new([]),
60 size: None,
61 line_height: LineHeight::default(),
62 width: Length::Shrink,
63 height: Length::Shrink,
64 font: None,
65 align_x: Alignment::Default,
66 align_y: alignment::Vertical::Top,
67 wrapping: Wrapping::default(),
68 class: Theme::default(),
69 on_link_click: None,
70 on_link_hover: None,
71 on_hover_lost: None,
72 }
73 }
74
75 pub fn with_spans(
77 spans: impl AsRef<[Span<'a, Link, Renderer::Font>]> + 'a,
78 ) -> Self {
79 Self {
80 spans: Box::new(spans),
81 ..Self::new()
82 }
83 }
84
85 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
87 self.size = Some(size.into());
88 self
89 }
90
91 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
93 self.line_height = line_height.into();
94 self
95 }
96
97 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
99 self.font = Some(font.into());
100 self
101 }
102
103 pub fn width(mut self, width: impl Into<Length>) -> Self {
105 self.width = width.into();
106 self
107 }
108
109 pub fn height(mut self, height: impl Into<Length>) -> Self {
111 self.height = height.into();
112 self
113 }
114
115 pub fn center(self) -> Self {
117 self.align_x(alignment::Horizontal::Center)
118 .align_y(alignment::Vertical::Center)
119 }
120
121 pub fn align_x(mut self, alignment: impl Into<Alignment>) -> Self {
123 self.align_x = alignment.into();
124 self
125 }
126
127 pub fn align_y(
129 mut self,
130 alignment: impl Into<alignment::Vertical>,
131 ) -> Self {
132 self.align_y = alignment.into();
133 self
134 }
135
136 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
138 self.wrapping = wrapping;
139 self
140 }
141
142 pub fn on_link_click(
145 mut self,
146 on_link_clicked: impl Fn(Link) -> Message + 'a,
147 ) -> Self {
148 self.on_link_click = Some(Box::new(on_link_clicked));
149 self
150 }
151
152 pub fn on_link_hover(
155 mut self,
156 on_link_hovered: impl Fn(Link) -> Message + 'a,
157 ) -> Self {
158 self.on_link_hover = Some(Box::new(on_link_hovered));
159 self
160 }
161
162 pub fn on_hover_lost(mut self, on_hover_lost: Message) -> Self
165 where
166 Message: Clone + 'a,
167 {
168 self.on_hover_lost = Some(Box::new(move || on_hover_lost.clone()));
169 self
170 }
171
172 pub fn on_hover_lost_with(
175 mut self,
176 on_hover_lost: impl Fn() -> Message + 'a,
177 ) -> Self {
178 self.on_hover_lost = Some(Box::new(on_hover_lost));
179 self
180 }
181
182 #[must_use]
184 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
185 where
186 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
187 {
188 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
189 self
190 }
191
192 #[must_use]
194 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
195 self.class = class.into();
196 self
197 }
198}
199
200impl<'a, Link, Message, Theme, Renderer> Default
201 for Rich<'a, Link, Message, Theme, Renderer>
202where
203 Link: Clone + 'a,
204 Theme: Catalog,
205 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font>,
206 Renderer::Font: 'a,
207{
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213struct State<Link> {
214 spans: Vec<Span<'static, Link, Font>>,
215 span_pressed: Option<usize>,
216 hovered_link: Option<usize>,
217 paragraph: Paragraph,
218 is_hovered: bool,
219 selection: Selection,
220 dragging: Option<Dragging>,
221 last_click: Option<mouse::Click>,
222 keyboard_modifiers: keyboard::Modifiers,
223}
224
225impl<Link> State<Link> {
226 fn grapheme_line_and_index(&self, point: Point) -> Option<(usize, usize)> {
227 let cursor = self.paragraph.buffer().hit(point.x, point.y)?;
228 let value = self.paragraph.buffer().lines[cursor.line].text();
229
230 Some((
231 cursor.line,
232 unicode_segmentation::UnicodeSegmentation::graphemes(
233 &value[..cursor.index.min(value.len())],
234 true,
235 )
236 .count(),
237 ))
238 }
239
240 fn selection_end_points(&self) -> (usize, Point, Point) {
241 let Selection { start, end, .. } = self.selection;
242
243 let (start_row, start_position) = self
244 .grapheme_position(start.line, start.index)
245 .unwrap_or_default();
246
247 let (end_row, end_position) = self
248 .grapheme_position(end.line, end.index)
249 .unwrap_or_default();
250
251 (
252 end_row.saturating_sub(start_row) + 1,
253 start_position,
254 end_position,
255 )
256 }
257
258 fn grapheme_position(
259 &self,
260 line: usize,
261 index: usize,
262 ) -> Option<(usize, Point)> {
263 use unicode_segmentation::UnicodeSegmentation;
264
265 let mut first_run_index = None;
266 let mut last_run_index = None;
267 let mut last_start = None;
268 let mut last_grapheme_count = 0;
269 let mut last_run_graphemes = 0;
270 let mut real_index = 0;
271 let mut graphemes_seen = 0;
272
273 let mut glyphs = self
274 .paragraph
275 .buffer()
276 .layout_runs()
277 .enumerate()
278 .filter(|(_, run)| run.line_i == line)
279 .flat_map(|(run_idx, run)| {
280 let line_top = run.line_top;
281
282 if first_run_index.is_none() {
283 first_run_index = Some(run_idx);
284 }
285
286 run.glyphs.iter().map(move |glyph| {
287 let mut glyph = glyph.clone();
288 glyph.y += line_top;
289 (run_idx, glyph, run.text)
290 })
291 });
292
293 let (_, glyph, _) = glyphs
294 .find(|(run_idx, glyph, text)| {
295 if Some(glyph.start) != last_start {
296 last_grapheme_count =
297 text[glyph.start..glyph.end].graphemes(false).count();
298 last_start = Some(glyph.start);
299 graphemes_seen += last_grapheme_count;
300 last_run_graphemes += last_grapheme_count;
301 real_index += last_grapheme_count;
302
303 if Some(*run_idx) != last_run_index
304 && graphemes_seen < index
305 {
306 real_index = last_grapheme_count;
307 last_run_graphemes = last_grapheme_count;
308 }
309 } else if Some(*run_idx) != last_run_index
310 && graphemes_seen < index
311 {
312 real_index = 0;
313 last_run_graphemes = 0;
314 }
315
316 last_run_index = Some(*run_idx);
317
318 graphemes_seen >= index
319 })
320 .or_else(|| glyphs.last())?;
321
322 real_index -= graphemes_seen.saturating_sub(index);
323 real_index =
324 real_index.saturating_sub(last_run_index? - first_run_index?);
325
326 last_run_graphemes = last_run_graphemes
327 .saturating_sub(last_run_index? - first_run_index?);
328
329 let advance = if last_run_index? - first_run_index? <= 1 {
330 if real_index == 0 {
331 0.0
332 } else {
333 glyph.w
334 * (1.0
335 - last_run_graphemes.saturating_sub(real_index) as f32
336 / last_grapheme_count.max(1) as f32)
337 - glyph.w * (last_run_index? - first_run_index?) as f32
338 }
339 } else {
340 -(glyph.w
341 * (1.0
342 + last_run_graphemes.saturating_sub(real_index) as f32
343 / last_grapheme_count.max(1) as f32))
344 };
345
346 Some((
347 last_run_index?,
348 Point::new(
349 glyph.x + glyph.x_offset * glyph.font_size + advance,
350 glyph.y - glyph.y_offset * glyph.font_size,
351 ),
352 ))
353 }
354}
355
356impl<Link, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
357 for Rich<'_, Link, Message, Theme, Renderer>
358where
359 Link: Clone + 'static,
360 Theme: Catalog,
361 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font>,
362{
363 fn tag(&self) -> tree::Tag {
364 tree::Tag::of::<State<Link>>()
365 }
366
367 fn state(&self) -> tree::State {
368 tree::State::new(State::<Link> {
369 spans: Vec::new(),
370 span_pressed: None,
371 hovered_link: None,
372 paragraph: Paragraph::default(),
373 is_hovered: false,
374 selection: Selection::default(),
375 dragging: None,
376 last_click: None,
377 keyboard_modifiers: keyboard::Modifiers::default(),
378 })
379 }
380
381 fn size(&self) -> Size<Length> {
382 Size {
383 width: self.width,
384 height: self.height,
385 }
386 }
387
388 fn layout(
389 &mut self,
390 tree: &mut Tree,
391 renderer: &Renderer,
392 limits: &layout::Limits,
393 ) -> layout::Node {
394 layout(
395 tree.state.downcast_mut::<State<Link>>(),
396 renderer,
397 limits,
398 self.width,
399 self.height,
400 self.spans.as_ref().as_ref(),
401 self.line_height,
402 self.size,
403 self.font,
404 self.align_x,
405 self.align_y,
406 self.wrapping,
407 )
408 }
409
410 fn draw(
411 &self,
412 tree: &Tree,
413 renderer: &mut Renderer,
414 theme: &Theme,
415 defaults: &renderer::Style,
416 layout: Layout<'_>,
417 _cursor: mouse::Cursor,
418 viewport: &Rectangle,
419 ) {
420 if !layout.bounds().intersects(viewport) {
421 return;
422 }
423
424 let state = tree.state.downcast_ref::<State<Link>>();
425
426 let style = theme.style(&self.class);
427
428 for (index, span) in self.spans.as_ref().as_ref().iter().enumerate() {
429 let is_hovered_link = self.on_link_click.is_some()
430 && Some(index) == state.hovered_link;
431
432 if span.highlight.is_some()
433 || span.underline
434 || span.strikethrough
435 || is_hovered_link
436 {
437 let translation = layout.position() - Point::ORIGIN;
438 let regions = state.paragraph.span_bounds(index);
439
440 if let Some(highlight) = span.highlight {
441 for bounds in ®ions {
442 let bounds = Rectangle::new(
443 bounds.position()
444 - Vector::new(
445 span.padding.left,
446 span.padding.top,
447 ),
448 bounds.size()
449 + Size::new(span.padding.x(), span.padding.y()),
450 );
451
452 renderer.fill_quad(
453 renderer::Quad {
454 bounds: bounds + translation,
455 border: highlight.border,
456 ..Default::default()
457 },
458 highlight.background,
459 );
460 }
461 }
462
463 if span.underline || span.strikethrough || is_hovered_link {
464 let size = span
465 .size
466 .or(self.size)
467 .unwrap_or(renderer.default_size());
468
469 let line_height = span
470 .line_height
471 .unwrap_or(self.line_height)
472 .to_absolute(size);
473
474 let color = span
475 .color
476 .or(style.color)
477 .unwrap_or(defaults.text_color);
478
479 let baseline = translation
480 + Vector::new(
481 0.0,
482 size.0 + (line_height.0 - size.0) / 2.0,
483 );
484
485 if span.underline || is_hovered_link {
486 for bounds in ®ions {
487 renderer.fill_quad(
488 renderer::Quad {
489 bounds: Rectangle::new(
490 bounds.position() + baseline
491 - Vector::new(0.0, size.0 * 0.08),
492 Size::new(bounds.width, 1.0),
493 ),
494 ..Default::default()
495 },
496 color,
497 );
498 }
499 }
500
501 if span.strikethrough {
502 for bounds in ®ions {
503 renderer.fill_quad(
504 renderer::Quad {
505 bounds: Rectangle::new(
506 bounds.position() + baseline
507 - Vector::new(0.0, size.0 / 2.0),
508 Size::new(bounds.width, 1.0),
509 ),
510 ..Default::default()
511 },
512 color,
513 );
514 }
515 }
516 }
517 }
518 }
519
520 if !state.selection.is_empty() {
521 let bounds = layout.bounds();
522
523 let (rows, mut start, mut end) = state.selection_end_points();
524 start += core::Vector::new(bounds.x, bounds.y);
525 end += core::Vector::new(bounds.x, bounds.y);
526
527 let line_height = self
528 .line_height
529 .to_absolute(
530 self.size.unwrap_or_else(|| renderer.default_size()),
531 )
532 .0;
533
534 let baseline_y = bounds.y
535 + (((start.y - bounds.y) * 10.0).ceil() / 10.0 / line_height)
536 .floor()
537 * line_height;
538
539 for row in 0..rows {
540 let (x, width) = if row == 0 {
541 (
542 start.x,
543 if rows == 1 {
544 end.x.min(bounds.x + bounds.width) - start.x
545 } else {
546 bounds.x + bounds.width - start.x
547 },
548 )
549 } else if row == rows - 1 {
550 (bounds.x, end.x - bounds.x)
551 } else {
552 (bounds.x, bounds.width)
553 };
554
555 let y = baseline_y + row as f32 * line_height;
556
557 renderer.fill_quad(
558 renderer::Quad {
559 bounds: Rectangle {
560 x,
561 y,
562 width,
563 height: line_height,
564 },
565 snap: true,
566 ..Default::default()
567 },
568 style.selection,
569 );
570 }
571 }
572
573 crate::text::draw(
574 renderer,
575 defaults,
576 layout.bounds(),
577 &state.paragraph,
578 style,
579 viewport,
580 );
581 }
582
583 fn update(
584 &mut self,
585 tree: &mut Tree,
586 event: &Event,
587 layout: Layout<'_>,
588 cursor: mouse::Cursor,
589 _renderer: &Renderer,
590 clipboard: &mut dyn Clipboard,
591 shell: &mut Shell<'_, Message>,
592 viewport: &Rectangle,
593 ) {
594 let state = tree.state.downcast_mut::<State<Link>>();
595
596 let bounds = layout.bounds();
597 let click_position = cursor.position_in(bounds);
598
599 if viewport.intersection(&bounds).is_none()
600 && state.selection.is_empty()
601 && state.dragging.is_none()
602 {
603 return;
604 }
605
606 let was_hovered = state.is_hovered;
607 let hovered_link_before = state.hovered_link;
608 let selection_before = state.selection;
609
610 state.is_hovered = click_position.is_some();
611
612 if let Some(position) = click_position {
613 state.hovered_link =
614 state.paragraph.hit_span(position).and_then(|span| {
615 if self.spans.as_ref().as_ref().get(span)?.link.is_some() {
616 Some(span)
617 } else {
618 None
619 }
620 });
621 } else {
622 state.hovered_link = None;
623 }
624
625 match event {
626 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
627 | Event::Touch(touch::Event::FingerPressed { .. }) => {
628 if state.hovered_link.is_some() {
629 state.span_pressed = state.hovered_link;
630 shell.capture_event();
631 }
632
633 if let Some(position) = click_position {
634 let click = mouse::Click::new(
635 position,
636 mouse::Button::Left,
637 state.last_click,
638 );
639
640 let (line, index) = state
641 .grapheme_line_and_index(position)
642 .unwrap_or((0, 0));
643
644 match click.kind() {
645 mouse::click::Kind::Single => {
646 let new_end = SelectionEnd { line, index };
647
648 if state.keyboard_modifiers.shift() {
649 state.selection.change_selection(new_end);
650 } else {
651 state.selection.select_range(new_end, new_end);
652 }
653
654 state.dragging = Some(Dragging::Grapheme);
655 }
656 mouse::click::Kind::Double => {
657 state.selection.select_word(
658 line,
659 index,
660 &state.paragraph,
661 );
662 state.dragging = Some(Dragging::Word);
663 }
664 mouse::click::Kind::Triple => {
665 state.selection.select_line(line, &state.paragraph);
666 state.dragging = Some(Dragging::Line);
667 }
668 }
669
670 state.last_click = Some(click);
671
672 shell.capture_event();
673 } else {
674 state.selection = Selection::default();
675 }
676 }
677 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
678 | Event::Touch(touch::Event::FingerLifted { .. })
679 | Event::Touch(touch::Event::FingerLost { .. }) => {
680 state.dragging = None;
681 if !matches!(
682 event,
683 Event::Touch(touch::Event::FingerLost { .. })
684 ) && state.selection.is_empty()
685 {
686 match state.span_pressed {
687 Some(span) if Some(span) == state.hovered_link => {
688 if let Some((link, on_link_clicked)) = self
689 .spans
690 .as_ref()
691 .as_ref()
692 .get(span)
693 .and_then(|span| span.link.clone())
694 .zip(self.on_link_click.as_deref())
695 {
696 shell.publish(on_link_clicked(link));
697 }
698 }
699 _ => {}
700 }
701
702 state.span_pressed = None;
703 }
704 }
705 Event::Mouse(mouse::Event::CursorMoved { .. })
706 | Event::Touch(touch::Event::FingerMoved { .. }) => {
707 if let Some(position) = click_position
708 && let Some(dragging) = state.dragging
709 {
710 let (line, index) = state
711 .grapheme_line_and_index(position)
712 .unwrap_or((0, 0));
713
714 match dragging {
715 Dragging::Grapheme => {
716 let new_end = SelectionEnd { line, index };
717
718 state.selection.change_selection(new_end);
719 }
720 Dragging::Word => {
721 let new_end = SelectionEnd { line, index };
722
723 state.selection.change_selection_by_word(
724 new_end,
725 &state.paragraph,
726 );
727 }
728 Dragging::Line => {
729 state.selection.change_selection_by_line(
730 line,
731 &state.paragraph,
732 );
733 }
734 };
735 }
736 }
737 Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
738 match key.as_ref() {
739 keyboard::Key::Character("c")
740 if state.keyboard_modifiers.command()
741 && !state.selection.is_empty() =>
742 {
743 clipboard.write(
744 clipboard::Kind::Standard,
745 state.selection.text(&state.paragraph),
746 );
747
748 shell.capture_event();
749 }
750 keyboard::Key::Character("a")
751 if state.keyboard_modifiers.command()
752 && !state.selection.is_empty() =>
753 {
754 state.selection.select_all(&state.paragraph);
755
756 shell.capture_event();
757 }
758 keyboard::Key::Named(key::Named::Home)
759 if state.keyboard_modifiers.shift()
760 && !state.selection.is_empty() =>
761 {
762 if state.keyboard_modifiers.jump() {
763 state.selection.select_beginning();
764 } else {
765 state.selection.select_line_beginning();
766 }
767
768 shell.capture_event();
769 }
770 keyboard::Key::Named(key::Named::End)
771 if state.keyboard_modifiers.shift()
772 && !state.selection.is_empty() =>
773 {
774 if state.keyboard_modifiers.jump() {
775 state.selection.select_end(&state.paragraph);
776 } else {
777 state.selection.select_line_end(&state.paragraph);
778 }
779
780 shell.capture_event();
781 }
782 keyboard::Key::Named(key::Named::ArrowLeft)
783 if state.keyboard_modifiers.shift()
784 && !state.selection.is_empty() =>
785 {
786 if state.keyboard_modifiers.macos_command() {
787 state.selection.select_line_beginning();
788 } else if state.keyboard_modifiers.jump() {
789 state
790 .selection
791 .select_left_by_words(&state.paragraph);
792 } else {
793 state.selection.select_left(&state.paragraph);
794 }
795
796 shell.capture_event();
797 }
798 keyboard::Key::Named(key::Named::ArrowRight)
799 if state.keyboard_modifiers.shift()
800 && !state.selection.is_empty() =>
801 {
802 if state.keyboard_modifiers.macos_command() {
803 state.selection.select_line_end(&state.paragraph);
804 } else if state.keyboard_modifiers.jump() {
805 state
806 .selection
807 .select_right_by_words(&state.paragraph);
808 } else {
809 state.selection.select_right(&state.paragraph);
810 }
811
812 shell.capture_event();
813 }
814 keyboard::Key::Named(key::Named::ArrowUp)
815 if state.keyboard_modifiers.shift()
816 && !state.selection.is_empty() =>
817 {
818 if state.keyboard_modifiers.macos_command() {
819 state.selection.select_beginning();
820 } else if state.keyboard_modifiers.jump() {
821 state.selection.select_line_beginning();
822 } else {
823 state.selection.select_up(&state.paragraph);
824 }
825
826 shell.capture_event();
827 }
828 keyboard::Key::Named(key::Named::ArrowDown)
829 if state.keyboard_modifiers.shift()
830 && !state.selection.is_empty() =>
831 {
832 if state.keyboard_modifiers.macos_command() {
833 state.selection.select_end(&state.paragraph);
834 } else if state.keyboard_modifiers.jump() {
835 state.selection.select_line_end(&state.paragraph);
836 } else {
837 state.selection.select_down(&state.paragraph);
838 }
839
840 shell.capture_event();
841 }
842 keyboard::Key::Named(key::Named::Escape) => {
843 state.dragging = None;
844 state.selection = Selection::default();
845
846 state.keyboard_modifiers =
847 keyboard::Modifiers::default();
848
849 if state.selection != selection_before {
850 shell.capture_event();
851 }
852 }
853 _ => {}
854 }
855 }
856 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
857 state.keyboard_modifiers = *modifiers;
858 }
859 _ => {}
860 }
861
862 if state.is_hovered != was_hovered
863 || state.selection != selection_before
864 || state.hovered_link != hovered_link_before
865 {
866 if let Some(span) = state.hovered_link {
867 if let Some((link, on_link_hovered)) = self
868 .spans
869 .as_ref()
870 .as_ref()
871 .get(span)
872 .and_then(|span| span.link.clone())
873 .zip(self.on_link_hover.as_deref())
874 {
875 shell.publish(on_link_hovered(link));
876 }
877 } else if let Some(on_hover_lost) = self.on_hover_lost.as_deref() {
878 shell.publish(on_hover_lost());
879 }
880
881 shell.request_redraw();
882 }
883 }
884
885 fn mouse_interaction(
886 &self,
887 tree: &Tree,
888 layout: Layout<'_>,
889 cursor: mouse::Cursor,
890 _viewport: &Rectangle,
891 _renderer: &Renderer,
892 ) -> mouse::Interaction {
893 let state = tree.state.downcast_ref::<State<Link>>();
894
895 if state.hovered_link.is_some() {
896 mouse::Interaction::Pointer
897 } else if cursor.is_over(layout.bounds()) {
898 mouse::Interaction::Text
899 } else {
900 mouse::Interaction::None
901 }
902 }
903}
904
905#[allow(clippy::too_many_arguments)]
906fn layout<Link, Renderer>(
907 state: &mut State<Link>,
908 renderer: &Renderer,
909 limits: &layout::Limits,
910 width: Length,
911 height: Length,
912 spans: &[Span<'_, Link, Renderer::Font>],
913 line_height: LineHeight,
914 size: Option<Pixels>,
915 font: Option<Renderer::Font>,
916 align_x: Alignment,
917 align_y: alignment::Vertical,
918 wrapping: Wrapping,
919) -> layout::Node
920where
921 Link: Clone,
922 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font>,
923{
924 layout::sized(limits, width, height, |limits| {
925 let bounds = limits.max();
926
927 let size = size.unwrap_or_else(|| renderer.default_size());
928 let font = font.unwrap_or_else(|| renderer.default_font());
929
930 let text_with_spans = || core::Text {
931 content: spans,
932 bounds,
933 size,
934 line_height,
935 font,
936 align_x,
937 align_y,
938 shaping: Shaping::Advanced,
939 wrapping,
940 };
941
942 if state.spans != spans {
943 state.paragraph =
944 Renderer::Paragraph::with_spans(text_with_spans());
945 state.spans = spans.iter().cloned().map(Span::to_static).collect();
946 } else {
947 match state.paragraph.compare(core::Text {
948 content: (),
949 bounds,
950 size,
951 line_height,
952 font,
953 align_x,
954 align_y,
955 shaping: Shaping::Advanced,
956 wrapping,
957 }) {
958 core::text::Difference::None => {}
959 core::text::Difference::Bounds => {
960 state.paragraph.resize(bounds);
961 }
962 core::text::Difference::Shape => {
963 state.paragraph =
964 Renderer::Paragraph::with_spans(text_with_spans());
965 }
966 }
967 }
968
969 state.paragraph.min_bounds()
970 })
971}
972
973impl<'a, Link, Message, Theme, Renderer>
974 FromIterator<Span<'a, Link, Renderer::Font>>
975 for Rich<'a, Link, Message, Theme, Renderer>
976where
977 Link: Clone + 'a,
978 Theme: Catalog,
979 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font>,
980 Renderer::Font: 'a,
981{
982 fn from_iter<T: IntoIterator<Item = Span<'a, Link, Renderer::Font>>>(
983 spans: T,
984 ) -> Self {
985 Self::with_spans(spans.into_iter().collect::<Vec<_>>())
986 }
987}
988
989impl<'a, Link, Message, Theme, Renderer>
990 From<Rich<'a, Link, Message, Theme, Renderer>>
991 for Element<'a, Message, Theme, Renderer>
992where
993 Message: 'a,
994 Link: Clone + 'a,
995 Theme: Catalog + 'a,
996 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
997{
998 fn from(
999 text: Rich<'a, Link, Message, Theme, Renderer>,
1000 ) -> Element<'a, Message, Theme, Renderer> {
1001 Element::new(text)
1002 }
1003}