Merge branch 'nicbarker:main' into main

This commit is contained in:
AICDG 2025-01-07 14:22:04 +08:00 committed by GitHub
commit faf2f926b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 1600 additions and 709 deletions

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
cmake-build-*/
.DS_Store
.idea/
build/
node_modules/
*.dSYM

View File

@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.27)
project(clay)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_subdirectory("examples/cpp-project-example")
# Don't try to compile C99 projects using MSVC

View File

@ -245,7 +245,7 @@ This ID (or, if not provided, an auto generated ID) will be forwarded to the fin
Clay provides several functions for handling mouse and pointer interactions.
All pointer interactions depend on the function `void Clay_SetPointerState(Clay_Vector2 position)` being called after each mouse position update and before any other clay functions.
All pointer interactions depend on the function `void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown)` being called after each mouse position update and before any other clay functions.
**During UI declaration**

View File

@ -18,7 +18,7 @@ when ODIN_OS == .Windows {
}
String :: struct {
length: c.int,
length: c.int32_t,
chars: [^]c.char,
}
@ -30,9 +30,8 @@ Dimensions :: struct {
}
Arena :: struct {
label: String,
nextAllocation: u64,
capacity: u64,
nextAllocation: uintptr,
capacity: uintptr,
memory: [^]c.char,
}
@ -259,8 +258,8 @@ LayoutConfig :: struct {
}
ClayArray :: struct($type: typeid) {
capacity: u32,
length: u32,
capacity: i32,
length: i32,
internalArray: [^]type,
}
@ -270,12 +269,33 @@ TypedConfig :: struct {
id: ElementId,
}
ErrorType :: enum {
TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,
ARENA_CAPACITY_EXCEEDED,
ELEMENTS_CAPACITY_EXCEEDED,
TEXT_MEASUREMENT_CAPACITY_EXCEEDED,
DUPLICATE_ID,
FLOATING_CONTAINER_PARENT_NOT_FOUND,
INTERNAL_ERROR,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr
}
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -87,7 +87,7 @@ LandingPageDesktop :: proc() {
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 56, fontId = FONT_ID_TITLE_56, textColor = COLOR_RED}),
)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
// if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}),
@ -125,7 +125,7 @@ LandingPageMobile :: proc() {
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}),
)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}),
@ -207,7 +207,7 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
}
if clay.UI(clay.ID("SyntaxPageRightImage"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) {
if clay.UI(
clay.ID("SyntaxPageRightImage"),
clay.ID("SyntaxPageRightImageInner"),
clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}),
clay.Image({imageData = &syntaxImage, sourceDimensions = {1136, 1194}}),
) {}
@ -255,7 +255,7 @@ LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, se
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("PerformanceLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text("High Performance", clay.TextConfig(titleTextConfig))
if clay.UI(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
clay.Text(
"Fast enough to recompute your entire UI every frame.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
@ -347,7 +347,7 @@ RendererButtonInactive :: proc(index: u32, text: string) {
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("RendererLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text("Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig))
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
clay.Text(
"Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
@ -366,7 +366,7 @@ RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.
clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}, layoutDirection = .TOP_TO_BOTTOM, childGap = 16}),
) {
clay.Text("Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}))
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {}
RendererButtonActive(0, "Raylib Renderer")
}
}
@ -426,7 +426,7 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(50)}, childAlignment = {y = .CENTER}, childGap = 24, padding = {x = 32}}),
) {
clay.Text("Clay", &headerTextConfig)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({})}})) {}
if (!mobileScreen) {
if clay.UI(clay.ID("LinkExamplesOuter"), clay.Layout({}), clay.Rectangle({color = {0, 0, 0, 0}})) {
@ -483,12 +483,18 @@ loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) {
raylib.SetTextureFilter(raylibFonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
}
errorHandler :: proc "c" (errorData: clay.ErrorData) {
if (errorData.errorType == clay.ErrorType.DUPLICATE_ID) {
}
}
main :: proc() {
minMemorySize: u32 = clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.SetMeasureTextFunction(measureText)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")

1314
clay.h

File diff suppressed because it is too large Load Diff

32
cmake/FindCairo.cmake Normal file
View File

@ -0,0 +1,32 @@
# Defines:
# CAIRO_FOUND - System has Cairo
# CAIRO_INCLUDE_DIRS - Cairo include directories
# CAIRO_LIBRARY - Cairo library
# Cairo::Cairo - Imported target
find_path(CAIRO_INCLUDE_DIRS
NAMES cairo/cairo.h
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES include
)
find_library(CAIRO_LIBRARY
NAMES cairo
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES lib lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Cairo
REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS
)
if(Cairo_FOUND AND NOT TARGET Cairo::Cairo)
add_library(Cairo::Cairo UNKNOWN IMPORTED)
set_target_properties(Cairo::Cairo PROPERTIES
IMPORTED_LOCATION "${CAIRO_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}"
)
endif()
mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY)

View File

@ -33,8 +33,9 @@ target_link_libraries(SDL2_video_demo PUBLIC
SDL2::SDL2-static
SDL2_ttf::SDL2_ttf-static
)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET SDL2_video_demo POST_BUILD

View File

@ -2,14 +2,19 @@ cmake_minimum_required(VERSION 3.27)
project(clay_examples_cairo_pdf_rendering C)
set(CMAKE_C_STANDARD 99)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake")
add_executable(clay_examples_cairo_pdf_rendering main.c)
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC .)
find_package(Cairo REQUIRED)
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC cairo)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS})
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC Cairo::Cairo)
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_cairo_pdf_rendering POST_BUILD

