From 9bfaf2840cffe35d689bd115a308d21961ab082a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 21 Jul 2024 12:45:05 +0200 Subject: Add `Link` support to `rich_text` widget --- core/src/renderer/null.rs | 8 +- core/src/text.rs | 34 +++++++- core/src/text/paragraph.rs | 9 +- examples/markdown/Cargo.toml | 2 + examples/markdown/src/main.rs | 6 +- graphics/src/text/paragraph.rs | 42 ++++++++-- widget/src/helpers.rs | 7 +- widget/src/markdown.rs | 68 +++++++++------ widget/src/text/rich.rs | 182 +++++++++++++++++++++++++++++++++++------ 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( + _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 { None } + + fn hit_span(&self, _point: Point) -> Option { + 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, /// The [`Color`] of the [`Span`]. pub color: Option, + /// The link of the [`Span`]. + pub link: Option, } -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) -> Self { + self.link = Some(link.into()); + self + } + + /// Sets the link of the [`Span`], if any. + pub fn link_maybe(mut self, link: Option>) -> 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( + 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; + /// 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; + /// Returns the distance to the given grapheme index in the [`Paragraph`]. fn grapheme_position(&self, line: usize, index: usize) -> Option; 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(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 { + 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 { 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]>>, -) -> text::Rich<'a, Theme, Renderer> +pub fn rich_text<'a, Message, Link, Theme, Renderer>( + spans: impl Into]>>, +) -> 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>), + Heading(Vec>), /// A paragraph. - Paragraph(Vec>), + Paragraph(Vec>), /// A code block. /// /// You can enable the `highlighter` feature for syntax highligting. - CodeBlock(Vec>), + CodeBlock(Vec>), /// 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, + on_link_click: impl Fn(String) -> Message + Copy + 'a, ) -> Element<'a, Message, Theme, Renderer> where Message: 'a, Renderer: core::text::Renderer + '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, 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 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]>>, + spans: impl Into]>>, ) -> 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>) -> Self { + pub fn push( + mut self, + span: impl Into>, + ) -> 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 { - spans: Vec>, +struct State { + spans: Vec>, + span_pressed: Option, paragraph: P, } -impl<'a, Message, Theme, Renderer> Widget - for Rich<'a, Theme, Renderer> +impl<'a, Message, Link, Theme, Renderer> Widget + 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::>() + tree::Tag::of::>() } fn state(&self) -> tree::State { - tree::State::new(State { + tree::State::new(State:: { 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::>(), + tree.state + .downcast_mut::>(), renderer, limits, self.width, @@ -228,7 +258,10 @@ where _cursor_position: mouse::Cursor, viewport: &Rectangle, ) { - let state = tree.state.downcast_ref::>(); + let state = tree + .state + .downcast_ref::>(); + 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::>(); + + 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::>(); + + 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::>(); + + 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( - state: &mut State, +fn layout( + state: &mut State, renderer: &Renderer, limits: &layout::Limits, width: Length, height: Length, - spans: &[Span<'_, Renderer::Font>], + spans: &[Span<'_, Link, Renderer::Font>], line_height: LineHeight, size: Option, font: Option, @@ -256,6 +380,7 @@ fn layout( 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> - for Rich<'a, Theme, Renderer> +impl<'a, Message, Link, Theme, Renderer> + FromIterator> + for Rich<'a, Message, Link, Theme, Renderer> where + Link: Clone + 'static, Theme: Catalog, Renderer: core::text::Renderer, { - fn from_iter>>( + fn from_iter>>( spans: T, ) -> Self { Self { @@ -321,14 +448,17 @@ where } } -impl<'a, Message, Theme, Renderer> From> +impl<'a, Message, Link, Theme, Renderer> + From> 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) } -- cgit From 54500e61ed0ff2309f06dd5b441f9f5b627e05c9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 21 Jul 2024 13:01:27 +0200 Subject: Simplify font attributes in `Paragraph::with_spans` --- graphics/src/text/paragraph.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index b69110b2..da703ceb 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -123,15 +123,7 @@ impl core::text::Paragraph for Paragraph { buffer.set_rich_text( font_system.raw(), text.content.iter().enumerate().map(|(i, span)| { - let attrs = if let Some(font) = span.font { - cosmic_text::Attrs::new() - .family(text::to_family(font.family)) - .weight(text::to_weight(font.weight)) - .stretch(text::to_stretch(font.stretch)) - .style(text::to_style(font.style)) - } else { - text::to_attributes(text.font) - }; + let attrs = text::to_attributes(span.font.unwrap_or(text.font)); let attrs = match (span.size, span.line_height) { (None, None) => attrs, -- cgit From 7072c696a0a6d2a20e4cf5b44952360e15855d5a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 21 Jul 2024 13:12:38 +0200 Subject: Rename `on_link_click` to `on_link` --- widget/src/markdown.rs | 12 ++++++------ widget/src/text/rich.rs | 15 ++++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index dc207a34..ae4020bc 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -278,7 +278,7 @@ pub fn parse( /// You can obtain the items with [`parse`]. pub fn view<'a, Message, Renderer>( items: impl IntoIterator, - on_link_click: impl Fn(String) -> Message + Copy + 'a, + on_link: impl Fn(String) -> Message + Copy + 'a, ) -> Element<'a, Message, Theme, Renderer> where Message: 'a, @@ -286,16 +286,16 @@ where { let blocks = items.into_iter().enumerate().map(|(i, item)| match item { Item::Heading(heading) => { - container(rich_text(heading).on_link_click(on_link_click)) + container(rich_text(heading).on_link(on_link)) .padding(padding::top(if i > 0 { 8 } else { 0 })) .into() } Item::Paragraph(paragraph) => { - rich_text(paragraph).on_link_click(on_link_click).into() + rich_text(paragraph).on_link(on_link).into() } Item::List { start: None, items } => { column(items.iter().map(|items| { - row!["•", view(items, on_link_click)].spacing(10).into() + row!["•", view(items, on_link)].spacing(10).into() })) .spacing(10) .into() @@ -304,7 +304,7 @@ where start: Some(start), items, } => column(items.iter().enumerate().map(|(i, items)| { - row![text!("{}.", i as u64 + *start), view(items, on_link_click)] + row![text!("{}.", i as u64 + *start), view(items, on_link)] .spacing(10) .into() })) @@ -314,7 +314,7 @@ where rich_text(code) .font(Font::MONOSPACE) .size(12) - .on_link_click(on_link_click), + .on_link(on_link), ) .width(Length::Fill) .padding(10) diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index a44775c6..8e5e8be1 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -37,7 +37,7 @@ pub struct Rich< align_x: alignment::Horizontal, align_y: alignment::Vertical, class: Theme::Class<'a>, - on_link_click: Option Message + 'a>>, + on_link: Option Message + 'a>>, } impl<'a, Message, Link, Theme, Renderer> @@ -59,7 +59,7 @@ where align_x: alignment::Horizontal::Left, align_y: alignment::Vertical::Top, class: Theme::default(), - on_link_click: None, + on_link: None, } } @@ -156,11 +156,8 @@ where } /// 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)); + pub fn on_link(mut self, on_link: impl Fn(Link) -> Message + 'a) -> Self { + self.on_link = Some(Box::new(on_link)); self } @@ -285,7 +282,7 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - let Some(on_link_click) = self.on_link_click.as_ref() else { + let Some(on_link_click) = self.on_link.as_ref() else { return event::Status::Ignored; }; @@ -342,7 +339,7 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - if self.on_link_click.is_none() { + if self.on_link.is_none() { return mouse::Interaction::None; } -- cgit From 78c4f7e64646f84ae58bf78c36dabe02b35c4e18 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 21 Jul 2024 13:59:46 +0200 Subject: Use latest `spans` to retreive `Link` in `rich_text` --- widget/src/text/rich.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 8e5e8be1..625ea089 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -312,7 +312,7 @@ where { match state.paragraph.hit_span(position) { Some(span) if span == span_pressed => { - if let Some(link) = state + if let Some(link) = self .spans .get(span) .and_then(|span| span.link.clone()) @@ -351,7 +351,7 @@ where if let Some(span) = state .paragraph .hit_span(position) - .and_then(|span| state.spans.get(span)) + .and_then(|span| self.spans.get(span)) { if span.link.is_some() { return mouse::Interaction::Pointer; -- cgit From a2943798a3cf79e15344063fbf4ea8c84d261d6f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 21 Jul 2024 14:10:39 +0200 Subject: Use `open::that_in_background` in `markdown` example --- examples/markdown/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index bb6eb57b..ee5b5aab 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -52,7 +52,7 @@ impl Markdown { } } Message::LinkClicked(link) => { - let _ = open::that(link); + let _ = open::that_in_background(link); } } } -- cgit