#define SDL_MAIN_USE_CALLBACKS #include #include #include #include #define CLAY_IMPLEMENTATION #include #include #include "ui/clay_video_demo.c" #include #include static const Uint32 FONT_ID = 0; typedef struct app_state { SDL_Window *window; SDL_Gamepad *gamepad; 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; SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "0"); if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK | SDL_INIT_SENSOR | SDL_INIT_HAPTIC)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL: %s", SDL_GetError()); return SDL_APP_FAILURE; } if (!TTF_Init()) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to initialize SDL TTF: %s", SDL_GetError()); return SDL_APP_FAILURE; } AppState *state = SDL_calloc(1, sizeof(AppState)); if (!state) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for application state: %s", SDL_GetError()); return SDL_APP_FAILURE; } *appstate = state; state->window = SDL_CreateWindow("Game Dev", 1280, 960, SDL_WINDOW_RESIZABLE); if (!state->window) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window: %s", SDL_GetError()); return SDL_APP_FAILURE; } state->rendererData.renderer = SDL_CreateRenderer(state->window, NULL); if (!state->rendererData.renderer) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_SetRenderDrawBlendMode(state->rendererData.renderer, SDL_BLENDMODE_BLEND); 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); 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; SDL_UpdateGamepads(); 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: { SDL_KeyboardEvent kb_event = event->key; SDL_Keycode keycode = kb_event.key; SDL_Scancode scancode = kb_event.scancode; bool repeat = kb_event.repeat; if (repeat) { break; } SDL_Log("Pressed key: %s\n", SDL_GetKeyName(keycode)); switch (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; } case SDL_SCANCODE_F3: { Clay_SetDebugModeEnabled(!Clay_IsDebugModeEnabled()); // toggle debug mode SDL_Log("Toggled debug mode"); break; } } break; } case SDL_EVENT_KEY_UP: { SDL_KeyboardEvent kb_event = event->key; SDL_Keycode keycode = kb_event.key; SDL_Scancode scancode = kb_event.scancode; SDL_Log("Released key: %s\n", SDL_GetKeyName(keycode)); switch (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; } case SDL_EVENT_GAMEPAD_ADDED: { SDL_GamepadDeviceEvent device_event = event->gdevice; SDL_JoystickID joystick_id = device_event.which; SDL_Log("Gamepad ID %d added", joystick_id); // TODO: handle multiple gamepads in state if (state->gamepad) { SDL_Log("Closing gamepad"); SDL_CloseGamepad(state->gamepad); state->gamepad = NULL; } SDL_Log("Opening gamepad"); state->gamepad = SDL_OpenGamepad(joystick_id); if (!state->gamepad) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to open gamepad: %s", SDL_GetError()); } break; } case SDL_EVENT_GAMEPAD_REMOVED: { SDL_GamepadDeviceEvent device_event = event->gdevice; SDL_JoystickID joystick_id = device_event.which; SDL_Log("Gamepad ID %d removed", joystick_id); if (state->gamepad && SDL_GetGamepadID(state->gamepad) == joystick_id) { SDL_Log("Closing gamepad"); SDL_CloseGamepad(state->gamepad); state->gamepad = NULL; } break; } case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { SDL_GamepadButtonEvent button_event = event->gbutton; SDL_GamepadButton button = button_event.button; SDL_Log("Gamepad button down: %s", SDL_GetGamepadStringForButton(button)); if (state->gamepad) { if (!SDL_RumbleGamepad(state->gamepad, 0xFFFF, 0xFFFF, 1000)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to rumble gamepad: %s", SDL_GetError()); } } break; } case SDL_EVENT_GAMEPAD_BUTTON_UP: { SDL_GamepadButtonEvent button_event = event->gbutton; SDL_GamepadButton button = button_event.button; SDL_Log("Gamepad button up: %s", SDL_GetGamepadStringForButton(button)); break; } case SDL_EVENT_GAMEPAD_AXIS_MOTION: { SDL_GamepadAxisEvent axis_event = event->gaxis; SDL_GamepadAxis axis = axis_event.axis; Sint16 val = axis_event.value; SDL_Log("Gamepad axis motion: %s - %d", SDL_GetGamepadStringForAxis(axis), val); 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(); }