From 565599876172b3f56d86b119ae453b5bcd8949e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 07:53:56 +0100 Subject: Draft `Viewer` trait for `markdown` --- examples/changelog/src/main.rs | 26 ++++---- examples/markdown/Cargo.toml | 8 ++- examples/markdown/src/main.rs | 139 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 151 insertions(+), 22 deletions(-) (limited to 'examples') diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs index f889e757..a6528ce9 100644 --- a/examples/changelog/src/main.rs +++ b/examples/changelog/src/main.rs @@ -267,25 +267,21 @@ impl Generator { } => { let details = { let title = rich_text![ - span(&pull_request.title).size(24).link( - Message::OpenPullRequest(pull_request.id) - ), + span(&pull_request.title) + .size(24) + .link(pull_request.id), span(format!(" by {}", pull_request.author)) .font(Font { style: font::Style::Italic, ..Font::default() }), ] + .on_link_clicked(Message::OpenPullRequest) .font(Font::MONOSPACE); - let description = markdown::view( - description, - markdown::Settings::default(), - markdown::Style::from_palette( - self.theme().palette(), - ), - ) - .map(Message::UrlClicked); + let description = + markdown::view(&self.theme(), description) + .map(Message::UrlClicked); let labels = row(pull_request.labels.iter().map(|label| { @@ -349,11 +345,11 @@ impl Generator { container( scrollable( markdown::view( - preview, - markdown::Settings::with_text_size(12), - markdown::Style::from_palette( - self.theme().palette(), + markdown::Settings::with_text_size( + 12, + &self.theme(), ), + preview, ) .map(Message::UrlClicked), ) diff --git a/examples/markdown/Cargo.toml b/examples/markdown/Cargo.toml index fa6ced74..4711b1c4 100644 --- a/examples/markdown/Cargo.toml +++ b/examples/markdown/Cargo.toml @@ -7,6 +7,12 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["markdown", "highlighter", "tokio", "debug"] +iced.features = ["markdown", "highlighter", "image", "tokio", "debug"] + +reqwest.version = "0.12" +reqwest.features = ["json"] + +image.workspace = true +tokio.workspace = true open = "5.3" diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index ba93ee18..29625d79 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -1,10 +1,17 @@ use iced::highlighter; use iced::time::{self, milliseconds}; use iced::widget::{ - self, hover, markdown, right, row, scrollable, text_editor, toggler, + self, center_x, horizontal_space, hover, image, markdown, pop, right, row, + scrollable, text_editor, toggler, }; use iced::{Element, Fill, Font, Subscription, Task, Theme}; +use tokio::task; + +use std::collections::HashMap; +use std::io; +use std::sync::Arc; + pub fn main() -> iced::Result { iced::application("Markdown - Iced", Markdown::update, Markdown::view) .subscription(Markdown::subscription) @@ -14,6 +21,7 @@ pub fn main() -> iced::Result { struct Markdown { content: text_editor::Content, + images: HashMap, mode: Mode, theme: Theme, } @@ -26,10 +34,19 @@ enum Mode { }, } +enum Image { + Loading, + Ready(image::Handle), + #[allow(dead_code)] + Errored(Error), +} + #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), LinkClicked(markdown::Url), + ImageShown(markdown::Url), + ImageDownloaded(markdown::Url, Result), ToggleStream(bool), NextToken, } @@ -43,6 +60,7 @@ impl Markdown { ( Self { content: text_editor::Content::with_text(INITIAL_CONTENT), + images: HashMap::new(), mode: Mode::Preview(markdown::parse(INITIAL_CONTENT).collect()), theme, }, @@ -70,6 +88,25 @@ impl Markdown { Task::none() } + Message::ImageShown(url) => { + if self.images.contains_key(&url) { + return Task::none(); + } + + let _ = self.images.insert(url.clone(), Image::Loading); + + Task::perform(download_image(url.clone()), move |result| { + Message::ImageDownloaded(url.clone(), result) + }) + } + Message::ImageDownloaded(url, result) => { + let _ = self.images.insert( + url, + result.map(Image::Ready).unwrap_or_else(Image::Errored), + ); + + Task::none() + } Message::ToggleStream(enable_stream) => { if enable_stream { self.mode = Mode::Stream { @@ -126,12 +163,13 @@ impl Markdown { Mode::Stream { parsed, .. } => parsed.items(), }; - let preview = markdown( + let preview = markdown::view_with( + &MarkdownViewer { + images: &self.images, + }, + &self.theme, items, - markdown::Settings::default(), - markdown::Style::from_palette(self.theme.palette()), - ) - .map(Message::LinkClicked); + ); row![ editor, @@ -167,3 +205,92 @@ impl Markdown { } } } + +struct MarkdownViewer<'a> { + images: &'a HashMap, +} + +impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> { + fn on_link_clicked(url: markdown::Url) -> Message { + Message::LinkClicked(url) + } + + fn image( + &self, + _settings: markdown::Settings, + _title: &markdown::Text, + url: &'a markdown::Url, + ) -> Element<'a, Message> { + if let Some(Image::Ready(handle)) = self.images.get(url) { + center_x(image(handle)).into() + } else { + pop(horizontal_space().width(0)) + .key(url.as_str()) + .on_show(|_size| Message::ImageShown(url.clone())) + .into() + } + } +} + +async fn download_image(url: markdown::Url) -> Result { + use std::io; + use tokio::task; + + let client = reqwest::Client::new(); + + let bytes = client + .get(url) + .send() + .await? + .error_for_status()? + .bytes() + .await?; + + let image = task::spawn_blocking(move || { + Ok::<_, Error>( + ::image::ImageReader::new(io::Cursor::new(bytes)) + .with_guessed_format()? + .decode()? + .to_rgba8(), + ) + }) + .await??; + + Ok(image::Handle::from_rgba( + image.width(), + image.height(), + image.into_raw(), + )) +} + +#[derive(Debug, Clone)] +pub enum Error { + RequestFailed(Arc), + IOFailed(Arc), + JoinFailed(Arc), + ImageDecodingFailed(Arc<::image::ImageError>), +} + +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Self::RequestFailed(Arc::new(error)) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Self::IOFailed(Arc::new(error)) + } +} + +impl From for Error { + fn from(error: task::JoinError) -> Self { + Self::JoinFailed(Arc::new(error)) + } +} + +impl From<::image::ImageError> for Error { + fn from(error: ::image::ImageError) -> Self { + Self::ImageDecodingFailed(Arc::new(error)) + } +} -- cgit From 24cf355e96d8ae6432ca8972214c2c4f31d07b22 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 19:07:24 +0100 Subject: Fix documentation of `markdown` and `rich_text` --- examples/changelog/src/main.rs | 8 ++++---- examples/markdown/src/main.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'examples') diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs index a6528ce9..f3b9894f 100644 --- a/examples/changelog/src/main.rs +++ b/examples/changelog/src/main.rs @@ -280,7 +280,7 @@ impl Generator { .font(Font::MONOSPACE); let description = - markdown::view(&self.theme(), description) + markdown(description, self.theme()) .map(Message::UrlClicked); let labels = @@ -344,12 +344,12 @@ impl Generator { } else { container( scrollable( - markdown::view( + markdown( + preview, markdown::Settings::with_text_size( 12, - &self.theme(), + self.theme(), ), - preview, ) .map(Message::UrlClicked), ) diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 29625d79..96541342 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -164,11 +164,11 @@ impl Markdown { }; let preview = markdown::view_with( + items, + &self.theme, &MarkdownViewer { images: &self.images, }, - &self.theme, - items, ); row![ -- cgit From a6e64eac6f2a4451cdc100e4019ab76e1ce68064 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 19:57:51 +0100 Subject: Animate image fade in in `markdown` example --- examples/markdown/src/main.rs | 151 ++++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 49 deletions(-) (limited to 'examples') diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 96541342..2fb25376 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -1,12 +1,13 @@ +use iced::animation; use iced::highlighter; -use iced::time::{self, milliseconds}; +use iced::task; +use iced::time::{self, milliseconds, Instant}; use iced::widget::{ self, center_x, horizontal_space, hover, image, markdown, pop, right, row, scrollable, text_editor, toggler, }; -use iced::{Element, Fill, Font, Subscription, Task, Theme}; - -use tokio::task; +use iced::window; +use iced::{Animation, Element, Fill, Font, Subscription, Task, Theme}; use std::collections::HashMap; use std::io; @@ -20,23 +21,27 @@ pub fn main() -> iced::Result { } struct Markdown { - content: text_editor::Content, + content: markdown::Content, + raw: text_editor::Content, images: HashMap, mode: Mode, theme: Theme, + now: Instant, } enum Mode { - Preview(Vec), - Stream { - pending: String, - parsed: markdown::Content, - }, + Preview, + Stream { pending: String }, } enum Image { - Loading, - Ready(image::Handle), + Loading { + _download: task::Handle, + }, + Ready { + handle: image::Handle, + fade_in: Animation, + }, #[allow(dead_code)] Errored(Error), } @@ -49,20 +54,21 @@ enum Message { ImageDownloaded(markdown::Url, Result), ToggleStream(bool), NextToken, + Animate(Instant), } impl Markdown { fn new() -> (Self, Task) { const INITIAL_CONTENT: &str = include_str!("../overview.md"); - let theme = Theme::TokyoNight; - ( Self { - content: text_editor::Content::with_text(INITIAL_CONTENT), + content: markdown::Content::parse(INITIAL_CONTENT), + raw: text_editor::Content::with_text(INITIAL_CONTENT), images: HashMap::new(), - mode: Mode::Preview(markdown::parse(INITIAL_CONTENT).collect()), - theme, + mode: Mode::Preview, + theme: Theme::TokyoNight, + now: Instant::now(), }, widget::focus_next(), ) @@ -73,12 +79,14 @@ impl Markdown { Message::Edit(action) => { let is_edit = action.is_edit(); - self.content.perform(action); + self.raw.perform(action); if is_edit { - self.mode = Mode::Preview( - markdown::parse(&self.content.text()).collect(), - ); + self.content = markdown::Content::parse(&self.raw.text()); + self.mode = Mode::Preview; + + let images = self.content.images(); + self.images.retain(|url, _image| images.contains(url)); } Task::none() @@ -93,16 +101,40 @@ impl Markdown { return Task::none(); } - let _ = self.images.insert(url.clone(), Image::Loading); + let (download_image, handle) = Task::future({ + let url = url.clone(); - Task::perform(download_image(url.clone()), move |result| { + async move { + // Wait half a second for further editions before attempting download + tokio::time::sleep(milliseconds(500)).await; + download_image(url).await + } + }) + .abortable(); + + let _ = self.images.insert( + url.clone(), + Image::Loading { + _download: handle.abort_on_drop(), + }, + ); + + download_image.map(move |result| { Message::ImageDownloaded(url.clone(), result) }) } Message::ImageDownloaded(url, result) => { let _ = self.images.insert( url, - result.map(Image::Ready).unwrap_or_else(Image::Errored), + result + .map(|handle| Image::Ready { + handle, + fade_in: Animation::new(false) + .quick() + .easing(animation::Easing::EaseInOut) + .go(true), + }) + .unwrap_or_else(Image::Errored), ); Task::none() @@ -110,8 +142,7 @@ impl Markdown { Message::ToggleStream(enable_stream) => { if enable_stream { self.mode = Mode::Stream { - pending: self.content.text(), - parsed: markdown::Content::new(), + pending: self.raw.text(), }; scrollable::snap_to( @@ -119,24 +150,22 @@ impl Markdown { scrollable::RelativeOffset::END, ) } else { - self.mode = Mode::Preview( - markdown::parse(&self.content.text()).collect(), - ); + self.mode = Mode::Preview; Task::none() } } Message::NextToken => { match &mut self.mode { - Mode::Preview(_) => {} - Mode::Stream { pending, parsed } => { + Mode::Preview => {} + Mode::Stream { pending } => { if pending.is_empty() { - self.mode = Mode::Preview(parsed.items().to_vec()); + self.mode = Mode::Preview; } else { let mut tokens = pending.split(' '); if let Some(token) = tokens.next() { - parsed.push_str(&format!("{token} ")); + self.content.push_str(&format!("{token} ")); } *pending = tokens.collect::>().join(" "); @@ -144,13 +173,18 @@ impl Markdown { } } + Task::none() + } + Message::Animate(now) => { + self.now = now; + Task::none() } } } fn view(&self) -> Element { - let editor = text_editor(&self.content) + let editor = text_editor(&self.raw) .placeholder("Type your Markdown here...") .on_action(Message::Edit) .height(Fill) @@ -158,16 +192,12 @@ impl Markdown { .font(Font::MONOSPACE) .highlight("markdown", highlighter::Theme::Base16Ocean); - let items = match &self.mode { - Mode::Preview(items) => items.as_slice(), - Mode::Stream { parsed, .. } => parsed.items(), - }; - let preview = markdown::view_with( - items, + self.content.items(), &self.theme, &MarkdownViewer { images: &self.images, + now: self.now, }, ); @@ -197,17 +227,33 @@ impl Markdown { } fn subscription(&self) -> Subscription { - match self.mode { - Mode::Preview(_) => Subscription::none(), + let listen_stream = match self.mode { + Mode::Preview => Subscription::none(), Mode::Stream { .. } => { time::every(milliseconds(10)).map(|_| Message::NextToken) } - } + }; + + let animate = { + let is_animating = self.images.values().any(|image| match image { + Image::Ready { fade_in, .. } => fade_in.is_animating(self.now), + _ => false, + }); + + if is_animating { + window::frames().map(Message::Animate) + } else { + Subscription::none() + } + }; + + Subscription::batch([listen_stream, animate]) } } struct MarkdownViewer<'a> { images: &'a HashMap, + now: Instant, } impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> { @@ -221,10 +267,15 @@ impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> { _title: &markdown::Text, url: &'a markdown::Url, ) -> Element<'a, Message> { - if let Some(Image::Ready(handle)) = self.images.get(url) { - center_x(image(handle)).into() + if let Some(Image::Ready { handle, fade_in }) = self.images.get(url) { + center_x( + image(handle) + .opacity(fade_in.interpolate(0.0, 1.0, self.now)) + .scale(fade_in.interpolate(1.2, 1.0, self.now)), + ) + .into() } else { - pop(horizontal_space().width(0)) + pop(horizontal_space()) .key(url.as_str()) .on_show(|_size| Message::ImageShown(url.clone())) .into() @@ -236,6 +287,8 @@ async fn download_image(url: markdown::Url) -> Result { use std::io; use tokio::task; + println!("Trying to download image: {url}"); + let client = reqwest::Client::new(); let bytes = client @@ -267,7 +320,7 @@ async fn download_image(url: markdown::Url) -> Result { pub enum Error { RequestFailed(Arc), IOFailed(Arc), - JoinFailed(Arc), + JoinFailed(Arc), ImageDecodingFailed(Arc<::image::ImageError>), } @@ -283,8 +336,8 @@ impl From for Error { } } -impl From for Error { - fn from(error: task::JoinError) -> Self { +impl From for Error { + fn from(error: tokio::task::JoinError) -> Self { Self::JoinFailed(Arc::new(error)) } } -- cgit From 387abafa3abda3ba68eb7a2e4ce4240ad3bdda53 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 20:18:49 +0100 Subject: Add `alt` and `title` to `markdown` images --- examples/markdown/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 2fb25376..84c20b7e 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -195,7 +195,7 @@ impl Markdown { let preview = markdown::view_with( self.content.items(), &self.theme, - &MarkdownViewer { + &CustomViewer { images: &self.images, now: self.now, }, @@ -251,12 +251,12 @@ impl Markdown { } } -struct MarkdownViewer<'a> { +struct CustomViewer<'a> { images: &'a HashMap, now: Instant, } -impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> { +impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> { fn on_link_clicked(url: markdown::Url) -> Message { Message::LinkClicked(url) } @@ -264,8 +264,9 @@ impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> { fn image( &self, _settings: markdown::Settings, - _title: &markdown::Text, url: &'a markdown::Url, + _title: &'a str, + _alt: &markdown::Text, ) -> Element<'a, Message> { if let Some(Image::Ready { handle, fade_in }) = self.images.get(url) { center_x( -- cgit From e8020f3eaf3baec2b41847f6250d8554136e8d89 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 20:58:06 +0100 Subject: Add `Copy` action to code blocks in `markdown` example --- examples/markdown/Cargo.toml | 5 +++++ examples/markdown/build.rs | 5 +++++ examples/markdown/fonts/markdown-icons.toml | 4 ++++ examples/markdown/fonts/markdown-icons.ttf | Bin 0 -> 5856 bytes examples/markdown/src/icon.rs | 15 +++++++++++++ examples/markdown/src/main.rs | 32 ++++++++++++++++++++++++++-- 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 examples/markdown/build.rs create mode 100644 examples/markdown/fonts/markdown-icons.toml create mode 100644 examples/markdown/fonts/markdown-icons.ttf create mode 100644 examples/markdown/src/icon.rs (limited to 'examples') diff --git a/examples/markdown/Cargo.toml b/examples/markdown/Cargo.toml index 4711b1c4..7af1741b 100644 --- a/examples/markdown/Cargo.toml +++ b/examples/markdown/Cargo.toml @@ -16,3 +16,8 @@ image.workspace = true tokio.workspace = true open = "5.3" + +# Disabled to keep amount of build dependencies low +# This can be re-enabled on demand +# [build-dependencies] +# iced_fontello = "0.13" diff --git a/examples/markdown/build.rs b/examples/markdown/build.rs new file mode 100644 index 00000000..ecbb7666 --- /dev/null +++ b/examples/markdown/build.rs @@ -0,0 +1,5 @@ +pub fn main() { + // println!("cargo::rerun-if-changed=fonts/markdown-icons.toml"); + // iced_fontello::build("fonts/markdown-icons.toml") + // .expect("Build icons font"); +} diff --git a/examples/markdown/fonts/markdown-icons.toml b/examples/markdown/fonts/markdown-icons.toml new file mode 100644 index 00000000..60c91d17 --- /dev/null +++ b/examples/markdown/fonts/markdown-icons.toml @@ -0,0 +1,4 @@ +module = "icon" + +[glyphs] +copy = "fontawesome-docs" diff --git a/examples/markdown/fonts/markdown-icons.ttf b/examples/markdown/fonts/markdown-icons.ttf new file mode 100644 index 00000000..013f03a5 Binary files /dev/null and b/examples/markdown/fonts/markdown-icons.ttf differ diff --git a/examples/markdown/src/icon.rs b/examples/markdown/src/icon.rs new file mode 100644 index 00000000..cfe32541 --- /dev/null +++ b/examples/markdown/src/icon.rs @@ -0,0 +1,15 @@ +// Generated automatically by iced_fontello at build time. +// Do not edit manually. Source: ../fonts/markdown-icons.toml +// dcd2f0c969d603e2ee9237a4b70fa86b1a6e84d86f4689046d8fdd10440b06b9 +use iced::widget::{text, Text}; +use iced::Font; + +pub const FONT: &[u8] = include_bytes!("../fonts/markdown-icons.ttf"); + +pub fn copy<'a>() -> Text<'a> { + icon("\u{F0C5}") +} + +fn icon(codepoint: &str) -> Text<'_> { + text(codepoint).font(Font::with_name("markdown-icons")) +} diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 84c20b7e..6a881288 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -1,10 +1,13 @@ +mod icon; + use iced::animation; +use iced::clipboard; use iced::highlighter; use iced::task; use iced::time::{self, milliseconds, Instant}; use iced::widget::{ - self, center_x, horizontal_space, hover, image, markdown, pop, right, row, - scrollable, text_editor, toggler, + self, button, center_x, horizontal_space, hover, image, markdown, pop, + right, row, scrollable, text_editor, toggler, }; use iced::window; use iced::{Animation, Element, Fill, Font, Subscription, Task, Theme}; @@ -15,6 +18,7 @@ use std::sync::Arc; pub fn main() -> iced::Result { iced::application("Markdown - Iced", Markdown::update, Markdown::view) + .font(icon::FONT) .subscription(Markdown::subscription) .theme(Markdown::theme) .run_with(Markdown::new) @@ -49,6 +53,7 @@ enum Image { #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), + Copy(String), LinkClicked(markdown::Url), ImageShown(markdown::Url), ImageDownloaded(markdown::Url, Result), @@ -91,6 +96,7 @@ impl Markdown { Task::none() } + Message::Copy(content) => clipboard::write(content), Message::LinkClicked(link) => { let _ = open::that_in_background(link.to_string()); @@ -141,6 +147,8 @@ impl Markdown { } Message::ToggleStream(enable_stream) => { if enable_stream { + self.content = markdown::Content::new(); + self.mode = Mode::Stream { pending: self.raw.text(), }; @@ -282,6 +290,26 @@ impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> { .into() } } + + fn code_block( + &self, + settings: markdown::Settings, + code: &'a str, + lines: &'a [markdown::Text], + ) -> Element<'a, Message> { + let code_block = + markdown::code_block(settings, code, lines, Message::LinkClicked); + + hover( + code_block, + right( + button(icon::copy().size(12)) + .padding(settings.spacing / 2) + .on_press_with(|| Message::Copy(code.to_owned())) + .style(button::text), + ), + ) + } } async fn download_image(url: markdown::Url) -> Result { -- cgit From f8c71a20a99568b2ddd0e07ac021d37ce2933856 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 4 Feb 2025 21:22:01 +0100 Subject: Rename `on_link_clicked` to `on_link_click` --- examples/changelog/src/main.rs | 2 +- examples/markdown/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs index f3b9894f..a1d0d799 100644 --- a/examples/changelog/src/main.rs +++ b/examples/changelog/src/main.rs @@ -276,7 +276,7 @@ impl Generator { ..Font::default() }), ] - .on_link_clicked(Message::OpenPullRequest) + .on_link_click(Message::OpenPullRequest) .font(Font::MONOSPACE); let description = diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 6a881288..20957bcd 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -265,7 +265,7 @@ struct CustomViewer<'a> { } impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> { - fn on_link_clicked(url: markdown::Url) -> Message { + fn on_link_click(url: markdown::Url) -> Message { Message::LinkClicked(url) } -- cgit From c7711e59ab74f9cd5a31229b8fc4191ca1322917 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 5 Feb 2025 01:33:16 +0100 Subject: Add `language` to `Item::CodeBlock` in `markdown` --- examples/markdown/src/main.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'examples') diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 20957bcd..512d4b44 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -6,8 +6,8 @@ use iced::highlighter; use iced::task; use iced::time::{self, milliseconds, Instant}; use iced::widget::{ - self, button, center_x, horizontal_space, hover, image, markdown, pop, - right, row, scrollable, text_editor, toggler, + self, button, center_x, container, horizontal_space, hover, image, + markdown, pop, right, row, scrollable, text_editor, toggler, }; use iced::window; use iced::{Animation, Element, Fill, Font, Subscription, Task, Theme}; @@ -294,20 +294,22 @@ impl<'a> markdown::Viewer<'a, Message> for CustomViewer<'a> { fn code_block( &self, settings: markdown::Settings, + _language: Option<&'a str>, code: &'a str, lines: &'a [markdown::Text], ) -> Element<'a, Message> { let code_block = - markdown::code_block(settings, code, lines, Message::LinkClicked); + markdown::code_block(settings, lines, Message::LinkClicked); + + let copy = button(icon::copy().size(12)) + .padding(2) + .on_press_with(|| Message::Copy(code.to_owned())) + .style(button::text); hover( code_block, - right( - button(icon::copy().size(12)) - .padding(settings.spacing / 2) - .on_press_with(|| Message::Copy(code.to_owned())) - .style(button::text), - ), + right(container(copy).style(container::dark)) + .padding(settings.spacing / 2), ) } } -- cgit