Skip to content

Commit 1feb29a

Browse files
committed
Add background styling to span / rich text
1 parent a8c772e commit 1feb29a

7 files changed

Lines changed: 141 additions & 16 deletions

File tree

core/src/renderer/null.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ impl text::Paragraph for () {
111111
fn hit_span(&self, _point: Point) -> Option<usize> {
112112
None
113113
}
114+
115+
fn span_bounds(&self, _index: usize) -> Vec<Rectangle> {
116+
vec![]
117+
}
114118
}
115119

116120
impl text::Editor for () {

core/src/text.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub use highlighter::Highlighter;
88
pub use paragraph::Paragraph;
99

1010
use crate::alignment;
11-
use crate::{Color, Pixels, Point, Rectangle, Size};
11+
use crate::{Border, Color, Pixels, Point, Rectangle, Size};
1212

1313
use std::borrow::Cow;
1414
use std::hash::{Hash, Hasher};
@@ -235,6 +235,8 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
235235
pub font: Option<Font>,
236236
/// The [`Color`] of the [`Span`].
237237
pub color: Option<Color>,
238+
/// The [`Background`] of the [`Span`].
239+
pub background: Option<Background>,
238240
/// The link of the [`Span`].
239241
pub link: Option<Link>,
240242
}
@@ -248,6 +250,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
248250
line_height: None,
249251
font: None,
250252
color: None,
253+
background: None,
251254
link: None,
252255
}
253256
}
@@ -288,6 +291,21 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
288291
self
289292
}
290293

294+
/// Sets the [`Background`] of the [`Span`].
295+
pub fn background(mut self, background: impl Into<Background>) -> Self {
296+
self.background = Some(background.into());
297+
self
298+
}
299+
300+
/// Sets the [`Background`] of the [`Span`], if any.
301+
pub fn background_maybe(
302+
mut self,
303+
background: Option<impl Into<Background>>,
304+
) -> Self {
305+
self.background = background.map(Into::into);
306+
self
307+
}
308+
291309
/// Sets the link of the [`Span`].
292310
pub fn link(mut self, link: impl Into<Link>) -> Self {
293311
self.link = Some(link.into());
@@ -308,6 +326,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
308326
line_height: self.line_height,
309327
font: self.font,
310328
color: self.color,
329+
background: self.background,
311330
link: self.link,
312331
}
313332
}
@@ -406,3 +425,21 @@ into_fragment!(isize);
406425

407426
into_fragment!(f32);
408427
into_fragment!(f64);
428+
429+
/// The background style of text
430+
#[derive(Debug, Clone, Copy)]
431+
pub struct Background {
432+
/// The background [`Color`]
433+
pub color: Color,
434+
/// The background [`Border`]
435+
pub border: Border,
436+
}
437+
438+
impl From<Color> for Background {
439+
fn from(color: Color) -> Self {
440+
Background {
441+
color,
442+
border: Border::default(),
443+
}
444+
}
445+
}

core/src/text/paragraph.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Draw paragraphs.
22
use crate::alignment;
33
use crate::text::{Difference, Hit, Span, Text};
4-
use crate::{Point, Size};
4+
use crate::{Point, Rectangle, Size};
55

