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#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
25pub struct MarkdownStyle {
26 pub strong: bool,
28 pub emphasis: bool,
30 pub strikethrough: bool,
32}
33
34pub struct TextFnArgs {
40 pub txt: Txt,
42 pub style: MarkdownStyle,
44}
45
46pub struct LinkFnArgs {
50 pub url: Txt,
52
53 pub title: Txt,
55
56 pub items: UiVec,
58}
59
60pub struct CodeInlineFnArgs {
66 pub txt: Txt,
68 pub style: MarkdownStyle,
70}
71
72pub struct CodeBlockFnArgs {
76 pub lang: Txt,
78 pub txt: Txt,
80}
81
82pub struct ParagraphFnArgs {
86 pub index: u32,
88 pub items: UiVec,
90}
91
92pub struct HeadingFnArgs {
94 pub level: HeadingLevel,
96
97 pub anchor: Txt,
99
100 pub items: UiVec,
102}
103
104pub struct ListFnArgs {
106 pub depth: u32,
108
109 pub first_num: Option<u64>,
111
112 pub items: UiVec,
116}
117
118#[derive(Clone, Copy)]
120pub struct ListItemBulletFnArgs {
121 pub depth: u32,
123
124 pub num: Option<u64>,
126
127 pub checked: Option<bool>,
129}
130
131pub struct ListItemFnArgs {
133 pub bullet: ListItemBulletFnArgs,
135
136 pub items: UiVec,
138
139 pub blocks: UiVec,
141}
142
143pub struct DefListArgs {
145 pub items: UiVec,
149}
150
151pub struct DefListItemTitleArgs {
153 pub items: UiVec,
155}
156
157pub struct DefListItemDefinitionArgs {
159 pub items: UiVec,
161}
162
163pub struct ImageFnArgs {
165 pub source: ImageSource,
169 pub title: Txt,
171 pub alt_items: UiVec,
173 pub alt_txt: Txt,
175}
176
177pub struct RuleFnArgs {}
181
182pub struct BlockQuoteFnArgs {
184 pub level: u32,
190
191 pub items: UiVec,
193}
194
195pub struct FootnoteRefFnArgs {
197 pub label: Txt,
199}
200
201pub struct FootnoteDefFnArgs {
205 pub label: Txt,
207 pub items: UiVec,
209}
210
211pub struct TableFnArgs {
215 pub columns: Vec<Align>,
217 pub cells: UiVec,
219}
220
221pub struct TableCellFnArgs {
225 pub is_heading: bool,
227
228 pub col_align: Align,
230
231 pub items: UiVec,
233}
234
235pub struct PanelFnArgs {
239 pub items: UiVec,
241}
242
243context_var! {
244 pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = WidgetFn::new(default_text_fn);
246
247 pub static LINK_FN_VAR: WidgetFn<LinkFnArgs> = WidgetFn::new(default_link_fn);
249
250 pub static CODE_INLINE_FN_VAR: WidgetFn<CodeInlineFnArgs> = WidgetFn::new(default_code_inline_fn);
252
253 pub static CODE_BLOCK_FN_VAR: WidgetFn<CodeBlockFnArgs> = WidgetFn::new(default_code_block_fn);
255
256 pub static PARAGRAPH_FN_VAR: WidgetFn<ParagraphFnArgs> = WidgetFn::new(default_paragraph_fn);
258
259 pub static HEADING_FN_VAR: WidgetFn<HeadingFnArgs> = WidgetFn::new(default_heading_fn);
261
262 pub static LIST_FN_VAR: WidgetFn<ListFnArgs> = WidgetFn::new(default_list_fn);
264
265 pub static LIST_ITEM_BULLET_FN_VAR: WidgetFn<ListItemBulletFnArgs> = WidgetFn::new(default_list_item_bullet_fn);
267
268 pub static LIST_ITEM_FN_VAR: WidgetFn<ListItemFnArgs> = WidgetFn::new(default_list_item_fn);
270
271 pub static DEF_LIST_FN_VAR: WidgetFn<DefListArgs> = WidgetFn::new(default_def_list_fn);
273
274 pub static DEF_LIST_ITEM_TITLE_FN_VAR: WidgetFn<DefListItemTitleArgs> = WidgetFn::new(default_def_list_item_title_fn);
276
277 pub static DEF_LIST_ITEM_DEFINITION_FN_VAR: WidgetFn<DefListItemDefinitionArgs> = WidgetFn::new(default_def_list_item_definition_fn);
279
280 pub static IMAGE_FN_VAR: WidgetFn<ImageFnArgs> = WidgetFn::new(default_image_fn);
282
283 pub static RULE_FN_VAR: WidgetFn<RuleFnArgs> = WidgetFn::new(default_rule_fn);
285
286 pub static BLOCK_QUOTE_FN_VAR: WidgetFn<BlockQuoteFnArgs> = WidgetFn::new(default_block_quote_fn);
288
289 pub static FOOTNOTE_REF_FN_VAR: WidgetFn<FootnoteRefFnArgs> = WidgetFn::new(default_footnote_ref_fn);
291
292 pub static FOOTNOTE_DEF_FN_VAR: WidgetFn<FootnoteDefFnArgs> = WidgetFn::new(default_footnote_def_fn);
294
295 pub static TABLE_FN_VAR: WidgetFn<TableFnArgs> = WidgetFn::new(default_table_fn);
297
298 pub static TABLE_CELL_FN_VAR: WidgetFn<TableCellFnArgs> = WidgetFn::new(default_table_cell_fn);
300
301 pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = WidgetFn::new(default_panel_fn);
303}
304
305#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 }
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
492pub 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
500pub 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
515pub 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
549pub 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
577pub 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
593pub 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
618pub 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; 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
646pub 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
665pub 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
683pub 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
701pub 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
754pub 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
788pub 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
828pub fn default_rule_fn(_: RuleFnArgs) -> impl UiNode {
832 zng_wgt_rule_line::hr::Hr! {
833 opacity = 50.pct();
834 }
835}
836
837pub 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
864pub 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
895pub 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
923pub 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
939pub 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
959pub 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}