zng_wgt_markdown/
view_fn.rs

1use std::num::NonZeroU32;
2
3pub use pulldown_cmark::HeadingLevel;
4use zng_ext_font::*;
5use zng_ext_image::ImageSource;
6use zng_ext_input::gesture::ClickArgs;
7use zng_wgt::*;
8use zng_wgt_access::{self as access, AccessRole, access_role};
9use zng_wgt_button::{Button, LinkStyle};
10use zng_wgt_container::{Container, child_align, padding};
11use zng_wgt_fill::background_color;
12use zng_wgt_filter::opacity;
13use zng_wgt_grid::{self as grid, Grid};
14use zng_wgt_size_offset::{offset, size};
15use zng_wgt_stack::{Stack, StackDirection};
16use zng_wgt_text::{FONT_COLOR_VAR, PARAGRAPH_SPACING_VAR, Text, font_size, font_weight};
17use zng_wgt_tooltip::*;
18use zng_wgt_transform::scale;
19use zng_wgt_wrap::Wrap;
20
21use super::*;
22
23/// Markdown text run style.
24#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
25pub struct MarkdownStyle {
26    /// Bold.
27    pub strong: bool,
28    /// Italic.
29    pub emphasis: bool,
30    /// Strikethrough.
31    pub strikethrough: bool,
32}
33
34/// Arguments for a markdown text view.
35///
36/// The text can be inside a paragraph, heading, list item or any other markdown block item.
37///
38/// See [`TEXT_FN_VAR`] for more details.
39pub struct TextFnArgs {
40    /// The text run.
41    pub txt: Txt,
42    /// The style.
43    pub style: MarkdownStyle,
44}
45
46/// Arguments for a markdown inlined link view.
47///
48/// See [`LINK_FN_VAR`] for more details.
49pub struct LinkFnArgs {
50    /// The link.
51    pub url: Txt,
52
53    /// Link title, usually displayed as a tooltip.
54    pub title: Txt,
55
56    /// Inline items.
57    pub items: UiVec,
58}
59
60/// Arguments for a markdown inlined code text view.
61///
62/// The text can be inside a paragraph, heading, list item or any other markdown block item.
63///
64/// See [`CODE_INLINE_FN_VAR`] for more details.
65pub struct CodeInlineFnArgs {
66    /// The code text run.
67    pub txt: Txt,
68    /// The style.
69    pub style: MarkdownStyle,
70}
71
72/// Arguments for a markdown code block view.
73///
74/// See [`CODE_BLOCK_FN_VAR`] for more details.
75pub struct CodeBlockFnArgs {
76    /// Code language, can be empty.
77    pub lang: Txt,
78    /// Raw text.
79    pub txt: Txt,
80}
81
82/// Arguments for a markdown paragraph view.
83///
84/// See [`PARAGRAPH_FN_VAR`] for more details.
85pub struct ParagraphFnArgs {
86    /// Zero-sized index of the paragraph.
87    pub index: u32,
88    /// Inline items.
89    pub items: UiVec,
90}
91
92/// Arguments for a markdown heading view.
93pub struct HeadingFnArgs {
94    /// Level.
95    pub level: HeadingLevel,
96
97    /// Anchor label that identifies the header in the markdown context.
98    pub anchor: Txt,
99
100    /// Inline items.
101    pub items: UiVec,
102}
103
104/// Arguments for a markdown list view.
105pub struct ListFnArgs {
106    /// Nested list depth, starting from zero for the outer-list.
107    pub depth: u32,
108
109    /// If the list is *ordered*, the first item number.
110    pub first_num: Option<u64>,
111
112    /// List items.
113    ///
114    /// Each two items are the bullet or number followed by the item.
115    pub items: UiVec,
116}
117
118/// Arguments for a markdown list item bullet, check mark or number.
119#[derive(Clone, Copy)]
120pub struct ListItemBulletFnArgs {
121    /// Nested list depth, starting from zero for items in the outer-list.
122    pub depth: u32,
123
124    /// If the list is *ordered*, the item number.
125    pub num: Option<u64>,
126
127    /// If the list is checked. `Some(true)` is `[x]` and `Some(false)` is `[ ]`.
128    pub checked: Option<bool>,
129}
130
131/// Arguments for a markdown list item view.
132pub struct ListItemFnArgs {
133    /// Copy of the bullet args.
134    pub bullet: ListItemBulletFnArgs,
135
136    /// Inline items of the list item.
137    pub items: UiVec,
138
139    /// Inner block items, paragraphs and nested lists.
140    pub blocks: UiVec,
141}
142
143/// Arguments for a markdown definition list.
144pub struct DefListArgs {
145    /// List items.
146    ///
147    /// Each two items are the title and definition.
148    pub items: UiVec,
149}
150
151/// Arguments for a markdown definition list item title.
152pub struct DefListItemTitleArgs {
153    /// Inline items of the title.
154    pub items: UiVec,
155}
156
157/// Arguments for a markdown definition list item description.
158pub struct DefListItemDefinitionArgs {
159    /// Inline items of the description.
160    pub items: UiVec,
161}
162
163/// Arguments for a markdown image view.
164pub struct ImageFnArgs {
165    /// Image, resolved by the [`image_resolver`].
166    ///
167    /// [`image_resolver`]: fn@crate::image_resolver
168    pub source: ImageSource,
169    /// Image title, usually displayed as a tooltip.
170    pub title: Txt,
171    /// Items to display when the image does not load and for screen readers.
172    pub alt_items: UiVec,
173    /// Alt items in text form.
174    pub alt_txt: Txt,
175}
176
177/// Arguments for a markdown rule view.
178///
179/// Currently no args.
180pub struct RuleFnArgs {}
181
182/// Arguments for a markdown block quote view.
183pub struct BlockQuoteFnArgs {
184    /// Number of *parent* quotes in case of nesting.
185    ///
186    /// > 0
187    /// >> 1
188    /// >>> 2
189    pub level: u32,
190
191    /// Block items.
192    pub items: UiVec,
193}
194
195/// Arguments for a markdown footnote reference view.
196pub struct FootnoteRefFnArgs {
197    /// Footnote referenced.
198    pub label: Txt,
199}
200
201/// Arguments for a markdown footnote definition view.
202///
203/// See [`PARAGRAPH_FN_VAR`] for more details.
204pub struct FootnoteDefFnArgs {
205    /// Identifier label.
206    pub label: Txt,
207    /// Block items.
208    pub items: UiVec,
209}
210
211/// Arguments for a markdown table view.
212///
213/// See [`TABLE_FN_VAR`] for more details.
214pub struct TableFnArgs {
215    /// Column definitions with align.
216    pub columns: Vec<Align>,
217    /// Cell items.
218    pub cells: UiVec,
219}
220
221/// Arguments for a markdown table cell view.
222///
223/// See [`TABLE_CELL_FN_VAR`] for more details.
224pub struct TableCellFnArgs {
225    /// If the cell is inside the header row.
226    pub is_heading: bool,
227
228    /// Column align.
229    pub col_align: Align,
230
231    /// Inline items.
232    pub items: UiVec,
233}
234
235/// Arguments for a markdown panel.
236///
237/// See [`PANEL_FN_VAR`] for more details.
238pub struct PanelFnArgs {
239    /// Block items.
240    pub items: UiVec,
241}
242
243context_var! {
244    /// Widget function for a markdown text segment.
245    pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = WidgetFn::new(default_text_fn);
246
247    /// Widget function for a markdown link segment.
248    pub static LINK_FN_VAR: WidgetFn<LinkFnArgs> = WidgetFn::new(default_link_fn);
249
250    /// Widget function for a markdown inline code segment.
251    pub static CODE_INLINE_FN_VAR: WidgetFn<CodeInlineFnArgs> = WidgetFn::new(default_code_inline_fn);
252
253    /// Widget function for a markdown code block segment.
254    pub static CODE_BLOCK_FN_VAR: WidgetFn<CodeBlockFnArgs> = WidgetFn::new(default_code_block_fn);
255
256    /// Widget function for a markdown paragraph.
257    pub static PARAGRAPH_FN_VAR: WidgetFn<ParagraphFnArgs> = WidgetFn::new(default_paragraph_fn);
258
259    /// Widget function for a markdown heading.
260    pub static HEADING_FN_VAR: WidgetFn<HeadingFnArgs> = WidgetFn::new(default_heading_fn);
261
262    /// Widget function for a markdown list.
263    pub static LIST_FN_VAR: WidgetFn<ListFnArgs> = WidgetFn::new(default_list_fn);
264
265    /// Widget function for a markdown list item bullet, check mark or number.
266    pub static LIST_ITEM_BULLET_FN_VAR: WidgetFn<ListItemBulletFnArgs> = WidgetFn::new(default_list_item_bullet_fn);
267
268    /// Widget function for a markdown list item content.
269    pub static LIST_ITEM_FN_VAR: WidgetFn<ListItemFnArgs> = WidgetFn::new(default_list_item_fn);
270
271    /// Widget function for a markdown definition list.
272    pub static DEF_LIST_FN_VAR: WidgetFn<DefListArgs> = WidgetFn::new(default_def_list_fn);
273
274    /// Widget function for a markdown definition list item title.
275    pub static DEF_LIST_ITEM_TITLE_FN_VAR: WidgetFn<DefListItemTitleArgs> = WidgetFn::new(default_def_list_item_title_fn);
276
277    /// Widget function for a markdown definition list item description.
278    pub static DEF_LIST_ITEM_DEFINITION_FN_VAR: WidgetFn<DefListItemDefinitionArgs> = WidgetFn::new(default_def_list_item_definition_fn);
279
280    /// Widget function for a markdown image.
281    pub static IMAGE_FN_VAR: WidgetFn<ImageFnArgs> = WidgetFn::new(default_image_fn);
282
283    /// Widget function for a markdown rule line.
284    pub static RULE_FN_VAR: WidgetFn<RuleFnArgs> = WidgetFn::new(default_rule_fn);
285
286    /// Widget function for a markdown block quote.
287    pub static BLOCK_QUOTE_FN_VAR: WidgetFn<BlockQuoteFnArgs> = WidgetFn::new(default_block_quote_fn);
288
289    /// Widget function for an inline reference to a footnote.
290    pub static FOOTNOTE_REF_FN_VAR: WidgetFn<FootnoteRefFnArgs> = WidgetFn::new(default_footnote_ref_fn);
291
292    /// Widget function for a footnote definition block.
293    pub static FOOTNOTE_DEF_FN_VAR: WidgetFn<FootnoteDefFnArgs> = WidgetFn::new(default_footnote_def_fn);
294
295    /// Widget function for a markdown table.
296    pub static TABLE_FN_VAR: WidgetFn<TableFnArgs> = WidgetFn::new(default_table_fn);
297
298    /// Widget function for a markdown table body cell.
299    pub static TABLE_CELL_FN_VAR: WidgetFn<TableCellFnArgs> = WidgetFn::new(default_table_cell_fn);
300
301    /// Widget function for a markdown panel.
302    pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = WidgetFn::new(default_panel_fn);
303}
304
305/// Widget function that converts [`TextFnArgs`] to widgets.
306///
307/// Sets the [`TEXT_FN_VAR`].
308#[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(Markdown))]
309pub fn text_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> impl UiNode {
310    with_context_var(child, TEXT_FN_VAR, wgt_fn)
311}
312
313/// Widget function that converts [`LinkFnArgs`] to widgets.
314///
315/// Sets the [`LINK_FN_VAR`].
316#[property(CONTEXT, default(LINK_FN_VAR), widget_impl(Markdown))]
317pub fn link_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<LinkFnArgs>>) -> impl UiNode {
318    with_context_var(child, LINK_FN_VAR, wgt_fn)
319}
320
321/// Widget function that converts [`CodeInlineFnArgs`] to widgets.
322///
323/// Sets the [`CODE_INLINE_FN_VAR`].
324#[property(CONTEXT, default(CODE_INLINE_FN_VAR), widget_impl(Markdown))]
325pub fn code_inline_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<CodeInlineFnArgs>>) -> impl UiNode {
326    with_context_var(child, CODE_INLINE_FN_VAR, wgt_fn)
327}
328
329/// Widget function that converts [`CodeBlockFnArgs`] to widgets.
330///
331/// Sets the [`CODE_BLOCK_FN_VAR`].
332#[property(CONTEXT, default(CODE_BLOCK_FN_VAR), widget_impl(Markdown))]
333pub fn code_block_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<CodeBlockFnArgs>>) -> impl UiNode {
334    with_context_var(child, CODE_BLOCK_FN_VAR, wgt_fn)
335}
336
337/// Widget function that converts [`ParagraphFnArgs`] to widgets.
338///
339/// Sets the [`PARAGRAPH_FN_VAR`].
340#[property(CONTEXT, default(PARAGRAPH_FN_VAR), widget_impl(Markdown))]
341pub fn paragraph_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ParagraphFnArgs>>) -> impl UiNode {
342    with_context_var(child, PARAGRAPH_FN_VAR, wgt_fn)
343}
344
345/// Widget function that converts [`HeadingFnArgs`] to widgets.
346///
347/// Sets the [`HEADING_FN_VAR`].
348#[property(CONTEXT, default(HEADING_FN_VAR), widget_impl(Markdown))]
349pub fn heading_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<HeadingFnArgs>>) -> impl UiNode {
350    with_context_var(child, HEADING_FN_VAR, wgt_fn)
351}
352
353/// Widget function that converts [`ListFnArgs`] to widgets.
354///
355/// Sets the [`LIST_FN_VAR`].
356#[property(CONTEXT, default(LIST_FN_VAR), widget_impl(Markdown))]
357pub fn list_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ListFnArgs>>) -> impl UiNode {
358    with_context_var(child, LIST_FN_VAR, wgt_fn)
359}
360
361/// Widget function that converts [`DefListArgs`] to widgets.
362///
363/// Sets the [`DEF_LIST_FN_VAR`].
364#[property(CONTEXT, default(DEF_LIST_FN_VAR), widget_impl(Markdown))]
365pub fn def_list_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<DefListArgs>>) -> impl UiNode {
366    with_context_var(child, DEF_LIST_FN_VAR, wgt_fn)
367}
368
369/// Widget function that converts [`DefListItemTitleArgs`] to widgets.
370///
371/// Sets the [`DEF_LIST_ITEM_TITLE_FN_VAR`].
372#[property(CONTEXT, default(DEF_LIST_ITEM_TITLE_FN_VAR), widget_impl(Markdown))]
373pub fn def_list_item_title_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<DefListItemTitleArgs>>) -> impl UiNode {
374    with_context_var(child, DEF_LIST_ITEM_TITLE_FN_VAR, wgt_fn)
375}
376
377/// Widget function that converts [`DefListItemDefinitionArgs`] to widgets.
378///
379/// Sets the [`DEF_LIST_ITEM_DEFINITION_FN_VAR`].
380#[property(CONTEXT, default(DEF_LIST_ITEM_DEFINITION_FN_VAR), widget_impl(Markdown))]
381pub fn def_list_item_definition_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<DefListItemDefinitionArgs>>) -> impl UiNode {
382    with_context_var(child, DEF_LIST_ITEM_DEFINITION_FN_VAR, wgt_fn)
383}
384
385/// Widget function that converts [`ListItemBulletFnArgs`] to widgets.
386///
387/// Sets the [`LIST_ITEM_BULLET_FN_VAR`].
388#[property(CONTEXT, default(LIST_ITEM_BULLET_FN_VAR), widget_impl(Markdown))]
389pub fn list_item_bullet_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ListItemBulletFnArgs>>) -> impl UiNode {
390    with_context_var(child, LIST_ITEM_BULLET_FN_VAR, wgt_fn)
391}
392
393/// Widget function that converts [`ListItemFnArgs`] to widgets.
394///
395/// Sets the [`LIST_ITEM_FN_VAR`].
396#[property(CONTEXT, default(LIST_ITEM_FN_VAR), widget_impl(Markdown))]
397pub fn list_item_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ListItemFnArgs>>) -> impl UiNode {
398    with_context_var(child, LIST_ITEM_FN_VAR, wgt_fn)
399}
400
401/// Widget function that converts [`ImageFnArgs`] to widgets.
402///
403/// Sets the [`IMAGE_FN_VAR`].
404#[property(CONTEXT, default(IMAGE_FN_VAR), widget_impl(Markdown))]
405pub fn image_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ImageFnArgs>>) -> impl UiNode {
406    with_context_var(child, IMAGE_FN_VAR, wgt_fn)
407}
408
409/// Widget function that converts [`RuleFnArgs`] to widgets.
410///
411/// Sets the [`RULE_FN_VAR`].
412#[property(CONTEXT, default(RULE_FN_VAR), widget_impl(Markdown))]
413pub fn rule_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<RuleFnArgs>>) -> impl UiNode {
414    with_context_var(child, RULE_FN_VAR, wgt_fn)
415}
416
417/// Widget function that converts [`BlockQuoteFnArgs`] to widgets.
418///
419/// Sets the [`BLOCK_QUOTE_FN_VAR`].
420#[property(CONTEXT, default(BLOCK_QUOTE_FN_VAR), widget_impl(Markdown))]
421pub fn block_quote_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<BlockQuoteFnArgs>>) -> impl UiNode {
422    with_context_var(child, BLOCK_QUOTE_FN_VAR, wgt_fn)
423}
424
425/// Widget function that converts [`FootnoteRefFnArgs`] to widgets.
426///
427/// Sets the [`FOOTNOTE_REF_FN_VAR`].
428#[property(CONTEXT, default(FOOTNOTE_REF_FN_VAR), widget_impl(Markdown))]
429pub fn footnote_ref_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<FootnoteRefFnArgs>>) -> impl UiNode {
430    with_context_var(child, FOOTNOTE_REF_FN_VAR, wgt_fn)
431}
432
433/// Widget function that converts [`FootnoteDefFnArgs`] to widgets.
434///
435/// Sets the [`FOOTNOTE_DEF_FN_VAR`].
436#[property(CONTEXT, default(FOOTNOTE_DEF_FN_VAR), widget_impl(Markdown))]
437pub fn footnote_def_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<FootnoteDefFnArgs>>) -> impl UiNode {
438    with_context_var(child, FOOTNOTE_DEF_FN_VAR, wgt_fn)
439}
440
441/// Widget function that converts [`TableFnArgs`] to widgets.
442///
443/// Sets the [`TABLE_FN_VAR`].
444#[property(CONTEXT, default(TABLE_FN_VAR), widget_impl(Markdown))]
445pub fn table_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<TableFnArgs>>) -> impl UiNode {
446    with_context_var(child, TABLE_FN_VAR, wgt_fn)
447}
448
449/// Widget function that converts [`PanelFnArgs`] to a widget.
450///
451/// This generates the panel that contains all markdown blocks, it is the child of the [`Markdown!`] widget.
452///
453/// Sets the [`PANEL_FN_VAR`].
454///
455/// [`Markdown!`]: struct@crate::Markdown
456#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(Markdown))]
457pub fn panel_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> impl UiNode {
458    with_context_var(child, PANEL_FN_VAR, wgt_fn)
459}
460
461fn text_view_builder(txt: Txt, style: MarkdownStyle) -> Text {
462    let mut builder = Text::widget_new();
463
464    widget_set! {
465        &mut builder;
466        txt;
467        // white_space = WhiteSpace::Merge;
468    }
469
470    if style.strong {
471        widget_set! {
472            &mut builder;
473            font_weight = FontWeight::BOLD;
474        }
475    }
476    if style.emphasis {
477        widget_set! {
478            &mut builder;
479            font_style = FontStyle::Italic;
480        }
481    }
482    if style.strikethrough {
483        widget_set! {
484            &mut builder;
485            strikethrough = 1, LineStyle::Solid;
486        }
487    }
488
489    builder
490}
491
492/// Default text view.
493///
494/// See [`TEXT_FN_VAR`] for more details.
495pub fn default_text_fn(args: TextFnArgs) -> impl UiNode {
496    let mut builder = text_view_builder(args.txt, args.style);
497    builder.widget_build()
498}
499
500/// Default inlined code text view.
501///
502/// See [`CODE_INLINE_FN_VAR`] for more details.
503pub fn default_code_inline_fn(args: CodeInlineFnArgs) -> impl UiNode {
504    let mut builder = text_view_builder(args.txt, args.style);
505
506    widget_set! {
507        &mut builder;
508        font_family = ["JetBrains Mono", "Consolas", "monospace"];
509        background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
510    }
511
512    builder.widget_build()
513}
514
515/// Default inlined link view.
516///
517/// See [`LINK_FN_VAR`] for more details.
518pub fn default_link_fn(args: LinkFnArgs) -> impl UiNode {
519    if args.items.is_empty() {
520        NilUiNode.boxed()
521    } else {
522        let url = args.url;
523
524        let mut items = args.items;
525        let items = if items.len() == 1 {
526            items.remove(0)
527        } else {
528            Wrap! {
529                children = items;
530            }
531            .boxed()
532        };
533
534        Button! {
535            style_fn = LinkStyle!();
536            child = items;
537
538            on_click = hn!(|args: &ClickArgs| {
539                args.propagation().stop();
540
541                let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
542                LINK_EVENT.notify(LinkArgs::now(url.clone(), link));
543            });
544        }
545        .boxed()
546    }
547}
548
549/// Default code block view.
550///
551/// Is [`AnsiText!`] for the `ansi` and `console` languages, and only raw text for the rest.
552///
553/// See [`CODE_BLOCK_FN_VAR`] for more details.
554///
555/// [`AnsiText!`]: struct@zng_wgt_ansi_text::AnsiText
556pub fn default_code_block_fn(args: CodeBlockFnArgs) -> impl UiNode {
557    if ["ansi", "console"].contains(&args.lang.as_str()) {
558        zng_wgt_ansi_text::AnsiText! {
559            txt = args.txt;
560            padding = 6;
561            corner_radius = 4;
562            background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
563        }
564        .boxed()
565    } else {
566        Text! {
567            txt = args.txt;
568            padding = 6;
569            corner_radius = 4;
570            font_family = ["JetBrains Mono", "Consolas", "monospace"];
571            background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
572        }
573        .boxed()
574    }
575}
576
577/// Default paragraph view.
578///
579/// See [`PARAGRAPH_FN_VAR`] for more details.
580pub fn default_paragraph_fn(mut args: ParagraphFnArgs) -> impl UiNode {
581    if args.items.is_empty() {
582        NilUiNode.boxed()
583    } else if args.items.len() == 1 {
584        args.items.remove(0)
585    } else {
586        Wrap! {
587            children = args.items;
588        }
589        .boxed()
590    }
591}
592
593/// Default heading view.
594///
595/// See [`HEADING_FN_VAR`] for more details.
596pub fn default_heading_fn(args: HeadingFnArgs) -> impl UiNode {
597    if args.items.is_empty() {
598        NilUiNode.boxed()
599    } else {
600        Wrap! {
601            access_role = AccessRole::Heading;
602            access::level = NonZeroU32::new(args.level as _).unwrap();
603            font_size = match args.level {
604                HeadingLevel::H1 => 2.em(),
605                HeadingLevel::H2 => 1.5.em(),
606                HeadingLevel::H3 => 1.4.em(),
607                HeadingLevel::H4 => 1.3.em(),
608                HeadingLevel::H5 => 1.2.em(),
609                HeadingLevel::H6 => 1.1.em(),
610            };
611            children = args.items;
612            anchor = args.anchor;
613        }
614        .boxed()
615    }
616}
617
618/// Default list view.
619///
620/// Uses a [`Grid!`] with two columns, one default for the bullet or number, the other fills the leftover space.
621///
622/// See [`LIST_FN_VAR`] for more details.
623///
624/// [`Grid!`]: struct@Grid
625pub fn default_list_fn(args: ListFnArgs) -> impl UiNode {
626    if args.items.is_empty() {
627        NilUiNode.boxed()
628    } else {
629        Grid! {
630            grid::cell::at = grid::cell::AT_AUTO; // in case it is nested
631
632            access_role = AccessRole::List;
633            margin = (0, 0, 0, 1.em());
634            cells = args.items;
635            columns = ui_vec![
636                grid::Column!(),
637                grid::Column! {
638                    width = 1.lft()
639                },
640            ];
641        }
642        .boxed()
643    }
644}
645
646/// Default definition list view.
647///
648/// Is a simple vertical [`Stack!`].
649///
650/// [`Stack!`]: struct@Stack
651pub fn default_def_list_fn(args: DefListArgs) -> impl UiNode {
652    if args.items.is_empty() {
653        NilUiNode.boxed()
654    } else {
655        Stack! {
656            access_role = AccessRole::List;
657            direction = StackDirection::top_to_bottom();
658            spacing = PARAGRAPH_SPACING_VAR;
659            children = args.items;
660        }
661        .boxed()
662    }
663}
664
665/// Default definition list item title view.
666///
667/// Is a [`Wrap!`] with bold text.
668///
669/// [`Wrap!`]: struct@Wrap
670pub fn default_def_list_item_title_fn(args: DefListItemTitleArgs) -> impl UiNode {
671    if args.items.is_empty() {
672        NilUiNode.boxed()
673    } else {
674        Wrap! {
675            access_role = AccessRole::Term;
676            children = args.items;
677            font_weight = FontWeight::BOLD;
678        }
679        .boxed()
680    }
681}
682
683/// Default definition list item description view.
684///
685/// Is a [`Wrap!`].
686///
687/// [`Wrap!`]: struct@Wrap
688pub fn default_def_list_item_definition_fn(args: DefListItemDefinitionArgs) -> impl UiNode {
689    if args.items.is_empty() {
690        NilUiNode.boxed()
691    } else {
692        Wrap! {
693            access_role = AccessRole::Definition;
694            children = args.items;
695            margin = (0, 2.em());
696        }
697        .boxed()
698    }
699}
700
701/// Default list item bullet, check mark or number view.
702///
703/// See [`LIST_ITEM_BULLET_FN_VAR`] for more details.
704pub fn default_list_item_bullet_fn(args: ListItemBulletFnArgs) -> impl UiNode {
705    if let Some(checked) = args.checked {
706        Text! {
707            grid::cell::at = grid::cell::AT_AUTO;
708            align = Align::TOP;
709            txt = " ✓ ";
710            font_color = FONT_COLOR_VAR.map(move |c| if checked { *c } else { c.transparent() });
711            background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()));
712            corner_radius = 4;
713            scale = 0.8.fct();
714            offset = (-(0.1.fct()), 0);
715        }
716        .boxed()
717    } else if let Some(n) = args.num {
718        Text! {
719            grid::cell::at = grid::cell::AT_AUTO;
720            txt = formatx!("{n}. ");
721            align = Align::RIGHT;
722        }
723        .boxed()
724    } else {
725        match args.depth {
726            0 => Wgt! {
727                grid::cell::at = grid::cell::AT_AUTO;
728                align = Align::TOP;
729                size = (5, 5);
730                corner_radius = 5;
731                margin = (0.6.em(), 0.5.em(), 0, 0);
732                background_color = FONT_COLOR_VAR;
733            },
734            1 => Wgt! {
735                grid::cell::at = grid::cell::AT_AUTO;
736                align = Align::TOP;
737                size = (5, 5);
738                corner_radius = 5;
739                margin = (0.6.em(), 0.5.em(), 0, 0);
740                border = 1.px(), FONT_COLOR_VAR.map_into();
741            },
742            _ => Wgt! {
743                grid::cell::at = grid::cell::AT_AUTO;
744                align = Align::TOP;
745                size = (5, 5);
746                margin = (0.6.em(), 0.5.em(), 0, 0);
747                background_color = FONT_COLOR_VAR;
748            },
749        }
750        .boxed()
751    }
752}
753
754/// Default list item view.
755///
756/// See [`LIST_ITEM_FN_VAR`] for more details.
757pub fn default_list_item_fn(args: ListItemFnArgs) -> impl UiNode {
758    let mut blocks = args.blocks;
759    let mut items = args.items;
760
761    if items.is_empty() {
762        if blocks.is_empty() {
763            return NilUiNode.boxed();
764        }
765    } else {
766        let r = if items.len() == 1 { items.remove(0) } else { Wrap!(items).boxed() };
767        blocks.insert(0, r);
768    }
769
770    if blocks.len() > 1 {
771        Stack! {
772            access_role = AccessRole::ListItem;
773            grid::cell::at = grid::cell::AT_AUTO;
774            direction = StackDirection::top_to_bottom();
775            children = blocks;
776        }
777        .boxed()
778    } else {
779        Container! {
780            access_role = AccessRole::ListItem;
781            grid::cell::at = grid::cell::AT_AUTO;
782            child = blocks.remove(0);
783        }
784        .boxed()
785    }
786}
787
788/// Default image view.
789///
790/// See [`IMAGE_FN_VAR`] for more details.
791pub fn default_image_fn(args: ImageFnArgs) -> impl UiNode {
792    let tooltip_fn = if args.title.is_empty() {
793        wgt_fn!()
794    } else {
795        let title = args.title;
796        wgt_fn!(|_| Tip!(Text!(title.clone())))
797    };
798
799    let alt_txt = args.alt_txt;
800    let mut alt_items = args.alt_items;
801    if alt_items.is_empty() {
802        zng_wgt_image::Image! {
803            align = Align::TOP_LEFT;
804            tooltip_fn;
805            access::label = alt_txt;
806            source = args.source;
807        }
808    } else {
809        let alt_items = if alt_items.len() == 1 {
810            alt_items.remove(0)
811        } else {
812            Wrap! {
813                children = alt_items;
814            }
815            .boxed()
816        };
817        let alt_items = ArcNode::new(alt_items);
818        zng_wgt_image::Image! {
819            align = Align::TOP_LEFT;
820            source = args.source;
821            tooltip_fn;
822            zng_wgt_access::label = alt_txt;
823            img_error_fn = wgt_fn!(|_| { alt_items.take_on_init() });
824        }
825    }
826}
827
828/// Default rule view.
829///
830/// See [`RULE_FN_VAR`] for more details.
831pub fn default_rule_fn(_: RuleFnArgs) -> impl UiNode {
832    zng_wgt_rule_line::hr::Hr! {
833        opacity = 50.pct();
834    }
835}
836
837/// Default block quote view.
838///
839/// See [`BLOCK_QUOTE_FN_VAR`] for more details.
840pub fn default_block_quote_fn(args: BlockQuoteFnArgs) -> impl UiNode {
841    if args.items.is_empty() {
842        NilUiNode.boxed()
843    } else {
844        Stack! {
845            direction = StackDirection::top_to_bottom();
846            spacing = PARAGRAPH_SPACING_VAR;
847            children = args.items;
848            corner_radius = 2;
849            background_color = if args.level < 3 {
850                FONT_COLOR_VAR.map(|c| c.with_alpha(5.pct())).boxed()
851            } else {
852                colors::BLACK.transparent().into_boxed_var()
853            };
854            border = {
855                widths: (0, 0, 0, 4u32.saturating_sub(args.level).max(1) as i32),
856                sides: FONT_COLOR_VAR.map(|c| BorderSides::solid(c.with_alpha(60.pct()))),
857            };
858            padding = 4;
859        }
860        .boxed()
861    }
862}
863
864/// Default markdown table.
865///
866/// See [`TABLE_FN_VAR`] for more details.
867pub fn default_table_fn(args: TableFnArgs) -> impl UiNode {
868    Grid! {
869        access_role = AccessRole::Table;
870        background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(5.pct()));
871        border = 1, FONT_COLOR_VAR.map(|c| c.with_alpha(30.pct()).into());
872        align = Align::LEFT;
873        auto_grow_fn = wgt_fn!(|args: grid::AutoGrowFnArgs| {
874            grid::Row! {
875                border = (0, 0, 1, 0), FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()).into());
876                background_color = {
877                    let alpha = if args.index % 2 == 0 {
878                        5.pct()
879                    } else {
880                        0.pct()
881                    };
882                    FONT_COLOR_VAR.map(move |c| c.with_alpha(alpha))
883                };
884
885                when *#is_last {
886                    border = 0, BorderStyle::Hidden;
887                }
888            }
889        });
890        columns = std::iter::repeat_with(|| grid::Column!{}.boxed()).take(args.columns.len()).collect::<UiVec>();
891        cells = args.cells;
892    }
893}
894
895/// Default markdown table.
896///
897/// See [`TABLE_CELL_FN_VAR`] for more details.
898pub fn default_table_cell_fn(args: TableCellFnArgs) -> impl UiNode {
899    if args.items.is_empty() {
900        NilUiNode.boxed()
901    } else if args.is_heading {
902        Wrap! {
903            access_role = AccessRole::Cell;
904            grid::cell::at = grid::cell::AT_AUTO;
905            font_weight = FontWeight::BOLD;
906            padding = 6;
907            child_align = args.col_align;
908            children = args.items;
909        }
910        .boxed()
911    } else {
912        Wrap! {
913            access_role = AccessRole::Cell;
914            grid::cell::at = grid::cell::AT_AUTO;
915            padding = 6;
916            child_align = args.col_align;
917            children = args.items;
918        }
919        .boxed()
920    }
921}
922
923/// Default markdown panel.
924///
925/// See [`PANEL_FN_VAR`] for more details.
926pub fn default_panel_fn(args: PanelFnArgs) -> impl UiNode {
927    if args.items.is_empty() {
928        NilUiNode.boxed()
929    } else {
930        Stack! {
931            direction = StackDirection::top_to_bottom();
932            spacing = PARAGRAPH_SPACING_VAR;
933            children = args.items;
934        }
935        .boxed()
936    }
937}
938
939/// Default markdown footnote reference.
940///
941/// See [`FOOTNOTE_REF_FN_VAR`] for more details.
942pub fn default_footnote_ref_fn(args: FootnoteRefFnArgs) -> impl UiNode {
943    let url = formatx!("#footnote-{}", args.label);
944    Button! {
945        style_fn = LinkStyle!();
946        font_size = 0.7.em();
947        offset = (0, (-0.5).em());
948        crate::anchor = formatx!("footnote-ref-{}", args.label);
949        child = Text!("[{}]", args.label);
950        on_click = hn!(|args: &ClickArgs| {
951            args.propagation().stop();
952
953            let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
954            crate::LINK_EVENT.notify(crate::LinkArgs::now(url.clone(), link));
955        });
956    }
957}
958
959/// Default markdown footnote definition.
960///
961/// See [`FOOTNOTE_DEF_FN_VAR`] for more details.
962pub fn default_footnote_def_fn(args: FootnoteDefFnArgs) -> impl UiNode {
963    let mut items = args.items;
964    let items = if items.is_empty() {
965        NilUiNode.boxed()
966    } else if items.len() == 1 {
967        items.remove(0)
968    } else {
969        Stack! {
970            direction = StackDirection::top_to_bottom();
971            children = items;
972        }
973        .boxed()
974    };
975
976    let url_back = formatx!("#footnote-ref-{}", args.label);
977    Stack! {
978        direction = StackDirection::left_to_right();
979        spacing = 0.5.em();
980        anchor = formatx!("footnote-{}", args.label);
981        children = ui_vec![
982            Button! {
983                style_fn = LinkStyle!();
984                child = Text!("[^{}]", args.label);
985                on_click = hn!(|args: &ClickArgs| {
986                    args.propagation().stop();
987
988                    let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
989                    LINK_EVENT.notify(LinkArgs::now(url_back.clone(), link));
990                });
991            },
992            items,
993        ];
994    }
995}