diff postdog/main.c @ 116:7bd795bac997

[Postdog] Added scrollable area to inputs and history files, buttons to delete and view.
author June Park <parkjune1995@gmail.com>
date Wed, 07 Jan 2026 04:52:17 -0800
parents 96db6c3f38d6
children b91f2dd6f84d
line wrap: on
line diff
--- a/postdog/main.c	Tue Jan 06 08:19:07 2026 -0800
+++ b/postdog/main.c	Wed Jan 07 04:52:17 2026 -0800
@@ -16,19 +16,13 @@
 
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 780
-#define TEXT_SIZE 10
-
-#define JSON_INPUT_BUFFER_LEN 8192
-#define HEADER_INPUT_BUFFER_LEN 4096
-#define PARAM_INPUT_BUFFER_LEN 4096
-#define MAX_HISTORY_ITEMS 100
+#define MAX_SCROLL_HEIGHT 10000
 
 #define HEADER_BUFFER_LENGTH 1024 * 4
 #define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4
-#define URL_TEXT_BUFFER 1024 * 10
-#define BODY_BUFFER_LENGTH 1024 * 1025 * 5
-#define RESULT_BUFFER_LENGTH 1024 * 1025 * 5
-#define AREANA_BUFFER_LENGTH 1024 * 1025 * 15
+#define URL_TEXT_BUFFER_LENGTH 1024 * 10
+#define BODY_BUFFER_LENGTH 1024 * 1024 * 5
+#define RESULT_BUFFER_LENGTH 1024 * 1024 * 5
 
 
 #ifdef _WIN32
@@ -171,6 +165,13 @@
   return 0;
 }
 
+char *PostDog_Construct_URL(char *filename)
+{
+  char full_file_path[512] = {0};
+  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
+  return &full_file_path;
+}
+
 void PostDog_History_CreateFile(char *filename, char* values)
 {
   char full_file_path[512] = {0};
@@ -263,7 +264,7 @@
     const char *method,
     const char *headers,
     const char *body, 
-    char *response, size_t responseSize)
+    char *response, size_t response_size)
 {
   CURL *curl;
   CURLcode res;
@@ -273,7 +274,7 @@
 
   if (buffer.data == NULL)
   {
-    snprintf(response, responseSize, "Error: Failed to allocate memory");
+    snprintf(response, response_size, "Error: Failed to allocate memory");
     return -1;
   }
   buffer.data[0] = '\0';
@@ -335,22 +336,25 @@
     res = curl_easy_perform(curl);
 
     if (res != CURLE_OK)
-      snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res));
+      snprintf(response, response_size, "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",
+      if (buffer.size > response_size)
+        printf("TODO: Realloc\n");
+
+      snprintf(response, response_size, "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");
   }
+  else
+    snprintf(response, response_size, "Error: Failed to initialize curl");
 
   free(buffer.data);
   curl_global_cleanup();
@@ -461,8 +465,10 @@
   Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2);
   char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1);
   fread(file_buffer, 1, file_size, file);
+  fclose(file);
+
   char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena);
-  Dowa_Arena_Free(init_arena);
+
   for (int i = 0; i < Dowa_Array_Length(values); i++)
   {
     if (i == 0)
@@ -487,6 +493,9 @@
     else
       snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]);
   }
+
+  Dowa_Arena_Free(init_arena);
+  Dowa_Arena_Free(split_arena);
 }
 
 Rectangle AddPadding(Rectangle rect, float padding)
@@ -499,6 +508,26 @@
   };
 }
 
+Rectangle AddPaddingHorizontal(Rectangle rect, float padding)
+{
+  return (Rectangle){
+    rect.x + padding,
+    rect.y,
+    rect.width - (2 * padding),
+    rect.height
+  };
+}
+
+Rectangle AddPaddingVertical(Rectangle rect, float padding)
+{
+  return (Rectangle){
+    rect.x,
+    rect.y + padding,
+    rect.width,
+    rect.height - (2 * padding)
+  };
+}
+
 // Layout helper functions
 Rectangle RightOf(Rectangle ref, float padding)
 {
@@ -557,20 +586,20 @@
   SetWindowState(FLAG_WINDOW_RESIZABLE);
   SetTargetFPS(60);
 
-  Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH);
-
   Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
   GuiSetFont(customFont);
-  GuiSetStyle(DEFAULT, TEXT_SIZE, 20);
+  GuiSetStyle(DEFAULT, TEXT_SIZE, 10);
+  Image logo_original = LoadImage("postdog/epi_all_colors.png");
+  ImageResize(&logo_original, 60, 60);
+  Texture2D logo_texture = LoadTextureFromImage(logo_original); 
+  UnloadImage(logo_original);
 
   // -- Starting pos ---//
