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 zcmd^DO;B6c6+TzL{8@kj;aCp2@-rBtCxI~#Vq=@=Nst65kui28r)l(%gunr$fc%#x zjq}$!r0LJ5i>5Q}&Yeyd_u)2i`ZJU1OlGod7yVhaooN>KqBHKIi_Elkzw@3%z_HzF zw|ySH`<-*Xd+#~to_p`ZB#5Y)E|HtclP6BbKzBAnMI^;iMj*fC^wNkj0zewc2345%(QYe4*{X_2)1rw0JPaJy# z4bTyCB_@u|P+-xlEYnW%K+g}`H)FB5P&BEoY7wxpRX`)iSL3^{i*J*c$n}!zm(YE~ zX~kb?zi>fIUN^LNc~W!9JbiTY#%dAi_+frAWBW<; z6tv9N(Kgt65AC)&9@gLd`1bAE8?xg$3HJN{{b{lju&bH|J6D`3$bz$d`;7a#dzv0X zHoNHnjZt}gNyCaiDC%jibossRV@SD=+`eKHI@f!tzKDK)kJn#Bo*U{s4Mo(5tFg{i z+$I`@XnY1uqj;VmJUuidXlQI`?9d}4!vp;>eP3_)p55V{oq?u$A3Y?x0={5G3*f3hljNeu(a0j@X&~8Wnr(@>R#{c5;FL& z8)$C*7O!B-r_Ce5>p(r>$tzcGy?5oxlKOpD=MB*nboKn9tMj_+W$;hi-F_&8;(TzV z`O}u541Umg{)%|z%CFUp8$k^A-nW7yx1MsnaXl!-t?zNR>|d#P@XyaTkNtz1-Pbv% zfBocve6=lXWE1-to56X|&#rlL_;~Y3n;#cj94pj(1~9`#hwa_KXcxr^Cjq?{a8{FO zJN;>6oZ)H@?iZlps@_F@GOE}O{S#H}0Zv!3ml|oViha<(Sj7z#p;xQ8k=p3Ty!$-$ znCA@rs)_~e5__xIMFC+{u^am1RqO#?s$wq%#oJZvgZ>9q+(4(qFRHkadR(nWseEx| zp*UZWy~aK{I50RYXD`YUR13@IqBPG|=1VK9a$L@pmMewD#ZoL+T3Rw!UdWfuFGm-0 zrRCM>Lhbc@zr7|byR!U29qB@@} zuawRda+TP8rBXiF-@n-pq`X9Bx=1UuKt-CT3Q6jPb|2b79LIxLAW5@eNH87N0xgqC zi_n;K7Cq)|%PQn?{N^AnLt6l25jaLUyqECLME@5k59vG@QOwAeWm=_au!|UL(dOKb zc|Y4JjIx5!7VJ?a?Z?~$_nmhVJ(lfWrtNVa{yVX*(m8lE2yF!saa1dI6p|+F(Law* zB7bZx<-NHQ_6SZbXpCCRBt9bC)ZA)?%luV$dk4i0)n>?SHg37vjkwkAHk8Ylxgb@O24I+Z znwMrXnY@`%B9h5eBgn`+;?d%nsPgJEDV3+&L`;4ol~#T&uIjWnRsdbHQRTDKjZDbA z<)4j9HgdQj$9;Y)$|iHl+ZTq>kZW=cb6Er4Ze-|qI-3fa)0woE31_4lKaqwl#CfT@ z5>mX`z4dtT6P34-MQz8dPl)o>k>UGK91`w8qWlSS{PNuEx+YKemxN42?mP2X9t!93;;H5+mYvJxrr7hlvNLPFfD~xGd z`obEz)iCOT?c4=BnXMT_G70}Dq20Pz_gF%Us7*tXQBX(Lg%EZ;)^+qJk% zssPGw8&)nGm)Q??>=4w7>ogvZa|YUB6QHzFyGyrx+&-vVer~&TtB%`;bgQ1*h>mPH`B%OyGOfv&63_8&i7I`Q zF}PuT(J@9h8GAO2D~?g>q?#l5j1xiq%n6m_+#FvR@k#i#8}V_gAwF*RAUzR7h>zP|#K-ME#K&zP;^Q`^%Q1T+_3JXLc4Q?EJ(1;_Fwv?Q*X)3<`Xj0z`*ARg!@5?|rM%7?#iLHPy$rY#e_gP-ikxb)Isd?0|k_Q_(in4?kkupa9ii>lH8L`7le zKz{^FM8R$ulaqWTAhnOLtxal^ID*o+_;BjtMj92Oqa7JOh_frGIzjW`IPJD+Rujcl zqY){r#WX3$)-dLwtuz^P##25mUSms@1bGdS&RUr^mZ96X%n*0f#o`hX>k3 zR{5~nTemBAmWVjFlE=vAHRZ*nio?!hm?7Y72Is?_eN4E9^Iw}VM?)HBm_VFp>{&40 zT_%Aed?fpDh+r|iC^+08cX)ykO}?$z2{+!=ll1esP%LA$Tu6A}?WyKM8$+57ZT+fI1Lw|=YfC(i^CQMtoKT%cIu*j5D#tQ0Ye2MP-#)s*r4Y+H#`t8d9?@D^|ZUy$gR59n9&#^IWG!))cFX2qp&q*A>2P3tt zJO=4dB+P5Zd8tOTk%GsOf(~cfbRg4T z!Z?SWA;024CcNxGCcFafk&W@bYD@NbuR4(FmmJ9S*Uo(u zm(wig6IL(F*XqboP$Cnu_Oj~db(ip8E#sGSGd2?jek=;~T@$z8Ql58l9Vf1O^FB%> F{tYyuYzqJY literal 0 HcmV?d00001 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 { 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.