diff options
-rw-r--r-- | core/src/renderer/null.rs | 4 | ||||
-rw-r--r-- | core/src/size.rs | 14 | ||||
-rw-r--r-- | core/src/text.rs | 90 | ||||
-rw-r--r-- | core/src/text/paragraph.rs | 6 | ||||
-rw-r--r-- | graphics/src/text/paragraph.rs | 53 | ||||
-rw-r--r-- | widget/src/markdown.rs | 10 | ||||
-rw-r--r-- | widget/src/text/rich.rs | 31 |
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, |