Debug panels ​
Tune a renderer or an example from JavaScript, with no template: the facade builds the fields from a plain object and writes edits back into it. Add a frame-time overlay with jolly-stats.
1. Create a pane ​
ts
import { Pane } from "@jolly-pixel/ui/facade";
const settings = {
enabled: true,
opacity: 0.75,
mode: "overlay",
position: new THREE.Vector3(0, 0, 0)
};
const pane = new Pane({ title: "Renderer" });- With no
container, the pane floats underdocument.body, in a scope of its own. It can be moved and resized, and remembers where it was. pane.dispose()removes it, with its window and scope.
2. Bind properties ​
ts
pane.addBinding(settings, "enabled");
pane.addBinding(settings, "opacity", { min: 0, max: 1, step: 0.01 });
pane.addBinding(settings, "mode", {
options: { Off: "off", Overlay: "overlay", Wireframe: "wireframe" }
});
pane.addBinding(settings, "position", { step: 1 });The value picks the control: a checkbox, a slider, a select and a vector row. See control selection.
3. React to edits ​
ts
pane
.addBinding(settings, "mode")
.on("change", ({ value, last }) => {
renderer.debugMode = value;
if (last) {
saveSettings(settings);
}
});- The object is already updated when the handler runs.
lastistrueonce per committed edit, as in Preview and commit.- A
THREE.Vector3is written axis by axis, so the object keeps its identity.
4. Group and add actions ​
ts
const display = pane.addFolder({ title: "Display" });
display.addBinding(settings, "opacity", { min: 0, max: 1, step: 0.01 });
display.addSeparator();
display
.addButton({ title: "Reset opacity" })
.on("click", () => {
settings.opacity = 1;
display.refresh();
});Bindings do not observe assignments: call refresh() after changing the object yourself.
5. Show live values ​
ts
const info = { calls: 0, frameMs: 0 };
const perf = pane.addFolder({ title: "Performance" });
perf.addMonitors(info, {
calls: { label: "Draw calls" },
frameMs: { label: "Frame", format: (value) => `${value.toFixed(1)} ms` }
});
function onFrame(): void {
info.calls = renderer.info.render.drawCalls;
info.frameMs = renderer.frameTime;
perf.refresh();
}Monitors have no timer. Refresh at the rate the numbers are worth reading, not every frame of a fast loop.
6. Dock the panes on one side ​
Several debug panes read better in one collapsible dock than as separate windows:
html
<jolly-scope>
<jolly-dock-layout storage-key="examples:layout">
<jolly-dock id="tools" key="tools" side="right" align="start" size="340"
overlay collapsible></jolly-dock>
</jolly-dock-layout>
</jolly-scope>ts
import { DockFacade, Pane } from "@jolly-pixel/ui/facade";
const dock = DockFacade.query("#tools");
const config = new Pane({
title: "Configuration",
container: dock.element,
grow: false,
locked: true
});
config.addThemePreferences({ storageKey: "examples" });
const pane = new Pane({
title: "Renderer",
container: dock.element,
grow: false,
collapsible: true
});
dock.sync();
document.addEventListener("keydown", (event) => {
if (event.key === "F3") {
event.preventDefault();
dock.hidden = !dock.hidden;
}
});overlaylays the dock over the canvas instead of shrinking it.dock.sync()lets the layout adopt the new panes. Call it after adding them.addThemePreferencesadds the theme and density controls, stored understorageKey.- A page shared by several examples should declare the dock once, in its HTML, rather than create it from each example.
7. Add a frame-time overlay ​
ts
import "@jolly-pixel/ui/elements/floating";
import "@jolly-pixel/ui/elements/stats";
import { StatsRecorder } from "@jolly-pixel/ui/stats";
const recorder = new StatsRecorder();
recorder.addMetric({
id: "calls",
label: "CALLS",
better: "lower",
sample: () => renderer.info.render.drawCalls
});
const stats = document.createElement("jolly-stats");
stats.recorder = recorder;
stats.storageKey = "examples:stats";
stats.style.width = "100%";
stats.style.height = "100%";
const floating = document.createElement("jolly-floating");
floating.x = 8;
floating.y = 8;
floating.width = 112;
floating.height = 56;
floating.storageKey = "examples:stats";
floating.append(stats);
document.querySelector("jolly-scope").append(floating);
function frame(): void {
recorder.begin();
renderer.render(scene, camera);
recorder.end();
requestAnimationFrame(frame);
}@jolly-pixel/ui/statsregisters no element:jolly-statsandjolly-floatingcome from theirelements/entry points.- Clicking the overlay cycles through the built-in metrics (FPS, frame time, worst frame, heap when the browser reports it) and the added ones. The choice is stored under
storageKey. sample()is read once per flush, every 250 ms by default. Usetrack(id, value)for values that arrive as events.- Call
recorder.skipGap()when the loop sleeps, so the pause does not show as a low FPS sample.