mirror of
https://github.com/nicbarker/clay.git
synced 2025-05-14 14:28:06 +00:00
Compare commits
14 Commits
c0867bc316
...
acb9c2b150
Author | SHA1 | Date | |
---|---|---|---|
|
acb9c2b150 | ||
|
5a328da308 | ||
|
3030390038 | ||
|
92582f66d8 | ||
|
65d2122dd6 | ||
|
fd76ce62f3 | ||
|
a5983dee96 | ||
|
76c8e1f115 | ||
|
df79cc9e53 | ||
|
8718ce6303 | ||
|
c5726b68d9 | ||
|
81032d9457 | ||
|
73f30d10b6 | ||
|
8df5da50c7 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -5,3 +5,8 @@ cmake-build-release/
|
||||
node_modules/
|
||||
*.dSYM
|
||||
.vs/
|
||||
bindings/odin/clay-odin/tmp/
|
||||
|
||||
generator/__pycache__/
|
||||
|
||||
generator/generators/__pycache__/
|
||||
|
1
bindings/csharp/README
Normal file
1
bindings/csharp/README
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/Orcolom/clay-cs
|
@ -317,7 +317,7 @@ ElementDeclaration :: struct {
|
||||
userData: rawptr
|
||||
}
|
||||
|
||||
ErrorType :: enum {
|
||||
ErrorType :: enum EnumBackingType {
|
||||
TextMeasurementFunctionNotProvided,
|
||||
ArenaCapacityExceeded,
|
||||
ElementsCapacityExceeded,
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
53
clay.h
53
clay.h
@ -21,6 +21,11 @@
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#ifdef __JETBRAINS_IDE__
|
||||
// Help jetbrains IDEs like CLion and Rider with intellisense & debugging
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#endif
|
||||
|
||||
// -----------------------------------------
|
||||
// HEADER DECLARATIONS ---------------------
|
||||
// -----------------------------------------
|
||||
@ -424,6 +429,11 @@ typedef struct {
|
||||
void* customData;
|
||||
} Clay_CustomRenderData;
|
||||
|
||||
typedef struct {
|
||||
bool horizontal;
|
||||
bool vertical;
|
||||
} Clay_ScrollRenderData;
|
||||
|
||||
typedef struct {
|
||||
Clay_Color color;
|
||||
Clay_CornerRadius cornerRadius;
|
||||
@ -436,6 +446,7 @@ typedef union {
|
||||
Clay_ImageRenderData image;
|
||||
Clay_CustomRenderData custom;
|
||||
Clay_BorderRenderData border;
|
||||
Clay_ScrollRenderData scroll;
|
||||
} Clay_RenderData;
|
||||
|
||||
// Miscellaneous Structs & Enums ---------------------------------
|
||||
@ -1268,13 +1279,14 @@ Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) {
|
||||
return &Clay_LayoutElementHashMapItem_DEFAULT;
|
||||
}
|
||||
|
||||
void Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) {
|
||||
Clay_ElementId Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2));
|
||||
Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length, parentElement->id);
|
||||
openLayoutElement->id = elementId.id;
|
||||
Clay__AddHashMapItem(elementId, openLayoutElement, 0);
|
||||
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
|
||||
return elementId;
|
||||
}
|
||||
|
||||
bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConfigType type) {
|
||||
@ -1509,11 +1521,8 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
.errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."),
|
||||
.userData = context->errorHandler.userData });
|
||||
}
|
||||
if (declaration.id.id != 0) {
|
||||
Clay__AttachId(declaration.id);
|
||||
} else if (openLayoutElement->id == 0) {
|
||||
Clay__GenerateIdForAnonymousElement(openLayoutElement);
|
||||
}
|
||||
|
||||
Clay_ElementId openLayoutElementId = declaration.id;
|
||||
|
||||
openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length];
|
||||
Clay_SharedElementConfig *sharedConfig = NULL;
|
||||
@ -1566,6 +1575,9 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ROOT) {
|
||||
floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id;
|
||||
}
|
||||
if (!openLayoutElementId.id) {
|
||||
openLayoutElementId = Clay__HashString(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
|
||||
}
|
||||
Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) {
|
||||
.layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1),
|
||||
.parentId = floatingConfig.parentId,
|
||||
@ -1578,6 +1590,13 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
if (declaration.custom.customData) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration.custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
|
||||
}
|
||||
|
||||
if (openLayoutElementId.id != 0) {
|
||||
Clay__AttachId(openLayoutElementId);
|
||||
} else if (openLayoutElement->id == 0) {
|
||||
openLayoutElementId = Clay__GenerateIdForAnonymousElement(openLayoutElement);
|
||||
}
|
||||
|
||||
if (declaration.scroll.horizontal | declaration.scroll.vertical) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration.scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
|
||||
Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id);
|
||||
@ -2249,6 +2268,12 @@ void Clay__CalculateFinalLayout(void) {
|
||||
}
|
||||
case CLAY__ELEMENT_CONFIG_TYPE_SCROLL: {
|
||||
renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START;
|
||||
renderCommand.renderData = CLAY__INIT(Clay_RenderData) {
|
||||
.scroll = {
|
||||
.horizontal = elementConfig->config.scrollElementConfig->horizontal,
|
||||
.vertical = elementConfig->config.scrollElementConfig->vertical,
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: {
|
||||
@ -3095,11 +3120,11 @@ void Clay__RenderDebugView(void) {
|
||||
CLAY({{0}}) {
|
||||
CLAY_TEXT(CLAY_STRING("{ left: "), infoTextConfig);
|
||||
CLAY_TEXT(Clay__IntToString(borderConfig->width.left), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING("{ right: "), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING(", right: "), infoTextConfig);
|
||||
CLAY_TEXT(Clay__IntToString(borderConfig->width.right), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING("{ top: "), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING(", top: "), infoTextConfig);
|
||||
CLAY_TEXT(Clay__IntToString(borderConfig->width.top), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING("{ bottom: "), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING(", bottom: "), infoTextConfig);
|
||||
CLAY_TEXT(Clay__IntToString(borderConfig->width.bottom), infoTextConfig);
|
||||
CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig);
|
||||
}
|
||||
@ -3509,13 +3534,19 @@ CLAY_WASM_EXPORT("Clay_EndLayout")
|
||||
Clay_RenderCommandArray Clay_EndLayout(void) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay__CloseElement();
|
||||
if (context->debugModeEnabled) {
|
||||
bool elementsExceededBeforeDebugView = context->booleanWarnings.maxElementsExceeded;
|
||||
if (context->debugModeEnabled && !elementsExceededBeforeDebugView) {
|
||||
context->warningsEnabled = false;
|
||||
Clay__RenderDebugView();
|
||||
context->warningsEnabled = true;
|
||||
}
|
||||
if (context->booleanWarnings.maxElementsExceeded) {
|
||||
Clay_String message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount");
|
||||
Clay_String message;
|
||||
if (!elementsExceededBeforeDebugView) {
|
||||
message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount after adding the debug-view to the layout.");
|
||||
} else {
|
||||
message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount");
|
||||
}
|
||||
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) {
|
||||
.boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 },
|
||||
.renderData = { .text = { .stringContents = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .textColor = {255, 0, 0, 255}, .fontSize = 16 } },
|
||||
|
@ -86,6 +86,10 @@
|
||||
];
|
||||
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' },
|
||||
@ -101,9 +105,12 @@
|
||||
{name: 'chars', type: 'uint32_t' },
|
||||
{name: 'baseChars', type: 'uint32_t' },
|
||||
]};
|
||||
let borderDefinition = { type: 'struct', members: [
|
||||
{name: 'width', type: 'uint32_t'},
|
||||
{name: 'color', ...colorDefinition},
|
||||
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'},
|
||||
@ -111,44 +118,53 @@
|
||||
{name: 'bottomLeft', type: 'float'},
|
||||
{name: 'bottomRight', type: 'float'},
|
||||
]};
|
||||
let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [
|
||||
{ name: 'color', ...colorDefinition },
|
||||
{ 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 },
|
||||
]};
|
||||
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' }
|
||||
{ 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 scrollConfigDefinition = { name: 'text', type: 'struct', members: [
|
||||
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: 'sourceDimensions', ...dimensionsDefinition },
|
||||
{ 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 scrollRenderDataDefinition = { 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 sharedConfigDefinition = { name: 'shared', type: 'struct', members: [
|
||||
{ name: 'cornerRadius', ...cornerRadiusDefinition },
|
||||
let customHTMLDataDefinition = { type: 'struct', members: [
|
||||
{ name: 'link', ...stringDefinition },
|
||||
{ name: 'cursorPointer', type: 'uint8_t' },
|
||||
{ name: 'disablePointerEvents', type: 'uint8_t' },
|
||||
]};
|
||||
let renderCommandDefinition = {
|
||||
name: 'CLay_RenderCommand',
|
||||
@ -160,14 +176,19 @@
|
||||
{ name: 'width', type: 'float' },
|
||||
{ name: 'height', type: 'float' },
|
||||
]},
|
||||
{ name: 'config', type: 'uint32_t'},
|
||||
{ name: 'textOrSharedConfig', type: 'union', members: [
|
||||
{ name: 'text', ...stringSliceDefinition },
|
||||
{ name: 'sharedConfig', type: 'uint32_t' }
|
||||
{ name: 'renderData', type: 'union', members: [
|
||||
{ name: 'rectangle', ...rectangleRenderDataDefinition },
|
||||
{ name: 'text', ...textRenderDataDefinition },
|
||||
{ name: 'image', ...imageRenderDataDefinition },
|
||||
{ name: 'custom', ...customRenderDataDefinition },
|
||||
{ name: 'border', ...borderRenderDataDefinition },
|
||||
{ name: 'scroll', ...scrollRenderDataDefinition },
|
||||
]},
|
||||
{ name: 'zIndex', type: 'int32_t' },
|
||||
{ name: 'userData', type: 'uint32_t'},
|
||||
{ name: 'id', type: 'uint32_t' },
|
||||
{ name: 'commandType', type: 'uint32_t', },
|
||||
{ name: 'zIndex', type: 'int16_t' },
|
||||
{ name: 'commandType', type: 'uint8_t' },
|
||||
{ name: '_padding', type: 'uint8_t' },
|
||||
]
|
||||
};
|
||||
|
||||
@ -190,6 +211,7 @@
|
||||
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: {
|
||||
@ -219,6 +241,7 @@
|
||||
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: {
|
||||
@ -340,7 +363,7 @@
|
||||
memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
|
||||
memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
const { instance } = await WebAssembly.instantiateStreaming(
|
||||
@ -348,13 +371,15 @@
|
||||
);
|
||||
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
|
||||
scratchSpaceAddress = instance.exports.__heap_base.value;
|
||||
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
|
||||
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(arenaAddress, clayScratchSpaceAddress);
|
||||
renderCommandSize = getStructTotalSize(renderCommandDefinition);
|
||||
renderLoop();
|
||||
}
|
||||
@ -368,6 +393,22 @@
|
||||
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);
|
||||
@ -375,7 +416,7 @@
|
||||
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 entireRenderCommandMemory = new Uint8Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
|
||||
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
||||
let parentElement = scissorStack[scissorStack.length - 1];
|
||||
let element = null;
|
||||
@ -384,13 +425,12 @@
|
||||
let elementType = 'div';
|
||||
switch (renderCommand.commandType.value & 0xff) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
|
||||
elementType = 'a';
|
||||
}
|
||||
// if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links
|
||||
// elementType = 'a';
|
||||
// }
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
console.log('test5');
|
||||
elementType = 'img'; break;
|
||||
}
|
||||
default: break;
|
||||
@ -443,93 +483,82 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
|
||||
let sharedConfig = readStructAtAddress( renderCommand.textOrSharedConfig.sharedConfig.value, sharedConfigDefinition);
|
||||
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)) {
|
||||
let config = renderCommand.renderData.rectangle;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
if (linkContents.length > 0) {
|
||||
element.href = linkContents;
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
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 (sharedConfig.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = sharedConfig.cornerRadius.topLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = sharedConfig.cornerRadius.topRight.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = sharedConfig.cornerRadius.bottomLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomRight.value > 0) {
|
||||
element.style.borderBottomRightRadius = sharedConfig.cornerRadius.bottomRight.value + 'px';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
|
||||
let sharedConfig = readStructAtAddress( renderCommand.textOrSharedConfig.sharedConfig.value, sharedConfigDefinition);
|
||||
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
|
||||
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
|
||||
let config = renderCommand.renderData.border;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
let color = config.color;
|
||||
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.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.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.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.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.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.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.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 (sharedConfig.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = sharedConfig.cornerRadius.topLeft.value + 'px';
|
||||
if (config.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = sharedConfig.cornerRadius.topRight.value + 'px';
|
||||
if (config.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = sharedConfig.cornerRadius.bottomLeft.value + 'px';
|
||||
if (config.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomRight.value > 0) {
|
||||
element.style.borderBottomRightRadius = sharedConfig.cornerRadius.bottomRight.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.textOrSharedConfig.text;
|
||||
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
|
||||
if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
|
||||
let config = renderCommand.renderData.text;
|
||||
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
|
||||
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';
|
||||
element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all';
|
||||
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
}
|
||||
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
|
||||
@ -540,7 +569,11 @@
|
||||
}
|
||||
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);
|
||||
let config = renderCommand.renderData.scroll;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
if (config.horizontal.value) {
|
||||
element.style.overflowX = 'scroll';
|
||||
element.style.pointerEvents = 'auto';
|
||||
@ -549,6 +582,7 @@
|
||||
element.style.overflowY = 'scroll';
|
||||
element.style.pointerEvents = 'auto';
|
||||
}
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
|
||||
@ -556,9 +590,9 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
|
||||
console.log('test1');
|
||||
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));
|
||||
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);
|
||||
}
|
||||
@ -566,6 +600,9 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
|
||||
default: {
|
||||
console.log("Error: unhandled render command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,8 +639,8 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
|
||||
let color = config.color;
|
||||
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(
|
||||
@ -615,33 +652,35 @@
|
||||
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;
|
||||
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 = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
|
||||
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.top.width.value;
|
||||
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);
|
||||
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;
|
||||
if (config.width.top.value > 0) {
|
||||
let lineWidth = config.width.top.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);
|
||||
@ -650,19 +689,17 @@
|
||||
}
|
||||
// Top Right Corner
|
||||
if (config.cornerRadius.topRight.value > 0) {
|
||||
let lineWidth = config.top.width.value;
|
||||
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);
|
||||
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;
|
||||
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})`;
|
||||
@ -672,8 +709,7 @@
|
||||
}
|
||||
// Bottom Right Corner
|
||||
if (config.cornerRadius.bottomRight.value > 0) {
|
||||
let color = config.top.color;
|
||||
let lineWidth = config.top.width.value;
|
||||
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;
|
||||
@ -682,9 +718,8 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
// Bottom Border
|
||||
if (config.bottom.width.value > 0) {
|
||||
let color = config.bottom.color;
|
||||
let lineWidth = config.bottom.width.value;
|
||||
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})`;
|
||||
@ -694,8 +729,7 @@
|
||||
}
|
||||
// Bottom Left Corner
|
||||
if (config.cornerRadius.bottomLeft.value > 0) {
|
||||
let color = config.bottom.color;
|
||||
let lineWidth = config.bottom.width.value;
|
||||
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;
|
||||
@ -704,9 +738,8 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
// Left Border
|
||||
if (config.left.width.value > 0) {
|
||||
let color = config.left.color;
|
||||
let lineWidth = config.left.width.value;
|
||||
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})`;
|
||||
@ -718,8 +751,8 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
|
||||
let textContents = renderCommand.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]}`;
|
||||
@ -742,8 +775,9 @@
|
||||
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)));
|
||||
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(),
|
||||
|
Binary file not shown.
@ -86,6 +86,10 @@
|
||||
];
|
||||
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' },
|
||||
@ -101,9 +105,12 @@
|
||||
{name: 'chars', type: 'uint32_t' },
|
||||
{name: 'baseChars', type: 'uint32_t' },
|
||||
]};
|
||||
let borderDefinition = { type: 'struct', members: [
|
||||
{name: 'width', type: 'uint32_t'},
|
||||
{name: 'color', ...colorDefinition},
|
||||
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'},
|
||||
@ -111,44 +118,53 @@
|
||||
{name: 'bottomLeft', type: 'float'},
|
||||
{name: 'bottomRight', type: 'float'},
|
||||
]};
|
||||
let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [
|
||||
{ name: 'color', ...colorDefinition },
|
||||
{ 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 },
|
||||
]};
|
||||
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' }
|
||||
{ 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 scrollConfigDefinition = { name: 'text', type: 'struct', members: [
|
||||
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: 'sourceDimensions', ...dimensionsDefinition },
|
||||
{ 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 scrollRenderDataDefinition = { 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 sharedConfigDefinition = { name: 'shared', type: 'struct', members: [
|
||||
{ name: 'cornerRadius', ...cornerRadiusDefinition },
|
||||
let customHTMLDataDefinition = { type: 'struct', members: [
|
||||
{ name: 'link', ...stringDefinition },
|
||||
{ name: 'cursorPointer', type: 'uint8_t' },
|
||||
{ name: 'disablePointerEvents', type: 'uint8_t' },
|
||||
]};
|
||||
let renderCommandDefinition = {
|
||||
name: 'CLay_RenderCommand',
|
||||
@ -160,14 +176,19 @@
|
||||
{ name: 'width', type: 'float' },
|
||||
{ name: 'height', type: 'float' },
|
||||
]},
|
||||
{ name: 'config', type: 'uint32_t'},
|
||||
{ name: 'textOrSharedConfig', type: 'union', members: [
|
||||
{ name: 'text', ...stringSliceDefinition },
|
||||
{ name: 'sharedConfig', type: 'uint32_t' }
|
||||
{ name: 'renderData', type: 'union', members: [
|
||||
{ name: 'rectangle', ...rectangleRenderDataDefinition },
|
||||
{ name: 'text', ...textRenderDataDefinition },
|
||||
{ name: 'image', ...imageRenderDataDefinition },
|
||||
{ name: 'custom', ...customRenderDataDefinition },
|
||||
{ name: 'border', ...borderRenderDataDefinition },
|
||||
{ name: 'scroll', ...scrollRenderDataDefinition },
|
||||
]},
|
||||
{ name: 'zIndex', type: 'int32_t' },
|
||||
{ name: 'userData', type: 'uint32_t'},
|
||||
{ name: 'id', type: 'uint32_t' },
|
||||
{ name: 'commandType', type: 'uint32_t', },
|
||||
{ name: 'zIndex', type: 'int16_t' },
|
||||
{ name: 'commandType', type: 'uint8_t' },
|
||||
{ name: '_padding', type: 'uint8_t' },
|
||||
]
|
||||
};
|
||||
|
||||
@ -190,6 +211,7 @@
|
||||
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: {
|
||||
@ -219,6 +241,7 @@
|
||||
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: {
|
||||
@ -340,7 +363,7 @@
|
||||
memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
|
||||
memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
const { instance } = await WebAssembly.instantiateStreaming(
|
||||
@ -348,13 +371,15 @@
|
||||
);
|
||||
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
|
||||
scratchSpaceAddress = instance.exports.__heap_base.value;
|
||||
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
|
||||
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(arenaAddress, clayScratchSpaceAddress);
|
||||
renderCommandSize = getStructTotalSize(renderCommandDefinition);
|
||||
renderLoop();
|
||||
}
|
||||
@ -368,6 +393,22 @@
|
||||
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);
|
||||
@ -375,7 +416,7 @@
|
||||
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 entireRenderCommandMemory = new Uint8Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
|
||||
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
||||
let parentElement = scissorStack[scissorStack.length - 1];
|
||||
let element = null;
|
||||
@ -384,13 +425,12 @@
|
||||
let elementType = 'div';
|
||||
switch (renderCommand.commandType.value & 0xff) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
|
||||
elementType = 'a';
|
||||
}
|
||||
// if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links
|
||||
// elementType = 'a';
|
||||
// }
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
console.log('test5');
|
||||
elementType = 'img'; break;
|
||||
}
|
||||
default: break;
|
||||
@ -443,93 +483,82 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
|
||||
let sharedConfig = readStructAtAddress( renderCommand.textOrSharedConfig.sharedConfig.value, sharedConfigDefinition);
|
||||
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)) {
|
||||
let config = renderCommand.renderData.rectangle;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
if (linkContents.length > 0) {
|
||||
element.href = linkContents;
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
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 (sharedConfig.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = sharedConfig.cornerRadius.topLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = sharedConfig.cornerRadius.topRight.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = sharedConfig.cornerRadius.bottomLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomRight.value > 0) {
|
||||
element.style.borderBottomRightRadius = sharedConfig.cornerRadius.bottomRight.value + 'px';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
|
||||
let sharedConfig = readStructAtAddress( renderCommand.textOrSharedConfig.sharedConfig.value, sharedConfigDefinition);
|
||||
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
|
||||
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
|
||||
let config = renderCommand.renderData.border;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
let color = config.color;
|
||||
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.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.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.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.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.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.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.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 (sharedConfig.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = sharedConfig.cornerRadius.topLeft.value + 'px';
|
||||
if (config.cornerRadius.topLeft.value > 0) {
|
||||
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = sharedConfig.cornerRadius.topRight.value + 'px';
|
||||
if (config.cornerRadius.topRight.value > 0) {
|
||||
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = sharedConfig.cornerRadius.bottomLeft.value + 'px';
|
||||
if (config.cornerRadius.bottomLeft.value > 0) {
|
||||
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
|
||||
}
|
||||
if (sharedConfig.cornerRadius.bottomRight.value > 0) {
|
||||
element.style.borderBottomRightRadius = sharedConfig.cornerRadius.bottomRight.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.textOrSharedConfig.text;
|
||||
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
|
||||
if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
|
||||
let config = renderCommand.renderData.text;
|
||||
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
|
||||
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';
|
||||
element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all';
|
||||
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
}
|
||||
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
|
||||
@ -540,7 +569,11 @@
|
||||
}
|
||||
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);
|
||||
let config = renderCommand.renderData.scroll;
|
||||
let configMemory = JSON.stringify(config);
|
||||
if (configMemory === elementData.previousMemoryConfig) {
|
||||
break;
|
||||
}
|
||||
if (config.horizontal.value) {
|
||||
element.style.overflowX = 'scroll';
|
||||
element.style.pointerEvents = 'auto';
|
||||
@ -549,6 +582,7 @@
|
||||
element.style.overflowY = 'scroll';
|
||||
element.style.pointerEvents = 'auto';
|
||||
}
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
|
||||
@ -556,9 +590,9 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
|
||||
console.log('test1');
|
||||
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));
|
||||
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);
|
||||
}
|
||||
@ -566,6 +600,9 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
|
||||
default: {
|
||||
console.log("Error: unhandled render command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,8 +639,8 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
|
||||
let color = config.color;
|
||||
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(
|
||||
@ -615,33 +652,35 @@
|
||||
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;
|
||||
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 = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
|
||||
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.top.width.value;
|
||||
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);
|
||||
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;
|
||||
if (config.width.top.value > 0) {
|
||||
let lineWidth = config.width.top.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);
|
||||
@ -650,19 +689,17 @@
|
||||
}
|
||||
// Top Right Corner
|
||||
if (config.cornerRadius.topRight.value > 0) {
|
||||
let lineWidth = config.top.width.value;
|
||||
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);
|
||||
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;
|
||||
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})`;
|
||||
@ -672,8 +709,7 @@
|
||||
}
|
||||
// Bottom Right Corner
|
||||
if (config.cornerRadius.bottomRight.value > 0) {
|
||||
let color = config.top.color;
|
||||
let lineWidth = config.top.width.value;
|
||||
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;
|
||||
@ -682,9 +718,8 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
// Bottom Border
|
||||
if (config.bottom.width.value > 0) {
|
||||
let color = config.bottom.color;
|
||||
let lineWidth = config.bottom.width.value;
|
||||
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})`;
|
||||
@ -694,8 +729,7 @@
|
||||
}
|
||||
// Bottom Left Corner
|
||||
if (config.cornerRadius.bottomLeft.value > 0) {
|
||||
let color = config.bottom.color;
|
||||
let lineWidth = config.bottom.width.value;
|
||||
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;
|
||||
@ -704,9 +738,8 @@
|
||||
ctx.stroke();
|
||||
}
|
||||
// Left Border
|
||||
if (config.left.width.value > 0) {
|
||||
let color = config.left.color;
|
||||
let lineWidth = config.left.width.value;
|
||||
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})`;
|
||||
@ -718,8 +751,8 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
|
||||
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
|
||||
let textContents = renderCommand.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]}`;
|
||||
@ -742,8 +775,9 @@
|
||||
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)));
|
||||
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(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
double windowWidth = 1024, windowHeight = 768;
|
||||
float modelPageOneZRotation = 0;
|
||||
uint32_t ACTIVE_RENDERER_INDEX = 0;
|
||||
uint32_t ACTIVE_RENDERER_INDEX = 1;
|
||||
|
||||
const uint32_t FONT_ID_BODY_16 = 0;
|
||||
const uint32_t FONT_ID_TITLE_56 = 1;
|
||||
@ -52,13 +52,21 @@ typedef struct {
|
||||
|
||||
CustomHTMLData* FrameAllocateCustomData(CustomHTMLData data) {
|
||||
CustomHTMLData *customData = (CustomHTMLData *)(frameArena.memory + frameArena.offset);
|
||||
*customData = data;
|
||||
frameArena.offset += sizeof(CustomHTMLData);
|
||||
return customData;
|
||||
}
|
||||
|
||||
void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, char* imageURL) {
|
||||
Clay_String* FrameAllocateString(Clay_String string) {
|
||||
Clay_String *allocated = (Clay_String *)(frameArena.memory + frameArena.offset);
|
||||
*allocated = string;
|
||||
frameArena.offset += sizeof(Clay_String);
|
||||
return allocated;
|
||||
}
|
||||
|
||||
void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) {
|
||||
CLAY({ .id = CLAY_IDI("HeroBlob", index), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) {
|
||||
CLAY({ .id = CLAY_IDI("CheckImage", index), .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 128, 128 }, .imageData = imageURL } }) {}
|
||||
CLAY({ .id = CLAY_IDI("CheckImage", index), .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 128, 128 }, .imageData = FrameAllocateString(imageURL) } }) {}
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color }));
|
||||
}
|
||||
}
|
||||
@ -72,11 +80,11 @@ void LandingPageDesktop() {
|
||||
CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("HeroImageOuter"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
|
||||
LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), "/clay/images/check_5.png");
|
||||
LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), "/clay/images/check_4.png");
|
||||
LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), "/clay/images/check_3.png");
|
||||
LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), "/clay/images/check_2.png");
|
||||
LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), "/clay/images/check_1.png");
|
||||
LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
|
||||
LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
|
||||
LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
|
||||
LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
|
||||
LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,11 +98,11 @@ void LandingPageMobile() {
|
||||
CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("HeroImageOuter"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
|
||||
LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), "/clay/images/check_5.png");
|
||||
LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), "/clay/images/check_4.png");
|
||||
LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), "/clay/images/check_3.png");
|
||||
LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), "/clay/images/check_2.png");
|
||||
LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), "/clay/images/check_1.png");
|
||||
LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
|
||||
LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
|
||||
LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
|
||||
LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
|
||||
LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,7 +156,7 @@ void DeclarativeSyntaxPageDesktop() {
|
||||
CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = "/clay/images/declarative.png" } }) {}
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,7 +172,7 @@ void DeclarativeSyntaxPageMobile() {
|
||||
CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = "/clay/images/declarative.png" } }) {}
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,7 +245,7 @@ void RendererButtonActive(Clay_String text) {
|
||||
.layout = { .sizing = {CLAY_SIZING_FIXED(300) }, .padding = CLAY_PADDING_ALL(16) },
|
||||
.backgroundColor = Clay_Hovered() ? COLOR_RED_HOVER : COLOR_RED,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(10),
|
||||
.custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })}
|
||||
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
|
||||
}) {
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
|
||||
}
|
||||
@ -249,7 +257,7 @@ void RendererButtonInactive(Clay_String text, size_t rendererIndex) {
|
||||
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
|
||||
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(10),
|
||||
.custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })}
|
||||
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
|
||||
}) {
|
||||
Clay_OnHover(HandleRendererButtonInteraction, rendererIndex);
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
|
||||
@ -315,7 +323,7 @@ void DebuggerPageDesktop() {
|
||||
CLAY_TEXT(CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("DebuggerRightImageOuter"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("DebuggerPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .image = { .sourceDimensions = {1620, 1474}, .imageData = "/clay/images/debugger.png" } }) {}
|
||||
CLAY({ .id = CLAY_ID("DebuggerPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .image = { .sourceDimensions = {1620, 1474}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,10 +345,10 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
|
||||
CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig);
|
||||
CLAY({ .id = CLAY_ID("Spacer"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {}
|
||||
if (!mobileScreen) {
|
||||
CLAY({ .id = CLAY_ID("LinkExamplesOuter"), .layout = { .padding = {8, 8} }, .custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }) } }) {
|
||||
CLAY({ .id = CLAY_ID("LinkExamplesOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }) }) {
|
||||
CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("LinkDocsOuter"), .layout = { .padding = {8, 8} }, .custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }) } }) {
|
||||
CLAY({ .id = CLAY_ID("LinkDocsOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }) }) {
|
||||
CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
}
|
||||
@ -349,7 +357,7 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
|
||||
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
|
||||
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(10),
|
||||
.custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }) },
|
||||
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }),
|
||||
}) {
|
||||
CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
@ -358,7 +366,7 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
|
||||
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
|
||||
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(10),
|
||||
.custom = { .customData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }) },
|
||||
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }),
|
||||
}) {
|
||||
CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
@ -413,7 +421,12 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
|
||||
|
||||
bool debugModeEnabled = false;
|
||||
|
||||
CLAY_WASM_EXPORT("SetScratchMemory") void SetScratchMemory(void * memory) {
|
||||
frameArena.memory = memory;
|
||||
}
|
||||
|
||||
CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, bool arrowKeyDownPressedThisFrame, bool arrowKeyUpPressedThisFrame, bool dKeyPressedThisFrame, float deltaTime) {
|
||||
frameArena.offset = 0;
|
||||
windowWidth = width;
|
||||
windowHeight = height;
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions) { width, height });
|
||||
|
0
generator/__init__.py
Normal file
0
generator/__init__.py
Normal file
7
generator/clay_header_generator/array_add.template.c
Normal file
7
generator/clay_header_generator/array_add.template.c
Normal file
@ -0,0 +1,7 @@
|
||||
$TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) {
|
||||
if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) {
|
||||
array->internalArray[array->length++] = item;
|
||||
return &array->internalArray[array->length - 1];
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
void $NAME$_Add($NAME$ *array, $TYPE$ item) {
|
||||
if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) {
|
||||
array->internalArray[array->length++] = item;
|
||||
}
|
||||
}
|
@ -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__ALIGNMENT($TYPE$), arena)};
|
||||
}
|
@ -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)};
|
||||
}
|
6
generator/clay_header_generator/array_define.template.c
Normal file
6
generator/clay_header_generator/array_define.template.c
Normal file
@ -0,0 +1,6 @@
|
||||
CLAY__TYPEDEF($NAME$, struct
|
||||
{
|
||||
int32_t capacity;
|
||||
int32_t length;
|
||||
$TYPE$ *internalArray;
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
CLAY__TYPEDEF($NAME$Slice, struct
|
||||
{
|
||||
int32_t length;
|
||||
$TYPE$ *internalArray;
|
||||
});
|
3
generator/clay_header_generator/array_get.template.c
Normal file
3
generator/clay_header_generator/array_get.template.c
Normal file
@ -0,0 +1,3 @@
|
||||
$TYPE$ *$NAME$_Get($NAME$ *array, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
$TYPE$ $NAME$_Get($NAME$ *array, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int32_t index) {
|
||||
if (Clay__Array_RangeCheck(index, array->length)) {
|
||||
array->length--;
|
||||
$TYPE$ removed = array->internalArray[index];
|
||||
array->internalArray[index] = array->internalArray[array->length];
|
||||
return removed;
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
6
generator/clay_header_generator/array_set.template.c
Normal file
6
generator/clay_header_generator/array_set.template.c
Normal file
@ -0,0 +1,6 @@
|
||||
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;
|
||||
}
|
||||
}
|
69
generator/clay_header_generator/generate_templates.js
Normal file
69
generator/clay_header_generator/generate_templates.js
Normal file
@ -0,0 +1,69 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let files = ['../clay.h'];
|
||||
|
||||
let templates = ['./'];
|
||||
function readCTemplatesRecursive(directory) {
|
||||
fs.readdirSync(directory).forEach(template => {
|
||||
const absolute = path.join(directory, template);
|
||||
if (fs.statSync(absolute).isDirectory()) return readCTemplatesRecursive(absolute);
|
||||
else if (template.endsWith('template.c')) {
|
||||
return templates.push(absolute);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readCTemplatesRecursive(__dirname);
|
||||
|
||||
for (const file of files) {
|
||||
const contents = fs.readFileSync(file, 'utf8');
|
||||
const lines = contents.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith('// __GENERATED__ template')) {
|
||||
const [comment, generated, templateOpen, templateNames, ...args] = line.split(" ");
|
||||
let matchingEndingLine = -1;
|
||||
for (let j = i + 1; j < lines.length; j++) {
|
||||
if (lines[j].startsWith('// __GENERATED__ template')) {
|
||||
matchingEndingLine = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingEndingLine !== -1) {
|
||||
i++;
|
||||
lines.splice(i, matchingEndingLine - (i));
|
||||
lines.splice(i, 0, ['#pragma region generated']);
|
||||
i++;
|
||||
for (const templateName of templateNames.split(',')) {
|
||||
var matchingTemplate = templates.find(t => t.endsWith(`${templateName}.template.c`));
|
||||
if (matchingTemplate) {
|
||||
let templateContents = fs.readFileSync(matchingTemplate, 'utf8');
|
||||
for (const arg of args) {
|
||||
[argName, argValue] = arg.split('=');
|
||||
templateContents = templateContents.replaceAll(`\$${argName}\$`, argValue);
|
||||
}
|
||||
let remainingTokens = templateContents.split('$');
|
||||
if (remainingTokens.length > 1) {
|
||||
console.log(`Error at ${file}:${i}: Template is missing parameter ${remainingTokens[1]}`)
|
||||
process.exit();
|
||||
} else {
|
||||
templateContents = templateContents.split('\n');
|
||||
lines.splice(i, 0, ...templateContents);
|
||||
i += templateContents.length;
|
||||
}
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: no template with name ${templateName}.template.c was found.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
lines.splice(i, 0, ['#pragma endregion']);
|
||||
i++;
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: template was opened and not closed again.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(file, lines.join('\n'));
|
||||
}
|
83
generator/cli.py
Normal file
83
generator/cli.py
Normal file
@ -0,0 +1,83 @@
|
||||
import argparse
|
||||
import logging
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from generators.base_generator import BaseGenerator
|
||||
from generators.odin_generator import OdinGenerator
|
||||
from parser import parse_headers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GeneratorMap = dict[str, type[BaseGenerator]]
|
||||
GENERATORS = {
|
||||
'odin': OdinGenerator,
|
||||
}
|
||||
|
||||
def main() -> None:
|
||||
arg_parser = argparse.ArgumentParser(description='Generate clay bindings')
|
||||
|
||||
# Directories
|
||||
arg_parser.add_argument('input_files', nargs='+', type=str, help='Input header files')
|
||||
arg_parser.add_argument('--output-dir', type=str, help='Output directory', required=True)
|
||||
arg_parser.add_argument('--tmp-dir', type=str, help='Temporary directory')
|
||||
|
||||
# Generators
|
||||
arg_parser.add_argument('--generator', type=str, choices=list(GENERATORS.keys()), help='Generators to run', required=True)
|
||||
|
||||
# Logging
|
||||
arg_parser.add_argument('--verbose', action='store_true', help='Verbose logging')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_handler = logging.StreamHandler()
|
||||
log_handler.setFormatter(log_formatter)
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG, handlers=[log_handler])
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO, handlers=[log_handler])
|
||||
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if args.tmp_dir:
|
||||
tmp_dir = Path(args.tmp_dir)
|
||||
else:
|
||||
tmp_dir = output_dir / 'tmp'
|
||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
fake_libc_include_path = Path(__file__).parent / 'fake_libc_include'
|
||||
input_files = list(fake_libc_include_path.glob('*.h')) + [Path(f) for f in args.input_files]
|
||||
|
||||
logger.info(f'Input files: {input_files}')
|
||||
logger.info(f'Output directory: {output_dir}')
|
||||
logger.info(f'Temporary directory: {tmp_dir}')
|
||||
logger.info(f'Generator: {args.generator}')
|
||||
|
||||
logger.info('Parsing headers')
|
||||
extracted_symbols = parse_headers(input_files, tmp_dir)
|
||||
with open(tmp_dir / 'extracted_symbols.json', 'w') as f:
|
||||
f.write(json.dumps({
|
||||
'structs': extracted_symbols.structs,
|
||||
'enums': extracted_symbols.enums,
|
||||
'functions': extracted_symbols.functions,
|
||||
}, indent=2))
|
||||
|
||||
logger.info('Generating bindings')
|
||||
generator = GENERATORS[args.generator](extracted_symbols)
|
||||
generator.generate()
|
||||
logger.debug(f'Generated bindings:')
|
||||
# for file_name, content in generator.get_outputs().items():
|
||||
# logger.debug(f'{file_name}:')
|
||||
# logger.debug(content)
|
||||
# logger.debug('\n')
|
||||
|
||||
tmp_outputs_dir = tmp_dir / 'generated'
|
||||
tmp_outputs_dir.mkdir(parents=True, exist_ok=True)
|
||||
generator.write_outputs(tmp_outputs_dir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
262
generator/fake_libc_include/_fake_defines.h
Normal file
262
generator/fake_libc_include/_fake_defines.h
Normal file
@ -0,0 +1,262 @@
|
||||
#ifndef _FAKE_DEFINES_H
|
||||
#define _FAKE_DEFINES_H
|
||||
|
||||
#define NULL 0
|
||||
#define BUFSIZ 1024
|
||||
#define FOPEN_MAX 20
|
||||
#define FILENAME_MAX 1024
|
||||
|
||||
#ifndef SEEK_SET
|
||||
#define SEEK_SET 0 /* set file offset to offset */
|
||||
#endif
|
||||
#ifndef SEEK_CUR
|
||||
#define SEEK_CUR 1 /* set file offset to current plus offset */
|
||||
#endif
|
||||
#ifndef SEEK_END
|
||||
#define SEEK_END 2 /* set file offset to EOF plus offset */
|
||||
#endif
|
||||
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define LITTLE_ENDIAN __LITTLE_ENDIAN
|
||||
#define __BIG_ENDIAN 4321
|
||||
#define BIG_ENDIAN __BIG_ENDIAN
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#define BYTE_ORDER __BYTE_ORDER
|
||||
|
||||
#define EXIT_FAILURE 1
|
||||
#define EXIT_SUCCESS 0
|
||||
|
||||
#define SCHAR_MIN -128
|
||||
#define SCHAR_MAX 127
|
||||
#define CHAR_MIN -128
|
||||
#define CHAR_MAX 127
|
||||
#define UCHAR_MAX 255
|
||||
#define SHRT_MIN -32768
|
||||
#define SHRT_MAX 32767
|
||||
#define USHRT_MAX 65535
|
||||
#define INT_MIN -2147483648
|
||||
#define INT_MAX 2147483647
|
||||
#define UINT_MAX 4294967295U
|
||||
#define LONG_MIN -9223372036854775808L
|
||||
#define LONG_MAX 9223372036854775807L
|
||||
#define ULONG_MAX 18446744073709551615UL
|
||||
#define RAND_MAX 32767
|
||||
|
||||
/* C99 inttypes.h defines */
|
||||
#define PRId8 "d"
|
||||
#define PRIi8 "i"
|
||||
#define PRIo8 "o"
|
||||
#define PRIu8 "u"
|
||||
#define PRIx8 "x"
|
||||
#define PRIX8 "X"
|
||||
#define PRId16 "d"
|
||||
#define PRIi16 "i"
|
||||
#define PRIo16 "o"
|
||||
#define PRIu16 "u"
|
||||
#define PRIx16 "x"
|
||||
#define PRIX16 "X"
|
||||
#define PRId32 "d"
|
||||
#define PRIi32 "i"
|
||||
#define PRIo32 "o"
|
||||
#define PRIu32 "u"
|
||||
#define PRIx32 "x"
|
||||
#define PRIX32 "X"
|
||||
#define PRId64 "d"
|
||||
#define PRIi64 "i"
|
||||
#define PRIo64 "o"
|
||||
#define PRIu64 "u"
|
||||
#define PRIx64 "x"
|
||||
#define PRIX64 "X"
|
||||
#define PRIdLEAST8 "d"
|
||||
#define PRIiLEAST8 "i"
|
||||
#define PRIoLEAST8 "o"
|
||||
#define PRIuLEAST8 "u"
|
||||
#define PRIxLEAST8 "x"
|
||||
#define PRIXLEAST8 "X"
|
||||
#define PRIdLEAST16 "d"
|
||||
#define PRIiLEAST16 "i"
|
||||
#define PRIoLEAST16 "o"
|
||||
#define PRIuLEAST16 "u"
|
||||
#define PRIxLEAST16 "x"
|
||||
#define PRIXLEAST16 "X"
|
||||
#define PRIdLEAST32 "d"
|
||||
#define PRIiLEAST32 "i"
|
||||
#define PRIoLEAST32 "o"
|
||||
#define PRIuLEAST32 "u"
|
||||
#define PRIxLEAST32 "x"
|
||||
#define PRIXLEAST32 "X"
|
||||
#define PRIdLEAST64 "d"
|
||||
#define PRIiLEAST64 "i"
|
||||
#define PRIoLEAST64 "o"
|
||||
#define PRIuLEAST64 "u"
|
||||
#define PRIxLEAST64 "x"
|
||||
#define PRIXLEAST64 "X"
|
||||
#define PRIdFAST8 "d"
|
||||
#define PRIiFAST8 "i"
|
||||
#define PRIoFAST8 "o"
|
||||
#define PRIuFAST8 "u"
|
||||
#define PRIxFAST8 "x"
|
||||
#define PRIXFAST8 "X"
|
||||
#define PRIdFAST16 "d"
|
||||
#define PRIiFAST16 "i"
|
||||
#define PRIoFAST16 "o"
|
||||
#define PRIuFAST16 "u"
|
||||
#define PRIxFAST16 "x"
|
||||
#define PRIXFAST16 "X"
|
||||
#define PRIdFAST32 "d"
|
||||
#define PRIiFAST32 "i"
|
||||
#define PRIoFAST32 "o"
|
||||
#define PRIuFAST32 "u"
|
||||
#define PRIxFAST32 "x"
|
||||
#define PRIXFAST32 "X"
|
||||
#define PRIdFAST64 "d"
|
||||
#define PRIiFAST64 "i"
|
||||
#define PRIoFAST64 "o"
|
||||
#define PRIuFAST64 "u"
|
||||
#define PRIxFAST64 "x"
|
||||
#define PRIXFAST64 "X"
|
||||
#define PRIdPTR "d"
|
||||
#define PRIiPTR "i"
|
||||
#define PRIoPTR "o"
|
||||
#define PRIuPTR "u"
|
||||
#define PRIxPTR "x"
|
||||
#define PRIXPTR "X"
|
||||
#define PRIdMAX "d"
|
||||
#define PRIiMAX "i"
|
||||
#define PRIoMAX "o"
|
||||
#define PRIuMAX "u"
|
||||
#define PRIxMAX "x"
|
||||
#define PRIXMAX "X"
|
||||
#define SCNd8 "d"
|
||||
#define SCNi8 "i"
|
||||
#define SCNo8 "o"
|
||||
#define SCNu8 "u"
|
||||
#define SCNx8 "x"
|
||||
#define SCNd16 "d"
|
||||
#define SCNi16 "i"
|
||||
#define SCNo16 "o"
|
||||
#define SCNu16 "u"
|
||||
#define SCNx16 "x"
|
||||
#define SCNd32 "d"
|
||||
#define SCNi32 "i"
|
||||
#define SCNo32 "o"
|
||||
#define SCNu32 "u"
|
||||
#define SCNx32 "x"
|
||||
#define SCNd64 "d"
|
||||
#define SCNi64 "i"
|
||||
#define SCNo64 "o"
|
||||
#define SCNu64 "u"
|
||||
#define SCNx64 "x"
|
||||
#define SCNdLEAST8 "d"
|
||||
#define SCNiLEAST8 "i"
|
||||
#define SCNoLEAST8 "o"
|
||||
#define SCNuLEAST8 "u"
|
||||
#define SCNxLEAST8 "x"
|
||||
#define SCNdLEAST16 "d"
|
||||
#define SCNiLEAST16 "i"
|
||||
#define SCNoLEAST16 "o"
|
||||
#define SCNuLEAST16 "u"
|
||||
#define SCNxLEAST16 "x"
|
||||
#define SCNdLEAST32 "d"
|
||||
#define SCNiLEAST32 "i"
|
||||
#define SCNoLEAST32 "o"
|
||||
#define SCNuLEAST32 "u"
|
||||
#define SCNxLEAST32 "x"
|
||||
#define SCNdLEAST64 "d"
|
||||
#define SCNiLEAST64 "i"
|
||||
#define SCNoLEAST64 "o"
|
||||
#define SCNuLEAST64 "u"
|
||||
#define SCNxLEAST64 "x"
|
||||
#define SCNdFAST8 "d"
|
||||
#define SCNiFAST8 "i"
|
||||
#define SCNoFAST8 "o"
|
||||
#define SCNuFAST8 "u"
|
||||
#define SCNxFAST8 "x"
|
||||
#define SCNdFAST16 "d"
|
||||
#define SCNiFAST16 "i"
|
||||
#define SCNoFAST16 "o"
|
||||
#define SCNuFAST16 "u"
|
||||
#define SCNxFAST16 "x"
|
||||
#define SCNdFAST32 "d"
|
||||
#define SCNiFAST32 "i"
|
||||
#define SCNoFAST32 "o"
|
||||
#define SCNuFAST32 "u"
|
||||
#define SCNxFAST32 "x"
|
||||
#define SCNdFAST64 "d"
|
||||
#define SCNiFAST64 "i"
|
||||
#define SCNoFAST64 "o"
|
||||
#define SCNuFAST64 "u"
|
||||
#define SCNxFAST64 "x"
|
||||
#define SCNdPTR "d"
|
||||
#define SCNiPTR "i"
|
||||
#define SCNoPTR "o"
|
||||
#define SCNuPTR "u"
|
||||
#define SCNxPTR "x"
|
||||
#define SCNdMAX "d"
|
||||
#define SCNiMAX "i"
|
||||
#define SCNoMAX "o"
|
||||
#define SCNuMAX "u"
|
||||
#define SCNxMAX "x"
|
||||
|
||||
/* C99 stdbool.h defines */
|
||||
#define __bool_true_false_are_defined 1
|
||||
#define false 0
|
||||
#define true 1
|
||||
|
||||
/* va_arg macros and type*/
|
||||
#define va_start(_ap, _type) __builtin_va_start((_ap))
|
||||
#define va_arg(_ap, _type) __builtin_va_arg((_ap))
|
||||
#define va_end(_list)
|
||||
|
||||
/* Vectors */
|
||||
#define __m128 int
|
||||
#define __m128_u int
|
||||
#define __m128d int
|
||||
#define __m128d_u int
|
||||
#define __m128i int
|
||||
#define __m128i_u int
|
||||
#define __m256 int
|
||||
#define __m256_u int
|
||||
#define __m256d int
|
||||
#define __m256d_u int
|
||||
#define __m256i int
|
||||
#define __m256i_u int
|
||||
#define __m512 int
|
||||
#define __m512_u int
|
||||
#define __m512d int
|
||||
#define __m512d_u int
|
||||
#define __m512i int
|
||||
#define __m512i_u int
|
||||
|
||||
/* C11 stdnoreturn.h defines */
|
||||
#define __noreturn_is_defined 1
|
||||
#define noreturn _Noreturn
|
||||
|
||||
/* C11 threads.h defines */
|
||||
#define thread_local _Thread_local
|
||||
|
||||
/* C11 assert.h defines */
|
||||
#define static_assert _Static_assert
|
||||
|
||||
/* C11 stdatomic.h defines */
|
||||
#define ATOMIC_BOOL_LOCK_FREE 0
|
||||
#define ATOMIC_CHAR_LOCK_FREE 0
|
||||
#define ATOMIC_CHAR16_T_LOCK_FREE 0
|
||||
#define ATOMIC_CHAR32_T_LOCK_FREE 0
|
||||
#define ATOMIC_WCHAR_T_LOCK_FREE 0
|
||||
#define ATOMIC_SHORT_LOCK_FREE 0
|
||||
#define ATOMIC_INT_LOCK_FREE 0
|
||||
#define ATOMIC_LONG_LOCK_FREE 0
|
||||
#define ATOMIC_LLONG_LOCK_FREE 0
|
||||
#define ATOMIC_POINTER_LOCK_FREE 0
|
||||
#define ATOMIC_VAR_INIT(value) (value)
|
||||
#define ATOMIC_FLAG_INIT { 0 }
|
||||
#define kill_dependency(y) (y)
|
||||
|
||||
/* C11 stdalign.h defines */
|
||||
#define alignas _Alignas
|
||||
#define alignof _Alignof
|
||||
#define __alignas_is_defined 1
|
||||
#define __alignof_is_defined 1
|
||||
|
||||
#endif
|
222
generator/fake_libc_include/_fake_typedefs.h
Normal file
222
generator/fake_libc_include/_fake_typedefs.h
Normal file
@ -0,0 +1,222 @@
|
||||
#ifndef _FAKE_TYPEDEFS_H
|
||||
#define _FAKE_TYPEDEFS_H
|
||||
|
||||
typedef int size_t;
|
||||
typedef int __builtin_va_list;
|
||||
typedef int __gnuc_va_list;
|
||||
typedef int va_list;
|
||||
typedef int __int8_t;
|
||||
typedef int __uint8_t;
|
||||
typedef int __int16_t;
|
||||
typedef int __uint16_t;
|
||||
typedef int __int_least16_t;
|
||||
typedef int __uint_least16_t;
|
||||
typedef int __int32_t;
|
||||
typedef int __uint32_t;
|
||||
typedef int __int64_t;
|
||||
typedef int __uint64_t;
|
||||
typedef int __int_least32_t;
|
||||
typedef int __uint_least32_t;
|
||||
typedef int __s8;
|
||||
typedef int __u8;
|
||||
typedef int __s16;
|
||||
typedef int __u16;
|
||||
typedef int __s32;
|
||||
typedef int __u32;
|
||||
typedef int __s64;
|
||||
typedef int __u64;
|
||||
typedef int _LOCK_T;
|
||||
typedef int _LOCK_RECURSIVE_T;
|
||||
typedef int _off_t;
|
||||
typedef int __dev_t;
|
||||
typedef int __uid_t;
|
||||
typedef int __gid_t;
|
||||
typedef int _off64_t;
|
||||
typedef int _fpos_t;
|
||||
typedef int _ssize_t;
|
||||
typedef int wint_t;
|
||||
typedef int _mbstate_t;
|
||||
typedef int _flock_t;
|
||||
typedef int _iconv_t;
|
||||
typedef int __ULong;
|
||||
typedef int __FILE;
|
||||
typedef int ptrdiff_t;
|
||||
typedef int wchar_t;
|
||||
typedef int char16_t;
|
||||
typedef int char32_t;
|
||||
typedef int __off_t;
|
||||
typedef int __pid_t;
|
||||
typedef int __loff_t;
|
||||
typedef int u_char;
|
||||
typedef int u_short;
|
||||
typedef int u_int;
|
||||
typedef int u_long;
|
||||
typedef int ushort;
|
||||
typedef int uint;
|
||||
typedef int clock_t;
|
||||
typedef int time_t;
|
||||
typedef int daddr_t;
|
||||
typedef int caddr_t;
|
||||
typedef int ino_t;
|
||||
typedef int off_t;
|
||||
typedef int dev_t;
|
||||
typedef int uid_t;
|
||||
typedef int gid_t;
|
||||
typedef int pid_t;
|
||||
typedef int key_t;
|
||||
typedef int ssize_t;
|
||||
typedef int mode_t;
|
||||
typedef int nlink_t;
|
||||
typedef int fd_mask;
|
||||
typedef int _types_fd_set;
|
||||
typedef int clockid_t;
|
||||
typedef int timer_t;
|
||||
typedef int useconds_t;
|
||||
typedef int suseconds_t;
|
||||
typedef int FILE;
|
||||
typedef int fpos_t;
|
||||
typedef int cookie_read_function_t;
|
||||
typedef int cookie_write_function_t;
|
||||
typedef int cookie_seek_function_t;
|
||||
typedef int cookie_close_function_t;
|
||||
typedef int cookie_io_functions_t;
|
||||
typedef int div_t;
|
||||
typedef int ldiv_t;
|
||||
typedef int lldiv_t;
|
||||
typedef int sigset_t;
|
||||
typedef int __sigset_t;
|
||||
typedef int _sig_func_ptr;
|
||||
typedef int sig_atomic_t;
|
||||
typedef int __tzrule_type;
|
||||
typedef int __tzinfo_type;
|
||||
typedef int mbstate_t;
|
||||
typedef int sem_t;
|
||||
typedef int pthread_t;
|
||||
typedef int pthread_attr_t;
|
||||
typedef int pthread_mutex_t;
|
||||
typedef int pthread_mutexattr_t;
|
||||
typedef int pthread_cond_t;
|
||||
typedef int pthread_condattr_t;
|
||||
typedef int pthread_key_t;
|
||||
typedef int pthread_once_t;
|
||||
typedef int pthread_rwlock_t;
|
||||
typedef int pthread_rwlockattr_t;
|
||||
typedef int pthread_spinlock_t;
|
||||
typedef int pthread_barrier_t;
|
||||
typedef int pthread_barrierattr_t;
|
||||
typedef int jmp_buf;
|
||||
typedef int rlim_t;
|
||||
typedef int sa_family_t;
|
||||
typedef int sigjmp_buf;
|
||||
typedef int stack_t;
|
||||
typedef int siginfo_t;
|
||||
typedef int z_stream;
|
||||
|
||||
/* C99 exact-width integer types */
|
||||
typedef int int8_t;
|
||||
typedef int uint8_t;
|
||||
typedef int int16_t;
|
||||
typedef int uint16_t;
|
||||
typedef int int32_t;
|
||||
typedef int uint32_t;
|
||||
typedef int int64_t;
|
||||
typedef int uint64_t;
|
||||
|
||||
/* C99 minimum-width integer types */
|
||||
typedef int int_least8_t;
|
||||
typedef int uint_least8_t;
|
||||
typedef int int_least16_t;
|
||||
typedef int uint_least16_t;
|
||||
typedef int int_least32_t;
|
||||
typedef int uint_least32_t;
|
||||
typedef int int_least64_t;
|
||||
typedef int uint_least64_t;
|
||||
|
||||
/* C99 fastest minimum-width integer types */
|
||||
typedef int int_fast8_t;
|
||||
typedef int uint_fast8_t;
|
||||
typedef int int_fast16_t;
|
||||
typedef int uint_fast16_t;
|
||||
typedef int int_fast32_t;
|
||||
typedef int uint_fast32_t;
|
||||
typedef int int_fast64_t;
|
||||
typedef int uint_fast64_t;
|
||||
|
||||
/* C99 integer types capable of holding object pointers */
|
||||
typedef int intptr_t;
|
||||
typedef int uintptr_t;
|
||||
|
||||
/* C99 greatest-width integer types */
|
||||
typedef int intmax_t;
|
||||
typedef int uintmax_t;
|
||||
|
||||
/* C99 stdbool.h bool type. _Bool is built-in in C99 */
|
||||
typedef _Bool bool;
|
||||
|
||||
/* Mir typedefs */
|
||||
typedef void* MirEGLNativeWindowType;
|
||||
typedef void* MirEGLNativeDisplayType;
|
||||
typedef struct MirConnection MirConnection;
|
||||
typedef struct MirSurface MirSurface;
|
||||
typedef struct MirSurfaceSpec MirSurfaceSpec;
|
||||
typedef struct MirScreencast MirScreencast;
|
||||
typedef struct MirPromptSession MirPromptSession;
|
||||
typedef struct MirBufferStream MirBufferStream;
|
||||
typedef struct MirPersistentId MirPersistentId;
|
||||
typedef struct MirBlob MirBlob;
|
||||
typedef struct MirDisplayConfig MirDisplayConfig;
|
||||
|
||||
/* xcb typedefs */
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
typedef uint32_t xcb_window_t;
|
||||
typedef uint32_t xcb_visualid_t;
|
||||
|
||||
/* C11 stdatomic.h types */
|
||||
typedef _Atomic(_Bool) atomic_bool;
|
||||
typedef _Atomic(char) atomic_char;
|
||||
typedef _Atomic(signed char) atomic_schar;
|
||||
typedef _Atomic(unsigned char) atomic_uchar;
|
||||
typedef _Atomic(short) atomic_short;
|
||||
typedef _Atomic(unsigned short) atomic_ushort;
|
||||
typedef _Atomic(int) atomic_int;
|
||||
typedef _Atomic(unsigned int) atomic_uint;
|
||||
typedef _Atomic(long) atomic_long;
|
||||
typedef _Atomic(unsigned long) atomic_ulong;
|
||||
typedef _Atomic(long long) atomic_llong;
|
||||
typedef _Atomic(unsigned long long) atomic_ullong;
|
||||
typedef _Atomic(uint_least16_t) atomic_char16_t;
|
||||
typedef _Atomic(uint_least32_t) atomic_char32_t;
|
||||
typedef _Atomic(wchar_t) atomic_wchar_t;
|
||||
typedef _Atomic(int_least8_t) atomic_int_least8_t;
|
||||
typedef _Atomic(uint_least8_t) atomic_uint_least8_t;
|
||||
typedef _Atomic(int_least16_t) atomic_int_least16_t;
|
||||
typedef _Atomic(uint_least16_t) atomic_uint_least16_t;
|
||||
typedef _Atomic(int_least32_t) atomic_int_least32_t;
|
||||
typedef _Atomic(uint_least32_t) atomic_uint_least32_t;
|
||||
typedef _Atomic(int_least64_t) atomic_int_least64_t;
|
||||
typedef _Atomic(uint_least64_t) atomic_uint_least64_t;
|
||||
typedef _Atomic(int_fast8_t) atomic_int_fast8_t;
|
||||
typedef _Atomic(uint_fast8_t) atomic_uint_fast8_t;
|
||||
typedef _Atomic(int_fast16_t) atomic_int_fast16_t;
|
||||
typedef _Atomic(uint_fast16_t) atomic_uint_fast16_t;
|
||||
typedef _Atomic(int_fast32_t) atomic_int_fast32_t;
|
||||
typedef _Atomic(uint_fast32_t) atomic_uint_fast32_t;
|
||||
typedef _Atomic(int_fast64_t) atomic_int_fast64_t;
|
||||
typedef _Atomic(uint_fast64_t) atomic_uint_fast64_t;
|
||||
typedef _Atomic(intptr_t) atomic_intptr_t;
|
||||
typedef _Atomic(uintptr_t) atomic_uintptr_t;
|
||||
typedef _Atomic(size_t) atomic_size_t;
|
||||
typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;
|
||||
typedef _Atomic(intmax_t) atomic_intmax_t;
|
||||
typedef _Atomic(uintmax_t) atomic_uintmax_t;
|
||||
typedef struct atomic_flag { atomic_bool _Value; } atomic_flag;
|
||||
typedef enum memory_order {
|
||||
memory_order_relaxed,
|
||||
memory_order_consume,
|
||||
memory_order_acquire,
|
||||
memory_order_release,
|
||||
memory_order_acq_rel,
|
||||
memory_order_seq_cst
|
||||
} memory_order;
|
||||
|
||||
#endif
|
2
generator/fake_libc_include/_syslist.h
Normal file
2
generator/fake_libc_include/_syslist.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "_fake_defines.h"
|
||||
#include "_fake_typedefs.h"
|
2
generator/fake_libc_include/stdbool.h
Normal file
2
generator/fake_libc_include/stdbool.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "_fake_defines.h"
|
||||
#include "_fake_typedefs.h"
|
2
generator/fake_libc_include/stddef.h
Normal file
2
generator/fake_libc_include/stddef.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "_fake_defines.h"
|
||||
#include "_fake_typedefs.h"
|
2
generator/fake_libc_include/stdint.h
Normal file
2
generator/fake_libc_include/stdint.h
Normal file
@ -0,0 +1,2 @@
|
||||
#include "_fake_defines.h"
|
||||
#include "_fake_typedefs.h"
|
5
generator/gen_repo_bindings.sh
Normal file
5
generator/gen_repo_bindings.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
REPO_ROOT=$(realpath $(dirname $(dirname $0)))
|
||||
|
||||
# Generate odin bindings
|
||||
python $REPO_ROOT/generator/cli.py $REPO_ROOT/clay.h --output-dir $REPO_ROOT/bindings/odin/clay-odin --generator odin --verbose
|
0
generator/generators/__init__.py
Normal file
0
generator/generators/__init__.py
Normal file
47
generator/generators/base_generator.py
Normal file
47
generator/generators/base_generator.py
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
from parser import ExtractedSymbols, ExtractedEnum, ExtractedStruct, ExtractedFunction
|
||||
from typing import Any, Callable, DefaultDict, Literal, NotRequired, Optional, TypedDict
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
|
||||
SymbolType = Literal['enum', 'struct', 'function']
|
||||
|
||||
class BaseGenerator:
|
||||
def __init__(self, extracted_symbols: ExtractedSymbols):
|
||||
self.extracted_symbols = extracted_symbols
|
||||
self.output_content: dict[str, list[str]] = dict()
|
||||
|
||||
def generate(self) -> None:
|
||||
pass
|
||||
|
||||
def has_symbol(self, symbol: str) -> bool:
|
||||
return (
|
||||
symbol in self.extracted_symbols.enums or
|
||||
symbol in self.extracted_symbols.structs or
|
||||
symbol in self.extracted_symbols.functions
|
||||
)
|
||||
|
||||
def get_symbol_type(self, symbol: str) -> SymbolType:
|
||||
if symbol in self.extracted_symbols.enums:
|
||||
return 'enum'
|
||||
elif symbol in self.extracted_symbols.structs:
|
||||
return 'struct'
|
||||
elif symbol in self.extracted_symbols.functions:
|
||||
return 'function'
|
||||
raise ValueError(f'Unknown symbol: {symbol}')
|
||||
|
||||
def _write(self, file_name: str, content: str) -> None:
|
||||
if file_name not in self.output_content:
|
||||
self.output_content[file_name] = []
|
||||
self.output_content[file_name].append(content)
|
||||
|
||||
def write_outputs(self, output_dir: Path) -> None:
|
||||
for file_name, content in self.output_content.items():
|
||||
(output_dir / file_name).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_dir / file_name, 'w') as f:
|
||||
f.write("\n".join(content))
|
||||
|
||||
def get_outputs(self) -> dict[str, str]:
|
||||
return {file_name: "\n".join(content) for file_name, content in self.output_content.items()}
|
||||
|
||||
|
164
generator/generators/odin/clay.template.odin
Normal file
164
generator/generators/odin/clay.template.odin
Normal file
@ -0,0 +1,164 @@
|
||||
package clay
|
||||
|
||||
import "core:c"
|
||||
import "core:strings"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
foreign import Clay "windows/clay.lib"
|
||||
} else when ODIN_OS == .Linux {
|
||||
foreign import Clay "linux/clay.a"
|
||||
} else when ODIN_OS == .Darwin {
|
||||
when ODIN_ARCH == .arm64 {
|
||||
foreign import Clay "macos-arm64/clay.a"
|
||||
} else {
|
||||
foreign import Clay "macos/clay.a"
|
||||
}
|
||||
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
||||
foreign import Clay "wasm/clay.o"
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
EnumBackingType :: u32
|
||||
} else {
|
||||
EnumBackingType :: u8
|
||||
}
|
||||
|
||||
{{enums}}
|
||||
|
||||
Context :: struct {
|
||||
|
||||
}
|
||||
|
||||
ClayArray :: struct($type: typeid) {
|
||||
capacity: i32,
|
||||
length: i32,
|
||||
internalArray: [^]type,
|
||||
}
|
||||
|
||||
SizingConstraints :: struct #raw_union {
|
||||
sizeMinMax: SizingConstraintsMinMax,
|
||||
sizePercent: c.float,
|
||||
}
|
||||
|
||||
TypedConfig :: struct {
|
||||
type: ElementConfigType,
|
||||
config: rawptr,
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
{{structs}}
|
||||
|
||||
@(link_prefix = "Clay_", default_calling_convention = "c")
|
||||
foreign Clay {
|
||||
{{public_functions}}
|
||||
}
|
||||
|
||||
@(link_prefix = "Clay_", default_calling_convention = "c", private)
|
||||
foreign Clay {
|
||||
{{private_functions}}
|
||||
}
|
||||
|
||||
@(require_results, deferred_none = _CloseElement)
|
||||
UI :: proc(configs: ..TypedConfig) -> bool {
|
||||
_OpenElement()
|
||||
for config in configs {
|
||||
#partial switch (config.type) {
|
||||
case ElementConfigType.Id:
|
||||
_AttachId(config.id)
|
||||
case ElementConfigType.Layout:
|
||||
_AttachLayoutConfig(cast(^LayoutConfig)config.config)
|
||||
case:
|
||||
_AttachElementConfig(config.config, config.type)
|
||||
}
|
||||
}
|
||||
_ElementPostConfiguration()
|
||||
return true
|
||||
}
|
||||
|
||||
Layout :: proc(config: LayoutConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Layout, config = _StoreLayoutConfig(config) }
|
||||
}
|
||||
|
||||
PaddingAll :: proc (padding: u16) -> Padding {
|
||||
return { padding, padding, padding, padding }
|
||||
}
|
||||
|
||||
Rectangle :: proc(config: RectangleElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Rectangle, config = _StoreRectangleElementConfig(config)}
|
||||
}
|
||||
|
||||
Text :: proc(text: string, config: ^TextElementConfig) {
|
||||
_OpenTextElement(MakeString(text), config)
|
||||
}
|
||||
|
||||
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
|
||||
return _StoreTextElementConfig(config)
|
||||
}
|
||||
|
||||
Image :: proc(config: ImageElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Image, config = _StoreImageElementConfig(config)}
|
||||
}
|
||||
|
||||
Floating :: proc(config: FloatingElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Floating, config = _StoreFloatingElementConfig(config)}
|
||||
}
|
||||
|
||||
Custom :: proc(config: CustomElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Custom, config = _StoreCustomElementConfig(config)}
|
||||
}
|
||||
|
||||
Scroll :: proc(config: ScrollElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Scroll, config = _StoreScrollElementConfig(config)}
|
||||
}
|
||||
|
||||
Border :: proc(config: BorderElementConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Border, config = _StoreBorderElementConfig(config)}
|
||||
}
|
||||
|
||||
BorderOutside :: proc(outsideBorders: BorderData) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}) }
|
||||
}
|
||||
|
||||
BorderOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
) }
|
||||
}
|
||||
|
||||
BorderAll :: proc(allBorders: BorderData) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}) }
|
||||
}
|
||||
|
||||
BorderAllRadius :: proc(allBorders: BorderData, radius: f32) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
) }
|
||||
}
|
||||
|
||||
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
|
||||
return CornerRadius{radius, radius, radius, radius}
|
||||
}
|
||||
|
||||
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = sizeMinMax}}
|
||||
}
|
||||
|
||||
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.GROW, constraints = {sizeMinMax = sizeMinMax}}
|
||||
}
|
||||
|
||||
SizingFixed :: proc(size: c.float) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.FIXED, constraints = {sizeMinMax = {size, size}}}
|
||||
}
|
||||
|
||||
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.PERCENT, constraints = {sizePercent = sizePercent}}
|
||||
}
|
||||
|
||||
MakeString :: proc(label: string) -> String {
|
||||
return String{chars = raw_data(label), length = cast(c.int)len(label)}
|
||||
}
|
||||
|
||||
ID :: proc(label: string, index: u32 = 0) -> TypedConfig {
|
||||
return { type = ElementConfigType.Id, id = _HashString(MakeString(label), index, 0) }
|
||||
}
|
315
generator/generators/odin_generator.py
Normal file
315
generator/generators/odin_generator.py
Normal file
@ -0,0 +1,315 @@
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from parser import ExtractedSymbolType
|
||||
from generators.base_generator import BaseGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_common_prefix(keys: list[str]) -> str:
|
||||
# find a prefix that's shared between all keys
|
||||
prefix = ""
|
||||
for i in range(min(map(len, keys))):
|
||||
if len(set(key[i] for key in keys)) > 1:
|
||||
break
|
||||
prefix += keys[0][i]
|
||||
return prefix
|
||||
|
||||
def snake_case_to_pascal_case(snake_case: str) -> str:
|
||||
return ''.join(word.lower().capitalize() for word in snake_case.split('_'))
|
||||
|
||||
|
||||
SYMBOL_NAME_OVERRIDES = {
|
||||
'Clay_TextElementConfigWrapMode': 'TextWrapMode',
|
||||
'Clay_Border': 'BorderData',
|
||||
'Clay_SizingMinMax': 'SizingConstraintsMinMax',
|
||||
}
|
||||
SYMBOL_COMPLETE_OVERRIDES = {
|
||||
'Clay_RenderCommandArray': 'ClayArray(RenderCommand)',
|
||||
'Clay_Context': 'Context',
|
||||
'Clay_ElementConfig': None,
|
||||
# 'Clay_SetQueryScrollOffsetFunction': None,
|
||||
}
|
||||
|
||||
# These enums should have output binding members that are PascalCase instead of UPPER_SNAKE_CASE.
|
||||
ENUM_MEMBER_PASCAL = {
|
||||
'Clay_RenderCommandType',
|
||||
'Clay_TextElementConfigWrapMode',
|
||||
'Clay__ElementConfigType',
|
||||
}
|
||||
ENUM_MEMBER_OVERRIDES = {
|
||||
'Clay__ElementConfigType': {
|
||||
'CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER': 'Border',
|
||||
'CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER': 'Floating',
|
||||
'CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER': 'Scroll',
|
||||
}
|
||||
}
|
||||
ENUM_ADDITIONAL_MEMBERS = {
|
||||
'Clay__ElementConfigType': {
|
||||
'Id': 65,
|
||||
'Layout': 66,
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_MAPPING = {
|
||||
'*char': '[^]c.char',
|
||||
'const *char': '[^]c.char',
|
||||
'*void': 'rawptr',
|
||||
'bool': 'bool',
|
||||
'float': 'c.float',
|
||||
'uint16_t': 'u16',
|
||||
'uint32_t': 'u32',
|
||||
'int32_t': 'c.int32_t',
|
||||
'uintptr_t': 'rawptr',
|
||||
'intptr_t': 'rawptr',
|
||||
'void': 'void',
|
||||
}
|
||||
STRUCT_TYPE_OVERRIDES = {
|
||||
'Clay_Arena': {
|
||||
'nextAllocation': 'uintptr',
|
||||
'capacity': 'uintptr',
|
||||
},
|
||||
'Clay_SizingAxis': {
|
||||
'size': 'SizingConstraints',
|
||||
},
|
||||
"Clay_RenderCommand": {
|
||||
"zIndex": 'i32',
|
||||
},
|
||||
}
|
||||
STRUCT_MEMBER_OVERRIDES = {
|
||||
'Clay_ErrorHandler': {
|
||||
'errorHandlerFunction': 'handler',
|
||||
},
|
||||
'Clay_SizingAxis': {
|
||||
'size': 'constraints',
|
||||
},
|
||||
}
|
||||
STRUCT_OVERRIDE_AS_FIXED_ARRAY = {
|
||||
'Clay_Color',
|
||||
'Clay_Vector2',
|
||||
}
|
||||
|
||||
FUNCTION_PARAM_OVERRIDES = {
|
||||
'Clay_SetCurrentContext': {
|
||||
'context': 'ctx',
|
||||
},
|
||||
}
|
||||
FUNCTION_TYPE_OVERRIDES = {
|
||||
'Clay_CreateArenaWithCapacityAndMemory': {
|
||||
'offset': '[^]u8',
|
||||
},
|
||||
'Clay_SetMeasureTextFunction': {
|
||||
'userData': 'uintptr',
|
||||
},
|
||||
'Clay_RenderCommandArray_Get': {
|
||||
'index': 'i32',
|
||||
},
|
||||
"Clay__AttachElementConfig": {
|
||||
"config": 'rawptr',
|
||||
},
|
||||
}
|
||||
|
||||
class OdinGenerator(BaseGenerator):
|
||||
|
||||
def generate(self) -> None:
|
||||
self.generate_structs()
|
||||
self.generate_enums()
|
||||
self.generate_functions()
|
||||
|
||||
odin_template_path = Path(__file__).parent / 'odin' / 'clay.template.odin'
|
||||
with open(odin_template_path, 'r') as f:
|
||||
template = f.read()
|
||||
self.output_content['clay.odin'] = (
|
||||
template
|
||||
.replace('{{structs}}', '\n'.join(self.output_content['struct']))
|
||||
.replace('{{enums}}', '\n'.join(self.output_content['enum']))
|
||||
.replace('{{public_functions}}', '\n'.join(self.output_content['public_function']))
|
||||
.replace('{{private_functions}}', '\n'.join(self.output_content['private_function']))
|
||||
.splitlines()
|
||||
)
|
||||
del self.output_content['struct']
|
||||
del self.output_content['enum']
|
||||
del self.output_content['private_function']
|
||||
del self.output_content['public_function']
|
||||
|
||||
def get_symbol_name(self, symbol: str) -> str:
|
||||
if symbol in SYMBOL_NAME_OVERRIDES:
|
||||
return SYMBOL_NAME_OVERRIDES[symbol]
|
||||
symbol_type = self.get_symbol_type(symbol)
|
||||
base_name = symbol.removeprefix('Clay_')
|
||||
if symbol_type == 'enum':
|
||||
return base_name.removeprefix('_') # Clay_ and Clay__ are exported as public types.
|
||||
elif symbol_type == 'struct':
|
||||
return base_name
|
||||
elif symbol_type == 'function':
|
||||
return base_name
|
||||
raise ValueError(f'Unknown symbol: {symbol}')
|
||||
|
||||
def format_type(self, type: ExtractedSymbolType) -> str:
|
||||
if isinstance(type, str):
|
||||
return type
|
||||
|
||||
parameter_strs = []
|
||||
for param_name, param_type in type['params']:
|
||||
parameter_strs.append(f"{param_name}: {self.format_type(param_type or 'unknown')}")
|
||||
return_type_str = ''
|
||||
if type['return_type'] is not None and type['return_type'] != 'void':
|
||||
return_type_str = ' -> ' + self.format_type(type['return_type'])
|
||||
return f"proc \"c\" ({', '.join(parameter_strs)}){return_type_str}"
|
||||
|
||||
def resolve_binding_type(self, symbol: str, member: str | None, member_type: ExtractedSymbolType | None, type_overrides: dict[str, dict[str, str]]) -> str | None:
|
||||
if isinstance(member_type, str):
|
||||
if member_type in SYMBOL_COMPLETE_OVERRIDES:
|
||||
return SYMBOL_COMPLETE_OVERRIDES[member_type]
|
||||
if symbol in type_overrides and member in type_overrides[symbol]:
|
||||
return type_overrides[symbol][member]
|
||||
if member_type in TYPE_MAPPING:
|
||||
return TYPE_MAPPING[member_type]
|
||||
if member_type and self.has_symbol(member_type):
|
||||
return self.get_symbol_name(member_type)
|
||||
if member_type and member_type.startswith('*'):
|
||||
result = self.resolve_binding_type(symbol, member, member_type[1:], type_overrides)
|
||||
if result:
|
||||
return f"^{result}"
|
||||
return None
|
||||
if member_type is None:
|
||||
return None
|
||||
|
||||
resolved_parameters = []
|
||||
for param_name, param_type in member_type['params']:
|
||||
resolved_param = self.resolve_binding_type(symbol, param_name, param_type, type_overrides)
|
||||
if resolved_param is None:
|
||||
return None
|
||||
resolved_parameters.append((param_name, resolved_param))
|
||||
resolved_return_type = self.resolve_binding_type(symbol, None, member_type['return_type'], type_overrides)
|
||||
if resolved_return_type is None:
|
||||
return None
|
||||
return self.format_type({
|
||||
"params": resolved_parameters,
|
||||
"return_type": resolved_return_type,
|
||||
})
|
||||
|
||||
def generate_structs(self) -> None:
|
||||
for struct, struct_data in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]):
|
||||
members = struct_data['attrs']
|
||||
if not struct.startswith('Clay_'):
|
||||
continue
|
||||
if struct in SYMBOL_COMPLETE_OVERRIDES:
|
||||
continue
|
||||
|
||||
binding_name = self.get_symbol_name(struct)
|
||||
if binding_name.startswith('_'):
|
||||
continue
|
||||
|
||||
if struct in STRUCT_OVERRIDE_AS_FIXED_ARRAY:
|
||||
array_size = len(members)
|
||||
first_elem = list(members.values())[0]
|
||||
array_type = None
|
||||
if 'type' in first_elem:
|
||||
array_type = first_elem['type']
|
||||
|
||||
if array_type in TYPE_MAPPING:
|
||||
array_binding_type = TYPE_MAPPING[array_type]
|
||||
elif array_type and self.has_symbol(self.format_type(array_type)):
|
||||
array_binding_type = self.get_symbol_name(self.format_type(array_type))
|
||||
else:
|
||||
self._write('struct', f"// {struct} ({array_type}) - has no mapping")
|
||||
continue
|
||||
|
||||
self._write('struct', f"// {struct} (overridden as fixed array)")
|
||||
self._write('struct', f"{binding_name} :: [{array_size}]{array_binding_type}")
|
||||
self._write('struct', "")
|
||||
continue
|
||||
|
||||
raw_union = ' #raw_union' if struct_data.get('is_union', False) else ''
|
||||
|
||||
self._write('struct', f"// {struct}")
|
||||
self._write('struct', f"{binding_name} :: struct{raw_union} {{")
|
||||
|
||||
for member, member_info in members.items():
|
||||
if struct in STRUCT_TYPE_OVERRIDES and member in STRUCT_TYPE_OVERRIDES[struct]:
|
||||
member_type = 'unknown'
|
||||
elif not 'type' in member_info:
|
||||
self._write('struct', f" // {member} (unknown type)")
|
||||
continue
|
||||
else:
|
||||
member_type = member_info['type']
|
||||
|
||||
binding_member_name = member
|
||||
if struct in STRUCT_MEMBER_OVERRIDES and member in STRUCT_MEMBER_OVERRIDES[struct]:
|
||||
binding_member_name = STRUCT_MEMBER_OVERRIDES[struct][member]
|
||||
|
||||
member_binding_type = self.resolve_binding_type(struct, member, member_type, STRUCT_TYPE_OVERRIDES)
|
||||
if member_binding_type is None:
|
||||
self._write('struct', f" // {binding_member_name} ({member_type}) - has no mapping")
|
||||
continue
|
||||
self._write('struct', f" {binding_member_name}: {member_binding_type}, // {member} ({member_type})")
|
||||
self._write('struct', "}")
|
||||
self._write('struct', '')
|
||||
|
||||
def generate_enums(self) -> None:
|
||||
for enum, members in sorted(self.extracted_symbols.enums.items(), key=lambda x: x[0]):
|
||||
if not enum.startswith('Clay_'):
|
||||
continue
|
||||
if enum in SYMBOL_COMPLETE_OVERRIDES:
|
||||
continue
|
||||
|
||||
binding_name = self.get_symbol_name(enum)
|
||||
common_member_prefix = get_common_prefix(list(members.keys()))
|
||||
self._write('enum', f"// {enum}")
|
||||
self._write('enum', f"{binding_name} :: enum EnumBackingType {{")
|
||||
for member in members:
|
||||
if enum in ENUM_MEMBER_OVERRIDES and member in ENUM_MEMBER_OVERRIDES[enum]:
|
||||
binding_member_name = ENUM_MEMBER_OVERRIDES[enum][member]
|
||||
else:
|
||||
binding_member_name = member.removeprefix(common_member_prefix)
|
||||
if enum in ENUM_MEMBER_PASCAL:
|
||||
binding_member_name = snake_case_to_pascal_case(binding_member_name)
|
||||
|
||||
if members[member] is not None:
|
||||
self._write('enum', f" {binding_member_name} = {members[member]}, // {member}")
|
||||
else:
|
||||
self._write('enum', f" {binding_member_name}, // {member}")
|
||||
|
||||
if enum in ENUM_ADDITIONAL_MEMBERS:
|
||||
self._write('enum', ' // Odin specific enum types')
|
||||
for member, value in ENUM_ADDITIONAL_MEMBERS[enum].items():
|
||||
self._write('enum', f" {member} = {value},")
|
||||
self._write('enum', "}")
|
||||
self._write('enum', '')
|
||||
|
||||
def generate_functions(self) -> None:
|
||||
for function, function_info in sorted(self.extracted_symbols.functions.items(), key=lambda x: x[0]):
|
||||
if not function.startswith('Clay_'):
|
||||
continue
|
||||
if function in SYMBOL_COMPLETE_OVERRIDES:
|
||||
continue
|
||||
is_private = function.startswith('Clay__')
|
||||
write_to = 'private_function' if is_private else 'public_function'
|
||||
|
||||
binding_name = self.get_symbol_name(function)
|
||||
|
||||
return_type = function_info['return_type']
|
||||
binding_return_type = self.resolve_binding_type(function, None, return_type, {})
|
||||
if binding_return_type is None:
|
||||
self._write(write_to, f" // {function} ({return_type}) - has no mapping")
|
||||
continue
|
||||
|
||||
skip = False
|
||||
binding_params = []
|
||||
for param_name, param_type in function_info['params']:
|
||||
binding_param_name = param_name
|
||||
if function in FUNCTION_PARAM_OVERRIDES and param_name in FUNCTION_PARAM_OVERRIDES[function]:
|
||||
binding_param_name = FUNCTION_PARAM_OVERRIDES[function][param_name]
|
||||
binding_param_type = self.resolve_binding_type(function, param_name, param_type, FUNCTION_TYPE_OVERRIDES)
|
||||
if binding_param_type is None:
|
||||
skip = True
|
||||
binding_params.append(f"{binding_param_name}: {binding_param_type}")
|
||||
if skip:
|
||||
self._write(write_to, f" // {function} - has no mapping")
|
||||
continue
|
||||
|
||||
binding_params_str = ', '.join(binding_params)
|
||||
return_str = f" -> {binding_return_type}" if binding_return_type != 'void' else ''
|
||||
self._write(write_to, f" {binding_name} :: proc({binding_params_str}){return_str} --- // {function}")
|
||||
|
173
generator/parser.py
Normal file
173
generator/parser.py
Normal file
@ -0,0 +1,173 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, TypedDict, NotRequired, Union
|
||||
from pycparser import c_ast, parse_file, preprocess_file
|
||||
from pathlib import Path
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ExtractedSymbolType = Union[str, "ExtractedFunction"]
|
||||
|
||||
class ExtractedStructAttributeUnion(TypedDict):
|
||||
type: Optional[ExtractedSymbolType]
|
||||
|
||||
class ExtractedStructAttribute(TypedDict):
|
||||
type: NotRequired[ExtractedSymbolType]
|
||||
union: NotRequired[dict[str, Optional[ExtractedSymbolType]]]
|
||||
|
||||
class ExtractedStruct(TypedDict):
|
||||
attrs: dict[str, ExtractedStructAttribute]
|
||||
is_union: NotRequired[bool]
|
||||
|
||||
ExtractedEnum = dict[str, Optional[str]]
|
||||
ExtractedFunctionParam = tuple[str, Optional[ExtractedSymbolType]]
|
||||
|
||||
class ExtractedFunction(TypedDict):
|
||||
return_type: Optional["ExtractedSymbolType"]
|
||||
params: list[ExtractedFunctionParam]
|
||||
|
||||
@dataclass
|
||||
class ExtractedSymbols:
|
||||
structs: dict[str, ExtractedStruct]
|
||||
enums: dict[str, ExtractedEnum]
|
||||
functions: dict[str, ExtractedFunction]
|
||||
|
||||
def get_type_names(node: c_ast.Node, prefix: str="") -> Optional[ExtractedSymbolType]:
|
||||
if isinstance(node, c_ast.TypeDecl) and hasattr(node, 'quals') and node.quals:
|
||||
prefix = " ".join(node.quals) + " " + prefix
|
||||
if isinstance(node, c_ast.PtrDecl):
|
||||
prefix = "*" + prefix
|
||||
if isinstance(node, c_ast.FuncDecl):
|
||||
func: ExtractedFunction = {
|
||||
'return_type': get_type_names(node.type),
|
||||
'params': [],
|
||||
}
|
||||
for param in node.args.params:
|
||||
if param.name is None:
|
||||
continue
|
||||
func['params'].append((param.name, get_type_names(param)))
|
||||
return func
|
||||
|
||||
if hasattr(node, 'names'):
|
||||
return prefix + node.names[0] # type: ignore
|
||||
elif hasattr(node, 'type'):
|
||||
return get_type_names(node.type, prefix) # type: ignore
|
||||
return None
|
||||
|
||||
class Visitor(c_ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.structs: dict[str, ExtractedStruct] = {}
|
||||
self.enums: dict[str, ExtractedEnum] = {}
|
||||
self.functions: dict[str, ExtractedFunction] = {}
|
||||
|
||||
def visit_FuncDecl(self, node: c_ast.FuncDecl):
|
||||
# node.show()
|
||||
# logger.debug(node)
|
||||
node_type = node.type
|
||||
is_pointer = False
|
||||
if isinstance(node.type, c_ast.PtrDecl):
|
||||
node_type = node.type.type
|
||||
is_pointer = True
|
||||
|
||||
if hasattr(node_type, "declname"):
|
||||
return_type = get_type_names(node_type.type)
|
||||
if return_type is not None and isinstance(return_type, str) and is_pointer:
|
||||
return_type = "*" + return_type
|
||||
func: ExtractedFunction = {
|
||||
'return_type': return_type,
|
||||
'params': [],
|
||||
}
|
||||
for param in node.args.params:
|
||||
if param.name is None:
|
||||
continue
|
||||
func['params'].append((param.name, get_type_names(param)))
|
||||
self.functions[node_type.declname] = func
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Struct(self, node: c_ast.Struct):
|
||||
# node.show()
|
||||
if node.name and node.decls:
|
||||
struct = {}
|
||||
for decl in node.decls:
|
||||
struct[decl.name] = {
|
||||
"type": get_type_names(decl),
|
||||
}
|
||||
self.structs[node.name] = {
|
||||
'attrs': struct,
|
||||
}
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Typedef(self, node: c_ast.Typedef):
|
||||
# node.show()
|
||||
if hasattr(node.type, 'type') and hasattr(node.type.type, 'decls') and node.type.type.decls:
|
||||
struct = {}
|
||||
for decl in node.type.type.decls:
|
||||
if hasattr(decl, 'type') and hasattr(decl.type, 'type') and isinstance(decl.type.type, c_ast.Union):
|
||||
union = {}
|
||||
for field in decl.type.type.decls:
|
||||
union[field.name] = get_type_names(field)
|
||||
struct[decl.name] = {
|
||||
'union': union
|
||||
}
|
||||
else:
|
||||
struct[decl.name] = {
|
||||
"type": get_type_names(decl),
|
||||
}
|
||||
|
||||
self.structs[node.name] = {
|
||||
'attrs': struct,
|
||||
'is_union': isinstance(node.type.type, c_ast.Union),
|
||||
}
|
||||
if hasattr(node.type, 'type') and isinstance(node.type.type, c_ast.Enum):
|
||||
enum = {}
|
||||
for enumerator in node.type.type.values.enumerators:
|
||||
if enumerator.value is None:
|
||||
enum[enumerator.name] = None
|
||||
else:
|
||||
enum[enumerator.name] = enumerator.value.value
|
||||
self.enums[node.name] = enum
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def parse_headers(input_files: list[Path], tmp_dir: Path) -> ExtractedSymbols:
|
||||
cpp_args = ["-nostdinc", "-D__attribute__(x)=", "-E"]
|
||||
|
||||
# Make a new clay.h that combines the provided input files, so that we can add bindings for customized structs
|
||||
with open(tmp_dir / 'merged_clay.h', 'w') as f:
|
||||
for input_file in input_files:
|
||||
with open(input_file, 'r') as f2:
|
||||
for line in f2:
|
||||
# Ignore includes, as they should be manually included in input_files.
|
||||
if line.startswith("#include"):
|
||||
continue
|
||||
|
||||
# Ignore the CLAY_IMPLEMENTATION define, because we only want to parse the public api code.
|
||||
# This is helpful so that the user can provide their implementation code, which will contain any custom extensions
|
||||
if "#define CLAY_IMPLEMENTATION" in line:
|
||||
continue
|
||||
|
||||
f.write(line)
|
||||
|
||||
# Preprocess the file
|
||||
logger.info("Preprocessing file")
|
||||
preprocessed = preprocess_file(tmp_dir / 'merged_clay.h', cpp_path="cpp", cpp_args=cpp_args) # type: ignore
|
||||
with open(tmp_dir / 'clay.preprocessed.h', 'w') as f:
|
||||
f.write(preprocessed)
|
||||
|
||||
# Parse the file
|
||||
logger.info("Parsing file")
|
||||
ast = parse_file(tmp_dir / 'clay.preprocessed.h', use_cpp=False) # type: ignore
|
||||
|
||||
# Extract symbols
|
||||
visitor = Visitor()
|
||||
visitor.visit(ast)
|
||||
|
||||
result = ExtractedSymbols(
|
||||
structs=visitor.structs,
|
||||
enums=visitor.enums,
|
||||
functions=visitor.functions
|
||||
)
|
||||
return result
|
1
generator/requirements.txt
Normal file
1
generator/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
pycparser==2.22
|
Loading…
Reference in New Issue
Block a user