diff postdog/main.c @ 159:05cf9467a1c3

[Postdog] Updated to use text area that can handle like html text area.
author June Park <parkjune1995@gmail.com>
date Wed, 14 Jan 2026 08:56:33 -0800
parents 3bb45eb67906
children 87d8d3eb3491
line wrap: on
line diff
--- a/postdog/main.c	Wed Jan 14 07:59:19 2026 -0800
+++ b/postdog/main.c	Wed Jan 14 08:56:33 2026 -0800
@@ -39,6 +39,996 @@
 #define BODY_BUFFER_LENGTH 1024 * 1024 * 5
 #define RESULT_BUFFER_LENGTH 1024 * 1024 * 5
 
+// ============================================================================
+// TextArea Component
+// ============================================================================
+
+#define TEXT_SIZE_DEFAULT 10 // used to calcualte spacing
+#define TEXT_AREA_FONT_SIZE 16
+#define TEXT_AREA_LINE_HEIGHT 20
+#define TEXT_AREA_PADDING 8
+#define TEXT_AREA_CURSOR_WIDTH 2
+#define TEXT_AREA_MAX_UNDO_STATES 64
+#define TEXT_AREA_MAX_INSTANCES 8
+
+typedef struct {
+    char *text;
+    int cursor_pos;
+    int selection_start;
+    int selection_end;
+} TextAreaUndoEntry;
+
+typedef struct {
+    int id;                       // Unique ID for this text area
+    int cursor_pos;               // Current cursor position in text
+    int selection_start;          // Selection start (-1 if no selection)
+    int selection_end;            // Selection end (-1 if no selection)
+    float scroll_offset_y;        // Vertical scroll offset
+    float scroll_offset_x;        // Horizontal scroll offset (for non-wrap mode)
+    boolean is_selecting;         // Currently dragging to select
+    boolean is_initialized;       // State has been initialized
+
+    // Undo history
+    TextAreaUndoEntry *undo_stack;  // Dowa array of undo entries
+    int undo_index;                 // Current position in undo stack
+
+    // Internal tracking
+    double last_blink_time;
+    boolean cursor_visible;
+} TextAreaState;
+
+static TextAreaState g_text_area_states[TEXT_AREA_MAX_INSTANCES] = {0};
+static int g_text_area_state_count = 0;
+static char *g_clipboard_text = NULL;
+static Dowa_Arena *g_text_area_arena = NULL;
+
+// Helper functions
+static int TA_Min_Int(int a, int b) { return a < b ? a : b; }
+static int TA_Max_Int(int a, int b) { return a > b ? a : b; }
+static float TA_Min_Float(float a, float b) { return a < b ? a : b; }
+static float TA_Max_Float(float a, float b) { return a > b ? a : b; }
+
+static TextAreaState* GetTextAreaState(int id) {
+    for (int i = 0; i < g_text_area_state_count; i++) {
+        if (g_text_area_states[i].id == id && g_text_area_states[i].is_initialized) {
+            return &g_text_area_states[i];
+        }
+    }
+    return NULL;
+}
+
+static TextAreaState* CreateTextAreaState(int id) {
+    if (g_text_area_state_count >= TEXT_AREA_MAX_INSTANCES) {
+        return &g_text_area_states[0]; // Reuse first slot
+    }
+
+    TextAreaState *state = &g_text_area_states[g_text_area_state_count++];
+    memset(state, 0, sizeof(TextAreaState));
+    state->id = id;
+    state->selection_start = -1;
+    state->selection_end = -1;
+    state->undo_index = -1;
+    state->cursor_visible = TRUE;
+    state->is_initialized = TRUE;
+    return state;
+}
+
+static void GetLineAndColumn(const char *text, int pos, int *out_line, int *out_column) {
+    int line = 0;
+    int column = 0;
+    for (int i = 0; i < pos && text[i] != '\0'; i++) {
+        if (text[i] == '\n') {
+            line++;
+            column = 0;
+        } else {
+            column++;
+        }
+    }
+    *out_line = line;
+    *out_column = column;
+}
+
+static int GetPosFromLineColumn(const char *text, int line, int column) {
+    int current_line = 0;
+    int current_col = 0;
+    int i = 0;
+
+    while (text[i] != '\0') {
+        if (current_line == line && current_col == column) {
+            return i;
+        }
+        if (text[i] == '\n') {
+            if (current_line == line) {
+                return i;
+            }
+            current_line++;
+            current_col = 0;
+        } else {
+            current_col++;
+        }
+        i++;
+    }
+    return i;
+}
+
+static int GetLineStart(const char *text, int pos) {
+    int i = pos;
+    while (i > 0 && text[i - 1] != '\n') {
+        i--;
+    }
+    return i;
+}
+
+static int GetLineEnd(const char *text, int pos) {
+    int i = pos;
+    int len = strlen(text);
+    while (i < len && text[i] != '\n') {
+        i++;
+    }
+    return i;
+}
+
+static int CountLines(const char *text) {
+    int count = 1;
+    for (int i = 0; text[i] != '\0'; i++) {
+        if (text[i] == '\n') count++;
+    }
+    return count;
+}
+
+static int MeasureTextRange(const char *text, int start, int end, int font_size) {
+    if (start >= end) return 0;
+
+    char temp[1024];
+    int len = TA_Min_Int(end - start, 1023);
+    strncpy(temp, text + start, len);
+    temp[len] = '\0';
+
+    return MeasureTextEx(GuiGetFont(), temp, font_size, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE).x;
+}
+
+static int GetCharIndexFromPos(const char *text, Rectangle bounds, Vector2 pos,
+                                boolean wrap, float scroll_y, int font_size, int line_height) {
+    if (!text || strlen(text) == 0) return 0;
+
+    float content_x = bounds.x + TEXT_AREA_PADDING;
+    float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y;
+    float content_width = bounds.width - TEXT_AREA_PADDING * 2;
+
+    int text_len = strlen(text);
+
+    float click_line_y = (pos.y - content_y) / line_height;
+    int target_visual_line = (int)click_line_y;
+    if (target_visual_line < 0) target_visual_line = 0;
+
+    int current_visual_line = 0;
+    int i = 0;
+    int line_char_start = 0;
+
+    while (i <= text_len) {
+        boolean is_newline = (i < text_len && text[i] == '\n');
+
+        if (wrap && i > line_char_start) {
+            int line_width = MeasureTextRange(text, line_char_start, i, font_size);
+            if (line_width > content_width && i > line_char_start + 1) {
+                int wrap_pos = i - 1;
+                for (int j = i - 1; j > line_char_start; j--) {
+                    if (text[j] == ' ') {
+                        wrap_pos = j;
+                        break;
+                    }
+                }
+
+                if (current_visual_line == target_visual_line) {
+                    float click_x = pos.x - content_x;
+                    int best_pos = line_char_start;
+                    float best_dist = 99999;
+
+                    for (int k = line_char_start; k <= wrap_pos; k++) {
+                        int char_x = MeasureTextRange(text, line_char_start, k, font_size);
+                        float dist = (float)(click_x - char_x);
+                        if (dist < 0) dist = -dist;
+                        if (dist < best_dist) {
+                            best_dist = dist;
+                            best_pos = k;
+                        }
+                    }
+                    return best_pos;
+                }
+
+                current_visual_line++;
+                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
+                i = line_char_start;
+                continue;
+            }
+        }
+
+        if (is_newline || i == text_len) {
+            if (current_visual_line == target_visual_line || i == text_len) {
+                float click_x = pos.x - content_x;
+                int line_end = i;
+                int best_pos = line_char_start;
+                float best_dist = 99999;
+
+                for (int k = line_char_start; k <= line_end; k++) {
+                    int char_x = MeasureTextRange(text, line_char_start, k, font_size);
+                    float dist = (float)(click_x - char_x);
+                    if (dist < 0) dist = -dist;
+                    if (dist < best_dist) {
+                        best_dist = dist;
+                        best_pos = k;
+                    }
+                }
+                return TA_Min_Int(best_pos, text_len);
+            }
+
+            current_visual_line++;
+            line_char_start = i + 1;
+        }
+
+        i++;
+    }
+
+    return text_len;
+}
+
+static Vector2 GetCursorScreenPos(const char *text, int cursor_pos, Rectangle bounds,
+                                   boolean wrap, float scroll_y, int font_size, int line_height) {
+    float content_x = bounds.x + TEXT_AREA_PADDING;
+    float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y;
+    float content_width = bounds.width - TEXT_AREA_PADDING * 2;
+
+    if (!text || cursor_pos == 0) {
+        return (Vector2){content_x, content_y};
+    }
+
+    int text_len = strlen(text);
+    cursor_pos = TA_Min_Int(cursor_pos, text_len);
+
+    int visual_line = 0;
+    int line_char_start = 0;
+
+    for (int i = 0; i <= cursor_pos; i++) {
+        if (i == cursor_pos) {
+            float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
+            float y = content_y + visual_line * line_height;
+            return (Vector2){x, y};
+        }
+
+        if (text[i] == '\n') {
+            visual_line++;
+            line_char_start = i + 1;
+        } else if (wrap) {
+            int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size);
+            if (line_width > content_width && i > line_char_start) {
+                int wrap_pos = i;
+                for (int j = i; j > line_char_start; j--) {
+                    if (text[j] == ' ') {
+                        wrap_pos = j;
+                        break;
+                    }
+                }
+
+                if (cursor_pos <= wrap_pos) {
+                    float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
+                    float y = content_y + visual_line * line_height;
+                    return (Vector2){x, y};
+                }
+
+                visual_line++;
+                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
+            }
+        }
+    }
+
+    float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size);
+    float y = content_y + visual_line * line_height;
+    return (Vector2){x, y};
+}
+
+static float GetContentHeight(const char *text, Rectangle bounds, boolean wrap,
+                               int font_size, int line_height) {
+    if (!text || strlen(text) == 0) return line_height;
+
+    float content_width = bounds.width - TEXT_AREA_PADDING * 2;
+    int visual_lines = 1;
+    int line_char_start = 0;
+    int text_len = strlen(text);
+
+    for (int i = 0; i <= text_len; i++) {
+        if (i == text_len || text[i] == '\n') {
+            visual_lines++;
+            line_char_start = i + 1;
+        } else if (wrap) {
+            int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size);
+            if (line_width > content_width && i > line_char_start) {
+                int wrap_pos = i;
+                for (int j = i; j > line_char_start; j--) {
+                    if (text[j] == ' ') {
+                        wrap_pos = j;
+                        break;
+                    }
+                }
+                visual_lines++;
+                line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos;
+            }
+        }
+    }
+
+    return visual_lines * line_height;
+}
+
+static void PushUndoState(TextAreaState *state, const char *text, Dowa_Arena *arena) {
+    TextAreaUndoEntry entry;
+    entry.text = Dowa_String_Copy_Arena((char*)text, arena);
+    entry.cursor_pos = state->cursor_pos;
+    entry.selection_start = state->selection_start;
+    entry.selection_end = state->selection_end;
+
+    if (state->undo_index < (int)Dowa_Array_Length(state->undo_stack) - 1) {
+        dowa__header(state->undo_stack)->length = state->undo_index + 1;
+    }
+
+    Dowa_Array_Push_Arena(state->undo_stack, entry, arena);
+    state->undo_index = Dowa_Array_Length(state->undo_stack) - 1;
+
+    if (Dowa_Array_Length(state->undo_stack) > TEXT_AREA_MAX_UNDO_STATES) {
+        for (int i = 0; i < (int)Dowa_Array_Length(state->undo_stack) - 1; i++) {
+            state->undo_stack[i] = state->undo_stack[i + 1];
+        }
+        dowa__header(state->undo_stack)->length--;
+        state->undo_index--;
+    }
+}
+
+static boolean PerformUndo(TextAreaState *state, char *text, int text_size) {
+    if (state->undo_index <= 0) return FALSE;
+
+    state->undo_index--;
+    TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index];
+
+    strncpy(text, entry->text, text_size - 1);
+    text[text_size - 1] = '\0';
+    state->cursor_pos = entry->cursor_pos;
+    state->selection_start = entry->selection_start;
+    state->selection_end = entry->selection_end;
+
+    return TRUE;
+}
+
+static boolean PerformRedo(TextAreaState *state, char *text, int text_size) {
+    if (state->undo_index >= (int)Dowa_Array_Length(state->undo_stack) - 1) return FALSE;
+
+    state->undo_index++;
+    TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index];
+
+    strncpy(text, entry->text, text_size - 1);
+    text[text_size - 1] = '\0';
+    state->cursor_pos = entry->cursor_pos;
+    state->selection_start = entry->selection_start;
+    state->selection_end = entry->selection_end;
+
+    return TRUE;
+}
+
+static void InsertTextAtCursor(char *text, int text_size, int cursor_pos,
+                                const char *insert_text, int *new_cursor_pos) {
+    int text_len = strlen(text);
+    int insert_len = strlen(insert_text);
+
+    if (text_len + insert_len >= text_size - 1) {
+        insert_len = text_size - 1 - text_len;
+        if (insert_len <= 0) return;
+    }
+
+    memmove(text + cursor_pos + insert_len,
+            text + cursor_pos,
+            text_len - cursor_pos + 1);
+
+    memcpy(text + cursor_pos, insert_text, insert_len);
+
+    *new_cursor_pos = cursor_pos + insert_len;
+}
+
+static void DeleteTextRange(char *text, int start, int end) {
+    if (start >= end) return;
+    int text_len = strlen(text);
+    if (start < 0) start = 0;
+    if (end > text_len) end = text_len;
+
+    memmove(text + start, text + end, text_len - end + 1);
+}
+
+static char* GetSelectedText(const char *text, int sel_start, int sel_end, Dowa_Arena *arena) {
+    if (sel_start < 0 || sel_end < 0 || sel_start >= sel_end) return NULL;
+
+    int len = sel_end - sel_start;
+    char *result = Dowa_Arena_Allocate(arena, len + 1);
+    strncpy(result, text + sel_start, len);
+    result[len] = '\0';
+    return result;
+}
+
+boolean GuiTextArea(int id, Rectangle bounds, char *text, int text_size, boolean is_edit_mode,
+                    boolean should_text_wrap, Dowa_Arena *arena) {
+    boolean should_toggle_edit_mode = FALSE;
+
+    // Get or create state for this text area
+    TextAreaState *state = GetTextAreaState(id);
+    if (!state) {
+        state = CreateTextAreaState(id);
+        state->cursor_pos = strlen(text);
+        state->last_blink_time = GetTime();
+        PushUndoState(state, text, arena);
+    }
+
+    int text_len = strlen(text);
+    Vector2 mouse_pos = GetMousePosition();
+    boolean mouse_in_bounds = CheckCollisionPointRec(mouse_pos, bounds);
+
+    // Handle click to enter/exit edit mode
+    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+        if (mouse_in_bounds && !is_edit_mode) {
+            should_toggle_edit_mode = TRUE;
+        } else if (!mouse_in_bounds && is_edit_mode) {
+            should_toggle_edit_mode = TRUE;
+        }
+    }
+
+    // Content area
+    float content_height = GetContentHeight(text, bounds, should_text_wrap,
+                                            TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT);
+    float visible_height = bounds.height - TEXT_AREA_PADDING * 2;
+    float max_scroll = TA_Max_Float(0, content_height - visible_height);
+
+    // Handle scrolling
+    float wheel = GetMouseWheelMove();
+    if (mouse_in_bounds && wheel != 0) {
+        state->scroll_offset_y -= wheel * TEXT_AREA_LINE_HEIGHT * 3;
+        state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll));
+    }
+
+    if (is_edit_mode) {
+        boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) ||
+                               IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER);
+        boolean shift_pressed = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
+        boolean text_changed = FALSE;
+
+        double current_time = GetTime();
+        if (current_time - state->last_blink_time > 0.5) {
+            state->cursor_visible = !state->cursor_visible;
+            state->last_blink_time = current_time;
+        }
+
+        // Mouse Selection
+        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mouse_in_bounds) {
+            int click_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap,
+                                                 state->scroll_offset_y, TEXT_AREA_FONT_SIZE,
+                                                 TEXT_AREA_LINE_HEIGHT);
+            state->cursor_pos = click_pos;
+            state->selection_start = -1;
+            state->selection_end = -1;
+            state->is_selecting = TRUE;
+            state->cursor_visible = TRUE;
+            state->last_blink_time = current_time;
+        }
+
+        if (state->is_selecting && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
+            int drag_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap,
+                                                state->scroll_offset_y, TEXT_AREA_FONT_SIZE,
+                                                TEXT_AREA_LINE_HEIGHT);
+            if (drag_pos != state->cursor_pos) {
+                if (state->selection_start < 0) {
+                    state->selection_start = state->cursor_pos;
+                }
+                state->selection_end = drag_pos;
+            }
+        }
+
+        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) {
+            state->is_selecting = FALSE;
+            if (state->selection_start >= 0 && state->selection_end >= 0) {
+                if (state->selection_start > state->selection_end) {
+                    int temp = state->selection_start;
+                    state->selection_start = state->selection_end;
+                    state->selection_end = temp;
+                }
+                if (state->selection_start == state->selection_end) {
+                    state->selection_start = -1;
+                    state->selection_end = -1;
+                }
+            }
+        }
+
+        // Ctrl+A: Select All
+        if (ctrl_pressed && IsKeyPressed(KEY_A)) {
+            state->selection_start = 0;
+            state->selection_end = text_len;
+            state->cursor_pos = text_len;
+        }
+
+        // Ctrl+C: Copy
+        if (ctrl_pressed && IsKeyPressed(KEY_C)) {
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                char *selected = GetSelectedText(text, sel_min, sel_max, arena);
+                if (selected) {
+                    if (g_clipboard_text) free(g_clipboard_text);
+                    g_clipboard_text = strdup(selected);
+                    SetClipboardText(g_clipboard_text);
+                }
+            }
+        }
+
+        // Ctrl+X: Cut
+        if (ctrl_pressed && IsKeyPressed(KEY_X)) {
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                char *selected = GetSelectedText(text, sel_min, sel_max, arena);
+                if (selected) {
+                    if (g_clipboard_text) free(g_clipboard_text);
+                    g_clipboard_text = strdup(selected);
+                    SetClipboardText(g_clipboard_text);
+                }
+
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, sel_min, sel_max);
+                state->cursor_pos = sel_min;
+                state->selection_start = -1;
+                state->selection_end = -1;
+                text_changed = TRUE;
+            }
+        }
+
+        // Ctrl+V: Paste
+        if (ctrl_pressed && IsKeyPressed(KEY_V)) {
+            const char *clipboard = GetClipboardText();
+            if (clipboard && strlen(clipboard) > 0) {
+                if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                    state->selection_start != state->selection_end) {
+                    int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                    int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                    PushUndoState(state, text, arena);
+                    DeleteTextRange(text, sel_min, sel_max);
+                    state->cursor_pos = sel_min;
+                    state->selection_start = -1;
+                    state->selection_end = -1;
+                } else {
+                    PushUndoState(state, text, arena);
+                }
+
+                int new_cursor;
+                InsertTextAtCursor(text, text_size, state->cursor_pos, clipboard, &new_cursor);
+                state->cursor_pos = new_cursor;
+                text_changed = TRUE;
+            }
+        }
+
+        // Ctrl+Z: Undo / Ctrl+Shift+Z: Redo
+        if (ctrl_pressed && IsKeyPressed(KEY_Z)) {
+            if (shift_pressed) {
+                PerformRedo(state, text, text_size);
+            } else {
+                PerformUndo(state, text, text_size);
+            }
+        }
+
+        // Arrow Keys Navigation
+        if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) {
+            if (state->selection_start >= 0 && !shift_pressed) {
+                state->cursor_pos = TA_Min_Int(state->selection_start, state->selection_end);
+                state->selection_start = -1;
+                state->selection_end = -1;
+            } else if (state->cursor_pos > 0) {
+                if (shift_pressed) {
+                    if (state->selection_start < 0) {
+                        state->selection_start = state->cursor_pos;
+                        state->selection_end = state->cursor_pos;
+                    }
+                    state->cursor_pos--;
+                    state->selection_end = state->cursor_pos;
+                } else {
+                    state->cursor_pos--;
+                }
+            }
+            state->cursor_visible = TRUE;
+            state->last_blink_time = current_time;
+        }
+
+        if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) {
+            if (state->selection_start >= 0 && !shift_pressed) {
+                state->cursor_pos = TA_Max_Int(state->selection_start, state->selection_end);
+                state->selection_start = -1;
+                state->selection_end = -1;
+            } else if (state->cursor_pos < text_len) {
+                if (shift_pressed) {
+                    if (state->selection_start < 0) {
+                        state->selection_start = state->cursor_pos;
+                        state->selection_end = state->cursor_pos;
+                    }
+                    state->cursor_pos++;
+                    state->selection_end = state->cursor_pos;
+                } else {
+                    state->cursor_pos++;
+                }
+            }
+            state->cursor_visible = TRUE;
+            state->last_blink_time = current_time;
+        }
+
+        if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) {
+            int line, col;
+            GetLineAndColumn(text, state->cursor_pos, &line, &col);
+            if (line > 0) {
+                int new_pos = GetPosFromLineColumn(text, line - 1, col);
+                if (shift_pressed) {
+                    if (state->selection_start < 0) {
+                        state->selection_start = state->cursor_pos;
+                        state->selection_end = state->cursor_pos;
+                    }
+                    state->selection_end = new_pos;
+                } else {
+                    state->selection_start = -1;
+                    state->selection_end = -1;
+                }
+                state->cursor_pos = new_pos;
+            }
+            state->cursor_visible = TRUE;
+            state->last_blink_time = current_time;
+        }
+
+        if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) {
+            int line, col;
+            GetLineAndColumn(text, state->cursor_pos, &line, &col);
+            int total_lines = CountLines(text);
+            if (line < total_lines - 1) {
+                int new_pos = GetPosFromLineColumn(text, line + 1, col);
+                if (shift_pressed) {
+                    if (state->selection_start < 0) {
+                        state->selection_start = state->cursor_pos;
+                        state->selection_end = state->cursor_pos;
+                    }
+                    state->selection_end = new_pos;
+                } else {
+                    state->selection_start = -1;
+                    state->selection_end = -1;
+                }
+                state->cursor_pos = new_pos;
+            }
+            state->cursor_visible = TRUE;
+            state->last_blink_time = current_time;
+        }
+
+        // Home/End keys
+        if (IsKeyPressed(KEY_HOME)) {
+            int line_start = GetLineStart(text, state->cursor_pos);
+            if (shift_pressed) {
+                if (state->selection_start < 0) {
+                    state->selection_start = state->cursor_pos;
+                    state->selection_end = state->cursor_pos;
+                }
+                state->selection_end = line_start;
+            } else {
+                state->selection_start = -1;
+                state->selection_end = -1;
+            }
+            state->cursor_pos = line_start;
+        }
+
+        if (IsKeyPressed(KEY_END)) {
+            int line_end = GetLineEnd(text, state->cursor_pos);
+            if (shift_pressed) {
+                if (state->selection_start < 0) {
+                    state->selection_start = state->cursor_pos;
+                    state->selection_end = state->cursor_pos;
+                }
+                state->selection_end = line_end;
+            } else {
+                state->selection_start = -1;
+                state->selection_end = -1;
+            }
+            state->cursor_pos = line_end;
+        }
+
+        // Text Input
+        if (!ctrl_pressed) {
+            int key = GetCharPressed();
+            while (key > 0) {
+                if (key >= 32 && key <= 126) {
+                    if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                        state->selection_start != state->selection_end) {
+                        int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                        int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                        PushUndoState(state, text, arena);
+                        DeleteTextRange(text, sel_min, sel_max);
+                        state->cursor_pos = sel_min;
+                        state->selection_start = -1;
+                        state->selection_end = -1;
+                        text_changed = TRUE;
+                    } else if (!text_changed) {
+                        PushUndoState(state, text, arena);
+                    }
+
+                    char insert_str[2] = {(char)key, '\0'};
+                    int new_cursor;
+                    InsertTextAtCursor(text, text_size, state->cursor_pos, insert_str, &new_cursor);
+                    state->cursor_pos = new_cursor;
+                    text_changed = TRUE;
+                }
+                key = GetCharPressed();
+            }
+        }
+
+        // Enter key
+        if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, sel_min, sel_max);
+                state->cursor_pos = sel_min;
+                state->selection_start = -1;
+                state->selection_end = -1;
+            } else {
+                PushUndoState(state, text, arena);
+            }
+
+            int new_cursor;
+            InsertTextAtCursor(text, text_size, state->cursor_pos, "\n", &new_cursor);
+            state->cursor_pos = new_cursor;
+            text_changed = TRUE;
+        }
+
+        // Backspace
+        if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) {
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, sel_min, sel_max);
+                state->cursor_pos = sel_min;
+                state->selection_start = -1;
+                state->selection_end = -1;
+                text_changed = TRUE;
+            } else if (state->cursor_pos > 0) {
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, state->cursor_pos - 1, state->cursor_pos);
+                state->cursor_pos--;
+                text_changed = TRUE;
+            }
+        }
+
+        // Delete key
+        if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) {
+            text_len = strlen(text);
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, sel_min, sel_max);
+                state->cursor_pos = sel_min;
+                state->selection_start = -1;
+                state->selection_end = -1;
+                text_changed = TRUE;
+            } else if (state->cursor_pos < text_len) {
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, state->cursor_pos, state->cursor_pos + 1);
+                text_changed = TRUE;
+            }
+        }
+
+        // Tab key
+        if (IsKeyPressed(KEY_TAB)) {
+            if (state->selection_start >= 0 && state->selection_end >= 0 &&
+                state->selection_start != state->selection_end) {
+                int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+                int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+                PushUndoState(state, text, arena);
+                DeleteTextRange(text, sel_min, sel_max);
+                state->cursor_pos = sel_min;
+                state->selection_start = -1;
+                state->selection_end = -1;
+            } else {
+                PushUndoState(state, text, arena);
+            }
+
+            int new_cursor;
+            InsertTextAtCursor(text, text_size, state->cursor_pos, "    ", &new_cursor);
+            state->cursor_pos = new_cursor;
+            text_changed = TRUE;
+        }
+
+        // Auto-scroll to keep cursor visible
+        Vector2 cursor_screen = GetCursorScreenPos(text, state->cursor_pos, bounds,
+                                                    should_text_wrap, state->scroll_offset_y,
+                                                    TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT);
+
+        float visible_top = bounds.y + TEXT_AREA_PADDING;
+        float visible_bottom = bounds.y + bounds.height - TEXT_AREA_PADDING - TEXT_AREA_LINE_HEIGHT;
+
+        if (cursor_screen.y < visible_top) {
+            state->scroll_offset_y -= visible_top - cursor_screen.y;
+        } else if (cursor_screen.y > visible_bottom) {
+            state->scroll_offset_y += cursor_screen.y - visible_bottom;
+        }
+
+        state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll));
+    }
+
+    // Drawing
+    DrawRectangleRec(bounds, is_edit_mode ? DARKGRAY : (Color){40, 40, 40, 255});
+    DrawRectangleLinesEx(bounds, 1, is_edit_mode ? WHITE : GRAY);
+
+    BeginScissorMode((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height);
+
+    float content_x = bounds.x + TEXT_AREA_PADDING;
+    float content_y = bounds.y + TEXT_AREA_PADDING - state->scroll_offset_y;
+    float content_width = bounds.width - TEXT_AREA_PADDING * 2;
+
+    text_len = strlen(text);
+
+    // Draw selection highlight
+    if (state->selection_start >= 0 && state->selection_end >= 0 &&
+        state->selection_start != state->selection_end) {
+        int sel_min = TA_Min_Int(state->selection_start, state->selection_end);
+        int sel_max = TA_Max_Int(state->selection_start, state->selection_end);
+
+        int line_char_start = 0;
+        int visual_line = 0;
+
+        for (int i = 0; i <= text_len; i++) {
+            boolean is_end = (i == text_len);
+            boolean is_newline = (!is_end && text[i] == '\n');
+            boolean should_draw_line = is_end || is_newline;
+
+            if (should_text_wrap && !is_end && !is_newline) {
+                int line_width = MeasureTextRange(text, line_char_start, i + 1, TEXT_AREA_FONT_SIZE);
+                if (line_width > content_width && i > line_char_start) {
+                    should_draw_line = TRUE;
+                }
+            }
+
+            if (should_draw_line) {
+                int line_end = i;
+
+                if (sel_min < line_end && sel_max > line_char_start) {
+                    int highlight_start = TA_Max_Int(sel_min, line_char_start);
+                    int highlight_end = TA_Min_Int(sel_max, line_end);
+
+                    float x1 = content_x + MeasureTextRange(text, line_char_start, highlight_start, TEXT_AREA_FONT_SIZE);
+                    float x2 = content_x + MeasureTextRange(text, line_char_start, highlight_end, TEXT_AREA_FONT_SIZE);
+                    float y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT;
+
+                    DrawRectangle((int)x1, (int)y, (int)(x2 - x1), TEXT_AREA_LINE_HEIGHT,
+                                  Fade(SKYBLUE, 0.5f));
+                }
+
+                visual_line++;
+                line_char_start = i + 1;
+            }
+        }
+    }
+
+    // Draw text with wrapping support
+    if (should_text_wrap) {
+        int line_char_start = 0;
+        int visual_line = 0;
+
+        for (int i = 0; i <= text_len; i++) {
+            boolean is_end = (i == text_len);
+            boolean is_newline = (!is_end && text[i] == '\n');
+            boolean should_draw_line = is_end || is_newline;
+
+            if (!is_end && !is_newline) {
+                int line_width = MeasureTextRange(text, line_char_start, i + 1, TEXT_AREA_FONT_SIZE);
+                if (line_width > content_width && i > line_char_start) {
+                    should_draw_line = TRUE;
+                }
+            }
+
+            if (should_draw_line && i > line_char_start) {
+                char line_buffer[1024];
+                int line_len = TA_Min_Int(i - line_char_start, 1023);
+                strncpy(line_buffer, text + line_char_start, line_len);
+                line_buffer[line_len] = '\0';
+
+                Vector2 draw_text_vector = {
+                  .x = content_x,
+                  .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT
+                };
+                DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector,
+                        TEXT_AREA_FONT_SIZE, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE, WHITE);
+
+                visual_line++;
+                line_char_start = is_newline ? i + 1 : i;
+            } else if (is_newline) {
+                visual_line++;
+                line_char_start = i + 1;
+            }
+        }
+    } else {
+        int line_start = 0;
+        int visual_line = 0;
+
+        for (int i = 0; i <= text_len; i++) {
+            if (i == text_len || text[i] == '\n') {
+                if (i > line_start) {
+                    char line_buffer[1024];
+                    int line_len = TA_Min_Int(i - line_start, 1023);
+                    strncpy(line_buffer, text + line_start, line_len);
+                    line_buffer[line_len] = '\0';
+
+                    Vector2 draw_text_vector = {
+                      .x = content_x,
+                      .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT
+                    };
+                    DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector ,
+                            TEXT_AREA_FONT_SIZE, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE, WHITE);
+                }
+                visual_line++;
+                line_start = i + 1;
+            }
+        }
+    }
+
+    // Draw cursor
+    if (is_edit_mode && state->cursor_visible) {
+        Vector2 cursor_pos = GetCursorScreenPos(text, state->cursor_pos, bounds,
+                                                 should_text_wrap, state->scroll_offset_y,
+                                                 TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT);
+
+        DrawRectangle((int)cursor_pos.x, (int)cursor_pos.y,
+                      TEXT_AREA_CURSOR_WIDTH, TEXT_AREA_LINE_HEIGHT, WHITE);
+    }
+
+    EndScissorMode();
+
+    // Draw scrollbar if needed
+    if (max_scroll > 0) {
+        float scrollbar_height = (visible_height / content_height) * visible_height;
+        float scrollbar_y = bounds.y + TEXT_AREA_PADDING +
+                           (state->scroll_offset_y / max_scroll) * (visible_height - scrollbar_height);
+
+        DrawRectangle((int)(bounds.x + bounds.width - 8), (int)scrollbar_y,
+                      6, (int)scrollbar_height, Fade(WHITE, 0.3f));
+    }
+
+    return should_toggle_edit_mode;
+}
+
+void GuiTextAreaResetState(int id) {
+    TextAreaState *state = GetTextAreaState(id);
+    if (state) {
+        if (state->undo_stack) {
+            Dowa_Array_Free(state->undo_stack);
+            state->undo_stack = NULL;
+        }
+        state->is_initialized = FALSE;
+    }
+}
+
+void GuiTextAreaResetAllStates(void) {
+    for (int i = 0; i < g_text_area_state_count; i++) {
+        if (g_text_area_states[i].undo_stack) {
+            Dowa_Array_Free(g_text_area_states[i].undo_stack);
+            g_text_area_states[i].undo_stack = NULL;
+        }
+        g_text_area_states[i].is_initialized = FALSE;
+    }
+    g_text_area_state_count = 0;
+}
+
+// ============================================================================
+// End TextArea Component
+// ============================================================================
+
 typedef Dowa_KV(char*, char*) INPUT_HASHMAP;
 
 typedef struct {
@@ -48,7 +1038,7 @@
 
 typedef struct {
   char *filename;
-  char *title; 
+  char *title;
   Rectangle rect;
   long time_modified;
   boolean deleted;
@@ -68,6 +1058,13 @@
   TAB_LENGTH
 } PostDog_Tab_Enum;
 
+// Text area IDs
+#define TEXT_AREA_ID_INPUT_HEADER   1
+#define TEXT_AREA_ID_INPUT_BODY     2
+#define TEXT_AREA_ID_INPUT_PARAMS   3
+#define TEXT_AREA_ID_INPUT_WS       4
+#define TEXT_AREA_ID_RESULT         5
+
 static uint32 counter = 0;
 HistoryItem *history_items = NULL;
 HistoryItem *new_history_items = NULL;
@@ -80,7 +1077,9 @@
 int active_input_tab = 0;
 Seobeo_WebSocket *ws = NULL;
 boolean WS_BREAK = FALSE;
+pthread_t websocket_thread_id;
 Color TEXT_COLOR = BLACK;
+boolean LOADING = FALSE;
 
 int CompareHistoryItemsByDate(const void *a, const void *b) {
   HistoryItem *itemA = (HistoryItem *)a;
@@ -187,6 +1186,8 @@
   return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
 }
 
+// -------- END of UI ---- //
+
 char *PostDog_Enum_To_String(int active_enum)
 {
   switch(active_enum)
@@ -226,7 +1227,7 @@
   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);
+  char *title = Dowa_Arena_Allocate(arena, strlen(method) + strlen(url_input_text) + 2);
   sprintf(title, "%s %s", method, url_input_text);
   snprintf(
       new_file,
@@ -259,15 +1260,15 @@
   if (!filename)
   {
     perror("Error opening file");
-    exit(EXIT_FAILURE); 
+    exit(EXIT_FAILURE);
   }
   char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
   if (!uuid4)
   {
     perror("Error uuid");
-    exit(EXIT_FAILURE); 
+    exit(EXIT_FAILURE);
   }
-  
+
   int32 seed = (uint32)time(NULL) ^ counter++;
   Dowa_String_UUID(seed, uuid4);
   snprintf(filename, 1024, "%s.txt", uuid4);
@@ -275,24 +1276,15 @@
 
   HistoryItem item = {0};
   item.filename = strdup(filename);
-  item.title = title;
+  item.title = strdup(title);
   item.deleted = FALSE;
+
   Dowa_Array_Push(new_history_items, item);
-  
   Dowa_Arena_Free(arena);
 }
 
-int PostDog_Websocket_Send(void)
+void PostDog_Websocket_Listen(void)
 {
-  if (!ws)
-    ws = Seobeo_WebSocket_Connect(url_input_text);
-
-  printf("URL %s\n", url_input_text);
-  if (Seobeo_WebSocket_Send_Text(ws, input_body_array[active_input_tab]) < 0)
-    printf("Failed to send message\n");
-
-  printf("Receiving responses...\n");
-  int received = 0;
   while (TRUE)
   {
     if (WS_BREAK) break;
@@ -302,50 +1294,72 @@
     {
       if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
       {
-        printf("Response %d: %.*s\n", received + 1, (int)p_msg->length, (char*)p_msg->data);
-        snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text), 
+        printf("Response: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
+        snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
             "\n%s", (char*)p_msg->data);
-        received++;
       }
       Seobeo_WebSocket_Message_Destroy(p_msg);
     }
     usleep(10000);
+    printf("Listening\n");
   }
-  printf("Received %d/%d messages\n", received, 3);
-  Seobeo_WebSocket_Destroy(ws);
+  return;
+}
+
+void PostDog_Websocket_Connect(void)
+{
+  ws = Seobeo_WebSocket_Connect(url_input_text);
+  memset(result_text, 0, strlen(result_text));
 }
 
