import type { CanvasRenderingContext2D } from "@napi-rs/canvas"; import type { Layout } from "./layout"; export type DebugStats = Record; export type DebugHudOptions = { showGlobal: boolean; showRenderer: boolean; showCustom?: boolean; }; export type DebugHudData = { global: DebugStats | string[]; renderer: { id: string; label: string; stats: DebugStats; fps: number; }; custom?: DebugStats; }; export type DebugHud = { draw: (ctx: CanvasRenderingContext2D, layout: Layout, data: DebugHudData) => void; }; export function createDebugHud(options: DebugHudOptions): DebugHud { const padding = 8; const lineHeight = 16; const bg = "rgba(0, 0, 0, 0.65)"; const fg = "yellow"; // global const rendererFg = "#ff66cc"; // renderer const customFg = "#00c6ff"; // custom const drawBlock = ( ctx: CanvasRenderingContext2D, x: number, y: number, title: string, stats: DebugStats | string[], color: string ): { width: number; height: number } => { let lines: string[] = []; if (Array.isArray(stats)) { lines = stats } else { const keys = Object.keys(stats); lines = [title, ...keys.map((k) => `${k}: ${String(stats[k])}`)]; } ctx.font = "14px \"JetBrains Mono\", monospace"; const textWidth = Math.max(...lines.map((l) => ctx.measureText(l).width)); const height = lines.length * lineHeight + padding * 2; const width = textWidth + padding * 2; ctx.save(); ctx.fillStyle = bg; ctx.fillRect(x, y, width, height); ctx.fillStyle = color; ctx.textBaseline = "top"; lines.forEach((line, i) => { ctx.fillText(line, x + padding, y + padding + i * lineHeight); }); ctx.restore(); return { width, height }; }; const draw = (ctx: CanvasRenderingContext2D, layout: Layout, data: DebugHudData) => { if (!options.showGlobal && !options.showRenderer && !options.showCustom) return; ctx.save(); ctx.imageSmoothingEnabled = false; ctx.globalAlpha = 0.9; let cursorY = padding; const originX = padding; if (options.showGlobal) { const { height } = drawBlock(ctx, originX, cursorY, "Global", data.global, fg); cursorY += height + padding; } if (options.showRenderer) { const { height } = drawBlock( ctx, originX, cursorY, `Renderer: ${data.renderer.label}`, { fps: data.renderer.fps.toFixed(2), ...data.renderer.stats }, rendererFg ); cursorY += height + padding; } if (options.showCustom && data.custom) { drawBlock(ctx, originX, cursorY, "Custom", data.custom, customFg); } ctx.restore(); }; return { draw }; }