egui_demo_lib/easy_mark/
easy_mark_viewer.rs

1use super::easy_mark_parser as easy_mark;
2use egui::{
3    Align, Align2, Hyperlink, Layout, Response, RichText, Sense, Separator, Shape, TextStyle, Ui,
4    vec2,
5};
6
7/// Parse and display a VERY simple and small subset of Markdown.
8pub fn easy_mark(ui: &mut Ui, easy_mark: &str) {
9    easy_mark_it(ui, easy_mark::Parser::new(easy_mark));
10}
11
12pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Item<'em>>) {
13    let initial_size = vec2(
14        ui.available_width(),
15        ui.spacing().interact_size.y, // Assume there will be
16    );
17
18    let layout = Layout::left_to_right(Align::BOTTOM).with_main_wrap(true);
19
20    ui.allocate_ui_with_layout(initial_size, layout, |ui| {
21        ui.spacing_mut().item_spacing.x = 0.0;
22        let row_height = ui.text_style_height(&TextStyle::Body);
23        ui.set_row_height(row_height);
24
25        for item in items {
26            item_ui(ui, item);
27        }
28    });
29}
30
31pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
32    let row_height = ui.text_style_height(&TextStyle::Body);
33    let one_indent = row_height / 2.0;
34
35    match item {
36        easy_mark::Item::Newline => {
37            // ui.label("\n"); // too much spacing (paragraph spacing)
38            ui.allocate_exact_size(vec2(0.0, row_height), Sense::hover()); // make sure we take up some height
39            ui.end_row();
40            ui.set_row_height(row_height);
41        }
42
43        easy_mark::Item::Text(style, text) => {
44            let label = rich_text_from_style(text, &style);
45            if style.small && !style.raised {
46                ui.with_layout(Layout::left_to_right(Align::BOTTOM), |ui| {
47                    ui.set_min_height(row_height);
48                    ui.label(label);
49                });
50            } else {
51                ui.label(label);
52            }
53        }
54        easy_mark::Item::Hyperlink(style, text, url) => {
55            let label = rich_text_from_style(text, &style);
56            if style.small && !style.raised {
57                ui.with_layout(Layout::left_to_right(Align::BOTTOM), |ui| {
58                    ui.set_height(row_height);
59                    ui.add(Hyperlink::from_label_and_url(label, url));
60                });
61            } else {
62                ui.add(Hyperlink::from_label_and_url(label, url));
63            }
64        }
65
66        easy_mark::Item::Separator => {
67            ui.add(Separator::default().horizontal());
68        }
69        easy_mark::Item::Indentation(indent) => {
70            let indent = indent as f32 * one_indent;
71            ui.allocate_exact_size(vec2(indent, row_height), Sense::hover());
72        }
73        easy_mark::Item::QuoteIndent => {
74            let rect = ui
75                .allocate_exact_size(vec2(2.0 * one_indent, row_height), Sense::hover())
76                .0;
77            let rect = rect.expand2(ui.style().spacing.item_spacing * 0.5);
78            ui.painter().line_segment(
79                [rect.center_top(), rect.center_bottom()],
80                (1.0, ui.visuals().weak_text_color()),
81            );
82        }
83        easy_mark::Item::BulletPoint => {
84            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
85            bullet_point(ui, one_indent);
86            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
87        }
88        easy_mark::Item::NumberedPoint(number) => {
89            let width = 3.0 * one_indent;
90            numbered_point(ui, width, number);
91            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
92        }
93        easy_mark::Item::CodeBlock(_language, code) => {
94            let where_to_put_background = ui.painter().add(Shape::Noop);
95            let mut rect = ui.monospace(code).rect;
96            rect = rect.expand(1.0); // looks better
97            rect.max.x = ui.max_rect().max.x;
98            let code_bg_color = ui.visuals().code_bg_color;
99            ui.painter().set(
100                where_to_put_background,
101                Shape::rect_filled(rect, 1.0, code_bg_color),
102            );
103        }
104    }
105}
106
107fn rich_text_from_style(text: &str, style: &easy_mark::Style) -> RichText {
108    let easy_mark::Style {
109        heading,
110        quoted,
111        code,
112        strong,
113        underline,
114        strikethrough,
115        italics,
116        small,
117        raised,
118    } = *style;
119
120    let small = small || raised; // Raised text is also smaller
121
122    let mut rich_text = RichText::new(text);
123    if heading && !small {
124        rich_text = rich_text.heading().strong();
125    }
126    if small && !heading {
127        rich_text = rich_text.small();
128    }
129    if code {
130        rich_text = rich_text.code();
131    }
132    if strong {
133        rich_text = rich_text.strong();
134    } else if quoted {
135        rich_text = rich_text.weak();
136    }
137    if underline {
138        rich_text = rich_text.underline();
139    }
140    if strikethrough {
141        rich_text = rich_text.strikethrough();
142    }
143    if italics {
144        rich_text = rich_text.italics();
145    }
146    if raised {
147        rich_text = rich_text.raised();
148    }
149    rich_text
150}
151
152fn bullet_point(ui: &mut Ui, width: f32) -> Response {
153    let row_height = ui.text_style_height(&TextStyle::Body);
154    let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
155    ui.painter().circle_filled(
156        rect.center(),
157        rect.height() / 8.0,
158        ui.visuals().strong_text_color(),
159    );
160    response
161}
162
163fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
164    let font_id = TextStyle::Body.resolve(ui.style());
165    let row_height = ui.fonts_mut(|f| f.row_height(&font_id));
166    let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
167    let text = format!("{number}.");
168    let text_color = ui.visuals().strong_text_color();
169    ui.painter().text(
170        rect.right_center(),
171        Align2::RIGHT_CENTER,
172        text,
173        font_id,
174        text_color,
175    );
176    response
177}