notation_bevy_utils 0.4.2

Fun notation - bevy utils
use bevy::prelude::EventWriter;
use bevy_egui::egui::*;
use crate::egui::EasyLinkEvent;

use super::easy_mark_parser as easy_mark;

/// Parse and display a VERY simple and small subset of Markdown.
pub fn easy_mark(ui: &mut Ui, easy_mark: &str, link_evts: &mut EventWriter<EasyLinkEvent>) {
    easy_mark_it(ui, easy_mark::Parser::new(easy_mark), link_evts);
}

pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Item<'em>>, link_evts: &mut EventWriter<EasyLinkEvent>) {
    let initial_size = vec2(
        ui.available_width(),
        ui.spacing().interact_size.y, // Assume there will be
    );

    let layout = Layout::left_to_right()
        .with_main_wrap(true)
        .with_cross_align(Align::BOTTOM);

    ui.allocate_ui_with_layout(initial_size, layout, |ui| {
        ui.spacing_mut().item_spacing.x = 0.0;
        ui.set_row_height(ui.fonts()[TextStyle::Body].row_height());

        for item in items {
            item_ui(ui, item, link_evts);
        }
    });
}

pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>, link_evts: &mut EventWriter<EasyLinkEvent>) {
    let row_height = ui.fonts()[TextStyle::Body].row_height();
    let one_indent = row_height / 2.0;

    match item {
        easy_mark::Item::Newline => {
            // ui.label("\n"); // too much spacing (paragraph spacing)
            ui.allocate_exact_size(vec2(0.0, row_height), Sense::hover()); // make sure we take up some height
            ui.end_row();
            ui.set_row_height(row_height);
        }

        easy_mark::Item::Text(style, text) => {
            ui.label(rich_text_from_style(text, &style));
        }
        easy_mark::Item::Hyperlink(style, text, url) => {
            /*
            let label = rich_text_from_style(text, &style);
            ui.add(Hyperlink::from_label_and_url(label, url));
             */
            crate::egui::EasyLink::new(url, text, style).ui(ui, link_evts);
        }

        easy_mark::Item::Separator => {
            ui.add(Separator::default().horizontal());
        }
        easy_mark::Item::Indentation(indent) => {
            let indent = indent as f32 * one_indent;
            ui.allocate_exact_size(vec2(indent, row_height), Sense::hover());
        }
        easy_mark::Item::QuoteIndent => {
            let rect = ui
                .allocate_exact_size(vec2(2.0 * one_indent, row_height), Sense::hover())
                .0;
            let rect = rect.expand2(ui.style().spacing.item_spacing * 0.5);
            ui.painter().line_segment(
                [rect.center_top(), rect.center_bottom()],
                (1.0, ui.visuals().weak_text_color()),
            );
        }
        easy_mark::Item::BulletPoint => {
            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
            bullet_point(ui, one_indent);
            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
        }
        easy_mark::Item::NumberedPoint(number) => {
            let width = 3.0 * one_indent;
            numbered_point(ui, width, number);
            ui.allocate_exact_size(vec2(one_indent, row_height), Sense::hover());
        }
        easy_mark::Item::CodeBlock(_language, code) => {
            let where_to_put_background = ui.painter().add(Shape::Noop);
            let mut rect = ui.monospace(code).rect;
            rect = rect.expand(1.0); // looks better
            rect.max.x = ui.max_rect().max.x;
            let code_bg_color = ui.visuals().code_bg_color;
            ui.painter().set(
                where_to_put_background,
                Shape::rect_filled(rect, 1.0, code_bg_color),
            );
        }
    };
}

pub fn rich_text_from_style(text: &str, style: &easy_mark::Style) -> RichText {
    let easy_mark::Style {
        heading,
        quoted,
        code,
        strong,
        underline,
        strikethrough,
        italics,
        small,
        raised,
    } = *style;

    let small = small || raised; // Raised text is also smaller

    let mut rich_text = RichText::new(text);
    if heading && !small {
        rich_text = rich_text.heading().strong();
    }
    if small && !heading {
        rich_text = rich_text.small();
    }
    if code {
        rich_text = rich_text.code();
    }
    if strong {
        rich_text = rich_text.strong();
    } else if quoted {
        rich_text = rich_text.weak();
    }
    if underline {
        rich_text = rich_text.underline();
    }
    if strikethrough {
        rich_text = rich_text.strikethrough();
    }
    if italics {
        rich_text = rich_text.italics();
    }
    if raised {
        rich_text = rich_text.raised();
    }
    rich_text
}

fn bullet_point(ui: &mut Ui, width: f32) -> Response {
    let row_height = ui.fonts()[TextStyle::Body].row_height();
    let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
    ui.painter().circle_filled(
        rect.center(),
        rect.height() / 8.0,
        ui.visuals().strong_text_color(),
    );
    response
}

fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
    let row_height = ui.fonts()[TextStyle::Body].row_height();
    let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
    let text = format!("{}.", number);
    let text_color = ui.visuals().strong_text_color();
    ui.painter().text(
        rect.right_center(),
        Align2::RIGHT_CENTER,
        text,
        TextStyle::Body,
        text_color,
    );
    response
}