Skip to content

Commit 5655998

Browse files
committed
Draft Viewer trait for markdown
1 parent c02ae0c commit 5655998

File tree

9 files changed

+588
-234
lines changed

9 files changed

+588
-234
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/src/padding.rs

+6
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,9 @@ impl From<Padding> for Size {
202202
Self::new(padding.horizontal(), padding.vertical())
203203
}
204204
}
205+
206+
impl From<Pixels> for Padding {
207+
fn from(pixels: Pixels) -> Self {
208+
Self::from(pixels.0)
209+
}
210+
}

examples/changelog/src/main.rs

+11-15
Original file line numberDiff line numberDiff line change
@@ -267,25 +267,21 @@ impl Generator {
267267
} => {
268268
let details = {
269269
let title = rich_text![
270-
span(&pull_request.title).size(24).link(
271-
Message::OpenPullRequest(pull_request.id)
272-
),
270+
span(&pull_request.title)
271+
.size(24)
272+
.link(pull_request.id),
273273
span(format!(" by {}", pull_request.author))
274274
.font(Font {
275275
style: font::Style::Italic,
276276
..Font::default()
277277
}),
278278
]
279+
.on_link_clicked(Message::OpenPullRequest)
279280
.font(Font::MONOSPACE);
280281

281-
let description = markdown::view(
282-
description,
283-
markdown::Settings::default(),
284-
markdown::Style::from_palette(
285-
self.theme().palette(),
286-
),
287-
)
288-
.map(Message::UrlClicked);
282+
let description =
283+
markdown::view(&self.theme(), description)
284+
.map(Message::UrlClicked);
289285

290286
let labels =
291287
row(pull_request.labels.iter().map(|label| {
@@ -349,11 +345,11 @@ impl Generator {
349345
container(
350346
scrollable(
351347
markdown::view(
352-
preview,
353-
markdown::Settings::with_text_size(12),
354-
markdown::Style::from_palette(
355-
self.theme().palette(),
348+
markdown::Settings::with_text_size(
349+
12,
350+
&self.theme(),
356351
),
352+
preview,
357353
)
358354
.map(Message::UrlClicked),
359355
)

examples/markdown/Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ publish = false
77

88
[dependencies]
99
iced.workspace = true
10-
iced.features = ["markdown", "highlighter", "tokio", "debug"]
10+
iced.features = ["markdown", "highlighter", "image", "tokio", "debug"]
11+
12+
reqwest.version = "0.12"
13+
reqwest.features = ["json"]
14+
15+
image.workspace = true
16+
tokio.workspace = true
1117

1218
open = "5.3"

examples/markdown/src/main.rs

+133-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
use iced::highlighter;
22
use iced::time::{self, milliseconds};
33
use iced::widget::{
4-
self, hover, markdown, right, row, scrollable, text_editor, toggler,
4+
self, center_x, horizontal_space, hover, image, markdown, pop, right, row,
5+
scrollable, text_editor, toggler,
56
};
67
use iced::{Element, Fill, Font, Subscription, Task, Theme};
78

9+
use tokio::task;
10+
11+
use std::collections::HashMap;
12+
use std::io;
13+
use std::sync::Arc;
14+
815
pub fn main() -> iced::Result {
916
iced::application("Markdown - Iced", Markdown::update, Markdown::view)
1017
.subscription(Markdown::subscription)
@@ -14,6 +21,7 @@ pub fn main() -> iced::Result {
1421

1522
struct Markdown {
1623
content: text_editor::Content,
24+
images: HashMap<markdown::Url, Image>,
1725
mode: Mode,
1826
theme: Theme,
1927
}
@@ -26,10 +34,19 @@ enum Mode {
2634
},
2735
}
2836

37+
enum Image {
38+
Loading,
39+
Ready(image::Handle),
40+
#[allow(dead_code)]
41+
Errored(Error),
42+
}
43+
2944
#[derive(Debug, Clone)]
3045
enum Message {
3146
Edit(text_editor::Action),
3247
LinkClicked(markdown::Url),
48+
ImageShown(markdown::Url),
49+
ImageDownloaded(markdown::Url, Result<image::Handle, Error>),
3350
ToggleStream(bool),
3451
NextToken,
3552
}
@@ -43,6 +60,7 @@ impl Markdown {
4360
(
4461
Self {
4562
content: text_editor::Content::with_text(INITIAL_CONTENT),
63+
images: HashMap::new(),
4664
mode: Mode::Preview(markdown::parse(INITIAL_CONTENT).collect()),
4765
theme,
4866
},
@@ -70,6 +88,25 @@ impl Markdown {
7088

7189
Task::none()
7290
}
91+
Message::ImageShown(url) => {
92+
if self.images.contains_key(&url) {
93+
return Task::none();
94+
}
95+
96+
let _ = self.images.insert(url.clone(), Image::Loading);
97+
98+
Task::perform(download_image(url.clone()), move |result| {
99+
Message::ImageDownloaded(url.clone(), result)
100+
})
101+
}
102+
Message::ImageDownloaded(url, result) => {
103+
let _ = self.images.insert(
104+
url,
105+
result.map(Image::Ready).unwrap_or_else(Image::Errored),
106+
);
107+
108+
Task::none()
109+
}
73110
Message::ToggleStream(enable_stream) => {
74111
if enable_stream {
75112
self.mode = Mode::Stream {
@@ -126,12 +163,13 @@ impl Markdown {
126163
Mode::Stream { parsed, .. } => parsed.items(),
127164
};
128165

129-
let preview = markdown(
166+
let preview = markdown::view_with(
167+
&MarkdownViewer {
168+
images: &self.images,
169+
},
170+
&self.theme,
130171
items,
131-
markdown::Settings::default(),
132-
markdown::Style::from_palette(self.theme.palette()),
133-
)
134-
.map(Message::LinkClicked);
172+
);
135173

136174
row![
137175
editor,
@@ -167,3 +205,92 @@ impl Markdown {
167205
}
168206
}
169207
}
208+
209+
struct MarkdownViewer<'a> {
210+
images: &'a HashMap<markdown::Url, Image>,
211+
}
212+
213+
impl<'a> markdown::Viewer<'a, Message> for MarkdownViewer<'a> {
214+
fn on_link_clicked(url: markdown::Url) -> Message {
215+
Message::LinkClicked(url)
216+
}
217+
218+
fn image(
219+
&self,
220+
_settings: markdown::Settings,
221+
_title: &markdown::Text,
222+
url: &'a markdown::Url,
223+
) -> Element<'a, Message> {
224+
if let Some(Image::Ready(handle)) = self.images.get(url) {
225+
center_x(image(handle)).into()
226+
} else {
227+
pop(horizontal_space().width(0))
228+
.key(url.as_str())
229+
.on_show(|_size| Message::ImageShown(url.clone()))
230+
.into()
231+
}
232+
}
233+
}
234+
235+
async fn download_image(url: markdown::Url) -> Result<image::Handle, Error> {
236+
use std::io;
237+
use tokio::task;
238+
239+
let client = reqwest::Client::new();
240+
241+
let bytes = client
242+
.get(url)
243+
.send()
244+
.await?
245+
.error_for_status()?
246+
.bytes()
247+
.await?;
248+
249+
let image = task::spawn_blocking(move || {
250+
Ok::<_, Error>(
251+
::image::ImageReader::new(io::Cursor::new(bytes))
252+
.with_guessed_format()?
253+
.decode()?
254+
.to_rgba8(),
255+
)
256+
})
257+
.await??;
258+
259+
Ok(image::Handle::from_rgba(
260+
image.width(),
261+
image.height(),
262+
image.into_raw(),
263+
))
264+
}
265+
266+
#[derive(Debug, Clone)]
267+
pub enum Error {
268+
RequestFailed(Arc<reqwest::Error>),
269+
IOFailed(Arc<io::Error>),
270+
JoinFailed(Arc<task::JoinError>),
271+
ImageDecodingFailed(Arc<::image::ImageError>),
272+
}
273+
274+
impl From<reqwest::Error> for Error {
275+
fn from(error: reqwest::Error) -> Self {
276+
Self::RequestFailed(Arc::new(error))
277+
}
278+
}
279+
280+
impl From<io::Error> for Error {
281+
fn from(error: io::Error) -> Self {
282+
Self::IOFailed(Arc::new(error))
283+
}
284+
}
285+
286+
impl From<task::JoinError> for Error {
287+
fn from(error: task::JoinError) -> Self {
288+
Self::JoinFailed(Arc::new(error))
289+
}
290+
}
291+
292+
impl From<::image::ImageError> for Error {
293+
fn from(error: ::image::ImageError) -> Self {
294+
Self::ImageDecodingFailed(Arc::new(error))
295+
}
296+
}

widget/src/helpers.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ macro_rules! text {
187187
#[macro_export]
188188
macro_rules! rich_text {
189189
() => (
190-
$crate::Column::new()
190+
$crate::text::Rich::new()
191191
);
192192
($($x:expr),+ $(,)?) => (
193193
$crate::text::Rich::from_iter([$($crate::text::Span::from($x)),+])
@@ -1155,9 +1155,9 @@ where
11551155
/// .into()
11561156
/// }
11571157
/// ```
1158-
pub fn rich_text<'a, Link, Theme, Renderer>(
1158+
pub fn rich_text<'a, Link, Message, Theme, Renderer>(
11591159
spans: impl AsRef<[text::Span<'a, Link, Renderer::Font>]> + 'a,
1160-
) -> text::Rich<'a, Link, Theme, Renderer>
1160+
) -> text::Rich<'a, Link, Message, Theme, Renderer>
11611161
where
11621162
Link: Clone + 'static,
11631163
Theme: text::Catalog + 'a,

0 commit comments

Comments
 (0)