rae 0.1.12

Renderer-neutral widget, layout, sanitization, and GLSL shader primitives for Rust desktop tools.
Documentation
mod support;

use macroquad::prelude::*;
use rae::{
    materialize_output, CodeTokenKind, OutputDocument, OutputOptions, OutputRow, Palette, Rect,
    Rgba,
};
use support::*;

fn window_conf() -> Conf {
    Conf {
        window_title: "rae code output".to_string(),
        window_width: 1440,
        window_height: 900,
        high_dpi: true,
        sample_count: 4,
        ..Default::default()
    }
}

#[macroquad::main(window_conf)]
async fn main() {
    let palette = Palette::midnight();
    let doc = materialize_output(
        assistant_output(),
        OutputOptions::for_width(82).with_code_line_numbers(true),
    );
    let max_frames = max_frames_from_env();
    let mut frames = 0u64;

    loop {
        frames = frames.saturating_add(1);
        if should_stop(frames, max_frames) {
            break;
        }

        let time = get_time() as f32;
        let viewport = Rect::new(0.0, 0.0, screen_width(), screen_height());
        draw_scene_backdrop(&palette, time);
        draw_header(viewport, &palette, &doc);
        draw_output_document(viewport, &palette, &doc);
        draw_copy_panel(viewport, &palette, &doc);
        maybe_save_screenshot("demo_code_output", frames);

        next_frame().await;
    }
}

fn assistant_output() -> &'static str {
    concat!(
        "# Patch\n\n",
        "Here is a minimal safe code output. The incoming text can contain ANSI like ",
        "\x1b[31mred\x1b[0m or OSC links.\n\n",
        "```rust\n",
        "pub fn greet(name: &str) -> String {\n",
        "    // renderer gets sanitized tokens, not raw terminal escapes\n",
        "    format!(\"hello {name}\")\n",
        "}\n",
        "```\n\n",
        "- Strip ANSI before rendering.\n",
        "- Preserve copy-safe fenced code.\n",
        "- Keep line numbers and syntax token classes renderer-neutral.\n",
    )
}

fn draw_header(viewport: Rect, palette: &Palette, doc: &OutputDocument) {
    let header = Rect::new(28.0, 24.0, viewport.width - 56.0, 74.0);
    draw_glass_panel(header, 26.0, palette, palette.accent, true);
    draw_text_line(
        "Code Output",
        header.x + 26.0,
        header.y + 42.0,
        31.0,
        to_mq(palette.text),
    );
    draw_text_line(
        "sanitized Markdown/code rows with renderer-neutral token classes",
        header.x + 292.0,
        header.y + 40.0,
        17.0,
        to_mq(palette.muted),
    );
    draw_pill(
        Rect::new(header.right() - 376.0, header.y + 20.0, 142.0, 32.0),
        &format!("{} rows", doc.rows.len()),
        palette.success,
        palette.text,
    );
    draw_pill(
        Rect::new(header.right() - 220.0, header.y + 20.0, 198.0, 32.0),
        &format!("{} escapes removed", doc.removed_escape_sequences),
        palette.warning,
        palette.text,
    );
}

