#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); } } } }