mirror of
https://github.com/nicbarker/clay.git
synced 2025-01-24 18:36:10 +00:00
485 lines
25 KiB
HTML
485 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Clay - UI Layout Library</title>
|
|
<style>
|
|
html,
|
|
body {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
padding: 0;
|
|
margin: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Calistoga';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
src: url('/fonts/Calistoga-Regular.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Quicksand';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
src: url('/fonts/Quicksand-Semibold.ttf') format('truetype');
|
|
}
|
|
|
|
body>canvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
</style>
|
|
</head>
|
|
<script type="module">
|
|
const CLAY_RENDER_COMMAND_TYPE_NONE = 0;
|
|
const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1;
|
|
const CLAY_RENDER_COMMAND_TYPE_BORDER = 2;
|
|
const CLAY_RENDER_COMMAND_TYPE_TEXT = 3;
|
|
const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4;
|
|
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5;
|
|
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6;
|
|
const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7;
|
|
const GLOBAL_FONT_SCALING_FACTOR = 0.8;
|
|
let renderCommandSize = 0;
|
|
let scratchSpaceAddress = 0;
|
|
let heapSpaceAddress = 0;
|
|
let memoryDataView;
|
|
let textDecoder = new TextDecoder("utf-8");
|
|
let previousFrameTime;
|
|
let fontsById = [
|
|
// YOUR FONTS HERE
|
|
];
|
|
let elementCache = {};
|
|
let imageCache = {};
|
|
let colorDefinition = {
|
|
type: 'struct', members: [
|
|
{ name: 'r', type: 'float' },
|
|
{ name: 'g', type: 'float' },
|
|
{ name: 'b', type: 'float' },
|
|
{ name: 'a', type: 'float' },
|
|
]
|
|
};
|
|
let stringDefinition = {
|
|
type: 'struct', members: [
|
|
{ name: 'length', type: 'uint32_t' },
|
|
{ name: 'chars', type: 'uint32_t' },
|
|
]
|
|
};
|
|
let borderDefinition = {
|
|
type: 'struct', members: [
|
|
{ name: 'width', type: 'uint32_t' },
|
|
{ name: 'color', ...colorDefinition },
|
|
]
|
|
};
|
|
let cornerRadiusDefinition = {
|
|
type: 'struct', members: [
|
|
{ name: 'topLeft', type: 'float' },
|
|
{ name: 'topRight', type: 'float' },
|
|
{ name: 'bottomLeft', type: 'float' },
|
|
{ name: 'bottomRight', type: 'float' },
|
|
]
|
|
};
|
|
let rectangleConfigDefinition = {
|
|
name: 'rectangle', type: 'struct', members: [
|
|
{ name: 'color', ...colorDefinition },
|
|
{ name: 'cornerRadius', ...cornerRadiusDefinition },
|
|
{ name: 'link', ...stringDefinition },
|
|
{ name: 'cursorPointer', type: 'uint8_t' },
|
|
]
|
|
};
|
|
let borderConfigDefinition = {
|
|
name: 'text', type: 'struct', members: [
|
|
{ name: 'left', ...borderDefinition },
|
|
{ name: 'right', ...borderDefinition },
|
|
{ name: 'top', ...borderDefinition },
|
|
{ name: 'bottom', ...borderDefinition },
|
|
{ name: 'betweenChildren', ...borderDefinition },
|
|
{ name: 'cornerRadius', ...cornerRadiusDefinition }
|
|
]
|
|
};
|
|
let textConfigDefinition = {
|
|
name: 'text', type: 'struct', members: [
|
|
{ name: 'textColor', ...colorDefinition },
|
|
{ name: 'fontId', type: 'uint16_t' },
|
|
{ name: 'fontSize', type: 'uint16_t' },
|
|
{ name: 'letterSpacing', type: 'uint16_t' },
|
|
{ name: 'lineSpacing', type: 'uint16_t' },
|
|
{ name: 'disablePointerEvents', type: 'uint8_t' }
|
|
]
|
|
};
|
|
let imageConfigDefinition = {
|
|
name: 'image', type: 'struct', members: [
|
|
{ name: 'imageData', type: 'uint32_t' },
|
|
{
|
|
name: 'sourceDimensions', type: 'struct', members: [
|
|
{ name: 'width', type: 'float' },
|
|
{ name: 'height', type: 'float' },
|
|
]
|
|
},
|
|
{ name: 'sourceURL', ...stringDefinition }
|
|
]
|
|
};
|
|
let customConfigDefinition = {
|
|
name: 'custom', type: 'struct', members: [
|
|
{ name: 'customData', type: 'uint32_t' },
|
|
]
|
|
}
|
|
let renderCommandDefinition = {
|
|
name: 'CLay_RenderCommand',
|
|
type: 'struct',
|
|
members: [
|
|
{
|
|
name: 'boundingBox', type: 'struct', members: [
|
|
{ name: 'x', type: 'float' },
|
|
{ name: 'y', type: 'float' },
|
|
{ name: 'width', type: 'float' },
|
|
{ name: 'height', type: 'float' },
|
|
]
|
|
},
|
|
{ name: 'config', type: 'uint32_t' },
|
|
{ name: 'text', ...stringDefinition },
|
|
{ name: 'id', type: 'uint32_t' },
|
|
{ name: 'commandType', type: 'uint32_t', },
|
|
]
|
|
};
|
|
|
|
function getStructTotalSize(definition) {
|
|
switch (definition.type) {
|
|
case 'union':
|
|
case 'struct': {
|
|
let totalSize = 0;
|
|
for (const member of definition.members) {
|
|
let result = getStructTotalSize(member);
|
|
if (definition.type === 'struct') {
|
|
totalSize += result;
|
|
} else {
|
|
totalSize = Math.max(totalSize, result);
|
|
}
|
|
}
|
|
return totalSize;
|
|
}
|
|
case 'float': return 4;
|
|
case 'uint32_t': return 4;
|
|
case 'uint16_t': return 2;
|
|
case 'uint8_t': return 1;
|
|
default: {
|
|
throw "Unimplemented C data type " + definition.type
|
|
}
|
|
}
|
|
}
|
|
|
|
function readStructAtAddress(address, definition) {
|
|
switch (definition.type) {
|
|
case 'union':
|
|
case 'struct': {
|
|
let struct = { __size: 0 };
|
|
for (const member of definition.members) {
|
|
let result = readStructAtAddress(address, member);
|
|
struct[member.name] = result;
|
|
if (definition.type === 'struct') {
|
|
struct.__size += result.__size;
|
|
address += result.__size;
|
|
} else {
|
|
struct.__size = Math.max(struct.__size, result.__size);
|
|
}
|
|
}
|
|
return struct;
|
|
}
|
|
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
|
|
case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
|
|
case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
|
|
case 'uint8_t': return { value: memoryDataView.getUint16(address, true), __size: 1 };
|
|
default: {
|
|
throw "Unimplemented C data type " + definition.type
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTextDimensions(text, font) {
|
|
// re-use canvas object for better performance
|
|
window.canvasContext.font = font;
|
|
let metrics = window.canvasContext.measureText(text);
|
|
return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent };
|
|
}
|
|
|
|
function createMainArena(arenaStructAddress, arenaMemoryAddress) {
|
|
let memorySize = instance.exports.Clay_MinMemorySize();
|
|
// Last arg is address to store return value
|
|
instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress);
|
|
}
|
|
async function init() {
|
|
window.htmlRoot = document.body.appendChild(document.createElement('div'));
|
|
window.canvasRoot = document.body.appendChild(document.createElement('canvas'));
|
|
window.canvasContext = window.canvasRoot.getContext("2d");
|
|
window.mousePositionXThisFrame = 0;
|
|
window.mousePositionYThisFrame = 0;
|
|
window.mouseWheelXThisFrame = 0;
|
|
window.mouseWheelYThisFrame = 0;
|
|
window.touchDown = false;
|
|
let zeroTimeout = null;
|
|
addEventListener("wheel", (event) => {
|
|
window.mouseWheelXThisFrame = event.deltaX * -0.1;
|
|
window.mouseWheelYThisFrame = event.deltaY * -0.1;
|
|
clearTimeout(zeroTimeout);
|
|
zeroTimeout = setTimeout(() => {
|
|
window.mouseWheelXThisFrame = 0;
|
|
window.mouseWheelYThisFrame = 0;
|
|
}, 10);
|
|
});
|
|
|
|
function handleTouch(event) {
|
|
if (event.touches.length === 1) {
|
|
window.touchDown = true;
|
|
window.mousePositionXThisFrame = event.changedTouches[0].pageX;
|
|
window.mousePositionYThisFrame = event.changedTouches[0].pageY;
|
|
}
|
|
}
|
|
|
|
document.addEventListener("touchstart", handleTouch);
|
|
document.addEventListener("touchmove", handleTouch);
|
|
document.addEventListener("touchend", () => {
|
|
window.touchDown = false;
|
|
window.mousePositionXThisFrame = 0;
|
|
window.mousePositionYThisFrame = 0;
|
|
})
|
|
|
|
document.addEventListener("mousemove", (event) => {
|
|
window.mousePositionXThisFrame = event.x;
|
|
window.mousePositionYThisFrame = event.y;
|
|
});
|
|
|
|
document.addEventListener("mousedown", (event) => {
|
|
window.mouseDown = true;
|
|
});
|
|
|
|
const importObject = {
|
|
clay: {
|
|
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
|
|
let stringLength = memoryDataView.getUint32(textToMeasure, true);
|
|
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
|
|
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
|
|
let textDecoder = new TextDecoder("utf-8");
|
|
let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength));
|
|
let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`);
|
|
memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true);
|
|
memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true);
|
|
}
|
|
},
|
|
};
|
|
const { instance } = await WebAssembly.instantiateStreaming(
|
|
fetch("./index.wasm"), importObject
|
|
);
|
|
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
|
|
scratchSpaceAddress = instance.exports.__heap_base.value;
|
|
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
|
|
let arenaAddress = scratchSpaceAddress;
|
|
window.instance = instance;
|
|
createMainArena(arenaAddress, heapSpaceAddress);
|
|
instance.exports.Clay_Initialize(arenaAddress);
|
|
renderCommandSize = getStructTotalSize(renderCommandDefinition);
|
|
renderLoop();
|
|
}
|
|
|
|
function renderLoopCanvas() {
|
|
// Note: Rendering to canvas needs to be scaled up by window.devicePixelRatio in both width and height.
|
|
// e.g. if we're working on a device where devicePixelRatio is 2, we need to render
|
|
// everything at width^2 x height^2 resolution, then scale back down with css to get the correct pixel density.
|
|
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
|
|
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
|
|
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
|
|
if (window.previousWidth !== window.innerWidth) {
|
|
window.canvasRoot.width = window.innerWidth * window.devicePixelRatio;
|
|
window.previousWidth = window.innerWidth;
|
|
}
|
|
if (window.previousHeight !== window.innerHeight) {
|
|
window.canvasRoot.height = window.innerHeight * window.devicePixelRatio;
|
|
window.previousHeight = window.innerHeight;
|
|
}
|
|
let ctx = window.canvasContext;
|
|
let scale = window.devicePixelRatio;
|
|
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
|
|
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
|
let boundingBox = renderCommand.boundingBox;
|
|
switch (renderCommand.commandType.value) {
|
|
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
|
|
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
|
|
let color = config.color;
|
|
ctx.beginPath();
|
|
window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
window.canvasContext.roundRect(
|
|
boundingBox.x.value * scale, // x
|
|
boundingBox.y.value * scale, // y
|
|
boundingBox.width.value * scale, // width
|
|
boundingBox.height.value * scale,
|
|
[config.cornerRadius.topLeft.value * scale, config.cornerRadius.topRight.value * scale, config.cornerRadius.bottomRight.value * scale, config.cornerRadius.bottomLeft.value * scale]) // height;
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
// Handle link clicks
|
|
let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0;
|
|
if (linkContents.length > 0 && (window.mouseDown || window.touchDown) && instance.exports.Clay_PointerOver(renderCommand.id.value)) {
|
|
window.location.href = linkContents;
|
|
}
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
|
|
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
|
|
ctx.beginPath();
|
|
ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
|
|
// Top Left Corner
|
|
if (config.cornerRadius.topLeft.value > 0) {
|
|
let lineWidth = config.top.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale);
|
|
let color = config.top.color;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Top border
|
|
if (config.top.width.value > 0) {
|
|
let lineWidth = config.top.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
let color = config.top.color;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
|
|
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Top Right Corner
|
|
if (config.cornerRadius.topRight.value > 0) {
|
|
let lineWidth = config.top.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
|
|
let color = config.top.color;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Right border
|
|
if (config.right.width.value > 0) {
|
|
let color = config.right.color;
|
|
let lineWidth = config.right.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale);
|
|
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.topRight.value - halfLineWidth) * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Bottom Right Corner
|
|
if (config.cornerRadius.bottomRight.value > 0) {
|
|
let color = config.top.color;
|
|
let lineWidth = config.top.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale);
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, config.cornerRadius.bottomRight.value * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Bottom Border
|
|
if (config.bottom.width.value > 0) {
|
|
let color = config.bottom.color;
|
|
let lineWidth = config.bottom.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
|
|
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Bottom Left Corner
|
|
if (config.cornerRadius.bottomLeft.value > 0) {
|
|
let color = config.bottom.color;
|
|
let lineWidth = config.bottom.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale, config.cornerRadius.bottomLeft.value * scale);
|
|
ctx.stroke();
|
|
}
|
|
// Left Border
|
|
if (config.left.width.value > 0) {
|
|
let color = config.left.color;
|
|
let lineWidth = config.left.width.value;
|
|
let halfLineWidth = lineWidth / 2;
|
|
ctx.lineWidth = lineWidth * scale;
|
|
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale);
|
|
ctx.lineTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.bottomRight.value + halfLineWidth) * scale);
|
|
ctx.stroke();
|
|
}
|
|
ctx.closePath();
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
|
|
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
|
|
let textContents = renderCommand.text;
|
|
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
|
|
let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale;
|
|
ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`;
|
|
let color = config.textColor;
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
|
ctx.fillText(textDecoder.decode(stringContents), boundingBox.x.value * scale, (boundingBox.y.value + boundingBox.height.value / 2 + 1) * scale);
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
|
|
window.canvasContext.beginPath();
|
|
window.canvasContext.rect(boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
|
|
window.canvasContext.clip();
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
|
|
window.canvasContext.restore();
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
|
|
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition);
|
|
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value)));
|
|
if (!imageCache[src]) {
|
|
imageCache[src] = {
|
|
image: new Image(),
|
|
loaded: false,
|
|
}
|
|
imageCache[src].image.onload = () => imageCache[src].loaded = true;
|
|
imageCache[src].image.src = src;
|
|
} else if (imageCache[src].loaded) {
|
|
ctx.drawImage(imageCache[src].image, boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
|
|
}
|
|
break;
|
|
}
|
|
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderLoop(currentTime) {
|
|
const elapsed = currentTime - previousFrameTime;
|
|
previousFrameTime = currentTime;
|
|
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, elapsed / 1000);
|
|
renderLoopCanvas();
|
|
requestAnimationFrame(renderLoop);
|
|
window.mouseDown = false;
|
|
}
|
|
init();
|
|
</script>
|
|
|
|
<body>
|
|
</body>
|
|
|
|
</html> |