fn draw_output_document(viewport: Rect, palette: &Palette, doc: &OutputDocument) {
    let rect = Rect::new(28.0, 126.0, viewport.width * 0.66, viewport.height - 154.0);
    draw_glass_panel(rect, 30.0, palette, palette.success, true);
    draw_section_header(
        "RENDERED OUTPUT ROWS",
        Rect::new(rect.x + 26.0, rect.y + 38.0, rect.width - 52.0, 18.0),
        palette.success,
        palette.muted,
    );

    let mut y = rect.y + 78.0;
    let left = rect.x + 30.0;
    let width = rect.width - 60.0;
    let mut in_code = false;

    for row in doc.visible_rows(0, 22) {
        match row {
            OutputRow::Blank => y += 14.0,
            OutputRow::Heading { level, text } => {
                draw_text_line(&format!("H{level}"), left, y, 13.0, to_mq(palette.accent));
                draw_text_line(text, left + 42.0, y, 24.0, to_mq(palette.text));
                y += 36.0;
            }
            OutputRow::Paragraph { text, .. } => {
                y = draw_wrapped_text(
                    text,
                    left + 42.0,
                    y,
                    width - 42.0,
                    16.0,
                    22.0,
                    to_mq(palette.text),
                );
            }
            OutputRow::Bullet { text, .. } => {
                draw_text_line("•", left + 8.0, y, 19.0, to_mq(palette.warning));
                y = draw_wrapped_text(
                    text,
                    left + 42.0,
                    y,
                    width - 42.0,
                    16.0,
                    22.0,
                    to_mq(palette.text),
                );
            }
            OutputRow::Quote { text, .. } => {
                draw_line(
                    left + 8.0,
                    y - 16.0,
                    left + 8.0,
                    y + 10.0,
                    2.0,
                    to_mq(palette.accent),
                );
                y = draw_wrapped_text(
                    text,
                    left + 28.0,
                    y,
                    width - 28.0,
                    16.0,
                    22.0,
                    to_mq(palette.muted),
                );
            }
            OutputRow::CodeFenceStart { language } => {
                in_code = true;
                draw_pill(
                    Rect::new(left, y - 18.0, 104.0, 26.0),
                    language,
                    palette.accent,
                    palette.text,
                );
                y += 18.0;
            }
            OutputRow::Code { gutter, tokens, .. } => {
                let line_rect = Rect::new(left, y - 18.0, width, 26.0);
                draw_rounded_rect(
                    line_rect,
                    8.0,
                    rgba8(1, 5, 16, if in_code { 150 } else { 80 }),
                );
                draw_code_text_line(
                    gutter.as_deref().unwrap_or(""),
                    left + 12.0,
                    y,
                    14.0,
                    to_mq(palette.muted),
                );
                let mut x = left + 68.0;
                for token in tokens {
                    let color = token_color(token.kind, palette);
                    draw_code_text_line(&token.text, x, y, 14.0, to_mq(color));
                    x += measure_code_text_width(&token.text, 14.0);
                }
                y += 29.0;
            }
            OutputRow::CodeFenceEnd => {
                in_code = false;
                y += 12.0;
            }
        }
    }

    draw_materialization_pipeline(
        Rect::new(
            rect.x + 30.0,
            rect.bottom() - 248.0,
            rect.width - 60.0,
            206.0,
        ),
        palette,
        doc,
    );
}

fn draw_materialization_pipeline(rect: Rect, palette: &Palette, doc: &OutputDocument) {
    draw_rounded_rect(rect, 24.0, rgba8(1, 5, 16, 118));
    draw_rounded_rect_lines(rect, 24.0, 1.0, to_mq(palette.accent.with_alpha(0.24)));
    draw_text_line(
        "materialization pipeline",
        rect.x + 18.0,
        rect.y + 30.0,
        17.0,
        to_mq(palette.text),
    );
    draw_text_line(
        "terminal text becomes renderer-neutral rows before any pixels are drawn",
        rect.x + 220.0,
        rect.y + 29.0,
        13.0,
        to_mq(palette.muted),
    );

    let code_rows = doc
        .rows
        .iter()
        .filter(|row| matches!(row, OutputRow::Code { .. }))
        .count();
    let stages = [
        (
            "sanitize",
            format!("{} esc", doc.removed_escape_sequences),
            palette.warning,
        ),
        ("parse", format!("{} rows", doc.rows.len()), palette.accent),
        ("tokenize", format!("{} code", code_rows), palette.success),
        (
            "copy-safe",
            format!("{}b", doc.copy_text.len()),
            Rgba::rgb8(205, 130, 255),
        ),
    ];
    let gap = 14.0;
    let card_w = (rect.width - 36.0 - gap * 3.0) / 4.0;
    let card_y = rect.y + 56.0;
    for (index, (label, value, color)) in stages.iter().enumerate() {
        let card = Rect::new(
            rect.x + 18.0 + index as f32 * (card_w + gap),
            card_y,
            card_w,
            70.0,
        );
        draw_rounded_rect(card, 18.0, rgba8(0, 3, 12, 142));
        draw_rounded_rect_lines(card, 18.0, 1.0, to_mq(color.with_alpha(0.32)));
        draw_text_line(
            label,
            card.x + 14.0,
            card.y + 24.0,
            12.0,
            to_mq(palette.muted),
        );
        draw_text_line(value, card.x + 14.0, card.y + 54.0, 22.0, to_mq(*color));
        if index < stages.len() - 1 {
            let x = card.right() + gap * 0.34;
            draw_line(
                x,
                card.y + card.height * 0.5,
                x + gap * 0.32,
                card.y + card.height * 0.5,
                1.6,
                to_mq(palette.accent.with_alpha(0.34)),
            );
        }
    }

    let note = Rect::new(rect.x + 18.0, rect.bottom() - 54.0, rect.width - 36.0, 34.0);
    draw_rounded_rect(note, 16.0, to_mq(palette.success.with_alpha(0.10)));
    draw_text_line(
        "renderer contract: draw rows, preserve copy text, never paint raw ANSI/OSC controls",
        note.x + 14.0,
        note.y + 23.0,
        13.0,
        to_mq(palette.text),
    );
}

