commit 80e1b84cb491b8a21a832ade4dd80cc61ebdd170 Author: Sara Date: Mon Dec 30 19:08:22 2024 +0100 feat: initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..182e427 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +bin/ +compile_commands/ +compile_commands.json +.cache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eef0323 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +# Alternative GNU Make workspace makefile autogenerated by Premake + +ifndef config + config=debug +endif + +ifndef verbose + SILENT = @ +endif + +ifeq ($(config),debug) + datacomp_config = debug + +else ifeq ($(config),release) + datacomp_config = release + +else + $(error "invalid configuration $(config)") +endif + +PROJECTS := datacomp + +.PHONY: all clean help $(PROJECTS) + +all: $(PROJECTS) + +datacomp: +ifneq (,$(datacomp_config)) + @echo "==== Building datacomp ($(datacomp_config)) ====" + @${MAKE} --no-print-directory -C build -f Makefile config=$(datacomp_config) +endif + +clean: + @${MAKE} --no-print-directory -C build -f Makefile clean + +help: + @echo "Usage: make [config=name] [target]" + @echo "" + @echo "CONFIGURATIONS:" + @echo " debug" + @echo " release" + @echo "" + @echo "TARGETS:" + @echo " all (default)" + @echo " clean" + @echo " datacomp" + @echo "" + @echo "For more information, see https://github.com/premake/premake-core/wiki" \ No newline at end of file diff --git a/documentation/ux.pdf b/documentation/ux.pdf new file mode 100644 index 0000000..58cd2a0 Binary files /dev/null and b/documentation/ux.pdf differ diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..8e14836 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,21 @@ +workspace "datacomp" + configurations { "debug", "release" } + location "." + +project "datacomp" + kind "WindowedApp" + language "C" + location "build/" + files { "src/**.c" } + includedirs { "src/", "vendor/" } + links { "raylib", "m" } + targetdir "bin/" + postbuildcommands "{COPYDIR} ../resources %{cfg.targetdir}" + filter "configurations:debug" + defines { "DEBUG" } + optimize "Off" + symbols "On" + filter "configurations:release" + defines { "NDEBUG" } + optimize "On" + symbols "Off" diff --git a/resources/Inter-Regular.ttf b/resources/Inter-Regular.ttf new file mode 100644 index 0000000..b7aaca8 Binary files /dev/null and b/resources/Inter-Regular.ttf differ diff --git a/resources/InterVariable.ttf b/resources/InterVariable.ttf new file mode 100644 index 0000000..4ab79e0 Binary files /dev/null and b/resources/InterVariable.ttf differ diff --git a/src/library.c b/src/library.c new file mode 100644 index 0000000..a4eb481 --- /dev/null +++ b/src/library.c @@ -0,0 +1,59 @@ +#include "library.h" +#include "clay.h" + +bool static sidebar_open = true; + +Clay_Color const SIDEBAR_COLOR = {120.f, 120.f, 120.f, 255.f}; + +Clay_LayoutConfig const SIDEBAR_LAYOUT = { + .sizing={ + .height=CLAY_SIZING_GROW(), + .width=CLAY_SIZING_FIXED(300) + }, + .layoutDirection=CLAY_TOP_TO_BOTTOM +}; + +Clay_RectangleElementConfig const SIDEBAR_RECTANGLE = { + .color=SIDEBAR_COLOR, +}; + +static +void render_composit_library_sidebar() { + CLAY(CLAY_ID("Sidebar"), + CLAY_LAYOUT(SIDEBAR_LAYOUT), + CLAY_RECTANGLE(SIDEBAR_RECTANGLE) + ) { + CLAY(CLAY_ID("SidebarTab"), + CLAY_FLOATING({ + .attachment={ + .element=CLAY_ATTACH_POINT_LEFT_TOP, + .parent=CLAY_ATTACH_POINT_RIGHT_TOP + } + }), + CLAY_LAYOUT({ + .sizing={.width=CLAY_SIZING_FIXED(10), .height=CLAY_SIZING_FIXED(40)}, + }), + CLAY_RECTANGLE({ + .color=SIDEBAR_COLOR, + .cornerRadius={ + .topRight=16, + .bottomRight=16, + .topLeft=16, + .bottomLeft=16 + } + }) + ) { + } + } +} + +static +void render_composit_library_content() { + CLAY(CLAY_ID("Content")) { + } +} + +void render_composit_library() { + render_composit_library_sidebar(); + render_composit_library_content(); +} diff --git a/src/library.h b/src/library.h new file mode 100644 index 0000000..4855361 --- /dev/null +++ b/src/library.h @@ -0,0 +1,6 @@ +#ifndef LIBRARY_H +#define LIBRARY_H + +extern void render_composit_library(); + +#endif // !LIBRARY_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..24fa655 --- /dev/null +++ b/src/main.c @@ -0,0 +1,78 @@ +#include "styles.h" +#include "clay_renderer_raylib.c" +#include "raylib.h" +#include "library.h" +#include +#include +#include + +#define CLAY_IMPLEMENTATION +#include "clay.h" + +Clay_LayoutConfig const APPLICATION_LAYOUT = { + .sizing={ + .width=CLAY_SIZING_GROW(), + .height=CLAY_SIZING_GROW() + }, + .childGap=16, + .layoutDirection=CLAY_LEFT_TO_RIGHT +}; + +Color const BACKGROUND_COLOR = {220.f, 226.f, 233.f, 255.f}; + +bool static clay_debug_enabled = false; + +void handle_clay_errors(Clay_ErrorData error) { + printf("Clay Error: %s\n", error.errorText.chars); + abort(); +} + +void init_clay(Clay_Arena *o_arena) { + uint64_t memory_required = Clay_MinMemorySize(); + *o_arena = Clay_CreateArenaWithCapacityAndMemory(memory_required, malloc(memory_required)); + Clay_SetMeasureTextFunction(Raylib_MeasureText); + Clay_Initialize(*o_arena, + (Clay_Dimensions){ + (float)GetScreenWidth(), + (float)GetScreenHeight()}, + (Clay_ErrorHandler){handle_clay_errors}); + Clay_Raylib_Initialize(1920, 1080, "DataComp", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT); + Raylib_fonts[0] = (Raylib_Font){ + .font = LoadFontEx("resources/Inter-Regular.ttf", 48, NULL, 400), + .fontId = 0 + }; + SetTextureFilter(Raylib_fonts[0].font.texture, TEXTURE_FILTER_BILINEAR); +} + +Clay_RenderCommandArray create_layout() { + Clay_BeginLayout(); + CLAY(CLAY_ID("ApplicationContainer"), + CLAY_LAYOUT(APPLICATION_LAYOUT)) { + render_composit_library(); + } + return Clay_EndLayout(); +} + +void update_ui() { + if(IsKeyPressed(KEY_D) && IsKeyDown(KEY_LEFT_CONTROL)) + Clay_SetDebugModeEnabled(clay_debug_enabled = !clay_debug_enabled); + Vector2 const scroll_delta = GetMouseWheelMoveV(); + Vector2 const mouse_position = GetMousePosition(); + Clay_SetPointerState((Clay_Vector2){mouse_position.x, mouse_position.y}, IsMouseButtonDown(0)); + Clay_SetLayoutDimensions((Clay_Dimensions){.width=(float)GetScreenWidth(), .height=(float)GetScreenHeight()}); + Clay_UpdateScrollContainers(true, (Clay_Vector2){scroll_delta.x, scroll_delta.y}, GetFrameTime()); +} + +int main(void) { + Clay_Arena clay_mem; + init_clay(&clay_mem); + while(!WindowShouldClose()) { + update_ui(); + Clay_RenderCommandArray commands = create_layout(); + BeginDrawing(); + ClearBackground(BACKGROUND_COLOR); + Clay_Raylib_Render(commands); + EndDrawing(); + } + return 0; +} diff --git a/src/styles.c b/src/styles.c new file mode 100644 index 0000000..c65e72f --- /dev/null +++ b/src/styles.c @@ -0,0 +1,3 @@ +#include "styles.h" + +Clay_Color const TRANSPARENT = {0.f, 0.f, 0.f, 0.f}; diff --git a/src/styles.h b/src/styles.h new file mode 100644 index 0000000..f56b5cf --- /dev/null +++ b/src/styles.h @@ -0,0 +1,8 @@ +#ifndef DATACOMP_STYLES_H +#define DATACOMP_STYLES_H + +#include "clay.h" + +extern Clay_Color const TRANSPARENT; + +#endif // !DATACOMP_STYLES_H diff --git a/vendor/clay.h b/vendor/clay.h new file mode 100644 index 0000000..02b84aa --- /dev/null +++ b/vendor/clay.h @@ -0,0 +1,3900 @@ +// VERSION: 0.11 + +/* + NOTE: In order to use this library you must define + the following macro in exactly one file, _before_ including clay.h: + + #define CLAY_IMPLEMENTATION + #include "clay.h" + + See the examples folder for details. +*/ + +#include +#include +#include + +// ----------------------------------------- +// HEADER DECLARATIONS --------------------- +// ----------------------------------------- + +#ifndef CLAY_HEADER +#define CLAY_HEADER + +#ifdef CLAY_WASM +#define CLAY_WASM_EXPORT(name) __attribute__((export_name(name))) +#else +#define CLAY_WASM_EXPORT(null) +#endif + +// Public Macro API ------------------------ + +#ifdef __cplusplus +#define CLAY__CONFIG_WRAPPER(type, ...) __VA_ARGS__ +#else +#define CLAY__CONFIG_WRAPPER(type, ...) (type) __VA_ARGS__ +#endif + +#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#define CLAY_LAYOUT(...) Clay__AttachLayoutConfig(Clay__StoreLayoutConfig(CLAY__CONFIG_WRAPPER(Clay_LayoutConfig, __VA_ARGS__))) + +#define CLAY_RECTANGLE(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .rectangleElementConfig = Clay__StoreRectangleElementConfig(CLAY__INIT(Clay_RectangleElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE)) + +#define CLAY_TEXT_CONFIG(...) Clay__StoreTextElementConfig(CLAY__CONFIG_WRAPPER(Clay_TextElementConfig, __VA_ARGS__)) + +#define CLAY_IMAGE(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .imageElementConfig = Clay__StoreImageElementConfig(CLAY__INIT(Clay_ImageElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) + +#define CLAY_FLOATING(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .floatingElementConfig = Clay__StoreFloatingElementConfig(CLAY__INIT(Clay_FloatingElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER)) + +#define CLAY_CUSTOM_ELEMENT(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .customElementConfig = Clay__StoreCustomElementConfig(CLAY__INIT(Clay_CustomElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM)) + +#define CLAY_SCROLL(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .scrollElementConfig = Clay__StoreScrollElementConfig(CLAY__INIT(Clay_ScrollElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) + +#define CLAY_BORDER(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .borderElementConfig = Clay__StoreBorderElementConfig(CLAY__INIT(Clay_BorderElementConfig) __VA_ARGS__) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER)) + +#define CLAY_BORDER_OUTSIDE(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .borderElementConfig = Clay__StoreBorderElementConfig(CLAY__INIT(Clay_BorderElementConfig) { .left = __VA_ARGS__, .right = __VA_ARGS__, .top = __VA_ARGS__, .bottom = __VA_ARGS__ }) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER)) + +#define CLAY_BORDER_OUTSIDE_RADIUS(width, color, radius) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .borderElementConfig = Clay__StoreBorderElementConfig(CLAY__INIT(Clay_BorderElementConfig) { .left = { width, color }, .right = { width, color }, .top = { width, color }, .bottom = { width, color }, .cornerRadius = { radius, radius, radius, radius } })}, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER)) + +#define CLAY_BORDER_ALL(...) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .borderElementConfig = Clay__StoreBorderElementConfig(CLAY__INIT(Clay_BorderElementConfig) { .left = __VA_ARGS__, .right = __VA_ARGS__, .top = __VA_ARGS__, .bottom = __VA_ARGS__, .betweenChildren = __VA_ARGS__ }) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER)) + +#define CLAY_BORDER_ALL_RADIUS(width, color, radius) Clay__AttachElementConfig(CLAY__CONFIG_WRAPPER(Clay_ElementConfigUnion, { .borderElementConfig = Clay__StoreBorderElementConfig(CLAY__INIT(Clay_BorderElementConfig) { .left = { width, color }, .right = { width, color }, .top = { width, color }, .bottom = { width, color }, .betweenChildren = { width, color }, .cornerRadius = { radius, radius, radius, radius }}) })) + +#define CLAY_CORNER_RADIUS(radius) (CLAY__INIT(Clay_CornerRadius) { radius, radius, radius, radius }) + +#define CLAY__STRUCT_1_ARGS(a) a +#define CLAY__STRUCT_0_ARGS() {0} +#define CLAY__STRUCT_OVERRIDE(_0, _1, NAME, ...) NAME + +#define CLAY__SIZING_FIT_INTERNAL(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = __VA_ARGS__ }, .type = CLAY__SIZING_TYPE_FIT }) +#define CLAY_SIZING_FIT(...) CLAY__SIZING_FIT_INTERNAL(CLAY__STRUCT_OVERRIDE("empty", ##__VA_ARGS__, CLAY__STRUCT_1_ARGS, CLAY__STRUCT_0_ARGS)(__VA_ARGS__)) + +#define CLAY__SIZING_GROW_INTERNAL(...) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = __VA_ARGS__ }, .type = CLAY__SIZING_TYPE_GROW }) +#define CLAY_SIZING_GROW(...) CLAY__SIZING_GROW_INTERNAL(CLAY__STRUCT_OVERRIDE("empty", ##__VA_ARGS__, CLAY__STRUCT_1_ARGS, CLAY__STRUCT_0_ARGS)(__VA_ARGS__)) + +#define CLAY_SIZING_FIXED(fixedSize) (CLAY__INIT(Clay_SizingAxis) { .size = { .minMax = { fixedSize, fixedSize } }, .type = CLAY__SIZING_TYPE_FIXED }) + +#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT }) + +#define CLAY_ID(label) Clay__AttachId(Clay__HashString(CLAY_STRING(label), 0, 0)) + +#define CLAY_IDI(label, index) Clay__AttachId(Clay__HashString(CLAY_STRING(label), index, 0)) + +#define CLAY_ID_LOCAL(label) CLAY_IDI_LOCAL(label, 0) + +#define CLAY_IDI_LOCAL(label, index) Clay__AttachId(Clay__HashString(CLAY_STRING(label), Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&Clay__openLayoutElementStack, Clay__openLayoutElementStack.length - 2))->children.length + 1, Clay__GetOpenLayoutElement()->id)) + +#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0])) + +#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(string), .chars = (string) }) + +static int CLAY__ELEMENT_DEFINITION_LATCH = 0; + +#define CLAY__ELEMENT_INTERNAL(...) \ + for (\ + CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), __VA_ARGS__, Clay__ElementPostConfiguration(), 0); \ + CLAY__ELEMENT_DEFINITION_LATCH < 1; \ + ++CLAY__ELEMENT_DEFINITION_LATCH, Clay__CloseElement() \ + ) + +#define CLAY__6_ARGS(a, b, c, d, e, f) a, b, c, d, e, f +#define CLAY__5_ARGS(a, b, c, d, e) a, b, c, d, e +#define CLAY__4_ARGS(a, b, c, d) a, b, c, d +#define CLAY__3_ARGS(a, b, c) a, b, c +#define CLAY__2_ARGS(a, b) a, b +#define CLAY__1_ARGS(a) a +#define CLAY__0_ARGS() Clay__Noop() +#define CLAY__ARGS_OVERRIDE(_0, _1, _2, _3, _4, _5, _6, NAME, ...) NAME + +// Publicly visible layout element macros ----------------------------------------------------- +#define CLAY(...) CLAY__ELEMENT_INTERNAL(CLAY__ARGS_OVERRIDE("empty", ##__VA_ARGS__, CLAY__6_ARGS, CLAY__5_ARGS, CLAY__4_ARGS, CLAY__3_ARGS, CLAY__2_ARGS, CLAY__1_ARGS, CLAY__0_ARGS)(__VA_ARGS__)) + +#define CLAY_TEXT(text, textConfig) Clay__OpenTextElement(text, textConfig) + +#ifdef __cplusplus +#define CLAY__INIT(type) type +#define CLAY__TYPEDEF(name, ...) typedef __VA_ARGS__ name +#define CLAY__ALIGNMENT(type) alignof(type) +#define CLAY__POINTER_ALIGNMENT alignof(void *) +#define CLAY_PACKED_ENUM enum : uint8_t +#else + +#define CLAY__INIT(type) (type) + +#define CLAY__ALIGNMENT_STRUCT(type) struct Clay__Align##type { char c; type x; } +#define CLAY__TYPEDEF(name, ...) typedef __VA_ARGS__ name; CLAY__ALIGNMENT_STRUCT(name) +#define CLAY__ALIGNMENT(type) (offsetof(struct Clay__Align##type, x)) +#define CLAY__POINTER_ALIGNMENT CLAY__ALIGNMENT(pointer) + +// NOTE: If you need to get the offset for other standard types in the future, add them here. +struct Clay__Alignpointer { char c; void *x; }; +CLAY__ALIGNMENT_STRUCT(bool); +CLAY__ALIGNMENT_STRUCT(uint8_t); +CLAY__ALIGNMENT_STRUCT(int32_t); + +#ifdef _MSC_VER +#define CLAY_PACKED_ENUM __pragma(pack(push, 1)) enum __pragma(pack(pop)) +#else +#define CLAY_PACKED_ENUM enum __attribute__((__packed__)) +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Utility Structs ------------------------- +// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string, +// but it is also used to represent slices. +CLAY__TYPEDEF(Clay_String, struct { + size_t length; + const char *chars; +}); + +CLAY__TYPEDEF(Clay__StringArray, struct { + uint32_t capacity; + uint32_t length; + Clay_String *internalArray; +}); + +CLAY__TYPEDEF(Clay_Arena, struct { + uintptr_t nextAllocation; + size_t capacity; + char *memory; +}); + +CLAY__TYPEDEF(Clay_Dimensions, struct { + float width, height; +}); + +CLAY__TYPEDEF(Clay_Vector2, struct { + float x, y; +}); + +CLAY__TYPEDEF(Clay_Color, struct { + float r, g, b, a; +}); + +CLAY__TYPEDEF(Clay_BoundingBox, struct { + float x, y, width, height; +}); + +// baseId + offset = id +CLAY__TYPEDEF(Clay_ElementId, struct { + uint32_t id; + uint32_t offset; + uint32_t baseId; + Clay_String stringId; +}); + +CLAY__TYPEDEF(Clay_CornerRadius, struct { + float topLeft; + float topRight; + float bottomLeft; + float bottomRight; +}); + +CLAY__TYPEDEF(Clay__ElementConfigType, CLAY_PACKED_ENUM { + CLAY__ELEMENT_CONFIG_TYPE_NONE = 0, + CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE = 1, + CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER = 2, + CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER = 4, + CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER = 8, + CLAY__ELEMENT_CONFIG_TYPE_IMAGE = 16, + CLAY__ELEMENT_CONFIG_TYPE_TEXT = 32, + CLAY__ELEMENT_CONFIG_TYPE_CUSTOM = 64, +}); + +// Element Configs --------------------------- +// Layout +CLAY__TYPEDEF(Clay_LayoutDirection, CLAY_PACKED_ENUM { + CLAY_LEFT_TO_RIGHT, + CLAY_TOP_TO_BOTTOM, +}); + +CLAY__TYPEDEF(Clay_LayoutAlignmentX, CLAY_PACKED_ENUM { + CLAY_ALIGN_X_LEFT, + CLAY_ALIGN_X_RIGHT, + CLAY_ALIGN_X_CENTER, +}); + +CLAY__TYPEDEF(Clay_LayoutAlignmentY, CLAY_PACKED_ENUM { + CLAY_ALIGN_Y_TOP, + CLAY_ALIGN_Y_BOTTOM, + CLAY_ALIGN_Y_CENTER, +}); + +CLAY__TYPEDEF(Clay__SizingType, CLAY_PACKED_ENUM { + CLAY__SIZING_TYPE_FIT, + CLAY__SIZING_TYPE_GROW, + CLAY__SIZING_TYPE_PERCENT, + CLAY__SIZING_TYPE_FIXED, +}); + +CLAY__TYPEDEF(Clay_ChildAlignment, struct { + Clay_LayoutAlignmentX x; + Clay_LayoutAlignmentY y; +}); + +CLAY__TYPEDEF(Clay_SizingMinMax, struct { + float min; + float max; +}); + +CLAY__TYPEDEF(Clay_SizingAxis, struct { + union { + Clay_SizingMinMax minMax; + float percent; + } size; + Clay__SizingType type; +}); + +CLAY__TYPEDEF(Clay_Sizing, struct { + Clay_SizingAxis width; + Clay_SizingAxis height; +}); + +CLAY__TYPEDEF(Clay_Padding, struct { + uint16_t x; + uint16_t y; +}); + +CLAY__TYPEDEF(Clay_LayoutConfig, struct { + Clay_Sizing sizing; + Clay_Padding padding; + uint16_t childGap; + Clay_ChildAlignment childAlignment; + Clay_LayoutDirection layoutDirection; +}); + +extern Clay_LayoutConfig CLAY_LAYOUT_DEFAULT; + +// Rectangle +// NOTE: Not declared in the typedef as an ifdef inside macro arguments is UB +struct Clay_RectangleElementConfig { + Clay_Color color; + Clay_CornerRadius cornerRadius; + #ifdef CLAY_EXTEND_CONFIG_RECTANGLE + CLAY_EXTEND_CONFIG_RECTANGLE + #endif +}; +CLAY__TYPEDEF(Clay_RectangleElementConfig, struct Clay_RectangleElementConfig); + +// Text +CLAY__TYPEDEF(Clay_TextElementConfigWrapMode, enum { + CLAY_TEXT_WRAP_WORDS, + CLAY_TEXT_WRAP_NEWLINES, + CLAY_TEXT_WRAP_NONE, +}); + +struct Clay_TextElementConfig { + Clay_Color textColor; + uint16_t fontId; + uint16_t fontSize; + uint16_t letterSpacing; + uint16_t lineHeight; + Clay_TextElementConfigWrapMode wrapMode; + #ifdef CLAY_EXTEND_CONFIG_TEXT + CLAY_EXTEND_CONFIG_TEXT + #endif +}; +CLAY__TYPEDEF(Clay_TextElementConfig, struct Clay_TextElementConfig); + +// Image +struct Clay_ImageElementConfig { + void *imageData; + Clay_Dimensions sourceDimensions; + #ifdef CLAY_EXTEND_CONFIG_IMAGE + CLAY_EXTEND_CONFIG_IMAGE + #endif +}; +CLAY__TYPEDEF(Clay_ImageElementConfig, struct Clay_ImageElementConfig); + +// Floating +CLAY__TYPEDEF(Clay_FloatingAttachPointType, CLAY_PACKED_ENUM { + CLAY_ATTACH_POINT_LEFT_TOP, + CLAY_ATTACH_POINT_LEFT_CENTER, + CLAY_ATTACH_POINT_LEFT_BOTTOM, + CLAY_ATTACH_POINT_CENTER_TOP, + CLAY_ATTACH_POINT_CENTER_CENTER, + CLAY_ATTACH_POINT_CENTER_BOTTOM, + CLAY_ATTACH_POINT_RIGHT_TOP, + CLAY_ATTACH_POINT_RIGHT_CENTER, + CLAY_ATTACH_POINT_RIGHT_BOTTOM, +}); + +CLAY__TYPEDEF(Clay_FloatingAttachPoints, struct { + Clay_FloatingAttachPointType element; + Clay_FloatingAttachPointType parent; +}); + +CLAY__TYPEDEF(Clay_PointerCaptureMode, enum { + CLAY_POINTER_CAPTURE_MODE_CAPTURE, +// CLAY_POINTER_CAPTURE_MODE_PARENT, TODO pass pointer through to attached parent + CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH, +}); + +CLAY__TYPEDEF(Clay_FloatingElementConfig, struct { + Clay_Vector2 offset; + Clay_Dimensions expand; + uint16_t zIndex; + uint32_t parentId; + Clay_FloatingAttachPoints attachment; + Clay_PointerCaptureMode pointerCaptureMode; +}); + +// Custom +struct Clay_CustomElementConfig { + #ifndef CLAY_EXTEND_CONFIG_CUSTOM + void *customData; + #else + CLAY_EXTEND_CONFIG_CUSTOM + #endif +}; +CLAY__TYPEDEF(Clay_CustomElementConfig, struct Clay_CustomElementConfig); + +// Scroll +CLAY__TYPEDEF(Clay_ScrollElementConfig, struct { + bool horizontal; + bool vertical; +}); + +// Border +CLAY__TYPEDEF(Clay_Border, struct { + uint32_t width; + Clay_Color color; +}); + +CLAY__TYPEDEF(Clay_BorderElementConfig, struct { + Clay_Border left; + Clay_Border right; + Clay_Border top; + Clay_Border bottom; + Clay_Border betweenChildren; + Clay_CornerRadius cornerRadius; +}); + +CLAY__TYPEDEF(Clay_ElementConfigUnion, union { + Clay_RectangleElementConfig *rectangleElementConfig; + Clay_TextElementConfig *textElementConfig; + Clay_ImageElementConfig *imageElementConfig; + Clay_FloatingElementConfig *floatingElementConfig; + Clay_CustomElementConfig *customElementConfig; + Clay_ScrollElementConfig *scrollElementConfig; + Clay_BorderElementConfig *borderElementConfig; +}); + +CLAY__TYPEDEF(Clay_ElementConfig, struct { + Clay__ElementConfigType type; + Clay_ElementConfigUnion config; +}); + +// Miscellaneous Structs & Enums --------------------------------- +CLAY__TYPEDEF(Clay_ScrollContainerData, struct { + // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. + // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. + Clay_Vector2 *scrollPosition; + Clay_Dimensions scrollContainerDimensions; + Clay_Dimensions contentDimensions; + Clay_ScrollElementConfig config; + // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. + bool found; +}); + +CLAY__TYPEDEF(Clay_RenderCommandType, enum { + CLAY_RENDER_COMMAND_TYPE_NONE, + CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + CLAY_RENDER_COMMAND_TYPE_BORDER, + CLAY_RENDER_COMMAND_TYPE_TEXT, + CLAY_RENDER_COMMAND_TYPE_IMAGE, + CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + CLAY_RENDER_COMMAND_TYPE_CUSTOM, +}); + +CLAY__TYPEDEF(Clay_RenderCommand, struct { + Clay_BoundingBox boundingBox; + Clay_ElementConfigUnion config; + Clay_String text; // TODO I wish there was a way to avoid having to have this on every render command + uint32_t id; + Clay_RenderCommandType commandType; +}); + +CLAY__TYPEDEF(Clay_RenderCommandArray, struct { + uint32_t capacity; + uint32_t length; + Clay_RenderCommand *internalArray; +}); + +CLAY__TYPEDEF(Clay_PointerDataInteractionState, enum { + CLAY_POINTER_DATA_PRESSED_THIS_FRAME, + CLAY_POINTER_DATA_PRESSED, + CLAY_POINTER_DATA_RELEASED_THIS_FRAME, + CLAY_POINTER_DATA_RELEASED, +}); + +CLAY__TYPEDEF(Clay_PointerData, struct { + Clay_Vector2 position; + Clay_PointerDataInteractionState state; +}); + +CLAY__TYPEDEF(Clay_ErrorType, enum { + CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, + CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, + CLAY_ERROR_TYPE_DUPLICATE_ID, + CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, + CLAY_ERROR_TYPE_INTERNAL_ERROR, +}); + +CLAY__TYPEDEF(Clay_ErrorData, struct { + Clay_ErrorType errorType; + Clay_String errorText; + uintptr_t userData; +}); + +CLAY__TYPEDEF(Clay_ErrorHandler, struct { + void (*errorHandlerFunction)(Clay_ErrorData errorText); + uintptr_t userData; +}); + +// Function Forward Declarations --------------------------------- +// Public API functions --- +uint32_t Clay_MinMemorySize(void); +Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset); +void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown); +void Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler); +void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime); +void Clay_SetLayoutDimensions(Clay_Dimensions dimensions); +void Clay_BeginLayout(void); +Clay_RenderCommandArray Clay_EndLayout(void); +Clay_ElementId Clay_GetElementId(Clay_String idString); +Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index); +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)); +Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index); +void Clay_SetDebugModeEnabled(bool enabled); +bool Clay_IsDebugModeEnabled(void); +void Clay_SetCullingEnabled(bool enabled); +void Clay_SetMaxElementCount(uint32_t maxElementCount); +void Clay_SetMaxMeasureTextCacheWordCount(uint32_t maxMeasureTextCacheWordCount); + +// Internal API functions required by macros +void Clay__OpenElement(void); +void Clay__CloseElement(void); +Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config); +void Clay__ElementPostConfiguration(void); +void Clay__AttachId(Clay_ElementId id); +void Clay__AttachLayoutConfig(Clay_LayoutConfig *config); +void Clay__AttachElementConfig(Clay_ElementConfigUnion config, Clay__ElementConfigType type); +Clay_RectangleElementConfig * Clay__StoreRectangleElementConfig(Clay_RectangleElementConfig config); +Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config); +Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config); +Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config); +Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config); +Clay_ScrollElementConfig * Clay__StoreScrollElementConfig(Clay_ScrollElementConfig config); +Clay_BorderElementConfig * Clay__StoreBorderElementConfig(Clay_BorderElementConfig config); +Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed); +void Clay__Noop(void); +void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig); + +extern Clay_Color Clay__debugViewHighlightColor; +extern uint32_t Clay__debugViewWidth; + +#ifdef __cplusplus +} +#endif + +#endif // CLAY_HEADER + +// ----------------------------------------- +// IMPLEMENTATION -------------------------- +// ----------------------------------------- +#ifdef CLAY_IMPLEMENTATION +#undef CLAY_IMPLEMENTATION + +#ifndef CLAY__NULL +#define CLAY__NULL 0 +#endif + +#ifndef CLAY__MAXFLOAT +#define CLAY__MAXFLOAT 3.40282346638528859812e+38F +#endif + +bool Clay__warningsEnabled = true; +uint32_t Clay__maxElementCount = 8192; +uint32_t Clay__maxMeasureTextCacheWordCount = 16384; +void Clay__ErrorHandlerFunctionDefault(Clay_ErrorData errorText) { + (void) errorText; +} +Clay_ErrorHandler Clay__errorHandler = { .errorHandlerFunction = Clay__ErrorHandlerFunctionDefault }; + +void Clay__Noop(void) {} + +Clay_String CLAY__SPACECHAR = { .length = 1, .chars = " " }; +Clay_String CLAY__STRING_DEFAULT = { .length = 0, .chars = NULL }; + +CLAY__TYPEDEF(Clay_BooleanWarnings, struct { + bool maxElementsExceeded; + bool maxRenderCommandsExceeded; + bool maxTextMeasureCacheExceeded; +}); + +Clay_BooleanWarnings Clay__booleanWarnings; + +CLAY__TYPEDEF(Clay__Warning, struct { + Clay_String baseMessage; + Clay_String dynamicMessage; +}); + +Clay__Warning CLAY__WARNING_DEFAULT = {0}; + +#pragma region generated +CLAY__TYPEDEF(Clay__WarningArray, struct { + uint32_t capacity; + uint32_t length; + Clay__Warning *internalArray; +}); + +Clay__WarningArray Clay__WarningArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + size_t totalSizeBytes = capacity * sizeof(Clay_String); + Clay__WarningArray array = {.capacity = capacity, .length = 0}; + uintptr_t nextAllocAddress = arena->nextAllocation + (uintptr_t)arena->memory; + uintptr_t arenaOffsetAligned = nextAllocAddress + (CLAY__ALIGNMENT(Clay_String) - (nextAllocAddress % CLAY__ALIGNMENT(Clay_String))); + arenaOffsetAligned -= (uintptr_t)arena->memory; + if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) { + array.internalArray = (Clay__Warning*)((uintptr_t)arena->memory + (uintptr_t)arenaOffsetAligned); + arena->nextAllocation = arenaOffsetAligned + totalSizeBytes; + } + else { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), + .userData = Clay__errorHandler.userData }); + } + return array; +} + +Clay__WarningArray Clay_warnings = {0}; + +Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning item) +{ + if (array->length < array->capacity) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__WARNING_DEFAULT; +} + +void* Clay__Array_Allocate_Arena(uint32_t capacity, uint32_t itemSize, uint32_t alignment, Clay_Arena *arena) +{ + size_t totalSizeBytes = capacity * itemSize; + uintptr_t nextAllocAddress = arena->nextAllocation + (uintptr_t)arena->memory; + uintptr_t arenaOffsetAligned = nextAllocAddress + (alignment - (nextAllocAddress % alignment)); + arenaOffsetAligned -= (uintptr_t)arena->memory; + if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) { + arena->nextAllocation = arenaOffsetAligned + totalSizeBytes; + return (void*)((uintptr_t)arena->memory + (uintptr_t)arenaOffsetAligned); + } + else { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay attempted to allocate memory in its arena, but ran out of capacity. Try increasing the capacity of the arena passed to Clay_Initialize()"), + .userData = Clay__errorHandler.userData }); + } + return CLAY__NULL; +} + +bool Clay__Array_RangeCheck(int index, uint32_t length) +{ + if (index < length && index >= 0) { + return true; + } + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, + .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), + .userData = Clay__errorHandler.userData }); + return false; +} + +bool Clay__Array_AddCapacityCheck(uint32_t length, uint32_t capacity) +{ + if (length < capacity) { + return true; + } + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_INTERNAL_ERROR, + .errorText = CLAY_STRING("Clay attempted to make an out of bounds array access. This is an internal error and is likely a bug."), + .userData = Clay__errorHandler.userData }); + return false; +} + +bool CLAY__BOOL_DEFAULT = false; + +// __GENERATED__ template array_define,array_allocate TYPE=bool NAME=Clay__BoolArray +#pragma region generated +CLAY__TYPEDEF(Clay__BoolArray, struct +{ + uint32_t capacity; + uint32_t length; + bool *internalArray; +}); +Clay__BoolArray Clay__BoolArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__BoolArray){.capacity = capacity, .length = 0, .internalArray = (bool *)Clay__Array_Allocate_Arena(capacity, sizeof(bool), CLAY__ALIGNMENT(bool), arena)}; +} +#pragma endregion +// __GENERATED__ template + +Clay_ElementId CLAY__ELEMENT_ID_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_get,array_add TYPE=Clay_ElementId NAME=Clay__ElementIdArray DEFAULT_VALUE=&CLAY__ELEMENT_ID_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__ElementIdArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_ElementId *internalArray; +}); +Clay__ElementIdArray Clay__ElementIdArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__ElementIdArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ElementId *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ElementId), CLAY__ALIGNMENT(Clay_ElementId), arena)}; +} +Clay_ElementId *Clay__ElementIdArray_Get(Clay__ElementIdArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__ELEMENT_ID_DEFAULT; +} +Clay_ElementId *Clay__ElementIdArray_Add(Clay__ElementIdArray *array, Clay_ElementId item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__ELEMENT_ID_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_ElementConfig CLAY__ELEMENT_CONFIG_DEFAULT = {CLAY__ELEMENT_CONFIG_TYPE_NONE, {0}}; + +// __GENERATED__ template array_define,array_define_slice,array_allocate,array_get,array_add,array_get_slice TYPE=Clay_ElementConfig NAME=Clay__ElementConfigArray DEFAULT_VALUE=&CLAY__ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__ElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_ElementConfig *internalArray; +}); +CLAY__TYPEDEF(Clay__ElementConfigArraySlice, struct +{ + uint32_t length; + Clay_ElementConfig *internalArray; +}); +Clay__ElementConfigArray Clay__ElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__ElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ElementConfig), CLAY__ALIGNMENT(Clay_ElementConfig), arena)}; +} +Clay_ElementConfig *Clay__ElementConfigArray_Get(Clay__ElementConfigArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__ELEMENT_CONFIG_DEFAULT; +} +Clay_ElementConfig *Clay__ElementConfigArray_Add(Clay__ElementConfigArray *array, Clay_ElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__ELEMENT_CONFIG_DEFAULT; +} +Clay_ElementConfig *Clay__ElementConfigArraySlice_Get(Clay__ElementConfigArraySlice *slice, int index) { + return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : &CLAY__ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = { .sizing = { .width = { .size = { .minMax = {0, CLAY__MAXFLOAT } }, .type = CLAY__SIZING_TYPE_FIT }, .height = { .size = { .minMax = {0, CLAY__MAXFLOAT } }, .type = CLAY__SIZING_TYPE_FIT } } }; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_LayoutConfig NAME=Clay__LayoutConfigArray DEFAULT_VALUE=&CLAY_LAYOUT_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__LayoutConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutConfig *internalArray; +}); +Clay__LayoutConfigArray Clay__LayoutConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__LayoutConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutConfig), CLAY__ALIGNMENT(Clay_LayoutConfig), arena)}; +} +Clay_LayoutConfig *Clay__LayoutConfigArray_Add(Clay__LayoutConfigArray *array, Clay_LayoutConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY_LAYOUT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_RectangleElementConfig CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_RectangleElementConfig NAME=Clay__RectangleElementConfigArray DEFAULT_VALUE=&CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__RectangleElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_RectangleElementConfig *internalArray; +}); +Clay__RectangleElementConfigArray Clay__RectangleElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__RectangleElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RectangleElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RectangleElementConfig), CLAY__ALIGNMENT(Clay_RectangleElementConfig), arena)}; +} +Clay_RectangleElementConfig *Clay__RectangleElementConfigArray_Add(Clay__RectangleElementConfigArray *array, Clay_RectangleElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_TextElementConfig CLAY__TEXT_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_TextElementConfig NAME=Clay__TextElementConfigArray DEFAULT_VALUE=&CLAY__TEXT_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__TextElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_TextElementConfig *internalArray; +}); +Clay__TextElementConfigArray Clay__TextElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__TextElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_TextElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_TextElementConfig), CLAY__ALIGNMENT(Clay_TextElementConfig), arena)}; +} +Clay_TextElementConfig *Clay__TextElementConfigArray_Add(Clay__TextElementConfigArray *array, Clay_TextElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__TEXT_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_ImageElementConfig CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_ImageElementConfig NAME=Clay__ImageElementConfigArray DEFAULT_VALUE=&CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__ImageElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_ImageElementConfig *internalArray; +}); +Clay__ImageElementConfigArray Clay__ImageElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__ImageElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ImageElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ImageElementConfig), CLAY__ALIGNMENT(Clay_ImageElementConfig), arena)}; +} +Clay_ImageElementConfig *Clay__ImageElementConfigArray_Add(Clay__ImageElementConfigArray *array, Clay_ImageElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_FloatingElementConfig CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_FloatingElementConfig NAME=Clay__FloatingElementConfigArray DEFAULT_VALUE=&CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__FloatingElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_FloatingElementConfig *internalArray; +}); +Clay__FloatingElementConfigArray Clay__FloatingElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__FloatingElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_FloatingElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_FloatingElementConfig), CLAY__ALIGNMENT(Clay_FloatingElementConfig), arena)}; +} +Clay_FloatingElementConfig *Clay__FloatingElementConfigArray_Add(Clay__FloatingElementConfigArray *array, Clay_FloatingElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_CustomElementConfig CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_CustomElementConfig NAME=Clay__CustomElementConfigArray DEFAULT_VALUE=&CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__CustomElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_CustomElementConfig *internalArray; +}); +Clay__CustomElementConfigArray Clay__CustomElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__CustomElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_CustomElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_CustomElementConfig), CLAY__ALIGNMENT(Clay_CustomElementConfig), arena)}; +} +Clay_CustomElementConfig *Clay__CustomElementConfigArray_Add(Clay__CustomElementConfigArray *array, Clay_CustomElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_ScrollElementConfig CLAY__SCROLL_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_ScrollElementConfig NAME=Clay__ScrollElementConfigArray DEFAULT_VALUE=&CLAY__SCROLL_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__ScrollElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_ScrollElementConfig *internalArray; +}); +Clay__ScrollElementConfigArray Clay__ScrollElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__ScrollElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ScrollElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ScrollElementConfig), CLAY__ALIGNMENT(Clay_ScrollElementConfig), arena)}; +} +Clay_ScrollElementConfig *Clay__ScrollElementConfigArray_Add(Clay__ScrollElementConfigArray *array, Clay_ScrollElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__SCROLL_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_define_slice,array_allocate,array_add TYPE=Clay_String NAME=Clay__StringArray DEFAULT_VALUE=&CLAY__STRING_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__StringArraySlice, struct +{ + uint32_t length; + Clay_String *internalArray; +}); +Clay__StringArray Clay__StringArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__StringArray){.capacity = capacity, .length = 0, .internalArray = (Clay_String *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_String), CLAY__ALIGNMENT(Clay_String), arena)}; +} +Clay_String *Clay__StringArray_Add(Clay__StringArray *array, Clay_String item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__STRING_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__WrappedTextLine, struct { + Clay_Dimensions dimensions; + Clay_String line; +}); + +Clay__WrappedTextLine CLAY__WRAPPED_TEXT_LINE_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_define_slice,array_allocate,array_add,array_get TYPE=Clay__WrappedTextLine NAME=Clay__WrappedTextLineArray DEFAULT_VALUE=&CLAY__WRAPPED_TEXT_LINE_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__WrappedTextLineArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__WrappedTextLine *internalArray; +}); +CLAY__TYPEDEF(Clay__WrappedTextLineArraySlice, struct +{ + uint32_t length; + Clay__WrappedTextLine *internalArray; +}); +Clay__WrappedTextLineArray Clay__WrappedTextLineArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__WrappedTextLineArray){.capacity = capacity, .length = 0, .internalArray = (Clay__WrappedTextLine *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__WrappedTextLine), CLAY__ALIGNMENT(Clay__WrappedTextLine), arena)}; +} +Clay__WrappedTextLine *Clay__WrappedTextLineArray_Add(Clay__WrappedTextLineArray *array, Clay__WrappedTextLine item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__WRAPPED_TEXT_LINE_DEFAULT; +} +Clay__WrappedTextLine *Clay__WrappedTextLineArray_Get(Clay__WrappedTextLineArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__WRAPPED_TEXT_LINE_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__TextElementData, struct { + Clay_String text; + Clay_Dimensions preferredDimensions; + uint32_t elementIndex; + Clay__WrappedTextLineArraySlice wrappedLines; +}); + +Clay__TextElementData CLAY__TEXT_ELEMENT_DATA_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_get,array_add TYPE=Clay__TextElementData NAME=Clay__TextElementDataArray DEFAULT_VALUE=&CLAY__TEXT_ELEMENT_DATA_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__TextElementDataArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__TextElementData *internalArray; +}); +Clay__TextElementDataArray Clay__TextElementDataArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__TextElementDataArray){.capacity = capacity, .length = 0, .internalArray = (Clay__TextElementData *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__TextElementData), CLAY__ALIGNMENT(Clay__TextElementData), arena)}; +} +Clay__TextElementData *Clay__TextElementDataArray_Get(Clay__TextElementDataArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__TEXT_ELEMENT_DATA_DEFAULT; +} +Clay__TextElementData *Clay__TextElementDataArray_Add(Clay__TextElementDataArray *array, Clay__TextElementData item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__TEXT_ELEMENT_DATA_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +Clay_BorderElementConfig CLAY__BORDER_ELEMENT_CONFIG_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add TYPE=Clay_BorderElementConfig NAME=Clay__BorderElementConfigArray DEFAULT_VALUE=&CLAY__BORDER_ELEMENT_CONFIG_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__BorderElementConfigArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_BorderElementConfig *internalArray; +}); +Clay__BorderElementConfigArray Clay__BorderElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__BorderElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_BorderElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_BorderElementConfig), CLAY__ALIGNMENT(Clay_BorderElementConfig), arena)}; +} +Clay_BorderElementConfig *Clay__BorderElementConfigArray_Add(Clay__BorderElementConfigArray *array, Clay_BorderElementConfig item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__BORDER_ELEMENT_CONFIG_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__LayoutElementChildren, struct { + int32_t *elements; + uint16_t length; +}); + +CLAY__TYPEDEF(Clay_LayoutElement, struct { + union { + Clay__LayoutElementChildren children; + Clay__TextElementData *textElementData; + } childrenOrTextContent; + Clay_Dimensions dimensions; + Clay_Dimensions minDimensions; + Clay_LayoutConfig *layoutConfig; + Clay__ElementConfigArraySlice elementConfigs; + uint32_t configsEnabled; + uint32_t id; +}); + +Clay_LayoutElement CLAY__LAYOUT_ELEMENT_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add,array_get TYPE=Clay_LayoutElement NAME=Clay_LayoutElementArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay_LayoutElementArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElement *internalArray; +}); +Clay_LayoutElementArray Clay_LayoutElementArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay_LayoutElementArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement), CLAY__ALIGNMENT(Clay_LayoutElement), arena)}; +} +Clay_LayoutElement *Clay_LayoutElementArray_Add(Clay_LayoutElementArray *array, Clay_LayoutElement item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_DEFAULT; +} +Clay_LayoutElement *Clay_LayoutElementArray_Get(Clay_LayoutElementArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_define,array_allocate_pointer,array_add,array_get_value,array_remove_swapback TYPE=Clay_LayoutElement* NAME=Clay__LayoutElementPointerArray DEFAULT_VALUE=CLAY__NULL +#pragma region generated +CLAY__TYPEDEF(Clay__LayoutElementPointerArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElement* *internalArray; +}); +Clay__LayoutElementPointerArray Clay__LayoutElementPointerArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__LayoutElementPointerArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement* *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement*), CLAY__POINTER_ALIGNMENT, arena)}; +} +Clay_LayoutElement* *Clay__LayoutElementPointerArray_Add(Clay__LayoutElementPointerArray *array, Clay_LayoutElement* item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return CLAY__NULL; +} +Clay_LayoutElement* Clay__LayoutElementPointerArray_Get(Clay__LayoutElementPointerArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : CLAY__NULL; +} +Clay_LayoutElement* Clay__LayoutElementPointerArray_RemoveSwapback(Clay__LayoutElementPointerArray *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + Clay_LayoutElement* removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return CLAY__NULL; +} +#pragma endregion +// __GENERATED__ template + +Clay_RenderCommand CLAY__RENDER_COMMAND_DEFAULT = {0}; + +// __GENERATED__ template array_allocate,array_add,array_get TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray DEFAULT_VALUE=&CLAY__RENDER_COMMAND_DEFAULT +#pragma region generated +Clay_RenderCommandArray Clay_RenderCommandArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay_RenderCommandArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RenderCommand *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RenderCommand), CLAY__ALIGNMENT(Clay_RenderCommand), arena)}; +} +Clay_RenderCommand *Clay_RenderCommandArray_Add(Clay_RenderCommandArray *array, Clay_RenderCommand item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__RENDER_COMMAND_DEFAULT; +} +Clay_RenderCommand *Clay_RenderCommandArray_Get(Clay_RenderCommandArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__RENDER_COMMAND_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__ScrollContainerDataInternal, struct { + Clay_LayoutElement *layoutElement; + Clay_BoundingBox boundingBox; + Clay_Dimensions contentSize; + Clay_Vector2 scrollOrigin; + Clay_Vector2 pointerOrigin; + Clay_Vector2 scrollMomentum; + Clay_Vector2 scrollPosition; + Clay_Vector2 previousDelta; + float momentumTime; + uint32_t elementId; + bool openThisFrame; + bool pointerScrollActive; +}); + +Clay__ScrollContainerDataInternal CLAY__SCROLL_CONTAINER_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add,array_get TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__ScrollContainerDataInternalArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__ScrollContainerDataInternal *internalArray; +}); +Clay__ScrollContainerDataInternalArray Clay__ScrollContainerDataInternalArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__ScrollContainerDataInternalArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerDataInternal *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerDataInternal), CLAY__ALIGNMENT(Clay__ScrollContainerDataInternal), arena)}; +} +Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Add(Clay__ScrollContainerDataInternalArray *array, Clay__ScrollContainerDataInternal item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__SCROLL_CONTAINER_DEFAULT; +} +Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Get(Clay__ScrollContainerDataInternalArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__SCROLL_CONTAINER_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_remove_swapback TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT +#pragma region generated +Clay__ScrollContainerDataInternal Clay__ScrollContainerDataInternalArray_RemoveSwapback(Clay__ScrollContainerDataInternalArray *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + Clay__ScrollContainerDataInternal removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return CLAY__SCROLL_CONTAINER_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__DebugElementData, struct { + bool collision; + bool collapsed; +}); + +Clay__DebugElementData CLAY__DEBUG_ELEMENT_DATA_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add,array_get TYPE=Clay__DebugElementData NAME=Clay__DebugElementDataArray DEFAULT_VALUE=&CLAY__DEBUG_ELEMENT_DATA_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__DebugElementDataArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__DebugElementData *internalArray; +}); +Clay__DebugElementDataArray Clay__DebugElementDataArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__DebugElementDataArray){.capacity = capacity, .length = 0, .internalArray = (Clay__DebugElementData *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__DebugElementData), CLAY__ALIGNMENT(Clay__DebugElementData), arena)}; +} +Clay__DebugElementData *Clay__DebugElementDataArray_Add(Clay__DebugElementDataArray *array, Clay__DebugElementData item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__DEBUG_ELEMENT_DATA_DEFAULT; +} +Clay__DebugElementData *Clay__DebugElementDataArray_Get(Clay__DebugElementDataArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__DEBUG_ELEMENT_DATA_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay_LayoutElementHashMapItem, struct { // todo get this struct into a single cache line + Clay_BoundingBox boundingBox; + Clay_ElementId elementId; + Clay_LayoutElement* layoutElement; + void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData); + intptr_t hoverFunctionUserData; + int32_t nextIndex; + uint32_t generation; + Clay__DebugElementData *debugData; +}); + +Clay_LayoutElementHashMapItem CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT = { .layoutElement = &CLAY__LAYOUT_ELEMENT_DEFAULT }; + +// __GENERATED__ template array_define,array_allocate,array_get,array_add TYPE=Clay_LayoutElementHashMapItem NAME=Clay__LayoutElementHashMapItemArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__LayoutElementHashMapItemArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay_LayoutElementHashMapItem *internalArray; +}); +Clay__LayoutElementHashMapItemArray Clay__LayoutElementHashMapItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__LayoutElementHashMapItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElementHashMapItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElementHashMapItem), CLAY__ALIGNMENT(Clay_LayoutElementHashMapItem), arena)}; +} +Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Get(Clay__LayoutElementHashMapItemArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT; +} +Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Add(Clay__LayoutElementHashMapItemArray *array, Clay_LayoutElementHashMapItem item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__MeasuredWord, struct { + uint32_t startOffset; + uint32_t length; + float width; + int32_t next; +}); + +Clay__MeasuredWord CLAY__MEASURED_WORD_DEFAULT = { .next = -1 }; + +// __GENERATED__ template array_define,array_allocate,array_get,array_set,array_add TYPE=Clay__MeasuredWord NAME=Clay__MeasuredWordArray DEFAULT_VALUE=&CLAY__MEASURED_WORD_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__MeasuredWordArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__MeasuredWord *internalArray; +}); +Clay__MeasuredWordArray Clay__MeasuredWordArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__MeasuredWordArray){.capacity = capacity, .length = 0, .internalArray = (Clay__MeasuredWord *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__MeasuredWord), CLAY__ALIGNMENT(Clay__MeasuredWord), arena)}; +} +Clay__MeasuredWord *Clay__MeasuredWordArray_Get(Clay__MeasuredWordArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__MEASURED_WORD_DEFAULT; +} +void Clay__MeasuredWordArray_Set(Clay__MeasuredWordArray *array, int index, Clay__MeasuredWord value) { + if (Clay__Array_RangeCheck(index, array->capacity)) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } +} +Clay__MeasuredWord *Clay__MeasuredWordArray_Add(Clay__MeasuredWordArray *array, Clay__MeasuredWord item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__MEASURED_WORD_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__MeasureTextCacheItem, struct { + Clay_Dimensions unwrappedDimensions; + int32_t measuredWordsStartIndex; + // Hash map data + uint32_t id; + int32_t nextIndex; + uint32_t generation; +}); + +Clay__MeasureTextCacheItem CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT = { .measuredWordsStartIndex = -1 }; + +// __GENERATED__ template array_define,array_allocate,array_get,array_add,array_set TYPE=Clay__MeasureTextCacheItem NAME=Clay__MeasureTextCacheItemArray DEFAULT_VALUE=&CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__MeasureTextCacheItemArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__MeasureTextCacheItem *internalArray; +}); +Clay__MeasureTextCacheItemArray Clay__MeasureTextCacheItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__MeasureTextCacheItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay__MeasureTextCacheItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__MeasureTextCacheItem), CLAY__ALIGNMENT(Clay__MeasureTextCacheItem), arena)}; +} +Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Get(Clay__MeasureTextCacheItemArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; +} +Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Add(Clay__MeasureTextCacheItemArray *array, Clay__MeasureTextCacheItem item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; +} +void Clay__MeasureTextCacheItemArray_Set(Clay__MeasureTextCacheItemArray *array, int index, Clay__MeasureTextCacheItem value) { + if (Clay__Array_RangeCheck(index, array->capacity)) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_define,array_allocate,array_get_value,array_add_value,array_set,array_remove_swapback TYPE=int32_t NAME=Clay__int32_tArray DEFAULT_VALUE=-1 +#pragma region generated +CLAY__TYPEDEF(Clay__int32_tArray, struct +{ + uint32_t capacity; + uint32_t length; + int32_t *internalArray; +}); +Clay__int32_tArray Clay__int32_tArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__int32_tArray){.capacity = capacity, .length = 0, .internalArray = (int32_t *)Clay__Array_Allocate_Arena(capacity, sizeof(int32_t), CLAY__ALIGNMENT(int32_t), arena)}; +} +int32_t Clay__int32_tArray_Get(Clay__int32_tArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : -1; +} +void Clay__int32_tArray_Add(Clay__int32_tArray *array, int32_t item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + } +} +void Clay__int32_tArray_Set(Clay__int32_tArray *array, int index, int32_t value) { + if (Clay__Array_RangeCheck(index, array->capacity)) { + array->internalArray[index] = value; + array->length = index < array->length ? array->length : index + 1; + } +} +int32_t Clay__int32_tArray_RemoveSwapback(Clay__int32_tArray *array, int index) { + if (Clay__Array_RangeCheck(index, array->length)) { + array->length--; + int32_t removed = array->internalArray[index]; + array->internalArray[index] = array->internalArray[array->length]; + return removed; + } + return -1; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__LayoutElementTreeNode, struct { + Clay_LayoutElement *layoutElement; + Clay_Vector2 position; + Clay_Vector2 nextChildOffset; +}); + +Clay__LayoutElementTreeNode CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add,array_get TYPE=Clay__LayoutElementTreeNode NAME=Clay__LayoutElementTreeNodeArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__LayoutElementTreeNodeArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__LayoutElementTreeNode *internalArray; +}); +Clay__LayoutElementTreeNodeArray Clay__LayoutElementTreeNodeArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__LayoutElementTreeNodeArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeNode *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeNode), CLAY__ALIGNMENT(Clay__LayoutElementTreeNode), arena)}; +} +Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Add(Clay__LayoutElementTreeNodeArray *array, Clay__LayoutElementTreeNode item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT; +} +Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Get(Clay__LayoutElementTreeNodeArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +CLAY__TYPEDEF(Clay__LayoutElementTreeRoot, struct { + uint32_t layoutElementIndex; + uint32_t parentId; // This can be zero in the case of the root layout tree + uint32_t clipElementId; // This can be zero if there is no clip element + uint32_t zIndex; + Clay_Vector2 pointerOffset; // Only used when scroll containers are managed externally +}); + +Clay__LayoutElementTreeRoot CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT = {0}; + +// __GENERATED__ template array_define,array_allocate,array_add,array_get TYPE=Clay__LayoutElementTreeRoot NAME=Clay__LayoutElementTreeRootArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT +#pragma region generated +CLAY__TYPEDEF(Clay__LayoutElementTreeRootArray, struct +{ + uint32_t capacity; + uint32_t length; + Clay__LayoutElementTreeRoot *internalArray; +}); +Clay__LayoutElementTreeRootArray Clay__LayoutElementTreeRootArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__LayoutElementTreeRootArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeRoot *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeRoot), CLAY__ALIGNMENT(Clay__LayoutElementTreeRoot), arena)}; +} +Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Add(Clay__LayoutElementTreeRootArray *array, Clay__LayoutElementTreeRoot item) { + if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) { + array->internalArray[array->length++] = item; + return &array->internalArray[array->length - 1]; + } + return &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT; +} +Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Get(Clay__LayoutElementTreeRootArray *array, int index) { + return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT; +} +#pragma endregion +// __GENERATED__ template + +// __GENERATED__ template array_define,array_allocate TYPE=uint8_t NAME=Clay__CharArray DEFAULT_VALUE=0 +#pragma region generated +CLAY__TYPEDEF(Clay__CharArray, struct +{ + uint32_t capacity; + uint32_t length; + uint8_t *internalArray; +}); +Clay__CharArray Clay__CharArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { + return CLAY__INIT(Clay__CharArray){.capacity = capacity, .length = 0, .internalArray = (uint8_t *)Clay__Array_Allocate_Arena(capacity, sizeof(uint8_t), CLAY__ALIGNMENT(uint8_t), arena)}; +} +#pragma endregion +// __GENERATED__ template + +Clay_String Clay__WriteStringToCharBuffer(Clay__CharArray *buffer, Clay_String string) { + for (size_t i = 0; i < string.length; i++) { + buffer->internalArray[buffer->length + i] = string.chars[i]; + } + buffer->length += string.length; + return CLAY__INIT(Clay_String) { .length = string.length, .chars = (const char *)(buffer->internalArray + buffer->length - string.length) }; +} + +// Global Variable Definitions ---------------------------------------------- +Clay_PointerData Clay__pointerInfo = { .position = {-1, -1} }; +Clay_Dimensions Clay__layoutDimensions = {0}; +Clay_ElementId Clay__dynamicElementIndexBaseHash = { .id = 128476991, .stringId = { .length = 8, .chars = "Auto ID" } }; +uint32_t Clay__dynamicElementIndex = 0; +bool Clay__debugModeEnabled = false; +bool Clay__disableCulling = false; +bool Clay__externalScrollHandlingEnabled = false; +uint32_t Clay__debugSelectedElementId = 0; +uint32_t Clay__debugViewWidth = 400; +Clay_Color Clay__debugViewHighlightColor = { 168, 66, 28, 100 }; +uint32_t Clay__generation = 0; +uint64_t Clay__arenaResetOffset = 0; +Clay_Arena Clay__internalArena; +// Layout Elements / Render Commands +Clay_LayoutElementArray Clay__layoutElements; +Clay_RenderCommandArray Clay__renderCommands; +Clay__int32_tArray Clay__openLayoutElementStack; +Clay__int32_tArray Clay__layoutElementChildren; +Clay__int32_tArray Clay__layoutElementChildrenBuffer; +Clay__TextElementDataArray Clay__textElementData; +Clay__LayoutElementPointerArray Clay__imageElementPointers; +Clay__int32_tArray Clay__reusableElementIndexBuffer; +Clay__int32_tArray Clay__layoutElementClipElementIds; +// Configs +Clay__LayoutConfigArray Clay__layoutConfigs; +Clay__ElementConfigArray Clay__elementConfigBuffer; +Clay__ElementConfigArray Clay__elementConfigs; +Clay__RectangleElementConfigArray Clay__rectangleElementConfigs; +Clay__TextElementConfigArray Clay__textElementConfigs; +Clay__ImageElementConfigArray Clay__imageElementConfigs; +Clay__FloatingElementConfigArray Clay__floatingElementConfigs; +Clay__ScrollElementConfigArray Clay__scrollElementConfigs; +Clay__CustomElementConfigArray Clay__customElementConfigs; +Clay__BorderElementConfigArray Clay__borderElementConfigs; +// Misc Data Structures +Clay__StringArray Clay__layoutElementIdStrings; +Clay__WrappedTextLineArray Clay__wrappedTextLines; +Clay__LayoutElementTreeNodeArray Clay__layoutElementTreeNodeArray1; +Clay__LayoutElementTreeRootArray Clay__layoutElementTreeRoots; +Clay__LayoutElementHashMapItemArray Clay__layoutElementsHashMapInternal; +Clay__int32_tArray Clay__layoutElementsHashMap; +Clay__MeasureTextCacheItemArray Clay__measureTextHashMapInternal; +Clay__int32_tArray Clay__measureTextHashMapInternalFreeList; +Clay__int32_tArray Clay__measureTextHashMap; +Clay__MeasuredWordArray Clay__measuredWords; +Clay__int32_tArray Clay__measuredWordsFreeList; +Clay__int32_tArray Clay__openClipElementStack; +Clay__ElementIdArray Clay__pointerOverIds; +Clay__ScrollContainerDataInternalArray Clay__scrollContainerDatas; +Clay__BoolArray Clay__treeNodeVisited; +Clay__CharArray Clay__dynamicStringData; +Clay__DebugElementDataArray Clay__debugElementData; + +#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); +#else + Clay_Dimensions (*Clay__MeasureText)(Clay_String *text, Clay_TextElementConfig *config); + Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId); +#endif + +Clay_LayoutElement* Clay__GetOpenLayoutElement(void) { + return Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&Clay__openLayoutElementStack, Clay__openLayoutElementStack.length - 1)); +} + +bool Clay__ElementHasConfig(Clay_LayoutElement *element, Clay__ElementConfigType type) { + return (element->configsEnabled & type); +} + +Clay_ElementConfigUnion Clay__FindElementConfigWithType(Clay_LayoutElement *element, Clay__ElementConfigType type) { + for (uint32_t i = 0; i < element->elementConfigs.length; i++) { + Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&element->elementConfigs, i); + if (config->type == type) { + return config->config; + } + } + return CLAY__INIT(Clay_ElementConfigUnion) { NULL }; +} + +Clay_ElementId Clay__HashNumber(const uint32_t offset, const uint32_t seed) { + uint32_t hash = seed; + hash += (offset + 48); + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = seed, .stringId = CLAY__STRING_DEFAULT }; // Reserve the hash result of zero as "null id" +} + +Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const uint32_t seed) { + uint32_t hash = 0; + uint32_t base = seed; + + for (size_t i = 0; i < key.length; i++) { + base += key.chars[i]; + base += (base << 10); + base ^= (base >> 6); + } + hash = base; + hash += offset; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + base += (base << 3); + hash ^= (hash >> 11); + base ^= (base >> 11); + hash += (hash << 15); + base += (base << 15); + return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id" +} + +Clay_ElementId Clay__Rehash(Clay_ElementId elementId, uint32_t number) { + uint32_t id = elementId.baseId; + id += number; + id += (id << 10); + id ^= (id >> 6); + + id += (id << 3); + id ^= (id >> 11); + id += (id << 15); + return CLAY__INIT(Clay_ElementId) { .id = id, .offset = number, .baseId = elementId.baseId, .stringId = elementId.stringId }; +} + +uint32_t Clay__RehashWithNumber(uint32_t id, uint32_t number) { + id += number; + id += (id << 10); + id ^= (id >> 6); + + id += (id << 3); + id ^= (id >> 11); + id += (id << 15); + return id; +} + +uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) { + uint32_t hash = 0; + uintptr_t pointerAsNumber = (uintptr_t)text->chars; + + hash += pointerAsNumber; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += text->length; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->fontId; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->fontSize; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->lineHeight; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->letterSpacing; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += config->wrapMode; + hash += (hash << 10); + hash ^= (hash >> 6); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash + 1; // Reserve the hash result of zero as "null id" +} + +Clay__MeasuredWord *Clay__AddMeasuredWord(Clay__MeasuredWord word, Clay__MeasuredWord *previousWord) { + if (Clay__measuredWordsFreeList.length > 0) { + uint32_t newItemIndex = Clay__int32_tArray_Get(&Clay__measuredWordsFreeList, (int)Clay__measuredWordsFreeList.length - 1); + Clay__measuredWordsFreeList.length--; + Clay__MeasuredWordArray_Set(&Clay__measuredWords, (int)newItemIndex, word); + previousWord->next = (int32_t)newItemIndex; + return Clay__MeasuredWordArray_Get(&Clay__measuredWords, (int)newItemIndex); + } else { + previousWord->next = (int32_t)Clay__measuredWords.length; + return Clay__MeasuredWordArray_Add(&Clay__measuredWords, word); + } +} + +Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) { + #ifndef CLAY_WASM + if (!Clay__MeasureText) { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED, + .errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."), + .userData = Clay__errorHandler.userData }); + return NULL; + } + #endif + uint32_t id = Clay__HashTextWithConfig(text, config); + uint32_t hashBucket = id % (Clay__maxMeasureTextCacheWordCount / 32); + int32_t elementIndexPrevious = 0; + int32_t elementIndex = Clay__measureTextHashMap.internalArray[hashBucket]; + while (elementIndex != 0) { + Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndex); + if (hashEntry->id == id) { + hashEntry->generation = Clay__generation; + return hashEntry; + } + // This element hasn't been seen in a few frames, delete the hash map item + if (Clay__generation - hashEntry->generation > 2) { + // Add all the measured words that were included in this measurement to the freelist + int32_t nextWordIndex = hashEntry->measuredWordsStartIndex; + while (nextWordIndex != -1) { + Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&Clay__measuredWords, nextWordIndex); + Clay__int32_tArray_Add(&Clay__measuredWordsFreeList, nextWordIndex); + nextWordIndex = measuredWord->next; + } + + uint32_t nextIndex = hashEntry->nextIndex; + Clay__MeasureTextCacheItemArray_Set(&Clay__measureTextHashMapInternal, elementIndex, CLAY__INIT(Clay__MeasureTextCacheItem) { .measuredWordsStartIndex = -1 }); + Clay__int32_tArray_Add(&Clay__measureTextHashMapInternalFreeList, elementIndex); + if (elementIndexPrevious == 0) { + Clay__measureTextHashMap.internalArray[hashBucket] = nextIndex; + } else { + Clay__MeasureTextCacheItem *previousHashEntry = Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndexPrevious); + previousHashEntry->nextIndex = nextIndex; + } + elementIndex = nextIndex; + } else { + elementIndexPrevious = elementIndex; + elementIndex = hashEntry->nextIndex; + } + } + + uint32_t newItemIndex = 0; + Clay__MeasureTextCacheItem newCacheItem = { .measuredWordsStartIndex = -1, .id = id, .generation = Clay__generation }; + Clay__MeasureTextCacheItem *measured = NULL; + if (Clay__measureTextHashMapInternalFreeList.length > 0) { + newItemIndex = Clay__int32_tArray_Get(&Clay__measureTextHashMapInternalFreeList, Clay__measureTextHashMapInternalFreeList.length - 1); + Clay__measureTextHashMapInternalFreeList.length--; + Clay__MeasureTextCacheItemArray_Set(&Clay__measureTextHashMapInternal, newItemIndex, newCacheItem); + measured = Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, newItemIndex); + } else { + if (Clay__measureTextHashMapInternal.length == Clay__measureTextHashMapInternal.capacity - 1) { + if (Clay__booleanWarnings.maxTextMeasureCacheExceeded) { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."), + .userData = Clay__errorHandler.userData }); + Clay__booleanWarnings.maxTextMeasureCacheExceeded = true; + } + return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; + } + measured = Clay__MeasureTextCacheItemArray_Add(&Clay__measureTextHashMapInternal, newCacheItem); + newItemIndex = Clay__measureTextHashMapInternal.length - 1; + } + + uint32_t start = 0; + uint32_t end = 0; + float measuredWidth = 0; + float measuredHeight = 0; + float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, config).width; + Clay__MeasuredWord tempWord = { .next = -1 }; + Clay__MeasuredWord *previousWord = &tempWord; + while (end < text->length) { + if (Clay__measuredWords.length == Clay__measuredWords.capacity - 1) { + if (!Clay__booleanWarnings.maxTextMeasureCacheExceeded) { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay has run out of space in it's internal text measurement cache. Try using Clay_SetMaxMeasureTextCacheWordCount() (default 16384, with 1 unit storing 1 measured word)."), + .userData = Clay__errorHandler.userData }); + Clay__booleanWarnings.maxTextMeasureCacheExceeded = true; + } + return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT; + } + char current = text->chars[end]; + if (current == ' ' || current == '\n') { + uint32_t length = end - start; + Clay_String word = { .length = length, .chars = &text->chars[start] }; + Clay_Dimensions dimensions = Clay__MeasureText(&word, config); + if (current == ' ') { + dimensions.width += spaceWidth; + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length + 1, .width = dimensions.width, .next = -1 }, previousWord); + } + if (current == '\n') { + if (length > 1) { + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length, .width = dimensions.width, .next = -1 }, previousWord); + } + previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = end + 1, .length = 0, .width = 0, .next = -1 }, previousWord); + } + measuredWidth += dimensions.width; + measuredHeight = dimensions.height; + start = end + 1; + } + end++; + } + if (end - start > 0) { + Clay_String lastWord = { .length = end - start, .chars = &text->chars[start] }; + Clay_Dimensions dimensions = Clay__MeasureText(&lastWord, config); + Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord); + measuredWidth += dimensions.width; + measuredHeight = dimensions.height; + } + measured->measuredWordsStartIndex = tempWord.next; + measured->unwrappedDimensions.width = measuredWidth; + measured->unwrappedDimensions.height = measuredHeight; + + if (elementIndexPrevious != 0) { + Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndexPrevious)->nextIndex = newItemIndex; + } else { + Clay__measureTextHashMap.internalArray[hashBucket] = newItemIndex; + } + return measured; +} + +bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { + return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; +} + +Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Clay_LayoutElement* layoutElement) { + if (Clay__layoutElementsHashMapInternal.length == Clay__layoutElementsHashMapInternal.capacity - 1) { + return NULL; + } + Clay_LayoutElementHashMapItem item = { .elementId = elementId, .layoutElement = layoutElement, .nextIndex = -1, .generation = Clay__generation + 1 }; + uint32_t hashBucket = elementId.id % Clay__layoutElementsHashMap.capacity; + int32_t hashItemPrevious = -1; + int32_t hashItemIndex = Clay__layoutElementsHashMap.internalArray[hashBucket]; + while (hashItemIndex != -1) { // Just replace collision, not a big deal - leave it up to the end user + Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemIndex); + if (hashItem->elementId.id == elementId.id) { // Collision - resolve based on generation + item.nextIndex = hashItem->nextIndex; + if (hashItem->generation <= Clay__generation) { // First collision - assume this is the "same" element + hashItem->generation = Clay__generation + 1; + hashItem->layoutElement = layoutElement; + hashItem->debugData->collision = false; + } else { // Multiple collisions this frame - two elements have the same ID + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_DUPLICATE_ID, + .errorText = CLAY_STRING("An element with this ID was already previously declared during this layout."), + .userData = Clay__errorHandler.userData }); + if (Clay__debugModeEnabled) { + hashItem->debugData->collision = true; + } + } + return hashItem; + } + hashItemPrevious = hashItemIndex; + hashItemIndex = hashItem->nextIndex; + } + Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&Clay__layoutElementsHashMapInternal, item); + hashItem->debugData = Clay__DebugElementDataArray_Add(&Clay__debugElementData, CLAY__INIT(Clay__DebugElementData) {0}); + if (hashItemPrevious != -1) { + Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)Clay__layoutElementsHashMapInternal.length - 1; + } else { + Clay__layoutElementsHashMap.internalArray[hashBucket] = (int32_t)Clay__layoutElementsHashMapInternal.length - 1; + } + return hashItem; +} + +Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) { + uint32_t hashBucket = id % Clay__layoutElementsHashMap.capacity; + int32_t elementIndex = Clay__layoutElementsHashMap.internalArray[hashBucket]; + while (elementIndex != -1) { + Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, elementIndex); + if (hashEntry->elementId.id == id) { + return hashEntry; + } + elementIndex = hashEntry->nextIndex; + } + return &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT; +} + +void Clay__GenerateIdForAnonymousElement(Clay_LayoutElement *openLayoutElement) { + Clay_LayoutElement *parentElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&Clay__openLayoutElementStack, Clay__openLayoutElementStack.length - 2)); + Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length, parentElement->id); + openLayoutElement->id = elementId.id; + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&Clay__layoutElementIdStrings, elementId.stringId); +} + +void Clay__ElementPostConfiguration(void) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + // ID + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + // Layout Config + if (!openLayoutElement->layoutConfig) { + openLayoutElement->layoutConfig = &CLAY_LAYOUT_DEFAULT; + } + + // Loop through element configs and handle special cases + openLayoutElement->elementConfigs.internalArray = &Clay__elementConfigs.internalArray[Clay__elementConfigs.length]; + for (uint32_t elementConfigIndex = 0; elementConfigIndex < openLayoutElement->elementConfigs.length; elementConfigIndex++) { + Clay_ElementConfig *config = Clay__ElementConfigArray_Add(&Clay__elementConfigs, *Clay__ElementConfigArray_Get(&Clay__elementConfigBuffer, Clay__elementConfigBuffer.length - openLayoutElement->elementConfigs.length + elementConfigIndex)); + openLayoutElement->configsEnabled |= config->type; + switch (config->type) { + case CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE: + case CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER: break; + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER: { + Clay_FloatingElementConfig *floatingConfig = config->config.floatingElementConfig; + // This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here + Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&Clay__openLayoutElementStack, Clay__openLayoutElementStack.length - 2)); + if (!hierarchicalParent) { + break; + } + int clipElementId = 0; + if (floatingConfig->parentId == 0) { + // If no parent id was specified, attach to the elements direct hierarchical parent + Clay_FloatingElementConfig newConfig = *floatingConfig; + newConfig.parentId = hierarchicalParent->id; + floatingConfig = Clay__FloatingElementConfigArray_Add(&Clay__floatingElementConfigs, newConfig); + config->config.floatingElementConfig = floatingConfig; + if (Clay__openClipElementStack.length > 0) { + clipElementId = Clay__int32_tArray_Get(&Clay__openClipElementStack, (int)Clay__openClipElementStack.length - 1); + } + } else { + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig->parentId); + clipElementId = Clay__int32_tArray_Get(&Clay__layoutElementClipElementIds, parentItem->layoutElement - Clay__layoutElements.internalArray); + if (!parentItem) { + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_FLOATING_CONTAINER_PARENT_NOT_FOUND, + .errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."), + .userData = Clay__errorHandler.userData }); + } + } + Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { + .layoutElementIndex = (uint32_t)Clay__int32_tArray_Get(&Clay__openLayoutElementStack, Clay__openLayoutElementStack.length - 1), + .parentId = floatingConfig->parentId, + .clipElementId = (uint32_t)clipElementId, + .zIndex = floatingConfig->zIndex, + }); + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER: { + Clay__int32_tArray_Add(&Clay__openClipElementStack, (int)openLayoutElement->id); + // Retrieve or create cached data to track scroll position across frames + Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL; + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (openLayoutElement->id == mapping->elementId) { + scrollOffset = mapping; + scrollOffset->layoutElement = openLayoutElement; + scrollOffset->openThisFrame = true; + } + } + if (!scrollOffset) { + scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&Clay__scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true}); + } + if (Clay__externalScrollHandlingEnabled) { + scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: break; + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { + Clay__LayoutElementPointerArray_Add(&Clay__imageElementPointers, openLayoutElement); + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: + default: break; + } + } + Clay__elementConfigBuffer.length -= openLayoutElement->elementConfigs.length; +} + +void Clay__CloseElement(void) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + Clay_LayoutConfig *layoutConfig = openLayoutElement->layoutConfig; + bool elementHasScrollHorizontal = false; + bool elementHasScrollVertical = false; + if (Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + elementHasScrollHorizontal = scrollConfig->horizontal; + elementHasScrollVertical = scrollConfig->vertical; + Clay__openClipElementStack.length--; + } + + // Attach children to the current open element + openLayoutElement->childrenOrTextContent.children.elements = &Clay__layoutElementChildren.internalArray[Clay__layoutElementChildren.length]; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + openLayoutElement->dimensions.width = layoutConfig->padding.x * 2; + for (int i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { + int32_t childIndex = Clay__int32_tArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&Clay__layoutElements, childIndex); + openLayoutElement->dimensions.width += child->dimensions.width; + openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.y * 2); + // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents + if (!elementHasScrollHorizontal) { + openLayoutElement->minDimensions.width += child->minDimensions.width; + } + if (!elementHasScrollVertical) { + openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.y * 2); + } + Clay__int32_tArray_Add(&Clay__layoutElementChildren, childIndex); + } + float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + openLayoutElement->dimensions.width += childGap; // TODO this is technically a bug with childgap and scroll containers + openLayoutElement->minDimensions.width += childGap; + } + else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + openLayoutElement->dimensions.height = layoutConfig->padding.y * 2; + for (int i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) { + int32_t childIndex = Clay__int32_tArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i); + Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&Clay__layoutElements, childIndex); + openLayoutElement->dimensions.height += child->dimensions.height; + openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.x * 2); + // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents + if (!elementHasScrollVertical) { + openLayoutElement->minDimensions.height += child->minDimensions.height; + } + if (!elementHasScrollHorizontal) { + openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.x * 2); + } + Clay__int32_tArray_Add(&Clay__layoutElementChildren, childIndex); + } + float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + openLayoutElement->dimensions.height += childGap; // TODO this is technically a bug with childgap and scroll containers + openLayoutElement->minDimensions.height += childGap; + } + + Clay__layoutElementChildrenBuffer.length -= openLayoutElement->childrenOrTextContent.children.length; + + // Clamp element min and max width to the values configured in the layout + if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) { + if (layoutConfig->sizing.width.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.width.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); + openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); + } else { + openLayoutElement->dimensions.width = 0; + } + + // Clamp element min and max height to the values configured in the layout + if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) { + if (layoutConfig->sizing.height.size.minMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier + layoutConfig->sizing.height.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } else { + openLayoutElement->dimensions.height = 0; + } + + bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER); + + // Close the currently open element + int32_t closingElementIndex = Clay__int32_tArray_RemoveSwapback(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1); + openLayoutElement = Clay__GetOpenLayoutElement(); + + if (!elementIsFloating && Clay__openLayoutElementStack.length > 1) { + openLayoutElement->childrenOrTextContent.children.length++; + Clay__int32_tArray_Add(&Clay__layoutElementChildrenBuffer, closingElementIndex); + } +} + +void Clay__OpenElement(void) { + if (Clay__layoutElements.length == Clay__layoutElements.capacity - 1 || Clay__booleanWarnings.maxElementsExceeded) { + Clay__booleanWarnings.maxElementsExceeded = true; + return; + } + Clay_LayoutElement layoutElement = {0}; + Clay_LayoutElementArray_Add(&Clay__layoutElements, layoutElement); + Clay__int32_tArray_Add(&Clay__openLayoutElementStack, Clay__layoutElements.length - 1); + if (Clay__openClipElementStack.length > 0) { + Clay__int32_tArray_Set(&Clay__layoutElementClipElementIds, Clay__layoutElements.length - 1, Clay__int32_tArray_Get(&Clay__openClipElementStack, (int)Clay__openClipElementStack.length - 1)); + } else { + Clay__int32_tArray_Set(&Clay__layoutElementClipElementIds, Clay__layoutElements.length - 1, 0); + } +} + +void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) { + if (Clay__layoutElements.length == Clay__layoutElements.capacity - 1 || Clay__booleanWarnings.maxElementsExceeded) { + Clay__booleanWarnings.maxElementsExceeded = true; + return; + } + Clay_LayoutElement *parentElement = Clay__GetOpenLayoutElement(); + parentElement->childrenOrTextContent.children.length++; + + Clay__OpenElement(); + Clay_LayoutElement * openLayoutElement = Clay__GetOpenLayoutElement(); + Clay__int32_tArray_Add(&Clay__layoutElementChildrenBuffer, Clay__layoutElements.length - 1); + Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, textConfig); + Clay_ElementId elementId = Clay__HashString(CLAY_STRING("Text"), parentElement->childrenOrTextContent.children.length, parentElement->id); + openLayoutElement->id = elementId.id; + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&Clay__layoutElementIdStrings, elementId.stringId); + Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? textConfig->lineHeight : textMeasured->unwrappedDimensions.height }; + openLayoutElement->dimensions = textDimensions; + openLayoutElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->unwrappedDimensions.height, .height = textDimensions.height }; // TODO not sure this is the best way to decide min width for text + openLayoutElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&Clay__textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = Clay__layoutElements.length - 1 }); + openLayoutElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) { + .length = 1, + .internalArray = Clay__ElementConfigArray_Add(&Clay__elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = CLAY__ELEMENT_CONFIG_TYPE_TEXT, .config = { .textElementConfig = textConfig }}) + }; + openLayoutElement->configsEnabled |= CLAY__ELEMENT_CONFIG_TYPE_TEXT; + openLayoutElement->layoutConfig = &CLAY_LAYOUT_DEFAULT; + // Close the currently open element + Clay__int32_tArray_RemoveSwapback(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1); +} + +void Clay__InitializeEphemeralMemory(Clay_Arena *arena) { + // Ephemeral Memory - reset every frame + Clay__internalArena.nextAllocation = Clay__arenaResetOffset; + + Clay__layoutElementChildrenBuffer = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElements = Clay_LayoutElementArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay_warnings = Clay__WarningArray_Allocate_Arena(100, arena); + + Clay__layoutConfigs = Clay__LayoutConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__elementConfigBuffer = Clay__ElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__elementConfigs = Clay__ElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__rectangleElementConfigs = Clay__RectangleElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__textElementConfigs = Clay__TextElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__imageElementConfigs = Clay__ImageElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__floatingElementConfigs = Clay__FloatingElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__scrollElementConfigs = Clay__ScrollElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__customElementConfigs = Clay__CustomElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__borderElementConfigs = Clay__BorderElementConfigArray_Allocate_Arena(Clay__maxElementCount, arena); + + Clay__layoutElementIdStrings = Clay__StringArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__wrappedTextLines = Clay__WrappedTextLineArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElementChildren = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__textElementData = Clay__TextElementDataArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__imageElementPointers = Clay__LayoutElementPointerArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__renderCommands = Clay_RenderCommandArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__treeNodeVisited = Clay__BoolArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__treeNodeVisited.length = Clay__treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list + Clay__openClipElementStack = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__reusableElementIndexBuffer = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElementClipElementIds = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__dynamicStringData = Clay__CharArray_Allocate_Arena(Clay__maxElementCount, arena); +} + +void Clay__InitializePersistentMemory(Clay_Arena *arena) { + // Persistent memory - initialized once and not reset + Clay__scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(10, arena); + Clay__layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__measureTextHashMapInternalFreeList = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__measuredWordsFreeList = Clay__int32_tArray_Allocate_Arena(Clay__maxMeasureTextCacheWordCount, arena); + Clay__measureTextHashMap = Clay__int32_tArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__measuredWords = Clay__MeasuredWordArray_Allocate_Arena(Clay__maxMeasureTextCacheWordCount, arena); + Clay__pointerOverIds = Clay__ElementIdArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__debugElementData = Clay__DebugElementDataArray_Allocate_Arena(Clay__maxElementCount, arena); + Clay__arenaResetOffset = arena->nextAllocation; +} + + +CLAY__TYPEDEF(Clay__SizeDistributionType, enum { + CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER, + CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER, + CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER, +}); + +float Clay__DistributeSizeAmongChildren(bool xAxis, float sizeToDistribute, Clay__int32_tArray resizableContainerBuffer, Clay__SizeDistributionType distributionType) { + Clay__int32_tArray remainingElements = Clay__openClipElementStack; + remainingElements.length = 0; + + for (uint32_t i = 0; i < resizableContainerBuffer.length; ++i) { + Clay__int32_tArray_Add(&remainingElements, Clay__int32_tArray_Get(&resizableContainerBuffer, i)); + } + + while (sizeToDistribute != 0 && remainingElements.length > 0) { + float dividedSize = sizeToDistribute / (float)remainingElements.length; + for (uint32_t childOffset = 0; childOffset < remainingElements.length; childOffset++) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&remainingElements, childOffset)); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height; + bool canDistribute = true; + + if ((sizeToDistribute < 0 && *childSize == childSizing.size.minMax.min) || (sizeToDistribute > 0 && *childSize == childSizing.size.minMax.max)) { + canDistribute = false; + } + // Currently, we don't support squishing aspect ratio images on their Y axis as it would break ratio + else if (!xAxis && Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) { + canDistribute = false; + } + else { + switch (distributionType) { + case CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER: break; + case CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER: if (childSizing.type != CLAY__SIZING_TYPE_GROW) canDistribute = false; break; + case CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER: { + if (Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + if ((xAxis && !scrollConfig->horizontal) || (!xAxis && !scrollConfig->vertical)) { + Clay__int32_tArray_RemoveSwapback(&remainingElements, childOffset); + childOffset--; + continue; + } + } + } + } + } + + if (!canDistribute) { + Clay__int32_tArray_RemoveSwapback(&remainingElements, childOffset); + childOffset--; + continue; + } + + float oldChildSize = *childSize; + *childSize = CLAY__MAX(CLAY__MAX(CLAY__MIN(childSizing.size.minMax.max, *childSize + dividedSize), childSizing.size.minMax.min), childMinSize); + float diff = *childSize - oldChildSize; + if (diff > -0.01 && diff < 0.01) { + Clay__int32_tArray_RemoveSwapback(&remainingElements, childOffset); + childOffset--; + continue; + } + sizeToDistribute -= diff; + } + } + return (sizeToDistribute > -0.01 && sizeToDistribute < 0.01) ? 0 : sizeToDistribute; +} + +void Clay__SizeContainersAlongAxis(bool xAxis) { + Clay__int32_tArray bfsBuffer = Clay__layoutElementChildrenBuffer; + Clay__int32_tArray resizableContainerBuffer = Clay__openLayoutElementStack; + for (uint32_t rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) { + bfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, (int)root->layoutElementIndex); + Clay__int32_tArray_Add(&bfsBuffer, (int32_t)root->layoutElementIndex); + + // Size floating containers to their parents + if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER)) { + Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig; + Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId); + if (parentItem) { + Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement; + if (rootElement->layoutConfig->sizing.width.type == CLAY__SIZING_TYPE_GROW) { + rootElement->dimensions.width = parentLayoutElement->dimensions.width; + } + if (rootElement->layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_GROW) { + rootElement->dimensions.height = parentLayoutElement->dimensions.height; + } + } + } + + rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.size.minMax.min), rootElement->layoutConfig->sizing.width.size.minMax.max); + rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.size.minMax.min), rootElement->layoutConfig->sizing.height.size.minMax.max); + + for (uint32_t i = 0; i < bfsBuffer.length; ++i) { + int32_t parentIndex = Clay__int32_tArray_Get(&bfsBuffer, i); + Clay_LayoutElement *parent = Clay_LayoutElementArray_Get(&Clay__layoutElements, parentIndex); + Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig; + int growContainerCount = 0; + float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height; + float parentPadding = (float)(xAxis ? parent->layoutConfig->padding.x : parent->layoutConfig->padding.y); + float innerContentSize = 0, growContainerContentSize = 0, totalPaddingAndChildGaps = parentPadding * 2; + bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM); + resizableContainerBuffer.length = 0; + float parentChildGap = parentStyleConfig->childGap; + + for (int childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { + int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, childElementIndex); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height; + + if (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && childElement->childrenOrTextContent.children.length > 0) { + Clay__int32_tArray_Add(&bfsBuffer, childElementIndex); + } + + if (childSizing.type != CLAY__SIZING_TYPE_PERCENT && childSizing.type != CLAY__SIZING_TYPE_FIXED && (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS))) { + Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); + } + + if (sizingAlongAxis) { + innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize); + if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + growContainerContentSize += childSize; + growContainerCount++; + } + if (childOffset > 0) { + innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child + totalPaddingAndChildGaps += parentChildGap; + } + } else { + innerContentSize = CLAY__MAX(childSize, innerContentSize); + } + } + + // Expand percentage containers to size + for (int childOffset = 0; childOffset < parent->childrenOrTextContent.children.length; childOffset++) { + int32_t childElementIndex = parent->childrenOrTextContent.children.elements[childOffset]; + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, childElementIndex); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) { + *childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent; + if (sizingAlongAxis) { + innerContentSize += *childSize; + if (childOffset > 0) { + innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child + totalPaddingAndChildGaps += parentChildGap; + } + } else { + innerContentSize = CLAY__MAX(*childSize, innerContentSize); + } + } + } + + if (sizingAlongAxis) { + float sizeToDistribute = parentSize - parentPadding * 2 - innerContentSize; + // If the content is too large, compress the children as much as possible + if (sizeToDistribute < 0) { + // If the parent can scroll in the axis direction in this direction, don't compress children, just leave them alone + if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) { + continue; + } + } + // Scrolling containers preferentially compress before others + sizeToDistribute = Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER); + + // If there is still height to make up, remove it from all containers that haven't hit their minimum size + if (sizeToDistribute < 0) { + Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER); + } + // The content is too small, allow SIZING_GROW containers to expand + } else if (sizeToDistribute > 0 && growContainerCount > 0) { + float targetSize = (sizeToDistribute + growContainerContentSize) / growContainerCount; + for (uint32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&resizableContainerBuffer, childOffset)); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + float *minSize = xAxis ? &childElement->minDimensions.width : &childElement->minDimensions.height; + if (targetSize < *minSize) { + growContainerContentSize -= *minSize; + Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childOffset); + growContainerCount--; + targetSize = (sizeToDistribute + growContainerContentSize) / growContainerCount; + childOffset = -1; + continue; + } + *childSize = targetSize; + } + } + } + // Sizing along the non layout axis ("off axis") + } else { + for (uint32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&resizableContainerBuffer, childOffset)); + Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height; + float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height; + + if (!xAxis && Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) { + continue; // Currently we don't support resizing aspect ratio images on the Y axis because it would break the ratio + } + + // If we're laying out the children of a scroll panel, grow containers expand to the height of the inner content, not the outer container + float maxSize = parentSize - parentPadding * 2; + if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) { + maxSize = CLAY__MAX(maxSize, innerContentSize); + } + } + if (childSizing.type == CLAY__SIZING_TYPE_FIT) { + *childSize = CLAY__MAX(childSizing.size.minMax.min, CLAY__MIN(*childSize, maxSize)); + } else if (childSizing.type == CLAY__SIZING_TYPE_GROW) { + *childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max); + } + } + } + } + } +} + +Clay_String Clay__IntToString(int integer) { + if (integer == 0) { + return CLAY__INIT(Clay_String) { .length = 1, .chars = "0" }; + } + char *chars = (char *)(Clay__dynamicStringData.internalArray + Clay__dynamicStringData.length); + size_t length = 0; + int sign = integer; + + if (integer < 0) { + integer = -integer; + } + while (integer > 0) { + chars[length++] = (char)(integer % 10 + '0'); + integer /= 10; + } + + if (sign < 0) { + chars[length++] = '-'; + } + + // Reverse the string to get the correct order + for (int j = 0, k = length - 1; j < k; j++, k--) { + char temp = chars[j]; + chars[j] = chars[k]; + chars[k] = temp; + } + Clay__dynamicStringData.length += length; + return CLAY__INIT(Clay_String) { .length = length, .chars = chars }; +} + +void Clay__AddRenderCommand(Clay_RenderCommand renderCommand) { + if (Clay__renderCommands.length < Clay__renderCommands.capacity - 1) { + Clay_RenderCommandArray_Add(&Clay__renderCommands, renderCommand); + } else { + if (!Clay__booleanWarnings.maxRenderCommandsExceeded) { + Clay__booleanWarnings.maxRenderCommandsExceeded = true; + Clay__errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) { + .errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED, + .errorText = CLAY_STRING("Clay ran out of capacity while attempting to create render commands. This is usually caused by a large amount of wrapping text elements while close to the max element capacity. Try using Clay_SetMaxElementCount() with a higher value."), + .userData = Clay__errorHandler.userData }); + } + } +} + +bool Clay__ElementIsOffscreen(Clay_BoundingBox *boundingBox) { + if (Clay__disableCulling) { + return false; + } + + return (boundingBox->x > (float)Clay__layoutDimensions.width) || + (boundingBox->y > (float)Clay__layoutDimensions.height) || + (boundingBox->x + boundingBox->width < 0) || + (boundingBox->y + boundingBox->height < 0); +} + +void Clay__CalculateFinalLayout(void) { + // Calculate sizing along the X axis + Clay__SizeContainersAlongAxis(true); + + // Wrap text + for (uint32_t textElementIndex = 0; textElementIndex < Clay__textElementData.length; ++textElementIndex) { + Clay__TextElementData *textElementData = Clay__TextElementDataArray_Get(&Clay__textElementData, textElementIndex); + textElementData->wrappedLines = CLAY__INIT(Clay__WrappedTextLineArraySlice) { .length = 0, .internalArray = &Clay__wrappedTextLines.internalArray[Clay__wrappedTextLines.length] }; + Clay_LayoutElement *containerElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, (int)textElementData->elementIndex); + Clay_TextElementConfig *textConfig = Clay__FindElementConfigWithType(containerElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig; + Clay__MeasureTextCacheItem *measureTextCacheItem = Clay__MeasureTextCached(&textElementData->text, textConfig); + float lineWidth = 0; + float lineHeight = textConfig->lineHeight > 0 ? textConfig->lineHeight : textElementData->preferredDimensions.height; + uint32_t lineLengthChars = 0; + uint32_t lineStartOffset = 0; + if (textElementData->preferredDimensions.width <= containerElement->dimensions.width) { + Clay__WrappedTextLineArray_Add(&Clay__wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { containerElement->dimensions, textElementData->text }); + textElementData->wrappedLines.length++; + continue; + } + int32_t wordIndex = measureTextCacheItem->measuredWordsStartIndex; + while (wordIndex != -1) { + if (Clay__wrappedTextLines.length > Clay__wrappedTextLines.capacity - 1) { + break; + } + Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&Clay__measuredWords, wordIndex); + // Only word on the line is too large, just render it anyway + if (lineLengthChars == 0 && lineWidth + measuredWord->width > containerElement->dimensions.width) { + Clay__WrappedTextLineArray_Add(&Clay__wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); + textElementData->wrappedLines.length++; + wordIndex = measuredWord->next; + } + // measuredWord->length == 0 means a newline character + else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { + // Wrapped text lines list has overflowed, just render out the line + Clay__WrappedTextLineArray_Add(&Clay__wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth, lineHeight }, { .length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); + textElementData->wrappedLines.length++; + if (lineLengthChars == 0 || measuredWord->length == 0) { + wordIndex = measuredWord->next; + } + lineWidth = 0; + lineLengthChars = 0; + lineStartOffset = measuredWord->startOffset; + } else { + lineWidth += measuredWord->width; + lineLengthChars += measuredWord->length; + wordIndex = measuredWord->next; + } + } + if (lineLengthChars > 0) { + Clay__WrappedTextLineArray_Add(&Clay__wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); + textElementData->wrappedLines.length++; + } + containerElement->dimensions.height = lineHeight * textElementData->wrappedLines.length; + } + + // Scale vertical image heights according to aspect ratio + for (uint32_t i = 0; i < Clay__imageElementPointers.length; ++i) { + Clay_LayoutElement* imageElement = Clay__LayoutElementPointerArray_Get(&Clay__imageElementPointers, i); + Clay_ImageElementConfig *config = Clay__FindElementConfigWithType(imageElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE).imageElementConfig; + imageElement->dimensions.height = (config->sourceDimensions.height / CLAY__MAX(config->sourceDimensions.width, 1)) * imageElement->dimensions.width; + } + + // Propagate effect of text wrapping, image aspect scaling etc. on height of parents + Clay__LayoutElementTreeNodeArray dfsBuffer = Clay__layoutElementTreeNodeArray1; + dfsBuffer.length = 0; + for (uint32_t i = 0; i < Clay__layoutElementTreeRoots.length; ++i) { + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, i); + Clay__treeNodeVisited.internalArray[dfsBuffer.length] = false; + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, (int)root->layoutElementIndex) }); + } + while (dfsBuffer.length > 0) { + Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; + if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + // If the element has no children or is the container for a text element, don't bother inspecting it + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0) { + dfsBuffer.length--; + continue; + } + // Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order) + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; i++) { + Clay__treeNodeVisited.internalArray[dfsBuffer.length] = false; + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]) }); + } + continue; + } + dfsBuffer.length--; + + // DFS node has been visited, this is on the way back up to the root + Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; + if (layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_PERCENT) { + continue; + } + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + // Resize any parent containers that have grown in height along their non layout axis + for (int j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.y * 2, currentElement->dimensions.height); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } + } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { + // Resizing along the layout axis + float contentHeight = (float)layoutConfig->padding.y * 2; + for (int j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[j]); + contentHeight += childElement->dimensions.height; + } + contentHeight += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); + } + } + + // Calculate sizing along the Y axis + Clay__SizeContainersAlongAxis(false); + + // Calculate final positions and generate render commands + Clay__renderCommands.length = 0; + dfsBuffer.length = 0; + for (uint32_t rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, (int)root->layoutElementIndex); + Clay_Vector2 rootPosition = {0}; + Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId); + // Position root floating containers + if (Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER) && parentHashMapItem) { + Clay_FloatingElementConfig *config = Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig; + Clay_Dimensions rootDimensions = rootElement->dimensions; + Clay_BoundingBox parentBoundingBox = parentHashMapItem->boundingBox; + // Set X position + Clay_Vector2 targetAttachPosition = {0}; + switch (config->attachment.parent) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_LEFT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x; break; + case CLAY_ATTACH_POINT_CENTER_TOP: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + (parentBoundingBox.width / 2); break; + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_RIGHT_CENTER: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + parentBoundingBox.width; break; + } + switch (config->attachment.element) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_LEFT_BOTTOM: break; + case CLAY_ATTACH_POINT_CENTER_TOP: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x -= (rootDimensions.width / 2); break; + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_RIGHT_CENTER: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x -= rootDimensions.width; break; + } + switch (config->attachment.parent) { // I know I could merge the x and y switch statements, but this is easier to read + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_CENTER_TOP: targetAttachPosition.y = parentBoundingBox.y; break; + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y = parentBoundingBox.y + (parentBoundingBox.height / 2); break; + case CLAY_ATTACH_POINT_LEFT_BOTTOM: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y = parentBoundingBox.y + parentBoundingBox.height; break; + } + switch (config->attachment.element) { + case CLAY_ATTACH_POINT_LEFT_TOP: + case CLAY_ATTACH_POINT_RIGHT_TOP: + case CLAY_ATTACH_POINT_CENTER_TOP: break; + case CLAY_ATTACH_POINT_LEFT_CENTER: + case CLAY_ATTACH_POINT_CENTER_CENTER: + case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y -= (rootDimensions.height / 2); break; + case CLAY_ATTACH_POINT_LEFT_BOTTOM: + case CLAY_ATTACH_POINT_CENTER_BOTTOM: + case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y -= rootDimensions.height; break; + } + targetAttachPosition.x += config->offset.x; + targetAttachPosition.y += config->offset.y; + rootPosition = targetAttachPosition; + } + if (root->clipElementId) { + Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId); + if (clipHashMapItem) { + // Floating elements that are attached to scrolling contents won't be correctly positioned if external scroll handling is enabled, fix here + if (Clay__externalScrollHandlingEnabled) { + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(clipHashMapItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (mapping->layoutElement == clipHashMapItem->layoutElement) { + root->pointerOffset = mapping->scrollPosition; + if (scrollConfig->horizontal) { + rootPosition.x += mapping->scrollPosition.x; + } + if (scrollConfig->vertical) { + rootPosition.y += mapping->scrollPosition.y; + } + break; + } + } + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = clipHashMapItem->boundingBox, + .config = { .scrollElementConfig = Clay__StoreScrollElementConfig(CLAY__INIT(Clay_ScrollElementConfig){0}) }, + .id = Clay__RehashWithNumber(rootElement->id, 10), // TODO need a better strategy for managing derived ids + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START, + }); + } + } + Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, CLAY__INIT(Clay__LayoutElementTreeNode) { .layoutElement = rootElement, .position = rootPosition, .nextChildOffset = { .x = (float)rootElement->layoutConfig->padding.x, .y = (float)rootElement->layoutConfig->padding.y } }); + + Clay__treeNodeVisited.internalArray[0] = false; + while (dfsBuffer.length > 0) { + Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement; + Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig; + Clay_Vector2 scrollOffset = {0}; + + // This will only be run a single time for each element in downwards DFS order + if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + + Clay_BoundingBox currentElementBoundingBox = { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height }; + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER)) { + Clay_FloatingElementConfig *floatingElementConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig; + Clay_Dimensions expand = floatingElementConfig->expand; + currentElementBoundingBox.x -= expand.width; + currentElementBoundingBox.width += expand.width * 2; + currentElementBoundingBox.y -= expand.height; + currentElementBoundingBox.height += expand.height * 2; + } + + Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL; + // Apply scroll offsets to container + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + + // This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + scrollContainerData = mapping; + mapping->boundingBox = currentElementBoundingBox; + if (scrollConfig->horizontal) { + scrollOffset.x = mapping->scrollPosition.x; + } + if (scrollConfig->vertical) { + scrollOffset.y = mapping->scrollPosition.y; + } + if (Clay__externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) {0}; + } + break; + } + } + } + + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(currentElement->id); + if (hashMapItem) { + hashMapItem->boundingBox = currentElementBoundingBox; + } + + int sortedConfigIndexes[20]; + for (uint32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + sortedConfigIndexes[elementConfigIndex] = elementConfigIndex; + } + int sortMax = currentElement->elementConfigs.length - 1; + while (sortMax > 0) { // todo dumb bubble sort + for (int i = 0; i < sortMax; ++i) { + int current = sortedConfigIndexes[i]; + int next = sortedConfigIndexes[i + 1]; + Clay__ElementConfigType currentType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, current)->type; + Clay__ElementConfigType nextType = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, next)->type; + if (nextType == CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER || currentType == CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER) { + sortedConfigIndexes[i] = next; + sortedConfigIndexes[i + 1] = current; + } + } + sortMax--; + } + + // Create the render commands for this element + for (uint32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, sortedConfigIndexes[elementConfigIndex]); + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .config = elementConfig->config, + .id = currentElement->id, + }; + + bool offscreen = Clay__ElementIsOffscreen(¤tElementBoundingBox); + // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow + bool shouldRender = !offscreen; + switch (elementConfig->type) { + case CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER: { + shouldRender = false; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_NONE; + shouldRender = false; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START; + shouldRender = true; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE; + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { + if (!shouldRender) { + break; + } + shouldRender = false; + Clay_ElementConfigUnion configUnion = elementConfig->config; + Clay_TextElementConfig *textElementConfig = configUnion.textElementConfig; + float naturalLineHeight = currentElement->childrenOrTextContent.textElementData->preferredDimensions.height; + float finalLineHeight = textElementConfig->lineHeight > 0 ? textElementConfig->lineHeight : naturalLineHeight; + float lineHeightOffset = (finalLineHeight - naturalLineHeight) / 2; + float yPosition = lineHeightOffset; + for (uint32_t lineIndex = 0; lineIndex < currentElement->childrenOrTextContent.textElementData->wrappedLines.length; ++lineIndex) { + Clay__WrappedTextLine wrappedLine = currentElement->childrenOrTextContent.textElementData->wrappedLines.internalArray[lineIndex]; // todo range check + if (wrappedLine.line.length == 0) { + yPosition += finalLineHeight; + continue; + } + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x, currentElementBoundingBox.y + yPosition, wrappedLine.dimensions.width, wrappedLine.dimensions.height }, // TODO width + .config = configUnion, + .text = wrappedLine.line, + .id = Clay__HashNumber(lineIndex, currentElement->id).id, + .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT, + }); + yPosition += finalLineHeight; + + if (!Clay__disableCulling && (currentElementBoundingBox.y + yPosition > Clay__layoutDimensions.height)) { + break; + } + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: { + renderCommand.commandType = CLAY_RENDER_COMMAND_TYPE_CUSTOM; + break; + } + default: break; + } + if (shouldRender) { + Clay__AddRenderCommand(renderCommand); + } + if (offscreen) { + // NOTE: You may be tempted to try an early return / continue if an element is off screen. Why bother calculating layout for its children, right? + // Unfortunately, a FLOATING_CONTAINER may be defined that attaches to a child or grandchild of this element, which is large enough to still + // be on screen, even if this element isn't. That depends on this element and it's children being laid out correctly (even if they are entirely off screen) + } + } + + // Setup initial on-axis alignment + if (!Clay__ElementHasConfig(currentElementTreeNode->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + Clay_Dimensions contentSize = {0,0}; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + contentSize.width += childElement->dimensions.width; + contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height); + } + contentSize.width += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.width - (float)layoutConfig->padding.x * 2 - contentSize.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: extraSpace = 0; break; + case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break; + default: break; + } + currentElementTreeNode->nextChildOffset.x += extraSpace; + } else { + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width); + contentSize.height += childElement->dimensions.height; + } + contentSize.height += (float)(CLAY__MAX(currentElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); + float extraSpace = currentElement->dimensions.height - (float)layoutConfig->padding.y * 2 - contentSize.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: extraSpace = 0; break; + case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; + default: break; + } + currentElementTreeNode->nextChildOffset.y += extraSpace; + } + + if (scrollContainerData) { + scrollContainerData->contentSize = CLAY__INIT(Clay_Dimensions) { contentSize.width + layoutConfig->padding.x * 2, contentSize.height + layoutConfig->padding.y * 2 }; + } + } + } + else { + // DFS is returning upwards backwards + bool closeScrollElement = false; + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER)) { + closeScrollElement = true; + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (mapping->layoutElement == currentElement) { + if (scrollConfig->horizontal) { scrollOffset.x = mapping->scrollPosition.x; } + if (scrollConfig->vertical) { scrollOffset.y = mapping->scrollPosition.y; } + if (Clay__externalScrollHandlingEnabled) { + scrollOffset = CLAY__INIT(Clay_Vector2) {0}; + } + break; + } + } + } + + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER)) { + Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); + Clay_BoundingBox currentElementBoundingBox = currentElementData->boundingBox; + + // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow + if (!Clay__ElementIsOffscreen(¤tElementBoundingBox)) { + Clay_BorderElementConfig *borderConfig = Clay__FindElementConfigWithType(currentElement, CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER).borderElementConfig; + Clay_RenderCommand renderCommand = { + .boundingBox = currentElementBoundingBox, + .config = { .borderElementConfig = borderConfig }, + .id = Clay__RehashWithNumber(currentElement->id, 4), + .commandType = CLAY_RENDER_COMMAND_TYPE_BORDER, + }; + Clay__AddRenderCommand(renderCommand); + if (borderConfig->betweenChildren.width > 0 && borderConfig->betweenChildren.color.a > 0) { + Clay_RectangleElementConfig *rectangleConfig = Clay__StoreRectangleElementConfig(CLAY__INIT(Clay_RectangleElementConfig) {.color = borderConfig->betweenChildren.color}); + Clay_Vector2 borderOffset = { (float)layoutConfig->padding.x, (float)layoutConfig->padding.y }; + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + if (i > 0) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + borderOffset.x + scrollOffset.x, currentElementBoundingBox.y + scrollOffset.y, (float)borderConfig->betweenChildren.width, currentElement->dimensions.height }, + .config = { rectangleConfig }, + .id = Clay__RehashWithNumber(currentElement->id, 5 + i), + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }); + } + borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap / 2); + } + } else { + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + if (i > 0) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .boundingBox = { currentElementBoundingBox.x + scrollOffset.x, currentElementBoundingBox.y + borderOffset.y + scrollOffset.y, currentElement->dimensions.width, (float)borderConfig->betweenChildren.width }, + .config = { rectangleConfig }, + .id = Clay__RehashWithNumber(currentElement->id, 5 + i), + .commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE, + }); + } + borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap / 2); + } + } + } + } + } + // This exists because the scissor needs to end _after_ borders between elements + if (closeScrollElement) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { + .id = Clay__RehashWithNumber(currentElement->id, 11), + .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END, + }); + } + + dfsBuffer.length--; + continue; + } + + // Add children to the DFS buffer + if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + dfsBuffer.length += currentElement->childrenOrTextContent.children.length; + for (int i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { + Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, currentElement->childrenOrTextContent.children.elements[i]); + // Alignment along non layout axis + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.y; + float whiteSpaceAroundChild = currentElement->dimensions.height - (float)currentElement->layoutConfig->padding.y * 2 - childElement->dimensions.height; + switch (layoutConfig->childAlignment.y) { + case CLAY_ALIGN_Y_TOP: break; + case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break; + } + } else { + currentElementTreeNode->nextChildOffset.x = currentElement->layoutConfig->padding.x; + float whiteSpaceAroundChild = currentElement->dimensions.width - (float)currentElement->layoutConfig->padding.x * 2 - childElement->dimensions.width; + switch (layoutConfig->childAlignment.x) { + case CLAY_ALIGN_X_LEFT: break; + case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break; + case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break; + } + } + + Clay_Vector2 childPosition = { + currentElementTreeNode->position.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x, + currentElementTreeNode->position.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y, + }; + + // DFS buffer elements need to be added in reverse because stack traversal happens backwards + uint32_t newNodeIndex = dfsBuffer.length - 1 - i; + dfsBuffer.internalArray[newNodeIndex] = CLAY__INIT(Clay__LayoutElementTreeNode) { + .layoutElement = childElement, + .position = { childPosition.x, childPosition.y }, + .nextChildOffset = { .x = (float)childElement->layoutConfig->padding.x, .y = (float)childElement->layoutConfig->padding.y }, + }; + Clay__treeNodeVisited.internalArray[newNodeIndex] = false; + + // Update parent offsets + if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) { + currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap; + } else { + currentElementTreeNode->nextChildOffset.y += childElement->dimensions.height + (float)layoutConfig->childGap; + } + } + } + } + + if (root->clipElementId) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .id = Clay__RehashWithNumber(rootElement->id, 11), .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END }); + } + } +} + +void Clay__AttachId(Clay_ElementId elementId) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + openLayoutElement->id = elementId.id; + Clay__AddHashMapItem(elementId, openLayoutElement); + Clay__StringArray_Add(&Clay__layoutElementIdStrings, elementId.stringId); +} + +void Clay__AttachLayoutConfig(Clay_LayoutConfig *config) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay__GetOpenLayoutElement()->layoutConfig = config; +} +void Clay__AttachElementConfig(Clay_ElementConfigUnion config, Clay__ElementConfigType type) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + openLayoutElement->elementConfigs.length++; + Clay__ElementConfigArray_Add(&Clay__elementConfigBuffer, CLAY__INIT(Clay_ElementConfig) { .type = type, .config = config }); +} +Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY_LAYOUT_DEFAULT : Clay__LayoutConfigArray_Add(&Clay__layoutConfigs, config); } +Clay_RectangleElementConfig * Clay__StoreRectangleElementConfig(Clay_RectangleElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT : Clay__RectangleElementConfigArray_Add(&Clay__rectangleElementConfigs, config); } +Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__TEXT_ELEMENT_CONFIG_DEFAULT : Clay__TextElementConfigArray_Add(&Clay__textElementConfigs, config); } +Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT : Clay__ImageElementConfigArray_Add(&Clay__imageElementConfigs, config); } +Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT : Clay__FloatingElementConfigArray_Add(&Clay__floatingElementConfigs, config); } +Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT : Clay__CustomElementConfigArray_Add(&Clay__customElementConfigs, config); } +Clay_ScrollElementConfig * Clay__StoreScrollElementConfig(Clay_ScrollElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__SCROLL_ELEMENT_CONFIG_DEFAULT : Clay__ScrollElementConfigArray_Add(&Clay__scrollElementConfigs, config); } +Clay_BorderElementConfig * Clay__StoreBorderElementConfig(Clay_BorderElementConfig config) { return Clay__booleanWarnings.maxElementsExceeded ? &CLAY__BORDER_ELEMENT_CONFIG_DEFAULT : Clay__BorderElementConfigArray_Add(&Clay__borderElementConfigs, config); } + +#pragma region DebugTools +Clay_Color CLAY__DEBUGVIEW_COLOR_1 = {58, 56, 52, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_2 = {62, 60, 58, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_3 = {141, 133, 135, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_4 = {238, 226, 231, 255}; +Clay_Color CLAY__DEBUGVIEW_COLOR_SELECTED_ROW = {102, 80, 78, 255}; +const int CLAY__DEBUGVIEW_ROW_HEIGHT = 30; +const int CLAY__DEBUGVIEW_OUTER_PADDING = 10; +const int CLAY__DEBUGVIEW_INDENT_WIDTH = 16; +Clay_TextElementConfig Clay__DebugView_TextNameConfig = {.textColor = {238, 226, 231, 255}, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }; +Clay_LayoutConfig Clay__DebugView_ScrollViewItemLayoutConfig = {0}; + +CLAY__TYPEDEF(Clay__DebugElementConfigTypeLabelConfig, struct { + Clay_String label; + Clay_Color color; +}); + +Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Clay__ElementConfigType type) { + switch (type) { + case CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Rectangle"), {243,134,48,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Floating"), {250,105,0,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Scroll"), {242,196,90,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Border"), {108,91,123, 255} }; + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Custom"), {11,72,107,255} }; + default: break; + } + return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Error"), {0,0,0,255} }; +} + +CLAY__TYPEDEF(Clay__RenderDebugLayoutData, struct { + uint32_t rowCount; + uint32_t selectedElementRowIndex; +}); + +// Returns row count +Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialRootsLength, int32_t highlightedRowIndex) { + Clay__int32_tArray dfsBuffer = Clay__reusableElementIndexBuffer; + Clay__DebugView_ScrollViewItemLayoutConfig = CLAY__INIT(Clay_LayoutConfig) { .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT) }, .childGap = 6, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }}; + Clay__RenderDebugLayoutData layoutData = {0}; + + uint32_t highlightedElementId = 0; + + for (int rootIndex = 0; rootIndex < initialRootsLength; ++rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); + Clay__treeNodeVisited.internalArray[0] = false; + if (rootIndex > 0) { + CLAY(CLAY_IDI("Clay__DebugView_EmptyRowOuter", rootIndex), CLAY_LAYOUT({ .sizing = {.width = CLAY_SIZING_GROW({0})}, .padding = {CLAY__DEBUGVIEW_INDENT_WIDTH / 2, 0} })) { + CLAY(CLAY_IDI("Clay__DebugView_EmptyRow", rootIndex), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW({0}), .height = CLAY_SIZING_FIXED((float)CLAY__DEBUGVIEW_ROW_HEIGHT) }}), CLAY_BORDER({ .top = { .width = 1, .color = CLAY__DEBUGVIEW_COLOR_3 } })) {} + } + layoutData.rowCount++; + } + while (dfsBuffer.length > 0) { + uint32_t currentElementIndex = Clay__int32_tArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1); + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, (int)currentElementIndex); + if (Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && currentElement->childrenOrTextContent.children.length > 0) { + Clay__CloseElement(); + Clay__CloseElement(); + Clay__CloseElement(); + } + dfsBuffer.length--; + continue; + } + + if (highlightedRowIndex == layoutData.rowCount) { + if (Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + Clay__debugSelectedElementId = currentElement->id; + } + highlightedElementId = currentElement->id; + } + + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElementHashMapItem *currentElementData = Clay__GetHashMapItem(currentElement->id); + bool offscreen = Clay__ElementIsOffscreen(¤tElementData->boundingBox); + if (Clay__debugSelectedElementId == currentElement->id) { + layoutData.selectedElementRowIndex = layoutData.rowCount; + } + CLAY(CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), Clay__AttachLayoutConfig(&Clay__DebugView_ScrollViewItemLayoutConfig)) { + // Collapse icon / button + if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0)) { + CLAY(CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id), + CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }), + CLAY_BORDER_OUTSIDE_RADIUS(1, CLAY__DEBUGVIEW_COLOR_3, 4) + ) { + CLAY_TEXT((currentElementData && currentElementData->debugData->collapsed) ? CLAY_STRING("+") : CLAY_STRING("-"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } else { // Square dot for empty containers + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER } })) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(8), CLAY_SIZING_FIXED(8)} }), CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_3, .cornerRadius = CLAY_CORNER_RADIUS(2) })) {} + } + } + // Collisions and offscreen info + if (currentElementData) { + if (currentElementData->debugData->collision) { + CLAY(CLAY_LAYOUT({ .padding = { 8, 2 } }), CLAY_BORDER_OUTSIDE_RADIUS(1, (CLAY__INIT(Clay_Color){177, 147, 8, 255}), 4)) { + CLAY_TEXT(CLAY_STRING("Duplicate ID"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); + } + } + if (offscreen) { + CLAY(CLAY_LAYOUT({ .padding = { 8, 2 } }), CLAY_BORDER_OUTSIDE_RADIUS(1, CLAY__DEBUGVIEW_COLOR_3, 4)) { + CLAY_TEXT(CLAY_STRING("Offscreen"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 })); + } + } + } + Clay_String idString = Clay__layoutElementIdStrings.internalArray[currentElementIndex]; + if (idString.length > 0) { + CLAY_TEXT(idString, offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig); + } + for (uint32_t elementConfigIndex = 0; elementConfigIndex < currentElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(¤tElement->elementConfigs, elementConfigIndex); + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(elementConfig->type); + Clay_Color backgroundColor = config.color; + backgroundColor.a = 90; + CLAY(CLAY_LAYOUT({ .padding = { 8, 2 } }), CLAY_RECTANGLE({ .color = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4) }), CLAY_BORDER_OUTSIDE_RADIUS(1, config.color, 4)) { + CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = offscreen ? CLAY__DEBUGVIEW_COLOR_3 : CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + } + + // Render the text contents below the element as a non-interactive row + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + layoutData.rowCount++; + Clay__TextElementData *textElementData = currentElement->childrenOrTextContent.textElementData; + Clay_TextElementConfig *rawTextConfig = offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig; + CLAY(CLAY_LAYOUT({ .sizing = { .height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }), CLAY_RECTANGLE({0})) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_INDENT_WIDTH + 16), {0}} })) {} + CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); + CLAY_TEXT(textElementData->text.length > 40 ? (CLAY__INIT(Clay_String) { .length = 40, .chars = textElementData->text.chars }) : textElementData->text, rawTextConfig); + if (textElementData->text.length > 40) { + CLAY_TEXT(CLAY_STRING("..."), rawTextConfig); + } + CLAY_TEXT(CLAY_STRING("\""), rawTextConfig); + } + } else if (currentElement->childrenOrTextContent.children.length > 0) { + Clay__OpenElement(); + CLAY_LAYOUT({ .padding = { 8 , 0} }); + Clay__ElementPostConfiguration(); + Clay__OpenElement(); + CLAY_BORDER({ .left = { .width = 1, .color = CLAY__DEBUGVIEW_COLOR_3 }}); + Clay__ElementPostConfiguration(); + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED( CLAY__DEBUGVIEW_INDENT_WIDTH), {0}}, .childAlignment = { .x = CLAY_ALIGN_X_RIGHT } })) {} + Clay__OpenElement(); + CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM }); + Clay__ElementPostConfiguration(); + } + + layoutData.rowCount++; + if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (currentElementData && currentElementData->debugData->collapsed))) { + for (int i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } + } + } + + if (Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + Clay_ElementId collapseButtonId = Clay__HashString(CLAY_STRING("Clay__DebugView_CollapseElement"), 0, 0); + for (int i = (int)Clay__pointerOverIds.length - 1; i >= 0; i--) { + Clay_ElementId *elementId = Clay__ElementIdArray_Get(&Clay__pointerOverIds, i); + if (elementId->baseId == collapseButtonId.baseId) { + Clay_LayoutElementHashMapItem *highlightedItem = Clay__GetHashMapItem(elementId->offset); + highlightedItem->debugData->collapsed = !highlightedItem->debugData->collapsed; + break; + } + } + } + + if (highlightedElementId) { + CLAY(CLAY_ID("Clay__DebugView_ElementHighlight"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})} }), CLAY_FLOATING({ .zIndex = 65535, .parentId = highlightedElementId })) { + CLAY(CLAY_ID("Clay__DebugView_ElementHighlightRectangle"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})} }), CLAY_RECTANGLE({.color = Clay__debugViewHighlightColor })) {} + } + } + return layoutData; +} + +void Clay__RenderDebugLayoutSizing(Clay_SizingAxis sizing, Clay_TextElementConfig *infoTextConfig) { + Clay_String sizingLabel = CLAY_STRING("GROW"); + if (sizing.type == CLAY__SIZING_TYPE_FIT) { + sizingLabel = CLAY_STRING("FIT"); + } else if (sizing.type == CLAY__SIZING_TYPE_PERCENT) { + sizingLabel = CLAY_STRING("PERCENT"); + } + CLAY_TEXT(sizingLabel, infoTextConfig); + if (sizing.type == CLAY__SIZING_TYPE_GROW || sizing.type == CLAY__SIZING_TYPE_FIT) { + CLAY_TEXT(CLAY_STRING("("), infoTextConfig); + if (sizing.size.minMax.min != 0) { + CLAY_TEXT(CLAY_STRING("min: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(sizing.size.minMax.min), infoTextConfig); + if (sizing.size.minMax.max != CLAY__MAXFLOAT) { + CLAY_TEXT(CLAY_STRING(", "), infoTextConfig); + } + } + if (sizing.size.minMax.max != CLAY__MAXFLOAT) { + CLAY_TEXT(CLAY_STRING("max: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(sizing.size.minMax.max), infoTextConfig); + } + CLAY_TEXT(CLAY_STRING(")"), infoTextConfig); + } +} + +void Clay__RenderDebugViewElementConfigHeader(Clay_String elementId, Clay__ElementConfigType type) { + Clay__DebugElementConfigTypeLabelConfig config = Clay__DebugGetElementConfigTypeLabel(type); + Clay_Color backgroundColor = config.color; + backgroundColor.a = 90; + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT + 8)}, .padding = { .x = CLAY__DEBUGVIEW_OUTER_PADDING }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } })) { + CLAY(CLAY_LAYOUT({ .padding = { 8, 2 } }), CLAY_RECTANGLE({ .color = backgroundColor, .cornerRadius = CLAY_CORNER_RADIUS(4) }), CLAY_BORDER_OUTSIDE_RADIUS(1, config.color, 4)) { + CLAY_TEXT(config.label, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + CLAY(CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW({0}) } })) {} + CLAY_TEXT(elementId, CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE })); + } +} + +void Clay__RenderDebugViewColor(Clay_Color color, Clay_TextElementConfig *textConfig) { + CLAY(CLAY_LAYOUT({ .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(CLAY_STRING("{ r: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.r), textConfig); + CLAY_TEXT(CLAY_STRING(", g: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.g), textConfig); + CLAY_TEXT(CLAY_STRING(", b: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.b), textConfig); + CLAY_TEXT(CLAY_STRING(", a: "), textConfig); + CLAY_TEXT(Clay__IntToString(color.a), textConfig); + CLAY_TEXT(CLAY_STRING(" }"), textConfig); + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_FIXED(10), {0} } })) {} + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8)} }), CLAY_BORDER_OUTSIDE_RADIUS(1, CLAY__DEBUGVIEW_COLOR_4, 4)) { + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 8)} }), CLAY_RECTANGLE({ .color = color, .cornerRadius = CLAY_CORNER_RADIUS(4) })) {} + } + } +} + +void Clay__RenderDebugViewCornerRadius(Clay_CornerRadius cornerRadius, Clay_TextElementConfig *textConfig) { + CLAY(CLAY_LAYOUT({ .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(CLAY_STRING("{ topLeft: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.topLeft), textConfig); + CLAY_TEXT(CLAY_STRING(", topRight: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.topRight), textConfig); + CLAY_TEXT(CLAY_STRING(", bottomLeft: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.bottomLeft), textConfig); + CLAY_TEXT(CLAY_STRING(", bottomRight: "), textConfig); + CLAY_TEXT(Clay__IntToString(cornerRadius.bottomRight), textConfig); + CLAY_TEXT(CLAY_STRING(" }"), textConfig); + } +} + +void Clay__RenderDebugViewBorder(int index, Clay_Border border, Clay_TextElementConfig *textConfig) { + (void) index; + CLAY(CLAY_LAYOUT({ .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(CLAY_STRING("{ width: "), textConfig); + CLAY_TEXT(Clay__IntToString(border.width), textConfig); + CLAY_TEXT(CLAY_STRING(", color: "), textConfig); + Clay__RenderDebugViewColor(border.color, textConfig); + CLAY_TEXT(CLAY_STRING(" }"), textConfig); + } +} + +void HandleDebugViewCloseButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData) { + (void) elementId; (void) pointerInfo; (void) userData; + if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + Clay__debugModeEnabled = false; + } +} + +void Clay__RenderDebugView(void) { + Clay_ElementId closeButtonId = Clay__HashString(CLAY_STRING("Clay__DebugViewTopHeaderCloseButtonOuter"), 0, 0); + if (Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + for (uint32_t i = 0; i < Clay__pointerOverIds.length; ++i) { + Clay_ElementId *elementId = Clay__ElementIdArray_Get(&Clay__pointerOverIds, i); + if (elementId->id == closeButtonId.id) { + Clay__debugModeEnabled = false; + return; + } + } + } + + uint32_t initialRootsLength = Clay__layoutElementTreeRoots.length; + uint32_t initialElementsLength = Clay__layoutElements.length; + Clay_TextElementConfig *infoTextConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_TextElementConfig *infoTitleConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + Clay_ElementId scrollId = Clay__HashString(CLAY_STRING("Clay__DebugViewOuterScrollPane"), 0, 0); + float scrollYOffset = 0; + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; ++i) { + Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (scrollContainerData->elementId == scrollId.id) { + if (!Clay__externalScrollHandlingEnabled) { + scrollYOffset = scrollContainerData->scrollPosition.y; + } + break; + } + } + int32_t highlightedRow = Clay__pointerInfo.position.y < Clay__layoutDimensions.height - 300 + ? (int32_t)((Clay__pointerInfo.position.y - scrollYOffset) / (float)CLAY__DEBUGVIEW_ROW_HEIGHT) - 1 + : -1; + if (Clay__pointerInfo.position.x < Clay__layoutDimensions.width - (float)Clay__debugViewWidth) { + highlightedRow = -1; + } + Clay__RenderDebugLayoutData layoutData = {0}; + CLAY(CLAY_ID("Clay__DebugView"), + CLAY_FLOATING({ .parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id, .attachment = { .element = CLAY_ATTACH_POINT_LEFT_CENTER, .parent = CLAY_ATTACH_POINT_RIGHT_CENTER }}), + CLAY_LAYOUT({ .sizing = { CLAY_SIZING_FIXED((float)Clay__debugViewWidth) , CLAY_SIZING_FIXED(Clay__layoutDimensions.height) }, .layoutDirection = CLAY_TOP_TO_BOTTOM }), + CLAY_BORDER({ .bottom = { .width = 1, .color = CLAY__DEBUGVIEW_COLOR_3 }}) + ) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 0}, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }), CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_2 })) { + CLAY_TEXT(CLAY_STRING("Clay Debug Tools"), infoTextConfig); + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({0}), {0} } })) {} + // Close button + CLAY(CLAY_BORDER_OUTSIDE_RADIUS(1, (CLAY__INIT(Clay_Color){217,91,67,255}), 4), + CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT - 10)}, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }), + CLAY_RECTANGLE({ .color = {217,91,67,80} }), + Clay_OnHover(HandleDebugViewCloseButtonInteraction, 0) + ) { + CLAY_TEXT(CLAY_STRING("x"), CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16 })); + } + } + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(1)} }), CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_3 })) {} + CLAY(Clay__AttachId(scrollId), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})} }), CLAY_SCROLL({ .horizontal = true, .vertical = true })) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})}, .layoutDirection = CLAY_TOP_TO_BOTTOM }), CLAY_RECTANGLE({ .color = ((initialElementsLength + initialRootsLength) & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1 })) { + Clay_ElementId panelContentsId = Clay__HashString(CLAY_STRING("Clay__DebugViewPaneOuter"), 0, 0); + // Element list + CLAY(Clay__AttachId(panelContentsId), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})} }), CLAY_FLOATING({ .pointerCaptureMode = CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH })) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_GROW({0})}, .padding = {.x = CLAY__DEBUGVIEW_OUTER_PADDING }, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + layoutData = Clay__RenderDebugLayoutElementsList((int32_t)initialRootsLength, highlightedRow); + } + } + float contentWidth = Clay__GetHashMapItem(panelContentsId.id)->layoutElement->dimensions.width; + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(contentWidth), {0}}, .layoutDirection = CLAY_TOP_TO_BOTTOM })) {} + for (uint32_t i = 0; i < layoutData.rowCount; i++) { + Clay_Color rowColor = (i & 1) == 0 ? CLAY__DEBUGVIEW_COLOR_2 : CLAY__DEBUGVIEW_COLOR_1; + if (i == layoutData.selectedElementRowIndex) { + rowColor = CLAY__DEBUGVIEW_COLOR_SELECTED_ROW; + } + if (i == highlightedRow) { + rowColor.r *= 1.25f; + rowColor.g *= 1.25f; + rowColor.b *= 1.25f; + } + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }), CLAY_RECTANGLE({ .color = rowColor })) {} + } + } + } + CLAY(CLAY_LAYOUT({ .sizing = {.width = CLAY_SIZING_GROW({0}), .height = CLAY_SIZING_FIXED(1)} }), CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_3 })) {} + if (Clay__debugSelectedElementId != 0) { + Clay_LayoutElementHashMapItem *selectedItem = Clay__GetHashMapItem(Clay__debugSelectedElementId); + CLAY( + CLAY_SCROLL({ .vertical = true }), + CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(300)}, .layoutDirection = CLAY_TOP_TO_BOTTOM }), + CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_2 }), + CLAY_BORDER({ .betweenChildren = { .width = 1, .color = CLAY__DEBUGVIEW_COLOR_3 }}) + ) { + CLAY(CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT + 8)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 0}, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(CLAY_STRING("Layout Config"), infoTextConfig); + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({0}), {0} } })) {} + if (selectedItem->elementId.stringId.length != 0) { + CLAY_TEXT(selectedItem->elementId.stringId, infoTitleConfig); + if (selectedItem->elementId.offset != 0) { + CLAY_TEXT(CLAY_STRING(" ("), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->elementId.offset), infoTitleConfig); + CLAY_TEXT(CLAY_STRING(")"), infoTitleConfig); + } + } + } + // Clay_LayoutConfig debug info + CLAY(CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .boundingBox + CLAY_TEXT(CLAY_STRING("Bounding Box"), infoTitleConfig); + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(selectedItem->boundingBox.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .layoutDirection + CLAY_TEXT(CLAY_STRING("Layout Direction"), infoTitleConfig); + Clay_LayoutConfig *layoutConfig = selectedItem->layoutElement->layoutConfig; + CLAY_TEXT(layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM ? CLAY_STRING("TOP_TO_BOTTOM") : CLAY_STRING("LEFT_TO_RIGHT"), infoTextConfig); + // .sizing + CLAY_TEXT(CLAY_STRING("Sizing"), infoTitleConfig); + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("width: "), infoTextConfig); + Clay__RenderDebugLayoutSizing(layoutConfig->sizing.width, infoTextConfig); + } + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("height: "), infoTextConfig); + Clay__RenderDebugLayoutSizing(layoutConfig->sizing.height, infoTextConfig); + } + // .padding + CLAY_TEXT(CLAY_STRING("Padding"), infoTitleConfig); + CLAY(CLAY_ID("Clay__DebugViewElementInfoPadding")) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->padding.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .childGap + CLAY_TEXT(CLAY_STRING("Child Gap"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(layoutConfig->childGap), infoTextConfig); + // .childAlignment + CLAY_TEXT(CLAY_STRING("Child Alignment"), infoTitleConfig); + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + Clay_String alignX = CLAY_STRING("LEFT"); + if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_CENTER) { + alignX = CLAY_STRING("CENTER"); + } else if (layoutConfig->childAlignment.x == CLAY_ALIGN_X_RIGHT) { + alignX = CLAY_STRING("RIGHT"); + } + CLAY_TEXT(alignX, infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + Clay_String alignY = CLAY_STRING("TOP"); + if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_CENTER) { + alignY = CLAY_STRING("CENTER"); + } else if (layoutConfig->childAlignment.y == CLAY_ALIGN_Y_BOTTOM) { + alignY = CLAY_STRING("BOTTOM"); + } + CLAY_TEXT(alignY, infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + } + for (uint32_t elementConfigIndex = 0; elementConfigIndex < selectedItem->layoutElement->elementConfigs.length; ++elementConfigIndex) { + Clay_ElementConfig *elementConfig = Clay__ElementConfigArraySlice_Get(&selectedItem->layoutElement->elementConfigs, elementConfigIndex); + Clay__RenderDebugViewElementConfigHeader(selectedItem->elementId.stringId, elementConfig->type); + switch (elementConfig->type) { + case CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE: { + Clay_RectangleElementConfig *rectangleConfig = elementConfig->config.rectangleElementConfig; + CLAY(CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .color + CLAY_TEXT(CLAY_STRING("Color"), infoTitleConfig); + Clay__RenderDebugViewColor(rectangleConfig->color, infoTextConfig); + // .cornerRadius + CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); + Clay__RenderDebugViewCornerRadius(rectangleConfig->cornerRadius, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_TEXT: { + Clay_TextElementConfig *textConfig = elementConfig->config.textElementConfig; + CLAY(CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .fontSize + CLAY_TEXT(CLAY_STRING("Font Size"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontSize), infoTextConfig); + // .fontId + CLAY_TEXT(CLAY_STRING("Font ID"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->fontId), infoTextConfig); + // .lineHeight + CLAY_TEXT(CLAY_STRING("Line Height"), infoTitleConfig); + CLAY_TEXT(textConfig->lineHeight == 0 ? CLAY_STRING("auto") : Clay__IntToString(textConfig->lineHeight), infoTextConfig); + // .letterSpacing + CLAY_TEXT(CLAY_STRING("Letter Spacing"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(textConfig->letterSpacing), infoTextConfig); + // .lineSpacing + CLAY_TEXT(CLAY_STRING("Wrap Mode"), infoTitleConfig); + Clay_String wrapMode = CLAY_STRING("WORDS"); + if (textConfig->wrapMode == CLAY_TEXT_WRAP_NONE) { + wrapMode = CLAY_STRING("NONE"); + } else if (textConfig->wrapMode == CLAY_TEXT_WRAP_NEWLINES) { + wrapMode = CLAY_STRING("NEWLINES"); + } + CLAY_TEXT(wrapMode, infoTextConfig); + // .textColor + CLAY_TEXT(CLAY_STRING("Text Color"), infoTitleConfig); + Clay__RenderDebugViewColor(textConfig->textColor, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { + Clay_ImageElementConfig *imageConfig = elementConfig->config.imageElementConfig; + CLAY(CLAY_ID("Clay__DebugViewElementInfoImageBody"), CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .sourceDimensions + CLAY_TEXT(CLAY_STRING("Source Dimensions"), infoTitleConfig); + CLAY(CLAY_ID("Clay__DebugViewElementInfoImageDimensions")) { + CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(imageConfig->sourceDimensions.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(imageConfig->sourceDimensions.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // Image Preview + CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); + CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = imageConfig->sourceDimensions.width }), {0} }}), Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = imageConfig }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) {} + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER: { + Clay_ScrollElementConfig *scrollConfig = elementConfig->config.scrollElementConfig; + CLAY(CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .vertical + CLAY_TEXT(CLAY_STRING("Vertical"), infoTitleConfig); + CLAY_TEXT(scrollConfig->vertical ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + // .horizontal + CLAY_TEXT(CLAY_STRING("Horizontal"), infoTitleConfig); + CLAY_TEXT(scrollConfig->horizontal ? CLAY_STRING("true") : CLAY_STRING("false") , infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER: { + Clay_FloatingElementConfig *floatingConfig = elementConfig->config.floatingElementConfig; + CLAY(CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .offset + CLAY_TEXT(CLAY_STRING("Offset"), infoTitleConfig); + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("{ x: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.x), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", y: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->offset.y), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .expand + CLAY_TEXT(CLAY_STRING("Expand"), infoTitleConfig); + CLAY(CLAY_LAYOUT({0})) { + CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.width), infoTextConfig); + CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->expand.height), infoTextConfig); + CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); + } + // .zIndex + CLAY_TEXT(CLAY_STRING("z-index"), infoTitleConfig); + CLAY_TEXT(Clay__IntToString(floatingConfig->zIndex), infoTextConfig); + // .parentId + CLAY_TEXT(CLAY_STRING("Parent"), infoTitleConfig); + Clay_LayoutElementHashMapItem *hashItem = Clay__GetHashMapItem(floatingConfig->parentId); + CLAY_TEXT(hashItem->elementId.stringId, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER: { + Clay_BorderElementConfig *borderConfig = elementConfig->config.borderElementConfig; + CLAY(CLAY_ID("Clay__DebugViewElementInfoBorderBody"), CLAY_LAYOUT({ .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 8}, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM })) { + // .left + CLAY_TEXT(CLAY_STRING("Left Border"), infoTitleConfig); + Clay__RenderDebugViewBorder(1, borderConfig->left, infoTextConfig); + // .right + CLAY_TEXT(CLAY_STRING("Right Border"), infoTitleConfig); + Clay__RenderDebugViewBorder(2, borderConfig->right, infoTextConfig); + // .top + CLAY_TEXT(CLAY_STRING("Top Border"), infoTitleConfig); + Clay__RenderDebugViewBorder(3, borderConfig->top, infoTextConfig); + // .bottom + CLAY_TEXT(CLAY_STRING("Bottom Border"), infoTitleConfig); + Clay__RenderDebugViewBorder(4, borderConfig->bottom, infoTextConfig); + // .betweenChildren + CLAY_TEXT(CLAY_STRING("Border Between Children"), infoTitleConfig); + Clay__RenderDebugViewBorder(5, borderConfig->betweenChildren, infoTextConfig); + // .cornerRadius + CLAY_TEXT(CLAY_STRING("Corner Radius"), infoTitleConfig); + Clay__RenderDebugViewCornerRadius(borderConfig->cornerRadius, infoTextConfig); + } + break; + } + case CLAY__ELEMENT_CONFIG_TYPE_CUSTOM: + default: break; + } + } + } + } else { + CLAY(CLAY_ID("Clay__DebugViewWarningsScrollPane"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW({0}), CLAY_SIZING_FIXED(300)}, .childGap = 6, .layoutDirection = CLAY_TOP_TO_BOTTOM }), CLAY_SCROLL({ .horizontal = true, .vertical = true }), CLAY_RECTANGLE({ .color = CLAY__DEBUGVIEW_COLOR_2 })) { + Clay_TextElementConfig *warningConfig = CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_4, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }); + CLAY(CLAY_ID("Clay__DebugViewWarningItemHeader"), CLAY_LAYOUT({ .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 0}, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(CLAY_STRING("Warnings"), warningConfig); + } + CLAY(CLAY_ID("Clay__DebugViewWarningsTopBorder"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW({0}), .height = CLAY_SIZING_FIXED(1)} }), CLAY_RECTANGLE({ .color = {200, 200, 200, 255} })) {} + int previousWarningsLength = (int)Clay_warnings.length; + for (int i = 0; i < previousWarningsLength; i++) { + Clay__Warning warning = Clay_warnings.internalArray[i]; + CLAY(CLAY_IDI("Clay__DebugViewWarningItem", i), CLAY_LAYOUT({ .sizing = {.height = CLAY_SIZING_FIXED(CLAY__DEBUGVIEW_ROW_HEIGHT)}, .padding = {CLAY__DEBUGVIEW_OUTER_PADDING, 0}, .childGap = 8, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} })) { + CLAY_TEXT(warning.baseMessage, warningConfig); + if (warning.dynamicMessage.length > 0) { + CLAY_TEXT(warning.dynamicMessage, warningConfig); + } + } + } + } + } + } +} +#pragma endregion + +// PUBLIC API FROM HERE --------------------------------------- + +CLAY_WASM_EXPORT("Clay_MinMemorySize") +uint32_t Clay_MinMemorySize(void) { + Clay_Arena fakeArena = { .capacity = SIZE_MAX }; + Clay__InitializePersistentMemory(&fakeArena); + Clay__InitializeEphemeralMemory(&fakeArena); + return fakeArena.nextAllocation; +} + +CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory") +Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset) { + Clay_Arena arena = { + .capacity = capacity, + .memory = (char *)offset + }; + return arena; +} + +#ifndef CLAY_WASM +void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config)) { + Clay__MeasureText = measureTextFunction; +} +void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId)) { + Clay__QueryScrollOffset = queryScrollOffsetFunction; +} +#endif + +CLAY_WASM_EXPORT("Clay_SetLayoutDimensions") +void Clay_SetLayoutDimensions(Clay_Dimensions dimensions) { + Clay__layoutDimensions = dimensions; +} + +CLAY_WASM_EXPORT("Clay_SetPointerState") +void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay__pointerInfo.position = position; + Clay__pointerOverIds.length = 0; + Clay__int32_tArray dfsBuffer = Clay__layoutElementChildrenBuffer; + for (int rootIndex = Clay__layoutElementTreeRoots.length - 1; rootIndex >= 0; --rootIndex) { + dfsBuffer.length = 0; + Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex); + Clay__int32_tArray_Add(&dfsBuffer, (int32_t)root->layoutElementIndex); + Clay__treeNodeVisited.internalArray[0] = false; + bool found = false; + while (dfsBuffer.length > 0) { + if (Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) { + dfsBuffer.length--; + continue; + } + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true; + Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, Clay__int32_tArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1)); + Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great + Clay_BoundingBox elementBox = mapItem->boundingBox; + elementBox.x -= root->pointerOffset.x; + elementBox.y -= root->pointerOffset.y; + if (mapItem) { + if ((Clay__PointIsInsideRect(position, elementBox))) { + if (mapItem->onHoverFunction) { + mapItem->onHoverFunction(mapItem->elementId, Clay__pointerInfo, mapItem->hoverFunctionUserData); + } + Clay__ElementIdArray_Add(&Clay__pointerOverIds, mapItem->elementId); + found = true; + } + if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) { + dfsBuffer.length--; + continue; + } + for (int i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) { + Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]); + Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked + } + } else { + dfsBuffer.length--; + } + } + + Clay_LayoutElement *rootElement = Clay_LayoutElementArray_Get(&Clay__layoutElements, root->layoutElementIndex); + if (found && Clay__ElementHasConfig(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER) && + Clay__FindElementConfigWithType(rootElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER).floatingElementConfig->pointerCaptureMode == CLAY_POINTER_CAPTURE_MODE_CAPTURE) { + break; + } + } + + if (isPointerDown) { + if (Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + Clay__pointerInfo.state = CLAY_POINTER_DATA_PRESSED; + } else if (Clay__pointerInfo.state != CLAY_POINTER_DATA_PRESSED) { + Clay__pointerInfo.state = CLAY_POINTER_DATA_PRESSED_THIS_FRAME; + } + } else { + if (Clay__pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME) { + Clay__pointerInfo.state = CLAY_POINTER_DATA_RELEASED; + } else if (Clay__pointerInfo.state != CLAY_POINTER_DATA_RELEASED) { + Clay__pointerInfo.state = CLAY_POINTER_DATA_RELEASED_THIS_FRAME; + } + } +} + +CLAY_WASM_EXPORT("Clay_Initialize") +void Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { + Clay__internalArena = arena; + Clay__InitializePersistentMemory(&Clay__internalArena); + Clay__InitializeEphemeralMemory(&Clay__internalArena); + for (uint32_t i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) { + Clay__layoutElementsHashMap.internalArray[i] = -1; + } + for (uint32_t i = 0; i < Clay__measureTextHashMap.capacity; ++i) { + Clay__measureTextHashMap.internalArray[i] = 0; + } + Clay__measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element" + Clay__layoutDimensions = layoutDimensions; + Clay__errorHandler = errorHandler; +} + +CLAY_WASM_EXPORT("Clay_UpdateScrollContainers") +void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime) { + bool isPointerActive = enableDragScrolling && (Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED || Clay__pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME); + // Don't apply scroll events to ancestors of the inner element + int32_t highestPriorityElementIndex = -1; + Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL; + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; i++) { + Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (!scrollData->openThisFrame) { + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i); + continue; + } + scrollData->openThisFrame = false; + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId); + // Element isn't rendered this frame but scroll offset has been retained + if (!hashMapItem) { + Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i); + continue; + } + + // Touch / click is released + if (!isPointerActive && scrollData->pointerScrollActive) { + float xDiff = scrollData->scrollPosition.x - scrollData->scrollOrigin.x; + if (xDiff < -10 || xDiff > 10) { + scrollData->scrollMomentum.x = (scrollData->scrollPosition.x - scrollData->scrollOrigin.x) / (scrollData->momentumTime * 25); + } + float yDiff = scrollData->scrollPosition.y - scrollData->scrollOrigin.y; + if (yDiff < -10 || yDiff > 10) { + scrollData->scrollMomentum.y = (scrollData->scrollPosition.y - scrollData->scrollOrigin.y) / (scrollData->momentumTime * 25); + } + scrollData->pointerScrollActive = false; + + scrollData->pointerOrigin = CLAY__INIT(Clay_Vector2){0,0}; + scrollData->scrollOrigin = CLAY__INIT(Clay_Vector2){0,0}; + scrollData->momentumTime = 0; + } + + // Apply existing momentum + scrollData->scrollPosition.x += scrollData->scrollMomentum.x; + scrollData->scrollMomentum.x *= 0.95f; + bool scrollOccurred = scrollDelta.x != 0 || scrollDelta.y != 0; + if ((scrollData->scrollMomentum.x > -0.1f && scrollData->scrollMomentum.x < 0.1f) || scrollOccurred) { + scrollData->scrollMomentum.x = 0; + } + scrollData->scrollPosition.x = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.x, -(CLAY__MAX(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width, 0))), 0); + + scrollData->scrollPosition.y += scrollData->scrollMomentum.y; + scrollData->scrollMomentum.y *= 0.95f; + if ((scrollData->scrollMomentum.y > -0.1f && scrollData->scrollMomentum.y < 0.1f) || scrollOccurred) { + scrollData->scrollMomentum.y = 0; + } + scrollData->scrollPosition.y = CLAY__MIN(CLAY__MAX(scrollData->scrollPosition.y, -(CLAY__MAX(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height, 0))), 0); + + for (uint32_t j = 0; j < Clay__pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps + if (scrollData->layoutElement->id == Clay__ElementIdArray_Get(&Clay__pointerOverIds, j)->id) { + highestPriorityElementIndex = j; + highestPriorityScrollData = scrollData; + } + } + } + + if (highestPriorityElementIndex > -1 && highestPriorityScrollData) { + Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement; + Clay_ScrollElementConfig *scrollConfig = Clay__FindElementConfigWithType(scrollElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig; + bool canScrollVertically = scrollConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height; + bool canScrollHorizontally = scrollConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width; + // Handle wheel scroll + if (canScrollVertically) { + highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollPosition.y + scrollDelta.y * 10; + } + if (canScrollHorizontally) { + highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollPosition.x + scrollDelta.x * 10; + } + // Handle click / touch scroll + if (isPointerActive) { + highestPriorityScrollData->scrollMomentum = CLAY__INIT(Clay_Vector2){0}; + if (!highestPriorityScrollData->pointerScrollActive) { + highestPriorityScrollData->pointerOrigin = Clay__pointerInfo.position; + highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; + highestPriorityScrollData->pointerScrollActive = true; + } else { + float scrollDeltaX = 0, scrollDeltaY = 0; + if (canScrollHorizontally) { + float oldXScrollPosition = highestPriorityScrollData->scrollPosition.x; + highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollOrigin.x + (Clay__pointerInfo.position.x - highestPriorityScrollData->pointerOrigin.x); + highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - highestPriorityScrollData->boundingBox.width)); + scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldXScrollPosition; + } + if (canScrollVertically) { + float oldYScrollPosition = highestPriorityScrollData->scrollPosition.y; + highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollOrigin.y + (Clay__pointerInfo.position.y - highestPriorityScrollData->pointerOrigin.y); + highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height)); + scrollDeltaY = highestPriorityScrollData->scrollPosition.y - oldYScrollPosition; + } + if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.15f) { + highestPriorityScrollData->momentumTime = 0; + highestPriorityScrollData->pointerOrigin = Clay__pointerInfo.position; + highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition; + } else { + highestPriorityScrollData->momentumTime += deltaTime; + } + } + } + // Clamp any changes to scroll position to the maximum size of the contents + if (canScrollVertically) { + highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - scrollElement->dimensions.height)); + } + if (canScrollHorizontally) { + highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - scrollElement->dimensions.width)); + } + } +} + +CLAY_WASM_EXPORT("Clay_BeginLayout") +void Clay_BeginLayout(void) { + Clay__InitializeEphemeralMemory(&Clay__internalArena); + Clay__generation++; + Clay__dynamicElementIndex = 0; + // Set up the root container that covers the entire window + Clay_Dimensions rootDimensions = {Clay__layoutDimensions.width, Clay__layoutDimensions.height}; + if (Clay__debugModeEnabled) { + rootDimensions.width -= (float)Clay__debugViewWidth; + } + Clay__booleanWarnings.maxElementsExceeded = false; + Clay__booleanWarnings.maxTextMeasureCacheExceeded = false; + Clay__booleanWarnings.maxRenderCommandsExceeded = false; + Clay__OpenElement(); + CLAY_ID("Clay__RootContainer"); + CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED((rootDimensions.width)), CLAY_SIZING_FIXED(rootDimensions.height)} }); + Clay__ElementPostConfiguration(); + Clay__int32_tArray_Add(&Clay__openLayoutElementStack, 0); + Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) { .layoutElementIndex = 0 }); +} + +Clay_TextElementConfig Clay__DebugView_ErrorTextConfig = {.textColor = {255, 0, 0, 255}, .fontSize = 16, .wrapMode = CLAY_TEXT_WRAP_NONE }; + +CLAY_WASM_EXPORT("Clay_EndLayout") +Clay_RenderCommandArray Clay_EndLayout(void) { + Clay__CloseElement(); + if (Clay__debugModeEnabled) { + Clay__warningsEnabled = false; + Clay__RenderDebugView(); + Clay__warningsEnabled = true; + } + if (Clay__booleanWarnings.maxElementsExceeded) { + Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { .boundingBox = { Clay__layoutDimensions.width / 2 - 59 * 4, Clay__layoutDimensions.height / 2, 0, 0 }, .config = { .textElementConfig = &Clay__DebugView_ErrorTextConfig }, .text = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"), .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT }); + } else { + Clay__CalculateFinalLayout(); + } + return Clay__renderCommands; +} + +CLAY_WASM_EXPORT("Clay_GetElementId") +Clay_ElementId Clay_GetElementId(Clay_String idString) { + return Clay__HashString(idString, 0, 0); +} + +CLAY_WASM_EXPORT("Clay_GetElementIdWithIndex") +Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index) { + return Clay__HashString(idString, index, 0); +} + +bool Clay_Hovered(void) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return false; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + // If the element has no id attached at this point, we need to generate one + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + for (uint32_t i = 0; i < Clay__pointerOverIds.length; ++i) { + if (Clay__ElementIdArray_Get(&Clay__pointerOverIds, i)->id == openLayoutElement->id) { + return true; + } + } + return false; +} + +void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData), intptr_t userData) { + if (Clay__booleanWarnings.maxElementsExceeded) { + return; + } + Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); + if (openLayoutElement->id == 0) { + Clay__GenerateIdForAnonymousElement(openLayoutElement); + } + Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(openLayoutElement->id); + hashMapItem->onHoverFunction = onHoverFunction; + hashMapItem->hoverFunctionUserData = userData; +} + +CLAY_WASM_EXPORT("Clay_PointerOver") +bool Clay_PointerOver(Clay_ElementId elementId) { // TODO return priority for separating multiple results + for (uint32_t i = 0; i < Clay__pointerOverIds.length; ++i) { + if (Clay__ElementIdArray_Get(&Clay__pointerOverIds, i)->id == elementId.id) { + return true; + } + } + return false; +} + +CLAY_WASM_EXPORT("Clay_GetScrollContainerData") +Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) { + for (uint32_t i = 0; i < Clay__scrollContainerDatas.length; ++i) { + Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i); + if (scrollContainerData->elementId == id.id) { + return CLAY__INIT(Clay_ScrollContainerData) { + .scrollPosition = &scrollContainerData->scrollPosition, + .scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height }, + .contentDimensions = scrollContainerData->contentSize, + .config = *Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER).scrollElementConfig, + .found = true + }; + } + } + return CLAY__INIT(Clay_ScrollContainerData) {0}; +} + +CLAY_WASM_EXPORT("Clay_SetDebugModeEnabled") +void Clay_SetDebugModeEnabled(bool enabled) { + Clay__debugModeEnabled = enabled; +} + +CLAY_WASM_EXPORT("Clay_IsDebugModeEnabled") +bool Clay_IsDebugModeEnabled(void) { + return Clay__debugModeEnabled; +} + +CLAY_WASM_EXPORT("Clay_SetCullingEnabled") +void Clay_SetCullingEnabled(bool enabled) { + Clay__disableCulling = !enabled; +} + +CLAY_WASM_EXPORT("Clay_SetExternalScrollHandlingEnabled") +void Clay_SetExternalScrollHandlingEnabled(bool enabled) { + Clay__externalScrollHandlingEnabled = enabled; +} + +CLAY_WASM_EXPORT("Clay_SetMaxElementCount") +void Clay_SetMaxElementCount(uint32_t maxElementCount) { + Clay__maxElementCount = maxElementCount; +} + +CLAY_WASM_EXPORT("Clay_SetMaxMeasureTextCacheWordCount") +void Clay_SetMaxMeasureTextCacheWordCount(uint32_t maxMeasureTextCacheWordCount) { + Clay__maxMeasureTextCacheWordCount = maxMeasureTextCacheWordCount; +} + +#endif // CLAY_IMPLEMENTATION + +/* +LICENSE +zlib/libpng license + +Copyright (c) 2024 Nic Barker + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the +use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ diff --git a/vendor/clay_renderer_raylib.c b/vendor/clay_renderer_raylib.c new file mode 100644 index 0000000..0fe545a --- /dev/null +++ b/vendor/clay_renderer_raylib.c @@ -0,0 +1,233 @@ +#include "raylib.h" +#include "raymath.h" +#include "stdint.h" +#include "string.h" +#include "stdio.h" +#include "stdlib.h" + +#define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height } +#define CLAY_COLOR_TO_RAYLIB_COLOR(color) (Color) { .r = (unsigned char)roundf(color.r), .g = (unsigned char)roundf(color.g), .b = (unsigned char)roundf(color.b), .a = (unsigned char)roundf(color.a) } + +typedef struct +{ + uint32_t fontId; + Font font; +} Raylib_Font; + +Raylib_Font Raylib_fonts[10]; +Camera Raylib_camera; + +typedef enum +{ + CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL +} CustomLayoutElementType; + +typedef struct +{ + Model model; + float scale; + Vector3 position; + Matrix rotation; +} CustomLayoutElement_3DModel; + +typedef struct +{ + CustomLayoutElementType type; + union { + CustomLayoutElement_3DModel model; + }; +} CustomLayoutElement; + +// Get a ray trace from the screen position (i.e mouse) within a specific section of the screen +Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int screenWidth, int screenHeight, float zDistance) +{ + Ray ray = { 0 }; + + // Calculate normalized device coordinates + // NOTE: y value is negative + float x = (2.0f*position.x)/(float)screenWidth - 1.0f; + float y = 1.0f - (2.0f*position.y)/(float)screenHeight; + float z = 1.0f; + + // Store values in a vector + Vector3 deviceCoords = { x, y, z }; + + // Calculate view matrix from camera look at + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); + + Matrix matProj = MatrixIdentity(); + + if (camera.projection == CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)screenWidth/(double)screenHeight), 0.01f, zDistance); + } + else if (camera.projection == CAMERA_ORTHOGRAPHIC) + { + double aspect = (double)screenWidth/(double)screenHeight; + double top = camera.fovy/2.0; + double right = top*aspect; + + // Calculate projection matrix from orthographic + matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); + } + + // Unproject far/near points + Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); + Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); + + // Calculate normalized direction vector + Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); + + ray.position = farPoint; + + // Apply calculated vectors to ray + ray.direction = direction; + + return ray; +} + +uint32_t measureCalls = 0; + +static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) { + measureCalls++; + // Measure string size for Font + Clay_Dimensions textSize = { 0 }; + + float maxTextWidth = 0.0f; + float lineTextWidth = 0; + + float textHeight = config->fontSize; + Font fontToUse = Raylib_fonts[config->fontId].font; + float scaleFactor = config->fontSize/(float)fontToUse.baseSize; + + for (int i = 0; i < text->length; ++i) + { + if (text->chars[i] == '\n') { + maxTextWidth = fmax(maxTextWidth, lineTextWidth); + lineTextWidth = 0; + continue; + } + 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); + } + + maxTextWidth = fmax(maxTextWidth, lineTextWidth); + + textSize.width = maxTextWidth * scaleFactor; + textSize.height = textHeight; + + return textSize; +} + +void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) { + SetConfigFlags(flags); + InitWindow(width, height, title); +// EnableEventWaiting(); +} + +void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands) +{ + measureCalls = 0; + for (int j = 0; j < renderCommands.length; j++) + { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); + Clay_BoundingBox boundingBox = renderCommand->boundingBox; + switch (renderCommand->commandType) + { + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator + Clay_String text = renderCommand->text; + char *cloned = (char *)malloc(text.length + 1); + memcpy(cloned, text.chars, text.length); + cloned[text.length] = '\0'; + Font fontToUse = Raylib_fonts[renderCommand->config.textElementConfig->fontId].font; + DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)renderCommand->config.textElementConfig->fontSize, (float)renderCommand->config.textElementConfig->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.textElementConfig->textColor)); + free(cloned); + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Texture2D imageTexture = *(Texture2D *)renderCommand->config.imageElementConfig->imageData; + DrawTextureEx( + imageTexture, + (Vector2){boundingBox.x, boundingBox.y}, + 0, + boundingBox.width / (float)imageTexture.width, + WHITE); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + BeginScissorMode((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height)); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + EndScissorMode(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig; + if (config->cornerRadius.topLeft > 0) { + float radius = (config->cornerRadius.topLeft * 2) / (float)((boundingBox.width > boundingBox.height) ? boundingBox.height : boundingBox.width); + DrawRectangleRounded((Rectangle) { boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height }, radius, 8, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + } else { + DrawRectangle(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderElementConfig *config = renderCommand->config.borderElementConfig; + // Left border + if (config->left.width > 0) { + DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->left.width, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->left.color)); + } + // Right border + if (config->right.width > 0) { + DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->right.width), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->right.width, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->right.color)); + } + // Top border + if (config->top.width > 0) { + DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->top.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); + } + // Bottom border + if (config->bottom.width > 0) { + DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->bottom.width), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->bottom.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); + } + if (config->cornerRadius.topLeft > 0) { + DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->top.width), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); + } + if (config->cornerRadius.topRight > 0) { + DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->top.width), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); + } + if (config->cornerRadius.bottomLeft > 0) { + DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->top.width), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); + } + if (config->cornerRadius.bottomRight > 0) { + DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->bottom.width), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand->config.customElementConfig->customData; + if (!customElement) continue; + switch (customElement->type) { + case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { + Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox; + float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f); + Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140); + BeginMode3D(Raylib_camera); + DrawModel(customElement->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE); // Draw 3d model with texture + EndMode3D(); + break; + } + default: break; + } + break; + } + default: { + printf("Error: unhandled render command."); + exit(1); + } + } + } +} diff --git a/vendor/raymath.h b/vendor/raymath.h new file mode 100644 index 0000000..e522113 --- /dev/null +++ b/vendor/raymath.h @@ -0,0 +1,2941 @@ +/********************************************************************************************** +* +* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* +* CONVENTIONS: +* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all +* math operations performed by the library consider the structure as it was column-major +* It is like transposed versions of the matrices are used for all the maths +* It benefits some functions making them cache-friendly and also avoids matrix +* transpositions sometimes required by OpenGL +* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" variable for return (except C++ operators) +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* - No compound literals used to make sure libray is compatible with C++ +* +* CONFIGURATION: +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) + #elif defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { + float v[3]; +} float3; + +typedef struct float16 { + float v[16]; +} float16; + +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +// Wrap input value from min to max +RMAPI float Wrap(float value, float min, float max) +{ + float result = value - (max - min)*floorf((value - min)/(max - min)); + + return result; +} + +// Check whether two given floats are almost equal +RMAPI int FloatEquals(float x, float y) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) +{ + float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = 0.0f; + + float dot = v1.x*v2.x + v1.y*v2.y; + float det = v1.x*v2.y - v1.y*v2.x; + + result = atan2f(det, dot); + + return result; +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) +{ + float result = 0.0f; + + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + result = -atan2f(end.y - start.y, end.x - start.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Transforms a Vector2 by a given Matrix +RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) +{ + Vector2 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = 0; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.x = v.x*cosres - v.y*sinres; + result.y = v.x*sinres + v.y*cosres; + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector2 Vector2Invert(Vector2 v) +{ + Vector2 result = { 1.0f/v.x, 1.0f/v.y }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) +{ + Vector2 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + + return result; +} + +// Clamp the magnitude of the vector between two min and max values +RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) +{ + Vector2 result = v; + + float length = (v.x*v.x) + (v.y*v.y); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector2Equals(Vector2 p, Vector2 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) +{ + Vector2 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = fabsf(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabsf(v.y) < min) + { + min = fabsf(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabsf(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = dx*dx + dy*dy + dz*dz; + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length != 0.0f) + { + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + } + + return result; +} + +//Calculate the projection of the vector v1 on to v2 +RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v2.x*mag; + result.y = v2.y*mag; + result.z = v2.z*mag; + + return result; +} + +//Calculate the rejection of the vector v1 on to v2 +RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v1.x - (v2.x*mag); + result.y = v1.y - (v2.y*mag); + result.z = v1.z - (v2.z*mag); + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Rotates a vector around an axis +RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) +{ + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + Vector3 result = v; + + // Vector3Normalize(axis); + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + angle /= 2.0f; + float a = sinf(angle); + float b = axis.x*a; + float c = axis.y*a; + float d = axis.z*a; + a = cosf(angle); + Vector3 w = { b, c, d }; + + // Vector3CrossProduct(w, v) + Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; + + // Vector3CrossProduct(w, wv) + Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; + + // Vector3Scale(wv, 2*a) + a *= 2; + wv.x *= a; + wv.y *= a; + wv.z *= a; + + // Vector3Scale(wwv, 2) + wwv.x *= 2; + wwv.y *= 2; + wwv.z *= 2; + + result.x += wv.x; + result.y += wv.y; + result.z += wv.z; + + result.x += wwv.x; + result.y += wwv.y; + result.z += wwv.z; + + return result; +} + +// Move Vector towards target +RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) +{ + Vector3 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float value = (dx*dx) + (dy*dy) + (dz*dz); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate cubic hermite interpolation between two vectors and their tangents +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) +{ + Vector3 result = { 0 }; + + float amountPow2 = amount*amount; + float amountPow3 = amount*amount*amount; + + result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; + result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; + result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: We are avoiding calling other raymath functions despite available +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unprojecte matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +// Invert the given vector +RMAPI Vector3 Vector3Invert(Vector3 v) +{ + Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) +{ + Vector3 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + result.z = fminf(max.z, fmaxf(min.z, v.z)); + + return result; +} + +// Clamp the magnitude of the vector between two values +RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) +{ + Vector3 result = v; + + float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector3Equals(Vector3 p, Vector3 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) +{ + Vector3 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y + v.z*n.z; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + v.z = r*v.z - (r*dot + d)*n.z; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector4 math +//---------------------------------------------------------------------------------- + +RMAPI Vector4 Vector4Zero(void) +{ + Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; + return result; +} + +RMAPI Vector4 Vector4One(void) +{ + Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; + return result; +} + +RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x + v2.x, + v1.y + v2.y, + v1.z + v2.z, + v1.w + v2.w + }; + return result; +} + +RMAPI Vector4 Vector4AddValue(Vector4 v, float add) +{ + Vector4 result = { + v.x + add, + v.y + add, + v.z + add, + v.w + add + }; + return result; +} + +RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x - v2.x, + v1.y - v2.y, + v1.z - v2.z, + v1.w - v2.w + }; + return result; +} + +RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) +{ + Vector4 result = { + v.x - add, + v.y - add, + v.z - add, + v.w - add + }; + return result; +} + +RMAPI float Vector4Length(Vector4 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + return result; +} + +RMAPI float Vector4LengthSqr(Vector4 v) +{ + float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); + return result; +} + +RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) +{ + float result = sqrtf( + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) +{ + float result = + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); + + return result; +} + +RMAPI Vector4 Vector4Scale(Vector4 v, float scale) +{ + Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; + return result; +} + +// Multiply vector by vector +RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; + return result; +} + +// Negate vector +RMAPI Vector4 Vector4Negate(Vector4 v) +{ + Vector4 result = { -v.x, -v.y, -v.z, -v.w }; + return result; +} + +// Divide vector by vector +RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; + return result; +} + +// Normalize provided vector +RMAPI Vector4 Vector4Normalize(Vector4 v) +{ + Vector4 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + result.z = v.z*ilength; + result.w = v.w*ilength; + } + + return result; +} + +// Get min value for each pair of components +RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + result.w = fminf(v1.w, v2.w); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + result.w = fmaxf(v1.w, v2.w); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) +{ + Vector4 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + result.w = v1.w + amount*(v2.w - v1.w); + + return result; +} + +// Move Vector towards target +RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) +{ + Vector4 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float dw = target.w - v.w; + float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + result.w = v.w + dw/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector4 Vector4Invert(Vector4 v) +{ + Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector4Equals(Vector4 p, Vector4 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = sinres; + result.m9 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = -sinres; + result.m8 = sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = sinres; + result.m4 = -sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateXYZ(Vector3 angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-angle.z); + float sinz = sinf(-angle.z); + float cosy = cosf(-angle.y); + float siny = sinf(-angle.y); + float cosx = cosf(-angle.x); + float sinx = sinf(-angle.x); + + result.m0 = cosz*cosy; + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); + + result.m4 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); + + result.m8 = -siny; + result.m9 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) +{ + Matrix result = { 0 }; + + float cz = cosf(angle.z); + float sz = sinf(angle.z); + float cy = cosf(angle.y); + float sy = sinf(angle.y); + float cx = cosf(angle.x); + float sx = sinf(angle.x); + + result.m0 = cz*cy; + result.m4 = cz*sy*sx - cx*sz; + result.m8 = sz*sx + cz*cx*sy; + result.m12 = 0; + + result.m1 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m9 = cx*sz*sy - cz*sx; + result.m13 = 0; + + result.m2 = -sy; + result.m6 = cy*sx; + result.m10 = cy*cx; + result.m14 = 0; + + result.m3 = 0; + result.m7 = 0; + result.m11 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + double top = nearPlane*tan(fovY*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)farPlane + (float)nearPlane)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; + + if (lengthSq != 0.0f) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabsf(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabsf(sinHalfTheta) < EPSILON) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) +{ + float t2 = t*t; + float t3 = t2*t; + float h00 = 2*t3 - 3*t2 + 1; + float h10 = t3 - 2*t2 + t; + float h01 = -2*t3 + 3*t2; + float h11 = t3 - t2; + + Quaternion p0 = QuaternionScale(q1, h00); + Quaternion m0 = QuaternionScale(outTangent1, h10); + Quaternion p1 = QuaternionScale(q2, h01); + Quaternion m1 = QuaternionScale(inTangent2, h11); + + Quaternion result = { 0 }; + + result = QuaternionAdd(p0, m0); + result = QuaternionAdd(result, p1); + result = QuaternionAdd(result, m1); + result = QuaternionNormalize(result); + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; + float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; + float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; + float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; + float mult = 0.25f/biggestVal; + + switch (biggestIndex) + { + case 0: + result.w = biggestVal; + result.x = (mat.m6 - mat.m9)*mult; + result.y = (mat.m8 - mat.m2)*mult; + result.z = (mat.m1 - mat.m4)*mult; + break; + case 1: + result.x = biggestVal; + result.w = (mat.m6 - mat.m9)*mult; + result.y = (mat.m1 + mat.m4)*mult; + result.z = (mat.m8 + mat.m2)*mult; + break; + case 2: + result.y = biggestVal; + result.w = (mat.m8 - mat.m2)*mult; + result.x = (mat.m1 + mat.m4)*mult; + result.z = (mat.m6 + mat.m9)*mult; + break; + case 3: + result.z = biggestVal; + result.w = (mat.m1 - mat.m4)*mult; + result.x = (mat.m8 + mat.m2)*mult; + result.y = (mat.m6 + mat.m9)*mult; + break; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: Angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (axisLength != 0.0f) + { + angle *= 0.5f; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(axis) + length = axisLength; + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabsf(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > EPSILON) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || + (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); + + return result; +} + +// Decompose a transformation matrix into its rotational, translational and scaling components +RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) +{ + // Extract translation. + translation->x = mat.m12; + translation->y = mat.m13; + translation->z = mat.m14; + + // Extract upper-left for determinant computation + const float a = mat.m0; + const float b = mat.m4; + const float c = mat.m8; + const float d = mat.m1; + const float e = mat.m5; + const float f = mat.m9; + const float g = mat.m2; + const float h = mat.m6; + const float i = mat.m10; + const float A = e*i - f*h; + const float B = f*g - d*i; + const float C = d*h - e*g; + + // Extract scale + const float det = a*A + b*B + c*C; + Vector3 abc = { a, b, c }; + Vector3 def = { d, e, f }; + Vector3 ghi = { g, h, i }; + + float scalex = Vector3Length(abc); + float scaley = Vector3Length(def); + float scalez = Vector3Length(ghi); + Vector3 s = { scalex, scaley, scalez }; + + if (det < 0) s = Vector3Negate(s); + + *scale = s; + + // Remove scale from the matrix if it is not close to zero + Matrix clone = mat; + if (!FloatEquals(det, 0)) + { + clone.m0 /= s.x; + clone.m4 /= s.x; + clone.m8 /= s.x; + clone.m1 /= s.y; + clone.m5 /= s.y; + clone.m9 /= s.y; + clone.m2 /= s.z; + clone.m6 /= s.z; + clone.m10 /= s.z; + + // Extract rotation + *rotation = QuaternionFromMatrix(clone); + } + else + { + // Set to identity if close to zero + *rotation = QuaternionIdentity(); + } +} + +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f / rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f / rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f / rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} +//------------------------------------------------------------------------------- +#endif // C++ operators + +#endif // RAYMATH_H