From bf16d1ddcdcac21a4f4ad5ba79caba857067ee56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 28 Jul 2024 15:09:54 +0200 Subject: [PATCH 1/3] Implement `underline` support for `rich_text` spans --- core/src/text.rs | 10 ++++++ widget/src/markdown.rs | 4 ++- widget/src/text/rich.rs | 79 ++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/core/src/text.rs b/core/src/text.rs index 2f085bd86c..68c586f17a 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -245,6 +245,8 @@ pub struct Span<'a, Link = (), Font = crate::Font> { /// /// Currently, it only affects the bounds of the [`Highlight`]. pub padding: Padding, + /// Whether the [`Span`] should be underlined or not. + pub underline: bool, } /// A text highlight. @@ -268,6 +270,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> { highlight: None, link: None, padding: Padding::ZERO, + underline: false, } } @@ -386,6 +389,12 @@ impl<'a, Link, Font> Span<'a, Link, Font> { self } + /// Sets whether the [`Span`] shoud be underlined or not. + pub fn underline(mut self, underline: bool) -> Self { + self.underline = underline; + self + } + /// Turns the [`Span`] into a static one. pub fn to_static(self) -> Span<'static, Link, Font> { Span { @@ -397,6 +406,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> { link: self.link, highlight: self.highlight, padding: self.padding, + underline: self.underline, } } } diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 9cd4a62ff6..dbdb6e4286 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -248,7 +248,9 @@ pub fn parse( }; let span = if let Some(link) = link.as_ref() { - span.color(palette.primary).link(link.clone()) + span.color(palette.primary) + .link(link.clone()) + .underline(true) } else { span }; diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 9935e6c568..8e4b0b7e1d 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -237,7 +237,7 @@ where theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, - _cursor_position: mouse::Cursor, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { let state = tree @@ -247,28 +247,65 @@ where let style = theme.style(&self.class); for (index, span) in self.spans.iter().enumerate() { - if let Some(highlight) = span.highlight { + if span.highlight.is_some() || span.underline { let translation = layout.position() - Point::ORIGIN; + let regions = state.paragraph.span_bounds(index); + + if let Some(highlight) = span.highlight { + for bounds in ®ions { + let bounds = Rectangle::new( + bounds.position() + - Vector::new( + span.padding.left, + span.padding.top, + ), + bounds.size() + + Size::new( + span.padding.horizontal(), + span.padding.vertical(), + ), + ); + + renderer.fill_quad( + renderer::Quad { + bounds: bounds + translation, + border: highlight.border, + ..Default::default() + }, + highlight.background, + ); + } + } - for bounds in state.paragraph.span_bounds(index) { - let bounds = Rectangle::new( - bounds.position() - - Vector::new(span.padding.left, span.padding.top), - bounds.size() - + Size::new( - span.padding.horizontal(), - span.padding.vertical(), - ), - ); - - renderer.fill_quad( - renderer::Quad { - bounds: bounds + translation, - border: highlight.border, - ..Default::default() - }, - highlight.background, - ); + if span.underline { + let line_height = span + .line_height + .unwrap_or(self.line_height) + .to_absolute( + span.size + .or(self.size) + .unwrap_or(renderer.default_size()), + ); + + for bounds in regions { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle::new( + bounds.position() + + translation + + Vector::new( + 0.0, + line_height.0 * 0.8 + 1.0, + ), + Size::new(bounds.width, 1.0), + ), + ..Default::default() + }, + span.color + .or(style.color) + .unwrap_or(defaults.text_color), + ); + } } } } From ca31dcadd52b3be05bcf01aa0426bf4279ac5f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 28 Jul 2024 15:10:33 +0200 Subject: [PATCH 2/3] Underline `rich_text` links when hovered --- widget/src/markdown.rs | 4 +--- widget/src/text/rich.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index dbdb6e4286..9cd4a62ff6 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -248,9 +248,7 @@ pub fn parse( }; let span = if let Some(link) = link.as_ref() { - span.color(palette.primary) - .link(link.clone()) - .underline(true) + span.color(palette.primary).link(link.clone()) } else { span }; diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 8e4b0b7e1d..d179c2d688 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -237,7 +237,7 @@ where theme: &Theme, defaults: &renderer::Style, layout: Layout<'_>, - _cursor: mouse::Cursor, + cursor: mouse::Cursor, viewport: &Rectangle, ) { let state = tree @@ -246,8 +246,15 @@ where let style = theme.style(&self.class); + let hovered_span = cursor + .position_in(layout.bounds()) + .and_then(|position| state.paragraph.hit_span(position)); + for (index, span) in self.spans.iter().enumerate() { - if span.highlight.is_some() || span.underline { + let is_hovered_link = + span.link.is_some() && Some(index) == hovered_span; + + if span.highlight.is_some() || span.underline || is_hovered_link { let translation = layout.position() - Point::ORIGIN; let regions = state.paragraph.span_bounds(index); @@ -277,7 +284,7 @@ where } } - if span.underline { + if span.underline || is_hovered_link { let line_height = span .line_height .unwrap_or(self.line_height) From 9ce55eb51113b79d57b981ccd971242528a36395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 28 Jul 2024 15:40:22 +0200 Subject: [PATCH 3/3] Make `underline` positioning aware of `line_height` --- widget/src/text/rich.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index d179c2d688..096056d43a 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -285,14 +285,15 @@ where } if span.underline || is_hovered_link { + let size = span + .size + .or(self.size) + .unwrap_or(renderer.default_size()); + let line_height = span .line_height .unwrap_or(self.line_height) - .to_absolute( - span.size - .or(self.size) - .unwrap_or(renderer.default_size()), - ); + .to_absolute(size); for bounds in regions { renderer.fill_quad( @@ -302,7 +303,10 @@ where + translation + Vector::new( 0.0, - line_height.0 * 0.8 + 1.0, + size.0 + + (line_height.0 - size.0) + / 2.0 + - size.0 * 0.08, ), Size::new(bounds.width, 1.0), ),