1use std::num::NonZeroU32;
2
3pub use pulldown_cmark::HeadingLevel;
4use zng_ext_font::*;
5use zng_ext_image::ImageSource;
6use zng_wgt::*;
7use zng_wgt_access::{self as access, AccessRole, access_role};
8use zng_wgt_button::{Button, LinkStyle};
9use zng_wgt_container::{Container, child_align, padding};
10use zng_wgt_fill::background_color;
11use zng_wgt_filter::opacity;
12use zng_wgt_grid::{self as grid, Grid};
13use zng_wgt_size_offset::{offset, size};
14use zng_wgt_stack::{Stack, StackDirection};
15use zng_wgt_text::{FONT_COLOR_VAR, PARAGRAPH_SPACING_VAR, Text, font_size, font_weight};
16use zng_wgt_tooltip::*;
17use zng_wgt_transform::{scale, translate_y};
18use zng_wgt_wrap::Wrap;
19
20use super::*;
21
22#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
24#[non_exhaustive]
25pub struct MarkdownStyle {
26 pub strong: bool,
28 pub emphasis: bool,
30 pub strikethrough: bool,
32
33 pub subscript: bool,
35 pub superscript: bool,
37}
38
39#[non_exhaustive]
45pub struct TextFnArgs {
46 pub txt: Txt,
48 pub style: MarkdownStyle,
50}
51impl TextFnArgs {
52 pub fn new(txt: impl Into<Txt>, style: MarkdownStyle) -> Self {
54 Self { txt: txt.into(), style }
55 }
56}
57
58#[non_exhaustive]
62pub struct LinkFnArgs {
63 pub url: Txt,
65
66 pub title: Txt,
68
69 pub items: UiVec,
71}
72impl LinkFnArgs {
73 pub fn new(url: impl Into<Txt>, title: impl Into<Txt>, items: UiVec) -> Self {
75 Self {
76 url: url.into(),
77 title: title.into(),
78 items,
79 }
80 }
81}
82
83#[non_exhaustive]
89pub struct CodeInlineFnArgs {
90 pub txt: Txt,
92 pub style: MarkdownStyle,
94}
95impl CodeInlineFnArgs {
96 pub fn new(txt: impl Into<Txt>, style: MarkdownStyle) -> Self {
98 Self { txt: txt.into(), style }
99 }
100}
101
102#[non_exhaustive]
106pub struct CodeBlockFnArgs {
107 pub lang: Txt,
109 pub txt: Txt,
111}
112impl CodeBlockFnArgs {
113 pub fn new(lang: impl Into<Txt>, txt: impl Into<Txt>) -> Self {
115 Self {
116 lang: lang.into(),
117 txt: txt.into(),
118 }
119 }
120}
121
122#[non_exhaustive]
126pub struct ParagraphFnArgs {
127 pub index: u32,
129 pub items: UiVec,
131}
132impl ParagraphFnArgs {
133 pub fn new(index: u32, items: UiVec) -> Self {
135 Self { index, items }
136 }
137}
138
139#[non_exhaustive]
141pub struct HeadingFnArgs {
142 pub level: HeadingLevel,
144
145 pub anchor: Txt,
147
148 pub items: UiVec,
150}
151impl HeadingFnArgs {
152 pub fn new(level: HeadingLevel, anchor: impl Into<Txt>, items: UiVec) -> Self {
154 Self {
155 level,
156 anchor: anchor.into(),
157 items,
158 }
159 }
160}
161
162#[non_exhaustive]
164pub struct ListFnArgs {
165 pub depth: u32,
167
168 pub first_num: Option<u64>,
170
171 pub items: UiVec,
175}
176impl ListFnArgs {
177 pub fn new(depth: u32, first_num: Option<u64>, items: UiVec) -> Self {
179 Self { depth, first_num, items }
180 }
181}
182
183#[derive(Clone, Copy)]
185#[non_exhaustive]
186pub struct ListItemBulletFnArgs {
187 pub depth: u32,
189
190 pub num: Option<u64>,
192
193 pub checked: Option<bool>,
195}
196impl ListItemBulletFnArgs {
197 pub fn new(depth: u32, num: Option<u64>, checked: Option<bool>) -> Self {
199 Self { depth, num, checked }
200 }
201}
202
203#[non_exhaustive]
205pub struct ListItemFnArgs {
206 pub bullet: ListItemBulletFnArgs,
208
209 pub items: UiVec,
211
212 pub blocks: UiVec,
214}
215impl ListItemFnArgs {
216 pub fn new(bullet: ListItemBulletFnArgs, items: UiVec, blocks: UiVec) -> Self {
218 Self { bullet, items, blocks }
219 }
220}
221
222#[non_exhaustive]
224pub struct DefListArgs {
225 pub items: UiVec,
229}
230impl DefListArgs {
231 pub fn new(items: UiVec) -> Self {
233 Self { items }
234 }
235}
236
237#[non_exhaustive]
239pub struct DefListItemTitleArgs {
240 pub items: UiVec,
242}
243impl DefListItemTitleArgs {
244 pub fn new(items: UiVec) -> Self {
246 Self { items }
247 }
248}
249
250#[non_exhaustive]
252pub struct DefListItemDefinitionArgs {
253 pub items: UiVec,
255}
256impl DefListItemDefinitionArgs {
257 pub fn new(items: UiVec) -> Self {
259 Self { items }
260 }
261}
262
263#[non_exhaustive]
265pub struct ImageFnArgs {
266 pub source: ImageSource,
270 pub title: Txt,
272 pub alt_items: UiVec,
274 pub alt_txt: Txt,
276}
277impl ImageFnArgs {
278 pub fn new(source: ImageSource, title: impl Into<Txt>, alt_items: UiVec, alt_txt: impl Into<Txt>) -> Self {
280 Self {
281 source,
282 title: title.into(),
283 alt_items,
284 alt_txt: alt_txt.into(),
285 }
286 }
287}
288
289#[derive(Default)]
293#[non_exhaustive]
294pub struct RuleFnArgs {}
295
296#[non_exhaustive]
298pub struct BlockQuoteFnArgs {
299 pub level: u32,
305
306 pub items: UiVec,
308}
309impl BlockQuoteFnArgs {
310 pub fn new(level: u32, items: UiVec) -> Self {
312 Self { level, items }
313 }
314}
315
316#[non_exhaustive]
318pub struct FootnoteRefFnArgs {
319 pub label: Txt,
321}
322impl FootnoteRefFnArgs {
323 pub fn new(label: impl Into<Txt>) -> Self {
325 Self { label: label.into() }
326 }
327}
328
329#[non_exhaustive]
333pub struct FootnoteDefFnArgs {
334 pub label: Txt,
336 pub items: UiVec,
338}
339impl FootnoteDefFnArgs {
340 pub fn new(label: impl Into<Txt>, items: UiVec) -> Self {
342 Self {
343 label: label.into(),
344 items,
345 }
346 }
347}
348
349#[non_exhaustive]
353pub struct TableFnArgs {
354 pub columns: Vec<Align>,
356 pub cells: UiVec,
358}
359impl TableFnArgs {
360 pub fn new(columns: Vec<Align>, cells: UiVec) -> Self {
362 Self { columns, cells }
363 }
364}
365
366#[non_exhaustive]
370pub struct TableCellFnArgs {
371 pub is_heading: bool,
373
374 pub col_align: Align,
376
377 pub items: UiVec,
379}
380impl TableCellFnArgs {
381 pub fn new(is_heading: bool, col_align: Align, items: UiVec) -> Self {
383 Self {
384 is_heading,
385 col_align,
386 items,
387 }
388 }
389}
390
391#[non_exhaustive]
395pub struct PanelFnArgs {
396 pub items: UiVec,
398}
399impl PanelFnArgs {
400 pub fn new(items: UiVec) -> Self {
402 Self { items }
403 }
404}
405
406context_var! {
407 pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = WidgetFn::new(default_text_fn);
409
410 pub static LINK_FN_VAR: WidgetFn<LinkFnArgs> = WidgetFn::new(default_link_fn);
412
413 pub static CODE_INLINE_FN_VAR: WidgetFn<CodeInlineFnArgs> = WidgetFn::new(default_code_inline_fn);
415
416 pub static CODE_BLOCK_FN_VAR: WidgetFn<CodeBlockFnArgs> = WidgetFn::new(default_code_block_fn);
418
419 pub static PARAGRAPH_FN_VAR: WidgetFn<ParagraphFnArgs> = WidgetFn::new(default_paragraph_fn);
421
422 pub static HEADING_FN_VAR: WidgetFn<HeadingFnArgs> = WidgetFn::new(default_heading_fn);
424
425 pub static LIST_FN_VAR: WidgetFn<ListFnArgs> = WidgetFn::new(default_list_fn);
427
428 pub static LIST_ITEM_BULLET_FN_VAR: WidgetFn<ListItemBulletFnArgs> = WidgetFn::new(default_list_item_bullet_fn);
430
431 pub static LIST_ITEM_FN_VAR: WidgetFn<ListItemFnArgs> = WidgetFn::new(default_list_item_fn);
433
434 pub static DEF_LIST_FN_VAR: WidgetFn<DefListArgs> = WidgetFn::new(default_def_list_fn);
436
437 pub static DEF_LIST_ITEM_TITLE_FN_VAR: WidgetFn<DefListItemTitleArgs> = WidgetFn::new(default_def_list_item_title_fn);
439
440 pub static DEF_LIST_ITEM_DEFINITION_FN_VAR: WidgetFn<DefListItemDefinitionArgs> =
442 WidgetFn::new(default_def_list_item_definition_fn);
443
444 pub static IMAGE_FN_VAR: WidgetFn<ImageFnArgs> = WidgetFn::new(default_image_fn);
446
447 pub static RULE_FN_VAR: WidgetFn<RuleFnArgs> = WidgetFn::new(default_rule_fn);
449
450 pub static BLOCK_QUOTE_FN_VAR: WidgetFn<BlockQuoteFnArgs> = WidgetFn::new(default_block_quote_fn);
452
453 pub static FOOTNOTE_REF_FN_VAR: WidgetFn<FootnoteRefFnArgs> = WidgetFn::new(default_footnote_ref_fn);
455
456 pub static FOOTNOTE_DEF_FN_VAR: WidgetFn<FootnoteDefFnArgs> = WidgetFn::new(default_footnote_def_fn);
458
459 pub static TABLE_FN_VAR: WidgetFn<TableFnArgs> = WidgetFn::new(default_table_fn);
461
462 pub static TABLE_CELL_FN_VAR: WidgetFn<TableCellFnArgs> = WidgetFn::new(default_table_cell_fn);
464
465 pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = WidgetFn::new(default_panel_fn);
467}
468
469#[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(Markdown))]
473pub fn text_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> UiNode {
474 with_context_var(child, TEXT_FN_VAR, wgt_fn)
475}
476
477#[property(CONTEXT, default(LINK_FN_VAR), widget_impl(Markdown))]
481pub fn link_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<LinkFnArgs>>) -> UiNode {
482 with_context_var(child, LINK_FN_VAR, wgt_fn)
483}
484
485#[property(CONTEXT, default(CODE_INLINE_FN_VAR), widget_impl(Markdown))]
489pub fn code_inline_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<CodeInlineFnArgs>>) -> UiNode {
490 with_context_var(child, CODE_INLINE_FN_VAR, wgt_fn)
491}
492
493#[property(CONTEXT, default(CODE_BLOCK_FN_VAR), widget_impl(Markdown))]
497pub fn code_block_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<CodeBlockFnArgs>>) -> UiNode {
498 with_context_var(child, CODE_BLOCK_FN_VAR, wgt_fn)
499}
500
501#[property(CONTEXT, default(PARAGRAPH_FN_VAR), widget_impl(Markdown))]
505pub fn paragraph_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ParagraphFnArgs>>) -> UiNode {
506 with_context_var(child, PARAGRAPH_FN_VAR, wgt_fn)
507}
508
509#[property(CONTEXT, default(HEADING_FN_VAR), widget_impl(Markdown))]
513pub fn heading_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<HeadingFnArgs>>) -> UiNode {
514 with_context_var(child, HEADING_FN_VAR, wgt_fn)
515}
516
517#[property(CONTEXT, default(LIST_FN_VAR), widget_impl(Markdown))]
521pub fn list_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ListFnArgs>>) -> UiNode {
522 with_context_var(child, LIST_FN_VAR, wgt_fn)
523}
524
525#[property(CONTEXT, default(DEF_LIST_FN_VAR), widget_impl(Markdown))]
529pub fn def_list_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<DefListArgs>>) -> UiNode {
530 with_context_var(child, DEF_LIST_FN_VAR, wgt_fn)
531}
532
533#[property(CONTEXT, default(DEF_LIST_ITEM_TITLE_FN_VAR), widget_impl(Markdown))]
537pub fn def_list_item_title_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<DefListItemTitleArgs>>) -> UiNode {
538 with_context_var(child, DEF_LIST_ITEM_TITLE_FN_VAR, wgt_fn)
539}
540
541#[property(CONTEXT, default(DEF_LIST_ITEM_DEFINITION_FN_VAR), widget_impl(Markdown))]
545pub fn def_list_item_definition_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<DefListItemDefinitionArgs>>) -> UiNode {
546 with_context_var(child, DEF_LIST_ITEM_DEFINITION_FN_VAR, wgt_fn)
547}
548
549#[property(CONTEXT, default(LIST_ITEM_BULLET_FN_VAR), widget_impl(Markdown))]
553pub fn list_item_bullet_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ListItemBulletFnArgs>>) -> UiNode {
554 with_context_var(child, LIST_ITEM_BULLET_FN_VAR, wgt_fn)
555}
556
557#[property(CONTEXT, default(LIST_ITEM_FN_VAR), widget_impl(Markdown))]
561pub fn list_item_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ListItemFnArgs>>) -> UiNode {
562 with_context_var(child, LIST_ITEM_FN_VAR, wgt_fn)
563}
564
565#[property(CONTEXT, default(IMAGE_FN_VAR), widget_impl(Markdown))]
569pub fn image_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<ImageFnArgs>>) -> UiNode {
570 with_context_var(child, IMAGE_FN_VAR, wgt_fn)
571}
572
573#[property(CONTEXT, default(RULE_FN_VAR), widget_impl(Markdown))]
577pub fn rule_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<RuleFnArgs>>) -> UiNode {
578 with_context_var(child, RULE_FN_VAR, wgt_fn)
579}
580
581#[property(CONTEXT, default(BLOCK_QUOTE_FN_VAR), widget_impl(Markdown))]
585pub fn block_quote_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<BlockQuoteFnArgs>>) -> UiNode {
586 with_context_var(child, BLOCK_QUOTE_FN_VAR, wgt_fn)
587}
588
589#[property(CONTEXT, default(FOOTNOTE_REF_FN_VAR), widget_impl(Markdown))]
593pub fn footnote_ref_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<FootnoteRefFnArgs>>) -> UiNode {
594 with_context_var(child, FOOTNOTE_REF_FN_VAR, wgt_fn)
595}
596
597#[property(CONTEXT, default(FOOTNOTE_DEF_FN_VAR), widget_impl(Markdown))]
601pub fn footnote_def_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<FootnoteDefFnArgs>>) -> UiNode {
602 with_context_var(child, FOOTNOTE_DEF_FN_VAR, wgt_fn)
603}
604
605#[property(CONTEXT, default(TABLE_FN_VAR), widget_impl(Markdown))]
609pub fn table_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<TableFnArgs>>) -> UiNode {
610 with_context_var(child, TABLE_FN_VAR, wgt_fn)
611}
612
613#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(Markdown))]
621pub fn panel_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> UiNode {
622 with_context_var(child, PANEL_FN_VAR, wgt_fn)
623}
624
625fn text_view_builder(txt: Txt, style: MarkdownStyle) -> Text {
626 let mut builder = Text::widget_new();
627
628 widget_set! {
629 &mut builder;
630 txt;
631 }
633
634 if style.strong {
635 widget_set! {
636 &mut builder;
637 font_weight = FontWeight::BOLD;
638 }
639 }
640 if style.emphasis {
641 widget_set! {
642 &mut builder;
643 font_style = FontStyle::Italic;
644 }
645 }
646 if style.strikethrough {
647 widget_set! {
648 &mut builder;
649 strikethrough = 1, LineStyle::Solid;
650 }
651 }
652 if style.subscript {
653 widget_set! {
654 &mut builder;
655 font_size = 0.75.em();
656 translate_y = 0.25.em();
657 }
658 } else if style.superscript {
659 widget_set! {
660 &mut builder;
661 font_size = 0.75.em();
662 translate_y = -0.25.em();
663 }
664 }
665
666 builder
667}
668
669pub fn default_text_fn(args: TextFnArgs) -> UiNode {
673 let mut builder = text_view_builder(args.txt, args.style);
674 builder.widget_build()
675}
676
677pub fn default_code_inline_fn(args: CodeInlineFnArgs) -> UiNode {
681 let mut builder = text_view_builder(args.txt, args.style);
682
683 widget_set! {
684 &mut builder;
685 font_family = ["JetBrains Mono", "Consolas", "monospace"];
686 background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
687 }
688
689 builder.widget_build()
690}
691
692pub fn default_link_fn(args: LinkFnArgs) -> UiNode {
696 if args.items.is_empty() {
697 UiNode::nil()
698 } else {
699 let url = args.url;
700
701 let mut items = args.items;
702 let items = if items.len() == 1 {
703 items.remove(0)
704 } else {
705 Wrap! {
706 children = items;
707 }
708 };
709
710 Button! {
711 style_fn = LinkStyle!();
712 child = items;
713
714 on_click = hn!(|args| {
715 args.propagation().stop();
716
717 let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
718 LINK_EVENT.notify(LinkArgs::now(url.clone(), link));
719 });
720 }
721 }
722}
723
724pub fn default_code_block_fn(args: CodeBlockFnArgs) -> UiNode {
732 if ["ansi", "console"].contains(&args.lang.as_str()) {
733 zng_wgt_ansi_text::AnsiText! {
734 txt = args.txt;
735 padding = 6;
736 corner_radius = 4;
737 background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
738 }
739 } else {
740 Text! {
741 txt = args.txt;
742 padding = 6;
743 corner_radius = 4;
744 font_family = ["JetBrains Mono", "Consolas", "monospace"];
745 background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
746 }
747 }
748}
749
750pub fn default_paragraph_fn(mut args: ParagraphFnArgs) -> UiNode {
754 if args.items.is_empty() {
755 UiNode::nil()
756 } else if args.items.len() == 1 {
757 args.items.remove(0)
758 } else {
759 Wrap! {
760 children = args.items;
761 }
762 }
763}
764
765pub fn default_heading_fn(args: HeadingFnArgs) -> UiNode {
769 if args.items.is_empty() {
770 UiNode::nil()
771 } else {
772 Wrap! {
773 access_role = AccessRole::Heading;
774 access::level = NonZeroU32::new(args.level as _).unwrap();
775 font_size = match args.level {
776 HeadingLevel::H1 => 2.em(),
777 HeadingLevel::H2 => 1.5.em(),
778 HeadingLevel::H3 => 1.4.em(),
779 HeadingLevel::H4 => 1.3.em(),
780 HeadingLevel::H5 => 1.2.em(),
781 HeadingLevel::H6 => 1.1.em(),
782 };
783 children = args.items;
784 anchor = args.anchor;
785 }
786 }
787}
788
789pub fn default_list_fn(args: ListFnArgs) -> UiNode {
797 if args.items.is_empty() {
798 UiNode::nil()
799 } else {
800 Grid! {
801 grid::cell::at = grid::cell::AT_AUTO; access_role = AccessRole::List;
804 margin = (0, 0, 0, 1.em());
805 cells = args.items;
806 columns = ui_vec![
807 grid::Column!(),
808 grid::Column! {
809 width = 1.lft();
810 },
811 ];
812 }
813 }
814}
815
816pub fn default_def_list_fn(args: DefListArgs) -> UiNode {
822 if args.items.is_empty() {
823 UiNode::nil()
824 } else {
825 Stack! {
826 access_role = AccessRole::List;
827 direction = StackDirection::top_to_bottom();
828 spacing = PARAGRAPH_SPACING_VAR;
829 children = args.items;
830 }
831 }
832}
833
834pub fn default_def_list_item_title_fn(args: DefListItemTitleArgs) -> UiNode {
840 if args.items.is_empty() {
841 UiNode::nil()
842 } else {
843 Wrap! {
844 access_role = AccessRole::Term;
845 children = args.items;
846 font_weight = FontWeight::BOLD;
847 }
848 }
849}
850
851pub fn default_def_list_item_definition_fn(args: DefListItemDefinitionArgs) -> UiNode {
857 if args.items.is_empty() {
858 UiNode::nil()
859 } else {
860 Wrap! {
861 access_role = AccessRole::Definition;
862 children = args.items;
863 margin = (0, 2.em());
864 }
865 }
866}
867
868pub fn default_list_item_bullet_fn(args: ListItemBulletFnArgs) -> UiNode {
872 if let Some(checked) = args.checked {
873 Text! {
874 grid::cell::at = grid::cell::AT_AUTO;
875 align = Align::TOP;
876 txt = " ✓ ";
877 font_color = FONT_COLOR_VAR.map(move |c| if checked { *c } else { c.transparent() });
878 background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()));
879 corner_radius = 4;
880 scale = 0.8.fct();
881 offset = (-(0.1.fct()), 0);
882 }
883 } else if let Some(n) = args.num {
884 Text! {
885 grid::cell::at = grid::cell::AT_AUTO;
886 txt = formatx!("{n}. ");
887 align = Align::RIGHT;
888 }
889 } else {
890 match args.depth {
891 0 => Wgt! {
892 grid::cell::at = grid::cell::AT_AUTO;
893 align = Align::TOP;
894 size = (5, 5);
895 corner_radius = 5;
896 margin = (0.6.em(), 0.5.em(), 0, 0);
897 background_color = FONT_COLOR_VAR;
898 },
899 1 => Wgt! {
900 grid::cell::at = grid::cell::AT_AUTO;
901 align = Align::TOP;
902 size = (5, 5);
903 corner_radius = 5;
904 margin = (0.6.em(), 0.5.em(), 0, 0);
905 border = 1.px(), FONT_COLOR_VAR.map_into();
906 },
907 _ => Wgt! {
908 grid::cell::at = grid::cell::AT_AUTO;
909 align = Align::TOP;
910 size = (5, 5);
911 margin = (0.6.em(), 0.5.em(), 0, 0);
912 background_color = FONT_COLOR_VAR;
913 },
914 }
915 }
916}
917
918pub fn default_list_item_fn(args: ListItemFnArgs) -> UiNode {
922 let mut blocks = args.blocks;
923 let mut items = args.items;
924
925 if items.is_empty() {
926 if blocks.is_empty() {
927 return UiNode::nil();
928 }
929 } else {
930 let r = if items.len() == 1 { items.remove(0) } else { Wrap!(items) };
931 blocks.insert(0, r);
932 }
933
934 if blocks.len() > 1 {
935 Stack! {
936 access_role = AccessRole::ListItem;
937 grid::cell::at = grid::cell::AT_AUTO;
938 direction = StackDirection::top_to_bottom();
939 children = blocks;
940 }
941 } else {
942 Container! {
943 access_role = AccessRole::ListItem;
944 grid::cell::at = grid::cell::AT_AUTO;
945 child = blocks.remove(0);
946 }
947 }
948}
949
950pub fn default_image_fn(args: ImageFnArgs) -> UiNode {
954 let tooltip_fn = if args.title.is_empty() {
955 wgt_fn!()
956 } else {
957 let title = args.title;
958 wgt_fn!(|_| Tip!(Text!(title.clone())))
959 };
960
961 let alt_txt = args.alt_txt;
962 let mut alt_items = args.alt_items;
963 if alt_items.is_empty() {
964 zng_wgt_image::Image! {
965 align = Align::TOP_LEFT;
966 tooltip_fn;
967 access::label = alt_txt;
968 source = args.source;
969 }
970 } else {
971 let alt_items = if alt_items.len() == 1 {
972 alt_items.remove(0)
973 } else {
974 Wrap! {
975 children = alt_items;
976 }
977 };
978 let alt_items = ArcNode::new(alt_items);
979 zng_wgt_image::Image! {
980 align = Align::TOP_LEFT;
981 source = args.source;
982 tooltip_fn;
983 zng_wgt_access::label = alt_txt;
984 img_error_fn = wgt_fn!(|_| { alt_items.take_on_init() });
985 }
986 }
987}
988
989pub fn default_rule_fn(_: RuleFnArgs) -> UiNode {
993 zng_wgt_rule_line::hr::Hr! {
994 opacity = 50.pct();
995 }
996}
997
998pub fn default_block_quote_fn(args: BlockQuoteFnArgs) -> UiNode {
1002 if args.items.is_empty() {
1003 UiNode::nil()
1004 } else {
1005 Stack! {
1006 direction = StackDirection::top_to_bottom();
1007 spacing = PARAGRAPH_SPACING_VAR;
1008 children = args.items;
1009 corner_radius = 2;
1010 background_color = if args.level < 3 {
1011 FONT_COLOR_VAR.map(|c| c.with_alpha(5.pct()))
1012 } else {
1013 colors::BLACK.transparent().into_var()
1014 };
1015 border = {
1016 widths: (0, 0, 0, 4u32.saturating_sub(args.level).max(1) as i32),
1017 sides: FONT_COLOR_VAR.map(|c| BorderSides::solid(c.with_alpha(60.pct()))),
1018 };
1019 padding = 4;
1020 }
1021 }
1022}
1023
1024pub fn default_table_fn(args: TableFnArgs) -> UiNode {
1028 Grid! {
1029 access_role = AccessRole::Table;
1030 background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(5.pct()));
1031 border = 1, FONT_COLOR_VAR.map(|c| c.with_alpha(30.pct()).into());
1032 align = Align::LEFT;
1033 auto_grow_fn = wgt_fn!(|args: grid::AutoGrowFnArgs| {
1034 grid::Row! {
1035 border = (0, 0, 1, 0), FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()).into());
1036 background_color = {
1037 let alpha = if args.index.is_multiple_of(2) { 5.pct() } else { 0.pct() };
1038 FONT_COLOR_VAR.map(move |c| c.with_alpha(alpha))
1039 };
1040
1041 when *#is_last {
1042 border = 0, BorderStyle::Hidden;
1043 }
1044 }
1045 });
1046 columns = std::iter::repeat_with(|| grid::Column! {}).take(args.columns.len());
1047 cells = args.cells;
1048 }
1049}
1050
1051pub fn default_table_cell_fn(args: TableCellFnArgs) -> UiNode {
1055 if args.items.is_empty() {
1056 UiNode::nil()
1057 } else if args.is_heading {
1058 Wrap! {
1059 access_role = AccessRole::Cell;
1060 grid::cell::at = grid::cell::AT_AUTO;
1061 font_weight = FontWeight::BOLD;
1062 padding = 6;
1063 child_align = args.col_align;
1064 children = args.items;
1065 }
1066 } else {
1067 Wrap! {
1068 access_role = AccessRole::Cell;
1069 grid::cell::at = grid::cell::AT_AUTO;
1070 padding = 6;
1071 child_align = args.col_align;
1072 children = args.items;
1073 }
1074 }
1075}
1076
1077pub fn default_panel_fn(args: PanelFnArgs) -> UiNode {
1081 if args.items.is_empty() {
1082 UiNode::nil()
1083 } else {
1084 Stack! {
1085 direction = StackDirection::top_to_bottom();
1086 spacing = PARAGRAPH_SPACING_VAR;
1087 children = args.items;
1088 }
1089 }
1090}
1091
1092pub fn default_footnote_ref_fn(args: FootnoteRefFnArgs) -> UiNode {
1096 let url = formatx!("#footnote-{}", args.label);
1097 Button! {
1098 style_fn = LinkStyle!();
1099 font_size = 0.7.em();
1100 offset = (0, -0.5.em());
1101 crate::anchor = formatx!("footnote-ref-{}", args.label);
1102 child = Text!("[{}]", args.label);
1103 on_click = hn!(|args| {
1104 args.propagation().stop();
1105
1106 let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
1107 crate::LINK_EVENT.notify(crate::LinkArgs::now(url.clone(), link));
1108 });
1109 }
1110}
1111
1112pub fn default_footnote_def_fn(args: FootnoteDefFnArgs) -> UiNode {
1116 let mut items = args.items;
1117 let items = if items.is_empty() {
1118 UiNode::nil()
1119 } else if items.len() == 1 {
1120 items.remove(0)
1121 } else {
1122 Stack! {
1123 direction = StackDirection::top_to_bottom();
1124 children = items;
1125 }
1126 };
1127
1128 let url_back = formatx!("#footnote-ref-{}", args.label);
1129 Stack! {
1130 direction = StackDirection::left_to_right();
1131 spacing = 0.5.em();
1132 anchor = formatx!("footnote-{}", args.label);
1133 children = ui_vec![
1134 Button! {
1135 style_fn = LinkStyle!();
1136 child = Text!("[^{}]", args.label);
1137 on_click = hn!(|args| {
1138 args.propagation().stop();
1139
1140 let link = WINDOW.info().get(WIDGET.id()).unwrap().interaction_path();
1141 LINK_EVENT.notify(LinkArgs::now(url_back.clone(), link));
1142 });
1143 },
1144 items,
1145 ];
1146 }
1147}