View File

@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
add_executable(clay_official_website main.c)
target_compile_options(clay_official_website PUBLIC -Wall -Werror -Wno-unknown-pragmas)
target_compile_options(clay_official_website PUBLIC -Wall -Werror -Wno-unknown-pragmas -Wno-error=missing-braces)
target_include_directories(clay_official_website PUBLIC .)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_RELEASE "-O3")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View File

@ -0,0 +1,789 @@
<!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 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: 'wrapMode', type: 'uint32_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' }
]};
let scrollConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
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 'int32_t': return 4;
case 'uint16_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 '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) => {
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;
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
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);
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 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 Uint32Array(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) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).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 = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
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;
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 (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
break;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || config.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
elementData.previousMemoryConfig = configMemory;
let color = config.color;
element.style.backgroundColor = `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_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
break;
}
elementData.previousMemoryConfig = configMemory;
if (config.left.width.value > 0) {
let color = config.left.color;
element.style.borderLeft = `${config.left.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.right.width.value > 0) {
let color = config.right.color;
element.style.borderRight = `${config.right.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.top.width.value > 0) {
let color = config.top.color;
element.style.borderTop = `${config.top.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.bottom.width.value > 0) {
let color = config.bottom.color;
element.style.borderBottom = `${config.bottom.width.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 = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
let textContents = renderCommand.text;
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
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';
element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all';
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 = readStructAtAddress(renderCommand.config.value, scrollConfigDefinition);
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
scissorStack.splice(scissorStack.length - 1, 1);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.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;
}
}
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 = 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 / 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
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;
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 = 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 / 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.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 / 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.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 / 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.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 / 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 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 / 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.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 / 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 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 / 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.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 / 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 = 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 / 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 = 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;
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>

Binary file not shown.

View File

@ -178,6 +178,7 @@
}
case 'float': return 4;
case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2;
case 'uint8_t': return 1;
case 'bool': return 1;
@ -206,6 +207,7 @@
}
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 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
@ -312,7 +314,6 @@
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
@ -358,8 +359,8 @@
}
function renderLoopHTML() {
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
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;
@ -425,7 +426,8 @@
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
switch(renderCommand.commandType.value) {
// 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;
}
@ -579,7 +581,9 @@
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
switch(renderCommand.commandType.value) {
// 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;
}

View File

@ -165,7 +165,7 @@ Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
void HighPerformancePageDesktop(float lerpValue) {
CLAY(CLAY_ID("PerformanceDesktop"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformancePageOuter"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 })) {
CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = 16 }) }})) {}
@ -187,7 +187,7 @@ void HighPerformancePageDesktop(float lerpValue) {
}
void HighPerformancePageMobile(float lerpValue) {
CLAY(CLAY_ID("PerformanceMobile"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformancePageOuter"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 })) {
CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = 16 }) }})) {}
@ -390,7 +390,11 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
windowWidth = width;
windowHeight = height;
Clay_SetLayoutDimensions((Clay_Dimensions) { width, height });
if (deltaTime == deltaTime) { // NaN propagation can cause pain here
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_LayoutElementHashMapItem *perfPage = Clay__GetHashMapItem(Clay_GetElementId(CLAY_STRING("PerformancePageOuter")).id);
// NaN propagation can cause pain here
float perfPageYOffset = perfPage->boundingBox.y + scrollContainerData.scrollPosition->y;
if (deltaTime == deltaTime && perfPageYOffset < height && perfPageYOffset + perfPage->boundingBox.height > 0) {
animationLerpValue += deltaTime;
if (animationLerpValue > 1) {
animationLerpValue -= 2;
@ -413,12 +417,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
}
if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY };
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
scrollbarData.mouseDown = true;
} else if (scrollbarData.mouseDown) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
Clay_Vector2 ratio = (Clay_Vector2) {
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
@ -434,12 +436,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
}
if (arrowKeyDownPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
}
} else if (arrowKeyUpPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
}

View File

@ -9,4 +9,4 @@ add_executable(clay_examples_cpp_project_example main.cpp)
target_include_directories(clay_examples_cpp_project_example PUBLIC .)
set(CMAKE_CXX_FLAGS_DEBUG "-Werror -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

View File

@ -24,11 +24,12 @@ target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC)
target_include_directories(clay_examples_introducing_clay_video_demo PUBLIC .)
target_link_libraries(clay_examples_introducing_clay_video_demo PUBLIC raylib)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_introducing_clay_video_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -79,10 +79,7 @@ int main(void) {
Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = (Clay_Arena) {
.memory = malloc((size_t)1024 * 1024 * 1024 * 1024),
.capacity = clayRequiredMemory
};
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
@ -302,4 +299,4 @@ int main(void) {
Clay_Raylib_Render(renderCommands);
EndDrawing();
}
}
}

View File

@ -24,11 +24,12 @@ target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC)
target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .)
target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -1,3 +1,3 @@
$NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__ALIGNMENT($TYPE$), arena)};
}

View File

@ -0,0 +1,3 @@
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__POINTER_ALIGNMENT, arena)};
}

View File

@ -1,6 +1,6 @@
typedef struct
CLAY__TYPEDEF($NAME$, struct
{
uint32_t capacity;
uint32_t length;
int32_t capacity;
int32_t length;
$TYPE$ *internalArray;
} $NAME$;
});

View File

@ -1,5 +1,5 @@
typedef struct
CLAY__TYPEDEF($NAME$Slice, struct
{
uint32_t length;
int32_t length;
$TYPE$ *internalArray;
} $NAME$Slice;
});

View File

@ -1,3 +1,3 @@
$TYPE$ *$NAME$_Get($NAME$ *array, int index) {
$TYPE$ *$NAME$_Get($NAME$ *array, int32_t index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,3 +1,3 @@
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int index) {
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int32_t index) {
return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,3 +1,3 @@
$TYPE$ $NAME$_Get($NAME$ *array, int index) {
$TYPE$ $NAME$_Get($NAME$ *array, int32_t index) {
return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,4 +1,4 @@
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int index) {
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int32_t index) {
if (Clay__Array_RangeCheck(index, array->length)) {
array->length--;
$TYPE$ removed = array->internalArray[index];

View File

@ -1,4 +1,4 @@
void $NAME$_Set($NAME$ *array, int index, $TYPE$ value) {
void $NAME$_Set($NAME$ *array, int32_t index, $TYPE$ value) {
if (Clay__Array_RangeCheck(index, array->capacity)) {
array->internalArray[index] = value;
array->length = index < array->length ? array->length : index + 1;

View File

@ -317,7 +317,9 @@
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
switch(renderCommand.commandType.value) {
// 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;
}

View File

@ -336,7 +336,7 @@
let element = null;
if (!elementCache[renderCommand.id.value]) {
let elementType = 'div';
switch (renderCommand.commandType.value) {
switch (renderCommand.commandType.value & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
elementType = 'a';
@ -384,7 +384,8 @@
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
switch(renderCommand.commandType.value) {
// 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;
}