-  // Everyhting is relative to sidebar at this point lol
-  float side_bar_x = 10;
-  float side_bar_y = 10;
-  Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };
+  Rectangle history_sidebar = { 0 };
   Dowa_Array_Reserve(history_items, 10);
   Dowa_Array_Reserve(new_history_items, 10);
   PostDog_History_Load(&history_items);
+  int32 *history_deleted_items = NULL;
 
   Rectangle url_area = { 0 };
   Rectangle textBounds = { 0 };
@@ -581,19 +610,19 @@
   Rectangle method_dropdown = { 0 };
 
 
-  char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER);
-  snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get");
+  char *url_input_text = (char *)malloc(sizeof(char) * URL_TEXT_BUFFER_LENGTH);
+  snprintf(url_input_text, URL_TEXT_BUFFER_LENGTH, "https://httpbin.org/get");
 
   char **url_body_map = NULL;
-  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
-  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
-  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
-  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
+  Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * HEADER_BUFFER_LENGTH));
+  Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * BODY_BUFFER_LENGTH));
+  Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH));
+  Dowa_Array_Push(url_body_map, (char *)malloc(sizeof(char) * DEFAULT_TEXT_BUFFER_LENGTH));
 
   snprintf(url_body_map[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json");
   snprintf(url_body_map[TAB_BODY], HEADER_BUFFER_LENGTH, "");
 
-  char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH);
+  char *url_result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH);
 
   int active_method_dropdown = 0;
   bool method_edit = false;
@@ -614,39 +643,73 @@
   // General styling.
   float padding = 10; // TODO make it % based?
   int active_input_tab = 0;
+  int prev_input_tab = 0;
+
+  // Scroll offsets
+  float history_scroll_offset = 0;
+  float input_body_scroll_offset = 0;
+  float result_body_scroll_offset = 0;
 
   while (!WindowShouldClose())
   {
     int screen_width = GetScreenWidth();
     int screen_height = GetScreenHeight();
 
-    // Define main screen container
+    if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL))
+      GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1);
+
+    if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS))
+      GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1);
+
     Rectangle screen = { 0, 0, screen_width, screen_height };
 
-    // Layout: Sidebar (left 15%) and Content Area (right 85%)
+    // -- Side bar --//
     history_sidebar = LeftColumn(screen, 0.15, padding);
     Rectangle content_area = RightColumn(screen, history_sidebar, padding);
+    Rectangle logo_area = (Rectangle){
+      .x = history_sidebar.x,
+      .y = history_sidebar.y,
+      .width = history_sidebar.width,
+      .height = 80 
+    };
 
-    // History items inside sidebar
+    Rectangle history_list_area = Below(logo_area, padding);
+    history_list_area.width = history_sidebar.width;
+    history_list_area.height = history_sidebar.height - logo_area.height - padding;
+
     int32 new_history_items_length = Dowa_Array_Length(new_history_items);
     int32 history_item_length = Dowa_Array_Length(history_items);
     int32 total = new_history_items_length + history_item_length;
-    float item_height = history_sidebar.height * 0.05;
+    float item_height = history_list_area.height * 0.10;
 
+    int32 number_of_skipped_items = 0;
     for (int i = 0; i < total; i++)
     {
+      boolean skip = FALSE;
+      for (int32 j = 0; j < Dowa_Array_Length(history_deleted_items); j++)
+      {
+        if (i == j)
+        {
+          skip = TRUE;
+          number_of_skipped_items++;
+          break;
+        }
+      }
+      if (skip)
+        continue;
+
       HistoryItem *curr_history_items = i < new_history_items_length ?
         &new_history_items[i] : &history_items[i - new_history_items_length];
 
       curr_history_items->rect = (Rectangle){
-        .x = history_sidebar.x,
-        .y = history_sidebar.y + (padding + item_height) * i,
-        .width = history_sidebar.width,
+        .x = history_list_area.x,
+        .y = history_list_area.y + (padding + item_height) * (i - number_of_skipped_items) + history_scroll_offset,
+        .width = history_list_area.width,
         .height = item_height
       };
     }
 
-    // Content area: split into URL bar (top 10%) and body (bottom 90%)
+    // --- URL --- //
     url_area = (Rectangle){
       .x = content_area.x,
       .y = content_area.y,
@@ -654,7 +717,6 @@
       .height = content_area.height * 0.1
     };
 
-    // URL bar elements laid out horizontally
     float url_control_y = url_area.y + (url_area.height - TEXT_SIZE * 2) / 2;
 
     url_text_bounds = (Rectangle){
@@ -676,7 +738,7 @@
     method_dropdown.width = url_area.width * 0.1;
     method_dropdown.height = TEXT_SIZE * 2;
 
-    // Body area: split into input (left 50%) and result (right 50%)
+    // -- Body -- //
     Rectangle body_area = Below(url_area, 0);
     body_area.height = content_area.height - url_area.height;
 
@@ -684,7 +746,6 @@
     result_area = RightOf(input_area, 0);
     result_area.width = body_area.width - input_area.width;
 
-    // Input area: tabs at top (10%) and text box below (90%)
     input_tab = (Rectangle){
       .x = input_area.x + padding,
       .y = input_area.y + padding,
@@ -699,7 +760,7 @@
     input_body.width = input_tab.width;
     input_body.height = input_area.height - input_tab.height - (2 * padding);
 
-    // Result area: aligned with input tabs
+    // -- Result -- /
     result_body = (Rectangle){
       .x = result_area.x + padding,
       .y = input_body.y,
@@ -708,16 +769,122 @@
     };
 
     Vector2 mouse_position = GetMousePosition();
+    float mouse_wheel = GetMouseWheelMove();
+
+    // Reset input body scroll when tab changes
+    if (prev_input_tab != active_input_tab) {
+      input_body_scroll_offset = 0;
+      prev_input_tab = active_input_tab;
+    }
+
+    // Handle scroll wheel for history
+    if (InArea(mouse_position, history_list_area) && mouse_wheel != 0) {
+      history_scroll_offset += mouse_wheel * 30;  // 30 pixels per wheel tick
+      // Clamp scroll offset
+      float max_scroll = (total * (item_height + padding)) - history_list_area.height;
+      if (history_scroll_offset > 0) history_scroll_offset = 0;
+      if (history_scroll_offset < -max_scroll && max_scroll > 0) history_scroll_offset = -max_scroll;
+    }
+
+    // Handle scroll wheel for input body
+    if (InArea(mouse_position, input_body) && mouse_wheel != 0) {
+      input_body_scroll_offset += mouse_wheel * 30;
+      if (input_body_scroll_offset > 0) input_body_scroll_offset = 0;
+    }
+
+    // Handle scroll wheel for result body
+    if (InArea(mouse_position, result_body) && mouse_wheel != 0) {
+      result_body_scroll_offset += mouse_wheel * 30;
+      if (result_body_scroll_offset > 0) result_body_scroll_offset = 0;
+    }
 
     BeginDrawing();
       ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
 
       DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));
-      for (int i = 0; i < total; i++)
+
+      // DrawRectangleRec(logo_area, Fade(BLUE, 0.2f));
+      Rectangle logo_image_rect = AddPadding(logo_area, padding);
+      // Fit logo to area while maintaining aspect ratio
+      float logo_size = logo_image_rect.height < logo_image_rect.width ? logo_image_rect.height : logo_image_rect.width;
+      Rectangle dest = {
+        .x = logo_image_rect.x + (logo_image_rect.width - logo_size) / 2,  // Center horizontally
+        .y = logo_image_rect.y,
+        .width = logo_size,
+        .height = logo_size
+      };
+
+      Rectangle source = { 0, 0, logo_texture.width, logo_texture.height };
+      DrawTexturePro(logo_texture, source, dest, (Vector2){0, 0}, 0.0f, WHITE);
+
+      // Draw history items with scissor mode for clipping
+      BeginScissorMode(history_list_area.x, history_list_area.y, history_list_area.width, history_list_area.height);
+        for (int i = 0; i < total; i++)
+        {
+          boolean skip = FALSE;
+          for (int32 j = 0; j < Dowa_Array_Length(history_deleted_items); j++)
+          {
+            if (i == j)
+            {
+              skip = TRUE;
+              break;
+            }
+          }
+          if (skip)
+            continue;
+          HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
+
+          float diff = curr_history_items->rect.height*0.3;
+          // DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f));
+          Rectangle filename_area = curr_history_items->rect;
+
+          filename_area.height -= diff;
+          Rectangle icon_area = Below(filename_area, 0);
+          icon_area.height = diff;
+
+          DrawRectangleRec(filename_area, Fade(BLUE, 0.1f));
+          DrawRectangleRec(icon_area, Fade(YELLOW, 0.1f));
+
+          Rectangle icon_area_left_column = LeftColumn(icon_area, 0.5, 0);
+          Rectangle icon_area_right_column = RightColumn(icon_area, icon_area_left_column, 0);
+
+          GuiDrawText(curr_history_items->filename, AddPadding(filename_area, padding), TEXT_ALIGN_CENTER, RED);
+          if (GuiButton(AddPaddingHorizontal(icon_area_left_column, padding), "view"))
+            PostDog_Load_File(
+                curr_history_items->filename,
+                &url_input_text,
+                &active_method_dropdown,
+                url_body_map,
+                &url_result_text
+            );
+          if (GuiButton(AddPaddingHorizontal(icon_area_right_column,padding), "delete"))
+          {
+            if (!remove(PostDog_Construct_URL(curr_history_items->filename)))
+               Dowa_Array_Push(history_deleted_items, i);
+            else
+            {
+              fprintf(stderr, "Wasn't able to delete file: %s \n", curr_history_items->filename);
+            }
+          }
+        }
+      EndScissorMode();
+
+      // Draw scroll indicator for history
+      if (total > 0)
       {
-        HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
-        DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); 
-        GuiDrawText(curr_history_items->filename, AddPadding(curr_history_items->rect, padding), TEXT_ALIGN_CENTER, RED);
+        float content_height = total * (item_height + padding);
+        if (content_height > history_list_area.height)
+        {
+          float scrollbar_height = (history_list_area.height / content_height) * history_list_area.height;
+          float scrollbar_y = history_list_area.y - (history_scroll_offset / content_height) * history_list_area.height;
+          Rectangle scrollbar = {
+            history_list_area.x + history_list_area.width - 5,
+            scrollbar_y,
+            5,
+            scrollbar_height
+          };
+          DrawRectangleRec(scrollbar, Fade(WHITE, 0.5f));
+        }
       }
 
       // URL area Rect
@@ -743,8 +910,31 @@
       DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
       DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
       GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
-      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
-        input_body_bool = !input_body_bool;
+      GuiDrawRectangle(input_body, 1, GetColor(GuiGetStyle(TEXTBOX, BORDER)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
+
+      // Create scrollable input body with offset
+      BeginScissorMode(input_body.x, input_body.y, input_body.width, input_body.height);
+        Rectangle scrolled_input = AddPadding(input_body, padding * 2);
+        scrolled_input.y += input_body_scroll_offset;
+        scrolled_input.height = MAX_SCROLL_HEIGHT;
+        if (JUNE_GuiTextBox(scrolled_input, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
+          input_body_bool = !input_body_bool;
+      EndScissorMode();
+
+      // Draw scroll indicator for input body
+      if (input_body_scroll_offset < 0) {
+        float scrollbar_height = 50; // Fixed size indicator
+        float max_scroll = 1000; // Estimated max scroll
+        float scrollbar_y = input_body.y - (input_body_scroll_offset / max_scroll) * (input_body.height - scrollbar_height);
+        Rectangle scrollbar = {
+          input_body.x + input_body.width - 5,
+          scrollbar_y,
+          5,
+          scrollbar_height
+        };
+        DrawRectangleRec(scrollbar, Fade(BLUE, 0.5f));
+      }
+
       GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab);
 
       PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS]);
@@ -752,7 +942,28 @@
       // Result Rect
       DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
       DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
-      GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false);
+
+      // Create scrollable result body with offset
+      BeginScissorMode(result_body.x, result_body.y, result_body.width, result_body.height);
+        Rectangle scrolled_result = result_body;
+        scrolled_result.y += result_body_scroll_offset;
+        scrolled_result.height = MAX_SCROLL_HEIGHT;
+        GuiTextBoxMulti(scrolled_result, url_result_text, RESULT_BUFFER_LENGTH, false);
+      EndScissorMode();
+
+      // Draw scroll indicator for result body
+      if (result_body_scroll_offset < 0) {
+        float scrollbar_height = 50; // Fixed size indicator
+        float max_scroll = 1000; // Estimated max scroll
+        float scrollbar_y = result_body.y - (result_body_scroll_offset / max_scroll) * (result_body.height - scrollbar_height);
+        Rectangle scrollbar = {
+          result_body.x + result_body.width - 5,
+          scrollbar_y,
+          5,
+          scrollbar_height
+        };
+        DrawRectangleRec(scrollbar, Fade(GREEN, 0.5f));
+      }
 
       if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
       {
@@ -768,18 +979,6 @@
         if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
           SetClipboardText(url_result_text);
       }
-
-      for (int i = 0; i < Dowa_Array_Length(history_items); i++)
-      {
-        if (Clicked(mouse_position, history_items[i].rect))
-          PostDog_Load_File(
-              history_items[i].filename,
-              &url_input_text,
-              &active_method_dropdown,
-              url_body_map,
-              &url_result_text
-          );
-      }
     EndDrawing();
   }
   CloseWindow();