-void *PostDog_Websocket_Thread(void *arg)
+int PostDog_Websocket_Send(void)
 {
-  PostDog_Websocket_Send();
-  printf("Websocket request finished.\n");
+  if (Seobeo_WebSocket_Send_Text(ws, input_body_array[active_input_tab]) < 0)
+    snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
+        "Failed to send message\n");
+  else
+    snprintf(result_text + strlen(result_text),  RESULT_BUFFER_LENGTH - strlen(result_text),
+        "\n%s", input_body_array[active_input_tab]);
+}
+
+void PostDog_Websocket_Destroy(pthread_t thread_id)
+{
+  Seobeo_WebSocket_Destroy(ws);
+  pthread_detach(thread_id);
+}
+
+void *PostDog_Websocket_Start(void *arg)
+{
+  PostDog_Websocket_Connect();
+  PostDog_Websocket_Listen();
   return NULL;
 }
 
-void PostDog_Websocket_Thread_Send()
+pthread_t PostDog_Websocket_Start_Thread()
 {
   pthread_t thread_id;
-  
-  if (pthread_create(&thread_id, NULL, PostDog_Websocket_Thread, NULL) != 0)
+
+  if (pthread_create(&thread_id, NULL, PostDog_Websocket_Start, NULL) != 0)
   {
     perror("Failed to create thread");
-    return;
+    return 0;
   }
-  pthread_detach(thread_id);
+
+  return thread_id; 
 }
 
 int PostDog_Http_Request(void)
 {
   Seobeo_Client_Request *req = Seobeo_Client_Request_Create(url_input_text);
   Seobeo_Client_Response *res;
-  switch (active_method_dropdown) 
+  switch (active_method_dropdown)
   {
     case 0:
     {
       Seobeo_Client_Request_Set_Method(req, "GET");
       break;
     }
-    case 1: 
+    case 1:
     {
       Seobeo_Client_Request_Set_Method(req, "POST");
       break;
@@ -375,7 +1389,7 @@
         line = strtok(NULL, "\n");
       }
 
-    }  
+    }
     Seobeo_Client_Request_Set_Follow_Redirects(req, TRUE, 5); // TODO: remove magic number;
     res = Seobeo_Client_Request_Execute(req);
 
