1use iced_widget::graphics::text::Paragraph;
3use iced_widget::markdown::{
4 Bullet, Catalog, HeadingLevel, Item, Settings, Text, Viewer, view_with,
5};
6pub use iced_widget::markdown::{Content, Uri, parse};
7use iced_widget::{checkbox, column, container, row, scrollable};
8
9use crate::core::Font;
10use crate::core::alignment;
11use crate::core::padding;
12use crate::core::{self, Element, Length, Pixels};
13use crate::{rich_text, text};
14
15fn bullet_items(bullet: &Bullet) -> &[Item] {
16 match bullet {
17 Bullet::Point { items } | Bullet::Task { items, .. } => items,
18 }
19}
20
21pub fn view<'a, Theme, Renderer>(
23 items: impl IntoIterator<Item = &'a Item>,
24 settings: impl Into<Settings>,
25) -> Element<'a, Uri, Theme, Renderer>
26where
27 Theme: Catalog + text::Catalog + 'a,
28 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
29{
30 view_with(items, settings, &SelectableViewer)
31}
32pub fn heading<'a, Message, Theme, Renderer>(
34 settings: Settings,
35 level: &'a HeadingLevel,
36 text: &'a Text,
37 index: usize,
38 on_link_click: impl Fn(Uri) -> Message + 'a,
39) -> Element<'a, Message, Theme, Renderer>
40where
41 Message: 'a,
42 Theme: Catalog + text::Catalog + 'a,
43 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
44{
45 let Settings {
46 h1_size,
47 h2_size,
48 h3_size,
49 h4_size,
50 h5_size,
51 h6_size,
52 text_size,
53 ..
54 } = settings;
55
56 container(
57 rich_text(text.spans(settings.style))
58 .on_link_click(on_link_click)
59 .size(match level {
60 HeadingLevel::H1 => h1_size,
61 HeadingLevel::H2 => h2_size,
62 HeadingLevel::H3 => h3_size,
63 HeadingLevel::H4 => h4_size,
64 HeadingLevel::H5 => h5_size,
65 HeadingLevel::H6 => h6_size,
66 }),
67 )
68 .padding(padding::top(if index > 0 {
69 text_size / 2.0
70 } else {
71 Pixels::ZERO
72 }))
73 .into()
74}
75
76pub fn paragraph<'a, Message, Theme, Renderer>(
78 settings: Settings,
79 text: &Text,
80 on_link_click: impl Fn(Uri) -> Message + 'a,
81) -> Element<'a, Message, Theme, Renderer>
82where
83 Message: 'a,
84 Theme: Catalog + text::Catalog + 'a,
85 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
86{
87 rich_text(text.spans(settings.style))
88 .size(settings.text_size)
89 .on_link_click(on_link_click)
90 .into()
91}
92
93pub fn unordered_list<'a, Message, Theme, Renderer>(
98 viewer: &impl Viewer<'a, Message, Theme, Renderer>,
99 settings: Settings,
100 bullets: &'a [Bullet],
101) -> Element<'a, Message, Theme, Renderer>
102where
103 Message: 'a,
104 Theme: Catalog + text::Catalog + 'a,
105 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
106{
107 column(bullets.iter().map(|bullet| {
108 row![
109 match bullet {
110 Bullet::Point { .. } => {
111 text("•").size(settings.text_size).into()
112 }
113 Bullet::Task { done, .. } => {
114 Element::from(
115 container(checkbox(*done).size(settings.text_size))
116 .center_y(
117 iced_widget::text::LineHeight::default()
118 .to_absolute(settings.text_size),
119 ),
120 )
121 }
122 },
123 view_with(
124 bullet_items(bullet),
125 Settings {
126 spacing: settings.spacing * 0.6,
127 ..settings
128 },
129 viewer,
130 )
131 ]
132 .spacing(settings.spacing)
133 .into()
134 }))
135 .spacing(settings.spacing * 0.75)
136 .padding([0.0, settings.spacing.0])
137 .into()
138}
139
140pub fn ordered_list<'a, Message, Theme, Renderer>(
145 viewer: &impl Viewer<'a, Message, Theme, Renderer>,
146 settings: Settings,
147 start: u64,
148 bullets: &'a [Bullet],
149) -> Element<'a, Message, Theme, Renderer>
150where
151 Message: 'a,
152 Theme: Catalog + text::Catalog + 'a,
153 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
154{
155 let digits = ((start + bullets.len() as u64).max(1) as f32)
156 .log10()
157 .ceil();
158
159 column(bullets.iter().enumerate().map(|(i, bullet)| {
160 row![
161 text!("{}.", i as u64 + start)
162 .size(settings.text_size)
163 .align_x(alignment::Horizontal::Right)
164 .width(settings.text_size * ((digits / 2.0).ceil() + 1.0)),
165 view_with(
166 bullet_items(bullet),
167 Settings {
168 spacing: settings.spacing * 0.6,
169 ..settings
170 },
171 viewer,
172 )
173 ]
174 .spacing(settings.spacing)
175 .into()
176 }))
177 .spacing(settings.spacing * 0.75)
178 .into()
179}
180
181pub fn code_block<'a, Message, Theme, Renderer>(
183 settings: Settings,
184 lines: &'a [Text],
185 on_link_click: impl Fn(Uri) -> Message + Clone + 'a,
186) -> Element<'a, Message, Theme, Renderer>
187where
188 Message: 'a,
189 Theme: Catalog + text::Catalog + 'a,
190 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
191{
192 container(
193 scrollable(
194 container(column(lines.iter().map(|line| {
195 rich_text(line.spans(settings.style))
196 .on_link_click(on_link_click.clone())
197 .font(Font::MONOSPACE)
198 .size(settings.code_size)
199 .into()
200 })))
201 .padding(settings.code_size),
202 )
203 .direction(scrollable::Direction::Horizontal(
204 scrollable::Scrollbar::default()
205 .width(settings.code_size / 2)
206 .scroller_width(settings.code_size / 2),
207 )),
208 )
209 .width(Length::Fill)
210 .padding(settings.code_size / 4)
211 .class(Theme::code_block())
212 .into()
213}
214
215#[derive(Debug, Clone, Copy)]
216struct SelectableViewer;
217
218impl<'a, Theme, Renderer> Viewer<'a, Uri, Theme, Renderer> for SelectableViewer
219where
220 Theme: Catalog + text::Catalog + 'a,
221 Renderer: core::text::Renderer<Paragraph = Paragraph, Font = Font> + 'a,
222{
223 fn on_link_click(url: Uri) -> Uri {
224 url
225 }
226
227 fn heading(
228 &self,
229 settings: Settings,
230 level: &'a HeadingLevel,
231 text: &'a Text,
232 index: usize,
233 ) -> Element<'a, Uri, Theme, Renderer> {
234 heading::<'a, Uri, Theme, Renderer>(
235 settings,
236 level,
237 text,
238 index,
239 |url| url,
240 )
241 }
242
243 fn paragraph(
244 &self,
245 settings: Settings,
246 text: &Text,
247 ) -> Element<'a, Uri, Theme, Renderer> {
248 paragraph(settings, text, |url| url)
249 }
250
251 fn unordered_list(
252 &self,
253 settings: Settings,
254 items: &'a [Bullet],
255 ) -> Element<'a, Uri, Theme, Renderer> {
256 unordered_list(self, settings, items)
257 }
258
259 fn ordered_list(
260 &self,
261 settings: Settings,
262 start: u64,
263 items: &'a [Bullet],
264 ) -> Element<'a, Uri, Theme, Renderer> {
265 ordered_list(self, settings, start, items)
266 }
267
268 fn code_block(
269 &self,
270 settings: Settings,
271 _language: Option<&'a str>,
272 _code: &'a str,
273 lines: &'a [Text],
274 ) -> Element<'a, Uri, Theme, Renderer> {
275 code_block(settings, lines, |url| url)
276 }
277}