commit 5f47ce6cf679944c6e98222bacdda924745627cb Author: Bartkk Date: Wed Mar 26 20:16:50 2025 +0100 Initial diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ca184a9 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BreakBeforeBraces: Attach diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3147b59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +cache/ +clay/ +a.out diff --git a/main.c b/main.c new file mode 100644 index 0000000..761a9ab --- /dev/null +++ b/main.c @@ -0,0 +1,393 @@ +#include +#include +#include +#define SDL_MAIN_USE_CALLBACKS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define CLAY_IMPLEMENTATION +#include "clay/clay.h" +#include "clay/renderers/SDL3/clay_renderer_SDL3.c" + +const Clay_Color COLOR_LIGHT = (Clay_Color){224, 215, 210, 255}; +const Clay_Color COLOR_RED = (Clay_Color){168, 66, 28, 255}; +const Clay_Color COLOR_ORANGE = (Clay_Color){225, 138, 50, 255}; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static Clay_SDL3RendererData renderer_data; +static float window_w, window_h; +static float scroll_x, scroll_y; + +typedef struct { + SDL_Texture *texture; + int width, height; +} image_t; + +image_t images[1024]; +size_t n_images; + +typedef struct { + const char *url; + bool success; + size_t size; + char *data; +} request_t; + +size_t write_callback(char *data, size_t size, size_t nmemb, void *userdata) { + if (userdata == NULL) + return nmemb; + size_t realsize = size * nmemb; + + request_t *res = (request_t *)userdata; + char *ptr = SDL_realloc(res->data, res->size + realsize + 1); + + res->data = ptr; + memcpy(&(res->data[res->size]), data, realsize); + res->size += realsize; + res->data[res->size] = 0; + + return realsize; +} + +unsigned long hash(unsigned char *str) { + unsigned long hash = 5381; + int c; + + while (c = *str++) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + return hash; +} + +char *request_data(const char *url, size_t *size) { + unsigned long url_hash = hash(url); + char *cache_path; + SDL_asprintf(&cache_path, "cache/%lul", url_hash); + + size_t file_size; + char *cache_data = SDL_LoadFile(cache_path, &file_size); + if (cache_data != NULL) { + printf("Using cached file...\n"); + return cache_data; + } + + request_t request = {0}; + + CURL *curl = curl_easy_init(); + CURLcode res; + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "bartkk/sdl-esix"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&request); + + res = curl_easy_perform(curl); + SDL_assert(res == CURLE_OK); + + curl_easy_cleanup(curl); + + SDL_SaveFile(cache_path, request.data, request.size); + + *size = request.size; + return request.data; +} + +#define MULTI_HANDLE_COUNT 8 +void request_multi_data(size_t n_requests, request_t requests[n_requests]) { + request_t *handle_request[MULTI_HANDLE_COUNT] = {0}; + CURL *handles[MULTI_HANDLE_COUNT]; + CURLM *multi_handle; + + int n_running_handles; + int url_i = 0; + int finished_request = 0; + + // Initialize curl multi handle + multi_handle = curl_multi_init(); + + // Initialuze curl handles + for (int i = 0; i < MULTI_HANDLE_COUNT; i++) { + handles[i] = curl_easy_init(); + curl_easy_setopt(handles[i], CURLOPT_URL, ""); + curl_easy_setopt(handles[i], CURLOPT_USERAGENT, "bartkk/sdl-esix"); + curl_easy_setopt(handles[i], CURLOPT_WRITEFUNCTION, write_callback); + curl_multi_add_handle(multi_handle, handles[i]); + } + + // While there are unfinished requests + while (finished_request < n_requests) { + CURLMsg *msg; + int msgs_left; + while ((msg = curl_multi_info_read(multi_handle, &msgs_left)) != NULL) { + if (msg->msg == CURLMSG_DONE) { + int idx; + + for (idx = 0; idx < MULTI_HANDLE_COUNT; idx++) { + bool found = (msg->easy_handle == handles[idx]); + if (found) + break; + } + + printf("Handle %d done!\n", idx); + if (handle_request[idx] != NULL) { + handle_request[idx]->success = true; + finished_request++; + } + curl_multi_remove_handle(multi_handle, handles[idx]); + if (url_i < n_requests) { + printf("Requesting another url...\n"); + curl_easy_setopt(handles[idx], CURLOPT_WRITEDATA, + (void *)&requests[url_i]); + curl_easy_setopt(handles[idx], CURLOPT_URL, requests[url_i].url); + handle_request[idx] = &requests[url_i]; + url_i++; + curl_multi_add_handle(multi_handle, handles[idx]); + } + } + } + + curl_multi_perform(multi_handle, &n_running_handles); + + curl_multi_poll(multi_handle, NULL, 0, 100, NULL); + } +} + +void request_posts() { + CURL *curl = curl_easy_init(); + request_t request = {0}; + + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, + "https://e621.net/" + "posts.json?tags=solo%20raccoon%20rating%3Asafe%20order%" + "3Ascore&limit=5"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "bartkk/sdl-esix"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&request); + + res = curl_easy_perform(curl); + + SDL_assert(res == CURLE_OK); + if (res == CURLE_OK) { + /* printf("%s\n", response.data); */ + + json_t *root; + json_error_t error; + + root = json_loads(request.data, 0, &error); + SDL_assert(root != NULL); + json_t *posts = json_object_get(root, "posts"); + SDL_assert(posts != NULL); + + json_t *post; + size_t i; + + size_t n_requests = json_array_size(posts); + request_t requests[n_requests]; + SDL_memset(requests, 0, sizeof(request_t) * n_requests); + + json_array_foreach(posts, i, post) { + const char *file_url; + { + json_t *file = json_object_get(post, "preview"); + json_t *file_url_obj = json_object_get(file, "url"); + file_url = json_string_value(file_url_obj); + } + + requests[i].url = file_url; + } + + request_multi_data(n_requests, requests); + + for (int i = 0; i < n_requests; i++) { + if (!requests[i].success) + continue; + + SDL_IOStream *io = SDL_IOFromMem(requests[i].data, requests[i].size); + images[n_images].texture = IMG_LoadTexture_IO(renderer, io, true); + float width, height; + SDL_GetTextureSize(images[n_images].texture, &width, &height); + images[n_images].width = (int)width; + images[n_images].height = (int)height; + + n_images++; + } + + json_decref(root); + } + + SDL_free(request.data); + curl_easy_cleanup(curl); +} + +void clay_error_handler(Clay_ErrorData error_data) { + printf("%s", error_data.errorText.chars); + return; +} + +static inline Clay_Dimensions clay_measure_text(Clay_StringSlice text, + Clay_TextElementConfig *config, + void *userdata) { + TTF_Font **fonts = userdata; + TTF_Font *font = fonts[0]; + + int width, height; + if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s\n", + SDL_GetError()); + } + + return (Clay_Dimensions){(float)width, (float)height}; +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { + /* return SDL_APP_SUCCESS; */ + SDL_SetAppMetadata("Example Renderer Clear", "1.0", + "com.example.renderer-clear"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + TTF_Init(); + + if (!SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, + SDL_WINDOW_RESIZABLE, &window, &renderer)) { + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + renderer_data.renderer = renderer; + renderer_data.textEngine = TTF_CreateRendererTextEngine(renderer); + renderer_data.fonts = SDL_calloc(1, sizeof(TTF_Font *)); + + renderer_data.fonts[0] = + TTF_OpenFont("/usr/share/fonts/TTF/Roboto-Medium.ttf", 24); + SDL_assert(renderer_data.fonts[0] != NULL); + + uint64_t total_memory_size = Clay_MinMemorySize(); + Clay_Arena clay_memory = (Clay_Arena){.memory = SDL_malloc(total_memory_size), + .capacity = total_memory_size}; + + int width, height; + SDL_GetWindowSize(window, &width, &height); + Clay_Initialize(clay_memory, (Clay_Dimensions){width, height}, + (Clay_ErrorHandler){clay_error_handler}); + Clay_SetMeasureTextFunction(clay_measure_text, renderer_data.fonts); + + request_posts(); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +/* This function runs when a new event (mouse input, keypresses, etc) occurs. */ +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. + */ + } + + switch (event->type) { + case SDL_EVENT_WINDOW_RESIZED: + window_w = event->window.data1; + window_h = event->window.data2; + Clay_SetLayoutDimensions( + (Clay_Dimensions){event->window.data1, 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_UP: + 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: + scroll_x += event->wheel.x; + scroll_y += event->wheel.y; + Clay_UpdateScrollContainers( + true, (Clay_Vector2){event->wheel.x, event->wheel.y}, 0.01f); + break; + } + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} +// +// Layout config is just a struct that can be declared statically, or inline +Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration){ + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), + .height = CLAY_SIZING_FIXED(150)}}, + .backgroundColor = COLOR_ORANGE}; + +void PostComponent(image_t *image) { + CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(150), + .height = CLAY_SIZING_GROW(0)}}, + .image = {.imageData = (image->texture), + .sourceDimensions = {image->width, image->height}}}) {} +} + +SDL_AppResult SDL_AppIterate(void *appstate) { + SDL_RenderClear(renderer); + + /* for (size_t i = 0; i < n_images; i++) { */ + /* float x = (i % 6) * 100; */ + /* float y = (i / 6) * 100; */ + /* SDL_RenderTexture(renderer, images[i].texture, NULL, */ + /* &(SDL_FRect){.x = x, .y = y, .w = 100, .h = 100}); */ + /* } */ + + /* Clay_BeginLayout(); */ + /**/ + /* CLAY({.id = CLAY_ID("OuterContainer"), */ + /* .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, */ + /* .padding = CLAY_PADDING_ALL(16), */ + /* .childGap = 16}, */ + /* .backgroundColor = {250, 250, 255, 255}}) { */ + /**/ + /* CLAY({.id = CLAY_ID("MainContent"), */ + /* .scroll = {.vertical = true}, */ + /* .layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM, */ + /* .childGap = 8, */ + /* .sizing = {.width = CLAY_SIZING_GROW(0), */ + /* .height = CLAY_SIZING_GROW(0)}}, */ + /* .backgroundColor = COLOR_LIGHT}) { */ + /* for (int i = 0; i < n_images; i++) { */ + /* image_t *image = &images[i]; */ + /* PostComponent(image); */ + /* } */ + /* } */ + /* } */ + + /* Clay_RenderCommandArray render_commands = Clay_EndLayout(); */ + + /* SDL_Clay_RenderClayCommands(&renderer_data, &render_commands); */ + + const int size = 120; + float divider = window_w / size; + float remainder = SDL_fmodf(window_w, size); + for (int i = 0; i < n_images; i++) { + image_t *image = &images[i]; + int x = (i % (int)divider) * size + + remainder / divider * ((i % (int)divider) + 1); + int y = (i / (int)divider) * size + 8 * (i / (int)divider) + scroll_y * 20; + SDL_RenderTexture(renderer, image->texture, + &(SDL_FRect){.w = image->width, .h = image->height}, + &(SDL_FRect){.x = x, .y = y, .w = size, .h = size}); + } + + SDL_RenderPresent(renderer); + SDL_Delay(5); + + return SDL_APP_CONTINUE; /* carry on with the program! */ +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) {}