Add background styling to span / rich text
This commit is contained in:
parent
23a7e9f981
commit
ddcf02f9d0
7 changed files with 141 additions and 16 deletions
|
|
@ -111,6 +111,10 @@ impl text::Paragraph for () {
|
||||||
fn hit_span(&self, _point: Point) -> Option<usize> {
|
fn hit_span(&self, _point: Point) -> Option<usize> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn span_bounds(&self, _index: usize) -> Vec<Rectangle> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl text::Editor for () {
|
impl text::Editor for () {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pub use highlighter::Highlighter;
|
||||||
pub use paragraph::Paragraph;
|
pub use paragraph::Paragraph;
|
||||||
|
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
use crate::{Border, Color, Pixels, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
@ -235,6 +235,8 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
|
||||||
pub font: Option<Font>,
|
pub font: Option<Font>,
|
||||||
/// The [`Color`] of the [`Span`].
|
/// The [`Color`] of the [`Span`].
|
||||||
pub color: Option<Color>,
|
pub color: Option<Color>,
|
||||||
|
/// The [`Background`] of the [`Span`].
|
||||||
|
pub background: Option<Background>,
|
||||||
/// The link of the [`Span`].
|
/// The link of the [`Span`].
|
||||||
pub link: Option<Link>,
|
pub link: Option<Link>,
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +250,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
|
||||||
line_height: None,
|
line_height: None,
|
||||||
font: None,
|
font: None,
|
||||||
color: None,
|
color: None,
|
||||||
|
background: None,
|
||||||
link: None,
|
link: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +291,21 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Background`] of the [`Span`].
|
||||||
|
pub fn background(mut self, background: impl Into<Background>) -> Self {
|
||||||
|
self.background = Some(background.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Background`] of the [`Span`], if any.
|
||||||
|
pub fn background_maybe(
|
||||||
|
mut self,
|
||||||
|
background: Option<impl Into<Background>>,
|
||||||
|
) -> Self {
|
||||||
|
self.background = background.map(Into::into);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the link of the [`Span`].
|
/// Sets the link of the [`Span`].
|
||||||
pub fn link(mut self, link: impl Into<Link>) -> Self {
|
pub fn link(mut self, link: impl Into<Link>) -> Self {
|
||||||
self.link = Some(link.into());
|
self.link = Some(link.into());
|
||||||
|
|
@ -308,6 +326,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
|
||||||
line_height: self.line_height,
|
line_height: self.line_height,
|
||||||
font: self.font,
|
font: self.font,
|
||||||
color: self.color,
|
color: self.color,
|
||||||
|
background: self.background,
|
||||||
link: self.link,
|
link: self.link,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -406,3 +425,21 @@ into_fragment!(isize);
|
||||||
|
|
||||||
into_fragment!(f32);
|
into_fragment!(f32);
|
||||||
into_fragment!(f64);
|
into_fragment!(f64);
|
||||||
|
|
||||||
|
/// The background style of text
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Background {
|
||||||
|
/// The background [`Color`]
|
||||||
|
pub color: Color,
|
||||||
|
/// The background [`Border`]
|
||||||
|
pub border: Border,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for Background {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Background {
|
||||||
|
color,
|
||||||
|
border: Border::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Draw paragraphs.
|
//! Draw paragraphs.
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::text::{Difference, Hit, Span, Text};
|
use crate::text::{Difference, Hit, Span, Text};
|
||||||
use crate::{Point, Size};
|
use crate::{Point, Rectangle, Size};
|
||||||
|
|
||||||
/// A text paragraph.
|
/// A text paragraph.
|
||||||
pub trait Paragraph: Sized + Default {
|
pub trait Paragraph: Sized + Default {
|
||||||
|
|
@ -42,6 +42,10 @@ pub trait Paragraph: Sized + Default {
|
||||||
/// that was hit.
|
/// that was hit.
|
||||||
fn hit_span(&self, point: Point) -> Option<usize>;
|
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`].
|
/// Returns the distance to the given grapheme index in the [`Paragraph`].
|
||||||
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
|
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,10 @@ impl Markdown {
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
content: text_editor::Content::with_text(INITIAL_CONTENT),
|
content: text_editor::Content::with_text(INITIAL_CONTENT),
|
||||||
items: markdown::parse(INITIAL_CONTENT, theme.palette())
|
items: markdown::parse(
|
||||||
|
INITIAL_CONTENT,
|
||||||
|
theme.extended_palette(),
|
||||||
|
)
|
||||||
.collect(),
|
.collect(),
|
||||||
theme,
|
theme,
|
||||||
},
|
},
|
||||||
|
|
@ -46,7 +49,7 @@ impl Markdown {
|
||||||
if is_edit {
|
if is_edit {
|
||||||
self.items = markdown::parse(
|
self.items = markdown::parse(
|
||||||
&self.content.text(),
|
&self.content.text(),
|
||||||
self.theme.palette(),
|
self.theme.extended_palette(),
|
||||||
)
|
)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::text::{Hit, Shaping, Span, Text};
|
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 crate::text;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -251,6 +251,57 @@ impl core::text::Paragraph for Paragraph {
|
||||||
Some(glyph.metadata)
|
Some(glyph.metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
|
||||||
|
let internal = self.internal();
|
||||||
|
|
||||||
|
let mut current_bounds = None;
|
||||||
|
|
||||||
|
let mut bounds = 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)
|
||||||
|
.fold(vec![], |mut spans, (line_top, line_height, glyph)| {
|
||||||
|
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(bounds) if y != bounds.y => {
|
||||||
|
spans.push(*bounds);
|
||||||
|
*bounds = new_bounds();
|
||||||
|
}
|
||||||
|
Some(bounds) => {
|
||||||
|
bounds.width += glyph.w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spans
|
||||||
|
});
|
||||||
|
|
||||||
|
bounds.extend(current_bounds);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
|
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@
|
||||||
//! in code blocks.
|
//! in code blocks.
|
||||||
//!
|
//!
|
||||||
//! Only the variants of [`Item`] are currently supported.
|
//! Only the variants of [`Item`] are currently supported.
|
||||||
|
use crate::core::border;
|
||||||
use crate::core::font::{self, Font};
|
use crate::core::font::{self, Font};
|
||||||
use crate::core::padding;
|
use crate::core::padding;
|
||||||
use crate::core::theme::{self, Theme};
|
use crate::core::text::Background;
|
||||||
|
use crate::core::theme::palette;
|
||||||
|
use crate::core::theme::Theme;
|
||||||
use crate::core::{self, Element, Length, Pixels};
|
use crate::core::{self, Element, Length, Pixels};
|
||||||
use crate::{column, container, rich_text, row, scrollable, span, text};
|
use crate::{column, container, rich_text, row, scrollable, span, text};
|
||||||
|
|
||||||
|
|
@ -34,10 +37,10 @@ pub enum Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the given Markdown content.
|
/// Parse the given Markdown content.
|
||||||
pub fn parse(
|
pub fn parse<'a>(
|
||||||
markdown: &str,
|
markdown: &'a str,
|
||||||
palette: theme::Palette,
|
palette: &'a palette::Extended,
|
||||||
) -> impl Iterator<Item = Item> + '_ {
|
) -> impl Iterator<Item = Item> + 'a {
|
||||||
struct List {
|
struct List {
|
||||||
start: Option<u64>,
|
start: Option<u64>,
|
||||||
items: Vec<Vec<Item>>,
|
items: Vec<Vec<Item>>,
|
||||||
|
|
@ -247,7 +250,7 @@ pub fn parse(
|
||||||
};
|
};
|
||||||
|
|
||||||
let span = if let Some(link) = link.as_ref() {
|
let span = if let Some(link) = link.as_ref() {
|
||||||
span.color(palette.primary).link(link.clone())
|
span.color(palette.primary.base.color).link(link.clone())
|
||||||
} else {
|
} else {
|
||||||
span
|
span
|
||||||
};
|
};
|
||||||
|
|
@ -257,10 +260,15 @@ pub fn parse(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::Code(code) if !metadata && !table => {
|
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)
|
||||||
|
.background(Background {
|
||||||
|
color: palette.background.weak.color,
|
||||||
|
border: border::rounded(2),
|
||||||
|
});
|
||||||
|
|
||||||
let span = if let Some(link) = link.as_ref() {
|
let span = if let Some(link) = link.as_ref() {
|
||||||
span.color(palette.primary).link(link.clone())
|
span.color(palette.primary.base.color).link(link.clone())
|
||||||
} else {
|
} else {
|
||||||
span
|
span
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use crate::core::widget::text::{
|
||||||
};
|
};
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle,
|
self, Clipboard, Color, Element, Event, Layout, Length, Pixels, Point,
|
||||||
Shell, Size, Widget,
|
Rectangle, Shell, Size, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
@ -246,6 +246,24 @@ where
|
||||||
|
|
||||||
let style = theme.style(&self.class);
|
let style = theme.style(&self.class);
|
||||||
|
|
||||||
|
// Draw backgrounds
|
||||||
|
for (index, span) in self.spans.iter().enumerate() {
|
||||||
|
if let Some(background) = span.background {
|
||||||
|
let translation = layout.position() - Point::ORIGIN;
|
||||||
|
|
||||||
|
for bounds in state.paragraph.span_bounds(index) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: bounds + translation,
|
||||||
|
border: background.border,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
background.color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
text::draw(
|
text::draw(
|
||||||
renderer,
|
renderer,
|
||||||
defaults,
|
defaults,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue