summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-21 12:45:05 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-21 12:45:05 +0200
commit9bfaf2840cffe35d689bd115a308d21961ab082a (patch)
treec9a7b4fc04607c3315dabe3ad2527251b0a3dff8
parent4b44079f34aa9e01977a7974e5f49ae79ff6cd90 (diff)
downloadiced-9bfaf2840cffe35d689bd115a308d21961ab082a.tar.gz
iced-9bfaf2840cffe35d689bd115a308d21961ab082a.tar.bz2
iced-9bfaf2840cffe35d689bd115a308d21961ab082a.zip
Add `Link` support to `rich_text` widget
-rw-r--r--core/src/renderer/null.rs8
-rw-r--r--core/src/text.rs34
-rw-r--r--core/src/text/paragraph.rs9
-rw-r--r--examples/markdown/Cargo.toml2
-rw-r--r--examples/markdown/src/main.rs6
-rw-r--r--graphics/src/text/paragraph.rs42
-rw-r--r--widget/src/helpers.rs7
-rw-r--r--widget/src/markdown.rs68
-rw-r--r--widget/src/text/rich.rs182
9 files changed, 287 insertions, 71 deletions
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index f9d1a5b0..7aa3aafb 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -77,8 +77,8 @@ impl text::Paragraph for () {
fn with_text(_text: Text<&str>) -> Self {}
- fn with_spans(
- _text: Text<&[text::Span<'_, Self::Font>], Self::Font>,
+ fn with_spans<Link>(
+ _text: Text<&[text::Span<'_, Link, Self::Font>], Self::Font>,
) -> Self {
}
@@ -107,6 +107,10 @@ impl text::Paragraph for () {
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
None
}
+
+ fn hit_span(&self, _point: Point) -> Option<usize> {
+ None
+ }
}
impl text::Editor for () {
diff --git a/core/src/text.rs b/core/src/text.rs
index 22cfce13..c22734f8 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -223,8 +223,8 @@ pub trait Renderer: crate::Renderer {
}
/// A span of text.
-#[derive(Debug, Clone, PartialEq)]
-pub struct Span<'a, Font = crate::Font> {
+#[derive(Debug, Clone)]
+pub struct Span<'a, Link = (), Font = crate::Font> {
/// The [`Fragment`] of text.
pub text: Fragment<'a>,
/// The size of the [`Span`] in [`Pixels`].
@@ -235,9 +235,11 @@ pub struct Span<'a, Font = crate::Font> {
pub font: Option<Font>,
/// The [`Color`] of the [`Span`].
pub color: Option<Color>,
+ /// The link of the [`Span`].
+ pub link: Option<Link>,
}
-impl<'a, Font> Span<'a, Font> {
+impl<'a, Link, Font> Span<'a, Link, Font> {
/// Creates a new [`Span`] of text with the given text fragment.
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
Self {
@@ -246,6 +248,7 @@ impl<'a, Font> Span<'a, Font> {
line_height: None,
font: None,
color: None,
+ link: None,
}
}
@@ -285,14 +288,27 @@ impl<'a, Font> Span<'a, Font> {
self
}
+ /// Sets the link of the [`Span`].
+ pub fn link(mut self, link: impl Into<Link>) -> Self {
+ self.link = Some(link.into());
+ self
+ }
+
+ /// Sets the link of the [`Span`], if any.
+ pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
+ self.link = link.map(Into::into);
+ self
+ }
+
/// Turns the [`Span`] into a static one.
- pub fn to_static(self) -> Span<'static, Font> {
+ pub fn to_static(self) -> Span<'static, Link, Font> {
Span {
text: Cow::Owned(self.text.into_owned()),
size: self.size,
line_height: self.line_height,
font: self.font,
color: self.color,
+ link: self.link,
}
}
}
@@ -303,6 +319,16 @@ impl<'a, Font> From<&'a str> for Span<'a, Font> {
}
}
+impl<'a, Link, Font: PartialEq> PartialEq for Span<'a, Link, Font> {
+ fn eq(&self, other: &Self) -> bool {
+ self.text == other.text
+ && self.size == other.size
+ && self.line_height == other.line_height
+ && self.font == other.font
+ && self.color == other.color
+ }
+}
+
/// A fragment of [`Text`].
///
/// This is just an alias to a string that may be either
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index 4ee83798..26650793 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -12,7 +12,9 @@ pub trait Paragraph: Sized + Default {
fn with_text(text: Text<&str, Self::Font>) -> Self;
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
- fn with_spans(text: Text<&[Span<'_, Self::Font>], Self::Font>) -> Self;
+ fn with_spans<Link>(
+ text: Text<&[Span<'_, Link, Self::Font>], Self::Font>,
+ ) -> Self;
/// Lays out the [`Paragraph`] with some new boundaries.
fn resize(&mut self, new_bounds: Size);
@@ -35,6 +37,11 @@ pub trait Paragraph: Sized + Default {
/// [`Paragraph`], returning information about the nearest character.
fn hit_test(&self, point: Point) -> Option<Hit>;
+ /// Tests whether the provided point is within the boundaries of a
+ /// [`Span`] in the [`Paragraph`], returning the index of the [`Span`]
+ /// that was hit.
+ fn hit_span(&self, point: Point) -> Option<usize>;
+
/// Returns the distance to the given grapheme index in the [`Paragraph`].
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
diff --git a/examples/markdown/Cargo.toml b/examples/markdown/Cargo.toml
index 9404d5d2..cb74b954 100644
--- a/examples/markdown/Cargo.toml
+++ b/examples/markdown/Cargo.toml
@@ -8,3 +8,5 @@ publish = false
[dependencies]
iced.workspace = true
iced.features = ["markdown", "highlighter", "debug"]
+
+open = "5.3"
diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs
index 6b7adc12..bb6eb57b 100644
--- a/examples/markdown/src/main.rs
+++ b/examples/markdown/src/main.rs
@@ -16,6 +16,7 @@ struct Markdown {
#[derive(Debug, Clone)]
enum Message {
Edit(text_editor::Action),
+ LinkClicked(String),
}
impl Markdown {
@@ -50,6 +51,9 @@ impl Markdown {
.collect();
}
}
+ Message::LinkClicked(link) => {
+ let _ = open::that(link);
+ }
}
}
@@ -60,7 +64,7 @@ impl Markdown {
.padding(10)
.font(Font::MONOSPACE);
- let preview = markdown(&self.items);
+ let preview = markdown(&self.items, Message::LinkClicked);
row![editor, scrollable(preview).spacing(10).height(Fill)]
.spacing(10)
diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs
index 37fa97f2..b69110b2 100644
--- a/graphics/src/text/paragraph.rs
+++ b/graphics/src/text/paragraph.rs
@@ -100,8 +100,8 @@ impl core::text::Paragraph for Paragraph {
}))
}
- fn with_spans(text: Text<&[Span<'_>]>) -> Self {
- log::trace!("Allocating rich paragraph: {:?}", text.content);
+ fn with_spans<Link>(text: Text<&[Span<'_, Link>]>) -> Self {
+ log::trace!("Allocating rich paragraph: {} spans", text.content.len());
let mut font_system =
text::font_system().write().expect("Write font system");
@@ -122,11 +122,9 @@ impl core::text::Paragraph for Paragraph {
buffer.set_rich_text(
font_system.raw(),
- text.content.iter().map(|span| {
- let attrs = cosmic_text::Attrs::new();
-
+ text.content.iter().enumerate().map(|(i, span)| {
let attrs = if let Some(font) = span.font {
- attrs
+ cosmic_text::Attrs::new()
.family(text::to_family(font.family))
.weight(text::to_weight(font.weight))
.stretch(text::to_stretch(font.stretch))
@@ -156,7 +154,7 @@ impl core::text::Paragraph for Paragraph {
attrs
};
- (span.text.as_ref(), attrs)
+ (span.text.as_ref(), attrs.metadata(i))
}),
text::to_attributes(text.font),
text::to_shaping(text.shaping),
@@ -231,6 +229,36 @@ impl core::text::Paragraph for Paragraph {
Some(Hit::CharOffset(cursor.index))
}
+ fn hit_span(&self, point: Point) -> Option<usize> {
+ let internal = self.internal();
+
+ let cursor = internal.buffer.hit(point.x, point.y)?;
+ let line = internal.buffer.lines.get(cursor.line)?;
+
+ let mut last_glyph = None;
+ let mut glyphs = line
+ .layout_opt()
+ .as_ref()?
+ .iter()
+ .flat_map(|line| line.glyphs.iter())
+ .peekable();
+
+ while let Some(glyph) = glyphs.peek() {
+ if glyph.start <= cursor.index && cursor.index < glyph.end {
+ break;
+ }
+
+ last_glyph = glyphs.next();
+ }
+
+ let glyph = match cursor.affinity {
+ cosmic_text::Affinity::Before => last_glyph,
+ cosmic_text::Affinity::After => glyphs.next(),
+ }?;
+
+ Some(glyph.metadata)
+ }
+
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
use unicode_segmentation::UnicodeSegmentation;
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 6def61d5..5b1cb5bc 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -683,10 +683,11 @@ where
/// Creates a new [`Rich`] text widget with the provided spans.
///
/// [`Rich`]: text::Rich
-pub fn rich_text<'a, Theme, Renderer>(
- spans: impl Into<Cow<'a, [text::Span<'a, Renderer::Font>]>>,
-) -> text::Rich<'a, Theme, Renderer>
+pub fn rich_text<'a, Message, Link, Theme, Renderer>(
+ spans: impl Into<Cow<'a, [text::Span<'a, Link, Renderer::Font>]>>,
+) -> text::Rich<'a, Message, Link, Theme, Renderer>
where
+ Link: Clone,
Theme: text::Catalog + 'a,
Renderer: core::text::Renderer,
{
diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs
index de691a4d..dc207a34 100644
--- a/widget/src/markdown.rs
+++ b/widget/src/markdown.rs
@@ -14,13 +14,13 @@ use crate::{column, container, rich_text, row, span, text};
#[derive(Debug, Clone)]
pub enum Item {
/// A heading.
- Heading(Vec<text::Span<'static>>),
+ Heading(Vec<text::Span<'static, String>>),
/// A paragraph.
- Paragraph(Vec<text::Span<'static>>),
+ Paragraph(Vec<text::Span<'static, String>>),
/// A code block.
///
/// You can enable the `highlighter` feature for syntax highligting.
- CodeBlock(Vec<text::Span<'static>>),
+ CodeBlock(Vec<text::Span<'static, String>>),
/// A list.
List {
/// The first number of the list, if it is ordered.
@@ -46,7 +46,7 @@ pub fn parse(
let mut emphasis = false;
let mut metadata = false;
let mut table = false;
- let mut link = false;
+ let mut link = None;
let mut lists = Vec::new();
#[cfg(feature = "highlighter")]
@@ -93,8 +93,10 @@ pub fn parse(
emphasis = true;
None
}
- pulldown_cmark::Tag::Link { .. } if !metadata && !table => {
- link = true;
+ pulldown_cmark::Tag::Link { dest_url, .. }
+ if !metadata && !table =>
+ {
+ link = Some(dest_url);
None
}
pulldown_cmark::Tag::List(first_item) if !metadata && !table => {
@@ -150,7 +152,7 @@ pub fn parse(
None
}
pulldown_cmark::TagEnd::Link if !metadata && !table => {
- link = false;
+ link = None;
None
}
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
@@ -245,7 +247,11 @@ pub fn parse(
span
};
- let span = span.color_maybe(link.then_some(palette.primary));
+ let span = if let Some(link) = link.as_ref() {
+ span.color(palette.primary).link(link.to_string())
+ } else {
+ span
+ };
spans.push(span);
@@ -272,40 +278,48 @@ pub fn parse(
/// You can obtain the items with [`parse`].
pub fn view<'a, Message, Renderer>(
items: impl IntoIterator<Item = &'a Item>,
+ on_link_click: impl Fn(String) -> Message + Copy + 'a,
) -> Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
- Item::Heading(heading) => container(rich_text(heading))
- .padding(padding::top(if i > 0 { 8 } else { 0 }))
- .into(),
- Item::Paragraph(paragraph) => rich_text(paragraph).into(),
- Item::List { start: None, items } => column(
- items
- .iter()
- .map(|items| row!["•", view(items)].spacing(10).into()),
- )
- .spacing(10)
- .into(),
+ Item::Heading(heading) => {
+ container(rich_text(heading).on_link_click(on_link_click))
+ .padding(padding::top(if i > 0 { 8 } else { 0 }))
+ .into()
+ }
+ Item::Paragraph(paragraph) => {
+ rich_text(paragraph).on_link_click(on_link_click).into()
+ }
+ Item::List { start: None, items } => {
+ column(items.iter().map(|items| {
+ row!["•", view(items, on_link_click)].spacing(10).into()
+ }))
+ .spacing(10)
+ .into()
+ }
Item::List {
start: Some(start),
items,
} => column(items.iter().enumerate().map(|(i, items)| {
- row![text!("{}.", i as u64 + *start), view(items)]
+ row![text!("{}.", i as u64 + *start), view(items, on_link_click)]
.spacing(10)
.into()
}))
.spacing(10)
.into(),
- Item::CodeBlock(code) => {
- container(rich_text(code).font(Font::MONOSPACE).size(12))
- .width(Length::Fill)
- .padding(10)
- .style(container::rounded_box)
- .into()
- }
+ Item::CodeBlock(code) => container(
+ rich_text(code)
+ .font(Font::MONOSPACE)
+ .size(12)
+ .on_link_click(on_link_click),
+ )
+ .width(Length::Fill)
+ .padding(10)
+ .style(container::rounded_box)
+ .into(),
});
Element::new(column(blocks).width(Length::Fill).spacing(16))
diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs
index 5c44ed9e..a44775c6 100644
--- a/widget/src/text/rich.rs
+++ b/widget/src/text/rich.rs
@@ -1,5 +1,6 @@
use crate::core::alignment;
-use crate::core::layout::{self, Layout};
+use crate::core::event;
+use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::text::{Paragraph, Span};
@@ -8,19 +9,26 @@ use crate::core::widget::text::{
};
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- self, Color, Element, Length, Pixels, Rectangle, Size, Widget,
+ self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle,
+ Shell, Size, Widget,
};
use std::borrow::Cow;
/// A bunch of [`Rich`] text.
-#[derive(Debug)]
-pub struct Rich<'a, Theme = crate::Theme, Renderer = crate::Renderer>
-where
+#[allow(missing_debug_implementations)]
+pub struct Rich<
+ 'a,
+ Message,
+ Link = (),
+ Theme = crate::Theme,
+ Renderer = crate::Renderer,
+> where
+ Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
{
- spans: Cow<'a, [Span<'a, Renderer::Font>]>,
+ spans: Cow<'a, [Span<'a, Link, Renderer::Font>]>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@@ -29,10 +37,13 @@ where
align_x: alignment::Horizontal,
align_y: alignment::Vertical,
class: Theme::Class<'a>,
+ on_link_click: Option<Box<dyn Fn(Link) -> Message + 'a>>,
}
-impl<'a, Theme, Renderer> Rich<'a, Theme, Renderer>
+impl<'a, Message, Link, Theme, Renderer>
+ Rich<'a, Message, Link, Theme, Renderer>
where
+ Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
{
@@ -48,12 +59,13 @@ where
align_x: alignment::Horizontal::Left,
align_y: alignment::Vertical::Top,
class: Theme::default(),
+ on_link_click: None,
}
}
/// Creates a new [`Rich`] text with the given text spans.
pub fn with_spans(
- spans: impl Into<Cow<'a, [Span<'a, Renderer::Font>]>>,
+ spans: impl Into<Cow<'a, [Span<'a, Link, Renderer::Font>]>>,
) -> Self {
Self {
spans: spans.into(),
@@ -143,6 +155,15 @@ where
self.style(move |_theme| Style { color })
}
+ /// Sets the message handler for link clicks on the [`Rich`] text.
+ pub fn on_link_click(
+ mut self,
+ on_link_click: impl Fn(Link) -> Message + 'a,
+ ) -> Self {
+ self.on_link_click = Some(Box::new(on_link_click));
+ self
+ }
+
/// Sets the default style class of the [`Rich`] text.
#[cfg(feature = "advanced")]
#[must_use]
@@ -152,14 +173,19 @@ where
}
/// Adds a new text [`Span`] to the [`Rich`] text.
- pub fn push(mut self, span: impl Into<Span<'a, Renderer::Font>>) -> Self {
+ pub fn push(
+ mut self,
+ span: impl Into<Span<'a, Link, Renderer::Font>>,
+ ) -> Self {
self.spans.to_mut().push(span.into());
self
}
}
-impl<'a, Theme, Renderer> Default for Rich<'a, Theme, Renderer>
+impl<'a, Message, Link, Theme, Renderer> Default
+ for Rich<'a, Message, Link, Theme, Renderer>
where
+ Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
{
@@ -168,24 +194,27 @@ where
}
}
-struct State<P: Paragraph> {
- spans: Vec<Span<'static, P::Font>>,
+struct State<Link, P: Paragraph> {
+ spans: Vec<Span<'static, Link, P::Font>>,
+ span_pressed: Option<usize>,
paragraph: P,
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Rich<'a, Theme, Renderer>
+impl<'a, Message, Link, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Rich<'a, Message, Link, Theme, Renderer>
where
+ Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
{
fn tag(&self) -> tree::Tag {
- tree::Tag::of::<State<Renderer::Paragraph>>()
+ tree::Tag::of::<State<Link, Renderer::Paragraph>>()
}
fn state(&self) -> tree::State {
- tree::State::new(State {
+ tree::State::new(State::<Link, _> {
spans: Vec::new(),
+ span_pressed: None,
paragraph: Renderer::Paragraph::default(),
})
}
@@ -204,7 +233,8 @@ where
limits: &layout::Limits,
) -> layout::Node {
layout(
- tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
+ tree.state
+ .downcast_mut::<State<Link, Renderer::Paragraph>>(),
renderer,
limits,
self.width,
@@ -228,7 +258,10 @@ where
_cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
- let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
+ let state = tree
+ .state
+ .downcast_ref::<State<Link, Renderer::Paragraph>>();
+
let style = theme.style(&self.class);
text::draw(
@@ -240,15 +273,106 @@ where
viewport,
);
}
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ _viewport: &Rectangle,
+ ) -> event::Status {
+ let Some(on_link_click) = self.on_link_click.as_ref() else {
+ return event::Status::Ignored;
+ };
+
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ if let Some(position) = cursor.position_in(layout.bounds()) {
+ let state = tree
+ .state
+ .downcast_mut::<State<Link, Renderer::Paragraph>>();
+
+ if let Some(span) = state.paragraph.hit_span(position) {
+ state.span_pressed = Some(span);
+
+ return event::Status::Captured;
+ }
+ }
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ let state = tree
+ .state
+ .downcast_mut::<State<Link, Renderer::Paragraph>>();
+
+ if let Some(span_pressed) = state.span_pressed {
+ state.span_pressed = None;
+
+ if let Some(position) = cursor.position_in(layout.bounds())
+ {
+ match state.paragraph.hit_span(position) {
+ Some(span) if span == span_pressed => {
+ if let Some(link) = state
+ .spans
+ .get(span)
+ .and_then(|span| span.link.clone())
+ {
+ shell.publish(on_link_click(link));
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+
+ event::Status::Ignored
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ _viewport: &Rectangle,
+ _renderer: &Renderer,
+ ) -> mouse::Interaction {
+ if self.on_link_click.is_none() {
+ return mouse::Interaction::None;
+ }
+
+ if let Some(position) = cursor.position_in(layout.bounds()) {
+ let state = tree
+ .state
+ .downcast_ref::<State<Link, Renderer::Paragraph>>();
+
+ if let Some(span) = state
+ .paragraph
+ .hit_span(position)
+ .and_then(|span| state.spans.get(span))
+ {
+ if span.link.is_some() {
+ return mouse::Interaction::Pointer;
+ }
+ }
+ }
+
+ mouse::Interaction::None
+ }
}
-fn layout<Renderer>(
- state: &mut State<Renderer::Paragraph>,
+fn layout<Link, Renderer>(
+ state: &mut State<Link, Renderer::Paragraph>,
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
height: Length,
- spans: &[Span<'_, Renderer::Font>],
+ spans: &[Span<'_, Link, Renderer::Font>],
line_height: LineHeight,
size: Option<Pixels>,
font: Option<Renderer::Font>,
@@ -256,6 +380,7 @@ fn layout<Renderer>(
vertical_alignment: alignment::Vertical,
) -> layout::Node
where
+ Link: Clone,
Renderer: core::text::Renderer,
{
layout::sized(limits, width, height, |limits| {
@@ -305,13 +430,15 @@ where
})
}
-impl<'a, Theme, Renderer> FromIterator<Span<'a, Renderer::Font>>
- for Rich<'a, Theme, Renderer>
+impl<'a, Message, Link, Theme, Renderer>
+ FromIterator<Span<'a, Link, Renderer::Font>>
+ for Rich<'a, Message, Link, Theme, Renderer>
where
+ Link: Clone + 'static,
Theme: Catalog,
Renderer: core::text::Renderer,
{
- fn from_iter<T: IntoIterator<Item = Span<'a, Renderer::Font>>>(
+ fn from_iter<T: IntoIterator<Item = Span<'a, Link, Renderer::Font>>>(
spans: T,
) -> Self {
Self {
@@ -321,14 +448,17 @@ where
}
}
-impl<'a, Message, Theme, Renderer> From<Rich<'a, Theme, Renderer>>
+impl<'a, Message, Link, Theme, Renderer>
+ From<Rich<'a, Message, Link, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
+ Message: 'a,
+ Link: Clone + 'static,
Theme: Catalog + 'a,
Renderer: core::text::Renderer + 'a,
{
fn from(
- text: Rich<'a, Theme, Renderer>,
+ text: Rich<'a, Message, Link, Theme, Renderer>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(text)
}