runBrowserProgram function
Future<void>
runBrowserProgram(
- Model model, {
- BrowserRunOptions options = const BrowserRunOptions(),
Runs a Model in the browser, rendering to an HTML5 canvas element.
This is the web/WASM program host. It:
- Creates or finds a canvas element
- Sets up a
CanvasTerminalRendererfor cell rendering - Creates a
WebTerminalBackendwith DOM event wiring - Creates a
WebUltravioletRendererthat bridges UV rendering to the canvas - Runs the program via runProgram
Implementation
Future<void> runBrowserProgram(
Model model, {
BrowserRunOptions options = const BrowserRunOptions(),
}) async {
final canvas = options.canvasId != null
? (web.document.getElementById(options.canvasId!)
as web.HTMLCanvasElement)
: web.document.createElement('canvas') as web.HTMLCanvasElement;
if (options.appendToBody && canvas.parentNode == null) {
web.document.body!.appendChild(canvas);
}
final context = canvas.getContext('2d') as web.CanvasRenderingContext2D;
final canvasRenderer = CanvasTerminalRenderer(
context,
fontSize: options.fontSize,
fontFamily: options.fontFamily,
);
canvasRenderer.measureFont();
final initialSize = _viewportTerminalSize(canvasRenderer);
_resizeCanvasToTerminal(canvas, canvasRenderer, initialSize);
final backend = WebTerminalBackend(initialSize: initialSize);
final terminal = BackendTerminal(backend);
final renderer = WebUltravioletRenderer(
terminal: terminal,
options: const TuiRendererOptions(
fps: 60,
altScreen: false,
hideCursor: true,
),
canvasRenderer: canvasRenderer,
);
final onKeyDown = ((web.Event event) {
final ke = event as web.KeyboardEvent;
event.preventDefault();
if (ke.key.length == 1) {
backend.addInput(ke.key.codeUnits);
} else {
_handleSpecialKey(ke, backend);
}
}).toJS;
web.document.addEventListener('keydown', onKeyDown);
var activeMouseButton = -1;
final onMouseDown = ((web.Event event) {
final mouse = event as web.MouseEvent;
event.preventDefault();
activeMouseButton = mouse.button;
final position = _mouseCellPosition(mouse, canvas, canvasRenderer);
final cb = _mouseButtonCode(mouse.button) + _mouseModifiers(mouse);
backend.addInput(
_encodeMouseEvent(
cb: cb,
col: position.col,
row: position.row,
release: false,
),
);
}).toJS;
final onMouseUp = ((web.Event event) {
final mouse = event as web.MouseEvent;
event.preventDefault();
final position = _mouseCellPosition(mouse, canvas, canvasRenderer);
final cb = 3 + _mouseModifiers(mouse);
activeMouseButton = -1;
backend.addInput(
_encodeMouseEvent(
cb: cb,
col: position.col,
row: position.row,
release: true,
),
);
}).toJS;
final onMouseMove = ((web.Event event) {
final mouse = event as web.MouseEvent;
if (activeMouseButton < 0) return;
event.preventDefault();
final position = _mouseCellPosition(mouse, canvas, canvasRenderer);
final cb =
32 + _mouseButtonCode(activeMouseButton) + _mouseModifiers(mouse);
backend.addInput(
_encodeMouseEvent(
cb: cb,
col: position.col,
row: position.row,
release: false,
),
);
}).toJS;
canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mouseup', onMouseUp);
canvas.addEventListener('mousemove', onMouseMove);
final onWheel = ((web.Event event) {
final wheel = event as web.WheelEvent;
event.preventDefault();
if (wheel.deltaY == 0) return;
final ticks = math.max(1, (wheel.deltaY.abs() / 40).round());
for (var i = 0; i < ticks; i++) {
backend.addInput(_encodeWheelEvent(wheel, canvas, canvasRenderer));
}
}).toJS;
canvas.addEventListener('wheel', onWheel);
final onResize = ((web.Event _) {
final size = _viewportTerminalSize(canvasRenderer);
_resizeCanvasToTerminal(canvas, canvasRenderer, size);
backend.notifySizeChanged(size);
}).toJS;
web.window.addEventListener('resize', onResize);
final onBeforeUnload = ((web.Event _) {
backend.requestShutdown();
}).toJS;
web.window.addEventListener('beforeunload', onBeforeUnload);
try {
await runProgram(model, host: null, terminal: terminal, renderer: renderer);
} finally {
web.document.removeEventListener('keydown', onKeyDown);
canvas.removeEventListener('mousedown', onMouseDown);
canvas.removeEventListener('mouseup', onMouseUp);
canvas.removeEventListener('mousemove', onMouseMove);
canvas.removeEventListener('wheel', onWheel);
web.window.removeEventListener('resize', onResize);
web.window.removeEventListener('beforeunload', onBeforeUnload);
backend.dispose();
}
}