summaryrefslogtreecommitdiffstats
path: root/widget/src/markdown.rs
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-21 20:00:02 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-21 20:00:02 +0200
commit65b525af7ff2823cfe635c4b26d33aad9068e392 (patch)
treedbd792192e3cdc90a46c822b73287c1828380eeb /widget/src/markdown.rs
parentf830454ffad1cf60f1d6e56fe95514af96848a64 (diff)
downloadiced-65b525af7ff2823cfe635c4b26d33aad9068e392.tar.gz
iced-65b525af7ff2823cfe635c4b26d33aad9068e392.tar.bz2
iced-65b525af7ff2823cfe635c4b26d33aad9068e392.zip
Introduce `markdown::Settings`
Diffstat (limited to 'widget/src/markdown.rs')
-rw-r--r--widget/src/markdown.rs157
1 files changed, 116 insertions, 41 deletions
diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs
index 1df35036..e84ff8d6 100644
--- a/widget/src/markdown.rs
+++ b/widget/src/markdown.rs
@@ -7,16 +7,17 @@
use crate::core::font::{self, Font};
use crate::core::padding;
use crate::core::theme::{self, Theme};
-use crate::core::{self, Element, Length};
+use crate::core::{self, Element, Length, Pixels};
use crate::{column, container, rich_text, row, span, text};
+pub use pulldown_cmark::HeadingLevel;
pub use url::Url;
/// A Markdown item.
#[derive(Debug, Clone)]
pub enum Item {
/// A heading.
- Heading(Vec<text::Span<'static, Url>>),
+ Heading(pulldown_cmark::HeadingLevel, Vec<text::Span<'static, Url>>),
/// A paragraph.
Paragraph(Vec<text::Span<'static, Url>>),
/// A code block.
@@ -43,7 +44,6 @@ pub fn parse(
}
let mut spans = Vec::new();
- let mut heading = None;
let mut strong = false;
let mut emphasis = false;
let mut metadata = false;
@@ -81,12 +81,6 @@ pub fn parse(
#[allow(clippy::drain_collect)]
parser.filter_map(move |event| match event {
pulldown_cmark::Event::Start(tag) => match tag {
- pulldown_cmark::Tag::Heading { level, .. }
- if !metadata && !table =>
- {
- heading = Some(level);
- None
- }
pulldown_cmark::Tag::Strong if !metadata && !table => {
strong = true;
None
@@ -119,7 +113,11 @@ pub fn parse(
None
}
pulldown_cmark::Tag::Item => {
- lists.last_mut().expect("List").items.push(Vec::new());
+ lists
+ .last_mut()
+ .expect("list context")
+ .items
+ .push(Vec::new());
None
}
pulldown_cmark::Tag::CodeBlock(
@@ -150,9 +148,11 @@ pub fn parse(
_ => None,
},
pulldown_cmark::Event::End(tag) => match tag {
- pulldown_cmark::TagEnd::Heading(_) if !metadata && !table => {
- heading = None;
- produce(&mut lists, Item::Heading(spans.drain(..).collect()))
+ pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => {
+ produce(
+ &mut lists,
+ Item::Heading(level, spans.drain(..).collect()),
+ )
}
pulldown_cmark::TagEnd::Emphasis if !metadata && !table => {
emphasis = false;
@@ -180,7 +180,7 @@ pub fn parse(
}
}
pulldown_cmark::TagEnd::List(_) if !metadata && !table => {
- let list = lists.pop().expect("List");
+ let list = lists.pop().expect("list context");
produce(
&mut lists,
@@ -228,18 +228,6 @@ pub fn parse(
let span = span(text.into_string());
- let span = match heading {
- None => span,
- Some(heading) => span.size(match heading {
- pulldown_cmark::HeadingLevel::H1 => 32,
- pulldown_cmark::HeadingLevel::H2 => 28,
- pulldown_cmark::HeadingLevel::H3 => 24,
- pulldown_cmark::HeadingLevel::H4 => 20,
- pulldown_cmark::HeadingLevel::H5 => 16,
- pulldown_cmark::HeadingLevel::H6 => 16,
- }),
- };
-
let span = if strong || emphasis {
span.font(Font {
weight: if strong {
@@ -269,7 +257,15 @@ pub fn parse(
None
}
pulldown_cmark::Event::Code(code) if !metadata && !table => {
- spans.push(span(code.into_string()).font(Font::MONOSPACE));
+ let span = span(code.into_string()).font(Font::MONOSPACE);
+
+ let span = if let Some(link) = link.as_ref() {
+ span.color(palette.primary).link(link.clone())
+ } else {
+ span
+ };
+
+ spans.push(span);
None
}
pulldown_cmark::Event::SoftBreak if !metadata && !table => {
@@ -284,54 +280,133 @@ pub fn parse(
})
}
+/// Configuration controlling Markdown rendering in [`view`].
+#[derive(Debug, Clone, Copy)]
+pub struct Settings {
+ /// The base text size.
+ pub text_size: Pixels,
+ /// The text size of level 1 heading.
+ pub h1_size: Pixels,
+ /// The text size of level 2 heading.
+ pub h2_size: Pixels,
+ /// The text size of level 3 heading.
+ pub h3_size: Pixels,
+ /// The text size of level 4 heading.
+ pub h4_size: Pixels,
+ /// The text size of level 5 heading.
+ pub h5_size: Pixels,
+ /// The text size of level 6 heading.
+ pub h6_size: Pixels,
+ /// The text size used in code blocks.
+ pub code_size: Pixels,
+}
+
+impl Settings {
+ /// Creates new [`Settings`] with the given base text size in [`Pixels`].
+ ///
+ /// Heading levels will be adjusted automatically. Specifically,
+ /// the first level will be twice the base size, and then every level
+ /// after that will be 25% smaller.
+ pub fn with_text_size(text_size: impl Into<Pixels>) -> Self {
+ let text_size = text_size.into();
+
+ Self {
+ text_size,
+ h1_size: text_size * 2.0,
+ h2_size: text_size * 1.75,
+ h3_size: text_size * 1.5,
+ h4_size: text_size * 1.25,
+ h5_size: text_size,
+ h6_size: text_size,
+ code_size: text_size * 0.75,
+ }
+ }
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self::with_text_size(16)
+ }
+}
+
/// Display a bunch of Markdown items.
///
/// You can obtain the items with [`parse`].
pub fn view<'a, Message, Renderer>(
items: impl IntoIterator<Item = &'a Item>,
+ settings: Settings,
on_link: impl Fn(Url) -> Message + Copy + 'a,
) -> Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
+ let Settings {
+ text_size,
+ h1_size,
+ h2_size,
+ h3_size,
+ h4_size,
+ h5_size,
+ h6_size,
+ code_size,
+ } = settings;
+
+ let spacing = text_size * 0.625;
+
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
- Item::Heading(heading) => {
- container(rich_text(heading).on_link(on_link))
- .padding(padding::top(if i > 0 { 8 } else { 0 }))
- .into()
+ Item::Heading(level, heading) => {
+ container(rich_text(heading).on_link(on_link).size(match level {
+ pulldown_cmark::HeadingLevel::H1 => h1_size,
+ pulldown_cmark::HeadingLevel::H2 => h2_size,
+ pulldown_cmark::HeadingLevel::H3 => h3_size,
+ pulldown_cmark::HeadingLevel::H4 => h4_size,
+ pulldown_cmark::HeadingLevel::H5 => h5_size,
+ pulldown_cmark::HeadingLevel::H6 => h6_size,
+ }))
+ .padding(padding::top(if i > 0 {
+ text_size / 2.0
+ } else {
+ Pixels::ZERO
+ }))
+ .into()
}
Item::Paragraph(paragraph) => {
- rich_text(paragraph).on_link(on_link).into()
+ rich_text(paragraph).on_link(on_link).size(text_size).into()
}
Item::List { start: None, items } => {
column(items.iter().map(|items| {
- row!["•", view(items, on_link)].spacing(10).into()
+ row![text("•").size(text_size), view(items, settings, on_link)]
+ .spacing(spacing)
+ .into()
}))
- .spacing(10)
+ .spacing(spacing)
.into()
}
Item::List {
start: Some(start),
items,
} => column(items.iter().enumerate().map(|(i, items)| {
- row![text!("{}.", i as u64 + *start), view(items, on_link)]
- .spacing(10)
- .into()
+ row![
+ text!("{}.", i as u64 + *start).size(text_size),
+ view(items, settings, on_link)
+ ]
+ .spacing(spacing)
+ .into()
}))
- .spacing(10)
+ .spacing(spacing)
.into(),
Item::CodeBlock(code) => container(
rich_text(code)
.font(Font::MONOSPACE)
- .size(12)
+ .size(code_size)
.on_link(on_link),
)
.width(Length::Fill)
- .padding(10)
+ .padding(spacing.0)
.style(container::rounded_box)
.into(),
});
- Element::new(column(blocks).width(Length::Fill).spacing(16))
+ Element::new(column(blocks).width(Length::Fill).spacing(text_size))
}