aboutsummaryrefslogtreecommitdiff
path: root/src/markdown.rs
diff options
context:
space:
mode:
authorPolesznyák Márk <contact@pml68.dev>2025-10-20 01:31:15 +0200
committerPolesznyák Márk <contact@pml68.dev>2025-10-20 01:31:15 +0200
commit2a6e3a98a2452045622a4746558ec46cdfa8fc1d (patch)
tree195dd69196b6828e9ca9ad8d4267518437c361fc /src/markdown.rs
parentchore: add a to-do list (diff)
downloadiced_selection-2a6e3a98a2452045622a4746558ec46cdfa8fc1d.tar.gz
feat(wip): add markdown support
Diffstat (limited to 'src/markdown.rs')
-rw-r--r--src/markdown.rs255
1 files changed, 255 insertions, 0 deletions
diff --git a/src/markdown.rs b/src/markdown.rs
new file mode 100644
index 0000000..c54271a
--- /dev/null
+++ b/src/markdown.rs
@@ -0,0 +1,255 @@
+//! A custom markdown viewer and its corresponding functions.
+//!
+//! To be used with [`view_with`]
+//!
+//! [`view_with`]: iced_widget::markdown::view_with
+use iced_widget::graphics::text::Paragraph;
+use iced_widget::markdown::{
+ Catalog, HeadingLevel, Item, Settings, Text, Url, Viewer, view_with,
+};
+use iced_widget::{column, container, row, scrollable};
+
+use crate::core::Font;
+use crate::core::alignment;
+use crate::core::padding;
+use crate::core::{self, Element, Length, Pixels};
+use crate::{rich_text, text};
+
+/// Display a bunch of markdown items.
+pub fn view<'a, Theme, Renderer>(
+ items: impl IntoIterator<Item = &'a Item>,
+ settings: impl Into<Settings>,
+) -> Element<'a, Url, Theme, Renderer>
+where
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ view_with(items, settings, &SelectableViewer)
+}
+/// Displays a heading using the default look.
+pub fn heading<'a, Message, Theme, Renderer>(
+ settings: Settings,
+ level: &'a HeadingLevel,
+ text: &'a Text,
+ index: usize,
+ on_link_click: impl Fn(Url) -> Message + 'a,
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ let Settings {
+ h1_size,
+ h2_size,
+ h3_size,
+ h4_size,
+ h5_size,
+ h6_size,
+ text_size,
+ ..
+ } = settings;
+
+ container(
+ rich_text(text.spans(settings.style))
+ .on_link_click(on_link_click)
+ .size(match level {
+ HeadingLevel::H1 => h1_size,
+ HeadingLevel::H2 => h2_size,
+ HeadingLevel::H3 => h3_size,
+ HeadingLevel::H4 => h4_size,
+ HeadingLevel::H5 => h5_size,
+ HeadingLevel::H6 => h6_size,
+ }),
+ )
+ .padding(padding::top(if index > 0 {
+ text_size / 2.0
+ } else {
+ Pixels::ZERO
+ }))
+ .into()
+}
+
+/// Displays a paragraph using the default look.
+pub fn paragraph<'a, Message, Theme, Renderer>(
+ settings: Settings,
+ text: &Text,
+ on_link_click: impl Fn(Url) -> Message + 'a,
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ rich_text(text.spans(settings.style))
+ .size(settings.text_size)
+ .on_link_click(on_link_click)
+ .into()
+}
+
+/// Displays an unordered list using the default look and
+/// calling the [`Viewer`] for each bullet point item.
+pub fn unordered_list<'a, Message, Theme, Renderer>(
+ viewer: &impl Viewer<'a, Message, Theme, Renderer>,
+ settings: Settings,
+ items: &'a [Vec<Item>],
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ column(items.iter().map(|items| {
+ row![
+ text("•").size(settings.text_size),
+ view_with(
+ items,
+ Settings {
+ spacing: settings.spacing * 0.6,
+ ..settings
+ },
+ viewer,
+ )
+ ]
+ .spacing(settings.spacing)
+ .into()
+ }))
+ .spacing(settings.spacing * 0.75)
+ .padding([0.0, settings.spacing.0])
+ .into()
+}
+
+/// Displays an ordered list using the default look and
+/// calling the [`Viewer`] for each numbered item.
+pub fn ordered_list<'a, Message, Theme, Renderer>(
+ viewer: &impl Viewer<'a, Message, Theme, Renderer>,
+ settings: Settings,
+ start: u64,
+ items: &'a [Vec<Item>],
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ let digits = ((start + items.len() as u64).max(1) as f32).log10().ceil();
+
+ column(items.iter().enumerate().map(|(i, items)| {
+ row![
+ text!("{}.", i as u64 + start)
+ .size(settings.text_size)
+ .align_x(alignment::Horizontal::Right)
+ .width(settings.text_size * ((digits / 2.0).ceil() + 1.0)),
+ view_with(
+ items,
+ Settings {
+ spacing: settings.spacing * 0.6,
+ ..settings
+ },
+ viewer,
+ )
+ ]
+ .spacing(settings.spacing)
+ .into()
+ }))
+ .spacing(settings.spacing * 0.75)
+ .into()
+}
+
+/// Displays a code block using the default look.
+pub fn code_block<'a, Message, Theme, Renderer>(
+ settings: Settings,
+ lines: &'a [Text],
+ on_link_click: impl Fn(Url) -> Message + Clone + 'a,
+) -> Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ container(
+ scrollable(
+ container(column(lines.iter().map(|line| {
+ rich_text(line.spans(settings.style))
+ .on_link_click(on_link_click.clone())
+ .font(Font::MONOSPACE)
+ .size(settings.code_size)
+ .into()
+ })))
+ .padding(settings.code_size),
+ )
+ .direction(scrollable::Direction::Horizontal(
+ scrollable::Scrollbar::default()
+ .width(settings.code_size / 2)
+ .scroller_width(settings.code_size / 2),
+ )),
+ )
+ .width(Length::Fill)
+ .padding(settings.code_size / 4)
+ .class(Theme::code_block())
+ .into()
+}
+
+#[derive(Debug, Clone, Copy)]
+struct SelectableViewer;
+
+impl<'a, Theme, Renderer> Viewer<'a, Url, Theme, Renderer> for SelectableViewer
+where
+ Theme: Catalog + text::Catalog + 'a,
+ Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
+{
+ fn on_link_click(url: Url) -> Url {
+ url
+ }
+
+ fn heading(
+ &self,
+ settings: Settings,
+ level: &'a HeadingLevel,
+ text: &'a Text,
+ index: usize,
+ ) -> Element<'a, Url, Theme, Renderer> {
+ heading::<'a, Url, Theme, Renderer>(
+ settings,
+ level,
+ text,
+ index,
+ Self::on_link_click,
+ )
+ }
+
+ fn paragraph(
+ &self,
+ settings: Settings,
+ text: &Text,
+ ) -> Element<'a, Url, Theme, Renderer> {
+ paragraph(settings, text, Self::on_link_click)
+ }
+
+ fn unordered_list(
+ &self,
+ settings: Settings,
+ items: &'a [Vec<Item>],
+ ) -> Element<'a, Url, Theme, Renderer> {
+ unordered_list(self, settings, items)
+ }
+
+ fn ordered_list(
+ &self,
+ settings: Settings,
+ start: u64,
+ items: &'a [Vec<Item>],
+ ) -> Element<'a, Url, Theme, Renderer> {
+ ordered_list(self, settings, start, items)
+ }
+
+ fn code_block(
+ &self,
+ settings: Settings,
+ _language: Option<&'a str>,
+ _code: &'a str,
+ lines: &'a [Text],
+ ) -> Element<'a, Url, Theme, Renderer> {
+ code_block(settings, lines, Self::on_link_click)
+ }
+}