From e8020f3eaf3baec2b41847f6250d8554136e8d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 4 Feb 2025 20:58:06 +0100 Subject: [PATCH] Add `Copy` action to code blocks in `markdown` example --- Cargo.lock | 8 ++--- core/src/pixels.rs | 8 +++++ 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 ++++++++++++++++++-- widget/src/markdown.rs | 28 +++++++++++++---- 9 files changed, 93 insertions(+), 12 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 diff --git a/Cargo.lock b/Cargo.lock index 4175a188..5e14e0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,9 +770,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.11" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "jobserver", "libc", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.27" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", ] diff --git a/core/src/pixels.rs b/core/src/pixels.rs index a1ea0f15..7d6267cf 100644 --- a/core/src/pixels.rs +++ b/core/src/pixels.rs @@ -79,3 +79,11 @@ impl std::ops::Div for Pixels { Pixels(self.0 / rhs) } } + +impl std::ops::Div for Pixels { + type Output = Pixels; + + fn div(self, rhs: u32) -> Self { + Pixels(self.0 / rhs as f32) + } +} 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 0000000000000000000000000000000000000000..013f03a5d14de099608a1161fbd5eb557bd33e8f GIT binary patch literal 5856 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 -> 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 { diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 9ce5930f..3af301e9 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -55,6 +55,7 @@ use crate::{column, container, rich_text, row, scrollable, span, text}; use std::borrow::BorrowMut; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet}; +use std::mem; use std::ops::Range; use std::rc::Rc; use std::sync::Arc; @@ -182,7 +183,12 @@ pub enum Item { /// A code block. /// /// You can enable the `highlighter` feature for syntax highlighting. - CodeBlock(Vec), + CodeBlock { + /// The raw code of the code block. + code: String, + /// The styled lines of text in the code block. + lines: Vec, + }, /// A list. List { /// The first number of the list, if it is ordered. @@ -457,7 +463,8 @@ fn parse_with<'a>( let broken_links = Rc::new(RefCell::new(HashSet::new())); let mut spans = Vec::new(); - let mut code = Vec::new(); + let mut code = String::new(); + let mut code_lines = Vec::new(); let mut strong = false; let mut emphasis = false; let mut strikethrough = false; @@ -726,7 +733,10 @@ fn parse_with<'a>( produce( state.borrow_mut(), &mut stack, - Item::CodeBlock(code.drain(..).collect()), + Item::CodeBlock { + code: mem::take(&mut code), + lines: code_lines.drain(..).collect(), + }, source, ) } @@ -743,8 +753,10 @@ fn parse_with<'a>( pulldown_cmark::Event::Text(text) if !metadata && !table => { #[cfg(feature = "highlighter")] if let Some(highlighter) = &mut highlighter { + code.push_str(&text); + for line in text.lines() { - code.push(Text::new( + code_lines.push(Text::new( highlighter.highlight_line(line).to_vec(), )); } @@ -1017,7 +1029,9 @@ where viewer.heading(settings, level, text, index) } Item::Paragraph(text) => viewer.paragraph(settings, text), - Item::CodeBlock(lines) => viewer.code_block(settings, lines), + Item::CodeBlock { code, lines } => { + viewer.code_block(settings, code, lines) + } Item::List { start: None, items } => { viewer.unordered_list(settings, items) } @@ -1157,6 +1171,7 @@ where /// Displays a code block using the default look. pub fn code_block<'a, Message, Theme, Renderer>( settings: Settings, + _code: &'a str, lines: &'a [Text], on_link_clicked: impl Fn(Url) -> Message + Clone + 'a, ) -> Element<'a, Message, Theme, Renderer> @@ -1251,9 +1266,10 @@ where fn code_block( &self, settings: Settings, + code: &'a str, lines: &'a [Text], ) -> Element<'a, Message, Theme, Renderer> { - code_block(settings, lines, Self::on_link_clicked) + code_block(settings, code, lines, Self::on_link_clicked) } /// Displays an unordered list.