summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector@hecrj.dev>2024-07-28 14:40:58 +0200
committerLibravatar GitHub <noreply@github.com>2024-07-28 14:40:58 +0200
commitc47a6ed7b639cf76086554fe2b65a8acecb61ea2 (patch)
treeabb30c0620ccab372ed6058f1fd5a2b04e1e4726
parent23a7e9f981728e8a95039db8eb8e9f3d8c6ba3d7 (diff)
parent41a7318e5df3a49bf6e7fc2110155f2f22ff7e60 (diff)
downloadiced-c47a6ed7b639cf76086554fe2b65a8acecb61ea2.tar.gz
iced-c47a6ed7b639cf76086554fe2b65a8acecb61ea2.tar.bz2
iced-c47a6ed7b639cf76086554fe2b65a8acecb61ea2.zip
Merge pull request #2516 from tarkah/feat/span-background
Add background styling to span / rich text
-rw-r--r--core/src/renderer/null.rs4
-rw-r--r--core/src/size.rs14
-rw-r--r--core/src/text.rs90
-rw-r--r--core/src/text/paragraph.rs6
-rw-r--r--graphics/src/text/paragraph.rs53
-rw-r--r--widget/src/markdown.rs10
-rw-r--r--widget/src/text/rich.rs31
7 files changed, 201 insertions, 7 deletions
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index d8d3c50a..5c7513c6 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -111,6 +111,10 @@ impl text::Paragraph for () {
fn hit_span(&self, _point: Point) -> Option<usize> {
None
}
+
+ fn span_bounds(&self, _index: usize) -> Vec<Rectangle> {
+ vec![]
+ }
}
impl text::Editor for () {
diff --git a/core/src/size.rs b/core/src/size.rs
index d7459355..95089236 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -99,6 +99,20 @@ impl<T> From<Size<T>> for Vector<T> {
}
}
+impl<T> std::ops::Add for Size<T>
+where
+ T: std::ops::Add<Output = T>,
+{
+ type Output = Size<T>;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ Size {
+ width: self.width + rhs.width,
+ height: self.height + rhs.height,
+ }
+ }
+}
+
impl<T> std::ops::Sub for Size<T>
where
T: std::ops::Sub<Output = T>,
diff --git a/core/src/text.rs b/core/src/text.rs
index aa24d85f..2f085bd8 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -8,7 +8,9 @@ pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
-use crate::{Color, Pixels, Point, Rectangle, Size};
+use crate::{
+ Background, Border, Color, Padding, Pixels, Point, Rectangle, Size,
+};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
@@ -237,6 +239,21 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
pub color: Option<Color>,
/// The link of the [`Span`].
pub link: Option<Link>,
+ /// The [`Highlight`] of the [`Span`].
+ pub highlight: Option<Highlight>,
+ /// The [`Padding`] of the [`Span`].
+ ///
+ /// Currently, it only affects the bounds of the [`Highlight`].
+ pub padding: Padding,
+}
+
+/// A text highlight.
+#[derive(Debug, Clone, Copy)]
+pub struct Highlight {
+ /// The [`Background`] of the highlight.
+ pub background: Background,
+ /// The [`Border`] of the highlight.
+ pub border: Border,
}
impl<'a, Link, Font> Span<'a, Link, Font> {
@@ -248,7 +265,9 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
line_height: None,
font: None,
color: None,
+ highlight: None,
link: None,
+ padding: Padding::ZERO,
}
}
@@ -300,6 +319,73 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
self
}
+ /// Sets the [`Background`] of the [`Span`].
+ pub fn background(self, background: impl Into<Background>) -> Self {
+ self.background_maybe(Some(background))
+ }
+
+ /// Sets the [`Background`] of the [`Span`], if any.
+ pub fn background_maybe(
+ mut self,
+ background: Option<impl Into<Background>>,
+ ) -> Self {
+ let Some(background) = background else {
+ return self;
+ };
+
+ match &mut self.highlight {
+ Some(highlight) => {
+ highlight.background = background.into();
+ }
+ None => {
+ self.highlight = Some(Highlight {
+ background: background.into(),
+ border: Border::default(),
+ });
+ }
+ }
+
+ self
+ }
+
+ /// Sets the [`Border`] of the [`Span`].
+ pub fn border(self, border: impl Into<Border>) -> Self {
+ self.border_maybe(Some(border))
+ }
+
+ /// Sets the [`Border`] of the [`Span`], if any.
+ pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
+ let Some(border) = border else {
+ return self;
+ };
+
+ match &mut self.highlight {
+ Some(highlight) => {
+ highlight.border = border.into();
+ }
+ None => {
+ self.highlight = Some(Highlight {
+ border: border.into(),
+ background: Background::Color(Color::TRANSPARENT),
+ });
+ }
+ }
+
+ self
+ }
+
+ /// Sets the [`Padding`] of the [`Span`].
+ ///
+ /// It only affects the [`background`] and [`border`] of the
+ /// [`Span`], currently.
+ ///
+ /// [`background`]: Self::background
+ /// [`border`]: Self::border
+ pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
+ self.padding = padding.into();
+ self
+ }
+
/// Turns the [`Span`] into a static one.
pub fn to_static(self) -> Span<'static, Link, Font> {
Span {
@@ -309,6 +395,8 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
font: self.font,
color: self.color,
link: self.link,
+ highlight: self.highlight,
+ padding: self.padding,
}
}
}
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index 26650793..04a97f35 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -1,7 +1,7 @@
//! Draw paragraphs.
use crate::alignment;
use crate::text::{Difference, Hit, Span, Text};
-use crate::{Point, Size};
+use crate::{Point, Rectangle, Size};
/// A text paragraph.
pub trait Paragraph: Sized + Default {
@@ -42,6 +42,10 @@ pub trait Paragraph: Sized + Default {
/// that was hit.
fn hit_span(&self, point: Point) -> Option<usize>;
+ /// Returns all bounds for the provided [`Span`] index of the [`Paragraph`].
+ /// A [`Span`] can have multiple bounds for each line it's on.
+ fn span_bounds(&self, index: usize) -> Vec<Rectangle>;
+
/// Returns the distance to the given grapheme index in the [`Paragraph`].
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs
index da703ceb..b9f9c833 100644
--- a/graphics/src/text/paragraph.rs
+++ b/graphics/src/text/paragraph.rs
@@ -2,7 +2,7 @@
use crate::core;
use crate::core::alignment;
use crate::core::text::{Hit, Shaping, Span, Text};
-use crate::core::{Font, Point, Size};
+use crate::core::{Font, Point, Rectangle, Size};
use crate::text;
use std::fmt;
@@ -251,6 +251,57 @@ impl core::text::Paragraph for Paragraph {
Some(glyph.metadata)
}
+ fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
+ let internal = self.internal();
+
+ let mut bounds = Vec::new();
+ let mut current_bounds = None;
+
+ let glyphs = internal
+ .buffer
+ .layout_runs()
+ .flat_map(|run| {
+ let line_top = run.line_top;
+ let line_height = run.line_height;
+
+ run.glyphs
+ .iter()
+ .map(move |glyph| (line_top, line_height, glyph))
+ })
+ .skip_while(|(_, _, glyph)| glyph.metadata != index)
+ .take_while(|(_, _, glyph)| glyph.metadata == index);
+
+ for (line_top, line_height, glyph) in glyphs {
+ let y = line_top + glyph.y;
+
+ let new_bounds = || {
+ Rectangle::new(
+ Point::new(glyph.x, y),
+ Size::new(
+ glyph.w,
+ glyph.line_height_opt.unwrap_or(line_height),
+ ),
+ )
+ };
+
+ match current_bounds.as_mut() {
+ None => {
+ current_bounds = Some(new_bounds());
+ }
+ Some(current_bounds) if y != current_bounds.y => {
+ bounds.push(*current_bounds);
+ *current_bounds = new_bounds();
+ }
+ Some(current_bounds) => {
+ current_bounds.width += glyph.w;
+ }
+ }
+ }
+
+ bounds.extend(current_bounds);
+ bounds
+ }
+
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
use unicode_segmentation::UnicodeSegmentation;
diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs
index 9cfd3c33..9cd4a62f 100644
--- a/widget/src/markdown.rs
+++ b/widget/src/markdown.rs
@@ -4,10 +4,11 @@
//! in code blocks.
//!
//! Only the variants of [`Item`] are currently supported.
+use crate::core::border;
use crate::core::font::{self, Font};
use crate::core::padding;
use crate::core::theme::{self, Theme};
-use crate::core::{self, Element, Length, Pixels};
+use crate::core::{self, color, Color, Element, Length, Pixels};
use crate::{column, container, rich_text, row, scrollable, span, text};
pub use pulldown_cmark::HeadingLevel;
@@ -257,7 +258,12 @@ pub fn parse(
None
}
pulldown_cmark::Event::Code(code) if !metadata && !table => {
- let span = span(code.into_string()).font(Font::MONOSPACE);
+ let span = span(code.into_string())
+ .font(Font::MONOSPACE)
+ .color(Color::WHITE)
+ .background(color!(0x111111))
+ .border(border::rounded(2))
+ .padding(padding::left(2).right(2));
let span = if let Some(link) = link.as_ref() {
span.color(palette.primary).link(link.clone())
diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs
index 9c0e2fac..9935e6c5 100644
--- a/widget/src/text/rich.rs
+++ b/widget/src/text/rich.rs
@@ -9,8 +9,8 @@ use crate::core::widget::text::{
};
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle,
- Shell, Size, Widget,
+ self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Point,
+ Rectangle, Shell, Size, Vector, Widget,
};
use std::borrow::Cow;
@@ -246,6 +246,33 @@ where
let style = theme.style(&self.class);
+ for (index, span) in self.spans.iter().enumerate() {
+ if let Some(highlight) = span.highlight {
+ let translation = layout.position() - Point::ORIGIN;
+
+ 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,
+ );
+ }
+ }
+ }
+
text::draw(
renderer,
defaults,