66
/// A text paragraph.
77
pub trait Paragraph: Sized + Default {
@@ -42,6 +42,10 @@ pub trait Paragraph: Sized + Default {
4242
/// that was hit.
4343
fn hit_span(&self, point: Point) -> Option<usize>;
4444

45+
/// Returns all bounds for the provided [`Span`] index of the [`Paragraph`].
46+
/// A [`Span`] can have multiple bounds for each line it's on.
47+
fn span_bounds(&self, index: usize) -> Vec<Rectangle>;
48+
4549
/// Returns the distance to the given grapheme index in the [`Paragraph`].
4650
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
4751

examples/markdown/src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ impl Markdown {
2828
(
2929
Self {
3030
content: text_editor::Content::with_text(INITIAL_CONTENT),
31-
items: markdown::parse(INITIAL_CONTENT, theme.palette())
32-
.collect(),
31+
items: markdown::parse(
32+
INITIAL_CONTENT,
33+
theme.extended_palette(),
34+
)
35+
.collect(),
3336
theme,
3437
},
3538
widget::focus_next(),
@@ -46,7 +49,7 @@ impl Markdown {
4649
if is_edit {
4750
self.items = markdown::parse(
4851
&self.content.text(),
49-
self.theme.palette(),
52+
self.theme.extended_palette(),
5053
)
5154
.collect();
5255
}

graphics/src/text/paragraph.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use crate::core;
33
use crate::core::alignment;
44
use crate::core::text::{Hit, Shaping, Span, Text};
5-
use crate::core::{Font, Point, Size};
5+
use crate::core::{Font, Point, Rectangle, Size};
66
use crate::text;
77

88
use std::fmt;
@@ -251,6 +251,57 @@ impl core::text::Paragraph for Paragraph {
251251
Some(glyph.metadata)
252252
}
253253

254+
fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
255+
let internal = self.internal();
256+
257+
let mut current_bounds = None;
258+
259+
let mut spans = internal
260+
.buffer
261+
.layout_runs()
262+
.flat_map(|run| {
263+
let line_top = run.line_top;
264+
let line_height = run.line_height;
265+
266+
run.glyphs
267+
.iter()
268+
.map(move |glyph| (line_top, line_height, glyph))
269+
})
270+
.skip_while(|(_, _, glyph)| glyph.metadata != index)
271+
.take_while(|(_, _, glyph)| glyph.metadata == index)
272+
.fold(vec![], |mut spans, (line_top, line_height, glyph)| {
273+
let y = line_top + glyph.y;
274+
275+
let new_bounds = || {
276+
Rectangle::new(
277+
Point::new(glyph.x, y),
278+
Size::new(
279+
glyph.w,
280+
glyph.line_height_opt.unwrap_or(line_height),
281+
),
282+
)
283+
};
284+
285+
match current_bounds.as_mut() {
286+
None => {
287+
current_bounds = Some(new_bounds());
288+
}
289+
Some(bounds) if y != bounds.y => {
290+
spans.push(*bounds);
291+
*bounds = new_bounds();
292+
}
293+
Some(bounds) => {
294+
bounds.width += glyph.w;
295+
}
296+
}
297+
298+
spans
299+
});
300+
301+
spans.extend(current_bounds);
302+
spans
303+
}
304+
254305
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
255306
use unicode_segmentation::UnicodeSegmentation;
256307

widget/src/markdown.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
//! in code blocks.
55
//!
66
//! Only the variants of [`Item`] are currently supported.
7+
use crate::core::border;
78
use crate::core::font::{self, Font};
89
use crate::core::padding;
9-
use crate::core::theme::{self, Theme};
10+
use crate::core::text::Background;
11+
use crate::core::theme::palette;
12+
use crate::core::theme::Theme;
1013
use crate::core::{self, Element, Length, Pixels};
1114
use crate::{column, container, rich_text, row, scrollable, span, text};
1215

@@ -34,10 +37,10 @@ pub enum Item {
3437
}
3538

3639
/// Parse the given Markdown content.
37-
pub fn parse(
38-
markdown: &str,
39-
palette: theme::Palette,
40-
) -> impl Iterator<Item = Item> + '_ {
40+
pub fn parse<'a>(
41+
markdown: &'a str,
42+
palette: &'a palette::Extended,
43+
) -> impl Iterator<Item = Item> + 'a {
4144
struct List {
4245
start: Option<u64>,
4346
items: Vec<Vec<Item>>,
@@ -247,7 +250,7 @@ pub fn parse(
247250
};
248251

249252
let span = if let Some(link) = link.as_ref() {
250-
span.color(palette.primary).link(link.clone())
253+
span.color(palette.primary.base.color).link(link.clone())
251254
} else {
252255
span
253256
};
@@ -257,10 +260,15 @@ pub fn parse(
257260
None
258261
}
259262
pulldown_cmark::Event::Code(code) if !metadata && !table => {
260-
let span = span(code.into_string()).font(Font::MONOSPACE);
263+
let span = span(code.into_string())
264+
.font(Font::MONOSPACE)
265+
.background(Background {
266+
color: palette.background.weak.color,
267+
border: border::rounded(2),
268+
});
261269

262270
let span = if let Some(link) = link.as_ref() {
263-
span.color(palette.primary).link(link.clone())
271+
span.color(palette.primary.base.color).link(link.clone())
264272
} else {
265273
span
266274
};

widget/src/text/rich.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::core::widget::text::{
99
};
1010
use crate::core::widget::tree::{self, Tree};
1111
use crate::core::{
12-
self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle,
13-
Shell, Size, Widget,
12+
self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Point,
13+
Rectangle, Shell, Size, Widget,
1414
};
1515

1616
use std::borrow::Cow;
@@ -270,6 +270,24 @@ where
270270

271271
let style = theme.style(&self.class);
272272

273+
// Draw backgrounds
274+
for (index, span) in self.spans.iter().enumerate() {
275+
if let Some(background) = span.background {
276+
let translation = layout.position() - Point::ORIGIN;
277+
278+
for bounds in state.paragraph.span_bounds(index) {
279+
renderer.fill_quad(
280+
renderer::Quad {
281+
bounds: bounds + translation,
282+
border: background.border,
283+
..Default::default()
284+
},
285+
background.color,
286+
);
287+
}
288+
}
289+
}
290+
273291
text::draw(
274292
renderer,
275293
defaults,

0 commit comments

Comments
 (0)