Mercurial
view postdog/main.c @ 71:75de5903355c
Giagantic changes that update Dowa library to be more align with stb style array and hashmap. Updated Seobeo to be caching on server side instead of file level caching. Deleted bunch of things I don't really use.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sun, 28 Dec 2025 20:34:22 -0800 |
| parents | fff1b048dda6 |
| children | 48f260576059 |
line wrap: on
line source
/** * Entirely written by Claude AI. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/stat.h> #include <dirent.h> // third party #include <curl/curl.h> #include "third_party/raylib/include/raylib.h" #define RAYGUI_IMPLEMENTATION #include "third_party/raylib/include/raygui.h" #define SCREEN_WIDTH 1280 #define SCREEN_HEIGHT 780 #define TEXT_SIZE 10 #define LINE_HEIGHT 15 #define GENERIC_PADDING 15 #define SIDEBAR_WIDTH 200 #define SIDEBAR_PADDING_GENERAL 5 #define SIDEBAR_AREA_PADDING_X 10 #define SIDEBAR_AREA_PADDING_Y 10 #define SIDEBAR_REFERSH_BUTTON_WIDTH 90 #define SIDEBAR_REFERSH_BUTTON_HEIGHT 20 #define SIDEBAR_HISTORY_ITEM_HEIGHT 45 #define URL_INPUT_HEIGHT 40 #define TAB_LEN 3 #define METHOD_BUTTON_WIDTH 100 #define METHOD_BUTTON_HEIGHT 40 #define SEND_BUTTON_WIDTH 100 #define SEND_BUTTON_HEIGHT 40 #define JSON_INPUT_BUFFER_LEN 8192 #define HEADER_INPUT_BUFFER_LEN 4096 #define PARAM_INPUT_BUFFER_LEN 4096 #define MAX_HISTORY_ITEMS 100 // Structure to hold response data typedef struct { char *data; size_t size; } ResponseBuffer; // Structure to hold history item typedef struct { char filename[256]; char displayName[128]; char method[16]; time_t timestamp; } HistoryItem; typedef enum { ActiveTab_JSON = 0, ActiveTab_Headers, ActiveTab_Params, } ActiveTab; // Callback function for curl to write response data static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t real_size = size * nmemb; ResponseBuffer *buf = (ResponseBuffer *)userp; char *ptr = realloc(buf->data, buf->size + real_size + 1); if (ptr == NULL) { printf("Not enough memory for response\n"); return 0; } buf->data = ptr; memcpy(&(buf->data[buf->size]), contents, real_size); buf->size += real_size; buf->data[buf->size] = 0; return real_size; } // Function to make HTTP request using curl int PostDog_Make_HttpRequest(const char *url, const char *method, const char *headers, const char *body, char *response, size_t responseSize) { CURL *curl; CURLcode res; ResponseBuffer buffer = { .data = malloc(1), .size = 0 }; if (buffer.data == NULL) { snprintf(response, responseSize, "Error: Failed to allocate memory"); return -1; } buffer.data[0] = '\0'; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (curl) { struct curl_slist *headerList = NULL; // Set URL curl_easy_setopt(curl, CURLOPT_URL, url); // Set HTTP method if (strcmp(method, "POST") == 0) { curl_easy_setopt(curl, CURLOPT_POST, 1L); if (body && strlen(body) > 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } } else if (strcmp(method, "PUT") == 0) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); if (body && strlen(body) > 0) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); } } else if (strcmp(method, "DELETE") == 0) { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); } // Default is GET // Parse and add headers if (headers && strlen(headers) > 0) { char *headersCopy = strdup(headers); char *line = strtok(headersCopy, "\n"); while (line != NULL) { // Trim whitespace while (*line == ' ' || *line == '\t') line++; if (strlen(line) > 0) { headerList = curl_slist_append(headerList, line); } line = strtok(NULL, "\n"); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); free(headersCopy); } // Set write callback curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Postdog_Curl_Callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer); // Follow redirects curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Set timeout curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // Perform request res = curl_easy_perform(curl); if (res != CURLE_OK) { snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res)); } else { long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); snprintf(response, responseSize, "HTTP Status: %ld\n\n%s", response_code, buffer.data ? buffer.data : ""); } // Cleanup if (headerList) curl_slist_free_all(headerList); curl_easy_cleanup(curl); } else { snprintf(response, responseSize, "Error: Failed to initialize curl"); } free(buffer.data); curl_global_cleanup(); return 0; } void PostDog_HistoryDirectory_Exists() { struct stat st = {0}; if (stat("history", &st) == -1) { mkdir("history", 0700); } } int PostDog_HistoryDistory_ItemsLoad(HistoryItem *items, int maxItems) { DIR *dir = opendir("history"); if (!dir) return 0; struct dirent *entry; int count = 0; while ((entry = readdir(dir)) != NULL && count < maxItems) { if (entry->d_name[0] == '.') continue; if (strstr(entry->d_name, ".txt") == NULL) continue; // Parse filename: YYYYMMDD_HHMMSS_METHOD.txt strncpy(items[count].filename, entry->d_name, sizeof(items[count].filename) - 1); // Extract method from filename char *methodStart = strrchr(entry->d_name, '_'); if (methodStart) { methodStart++; // Skip underscore char *dotPos = strchr(methodStart, '.'); if (dotPos) { int len = dotPos - methodStart; if (len < 16) { strncpy(items[count].method, methodStart, len); items[count].method[len] = '\0'; } } } // Create display name: METHOD - YYYYMMDD HHMMSS char dateTime[32] = ""; if (strlen(entry->d_name) >= 15) { snprintf(dateTime, sizeof(dateTime), "%.8s %.6s", entry->d_name, entry->d_name + 9); } snprintf(items[count].displayName, sizeof(items[count].displayName), "%s - %s", items[count].method, dateTime); count++; } closedir(dir); return count; } int PostDog_HistoryDirectory_LoadRequest(const char *filename, char *url, char *method, char *headers, char *body) { char filepath[512]; snprintf(filepath, sizeof(filepath), "history/%s", filename); FILE *f = fopen(filepath, "r"); if (!f) return -1; char line[2048]; int section = 0; // 0=url, 1=method, 2=headers, 3=body url[0] = '\0'; method[0] = '\0'; headers[0] = '\0'; body[0] = '\0'; while (fgets(line, sizeof(line), f)) { // Remove newline line[strcspn(line, "\n")] = 0; if (section == 0) { // First line is URL strncpy(url, line, 1024 - 1); section = 1; } else if (strncmp(line, "Method: ", 8) == 0) { strncpy(method, line + 8, 15); section = 2; } else if (strncmp(line, "Headers:", 8) == 0) { section = 2; } else if (strcmp(line, "---") == 0) { section = 3; } else if (strncmp(line, "Body:", 5) == 0) { section = 3; } else if (section == 2 && strcmp(line, "None") != 0) { // Add header line if (strlen(headers) > 0) strcat(headers, "\n"); strncat(headers, line, HEADER_INPUT_BUFFER_LEN - strlen(headers) - 1); } else if (section == 3 && strcmp(line, "None") != 0) { // Add body line if (strlen(body) > 0) strcat(body, "\n"); strncat(body, line, JSON_INPUT_BUFFER_LEN - strlen(body) - 1); } } fclose(f); return 0; } void Postdog_UpdateUrlWithParams(char *url, size_t urlSize, const char *baseUrl, const char *params) { // Find if there's already a ? in the URL char *questionMark = strchr(baseUrl, '?'); if (questionMark != NULL) { // URL already has params, just copy the base strncpy(url, baseUrl, urlSize - 1); } else { // No params yet, add them snprintf(url, urlSize, "%s", baseUrl); } // Parse and append params if (params && strlen(params) > 0) { char *paramsCopy = strdup(params); char *line = strtok(paramsCopy, "\n"); bool firstParam = (questionMark == NULL); while (line != NULL) { // Trim whitespace while (*line == ' ' || *line == '\t') line++; if (strlen(line) > 0 && strchr(line, '=')) { size_t currentLen = strlen(url); if (currentLen + 2 < urlSize) { strcat(url, firstParam ? "?" : "&"); firstParam = false; strncat(url, line, urlSize - strlen(url) - 1); } } line = strtok(NULL, "\n"); } free(paramsCopy); } } // Save request to history file void Postdog_SaveRequestToHistory(const char *url, const char *method, const char *headers, const char *body) { PostDog_HistoryDirectory_Exists(); // Generate filename with timestamp time_t now = time(NULL); struct tm *t = localtime(&now); char filename[256]; snprintf(filename, sizeof(filename), "history/%04d%02d%02d_%02d%02d%02d_%s.txt", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, method); FILE *f = fopen(filename, "w"); if (f == NULL) { printf("Failed to create history file\n"); return; } // Write URL fprintf(f, "%s\n", url); // Write method fprintf(f, "Method: %s\n", method); // Write headers fprintf(f, "Headers:\n"); if (headers && strlen(headers) > 0) { fprintf(f, "%s\n", headers); } else { fprintf(f, "None\n"); } fprintf(f, "---\n"); // Write body fprintf(f, "Body:\n"); if (body && strlen(body) > 0) { fprintf(f, "%s\n", body); } else { fprintf(f, "None\n"); } fclose(f); printf("Request saved to %s\n", filename); } void PostDog_Render_TextWithScroll(Rectangle textArea, Vector2 scroll, char *input) { BeginScissorMode(textArea.x, textArea.y, textArea.width, textArea.height); int yPos = textArea.y + 5 - (int)scroll.y; int charactersPerLine = (int)(textArea.width / (TEXT_SIZE/1.5)); // Account for padding int totalPos = 0; int inputLen = strlen(input); while (totalPos < inputLen) { int lineEnd = totalPos; int lineLength = 0; while (lineEnd < inputLen && lineLength < charactersPerLine && input[lineEnd] != '\n') { lineEnd++; lineLength++; } if (yPos + LINE_HEIGHT > textArea.y && yPos < textArea.y + textArea.height) { char savedChar = input[lineEnd]; input[lineEnd] = '\0'; DrawText(&input[totalPos], textArea.x + 5, yPos, TEXT_SIZE, DARKGRAY); input[lineEnd] = savedChar; } yPos += LINE_HEIGHT; totalPos = lineEnd; if (totalPos < inputLen && input[totalPos] == '\n') { totalPos++; } } EndScissorMode(); } int main() { InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog - HTTP Client"); SetWindowState(FLAG_WINDOW_RESIZABLE); SetTargetFPS(60); PostDog_HistoryDirectory_Exists(); // UI State char urlInput[1024] = "https://httpbin.org/get"; bool urlEditMode = false; char jsonInput[JSON_INPUT_BUFFER_LEN] = "{\"key\":\"value\"}"; bool jsonEditMode = false; char headersInput[HEADER_INPUT_BUFFER_LEN] = "Content-Type: application/json"; bool headersEditMode = false; char responseText[16384] = "Response will appear here...\n\nTry the default URL or enter your own!"; char paramsInput[PARAM_INPUT_BUFFER_LEN] = "key1=value1\nkey2=value2"; bool paramsEditMode = false; ActiveTab activeTab = ActiveTab_JSON; // 0 = JSON, 1 = Headers, 2 = Params // HTTP method selection int methodActive = 0; bool methodDropdown = false; const char *methods[] = { "GET", "POST", "PUT", "DELETE" }; // Scroll support Vector2 jsonScroll = { 0, 0 }; Vector2 headersScroll = { 0, 0 }; Vector2 paramsScroll = { 0, 0 }; Vector2 responseScroll = { 0, 0 }; Vector2 historyScroll = { 0, 0 }; // History HistoryItem historyItems[MAX_HISTORY_ITEMS]; int historyCount = 0; int selectedHistoryIndex = -1; // Load initial history historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS); while (!WindowShouldClose()) { // Get current window dimensions for responsive layout int screenWidth = GetScreenWidth(); int screenHeight = GetScreenHeight(); // Layout calculations Rectangle sidebar = { 0, 10, SIDEBAR_WIDTH, screenHeight }; float mainX = SIDEBAR_WIDTH + GENERIC_PADDING; float mainWidth = screenWidth - SIDEBAR_WIDTH - GENERIC_PADDING * 2; // URL input box - leave space for SEND button on the right Rectangle urlBox = { mainX, GENERIC_PADDING, mainWidth - SEND_BUTTON_WIDTH - GENERIC_PADDING, URL_INPUT_HEIGHT }; // SEND button positioned beside URL input Rectangle sendButton = { urlBox.x + urlBox.width + GENERIC_PADDING, GENERIC_PADDING, SEND_BUTTON_WIDTH, SEND_BUTTON_HEIGHT }; // Method dropdown below URL Rectangle methodButton = { mainX, urlBox.y + urlBox.height + GENERIC_PADDING, METHOD_BUTTON_WIDTH, METHOD_BUTTON_HEIGHT }; float tabHeight = 30; float contentY = methodButton.y + methodButton.height + GENERIC_PADDING + tabHeight; float contentHeight = screenHeight - contentY - GENERIC_PADDING; float panelWidth = (mainWidth - GENERIC_PADDING) / 2; Rectangle tabBar = { mainX, methodButton.y + methodButton.height + GENERIC_PADDING, panelWidth, tabHeight }; Rectangle requestPanel = { mainX, contentY, panelWidth, contentHeight }; Rectangle responsePanel = { mainX + panelWidth + GENERIC_PADDING, contentY, panelWidth, contentHeight }; BeginDrawing(); ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // --- Sidebar Component--- DrawRectangleRec(sidebar, Fade(GRAY, 0.1f)); GuiGroupBox(sidebar, "HISTORY"); Rectangle refreshBtn = { sidebar.x + SIDEBAR_PADDING_GENERAL, sidebar.y + SIDEBAR_PADDING_GENERAL, SIDEBAR_REFERSH_BUTTON_WIDTH, SIDEBAR_REFERSH_BUTTON_HEIGHT }; if (GuiButton(refreshBtn, "Refresh")) { historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS); } Rectangle historyArea = { sidebar.x + SIDEBAR_PADDING_GENERAL, sidebar.y + SIDEBAR_AREA_PADDING_Y, sidebar.width - SIDEBAR_AREA_PADDING_X, sidebar.height - SIDEBAR_AREA_PADDING_Y }; if (CheckCollisionPointRec(GetMousePosition(), historyArea)) { float wheel = GetMouseWheelMove(); historyScroll.y += wheel * 20; if (historyScroll.y < 0) historyScroll.y = 0; } BeginScissorMode(historyArea.x, historyArea.y, historyArea.width, historyArea.height); if (historyCount == 0) { DrawText("No requests yet", historyArea.x + 5, historyArea.y + 25, 10, DARKGRAY); } else { int item_y_position = historyArea.y + SIDEBAR_AREA_PADDING_Y + 5 - (int)historyScroll.y; for ( int current_history_item_number = 0; current_history_item_number < historyCount; current_history_item_number++ ) { if (item_y_position > historyArea.y - SIDEBAR_HISTORY_ITEM_HEIGHT && item_y_position < historyArea.y + historyArea.height) { Rectangle itemRect = { historyArea.x, item_y_position, historyArea.width, SIDEBAR_HISTORY_ITEM_HEIGHT - 2 }; // Draw button for history item Color bgColor = (selectedHistoryIndex == current_history_item_number) ? Fade(BLUE, 0.3f) : Fade(LIGHTGRAY, 0.5f); if (CheckCollisionPointRec(GetMousePosition(), itemRect) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { // TODO: This is cringe as fuck probably should just have a strucut that we assign and then zero out lol char tempUrl[1024], tempMethod[16], tempHeaders[HEADER_INPUT_BUFFER_LEN], tempBody[JSON_INPUT_BUFFER_LEN]; if (PostDog_HistoryDirectory_LoadRequest( historyItems[current_history_item_number].filename, tempUrl, tempMethod, tempHeaders, tempBody) == 0) { strncpy(urlInput, tempUrl, sizeof(urlInput) - 1); strncpy(headersInput, tempHeaders, sizeof(headersInput) - 1); strncpy(jsonInput, tempBody, sizeof(jsonInput) - 1); // Set method for (int m = 0; m < 4; m++) { if (strcmp(methods[m], tempMethod) == 0) { methodActive = m; break; } } selectedHistoryIndex = current_history_item_number; strcpy(responseText, "Request loaded from history. Click SEND to execute."); } } DrawRectangleRec(itemRect, bgColor); DrawRectangleLinesEx(itemRect, 1, GRAY); // Draw method badge DrawText(historyItems[current_history_item_number].method, itemRect.x + 5, item_y_position + 5, 10, BLACK); // Draw timestamp (date only) char dateStr[16] = ""; if (strlen(historyItems[current_history_item_number].filename) >= 8) { snprintf(dateStr, sizeof(dateStr), "%.4s-%.2s-%.2s", historyItems[current_history_item_number].filename, historyItems[current_history_item_number].filename + 4, historyItems[current_history_item_number].filename + 6); } DrawText(dateStr, itemRect.x + 5, item_y_position + 18, 8, DARKGRAY); // Draw time char timeStr[16] = ""; if (strlen(historyItems[current_history_item_number].filename) >= 15) { snprintf(timeStr, sizeof(timeStr), "%.2s:%.2s:%.2s", historyItems[current_history_item_number].filename + 9, historyItems[current_history_item_number].filename + 11, historyItems[current_history_item_number].filename + 13); } DrawText(timeStr, itemRect.x + 5, item_y_position + 28, 8, DARKGRAY); } item_y_position += SIDEBAR_HISTORY_ITEM_HEIGHT; } } EndScissorMode(); // --- URL Input Component --- GuiLabel((Rectangle){ mainX, GENERIC_PADDING - 15, 100, 20 }, "URL:"); if (GuiTextBox(urlBox, urlInput, 1024, urlEditMode)) { urlEditMode = !urlEditMode; } if (urlEditMode && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) { SetClipboardText(urlInput); } // Send button (beside URL) if (GuiButton(sendButton, "SEND") || (urlEditMode && IsKeyDown(KEY_ENTER))) { strcpy(responseText, "Sending request...\n"); // Make the actual HTTP request char tempResponse[16384]; const char *selectedMethod = methods[methodActive]; // Use JSON body for POST/PUT, otherwise use empty body const char *requestBody = (methodActive == 1 || methodActive == 2) ? jsonInput : NULL; const char *requestHeaders = headersInput; // Save request to history Postdog_SaveRequestToHistory(urlInput, selectedMethod, requestHeaders, requestBody); // Reload history to show the new request historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS); // Making the requests. PostDog_Make_HttpRequest(urlInput, selectedMethod, requestHeaders, requestBody, tempResponse, sizeof(tempResponse)); strncpy(responseText, tempResponse, sizeof(responseText) - 1); responseText[sizeof(responseText) - 1] = '\0'; } // Tab toggle (3 tabs now) float tabWidth = tabBar.width / 3; Rectangle jsonTab = { tabBar.x, tabBar.y - 10, tabWidth, tabBar.height }; Rectangle headersTab = { tabBar.x + tabWidth, tabBar.y - 10, tabWidth, tabBar.height }; Rectangle paramsTab = { tabBar.x + tabWidth * 2, tabBar.y - 10, tabWidth, tabBar.height }; if (GuiButton(jsonTab, activeTab == ActiveTab_JSON ? "#191#Body" : "Body")) { activeTab = ActiveTab_JSON; } if (GuiButton(headersTab, activeTab == ActiveTab_Headers ? "#191#Headers" : "Headers")) { activeTab = ActiveTab_Headers; } if (GuiButton(paramsTab, activeTab == ActiveTab_Params ? "#191#Params" : "Params")) { activeTab = ActiveTab_Params; } const char *panelTitle; switch(activeTab) { case ActiveTab_JSON: { panelTitle = "Request Body (JSON)"; break; } case ActiveTab_Headers: { panelTitle = "Request Headers"; break; } case ActiveTab_Params: { panelTitle = "Query Parameters"; break; } } // Panel title GuiGroupBox(requestPanel, panelTitle); Rectangle textArea = { requestPanel.x + 10, requestPanel.y + 30, requestPanel.width - 20, requestPanel.height - 40 }; // Handle click outside to disable edit mode if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { if (!CheckCollisionPointRec(GetMousePosition(), textArea)) { jsonEditMode = false; headersEditMode = false; paramsEditMode = false; } } // Draw border for text area DrawRectangleLinesEx(textArea, 1, GRAY); // Manual scroll handling with mouse wheel if (CheckCollisionPointRec(GetMousePosition(), textArea)) { float wheel = GetMouseWheelMove(); switch(activeTab) { case ActiveTab_JSON: { jsonScroll.y += wheel * 20; if (jsonScroll.y < 0) jsonScroll.y = 0; } case ActiveTab_Headers: { headersScroll.y += wheel * 20; if (headersScroll.y < 0) headersScroll.y = 0; } case ActiveTab_Params: { paramsScroll.y += wheel * 20; if (paramsScroll.y < 0) paramsScroll.y = 0; } } } char *copyFromInput; bool *currentMode; switch(activeTab) { case ActiveTab_JSON: { PostDog_Render_TextWithScroll(textArea, jsonScroll, jsonInput); copyFromInput = jsonInput; currentMode = &jsonEditMode; break; } case ActiveTab_Headers: { PostDog_Render_TextWithScroll(textArea, headersScroll, headersInput); copyFromInput = headersInput; currentMode = &headersEditMode; break; } case ActiveTab_Params: { PostDog_Render_TextWithScroll(textArea, paramsScroll, paramsInput); copyFromInput = paramsInput; currentMode = ¶msEditMode; Rectangle updateUrlBtn = { textArea.x + 30, textArea.y + textArea.height - 10, 120, 20 }; // TODO: Automatic update if (GuiButton(updateUrlBtn, "Update URL")) { char tempUrl[1024]; strncpy(tempUrl, urlInput, sizeof(tempUrl) - 1); // Remove existing params if any char *questionMark = strchr(tempUrl, '?'); if (questionMark) *questionMark = '\0'; Postdog_UpdateUrlWithParams(urlInput, sizeof(urlInput), tempUrl, paramsInput); } break; } } if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) { SetClipboardText(copyFromInput); } Rectangle editBtn = { textArea.x + textArea.width - 60, textArea.y + textArea.height - 10, 50, 20 }; if (GuiButton(editBtn, "Edit")) { *currentMode = !(*currentMode); } // Response Panel with scroll GuiGroupBox(responsePanel, "Response"); Rectangle responseArea = { responsePanel.x + 10, responsePanel.y + 30, responsePanel.width - 20, responsePanel.height - 40 }; // Manual scroll for response if (CheckCollisionPointRec(GetMousePosition(), responseArea)) { float wheel = GetMouseWheelMove(); responseScroll.y += wheel * 20; if (responseScroll.y < 0) responseScroll.y = 0; } // Draw border DrawRectangleLinesEx(responseArea, 1, GRAY); // Draw response text with scroll PostDog_Render_TextWithScroll(responseArea, responseScroll, responseText); if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) { if (CheckCollisionPointRec(GetMousePosition(), responseArea)) { SetClipboardText(responseText); } } // --- Edit modal ---- if (jsonEditMode) { GuiTextBox( (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 }, jsonInput, JSON_INPUT_BUFFER_LEN, true); } if (headersEditMode) { GuiTextBox( (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 }, headersInput, HEADER_INPUT_BUFFER_LEN, true); } if (paramsEditMode) { GuiTextBox( (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 }, paramsInput, PARAM_INPUT_BUFFER_LEN, true); } if (GuiDropdownBox(methodButton, "GET;POST;PUT;DELETE", &methodActive, methodDropdown)) { methodDropdown = !methodDropdown; } EndDrawing(); } CloseWindow(); return 0; }