Make RichText generic over data structure
... and decouple `markdown::parse` from theming
This commit is contained in:
parent
55764b923e
commit
4c883f12b4
7 changed files with 223 additions and 94 deletions
|
|
@ -32,7 +32,7 @@ use crate::{Pixels, Size};
|
||||||
/// let widget = Widget::new().padding(20); // 20px on all sides
|
/// let widget = Widget::new().padding(20); // 20px on all sides
|
||||||
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
|
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||||
pub struct Padding {
|
pub struct Padding {
|
||||||
/// Top padding
|
/// Top padding
|
||||||
pub top: f32,
|
pub top: f32,
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ pub struct Span<'a, Link = (), Font = crate::Font> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A text highlight.
|
/// A text highlight.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Highlight {
|
pub struct Highlight {
|
||||||
/// The [`Background`] of the highlight.
|
/// The [`Background`] of the highlight.
|
||||||
pub background: Background,
|
pub background: Background,
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ 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).collect(),
|
||||||
.collect(),
|
|
||||||
theme,
|
theme,
|
||||||
},
|
},
|
||||||
widget::focus_next(),
|
widget::focus_next(),
|
||||||
|
|
@ -45,11 +44,8 @@ impl Markdown {
|
||||||
self.content.perform(action);
|
self.content.perform(action);
|
||||||
|
|
||||||
if is_edit {
|
if is_edit {
|
||||||
self.items = markdown::parse(
|
self.items =
|
||||||
&self.content.text(),
|
markdown::parse(&self.content.text()).collect();
|
||||||
self.theme.palette(),
|
|
||||||
)
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::LinkClicked(link) => {
|
Message::LinkClicked(link) => {
|
||||||
|
|
@ -67,8 +63,12 @@ impl Markdown {
|
||||||
.font(Font::MONOSPACE)
|
.font(Font::MONOSPACE)
|
||||||
.highlight("markdown", highlighter::Theme::Base16Ocean);
|
.highlight("markdown", highlighter::Theme::Base16Ocean);
|
||||||
|
|
||||||
let preview = markdown(&self.items, markdown::Settings::default())
|
let preview = markdown(
|
||||||
.map(Message::LinkClicked);
|
&self.items,
|
||||||
|
markdown::Settings::default(),
|
||||||
|
markdown::Style::from_palette(self.theme.palette()),
|
||||||
|
)
|
||||||
|
.map(Message::LinkClicked);
|
||||||
|
|
||||||
row![editor, scrollable(preview).spacing(10).height(Fill)]
|
row![editor, scrollable(preview).spacing(10).height(Fill)]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style class of the [`Container`].
|
/// Sets the style class of the [`Container`].
|
||||||
#[cfg(feature = "advanced")]
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
|
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
|
||||||
self.class = class.into();
|
self.class = class.into();
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::tooltip::{self, Tooltip};
|
||||||
use crate::vertical_slider::{self, VerticalSlider};
|
use crate::vertical_slider::{self, VerticalSlider};
|
||||||
use crate::{Column, MouseArea, Row, Space, Stack, Themer};
|
use crate::{Column, MouseArea, Row, Space, Stack, Themer};
|
||||||
|
|
||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::Borrow;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
/// Creates a [`Column`] with the given children.
|
/// Creates a [`Column`] with the given children.
|
||||||
|
|
@ -707,12 +707,13 @@ where
|
||||||
///
|
///
|
||||||
/// [`Rich`]: text::Rich
|
/// [`Rich`]: text::Rich
|
||||||
pub fn rich_text<'a, Link, Theme, Renderer>(
|
pub fn rich_text<'a, Link, Theme, Renderer>(
|
||||||
spans: impl Into<Cow<'a, [text::Span<'a, Link, Renderer::Font>]>>,
|
spans: impl AsRef<[text::Span<'a, Link, Renderer::Font>]> + 'a,
|
||||||
) -> text::Rich<'a, Link, Theme, Renderer>
|
) -> text::Rich<'a, Link, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Link: Clone + 'static,
|
Link: Clone + 'static,
|
||||||
Theme: text::Catalog + 'a,
|
Theme: text::Catalog + 'a,
|
||||||
Renderer: core::text::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
|
Renderer::Font: 'a,
|
||||||
{
|
{
|
||||||
text::Rich::with_spans(spans)
|
text::Rich::with_spans(spans)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,15 @@ 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;
|
use crate::core::theme;
|
||||||
use crate::core::{self, color, Color, Element, Length, Pixels, Theme};
|
use crate::core::{
|
||||||
|
self, color, Color, Element, Length, Padding, Pixels, Theme,
|
||||||
|
};
|
||||||
use crate::{column, container, rich_text, row, scrollable, span, text};
|
use crate::{column, container, rich_text, row, scrollable, span, text};
|
||||||
|
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub use core::text::Highlight;
|
||||||
pub use pulldown_cmark::HeadingLevel;
|
pub use pulldown_cmark::HeadingLevel;
|
||||||
pub use url::Url;
|
pub use url::Url;
|
||||||
|
|
||||||
|
|
@ -18,13 +24,13 @@ pub use url::Url;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
/// A heading.
|
/// A heading.
|
||||||
Heading(pulldown_cmark::HeadingLevel, Vec<text::Span<'static, Url>>),
|
Heading(pulldown_cmark::HeadingLevel, Text),
|
||||||
/// A paragraph.
|
/// A paragraph.
|
||||||
Paragraph(Vec<text::Span<'static, Url>>),
|
Paragraph(Text),
|
||||||
/// A code block.
|
/// A code block.
|
||||||
///
|
///
|
||||||
/// You can enable the `highlighter` feature for syntax highligting.
|
/// You can enable the `highlighter` feature for syntax highligting.
|
||||||
CodeBlock(Vec<text::Span<'static, Url>>),
|
CodeBlock(Text),
|
||||||
/// A list.
|
/// A list.
|
||||||
List {
|
List {
|
||||||
/// The first number of the list, if it is ordered.
|
/// The first number of the list, if it is ordered.
|
||||||
|
|
@ -34,11 +40,112 @@ pub enum Item {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bunch of parsed Markdown text.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Text {
|
||||||
|
spans: Vec<Span>,
|
||||||
|
last_style: Cell<Option<Style>>,
|
||||||
|
last_styled_spans: RefCell<Rc<[text::Span<'static, Url>]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Text {
|
||||||
|
fn new(spans: Vec<Span>) -> Self {
|
||||||
|
Self {
|
||||||
|
spans,
|
||||||
|
last_style: Cell::default(),
|
||||||
|
last_styled_spans: RefCell::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`rich_text`] spans ready to be used for the given style.
|
||||||
|
///
|
||||||
|
/// This method performs caching for you. It will only reallocate if the [`Style`]
|
||||||
|
/// provided changes.
|
||||||
|
pub fn spans(&self, style: Style) -> Rc<[text::Span<'static, Url>]> {
|
||||||
|
if Some(style) != self.last_style.get() {
|
||||||
|
*self.last_styled_spans.borrow_mut() =
|
||||||
|
self.spans.iter().map(|span| span.view(&style)).collect();
|
||||||
|
|
||||||
|
self.last_style.set(Some(style));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_styled_spans.borrow().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Span {
|
||||||
|
Standard {
|
||||||
|
text: String,
|
||||||
|
strikethrough: bool,
|
||||||
|
link: Option<Url>,
|
||||||
|
strong: bool,
|
||||||
|
emphasis: bool,
|
||||||
|
code: bool,
|
||||||
|
},
|
||||||
|
#[cfg(feature = "highlighter")]
|
||||||
|
Highlight {
|
||||||
|
text: String,
|
||||||
|
color: Option<Color>,
|
||||||
|
font: Option<Font>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Span {
|
||||||
|
fn view(&self, style: &Style) -> text::Span<'static, Url> {
|
||||||
|
match self {
|
||||||
|
Span::Standard {
|
||||||
|
text,
|
||||||
|
strikethrough,
|
||||||
|
link,
|
||||||
|
strong,
|
||||||
|
emphasis,
|
||||||
|
code,
|
||||||
|
} => {
|
||||||
|
let span = span(text.clone()).strikethrough(*strikethrough);
|
||||||
|
|
||||||
|
let span = if *code {
|
||||||
|
span.font(Font::MONOSPACE)
|
||||||
|
.color(style.inline_code_color)
|
||||||
|
.background(style.inline_code_highlight.background)
|
||||||
|
.border(style.inline_code_highlight.border)
|
||||||
|
.padding(style.inline_code_padding)
|
||||||
|
} else if *strong || *emphasis {
|
||||||
|
span.font(Font {
|
||||||
|
weight: if *strong {
|
||||||
|
font::Weight::Bold
|
||||||
|
} else {
|
||||||
|
font::Weight::Normal
|
||||||
|
},
|
||||||
|
style: if *emphasis {
|
||||||
|
font::Style::Italic
|
||||||
|
} else {
|
||||||
|
font::Style::Normal
|
||||||
|
},
|
||||||
|
..Font::default()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
span
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = if let Some(link) = link.as_ref() {
|
||||||
|
span.color(style.link_color).link(link.clone())
|
||||||
|
} else {
|
||||||
|
span
|
||||||
|
};
|
||||||
|
|
||||||
|
span
|
||||||
|
}
|
||||||
|
#[cfg(feature = "highlighter")]
|
||||||
|
Span::Highlight { text, color, font } => {
|
||||||
|
span(text.clone()).color_maybe(*color).font_maybe(*font)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the given Markdown content.
|
/// Parse the given Markdown content.
|
||||||
pub fn parse(
|
pub fn parse(markdown: &str) -> impl Iterator<Item = Item> + '_ {
|
||||||
markdown: &str,
|
|
||||||
palette: theme::Palette,
|
|
||||||
) -> impl Iterator<Item = Item> + '_ {
|
|
||||||
struct List {
|
struct List {
|
||||||
start: Option<u64>,
|
start: Option<u64>,
|
||||||
items: Vec<Vec<Item>>,
|
items: Vec<Vec<Item>>,
|
||||||
|
|
@ -158,7 +265,7 @@ pub fn parse(
|
||||||
pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
|
pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
|
||||||
produce(
|
produce(
|
||||||
&mut lists,
|
&mut lists,
|
||||||
Item::Heading(level, spans.drain(..).collect()),
|
Item::Heading(level, Text::new(spans.drain(..).collect())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Strong if !metadata && !table => {
|
pulldown_cmark::TagEnd::Strong if !metadata && !table => {
|
||||||
|
|
@ -178,7 +285,10 @@ pub fn parse(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
|
pulldown_cmark::TagEnd::Paragraph if !metadata && !table => {
|
||||||
produce(&mut lists, Item::Paragraph(spans.drain(..).collect()))
|
produce(
|
||||||
|
&mut lists,
|
||||||
|
Item::Paragraph(Text::new(spans.drain(..).collect())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::Item if !metadata && !table => {
|
pulldown_cmark::TagEnd::Item if !metadata && !table => {
|
||||||
if spans.is_empty() {
|
if spans.is_empty() {
|
||||||
|
|
@ -186,7 +296,7 @@ pub fn parse(
|
||||||
} else {
|
} else {
|
||||||
produce(
|
produce(
|
||||||
&mut lists,
|
&mut lists,
|
||||||
Item::Paragraph(spans.drain(..).collect()),
|
Item::Paragraph(Text::new(spans.drain(..).collect())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +317,10 @@ pub fn parse(
|
||||||
highlighter = None;
|
highlighter = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
produce(&mut lists, Item::CodeBlock(spans.drain(..).collect()))
|
produce(
|
||||||
|
&mut lists,
|
||||||
|
Item::CodeBlock(Text::new(spans.drain(..).collect())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pulldown_cmark::TagEnd::MetadataBlock(_) => {
|
pulldown_cmark::TagEnd::MetadataBlock(_) => {
|
||||||
metadata = false;
|
metadata = false;
|
||||||
|
|
@ -227,9 +340,11 @@ pub fn parse(
|
||||||
for (range, highlight) in
|
for (range, highlight) in
|
||||||
highlighter.highlight_line(text.as_ref())
|
highlighter.highlight_line(text.as_ref())
|
||||||
{
|
{
|
||||||
let span = span(text[range].to_owned())
|
let span = Span::Highlight {
|
||||||
.color_maybe(highlight.color())
|
text: text[range].to_owned(),
|
||||||
.font_maybe(highlight.font());
|
color: highlight.color(),
|
||||||
|
font: highlight.font(),
|
||||||
|
};
|
||||||
|
|
||||||
spans.push(span);
|
spans.push(span);
|
||||||
}
|
}
|
||||||
|
|
@ -237,30 +352,13 @@ pub fn parse(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = span(text.into_string()).strikethrough(strikethrough);
|
let span = Span::Standard {
|
||||||
|
text: text.into_string(),
|
||||||
let span = if strong || emphasis {
|
strong,
|
||||||
span.font(Font {
|
emphasis,
|
||||||
weight: if strong {
|
strikethrough,
|
||||||
font::Weight::Bold
|
link: link.clone(),
|
||||||
} else {
|
code: false,
|
||||||
font::Weight::Normal
|
|
||||||
},
|
|
||||||
style: if emphasis {
|
|
||||||
font::Style::Italic
|
|
||||||
} else {
|
|
||||||
font::Style::Normal
|
|
||||||
},
|
|
||||||
..Font::default()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
span
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = if let Some(link) = link.as_ref() {
|
|
||||||
span.color(palette.primary).link(link.clone())
|
|
||||||
} else {
|
|
||||||
span
|
|
||||||
};
|
};
|
||||||
|
|
||||||
spans.push(span);
|
spans.push(span);
|
||||||
|
|
@ -268,29 +366,38 @@ 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())
|
let span = Span::Standard {
|
||||||
.font(Font::MONOSPACE)
|
text: code.into_string(),
|
||||||
.color(Color::WHITE)
|
strong,
|
||||||
.background(color!(0x111111))
|
emphasis,
|
||||||
.border(border::rounded(2))
|
strikethrough,
|
||||||
.padding(padding::left(2).right(2))
|
link: link.clone(),
|
||||||
.strikethrough(strikethrough);
|
code: true,
|
||||||
|
|
||||||
let span = if let Some(link) = link.as_ref() {
|
|
||||||
span.color(palette.primary).link(link.clone())
|
|
||||||
} else {
|
|
||||||
span
|
|
||||||
};
|
};
|
||||||
|
|
||||||
spans.push(span);
|
spans.push(span);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
|
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
|
||||||
spans.push(span(" ").strikethrough(strikethrough));
|
spans.push(Span::Standard {
|
||||||
|
text: String::from(" "),
|
||||||
|
strikethrough,
|
||||||
|
strong,
|
||||||
|
emphasis,
|
||||||
|
link: link.clone(),
|
||||||
|
code: false,
|
||||||
|
});
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pulldown_cmark::Event::HardBreak if !metadata && !table => {
|
pulldown_cmark::Event::HardBreak if !metadata && !table => {
|
||||||
spans.push(span("\n"));
|
spans.push(Span::Standard {
|
||||||
|
text: String::from("\n"),
|
||||||
|
strikethrough,
|
||||||
|
strong,
|
||||||
|
emphasis,
|
||||||
|
link: link.clone(),
|
||||||
|
code: false,
|
||||||
|
});
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -346,12 +453,41 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text styling of some Markdown rendering in [`view`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Style {
|
||||||
|
/// The [`Highlight`] to be applied to the background of inline code.
|
||||||
|
pub inline_code_highlight: Highlight,
|
||||||
|
/// The [`Padding`] to be applied to the background of inline code.
|
||||||
|
pub inline_code_padding: Padding,
|
||||||
|
/// The [`Color`] to be applied to inline code.
|
||||||
|
pub inline_code_color: Color,
|
||||||
|
/// The [`Color`] to be applied to links.
|
||||||
|
pub link_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style {
|
||||||
|
/// Creates a new [`Style`] from the given [`theme::Palette`].
|
||||||
|
pub fn from_palette(palette: theme::Palette) -> Self {
|
||||||
|
Self {
|
||||||
|
inline_code_padding: padding::left(1).right(1),
|
||||||
|
inline_code_highlight: Highlight {
|
||||||
|
background: color!(0x111).into(),
|
||||||
|
border: border::rounded(2),
|
||||||
|
},
|
||||||
|
inline_code_color: Color::WHITE,
|
||||||
|
link_color: palette.primary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Display a bunch of Markdown items.
|
/// Display a bunch of Markdown items.
|
||||||
///
|
///
|
||||||
/// You can obtain the items with [`parse`].
|
/// You can obtain the items with [`parse`].
|
||||||
pub fn view<'a, Theme, Renderer>(
|
pub fn view<'a, Theme, Renderer>(
|
||||||
items: impl IntoIterator<Item = &'a Item>,
|
items: impl IntoIterator<Item = &'a Item>,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
style: Style,
|
||||||
) -> Element<'a, Url, Theme, Renderer>
|
) -> Element<'a, Url, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Theme: Catalog + 'a,
|
Theme: Catalog + 'a,
|
||||||
|
|
@ -372,7 +508,7 @@ where
|
||||||
|
|
||||||
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
|
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
|
||||||
Item::Heading(level, heading) => {
|
Item::Heading(level, heading) => {
|
||||||
container(rich_text(heading).size(match level {
|
container(rich_text(heading.spans(style)).size(match level {
|
||||||
pulldown_cmark::HeadingLevel::H1 => h1_size,
|
pulldown_cmark::HeadingLevel::H1 => h1_size,
|
||||||
pulldown_cmark::HeadingLevel::H2 => h2_size,
|
pulldown_cmark::HeadingLevel::H2 => h2_size,
|
||||||
pulldown_cmark::HeadingLevel::H3 => h3_size,
|
pulldown_cmark::HeadingLevel::H3 => h3_size,
|
||||||
|
|
@ -388,11 +524,11 @@ where
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
Item::Paragraph(paragraph) => {
|
Item::Paragraph(paragraph) => {
|
||||||
rich_text(paragraph).size(text_size).into()
|
rich_text(paragraph.spans(style)).size(text_size).into()
|
||||||
}
|
}
|
||||||
Item::List { start: None, items } => {
|
Item::List { start: None, items } => {
|
||||||
column(items.iter().map(|items| {
|
column(items.iter().map(|items| {
|
||||||
row![text("•").size(text_size), view(items, settings)]
|
row![text("•").size(text_size), view(items, settings, style)]
|
||||||
.spacing(spacing)
|
.spacing(spacing)
|
||||||
.into()
|
.into()
|
||||||
}))
|
}))
|
||||||
|
|
@ -405,7 +541,7 @@ where
|
||||||
} => column(items.iter().enumerate().map(|(i, items)| {
|
} => column(items.iter().enumerate().map(|(i, items)| {
|
||||||
row![
|
row![
|
||||||
text!("{}.", i as u64 + *start).size(text_size),
|
text!("{}.", i as u64 + *start).size(text_size),
|
||||||
view(items, settings)
|
view(items, settings, style)
|
||||||
]
|
]
|
||||||
.spacing(spacing)
|
.spacing(spacing)
|
||||||
.into()
|
.into()
|
||||||
|
|
@ -415,7 +551,9 @@ where
|
||||||
Item::CodeBlock(code) => container(
|
Item::CodeBlock(code) => container(
|
||||||
scrollable(
|
scrollable(
|
||||||
container(
|
container(
|
||||||
rich_text(code).font(Font::MONOSPACE).size(code_size),
|
rich_text(code.spans(style))
|
||||||
|
.font(Font::MONOSPACE)
|
||||||
|
.size(code_size),
|
||||||
)
|
)
|
||||||
.padding(spacing.0 / 2.0),
|
.padding(spacing.0 / 2.0),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ use crate::core::{
|
||||||
Rectangle, Shell, Size, Vector, Widget,
|
Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// A bunch of [`Rich`] text.
|
/// A bunch of [`Rich`] text.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Rich<'a, Link, Theme = crate::Theme, Renderer = crate::Renderer>
|
pub struct Rich<'a, Link, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||||
|
|
@ -23,7 +21,7 @@ where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::text::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
{
|
{
|
||||||
spans: Cow<'a, [Span<'a, Link, Renderer::Font>]>,
|
spans: Box<dyn AsRef<[Span<'a, Link, Renderer::Font>]> + 'a>,
|
||||||
size: Option<Pixels>,
|
size: Option<Pixels>,
|
||||||
line_height: LineHeight,
|
line_height: LineHeight,
|
||||||
width: Length,
|
width: Length,
|
||||||
|
|
@ -39,11 +37,12 @@ where
|
||||||
Link: Clone + 'static,
|
Link: Clone + 'static,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::text::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
|
Renderer::Font: 'a,
|
||||||
{
|
{
|
||||||
/// Creates a new empty [`Rich`] text.
|
/// Creates a new empty [`Rich`] text.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
spans: Cow::default(),
|
spans: Box::new([]),
|
||||||
size: None,
|
size: None,
|
||||||
line_height: LineHeight::default(),
|
line_height: LineHeight::default(),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
|
@ -57,10 +56,10 @@ where
|
||||||
|
|
||||||
/// Creates a new [`Rich`] text with the given text spans.
|
/// Creates a new [`Rich`] text with the given text spans.
|
||||||
pub fn with_spans(
|
pub fn with_spans(
|
||||||
spans: impl Into<Cow<'a, [Span<'a, Link, Renderer::Font>]>>,
|
spans: impl AsRef<[Span<'a, Link, Renderer::Font>]> + 'a,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
spans: spans.into(),
|
spans: Box::new(spans),
|
||||||
..Self::new()
|
..Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,15 +153,6 @@ where
|
||||||
self.class = class.into();
|
self.class = class.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new text [`Span`] to the [`Rich`] text.
|
|
||||||
pub fn push(
|
|
||||||
mut self,
|
|
||||||
span: impl Into<Span<'a, Link, Renderer::Font>>,
|
|
||||||
) -> Self {
|
|
||||||
self.spans.to_mut().push(span.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Link, Theme, Renderer> Default for Rich<'a, Link, Theme, Renderer>
|
impl<'a, Link, Theme, Renderer> Default for Rich<'a, Link, Theme, Renderer>
|
||||||
|
|
@ -170,6 +160,7 @@ where
|
||||||
Link: Clone + 'a,
|
Link: Clone + 'a,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::text::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
|
Renderer::Font: 'a,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
@ -221,7 +212,7 @@ where
|
||||||
limits,
|
limits,
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
self.spans.as_ref(),
|
self.spans.as_ref().as_ref(),
|
||||||
self.line_height,
|
self.line_height,
|
||||||
self.size,
|
self.size,
|
||||||
self.font,
|
self.font,
|
||||||
|
|
@ -250,7 +241,7 @@ where
|
||||||
.position_in(layout.bounds())
|
.position_in(layout.bounds())
|
||||||
.and_then(|position| state.paragraph.hit_span(position));
|
.and_then(|position| state.paragraph.hit_span(position));
|
||||||
|
|
||||||
for (index, span) in self.spans.iter().enumerate() {
|
for (index, span) in self.spans.as_ref().as_ref().iter().enumerate() {
|
||||||
let is_hovered_link =
|
let is_hovered_link =
|
||||||
span.link.is_some() && Some(index) == hovered_span;
|
span.link.is_some() && Some(index) == hovered_span;
|
||||||
|
|
||||||
|
|
@ -394,6 +385,8 @@ where
|
||||||
Some(span) if span == span_pressed => {
|
Some(span) if span == span_pressed => {
|
||||||
if let Some(link) = self
|
if let Some(link) = self
|
||||||
.spans
|
.spans
|
||||||
|
.as_ref()
|
||||||
|
.as_ref()
|
||||||
.get(span)
|
.get(span)
|
||||||
.and_then(|span| span.link.clone())
|
.and_then(|span| span.link.clone())
|
||||||
{
|
{
|
||||||
|
|
@ -427,7 +420,7 @@ where
|
||||||
if let Some(span) = state
|
if let Some(span) = state
|
||||||
.paragraph
|
.paragraph
|
||||||
.hit_span(position)
|
.hit_span(position)
|
||||||
.and_then(|span| self.spans.get(span))
|
.and_then(|span| self.spans.as_ref().as_ref().get(span))
|
||||||
{
|
{
|
||||||
if span.link.is_some() {
|
if span.link.is_some() {
|
||||||
return mouse::Interaction::Pointer;
|
return mouse::Interaction::Pointer;
|
||||||
|
|
@ -509,14 +502,12 @@ where
|
||||||
Link: Clone + 'a,
|
Link: Clone + 'a,
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: core::text::Renderer,
|
Renderer: core::text::Renderer,
|
||||||
|
Renderer::Font: 'a,
|
||||||
{
|
{
|
||||||
fn from_iter<T: IntoIterator<Item = Span<'a, Link, Renderer::Font>>>(
|
fn from_iter<T: IntoIterator<Item = Span<'a, Link, Renderer::Font>>>(
|
||||||
spans: T,
|
spans: T,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self::with_spans(spans.into_iter().collect::<Vec<_>>())
|
||||||
spans: spans.into_iter().collect(),
|
|
||||||
..Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue