Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/renderer/null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
14 changes: 14 additions & 0 deletions core/src/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
90 changes: 89 additions & 1 deletion core/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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> {
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion core/src/text/paragraph.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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>;

Expand Down
53 changes: 52 additions & 1 deletion graphics/src/text/paragraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
10 changes: 8 additions & 2 deletions widget/src/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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())
Expand Down
31 changes: 29 additions & 2 deletions widget/src/text/rich.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down