mod support;
use macroquad::prelude::*;
use rae::{Palette, Rect, TranscriptItem, TranscriptViewport};
use support::*;
fn window_conf() -> Conf {
Conf {
window_title: "rae transcript viewport".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 items = transcript_items();
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);
draw_transcript_viewports(viewport, &palette, &items, time);
next_frame().await;
}
}
fn transcript_items() -> Vec<TranscriptItem> {
vec![
TranscriptItem::system("rae example session initialized"),
TranscriptItem::user("Show how transcript rows wrap and clamp across renderer sizes."),
TranscriptItem::assistant(
"TranscriptViewport computes columns from glyph metrics, wraps long rows, preserves role labels on first lines, marks detail rows as muted, and clamps scroll to the available content.",
"detail: renderer chooses fonts, colors, selection, and scroll gestures",
),
TranscriptItem::assistant(
"Because this crate stays renderer-neutral, the viewport output is just rows and flags.",
"This example renders two different viewport sizes from the same transcript model.",
),
]
}
fn draw_header(viewport: Rect, palette: &Palette) {
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(
"Transcript",
header.x + 26.0,
header.y + 42.0,
31.0,
to_mq(palette.text),
);
draw_text_line(
"viewport wrapping, muted detail rows, and clamped scroll windows",
header.x + 222.0,
header.y + 40.0,
17.0,
to_mq(palette.muted),
);
}
fn draw_transcript_viewports(
viewport: Rect,
palette: &Palette,
items: &[TranscriptItem],
time: f32,
) {
let area = Rect::new(28.0, 126.0, viewport.width - 56.0, viewport.height - 154.0);
let left = Rect::new(area.x, area.y, area.width * 0.62 - 12.0, area.height);
let right = Rect::new(
left.right() + 24.0,
area.y,
area.width * 0.38 - 12.0,
area.height,
);
draw_transcript_panel(
left,
palette,
"wide viewport",
TranscriptViewport::new(left.width - 64.0, left.height - 118.0).with_metrics(9.0, 22.0),
items,
palette.success,
time,
);
draw_transcript_panel(
right,
palette,
"narrow scrolled",
TranscriptViewport::new(right.width - 64.0, right.height - 118.0)
.with_metrics(8.5, 20.0)
.with_scroll(3),
items,
palette.warning,
time + 0.7,
);
}
fn draw_transcript_panel(
rect: Rect,
palette: &Palette,
title: &str,
viewport: TranscriptViewport,
items: &[TranscriptItem],
accent: rae::Rgba,
time: f32,
) {
draw_glass_panel(rect, 30.0, palette, accent, true);
let view = viewport.materialize(items);
draw_text_line(
title,
rect.x + 26.0,
rect.y + 38.0,
24.0,
to_mq(palette.text),
);
draw_text_line(
&format!(
"total {} visible {} scroll {}/{}",
view.total_rows, view.visible_rows, view.scroll, view.max_scroll
),
rect.x + 26.0,
rect.y + 64.0,
14.0,
to_mq(palette.muted),
);
draw_glow(
rect.right() - 80.0,
rect.y + 92.0,
120.0,
accent.with_alpha(0.10 + time.sin().abs() * 0.04),
6,
);
let body = Rect::new(
rect.x + 26.0,
rect.y + 96.0,
rect.width - 52.0,
rect.height - 122.0,
);
draw_rounded_rect(body, 22.0, rgba8(1, 5, 16, 150));
let mut y = body.y + 32.0;
for row in view.rows {
let role_color = match row.role.as_str() {
"user" => palette.warning,
"assistant" => palette.success,
"system" => palette.accent,
_ => palette.muted,
};
if !row.role.is_empty() {
draw_pill(
Rect::new(body.x + 18.0, y - 18.0, 86.0, 25.0),
&row.role,
role_color,
palette.text,
);
}
let color = if row.muted {
palette.muted
} else {
palette.text
};
draw_wrapped_text(
&row.text,
body.x + 124.0,
y,
body.width - 146.0,
15.0,
20.0,
to_mq(color),
);
y += 36.0;
}
}