iced_selection/text/
rich.rs

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
21/// A bunch of [`Rich`] text.
22pub 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    /// Creates a new empty [`Rich`] text.
57    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    /// Creates a new [`Rich`] text with the given text spans.
76    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    /// Sets the default size of the [`Rich`] text.
86    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
87        self.size = Some(size.into());
88        self
89    }
90
91    /// Sets the default [`LineHeight`] of the [`Rich`] text.
92    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
93        self.line_height = line_height.into();
94        self
95    }
96
97    /// Sets the default font of the [`Rich`] text.
98    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
99        self.font = Some(font.into());
100        self
101    }
102
103    /// Sets the width of the [`Rich`] text boundaries.
104    pub fn width(mut self, width: impl Into<Length>) -> Self {
105        self.width = width.into();
106        self
107    }
108
109    /// Sets the height of the [`Rich`] text boundaries.
110    pub fn height(mut self, height: impl Into<Length>) -> Self {
111        self.height = height.into();
112        self
113    }
114
115    /// Centers the [`Rich`] text, both horizontally and vertically.
116    pub fn center(self) -> Self {
117        self.align_x(alignment::Horizontal::Center)
118            .align_y(alignment::Vertical::Center)
119    }
120
121    /// Sets the [`alignment::Horizontal`] of the [`Rich`] text.
122    pub fn align_x(mut self, alignment: impl Into<Alignment>) -> Self {
123        self.align_x = alignment.into();
124        self
125    }
126
127    /// Sets the [`alignment::Vertical`] of the [`Rich`] text.
128    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    /// Sets the [`Wrapping`] strategy of the [`Rich`] text.
137    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
138        self.wrapping = wrapping;
139        self
140    }
141
142    /// Sets the message that will be produced when a link of the [`Rich`] text
143    /// is clicked.
144    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    /// Sets the message that will be produced when a link of the [`Rich`] text
153    /// is hovered.
154    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    /// Sets the message that will be produced when a link of the [`Rich`] text
163    /// is no longer hovered.
164    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    /// Sets the message that will be produced when a link of the [`Rich`] text
173    /// is no longer hovered.
174    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    /// Sets the default style of the [`Rich`] text.
183    #[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    /// Sets the default style class of the [`Rich`] text.
193    #[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 &regions {
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 &regions {
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 &regions {
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}