@@ -391,6 +1405,26 @@
   return 0;
 }
 
+void *PostDog_Http_Thread(void *arg)
+{
+  PostDog_Http_Request();
+  printf("HTTP request finished.\n");
+  LOADING = FALSE;
+  return NULL;
+}
+
+void PostDog_Http_Thread_Request()
+{
+  pthread_t thread_id;
+  LOADING = TRUE;
+  if (pthread_create(&thread_id, NULL, PostDog_Http_Thread, NULL) != 0)
+  {
+    perror("Failed to create thread");
+    return;
+  }
+  pthread_detach(thread_id);
+}
+
 void PostDog_Update_URL(void)
 {
   // Save existing query string if present
@@ -450,6 +1484,9 @@
   active_method_dropdown = 0;
   for (int i = 0; i < Dowa_Array_Length(input_body_array); i++)
     input_body_array[i][0] = '\0';
+
+  // Reset text area states when clearing
+  GuiTextAreaResetAllStates();
 }
 
 void PostDog_Load_File(const char *filename)
@@ -513,6 +1550,9 @@
     }
   }
 
+  // Reset text area states when loading new file
+  GuiTextAreaResetAllStates();
+
   Dowa_Arena_Free(init_arena);
   Dowa_Arena_Free(split_arena);
 }
@@ -623,9 +1663,12 @@
   Image logo_original = LoadImage("postdog/epi_all_colors.png");
   ImageResize(&logo_original, 60, 60);
   SetWindowIcon(logo_original);
-  Texture2D logo_texture = LoadTextureFromImage(logo_original); 
+  Texture2D logo_texture = LoadTextureFromImage(logo_original);
   UnloadImage(logo_original);
 
+  // Arena for text area undo states
+  g_text_area_arena = Dowa_Arena_Create(1024 * 1024 * 8);  // 8MB for undo states
+
   // -- Starting pos ---//
   Rectangle history_sidebar_rect = { 0 };
   Dowa_Array_Reserve(history_items, 10);
@@ -654,6 +1697,7 @@
   snprintf(input_body_array[TAB_BODY], HEADER_BUFFER_LENGTH, "");
 
   result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH);
+  result_text[0] = '\0';
 
   bool method_edit = false;
 
@@ -664,11 +1708,12 @@
   Rectangle input_tab_rect = { 0 };
   Rectangle input_tab_item_rect = { 0 };
   Rectangle input_body_rect = { 0 };
-  bool input_body_bool = false;
+  bool input_body_edit_mode = false;
 
   // -- result --//
   Rectangle result_area_rect = { 0 };
   Rectangle result_body_rect = { 0 };
+  bool result_body_edit_mode = false;
 
   // General styling.
   float padding = 10; // TODO make it % based?
@@ -676,8 +1721,6 @@
 
   // Scroll offsets
   float history_scroll_offset = 0;
-  float input_body_scroll_offset = 0;
-  float result_body_scroll_offset = 0;
 
   while (!WindowShouldClose())
   {
@@ -699,11 +1742,11 @@
       .x = history_sidebar_rect.x,
       .y = history_sidebar_rect.y,
       .width = history_sidebar_rect.width,
-      .height = 80 
+      .height = 80
     };
 
     Rectangle history_list_area_rect = Below(logo_area_rect, padding);
-    history_list_area_rect.x += padding; 
+    history_list_area_rect.x += padding;
     history_list_area_rect.width = history_sidebar_rect.width -  (2 * padding);
     history_list_area_rect.height = history_sidebar_rect.height - logo_area_rect.height - padding;
 
@@ -716,7 +1759,7 @@
     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];
+        &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length];
 
       if (curr_history_items->deleted)
       {
@@ -794,10 +1837,9 @@
     Vector2 mouse_position = GetMousePosition();
     float mouse_wheel = GetMouseWheelMove();
 
-    // Reset input body scroll when tab changes
+    // Reset text area state when tab changes
     if (prev_input_tab != active_input_tab)
     {
-      input_body_scroll_offset = 0;
       prev_input_tab = active_input_tab;
     }
 
@@ -810,18 +1852,22 @@
       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_rect) && mouse_wheel != 0) {
-      input_body_scroll_offset += mouse_wheel * 30;
-      if (input_body_scroll_offset > 0) input_body_scroll_offset = 0;
-    }
+    // Reset
+    SetMouseCursor(MOUSE_CURSOR_DEFAULT);
 
-    // Handle scroll wheel for result body
-    if (InArea(mouse_position, result_body_rect) && mouse_wheel != 0) {
-      result_body_scroll_offset += mouse_wheel * 30;
-      if (result_body_scroll_offset > 0) result_body_scroll_offset = 0;
-    }
+    // TODO: Move all for loop rect up here so it does not flicker.
+    if (
+        InArea(mouse_position, result_area_rect) ||
+        InArea(mouse_position, input_tab_rect) ||
+        InArea(mouse_position, url_input_bounds_rect) ||
+        InArea(mouse_position, url_enter_button_rect) ||
+        InArea(mouse_position, method_dropdown_rect) ||
+        InArea(mouse_position, logo_area_rect)
+    )
+      SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
 
+    if (Clicked(mouse_position, logo_area_rect))
+      PostDog_Params_Reset();
     BeginDrawing();
       ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
 
@@ -845,8 +1891,8 @@
       BeginScissorMode(history_list_area_rect.x, history_list_area_rect.y, history_list_area_rect.width, history_list_area_rect.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];
+          HistoryItem *curr_history_items = i < new_history_items_length ?
+            &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length];
 
           if (curr_history_items->deleted)
             continue;
@@ -855,7 +1901,7 @@
           DrawRectangleRounded(curr_history_items->rect, 0.5, 1, Fade(BLACK, 0.1f));
           Rectangle filename_area_rect = curr_history_items->rect;
 
-          filename_area_rect.height -= diff; 
+          filename_area_rect.height -= diff;
           Rectangle icon_area = Below(filename_area_rect, 0);
           icon_area.height = diff;
 
@@ -866,7 +1912,14 @@
           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, AddPaddingHorizontal(curr_history_items->rect, padding), TEXT_ALIGN_MIDDLE, BLACK);
+          filename_area_rect.y += 2*padding;
+          GuiDrawText(curr_history_items->title, AddPadding(filename_area_rect, padding), TEXT_ALIGN_CENTER, BLACK);
+          if (
+            InArea(mouse_position, icon_area_left_column) ||
+            InArea(mouse_position, icon_area_right_column)
+          )
+            SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
+
           if (GuiButton(AddPadding(icon_area_left_column, padding), "view"))
             PostDog_Load_File(curr_history_items->filename);
           if (GuiButton(AddPadding(icon_area_right_column, padding), "delete"))
@@ -897,14 +1950,25 @@
       }
 
       // URL area Rect
-      GuiDrawText("URL: ", url_text_bounds_rect, TEXT_ALIGN_CENTER, BLACK); 
+      GuiDrawText("URL: ", url_text_bounds_rect, TEXT_ALIGN_CENTER, BLACK);
       DrawRectangleRec(url_area_rect, Fade(BLACK, 0.1f));
+
       if (GuiTextBox(url_input_bounds_rect, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit))
         url_input_edit = !url_input_edit;
 
+      if (url_input_edit)
+      {
+        if (IsKeyPressed(KEY_ENTER))
+        {
+          PostDog_Http_Thread_Request();
+          url_input_edit = !url_input_edit;
+        }
+      }
+
       sendRequest = GuiButton(url_enter_button_rect, "ENTER");
       if (sendRequest)
-        PostDog_Http_Request();
+        PostDog_Http_Thread_Request();
+
       if (GuiDropdownBox(method_dropdown_rect, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit))
         method_edit = !method_edit;
 
@@ -912,87 +1976,54 @@
       DrawRectangleRec(input_area_rect, Fade(BLUE, 0.1f));
       DrawRectangleRec(input_tab_rect,  Fade(DARKBLUE, 0.1f));
       GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
-      GuiDrawRectangle(input_body_rect, 1, GetColor(GuiGetStyle(TEXTBOX, BORDER)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
+
+      int text_area_id = TEXT_AREA_ID_INPUT_HEADER + active_input_tab;
+      if (GuiTextArea(text_area_id, input_body_rect, input_body_array[active_input_tab],
+                                   BODY_BUFFER_LENGTH, input_body_edit_mode, TRUE, g_text_area_arena))
+        input_body_edit_mode = !input_body_edit_mode;
 
-      BeginScissorMode(input_body_rect.x, input_body_rect.y, input_body_rect.width, input_body_rect.height);
-        Rectangle scrolled_input_rect = AddPadding(input_body_rect, padding * 2);
-        scrolled_input_rect.y += input_body_scroll_offset;
-        scrolled_input_rect.height = MAX_SCROLL_HEIGHT;
-        if (active_input_tab != TAB_WEBSOCKET)
-        {
-          WS_BREAK = TRUE;
-          if (JUNE_GuiTextBox(scrolled_input_rect, input_body_array[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
-            input_body_bool = !input_body_bool;
-        }
-        else
+      if (active_input_tab != TAB_WEBSOCKET)
+      {
+        WS_BREAK = TRUE;
+        if (websocket_thread_id)
+          PostDog_Websocket_Destroy(websocket_thread_id);
+      }
+      else
+      {
+        WS_BREAK = FALSE;
+        Rectangle temp = {
+          .x = input_body_rect.x + input_body_rect.width - (3 * padding) - 100,
+          .y = input_body_rect.y + input_body_rect.height - (3 * padding) - 20,
+          .width = 100,
+          .height = 40
+        };
+        if (GuiButton(temp, "Send") || (input_body_edit_mode && IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_ENTER)))
         {
-          boolean temp = true;
-          WS_BREAK = FALSE;
-          if (GuiTextInputBox(
-                input_body_rect,
-                "send message", ws != NULL ? "connected" : "start messaging",
-                "send", input_body_array[active_input_tab], BODY_BUFFER_LENGTH, &temp) == 1)
-            PostDog_Websocket_Thread_Send();
+          if (!ws)
+            websocket_thread_id = PostDog_Websocket_Start_Thread();
+          usleep(10000);
+          PostDog_Websocket_Send();
         }
-      EndScissorMode();
-
-      if (input_body_scroll_offset < 0) {
-        float scrollbar_height = 10; 
-        float scrollbar_y = input_body_rect.y - (input_body_scroll_offset / MAX_SCROLL_HEIGHT) * (input_body_rect.height - scrollbar_height);
-        Rectangle scrollbar_rect = {
-          input_body_rect.x + input_body_rect.width - 5,
-          scrollbar_y,
-          5,
-          scrollbar_height
-        };
-        DrawRectangleRec(scrollbar_rect, Fade(BLUE, 0.5f));
       }
 
+
       GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab);
-
       PostDog_Update_URL();
 
-      // Result Rect
-      DrawRectangleRec(result_area_rect, Fade(GREEN, 0.1f));
-      DrawRectangleRec(result_body_rect, Fade(DARKGREEN, 0.1f));
-
-      // Create scrollable result body with offset
-      BeginScissorMode(result_body_rect.x, result_body_rect.y, result_body_rect.width, result_body_rect.height);
-        Rectangle scrolled_result = result_body_rect;
-        scrolled_result.y += result_body_scroll_offset;
-        scrolled_result.height = MAX_SCROLL_HEIGHT;
-        GuiTextBoxMulti(scrolled_result, result_text, RESULT_BUFFER_LENGTH, false);
-      EndScissorMode();
+      // TODO: Add animations.
+      DrawRectangleRec(result_area_rect, LOADING ? Fade(RED, 0.1f) : Fade(GREEN, 0.1f));
+      boolean result_toggle = GuiTextArea(TEXT_AREA_ID_RESULT, result_body_rect, result_text,
+                                          RESULT_BUFFER_LENGTH, result_body_edit_mode, TRUE, g_text_area_arena);
 
-      if (result_body_scroll_offset < 0)
-      {
-        float scrollbar_height = 10;
-        float scrollbar_y = result_body_rect.y - (result_body_scroll_offset / MAX_SCROLL_HEIGHT) * (result_body_rect.height - scrollbar_height);
-        Rectangle scrollbar_rect = {
-          result_body_rect.x + result_body_rect.width - 5,
-          scrollbar_y,
-          5,
-          scrollbar_height
-        };
-        DrawRectangleRec(scrollbar_rect, Fade(GREEN, 0.5f));
-      }
+      GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab);
+      if (result_toggle)
+        result_body_edit_mode = !result_body_edit_mode;
 
-      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(input_body_array[active_input_tab]);
-      }
-      else if (InArea(mouse_position, result_body_rect))
-      {
-        DrawRectangleRec(result_body_rect, Fade(GREEN, 0.3f));
-        if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
-          SetClipboardText(result_text);
-      }
     EndDrawing();
   }
+
+  GuiTextAreaResetAllStates();
+  Dowa_Arena_Free(g_text_area_arena);
   CloseWindow();
   return 0;
 }