Merge pull request #2516 from tarkah/feat/span-background
Add background styling to span / rich text
This commit is contained in:
commit
c47a6ed7b6
7 changed files with 201 additions and 7 deletions
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue