view postdog/main.c @ 150:c37490913530

[Config] Updated .vimrc files and made command for ctags movements.
author June Park <parkjune1995@gmail.com>
date Sat, 10 Jan 2026 13:30:28 -0800
parents 249881ceff7b
children 7387eec8e7f8
line wrap: on
line source

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include "dowa/dowa.h"

#include <curl/curl.h>
#include "third_party/raylib/include/raylib.h"
#define RAYGUI_IMPLEMENTATION
#include "third_party/raylib/include/raygui.h"

#ifndef POSTDOG_PATHS
  #define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history"
#endif

#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 780
#define MAX_SCROLL_HEIGHT 10000

#define HEADER_BUFFER_LENGTH 1024 * 4
#define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4
#define URL_TEXT_BUFFER_LENGTH 1024 * 10
#define BODY_BUFFER_LENGTH 1024 * 1024 * 5
#define RESULT_BUFFER_LENGTH 1024 * 1024 * 5


#ifdef _WIN32
    #include <direct.h>
    #include <io.h>
    #define mkdir(path, mode) _mkdir(path)
    #define access _access
    #define F_OK 0
#else
    #include <sys/stat.h>
    #include <dirent.h>
    #include <unistd.h>
#endif

typedef Dowa_KV(char*, char*) INPUT_HASHMAP;

typedef struct {
  char *data;
  size_t size;
} ResponseBuffer;

typedef struct {
  char *filename;
  char *title; 
  Rectangle rect;
  long time_modified;
  boolean deleted;
} HistoryItem;

typedef struct {
  Rectangle rectangle;
  char *label;
  bool active;
} TabItem;

typedef enum {
  TAB_HEADER = 0,
  TAB_BODY,
  TAB_GET_PARAMS,
  TAB_BAR,
  TAB_LENGTH
} PostDog_Tab_Enum;

static uint32 counter = 0;
HistoryItem *history_items = NULL;
HistoryItem *new_history_items = NULL;

// Global UI state
char *url_input_text = NULL;
char *url_result_text = NULL;
char **url_body_map = NULL;
int active_method_dropdown = 0;

int CompareHistoryItemsByDate(const void *a, const void *b) {
  HistoryItem *itemA = (HistoryItem *)a;
  HistoryItem *itemB = (HistoryItem *)b;
  return (itemB->time_modified - itemA->time_modified);
}

char *PostDog_Extract_Title(const char *filename)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "r");
  if (!file)
    return strdup(filename);

  char *title = malloc(sizeof(char) * 512);
  if (!fgets(title, 512, file)) {
    fclose(file);
    return strdup(filename);
  }

  return title;
}

// TODO: Make this into generic fucntion so I can use it across different thing.
void PostDog_List_Directory(const char *path, HistoryItem **p_file_arr)
{
  HistoryItem *file_arr = *p_file_arr;
#ifdef _WIN32
  struct _finddata_t fileinfo;
  intptr_t handle;
  char search_path[256];
  sprintf(search_path, "%s\\*", path);

  if ((handle = _findfirst(search_path, &fileinfo)) == -1L) {
    printf("Directory is empty or cannot be read.\n");
  } else {
    do {
      HistoryItem item = {0};
      item.filename = strdup(fileinfo.name);
      item.title = PostDog_Extract_Title(fileinfo.name);
      item.time_modified = fileinfo.time_write;
      item.deleted = FALSE;
      Dowa_Array_Push(file_arr, item);
    } while (_findnext(handle, &fileinfo) == 0);
    _findclose(handle);
  }
#else
  struct dirent *entry;
  struct stat file_stat;
  DIR *dp = opendir(path);
  if (dp == NULL) return;

  char full_path[256];
  while ((entry = readdir(dp)))
  {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;
    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
    if (stat(full_path, &file_stat) == 0)
    {
      HistoryItem item = {0};
      item.filename = strdup(entry->d_name);
      item.title = PostDog_Extract_Title(entry->d_name);
      item.time_modified = file_stat.st_mtime;
      item.deleted = FALSE;
      Dowa_Array_Push(file_arr, item);
    }
  }
  closedir(dp);
#endif
  int count = Dowa_Array_Length(file_arr);
  if (count > 1) {
    qsort(file_arr, count, sizeof(HistoryItem), CompareHistoryItemsByDate);
  }
}

int PostDog_History_Load(HistoryItem **p_history_files)
{
  if (access(POSTDOG_PATHS, F_OK) == -1)
  {
    printf("Directory '%s' not found. Creating it...\n", POSTDOG_PATHS);
    if (mkdir(POSTDOG_PATHS, 0777) != 0)
      return -1;
    return 0;
  }
  printf("Directory '%s' already exists.\n", POSTDOG_PATHS);
  PostDog_List_Directory(POSTDOG_PATHS, p_history_files);
  return 0;
}

bool InArea(Vector2 mouse_position, Rectangle area)
{
  return (
    mouse_position.x >= area.x &&
    mouse_position.x < area.x + area.width &&
    mouse_position.y >= area.y &&
    mouse_position.y < area.y + area.height
  );
}

bool Clicked(Vector2 mouse_position, Rectangle area)
{
  return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}

char *PostDog_Enum_To_String(int active_enum)
{
  switch(active_enum)
  {
    case 0: return "GET";
    case 1: return "POST";
    case 2: return "PUT";
    case 3: return "DELETE";
  }
  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};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "w");
  if (!file)
  {
    printf("Failed to create a file: %s\n", full_file_path);
    return;
  }
  fwrite(values, 1, strlen(values), file);
  fclose(file);
}

void PostDog_Request_SaveFile(void)
{
  const char *method = PostDog_Enum_To_String(active_method_dropdown);
  size_t new_file_size = 1024 * 1024;
  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2);
  char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024);
  char *title = malloc(strlen(method) + strlen(url_input_text) + 2);
  sprintf(title, "%s %s", method, url_input_text);
  snprintf(
      new_file,
      new_file_size,
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n",
      title,
      url_input_text,
      method,
      url_body_map[TAB_HEADER],
      url_body_map[TAB_BODY],
      url_body_map[TAB_GET_PARAMS],
      url_body_map[TAB_BAR],
      url_result_text
  );
  char *filename = Dowa_Arena_Allocate(arena, 1024);
  if (!filename)
  {
    perror("Error opening file");
    exit(EXIT_FAILURE); 
  }
  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
  if (!uuid4)
  {
    perror("Error uuid");
    exit(EXIT_FAILURE); 
  }
  
  int32 seed = (uint32)time(NULL) ^ counter++;
  Dowa_String_UUID(seed, uuid4);
  snprintf(filename, 1024, "%s.txt", uuid4);
  PostDog_History_CreateFile(filename, new_file);

  HistoryItem item = {0};
  item.filename = strdup(filename);
  item.title = title;
  item.deleted = FALSE;
  Dowa_Array_Push(new_history_items, item);
  
  Dowa_Arena_Free(arena);
}

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;
}

int PostDog_Http_Request(void)
{
  const char *method = PostDog_Enum_To_String(active_method_dropdown);
  CURL *curl;
  CURLcode res;
  ResponseBuffer buffer = { .data = malloc(1), .size = 0 };

  url_result_text[0] = '\n';

  if (buffer.data == NULL)
  {
    snprintf(url_result_text, RESULT_BUFFER_LENGTH, "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_input_text);

    // Set HTTP method
    if (strcmp(method, "POST") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_POST, 1L);
      if (url_body_map[TAB_BODY] && strlen(url_body_map[TAB_BODY]) > 0)
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, url_body_map[TAB_BODY]);
    }
    else if (strcmp(method, "PUT") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      if (url_body_map[TAB_BODY] && strlen(url_body_map[TAB_BODY]) > 0)
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, url_body_map[TAB_BODY]);
    }
    else if (strcmp(method, "DELETE") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    }
    // Default is GET

    // Parse and add headers
    if (url_body_map[TAB_HEADER] && strlen(url_body_map[TAB_HEADER]) > 0)
    {
      char *headersCopy = strdup(url_body_map[TAB_HEADER]);
      char *line = strtok(headersCopy, "\n");
      while (line != NULL) {
        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);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
    res = curl_easy_perform(curl);

    if (res != CURLE_OK)
      snprintf(url_result_text, RESULT_BUFFER_LENGTH, "Error: %s\n", curl_easy_strerror(res));
    else
    {
      long response_code;
      curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

      if (buffer.size > RESULT_BUFFER_LENGTH)
        printf("TODO: Realloc\n");

      snprintf(url_result_text, RESULT_BUFFER_LENGTH, "HTTP Status: %ld\n\n%s",
               response_code, buffer.data ? buffer.data : "");
    }

    if (headerList) curl_slist_free_all(headerList);
    curl_easy_cleanup(curl);
  }
  else
    snprintf(url_result_text, RESULT_BUFFER_LENGTH, "Error: Failed to initialize curl");

  free(buffer.data);
  curl_global_cleanup();

  PostDog_Request_SaveFile();
  return 0;
}

void PostDog_Update_URL(void)
{
  // Save existing query string if present
  char *question_mark = strchr(url_input_text, '?');
  if (question_mark)
    *question_mark = '\0';

  int get_params_length = (int)strlen(url_body_map[TAB_GET_PARAMS]);
  if (get_params_length == 0)
    return;

  char *separator = "?";

  Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
  char **lines = Dowa_String_Split(url_body_map[TAB_GET_PARAMS], "\n", get_params_length, 1, arena);
  for (int i = 0; i < Dowa_Array_Length(lines); i++)
  {
    char *line = lines[i];
    char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);

    if (Dowa_Array_Length(key_value) < 2)
      break;

    strcat(url_input_text, separator);
    strcat(url_input_text, key_value[0]);
    strcat(url_input_text, "=");
    for (int i = 1; i < Dowa_Array_Length(key_value); i++)
    {
      if (!key_value[i] || key_value[i][0] == '\0')
        break;
      if (i > 1) strcat(url_input_text, "%20");
      strcat(url_input_text, key_value[i]);
    }
    separator = "&";
  }

  Dowa_Arena_Free(arena);
}

int PostDog_String_To_MethodEnum(char *value)
{
  if (strstr(value, "GET"))
    return 0;
  if (strstr(value, "POST"))
    return 1;
  if (strstr(value, "PUT"))
    return 2;
  if (strstr(value, "DELETE"))
    return 3;
  return 0;
}

void PostDog_Params_Reset(void)
{
  url_input_text[0] = '\0';
  url_result_text[0] = '\0';
  active_method_dropdown = 0;
  for (int i = 0; i < Dowa_Array_Length(url_body_map); i++)
    url_body_map[i][0] = '\0';
}

void PostDog_Load_File(const char *filename)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "r");
  if (!file)
    return;

  fseek(file, 0, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0, SEEK_SET);

  Dowa_Arena *init_arena = Dowa_Arena_Create(file_size + 2);
  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);

  for (int i = 0; i < Dowa_Array_Length(values); i++)
  {
    switch (i)
    {
      case 0:  // Title - skip
        break;

      case 1:  // URL
        snprintf(url_input_text, strlen(values[i]) + 1, "%s", values[i]);
        url_input_text[strcspn(url_input_text, "\n")] = '\0';
        break;

      case 2:  // Method
        active_method_dropdown = PostDog_String_To_MethodEnum(values[i]);
        break;

      case 3:  // Headers (TAB_HEADER)
      case 4:  // Body (TAB_BODY)
      case 5:  // Get Params (TAB_GET_PARAMS)
      case 6:  // Bar (TAB_BAR)
      {
        int map_index = i - 3;  // 3->0, 4->1, 5->2, 6->3
        snprintf(url_body_map[map_index], strlen(values[i]) + 1, "%s", values[i]);
        // Trim trailing newlines
        for (int j = strlen(values[i]); j > 0; j--)
        {
          if (url_body_map[map_index][j] == '\n')
          {
            url_body_map[map_index][j] = '\0';
            break;
          }
        }
        break;
      }

      default:  // Response (index 7+)
        snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]);
        break;
    }
  }

  Dowa_Arena_Free(init_arena);
  Dowa_Arena_Free(split_arena);
}

Rectangle AddPadding(Rectangle rect, float padding)
{
  return (Rectangle){
    rect.x + padding,
    rect.y + padding,
    rect.width - (2 * padding),
    rect.height - (2 * padding)
  };
}

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)
{
  return (Rectangle){
    .x = ref.x + ref.width + padding,
    .y = ref.y,
    .width = 0,
    .height = ref.height
  };
}

Rectangle Below(Rectangle ref, float padding)
{
  return (Rectangle){
    .x = ref.x,
    .y = ref.y + ref.height + padding,
    .width = ref.width,
    .height = 0
  };
}

Rectangle LeftColumn(Rectangle container, float ratio, float padding)
{
  return (Rectangle){
    .x = container.x + padding,
    .y = container.y + padding,
    .width = (container.width * ratio) - padding,
    .height = container.height - (2 * padding)
  };
}

Rectangle RightColumn(Rectangle container, Rectangle leftCol, float padding)
{
  return (Rectangle){
    .x = leftCol.x + leftCol.width + padding,
    .y = container.y + padding,
    .width = container.width - leftCol.width - (3 * padding),
    .height = container.height - (2 * padding)
  };
}

Rectangle HorizontalSplit(Rectangle container, float ratio)
{
  return (Rectangle){
    .x = container.x,
    .y = container.y,
    .width = container.width * ratio,
    .height = container.height
  };
}

int main()
{
  // -- initizlied --//
  InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog");
  SetWindowState(FLAG_WINDOW_RESIZABLE);
  SetTargetFPS(60);

  Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
  GuiSetFont(customFont);
  GuiSetStyle(DEFAULT, TEXT_SIZE, 10);
  Image logo_original = LoadImage("postdog/epi_all_colors.png");
  ImageResize(&logo_original, 60, 60);
  SetWindowIcon(logo_original);
  Texture2D logo_texture = LoadTextureFromImage(logo_original); 
  UnloadImage(logo_original);

  // -- Starting pos ---//
  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 };
  Rectangle url_input_bounds = { 0 };
  bool url_input_edit = false;
  Rectangle url_text_bounds = { 0 };
  Rectangle url_enter_button = { 0 };
  Rectangle method_dropdown = { 0 };


  // Initialize global UI state
  url_input_text = (char *)malloc(sizeof(char) * URL_TEXT_BUFFER_LENGTH);
  snprintf(url_input_text, URL_TEXT_BUFFER_LENGTH, "https://httpbin.org/get");

  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, "");

  url_result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH);

  bool method_edit = false;

  int sendRequest;

  // -- input --//
  Rectangle input_area = { 0 };
  Rectangle input_tab = { 0 };
  Rectangle input_tab_item = { 0 };
  Rectangle input_body = { 0 };
  bool input_body_bool = false;

  // -- result --//
  Rectangle result_area = { 0 };
  Rectangle result_body = { 0 };

  // 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();

    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 };

    // -- 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 
    };

    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_list_area.height * 0.10;

    int32 number_of_skipped_items = 0;
    for (int i = 0; i < total; i++)
    {
      HistoryItem *curr_history_items = i < new_history_items_length ?
        &new_history_items[i - new_history_items_length - 1] : &history_items[i - new_history_items_length];

      if (curr_history_items->deleted)
      {
        number_of_skipped_items++;
        continue;
      }

      curr_history_items->rect = (Rectangle){
        .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
      };
    }

    // --- URL --- //
    url_area = (Rectangle){
      .x = content_area.x,
      .y = content_area.y,
      .width = content_area.width,
      .height = content_area.height * 0.1
    };

    float url_control_y = url_area.y + (url_area.height - TEXT_SIZE * 2) / 2;

    url_text_bounds = (Rectangle){
      .x = url_area.x + padding,
      .y = url_control_y,
      .width = 7 * (TEXT_SIZE / 2),
      .height = TEXT_SIZE * 2
    };

    url_input_bounds = RightOf(url_text_bounds, padding);
    url_input_bounds.width = url_area.width * 0.7;
    url_input_bounds.height = TEXT_SIZE * 2;

    url_enter_button = RightOf(url_input_bounds, padding);
    url_enter_button.width = url_area.width * 0.1;
    url_enter_button.height = TEXT_SIZE * 2;

    method_dropdown = RightOf(url_enter_button, padding);
    method_dropdown.width = url_area.width * 0.1;
    method_dropdown.height = TEXT_SIZE * 2;

    // -- Body -- //
    Rectangle body_area = Below(url_area, 0);
    body_area.height = content_area.height - url_area.height;

    input_area = HorizontalSplit(body_area, 0.5);
    result_area = RightOf(input_area, 0);
    result_area.width = body_area.width - input_area.width;

    input_tab = (Rectangle){
      .x = input_area.x + padding,
      .y = input_area.y + padding,
      .width = input_area.width - (2 * padding),
      .height = input_area.height * 0.1
    };

    input_tab_item = input_tab;
    input_tab_item.width = input_tab.width / 4;

    input_body = Below(input_tab, 0);
    input_body.width = input_tab.width;
    input_body.height = input_area.height - input_tab.height - (2 * padding);

    // -- Result -- /
    result_body = (Rectangle){
      .x = result_area.x + padding,
      .y = input_body.y,
      .width = result_area.width - (2 * padding),
      .height = input_body.height
    };

    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));

      // 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);

      BeginScissorMode(history_list_area.x, history_list_area.y, history_list_area.width, history_list_area.height);
        for (int i = 0; i < total; i++)
        {
          HistoryItem *curr_history_items = i < new_history_items_length ? 
            &new_history_items[i - new_history_items_length - 1] : &history_items[i - new_history_items_length];

          if (curr_history_items->deleted)
            continue;

          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->title, AddPadding(filename_area, padding), TEXT_ALIGN_CENTER, RED);
          if (GuiButton(AddPaddingHorizontal(icon_area_left_column, padding), "view"))
            PostDog_Load_File(curr_history_items->filename);
          if (GuiButton(AddPaddingHorizontal(icon_area_right_column,padding), "delete"))
          {
            if (!remove(PostDog_Construct_URL(curr_history_items->filename)))
               curr_history_items->deleted = TRUE;
            else
              fprintf(stderr, "Wasn't able to delete file: %s \n", curr_history_items->filename);
          }
        }
      EndScissorMode();

      if (total > 0)
      {
        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
      GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED); 
      DrawRectangleRec(url_area, Fade(RED, 0.1f));
      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit))
        url_input_edit = !url_input_edit;

      sendRequest = GuiButton(url_enter_button, "ENTER");
      if (sendRequest)
        PostDog_Http_Request();
      if (GuiDropdownBox(method_dropdown, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit))
        method_edit = !method_edit;

      // Input Tabs Rect
      DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
      DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
      GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
      GuiDrawRectangle(input_body, 1, GetColor(GuiGetStyle(TEXTBOX, BORDER)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));

      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();

      if (input_body_scroll_offset < 0) {
        float scrollbar_height = 10; 
        float scrollbar_y = input_body.y - (input_body_scroll_offset / MAX_SCROLL_HEIGHT) * (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();

      // Result Rect
      DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
      DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));

      // 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();

      if (result_body_scroll_offset < 0) {
        float scrollbar_height = 10;
        float scrollbar_y = result_body.y - (result_body_scroll_offset / MAX_SCROLL_HEIGHT) * (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))
      {
        SetClipboardText(url_input_text);
      }
      else if (input_body_bool && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
      {
        SetClipboardText(url_body_map[active_input_tab]);
      }
      else if (InArea(mouse_position, result_body))
      {
        DrawRectangleRec(result_body, Fade(GREEN, 0.3f));
        if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
          SetClipboardText(url_result_text);
      }
    EndDrawing();
  }
  CloseWindow();
  return 0;
}