Compare commits

...

5 Commits

Author SHA1 Message Date
Harrison Lambeth
b3722fc00d
Merge 0cdc1fdc7a into 01d3ab127f 2025-01-20 11:18:17 +09:00
Nic Barker
01d3ab127f Update odin bindings for new text measurement API
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-01-20 11:39:59 +13:00
Nic Barker
326325ffaf
[Core] Convert measureText pointer to value string slice (#214) 2025-01-20 11:27:22 +13:00
Daniel Collin
e8025cc254
SetMesureText and SetQueryScrollOffset takes userData (#212) 2025-01-20 10:59:02 +13:00
Harrison Lambeth
0cdc1fdc7a initial pass 2025-01-15 22:34:35 -07:00
50 changed files with 1288 additions and 45 deletions

7
.gitignore vendored
View File

@ -4,4 +4,9 @@ cmake-build-release/
.idea/
node_modules/
*.dSYM
.vs/
.vs/
bindings/odin/clay-odin/tmp/
generator/__pycache__/
generator/generators/__pycache__/

View File

@ -2,7 +2,7 @@ cp ../../clay.h clay.c;
# Intel Mac
clang -c -DCLAY_IMPLEMENTATION -o clay.o -static -target x86_64-apple-darwin clay.c -fPIC && ar r clay-odin/macos/clay.a clay.o;
# ARM Mac
clang -c -DCLAY_IMPLEMENTATION -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o;
clang -c -DCLAY_IMPLEMENTATION -g -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o;
# x64 Windows
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/windows/clay.lib -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static clay.c;
# Linux

View File

@ -22,6 +22,12 @@ String :: struct {
chars: [^]c.char,
}
StringSlice :: struct {
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
}
Vector2 :: [2]c.float
Dimensions :: struct {
@ -305,7 +311,7 @@ foreign Clay {
PointerOver :: proc(id: ElementId) -> bool ---
GetElementId :: proc(id: String) -> ElementId ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions) ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: uintptr) -> Dimensions, userData: uintptr) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
SetDebugModeEnabled :: proc(enabled: bool) ---
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -493,8 +493,8 @@ main :: proc() {
minMemorySize: u32 = clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.SetMeasureTextFunction(measureText)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
clay.SetMeasureTextFunction(measureText, 0)
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")

View File

@ -16,7 +16,7 @@ clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color {
raylibFonts := [10]RaylibFont{}
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: uintptr) -> clay.Dimensions {
// Measure string size for Font
textSize: clay.Dimensions = {0, 0}

41
clay.h
View File

@ -1,4 +1,4 @@
// VERSION: 0.11
// VERSION: 0.12
/*
NOTE: In order to use this library you must define
@ -191,6 +191,13 @@ CLAY__TYPEDEF(Clay__StringArray, struct {
Clay_String *internalArray;
});
CLAY__TYPEDEF(Clay_StringSlice, struct {
int32_t length;
const char *chars;
// The source string / char* that this slice was derived from
const char *baseChars;
});
typedef struct Clay_Context Clay_Context;
CLAY__TYPEDEF(Clay_Arena, struct {
@ -527,8 +534,8 @@ bool Clay_Hovered(void);
void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
bool Clay_PointerOver(Clay_ElementId elementId);
Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config));
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId));
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData);
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, uintptr_t userData), uintptr_t userData);
Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
void Clay_SetDebugModeEnabled(bool enabled);
bool Clay_IsDebugModeEnabled(void);
@ -1407,6 +1414,8 @@ struct Clay_Context {
uint32_t debugSelectedElementId;
uint32_t generation;
uintptr_t arenaResetOffset;
uintptr_t mesureTextUserData;
uintptr_t queryScrollOffsetUserData;
Clay_Arena internalArena;
// Layout Elements / Render Commands
Clay_LayoutElementArray layoutElements;
@ -1480,11 +1489,11 @@ Clay_String Clay__WriteStringToCharBuffer(Clay__CharArray *buffer, Clay_String s
}
#ifdef CLAY_WASM
__attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_String *text, Clay_TextElementConfig *config);
__attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId);
__attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData);
__attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId, uintptr_t userData);
#else
Clay_Dimensions (*Clay__MeasureText)(Clay_String *text, Clay_TextElementConfig *config);
Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId);
Clay_Dimensions (*Clay__MeasureText)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData);
Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId, uintptr_t userData);
#endif
Clay_LayoutElement* Clay__GetOpenLayoutElement(void) {
@ -1699,7 +1708,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
float lineWidth = 0;
float measuredWidth = 0;
float measuredHeight = 0;
float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, config).width;
float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, config, context->mesureTextUserData).width;
Clay__MeasuredWord tempWord = { .next = -1 };
Clay__MeasuredWord *previousWord = &tempWord;
while (end < text->length) {
@ -1716,8 +1725,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
char current = text->chars[end];
if (current == ' ' || current == '\n') {
int32_t length = end - start;
Clay_String word = { .length = length, .chars = &text->chars[start] };
Clay_Dimensions dimensions = Clay__MeasureText(&word, config);
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->mesureTextUserData);
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
if (current == ' ') {
dimensions.width += spaceWidth;
@ -1739,8 +1747,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
end++;
}
if (end - start > 0) {
Clay_String lastWord = { .length = end - start, .chars = &text->chars[start] };
Clay_Dimensions dimensions = Clay__MeasureText(&lastWord, config);
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = end - start, .chars = &text->chars[start], .baseChars = text->chars }, config, context->mesureTextUserData);
Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord);
lineWidth += dimensions.width;
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
@ -1902,7 +1909,7 @@ void Clay__ElementPostConfiguration(void) {
scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&context->scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true});
}
if (context->externalScrollHandlingEnabled) {
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId);
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData);
}
break;
}
@ -3678,11 +3685,15 @@ Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset
}
#ifndef CLAY_WASM
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config)) {
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData) {
Clay_Context* context = Clay_GetCurrentContext();
Clay__MeasureText = measureTextFunction;
context->mesureTextUserData = userData;
}
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId)) {
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, uintptr_t userData), uintptr_t userData) {
Clay_Context* context = Clay_GetCurrentContext();
Clay__QueryScrollOffset = queryScrollOffsetFunction;
context->queryScrollOffsetUserData = userData;
}
#endif

View File

@ -299,7 +299,10 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError());
return 1;
}
SDL2_fonts[FONT_ID_BODY_16] = (SDL2_Font) {
SDL2_Font fonts[1] = {};
fonts[FONT_ID_BODY_16] = (SDL2_Font) {
.fontId = FONT_ID_BODY_16,
.font = font,
};
@ -315,12 +318,13 @@ int main(int argc, char *argv[]) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_SetMeasureTextFunction(SDL2_MeasureText);
int windowWidth = 0;
int windowHeight = 0;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL2_MeasureText, (uintptr_t)&fonts);
Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
double deltaTime = 0;
@ -361,7 +365,7 @@ int main(int argc, char *argv[]) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands);
Clay_SDL2_Render(renderer, renderCommands, fonts);
SDL_RenderPresent(renderer);
}

View File

@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.27)
project(clay_examples_sdl3_simple_demo C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -Wall -Werror")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
include(FetchContent)

View File

@ -21,12 +21,12 @@ typedef struct app_state {
SDL_Renderer *renderer;
} AppState;
static inline Clay_Dimensions SDL_MeasureText(Clay_String *text, Clay_TextElementConfig *config)
static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData)
{
TTF_Font *font = gFonts[config->fontId];
int width, height;
if (!TTF_GetStringSize(font, text->chars, text->length, &width, &height)) {
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError());
}
@ -114,8 +114,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
int width, height;
SDL_GetWindowSize(state->window, &width, &height);
Clay_SetMeasureTextFunction(SDL_MeasureText);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL_MeasureText, 0);
*appstate = state;
return SDL_APP_CONTINUE;

View File

@ -314,7 +314,7 @@
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);

View File

@ -314,7 +314,7 @@
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);

View File

@ -84,7 +84,7 @@ int main(void) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
Clay_SetMeasureTextFunction(Raylib_MeasureText);
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) {
.font = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400),
.fontId = FONT_ID_BODY_16

View File

@ -247,7 +247,7 @@ int main(void) {
.height = GetScreenHeight() / 2
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
Clay_SetMeasureTextFunction(Raylib_MeasureText);
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) {
.font = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400),
.fontId = FONT_ID_BODY_16

View File

@ -218,8 +218,8 @@ void HandleClayErrors(Clay_ErrorData errorData) {
int main(void) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_SetMeasureTextFunction(Raylib_MeasureText);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);
profilePicture = LoadTextureFromImage(LoadImage("resources/profile-picture.png"));
Raylib_fonts[FONT_ID_BODY_24] = (Raylib_Font) {

0
generator/__init__.py Normal file
View File

83
generator/cli.py Normal file
View 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, output_dir, 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()

View 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

View 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

View File

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View File

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View File

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View File

@ -0,0 +1,2 @@
#include "_fake_defines.h"
#include "_fake_typedefs.h"

View 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

View File

View File

@ -0,0 +1,46 @@
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():
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()}

View File

@ -0,0 +1,158 @@
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,
}
{{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) }
}

View File

@ -0,0 +1,279 @@
from pathlib import Path
import logging
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',
'void': 'void',
'*Clay_RectangleElementConfig': '^RectangleElementConfig',
'*Clay_TextElementConfig': '^TextElementConfig',
'*Clay_ImageElementConfig': '^ImageElementConfig',
'*Clay_FloatingElementConfig': '^FloatingElementConfig',
'*Clay_CustomElementConfig': '^CustomElementConfig',
'*Clay_ScrollElementConfig': '^ScrollElementConfig',
'*Clay_BorderElementConfig': '^BorderElementConfig',
}
STRUCT_TYPE_OVERRIDES = {
'Clay_Arena': {
'nextAllocation': 'uintptr',
'capacity': 'uintptr',
},
'Clay_ErrorHandler': {
'errorHandlerFunction': 'proc "c" (errorData: ErrorData)',
},
'Clay_SizingAxis': {
'size': 'SizingConstraints',
},
}
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': {
'measureTextFunction': 'proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions',
},
}
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 resolve_binding_type(self, symbol: str, member: str | None, member_type: str | None, type_overrides: dict[str, dict[str, str]]) -> str | None:
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
def generate_structs(self) -> None:
for struct, members in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]):
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)
array_type = list(members.values())[0]['type']
if array_type in TYPE_MAPPING:
array_binding_type = TYPE_MAPPING[array_type]
elif array_type and self.has_symbol(array_type):
array_binding_type = self.get_symbol_name(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
self._write('struct', f"// {struct}")
self._write('struct', f"{binding_name} :: struct {{")
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)
self._write(write_to, f" {binding_name} :: proc({binding_params_str}) -> {binding_return_type} --- // {function}")

152
generator/parser.py Normal file
View File

@ -0,0 +1,152 @@
from dataclasses import dataclass
from typing import Optional, TypedDict
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__)
class ExtractedStructAttributeUnion(TypedDict):
type: Optional[str]
class ExtractedStructAttribute(TypedDict):
type: Optional[str]
union: Optional[dict[str, Optional[str]]]
ExtractedStruct = dict[str, ExtractedStructAttribute]
ExtractedEnum = dict[str, Optional[str]]
ExtractedFunctionParam = tuple[str, Optional[str]]
class ExtractedFunction(TypedDict):
return_type: Optional[str]
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[str]:
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 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 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] = 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] = struct
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], output_dir: 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

View File

@ -0,0 +1 @@
pycparser==2.22

View File

@ -12,13 +12,14 @@ typedef struct
TTF_Font *font;
} SDL2_Font;
static SDL2_Font SDL2_fonts[1];
static Clay_Dimensions SDL2_MeasureText(Clay_String *text, Clay_TextElementConfig *config)
static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData)
{
TTF_Font *font = SDL2_fonts[config->fontId].font;
char *chars = (char *)calloc(text->length + 1, 1);
memcpy(chars, text->chars, text->length);
SDL2_Font *fonts = (SDL2_Font*)userData;
TTF_Font *font = fonts[config->fontId].font;
char *chars = (char *)calloc(text.length + 1, 1);
memcpy(chars, text.chars, text.length);
int width = 0;
int height = 0;
if (TTF_SizeUTF8(font, chars, &width, &height) < 0) {
@ -34,7 +35,7 @@ static Clay_Dimensions SDL2_MeasureText(Clay_String *text, Clay_TextElementConfi
SDL_Rect currentClippingRectangle;
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands)
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands, SDL2_Font *fonts)
{
for (uint32_t i = 0; i < renderCommands.length; i++)
{
@ -60,7 +61,7 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
Clay_String text = renderCommand->text;
char *cloned = (char *)calloc(text.length + 1, 1);
memcpy(cloned, text.chars, text.length);
TTF_Font* font = SDL2_fonts[config->fontId].font;
TTF_Font* font = fonts[config->fontId].font;
SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) {
.r = (Uint8)config->textColor.r,
.g = (Uint8)config->textColor.g,

View File

@ -83,7 +83,7 @@ static inline char *Clay_Cairo__NullTerminate(Clay_String *str) {
}
// Measure text using cairo's *toy* text API.
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_String *str, Clay_TextElementConfig *config) {
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_String *str, Clay_TextElementConfig *config, uintptr_t userData) {
// Edge case: Clay computes the width of a whitespace character
// once. Cairo does not factor in whitespaces when computing text
// extents, this edge-case serves as a short-circuit to introduce

View File

@ -89,7 +89,7 @@ Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int scre
uint32_t measureCalls = 0;
static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) {
measureCalls++;
// Measure string size for Font
Clay_Dimensions textSize = { 0 };
@ -101,14 +101,14 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextEle
Font fontToUse = Raylib_fonts[config->fontId].font;
float scaleFactor = config->fontSize/(float)fontToUse.baseSize;
for (int i = 0; i < text->length; ++i)
for (int i = 0; i < text.length; ++i)
{
if (text->chars[i] == '\n') {
if (text.chars[i] == '\n') {
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
lineTextWidth = 0;
continue;
}
int index = text->chars[i] - 32;
int index = text.chars[i] - 32;
if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX;
else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX);
}