mirror of
				https://github.com/nicbarker/clay.git
				synced 2025-11-04 00:26:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			857 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			857 lines
		
	
	
		
			46 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" />
 | 
						|
    <link rel="preload" href="/clay/fonts/Calistoga-Regular.ttf" as="font" type="font/ttf" crossorigin>
 | 
						|
    <link rel="preload" href="/clay/fonts/Quicksand-Semibold.ttf" as="font" type="font/ttf" crossorigin>
 | 
						|
    <title>Clay - UI Layout Library</title>
 | 
						|
    <style>
 | 
						|
        html, body {
 | 
						|
            width: 100%;
 | 
						|
            height: 100%;
 | 
						|
            overflow: hidden;
 | 
						|
            padding: 0;
 | 
						|
            margin: 0;
 | 
						|
            pointer-events: none;
 | 
						|
            background: rgb(244, 235, 230);
 | 
						|
        }
 | 
						|
        /* Import the font using @font-face */
 | 
						|
        @font-face {
 | 
						|
          font-family: 'Calistoga';
 | 
						|
          font-style: normal;
 | 
						|
          font-weight: 400;
 | 
						|
          src: url('/clay/fonts/Calistoga-Regular.ttf') format('truetype');
 | 
						|
        }
 | 
						|
 | 
						|
        @font-face {
 | 
						|
          font-family: 'Quicksand';
 | 
						|
          font-style: normal;
 | 
						|
          font-weight: 400;
 | 
						|
          src: url('/clay/fonts/Quicksand-Semibold.ttf') format('truetype');
 | 
						|
        }
 | 
						|
 | 
						|
        body > canvas {
 | 
						|
            width: 100%;
 | 
						|
            height: 100%;
 | 
						|
            touch-action: none;
 | 
						|
        }
 | 
						|
 | 
						|
        div, a, img {
 | 
						|
            position: absolute;
 | 
						|
            box-sizing: border-box;
 | 
						|
            -webkit-backface-visibility: hidden;
 | 
						|
            pointer-events: none;
 | 
						|
        }
 | 
						|
 | 
						|
        a {
 | 
						|
            cursor: pointer;
 | 
						|
            pointer-events: all;
 | 
						|
        }
 | 
						|
 | 
						|
        .text {
 | 
						|
            pointer-events: all;
 | 
						|
            white-space: pre;
 | 
						|
        }
 | 
						|
 | 
						|
        /* TODO special exception for text selection in debug tools */
 | 
						|
        [id='2067877626'] > * {
 | 
						|
            pointer-events: none !important;
 | 
						|
        }
 | 
						|
    </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 = 8;
 | 
						|
    let heapSpaceAddress = 0;
 | 
						|
    let memoryDataView;
 | 
						|
    let textDecoder = new TextDecoder("utf-8");
 | 
						|
    let previousFrameTime;
 | 
						|
    let fontsById = [
 | 
						|
        'Quicksand',
 | 
						|
        'Calistoga',
 | 
						|
        'Quicksand',
 | 
						|
        'Quicksand',
 | 
						|
        'Quicksand',
 | 
						|
    ];
 | 
						|
    let elementCache = {};
 | 
						|
    let imageCache = {};
 | 
						|
    let dimensionsDefinition = { type: 'struct', members: [
 | 
						|
        {name: 'width', type: 'float'},
 | 
						|
        {name: 'height', type: 'float'},
 | 
						|
    ]};
 | 
						|
    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: 'isStaticallyAllocated', type: 'uint32_t'},
 | 
						|
        {name: 'length', type: 'uint32_t' },
 | 
						|
        {name: 'chars', type: 'uint32_t' },
 | 
						|
    ]};
 | 
						|
    let stringSliceDefinition = { type: 'struct', members: [
 | 
						|
        {name: 'length', type: 'uint32_t' },
 | 
						|
        {name: 'chars', type: 'uint32_t' },
 | 
						|
        {name: 'baseChars', type: 'uint32_t' },
 | 
						|
    ]};
 | 
						|
    let borderWidthDefinition = { type: 'struct', members: [
 | 
						|
        {name: 'left', type: 'uint16_t'},
 | 
						|
        {name: 'right', type: 'uint16_t'},
 | 
						|
        {name: 'top', type: 'uint16_t'},
 | 
						|
        {name: 'bottom', type: 'uint16_t'},
 | 
						|
        {name: 'betweenChildren', type: 'uint16_t'},
 | 
						|
    ]};
 | 
						|
    let cornerRadiusDefinition = { type: 'struct', members: [
 | 
						|
        {name: 'topLeft', type: 'float'},
 | 
						|
        {name: 'topRight', type: 'float'},
 | 
						|
        {name: 'bottomLeft', type: 'float'},
 | 
						|
        {name: 'bottomRight', type: 'float'},
 | 
						|
    ]};
 | 
						|
    let textConfigDefinition = { name: 'text', type: 'struct', members: [
 | 
						|
        { name: 'userData', type: 'uint32_t' },
 | 
						|
        { name: 'textColor', ...colorDefinition },
 | 
						|
        { name: 'fontId', type: 'uint16_t' },
 | 
						|
        { name: 'fontSize', type: 'uint16_t' },
 | 
						|
        { name: 'letterSpacing', type: 'uint16_t' },
 | 
						|
        { name: 'lineSpacing', type: 'uint16_t' },
 | 
						|
        { name: 'wrapMode', type: 'uint8_t' },
 | 
						|
        { name: 'disablePointerEvents', type: 'uint8_t' },
 | 
						|
        { name: '_padding', type: 'uint16_t' },
 | 
						|
    ]};
 | 
						|
    let textRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'stringContents', ...stringSliceDefinition },
 | 
						|
        { name: 'textColor', ...colorDefinition },
 | 
						|
        { name: 'fontId', type: 'uint16_t' },
 | 
						|
        { name: 'fontSize', type: 'uint16_t' },
 | 
						|
        { name: 'letterSpacing', type: 'uint16_t' },
 | 
						|
        { name: 'lineHeight', type: 'uint16_t' },
 | 
						|
    ]};
 | 
						|
    let rectangleRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'backgroundColor', ...colorDefinition },
 | 
						|
        { name: 'cornerRadius', ...cornerRadiusDefinition },
 | 
						|
    ]};
 | 
						|
    let imageRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'backgroundColor', ...colorDefinition },
 | 
						|
        { name: 'cornerRadius', ...cornerRadiusDefinition },
 | 
						|
        { name: 'imageData', type: 'uint32_t' },
 | 
						|
    ]};
 | 
						|
    let customRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'backgroundColor', ...colorDefinition },
 | 
						|
        { name: 'cornerRadius', ...cornerRadiusDefinition },
 | 
						|
        { name: 'customData', type: 'uint32_t' },
 | 
						|
    ]};
 | 
						|
    let borderRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'color', ...colorDefinition },
 | 
						|
        { name: 'cornerRadius', ...cornerRadiusDefinition },
 | 
						|
        { name: 'width', ...borderWidthDefinition },
 | 
						|
        { name: 'padding', type: 'uint16_t'}
 | 
						|
    ]};
 | 
						|
    let clipRenderDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'horizontal', type: 'bool' },
 | 
						|
        { name: 'vertical', type: 'bool' },
 | 
						|
    ]};
 | 
						|
    let customHTMLDataDefinition = { type: 'struct', members: [
 | 
						|
        { name: 'link', ...stringDefinition },
 | 
						|
        { name: 'cursorPointer', type: 'uint8_t' },
 | 
						|
        { name: 'disablePointerEvents', type: 'uint8_t' },
 | 
						|
        { name: 'padding', type: 'uint16_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: 'renderData', type: 'union', members: [
 | 
						|
                { name: 'rectangle', ...rectangleRenderDataDefinition },
 | 
						|
                { name: 'text', ...textRenderDataDefinition },
 | 
						|
                { name: 'image', ...imageRenderDataDefinition },
 | 
						|
                { name: 'custom', ...customRenderDataDefinition },
 | 
						|
                { name: 'border', ...borderRenderDataDefinition },
 | 
						|
                { name: 'clip', ...clipRenderDataDefinition },
 | 
						|
            ]},
 | 
						|
            { name: 'userData', type: 'uint32_t'},
 | 
						|
            { name: 'id', type: 'uint32_t' },
 | 
						|
            { name: 'zIndex', type: 'int16_t' },
 | 
						|
            { name: 'commandType', type: 'uint8_t' },
 | 
						|
            { name: '_padding', type: 'uint8_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 'int32_t': return 4;
 | 
						|
            case 'uint16_t': return 2;
 | 
						|
            case 'int16_t': return 2;
 | 
						|
            case 'uint8_t': return 1;
 | 
						|
            case 'bool': 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 'int32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
 | 
						|
            case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
 | 
						|
            case 'int16_t': return { value: memoryDataView.getInt16(address, true), __size: 2 };
 | 
						|
            case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
 | 
						|
            case 'bool': return { value: memoryDataView.getUint8(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() {
 | 
						|
        await Promise.all(fontsById.map(f => document.fonts.load(`12px "${f}"`)));
 | 
						|
        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;
 | 
						|
        window.arrowKeyDownPressedThisFrame = false;
 | 
						|
        window.arrowKeyUpPressedThisFrame = false;
 | 
						|
        let zeroTimeout = null;
 | 
						|
        document.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;
 | 
						|
                let target = event.target;
 | 
						|
                let scrollTop = 0;
 | 
						|
                let scrollLeft = 0;
 | 
						|
                let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
 | 
						|
                while (activeRendererIndex !== 1 && target) {
 | 
						|
                    scrollLeft += target.scrollLeft;
 | 
						|
                    scrollTop += target.scrollTop;
 | 
						|
                    target = target.parentElement;
 | 
						|
                }
 | 
						|
                window.mousePositionXThisFrame = event.changedTouches[0].pageX + scrollLeft;
 | 
						|
                window.mousePositionYThisFrame = event.changedTouches[0].pageY + scrollTop;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        document.addEventListener("touchstart", handleTouch);
 | 
						|
        document.addEventListener("touchmove", handleTouch);
 | 
						|
        document.addEventListener("touchend", () => {
 | 
						|
            window.touchDown = false;
 | 
						|
            window.mousePositionXThisFrame = 0;
 | 
						|
            window.mousePositionYThisFrame = 0;
 | 
						|
        })
 | 
						|
 | 
						|
        document.addEventListener("mousemove", (event) => {
 | 
						|
            let target = event.target;
 | 
						|
            let scrollTop = 0;
 | 
						|
            let scrollLeft = 0;
 | 
						|
            let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
 | 
						|
            while (activeRendererIndex !== 1 && target) {
 | 
						|
                scrollLeft += target.scrollLeft;
 | 
						|
                scrollTop += target.scrollTop;
 | 
						|
                target = target.parentElement;
 | 
						|
            }
 | 
						|
            window.mousePositionXThisFrame = event.x + scrollLeft;
 | 
						|
            window.mousePositionYThisFrame = event.y + scrollTop;
 | 
						|
        });
 | 
						|
 | 
						|
        document.addEventListener("mousedown", (event) => {
 | 
						|
            window.mouseDown = true;
 | 
						|
            window.mouseDownThisFrame = true;
 | 
						|
        });
 | 
						|
 | 
						|
        document.addEventListener("mouseup", (event) => {
 | 
						|
            window.mouseDown = false;
 | 
						|
        });
 | 
						|
 | 
						|
        document.addEventListener("keydown", (event) => {
 | 
						|
            if (event.key === "ArrowDown") {
 | 
						|
                window.arrowKeyDownPressedThisFrame = true;
 | 
						|
            }
 | 
						|
            if (event.key === "ArrowUp") {
 | 
						|
                window.arrowKeyUpPressedThisFrame = true;
 | 
						|
            }
 | 
						|
            if (event.key === "d") {
 | 
						|
                window.dKeyPressedThisFrame = true;
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        const importObject = {
 | 
						|
            clay: {
 | 
						|
                measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
 | 
						|
                    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);
 | 
						|
                },
 | 
						|
                queryScrollOffsetFunction: (addressOfOffset, elementId) => {
 | 
						|
                    let container = document.getElementById(elementId.toString());
 | 
						|
                    if (container) {
 | 
						|
                        memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
 | 
						|
                        memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
 | 
						|
                    }
 | 
						|
                },
 | 
						|
            },
 | 
						|
        };
 | 
						|
        const { instance } = await WebAssembly.instantiateStreaming(
 | 
						|
            fetch("/clay/index.wasm"), importObject
 | 
						|
        );
 | 
						|
        memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
 | 
						|
        scratchSpaceAddress = instance.exports.__heap_base.value;
 | 
						|
        let clayScratchSpaceAddress = instance.exports.__heap_base.value + 1024;
 | 
						|
        heapSpaceAddress = instance.exports.__heap_base.value + 2048;
 | 
						|
        let arenaAddress = scratchSpaceAddress + 8;
 | 
						|
        window.instance = instance;
 | 
						|
        createMainArena(arenaAddress, heapSpaceAddress);
 | 
						|
        memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true);
 | 
						|
        memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true);
 | 
						|
        instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value);
 | 
						|
        instance.exports.SetScratchMemory(clayScratchSpaceAddress);
 | 
						|
        renderCommandSize = getStructTotalSize(renderCommandDefinition);
 | 
						|
        renderLoop();
 | 
						|
    }
 | 
						|
 | 
						|
    function MemoryIsDifferent(one, two, length) {
 | 
						|
        for (let i = 0; i < length; i++) {
 | 
						|
            if (one[i] !== two[i]) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    function SetElementBackgroundColorAndRadius(element, cornerRadius, backgroundColor) {
 | 
						|
        element.style.backgroundColor = `rgba(${backgroundColor.r.value}, ${backgroundColor.g.value}, ${backgroundColor.b.value}, ${backgroundColor.a.value / 255})`;
 | 
						|
        if (cornerRadius.topLeft.value > 0) {
 | 
						|
            element.style.borderTopLeftRadius = cornerRadius.topLeft.value + 'px';
 | 
						|
        }
 | 
						|
        if (cornerRadius.topRight.value > 0) {
 | 
						|
            element.style.borderTopRightRadius = cornerRadius.topRight.value + 'px';
 | 
						|
        }
 | 
						|
        if (cornerRadius.bottomLeft.value > 0) {
 | 
						|
            element.style.borderBottomLeftRadius = cornerRadius.bottomLeft.value + 'px';
 | 
						|
        }
 | 
						|
        if (cornerRadius.bottomRight.value > 0) {
 | 
						|
            element.style.borderBottomRightRadius = cornerRadius.bottomRight.value + 'px';
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    function renderLoopHTML() {
 | 
						|
        let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
 | 
						|
        let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
 | 
						|
        let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
 | 
						|
        let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
 | 
						|
        let previousId = 0;
 | 
						|
        for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
 | 
						|
            let entireRenderCommandMemory = new Uint8Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
 | 
						|
            let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
 | 
						|
            let parentElement = scissorStack[scissorStack.length - 1];
 | 
						|
            let element = null;
 | 
						|
            let isMultiConfigElement = previousId === renderCommand.id.value;
 | 
						|
            if (!elementCache[renderCommand.id.value]) {
 | 
						|
                let elementType = 'div';
 | 
						|
                switch (renderCommand.commandType.value & 0xff) {
 | 
						|
                    case CLAY_RENDER_COMMAND_TYPE_TEXT:
 | 
						|
                    case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
 | 
						|
                        if (renderCommand.userData.value !== 0) {
 | 
						|
                            if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) {
 | 
						|
                                elementType = 'a';
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
 | 
						|
                        elementType = 'img'; break;
 | 
						|
                    }
 | 
						|
                    default: break;
 | 
						|
                }
 | 
						|
                element = document.createElement(elementType);
 | 
						|
                element.id = renderCommand.id.value;
 | 
						|
                if (renderCommand.commandType.value === CLAY_RENDER_COMMAND_TYPE_SCISSOR_START) {
 | 
						|
                    element.style.overflow = 'hidden';
 | 
						|
                }
 | 
						|
                elementCache[renderCommand.id.value] = {
 | 
						|
                    exists: true,
 | 
						|
                    element: element,
 | 
						|
                    previousMemoryCommand: new Uint8Array(0),
 | 
						|
                    previousMemoryConfig: new Uint8Array(0),
 | 
						|
                    previousMemoryText: new Uint8Array(0)
 | 
						|
                };
 | 
						|
            }
 | 
						|
 | 
						|
            let elementData = elementCache[renderCommand.id.value];
 | 
						|
            element = elementData.element;
 | 
						|
            if (!isMultiConfigElement && Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) {
 | 
						|
                if (parentElement.nextElementIndex === 0) {
 | 
						|
                    parentElement.element.insertAdjacentElement('afterbegin', element);
 | 
						|
                } else {
 | 
						|
                    parentElement.element.childNodes[Math.min(parentElement.nextElementIndex - 1, parentElement.element.childNodes.length - 1)].insertAdjacentElement('afterend', element);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            elementData.exists = true;
 | 
						|
            // Don't get me started. Cheaper to compare the render command memory than to update HTML elements
 | 
						|
            let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize) && !isMultiConfigElement;
 | 
						|
            if (!isMultiConfigElement) {
 | 
						|
                parentElement.nextElementIndex++;
 | 
						|
            }
 | 
						|
 | 
						|
            previousId = renderCommand.id.value;
 | 
						|
 | 
						|
            elementData.previousMemoryCommand = entireRenderCommandMemory;
 | 
						|
            let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0;
 | 
						|
            let offsetY = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.y : 0;
 | 
						|
            if (dirty) {
 | 
						|
                element.style.transform = `translate(${Math.round(renderCommand.boundingBox.x.value - offsetX)}px, ${Math.round(renderCommand.boundingBox.y.value - offsetY)}px)`
 | 
						|
                element.style.width = Math.round(renderCommand.boundingBox.width.value) + 'px';
 | 
						|
                element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
 | 
						|
            }
 | 
						|
 | 
						|
            // note: commandType is packed to uint8_t and has 3 garbage bytes of padding
 | 
						|
            switch(renderCommand.commandType.value & 0xff) {
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_NONE): {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
 | 
						|
                    let config = renderCommand.renderData.rectangle;
 | 
						|
                    let configMemory = JSON.stringify(config);
 | 
						|
                    if (configMemory === elementData.previousMemoryConfig) {
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    SetElementBackgroundColorAndRadius(element, config.cornerRadius, config.backgroundColor);
 | 
						|
                    if (renderCommand.userData.value !== 0) {
 | 
						|
                        let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
 | 
						|
 | 
						|
                        let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
 | 
						|
                        memoryDataView.setUint32(0, renderCommand.id.value, true);
 | 
						|
                        if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
 | 
						|
                            window.location.href = linkContents;
 | 
						|
                        }
 | 
						|
                        if (linkContents.length > 0) {
 | 
						|
                            element.href = linkContents;
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (linkContents.length > 0 || customData.cursorPointer.value) {
 | 
						|
                            element.style.pointerEvents = 'all';
 | 
						|
                            element.style.cursor = 'pointer';
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
 | 
						|
                    elementData.previousMemoryConfig = configMemory;
 | 
						|
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
 | 
						|
                    let config = renderCommand.renderData.border;
 | 
						|
                    let configMemory = JSON.stringify(config);
 | 
						|
                    if (configMemory === elementData.previousMemoryConfig) {
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    let color = config.color;
 | 
						|
                    elementData.previousMemoryConfig = configMemory;
 | 
						|
                    if (config.width.left.value > 0) {
 | 
						|
                        element.style.borderLeft = `${config.width.left.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
 | 
						|
                    }
 | 
						|
                    if (config.width.right.value > 0) {
 | 
						|
                        element.style.borderRight = `${config.width.right.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
 | 
						|
                    }
 | 
						|
                    if (config.width.top.value > 0) {
 | 
						|
                        element.style.borderTop = `${config.width.top.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
 | 
						|
                    }
 | 
						|
                    if (config.width.bottom.value > 0) {
 | 
						|
                        element.style.borderBottom = `${config.width.bottom.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
 | 
						|
                    }
 | 
						|
                    if (config.cornerRadius.topLeft.value > 0) {
 | 
						|
                        element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
 | 
						|
                    }
 | 
						|
                    if (config.cornerRadius.topRight.value > 0) {
 | 
						|
                        element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
 | 
						|
                    }
 | 
						|
                    if (config.cornerRadius.bottomLeft.value > 0) {
 | 
						|
                        element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
 | 
						|
                    }
 | 
						|
                    if (config.cornerRadius.bottomRight.value > 0) {
 | 
						|
                        element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px';
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
 | 
						|
                    let config = renderCommand.renderData.text;
 | 
						|
                    let configMemory = JSON.stringify(config);
 | 
						|
                    let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
 | 
						|
                    if (configMemory !== elementData.previousMemoryConfig) {
 | 
						|
                        element.className = 'text';
 | 
						|
                        let textColor = config.textColor;
 | 
						|
                        let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR);
 | 
						|
                        element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`;
 | 
						|
                        element.style.fontFamily = fontsById[config.fontId.value];
 | 
						|
                        element.style.fontSize = fontSize + 'px';
 | 
						|
                        if (renderCommand.userData.value !== 0) {
 | 
						|
                            let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
 | 
						|
                            element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
 | 
						|
                            let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
 | 
						|
                            memoryDataView.setUint32(0, renderCommand.id.value, true);
 | 
						|
                            if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
 | 
						|
                                window.location.href = linkContents;
 | 
						|
                            }
 | 
						|
                            if (linkContents.length > 0) {
 | 
						|
                                element.href = linkContents;
 | 
						|
                            }
 | 
						|
 | 
						|
                            if (linkContents.length > 0 || customData.cursorPointer.value) {
 | 
						|
                                element.style.pointerEvents = 'all';
 | 
						|
                                element.style.cursor = 'pointer';
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        elementData.previousMemoryConfig = configMemory;
 | 
						|
                    }
 | 
						|
                    if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
 | 
						|
                        element.innerHTML = textDecoder.decode(stringContents);
 | 
						|
                    }
 | 
						|
                    elementData.previousMemoryText = stringContents;
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
 | 
						|
                    scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 });
 | 
						|
                    let config = renderCommand.renderData.clip;
 | 
						|
                    let configMemory = JSON.stringify(config);
 | 
						|
                    if (configMemory === elementData.previousMemoryConfig) {
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    if (config.horizontal.value) {
 | 
						|
                        element.style.overflowX = 'scroll';
 | 
						|
                        element.style.pointerEvents = 'auto';
 | 
						|
                    }
 | 
						|
                    if (config.vertical.value) {
 | 
						|
                        element.style.overflowY = 'scroll';
 | 
						|
                        element.style.pointerEvents = 'auto';
 | 
						|
                    }
 | 
						|
                    elementData.previousMemoryConfig = configMemory;
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
 | 
						|
                    scissorStack.splice(scissorStack.length - 1, 1);
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
 | 
						|
                    let config = renderCommand.renderData.image;
 | 
						|
                    let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
 | 
						|
                    let srcContents = new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value));
 | 
						|
                    if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) {
 | 
						|
                        element.src = textDecoder.decode(srcContents);
 | 
						|
                    }
 | 
						|
                    elementData.previousMemoryText = srcContents;
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
 | 
						|
                default: {
 | 
						|
                    console.log("Error: unhandled render command");
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        for (const key of Object.keys(elementCache)) {
 | 
						|
            if (elementCache[key].exists) {
 | 
						|
                elementCache[key].exists = false;
 | 
						|
            } else {
 | 
						|
                elementCache[key].element.remove();
 | 
						|
                delete elementCache[key];
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
        window.canvasRoot.width = window.innerWidth * window.devicePixelRatio;
 | 
						|
        window.canvasRoot.height = window.innerHeight * window.devicePixelRatio;
 | 
						|
        window.canvasRoot.style.width = window.innerWidth + 'px';
 | 
						|
        window.canvasRoot.style.height = window.innerHeight + 'px';
 | 
						|
        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;
 | 
						|
            
 | 
						|
            // note: commandType is packed to uint8_t and has 3 garbage bytes of padding
 | 
						|
            switch(renderCommand.commandType.value & 0xff) {
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_NONE): {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
 | 
						|
                    let config = renderCommand.renderData.rectangle;
 | 
						|
                    let color = config.backgroundColor;
 | 
						|
                    ctx.beginPath();
 | 
						|
                    window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                    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
 | 
						|
                    if (renderCommand.userData.value !== 0) {
 | 
						|
                        let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
 | 
						|
                        let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
 | 
						|
                        memoryDataView.setUint32(0, renderCommand.id.value, true);
 | 
						|
                        if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
 | 
						|
                            window.location.href = linkContents;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
 | 
						|
                    let config = renderCommand.renderData.border;
 | 
						|
                    let color = config.color;
 | 
						|
                    ctx.beginPath();
 | 
						|
                    ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
 | 
						|
                    // Top Left Corner
 | 
						|
                    if (config.cornerRadius.topLeft.value > 0) {
 | 
						|
                        let lineWidth = config.width.top.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale);
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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.width.top.value > 0) {
 | 
						|
                        let lineWidth = config.width.top.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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.width.top.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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.width.right.value > 0) {
 | 
						|
                        let lineWidth = config.width.right.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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 lineWidth = config.width.top.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 / 255})`;
 | 
						|
                        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.width.bottom.value > 0) {
 | 
						|
                        let lineWidth = config.width.bottom.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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 lineWidth = config.width.bottom.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 / 255})`;
 | 
						|
                        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.width.left.value > 0) {
 | 
						|
                        let lineWidth = config.width.left.value;
 | 
						|
                        let halfLineWidth = lineWidth / 2;
 | 
						|
                        ctx.lineWidth = lineWidth * scale;
 | 
						|
                        ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
 | 
						|
                        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 = renderCommand.renderData.text;
 | 
						|
                    let textContents = config.stringContents;
 | 
						|
                    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 / 255})`;
 | 
						|
                    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.save();
 | 
						|
                    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();
 | 
						|
                    window.canvasContext.closePath();
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
 | 
						|
                    window.canvasContext.restore();
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
 | 
						|
                    let config = renderCommand.renderData.image;
 | 
						|
                    let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
 | 
						|
                    let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.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;
 | 
						|
        let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
 | 
						|
        if (activeRendererIndex === 0) {
 | 
						|
            instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, 0, 0, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, 0, 0, window.dKeyPressedThisFrame, elapsed / 1000);
 | 
						|
        } else {
 | 
						|
            instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, window.arrowKeyDownPressedThisFrame, window.arrowKeyUpPressedThisFrame, window.dKeyPressedThisFrame, elapsed / 1000);
 | 
						|
        }
 | 
						|
        let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex;
 | 
						|
        switch (activeRendererIndex) {
 | 
						|
            case 0: {
 | 
						|
                renderLoopHTML();
 | 
						|
                if (rendererChanged) {
 | 
						|
                    window.htmlRoot.style.display = 'block';
 | 
						|
                    window.canvasRoot.style.display = 'none';
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case 1: {
 | 
						|
                renderLoopCanvas();
 | 
						|
                if (rendererChanged) {
 | 
						|
                    window.htmlRoot.style.display = 'none';
 | 
						|
                    window.canvasRoot.style.display = 'block';
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        window.previousActiveRendererIndex = activeRendererIndex;
 | 
						|
        requestAnimationFrame(renderLoop);
 | 
						|
        window.mouseDownThisFrame = false;
 | 
						|
        window.arrowKeyUpPressedThisFrame = false;
 | 
						|
        window.arrowKeyDownPressedThisFrame = false;
 | 
						|
        window.dKeyPressedThisFrame = false;
 | 
						|
    }
 | 
						|
    init();
 | 
						|
</script>
 | 
						|
<body>
 | 
						|
</body>
 | 
						|
</html> |