#define SDL_MAIN_USE_CALLBACKS #include #include #include #include #define CLAY_IMPLEMENTATION #include "clay/clay.h" #include "clay/clay_renderer_SDL3.c" #include "ui/clay_video_demo.c" #include #include static const Uint32 FONT_ID = 0; typedef struct app_state { SDL_Window *window; Clay_SDL3RendererData rendererData; ClayVideoDemo_Data demoData; ecs_world_t *world; } AppState; SDL_Surface *sample_image; static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { TTF_Font **fonts = userData; TTF_Font *font = fonts[config->fontId]; int width, height; if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); } return (Clay_Dimensions) { (float) width, (float) height }; } void HandleClayErrors(Clay_ErrorData errorData) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Clay Error: %s", errorData.errorText.chars); } SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { (void) argc; (void) argv; if (!TTF_Init()) { return SDL_APP_FAILURE; } AppState *state = SDL_calloc(1, sizeof(AppState)); if (!state) { return SDL_APP_FAILURE; } *appstate = state; if (!SDL_CreateWindowAndRenderer("Game Dev", 1280, 960, 0, &state->window, &state->rendererData.renderer)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetWindowResizable(state->window, true); state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer); if (!state->rendererData.textEngine) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *)); if (!state->rendererData.fonts) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError()); return SDL_APP_FAILURE; } TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); if (!font) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError()); return SDL_APP_FAILURE; } state->rendererData.fonts[FONT_ID] = font; sample_image = IMG_Load("resources/sample.png"); uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena clayMemory = (Clay_Arena) { .memory = SDL_malloc(totalMemorySize), .capacity = totalMemorySize }; int width, height; SDL_GetWindowSize(state->window, &width, &height); Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors }); Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts); Clay_SetDebugModeEnabled(true); state->demoData = ClayVideoDemo_Initialize(); ecs_world_t *world = ecs_init(); ECS_IMPORT(world, FlecsStats); ECS_IMPORT(world, FlecsScript); ecs_singleton_set(world, EcsRest, {0}); // Creates REST server on default port (27750) state->world = world; *appstate = state; return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { AppState *state = appstate; SDL_AppResult ret_val = SDL_APP_CONTINUE; switch (event->type) { case SDL_EVENT_QUIT: ret_val = SDL_APP_SUCCESS; break; case SDL_EVENT_WINDOW_RESIZED: Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 }); break; case SDL_EVENT_MOUSE_MOTION: Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y }, event->motion.state & SDL_BUTTON_LMASK); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y }, event->button.button == SDL_BUTTON_LEFT); break; case SDL_EVENT_MOUSE_WHEEL: Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f); break; case SDL_EVENT_KEY_DOWN: if (event->key.repeat) { break; } SDL_Log("Pressed key: %s\n", SDL_GetKeyName(event->key.key)); switch (event->key.scancode) { case SDL_SCANCODE_LCTRL: { ecs_world_t *world = state->world; ecs_entity_t e = ecs_entity(world, { .name = "Lily" }); SDL_Log("Spawned entity [%s]", ecs_get_name(world, e)); break; } case SDL_SCANCODE_X: { ecs_world_t *world = state->world; ecs_entity_t e = ecs_new(world); SDL_Log("Spawned unnamed entity"); break; } } break; case SDL_EVENT_KEY_UP: SDL_Log("Released key: %s\n", SDL_GetKeyName(event->key.key)); switch (event->key.scancode) { case SDL_SCANCODE_LCTRL: { ecs_world_t *world = state->world; ecs_entity_t e = ecs_lookup(world, "Lily"); SDL_Log("Entity [%s] is %s", ecs_get_name(world, e), ecs_is_alive(world, e) ? "alive" : "dead"); ecs_delete(world, e); SDL_Log("Entity is %s", ecs_is_alive(world, e) ? "alive" : "dead"); break; } } break; default: break; } return ret_val; } SDL_AppResult SDL_AppIterate(void *appstate) { AppState *state = appstate; ecs_progress(state->world, 0); Clay_RenderCommandArray render_commands = ClayVideoDemo_CreateLayout(&state->demoData); SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255); SDL_RenderClear(state->rendererData.renderer); SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands); SDL_RenderPresent(state->rendererData.renderer); return SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { (void) result; if (result != SDL_APP_SUCCESS) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run"); } AppState *state = appstate; if (state) { if (state->rendererData.renderer) SDL_DestroyRenderer(state->rendererData.renderer); if (state->window) SDL_DestroyWindow(state->window); if (state->rendererData.fonts) { for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) { TTF_CloseFont(state->rendererData.fonts[i]); } SDL_free(state->rendererData.fonts); } if (state->rendererData.textEngine) TTF_DestroyRendererTextEngine(state->rendererData.textEngine); ecs_fini(state->world); SDL_free(state); } TTF_Quit(); }