fn draw_copy_panel(viewport: Rect, palette: &Palette, doc: &OutputDocument) {
    let rect = Rect::new(
        viewport.width * 0.70,
        126.0,
        viewport.width * 0.30 - 28.0,
        viewport.height - 154.0,
    );
    draw_glass_panel(rect, 30.0, palette, palette.warning, false);
    draw_section_header(
        "SANITIZATION",
        Rect::new(rect.x + 24.0, rect.y + 38.0, rect.width - 48.0, 18.0),
        palette.warning,
        palette.muted,
    );

    let metric_w = (rect.width - 66.0) / 2.0;
    draw_metric(
        Rect::new(rect.x + 24.0, rect.y + 78.0, metric_w, 70.0),
        "escapes",
        &doc.removed_escape_sequences.to_string(),
        palette.warning,
        palette,
    );
    draw_metric(
        Rect::new(rect.x + 42.0 + metric_w, rect.y + 78.0, metric_w, 70.0),
        "controls",
        &doc.removed_control_chars.to_string(),
        palette.accent,
        palette,
    );
    draw_metric(
        Rect::new(rect.x + 24.0, rect.y + 164.0, metric_w, 70.0),
        "zero-width",
        &doc.removed_zero_width_chars.to_string(),
        palette.success,
        palette,
    );
    draw_metric(
        Rect::new(rect.x + 42.0 + metric_w, rect.y + 164.0, metric_w, 70.0),
        "truncated",
        if doc.truncated { "yes" } else { "no" },
        Rgba::rgb8(205, 130, 255),
        palette,
    );

    let copy = Rect::new(
        rect.x + 24.0,
        rect.y + 262.0,
        rect.width - 48.0,
        rect.height - 286.0,
    );
    draw_rounded_rect(copy, 20.0, rgba8(1, 5, 16, 150));
    draw_text_line(
        "copy-safe markdown",
        copy.x + 16.0,
        copy.y + 28.0,
        16.0,
        to_mq(palette.accent),
    );
    draw_wrapped_text(
        &doc.copy_text,
        copy.x + 16.0,
        copy.y + 58.0,
        copy.width - 32.0,
        13.0,
        18.0,
        to_mq(palette.muted),
    );
}

fn token_color(kind: CodeTokenKind, palette: &Palette) -> Rgba {
    match kind {
        CodeTokenKind::Text => palette.text,
        CodeTokenKind::Keyword => palette.accent,
        CodeTokenKind::String => palette.success,
        CodeTokenKind::Number => palette.warning,
        CodeTokenKind::Comment => palette.muted,
        CodeTokenKind::Punctuation => Rgba::rgb8(205, 130, 255),
    }
}