Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 158:1c0878eb17de | 159:05cf9467a1c3 |
|---|---|
| 37 #define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4 | 37 #define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4 |
| 38 #define URL_TEXT_BUFFER_LENGTH 1024 * 10 | 38 #define URL_TEXT_BUFFER_LENGTH 1024 * 10 |
| 39 #define BODY_BUFFER_LENGTH 1024 * 1024 * 5 | 39 #define BODY_BUFFER_LENGTH 1024 * 1024 * 5 |
| 40 #define RESULT_BUFFER_LENGTH 1024 * 1024 * 5 | 40 #define RESULT_BUFFER_LENGTH 1024 * 1024 * 5 |
| 41 | 41 |
| 42 // ============================================================================ | |
| 43 // TextArea Component | |
| 44 // ============================================================================ | |
| 45 | |
| 46 #define TEXT_SIZE_DEFAULT 10 // used to calcualte spacing | |
| 47 #define TEXT_AREA_FONT_SIZE 16 | |
| 48 #define TEXT_AREA_LINE_HEIGHT 20 | |
| 49 #define TEXT_AREA_PADDING 8 | |
| 50 #define TEXT_AREA_CURSOR_WIDTH 2 | |
| 51 #define TEXT_AREA_MAX_UNDO_STATES 64 | |
| 52 #define TEXT_AREA_MAX_INSTANCES 8 | |
| 53 | |
| 54 typedef struct { | |
| 55 char *text; | |
| 56 int cursor_pos; | |
| 57 int selection_start; | |
| 58 int selection_end; | |
| 59 } TextAreaUndoEntry; | |
| 60 | |
| 61 typedef struct { | |
| 62 int id; // Unique ID for this text area | |
| 63 int cursor_pos; // Current cursor position in text | |
| 64 int selection_start; // Selection start (-1 if no selection) | |
| 65 int selection_end; // Selection end (-1 if no selection) | |
| 66 float scroll_offset_y; // Vertical scroll offset | |
| 67 float scroll_offset_x; // Horizontal scroll offset (for non-wrap mode) | |
| 68 boolean is_selecting; // Currently dragging to select | |
| 69 boolean is_initialized; // State has been initialized | |
| 70 | |
| 71 // Undo history | |
| 72 TextAreaUndoEntry *undo_stack; // Dowa array of undo entries | |
| 73 int undo_index; // Current position in undo stack | |
| 74 | |
| 75 // Internal tracking | |
| 76 double last_blink_time; | |
| 77 boolean cursor_visible; | |
| 78 } TextAreaState; | |
| 79 | |
| 80 static TextAreaState g_text_area_states[TEXT_AREA_MAX_INSTANCES] = {0}; | |
| 81 static int g_text_area_state_count = 0; | |
| 82 static char *g_clipboard_text = NULL; | |
| 83 static Dowa_Arena *g_text_area_arena = NULL; | |
| 84 | |
| 85 // Helper functions | |
| 86 static int TA_Min_Int(int a, int b) { return a < b ? a : b; } | |
| 87 static int TA_Max_Int(int a, int b) { return a > b ? a : b; } | |
| 88 static float TA_Min_Float(float a, float b) { return a < b ? a : b; } | |
| 89 static float TA_Max_Float(float a, float b) { return a > b ? a : b; } | |
| 90 | |
| 91 static TextAreaState* GetTextAreaState(int id) { | |
| 92 for (int i = 0; i < g_text_area_state_count; i++) { | |
| 93 if (g_text_area_states[i].id == id && g_text_area_states[i].is_initialized) { | |
| 94 return &g_text_area_states[i]; | |
| 95 } | |
| 96 } | |
| 97 return NULL; | |
| 98 } | |
| 99 | |
| 100 static TextAreaState* CreateTextAreaState(int id) { | |
| 101 if (g_text_area_state_count >= TEXT_AREA_MAX_INSTANCES) { | |
| 102 return &g_text_area_states[0]; // Reuse first slot | |
| 103 } | |
| 104 | |
| 105 TextAreaState *state = &g_text_area_states[g_text_area_state_count++]; | |
| 106 memset(state, 0, sizeof(TextAreaState)); | |
| 107 state->id = id; | |
| 108 state->selection_start = -1; | |
| 109 state->selection_end = -1; | |
| 110 state->undo_index = -1; | |
| 111 state->cursor_visible = TRUE; | |
| 112 state->is_initialized = TRUE; | |
| 113 return state; | |
| 114 } | |
| 115 | |
| 116 static void GetLineAndColumn(const char *text, int pos, int *out_line, int *out_column) { | |
| 117 int line = 0; | |
| 118 int column = 0; | |
| 119 for (int i = 0; i < pos && text[i] != '\0'; i++) { | |
| 120 if (text[i] == '\n') { | |
| 121 line++; | |
| 122 column = 0; | |
| 123 } else { | |
| 124 column++; | |
| 125 } | |
| 126 } | |
| 127 *out_line = line; | |
| 128 *out_column = column; | |
| 129 } | |
| 130 | |
| 131 static int GetPosFromLineColumn(const char *text, int line, int column) { | |
| 132 int current_line = 0; | |
| 133 int current_col = 0; | |
| 134 int i = 0; | |
| 135 | |
| 136 while (text[i] != '\0') { | |
| 137 if (current_line == line && current_col == column) { | |
| 138 return i; | |
| 139 } | |
| 140 if (text[i] == '\n') { | |
| 141 if (current_line == line) { | |
| 142 return i; | |
| 143 } | |
| 144 current_line++; | |
| 145 current_col = 0; | |
| 146 } else { | |
| 147 current_col++; | |
| 148 } | |
| 149 i++; | |
| 150 } | |
| 151 return i; | |
| 152 } | |
| 153 | |
| 154 static int GetLineStart(const char *text, int pos) { | |
| 155 int i = pos; | |
| 156 while (i > 0 && text[i - 1] != '\n') { | |
| 157 i--; | |
| 158 } | |
| 159 return i; | |
| 160 } | |
| 161 | |
| 162 static int GetLineEnd(const char *text, int pos) { | |
| 163 int i = pos; | |
| 164 int len = strlen(text); | |
| 165 while (i < len && text[i] != '\n') { | |
| 166 i++; | |
| 167 } | |
| 168 return i; | |
| 169 } | |
| 170 | |
| 171 static int CountLines(const char *text) { | |
| 172 int count = 1; | |
| 173 for (int i = 0; text[i] != '\0'; i++) { | |
| 174 if (text[i] == '\n') count++; | |
| 175 } | |
| 176 return count; | |
| 177 } | |
| 178 | |
| 179 static int MeasureTextRange(const char *text, int start, int end, int font_size) { | |
| 180 if (start >= end) return 0; | |
| 181 | |
| 182 char temp[1024]; | |
| 183 int len = TA_Min_Int(end - start, 1023); | |
| 184 strncpy(temp, text + start, len); | |
| 185 temp[len] = '\0'; | |
| 186 | |
| 187 return MeasureTextEx(GuiGetFont(), temp, font_size, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE).x; | |
| 188 } | |
| 189 | |
| 190 static int GetCharIndexFromPos(const char *text, Rectangle bounds, Vector2 pos, | |
| 191 boolean wrap, float scroll_y, int font_size, int line_height) { | |
| 192 if (!text || strlen(text) == 0) return 0; | |
| 193 | |
| 194 float content_x = bounds.x + TEXT_AREA_PADDING; | |
| 195 float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y; | |
| 196 float content_width = bounds.width - TEXT_AREA_PADDING * 2; | |
| 197 | |
| 198 int text_len = strlen(text); | |
| 199 | |
| 200 float click_line_y = (pos.y - content_y) / line_height; | |
| 201 int target_visual_line = (int)click_line_y; | |
| 202 if (target_visual_line < 0) target_visual_line = 0; | |
| 203 | |
| 204 int current_visual_line = 0; | |
| 205 int i = 0; | |
| 206 int line_char_start = 0; | |
| 207 | |
| 208 while (i <= text_len) { | |
| 209 boolean is_newline = (i < text_len && text[i] == '\n'); | |
| 210 | |
| 211 if (wrap && i > line_char_start) { | |
| 212 int line_width = MeasureTextRange(text, line_char_start, i, font_size); | |
| 213 if (line_width > content_width && i > line_char_start + 1) { | |
| 214 int wrap_pos = i - 1; | |
| 215 for (int j = i - 1; j > line_char_start; j--) { | |
| 216 if (text[j] == ' ') { | |
| 217 wrap_pos = j; | |
| 218 break; | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 if (current_visual_line == target_visual_line) { | |
| 223 float click_x = pos.x - content_x; | |
| 224 int best_pos = line_char_start; | |
| 225 float best_dist = 99999; | |
| 226 | |
| 227 for (int k = line_char_start; k <= wrap_pos; k++) { | |
| 228 int char_x = MeasureTextRange(text, line_char_start, k, font_size); | |
| 229 float dist = (float)(click_x - char_x); | |
| 230 if (dist < 0) dist = -dist; | |
| 231 if (dist < best_dist) { | |
| 232 best_dist = dist; | |
| 233 best_pos = k; | |
| 234 } | |
| 235 } | |
| 236 return best_pos; | |
| 237 } | |
| 238 | |
| 239 current_visual_line++; | |
| 240 line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; | |
| 241 i = line_char_start; | |
| 242 continue; | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 if (is_newline || i == text_len) { | |
| 247 if (current_visual_line == target_visual_line || i == text_len) { | |
| 248 float click_x = pos.x - content_x; | |
| 249 int line_end = i; | |
| 250 int best_pos = line_char_start; | |
| 251 float best_dist = 99999; | |
| 252 | |
| 253 for (int k = line_char_start; k <= line_end; k++) { | |
| 254 int char_x = MeasureTextRange(text, line_char_start, k, font_size); | |
| 255 float dist = (float)(click_x - char_x); | |
| 256 if (dist < 0) dist = -dist; | |
| 257 if (dist < best_dist) { | |
| 258 best_dist = dist; | |
| 259 best_pos = k; | |
| 260 } | |
| 261 } | |
| 262 return TA_Min_Int(best_pos, text_len); | |
| 263 } | |
| 264 | |
| 265 current_visual_line++; | |
| 266 line_char_start = i + 1; | |
| 267 } | |
| 268 | |
| 269 i++; | |
| 270 } | |
| 271 | |
| 272 return text_len; | |
| 273 } | |
| 274 | |
| 275 static Vector2 GetCursorScreenPos(const char *text, int cursor_pos, Rectangle bounds, | |
| 276 boolean wrap, float scroll_y, int font_size, int line_height) { | |
| 277 float content_x = bounds.x + TEXT_AREA_PADDING; | |
| 278 float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y; | |
| 279 float content_width = bounds.width - TEXT_AREA_PADDING * 2; | |
| 280 | |
| 281 if (!text || cursor_pos == 0) { | |
| 282 return (Vector2){content_x, content_y}; | |
| 283 } | |
| 284 | |
| 285 int text_len = strlen(text); | |
| 286 cursor_pos = TA_Min_Int(cursor_pos, text_len); | |
| 287 | |
| 288 int visual_line = 0; | |
| 289 int line_char_start = 0; | |
| 290 | |
| 291 for (int i = 0; i <= cursor_pos; i++) { | |
| 292 if (i == cursor_pos) { | |
| 293 float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); | |
| 294 float y = content_y + visual_line * line_height; | |
| 295 return (Vector2){x, y}; | |
| 296 } | |
| 297 | |
| 298 if (text[i] == '\n') { | |
| 299 visual_line++; | |
| 300 line_char_start = i + 1; | |
| 301 } else if (wrap) { | |
| 302 int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size); | |
| 303 if (line_width > content_width && i > line_char_start) { | |
| 304 int wrap_pos = i; | |
| 305 for (int j = i; j > line_char_start; j--) { | |
| 306 if (text[j] == ' ') { | |
| 307 wrap_pos = j; | |
| 308 break; | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 if (cursor_pos <= wrap_pos) { | |
| 313 float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); | |
| 314 float y = content_y + visual_line * line_height; | |
| 315 return (Vector2){x, y}; | |
| 316 } | |
| 317 | |
| 318 visual_line++; | |
| 319 line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; | |
| 320 } | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); | |
| 325 float y = content_y + visual_line * line_height; | |
| 326 return (Vector2){x, y}; | |
| 327 } | |
| 328 | |
| 329 static float GetContentHeight(const char *text, Rectangle bounds, boolean wrap, | |
| 330 int font_size, int line_height) { | |
| 331 if (!text || strlen(text) == 0) return line_height; | |
| 332 | |
| 333 float content_width = bounds.width - TEXT_AREA_PADDING * 2; | |
| 334 int visual_lines = 1; | |
| 335 int line_char_start = 0; | |
| 336 int text_len = strlen(text); | |
| 337 | |
| 338 for (int i = 0; i <= text_len; i++) { | |
| 339 if (i == text_len || text[i] == '\n') { | |
| 340 visual_lines++; | |
| 341 line_char_start = i + 1; | |
| 342 } else if (wrap) { | |
| 343 int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size); | |
| 344 if (line_width > content_width && i > line_char_start) { | |
| 345 int wrap_pos = i; | |
| 346 for (int j = i; j > line_char_start; j--) { | |
| 347 if (text[j] == ' ') { | |
| 348 wrap_pos = j; | |
| 349 break; | |
| 350 } | |
| 351 } | |
| 352 visual_lines++; | |
| 353 line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; | |
| 354 } | |
| 355 } | |
| 356 } | |
| 357 | |
| 358 return visual_lines * line_height; | |
| 359 } | |
| 360 | |
| 361 static void PushUndoState(TextAreaState *state, const char *text, Dowa_Arena *arena) { | |
| 362 TextAreaUndoEntry entry; | |
| 363 entry.text = Dowa_String_Copy_Arena((char*)text, arena); | |
| 364 entry.cursor_pos = state->cursor_pos; | |
| 365 entry.selection_start = state->selection_start; | |
| 366 entry.selection_end = state->selection_end; | |
| 367 | |
| 368 if (state->undo_index < (int)Dowa_Array_Length(state->undo_stack) - 1) { | |
| 369 dowa__header(state->undo_stack)->length = state->undo_index + 1; | |
| 370 } | |
| 371 | |
| 372 Dowa_Array_Push_Arena(state->undo_stack, entry, arena); | |
| 373 state->undo_index = Dowa_Array_Length(state->undo_stack) - 1; | |
| 374 | |
| 375 if (Dowa_Array_Length(state->undo_stack) > TEXT_AREA_MAX_UNDO_STATES) { | |
| 376 for (int i = 0; i < (int)Dowa_Array_Length(state->undo_stack) - 1; i++) { | |
| 377 state->undo_stack[i] = state->undo_stack[i + 1]; | |
| 378 } | |
| 379 dowa__header(state->undo_stack)->length--; | |
| 380 state->undo_index--; | |
| 381 } | |
| 382 } | |
| 383 | |
| 384 static boolean PerformUndo(TextAreaState *state, char *text, int text_size) { | |
| 385 if (state->undo_index <= 0) return FALSE; | |
| 386 | |
| 387 state->undo_index--; | |
| 388 TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index]; | |
| 389 | |
| 390 strncpy(text, entry->text, text_size - 1); | |
| 391 text[text_size - 1] = '\0'; | |
| 392 state->cursor_pos = entry->cursor_pos; | |
| 393 state->selection_start = entry->selection_start; | |
| 394 state->selection_end = entry->selection_end; | |
| 395 | |
| 396 return TRUE; | |
| 397 } | |
| 398 | |
| 399 static boolean PerformRedo(TextAreaState *state, char *text, int text_size) { | |
| 400 if (state->undo_index >= (int)Dowa_Array_Length(state->undo_stack) - 1) return FALSE; | |
| 401 | |
| 402 state->undo_index++; | |
| 403 TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index]; | |
| 404 | |
| 405 strncpy(text, entry->text, text_size - 1); | |
| 406 text[text_size - 1] = '\0'; | |
| 407 state->cursor_pos = entry->cursor_pos; | |
| 408 state->selection_start = entry->selection_start; | |
| 409 state->selection_end = entry->selection_end; | |
| 410 | |
| 411 return TRUE; | |
| 412 } | |
| 413 | |
| 414 static void InsertTextAtCursor(char *text, int text_size, int cursor_pos, | |
| 415 const char *insert_text, int *new_cursor_pos) { | |
| 416 int text_len = strlen(text); | |
| 417 int insert_len = strlen(insert_text); | |
| 418 | |
| 419 if (text_len + insert_len >= text_size - 1) { | |
| 420 insert_len = text_size - 1 - text_len; | |
| 421 if (insert_len <= 0) return; | |
| 422 } | |
| 423 | |
| 424 memmove(text + cursor_pos + insert_len, | |
| 425 text + cursor_pos, | |
| 426 text_len - cursor_pos + 1); | |
| 427 | |
| 428 memcpy(text + cursor_pos, insert_text, insert_len); | |
| 429 | |
| 430 *new_cursor_pos = cursor_pos + insert_len; | |
| 431 } | |
| 432 | |
| 433 static void DeleteTextRange(char *text, int start, int end) { | |
| 434 if (start >= end) return; | |
| 435 int text_len = strlen(text); | |
| 436 if (start < 0) start = 0; | |
| 437 if (end > text_len) end = text_len; | |
| 438 | |
| 439 memmove(text + start, text + end, text_len - end + 1); | |
| 440 } | |
| 441 | |
| 442 static char* GetSelectedText(const char *text, int sel_start, int sel_end, Dowa_Arena *arena) { | |
| 443 if (sel_start < 0 || sel_end < 0 || sel_start >= sel_end) return NULL; | |
| 444 | |
| 445 int len = sel_end - sel_start; | |
| 446 char *result = Dowa_Arena_Allocate(arena, len + 1); | |
| 447 strncpy(result, text + sel_start, len); | |
| 448 result[len] = '\0'; | |
| 449 return result; | |
| 450 } | |
| 451 | |
| 452 boolean GuiTextArea(int id, Rectangle bounds, char *text, int text_size, boolean is_edit_mode, | |
| 453 boolean should_text_wrap, Dowa_Arena *arena) { | |
| 454 boolean should_toggle_edit_mode = FALSE; | |
| 455 | |
| 456 // Get or create state for this text area | |
| 457 TextAreaState *state = GetTextAreaState(id); | |
| 458 if (!state) { | |
| 459 state = CreateTextAreaState(id); | |
| 460 state->cursor_pos = strlen(text); | |
| 461 state->last_blink_time = GetTime(); | |
| 462 PushUndoState(state, text, arena); | |
| 463 } | |
| 464 | |
| 465 int text_len = strlen(text); | |
| 466 Vector2 mouse_pos = GetMousePosition(); | |
| 467 boolean mouse_in_bounds = CheckCollisionPointRec(mouse_pos, bounds); | |
| 468 | |
| 469 // Handle click to enter/exit edit mode | |
| 470 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { | |
| 471 if (mouse_in_bounds && !is_edit_mode) { | |
| 472 should_toggle_edit_mode = TRUE; | |
| 473 } else if (!mouse_in_bounds && is_edit_mode) { | |
| 474 should_toggle_edit_mode = TRUE; | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 // Content area | |
| 479 float content_height = GetContentHeight(text, bounds, should_text_wrap, | |
| 480 TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT); | |
| 481 float visible_height = bounds.height - TEXT_AREA_PADDING * 2; | |
| 482 float max_scroll = TA_Max_Float(0, content_height - visible_height); | |
| 483 | |
| 484 // Handle scrolling | |
| 485 float wheel = GetMouseWheelMove(); | |
| 486 if (mouse_in_bounds && wheel != 0) { | |
| 487 state->scroll_offset_y -= wheel * TEXT_AREA_LINE_HEIGHT * 3; | |
| 488 state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll)); | |
| 489 } | |
| 490 | |
| 491 if (is_edit_mode) { | |
| 492 boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || | |
| 493 IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER); | |
| 494 boolean shift_pressed = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); | |
| 495 boolean text_changed = FALSE; | |
| 496 | |
| 497 double current_time = GetTime(); | |
| 498 if (current_time - state->last_blink_time > 0.5) { | |
| 499 state->cursor_visible = !state->cursor_visible; | |
| 500 state->last_blink_time = current_time; | |
| 501 } | |
| 502 | |
| 503 // Mouse Selection | |
| 504 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mouse_in_bounds) { | |
| 505 int click_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap, | |
| 506 state->scroll_offset_y, TEXT_AREA_FONT_SIZE, | |
| 507 TEXT_AREA_LINE_HEIGHT); | |
| 508 state->cursor_pos = click_pos; | |
| 509 state->selection_start = -1; | |
| 510 state->selection_end = -1; | |
| 511 state->is_selecting = TRUE; | |
| 512 state->cursor_visible = TRUE; | |
| 513 state->last_blink_time = current_time; | |
| 514 } | |
| 515 | |
| 516 if (state->is_selecting && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { | |
| 517 int drag_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap, | |
| 518 state->scroll_offset_y, TEXT_AREA_FONT_SIZE, | |
| 519 TEXT_AREA_LINE_HEIGHT); | |
| 520 if (drag_pos != state->cursor_pos) { | |
| 521 if (state->selection_start < 0) { | |
| 522 state->selection_start = state->cursor_pos; | |
| 523 } | |
| 524 state->selection_end = drag_pos; | |
| 525 } | |
| 526 } | |
| 527 | |
| 528 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { | |
| 529 state->is_selecting = FALSE; | |
| 530 if (state->selection_start >= 0 && state->selection_end >= 0) { | |
| 531 if (state->selection_start > state->selection_end) { | |
| 532 int temp = state->selection_start; | |
| 533 state->selection_start = state->selection_end; | |
| 534 state->selection_end = temp; | |
| 535 } | |
| 536 if (state->selection_start == state->selection_end) { | |
| 537 state->selection_start = -1; | |
| 538 state->selection_end = -1; | |
| 539 } | |
| 540 } | |
| 541 } | |
| 542 | |
| 543 // Ctrl+A: Select All | |
| 544 if (ctrl_pressed && IsKeyPressed(KEY_A)) { | |
| 545 state->selection_start = 0; | |
| 546 state->selection_end = text_len; | |
| 547 state->cursor_pos = text_len; | |
| 548 } | |
| 549 | |
| 550 // Ctrl+C: Copy | |
| 551 if (ctrl_pressed && IsKeyPressed(KEY_C)) { | |
| 552 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 553 state->selection_start != state->selection_end) { | |
| 554 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 555 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 556 char *selected = GetSelectedText(text, sel_min, sel_max, arena); | |
| 557 if (selected) { | |
| 558 if (g_clipboard_text) free(g_clipboard_text); | |
| 559 g_clipboard_text = strdup(selected); | |
| 560 SetClipboardText(g_clipboard_text); | |
| 561 } | |
| 562 } | |
| 563 } | |
| 564 | |
| 565 // Ctrl+X: Cut | |
| 566 if (ctrl_pressed && IsKeyPressed(KEY_X)) { | |
| 567 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 568 state->selection_start != state->selection_end) { | |
| 569 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 570 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 571 char *selected = GetSelectedText(text, sel_min, sel_max, arena); | |
| 572 if (selected) { | |
| 573 if (g_clipboard_text) free(g_clipboard_text); | |
| 574 g_clipboard_text = strdup(selected); | |
| 575 SetClipboardText(g_clipboard_text); | |
| 576 } | |
| 577 | |
| 578 PushUndoState(state, text, arena); | |
| 579 DeleteTextRange(text, sel_min, sel_max); | |
| 580 state->cursor_pos = sel_min; | |
| 581 state->selection_start = -1; | |
| 582 state->selection_end = -1; | |
| 583 text_changed = TRUE; | |
| 584 } | |
| 585 } | |
| 586 | |
| 587 // Ctrl+V: Paste | |
| 588 if (ctrl_pressed && IsKeyPressed(KEY_V)) { | |
| 589 const char *clipboard = GetClipboardText(); | |
| 590 if (clipboard && strlen(clipboard) > 0) { | |
| 591 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 592 state->selection_start != state->selection_end) { | |
| 593 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 594 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 595 PushUndoState(state, text, arena); | |
| 596 DeleteTextRange(text, sel_min, sel_max); | |
| 597 state->cursor_pos = sel_min; | |
| 598 state->selection_start = -1; | |
| 599 state->selection_end = -1; | |
| 600 } else { | |
| 601 PushUndoState(state, text, arena); | |
| 602 } | |
| 603 | |
| 604 int new_cursor; | |
| 605 InsertTextAtCursor(text, text_size, state->cursor_pos, clipboard, &new_cursor); | |
| 606 state->cursor_pos = new_cursor; | |
| 607 text_changed = TRUE; | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 // Ctrl+Z: Undo / Ctrl+Shift+Z: Redo | |
| 612 if (ctrl_pressed && IsKeyPressed(KEY_Z)) { | |
| 613 if (shift_pressed) { | |
| 614 PerformRedo(state, text, text_size); | |
| 615 } else { | |
| 616 PerformUndo(state, text, text_size); | |
| 617 } | |
| 618 } | |
| 619 | |
| 620 // Arrow Keys Navigation | |
| 621 if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { | |
| 622 if (state->selection_start >= 0 && !shift_pressed) { | |
| 623 state->cursor_pos = TA_Min_Int(state->selection_start, state->selection_end); | |
| 624 state->selection_start = -1; | |
| 625 state->selection_end = -1; | |
| 626 } else if (state->cursor_pos > 0) { | |
| 627 if (shift_pressed) { | |
| 628 if (state->selection_start < 0) { | |
| 629 state->selection_start = state->cursor_pos; | |
| 630 state->selection_end = state->cursor_pos; | |
| 631 } | |
| 632 state->cursor_pos--; | |
| 633 state->selection_end = state->cursor_pos; | |
| 634 } else { | |
| 635 state->cursor_pos--; | |
| 636 } | |
| 637 } | |
| 638 state->cursor_visible = TRUE; | |
| 639 state->last_blink_time = current_time; | |
| 640 } | |
| 641 | |
| 642 if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { | |
| 643 if (state->selection_start >= 0 && !shift_pressed) { | |
| 644 state->cursor_pos = TA_Max_Int(state->selection_start, state->selection_end); | |
| 645 state->selection_start = -1; | |
| 646 state->selection_end = -1; | |
| 647 } else if (state->cursor_pos < text_len) { | |
| 648 if (shift_pressed) { | |
| 649 if (state->selection_start < 0) { | |
| 650 state->selection_start = state->cursor_pos; | |
| 651 state->selection_end = state->cursor_pos; | |
| 652 } | |
| 653 state->cursor_pos++; | |
| 654 state->selection_end = state->cursor_pos; | |
| 655 } else { | |
| 656 state->cursor_pos++; | |
| 657 } | |
| 658 } | |
| 659 state->cursor_visible = TRUE; | |
| 660 state->last_blink_time = current_time; | |
| 661 } | |
| 662 | |
| 663 if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { | |
| 664 int line, col; | |
| 665 GetLineAndColumn(text, state->cursor_pos, &line, &col); | |
| 666 if (line > 0) { | |
| 667 int new_pos = GetPosFromLineColumn(text, line - 1, col); | |
| 668 if (shift_pressed) { | |
| 669 if (state->selection_start < 0) { | |
| 670 state->selection_start = state->cursor_pos; | |
| 671 state->selection_end = state->cursor_pos; | |
| 672 } | |
| 673 state->selection_end = new_pos; | |
| 674 } else { | |
| 675 state->selection_start = -1; | |
| 676 state->selection_end = -1; | |
| 677 } | |
| 678 state->cursor_pos = new_pos; | |
| 679 } | |
| 680 state->cursor_visible = TRUE; | |
| 681 state->last_blink_time = current_time; | |
| 682 } | |
| 683 | |
| 684 if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { | |
| 685 int line, col; | |
| 686 GetLineAndColumn(text, state->cursor_pos, &line, &col); | |
| 687 int total_lines = CountLines(text); | |
| 688 if (line < total_lines - 1) { | |
| 689 int new_pos = GetPosFromLineColumn(text, line + 1, col); | |
| 690 if (shift_pressed) { | |
| 691 if (state->selection_start < 0) { | |
| 692 state->selection_start = state->cursor_pos; | |
| 693 state->selection_end = state->cursor_pos; | |
| 694 } | |
| 695 state->selection_end = new_pos; | |
| 696 } else { | |
| 697 state->selection_start = -1; | |
| 698 state->selection_end = -1; | |
| 699 } | |
| 700 state->cursor_pos = new_pos; | |
| 701 } | |
| 702 state->cursor_visible = TRUE; | |
| 703 state->last_blink_time = current_time; | |
| 704 } | |
| 705 | |
| 706 // Home/End keys | |
| 707 if (IsKeyPressed(KEY_HOME)) { | |
| 708 int line_start = GetLineStart(text, state->cursor_pos); | |
| 709 if (shift_pressed) { | |
| 710 if (state->selection_start < 0) { | |
| 711 state->selection_start = state->cursor_pos; | |
| 712 state->selection_end = state->cursor_pos; | |
| 713 } | |
| 714 state->selection_end = line_start; | |
| 715 } else { | |
| 716 state->selection_start = -1; | |
| 717 state->selection_end = -1; | |
| 718 } | |
| 719 state->cursor_pos = line_start; | |
| 720 } | |
| 721 | |
| 722 if (IsKeyPressed(KEY_END)) { | |
| 723 int line_end = GetLineEnd(text, state->cursor_pos); | |
| 724 if (shift_pressed) { | |
| 725 if (state->selection_start < 0) { | |
| 726 state->selection_start = state->cursor_pos; | |
| 727 state->selection_end = state->cursor_pos; | |
| 728 } | |
| 729 state->selection_end = line_end; | |
| 730 } else { | |
| 731 state->selection_start = -1; | |
| 732 state->selection_end = -1; | |
| 733 } | |
| 734 state->cursor_pos = line_end; | |
| 735 } | |
| 736 | |
| 737 // Text Input | |
| 738 if (!ctrl_pressed) { | |
| 739 int key = GetCharPressed(); | |
| 740 while (key > 0) { | |
| 741 if (key >= 32 && key <= 126) { | |
| 742 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 743 state->selection_start != state->selection_end) { | |
| 744 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 745 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 746 PushUndoState(state, text, arena); | |
| 747 DeleteTextRange(text, sel_min, sel_max); | |
| 748 state->cursor_pos = sel_min; | |
| 749 state->selection_start = -1; | |
| 750 state->selection_end = -1; | |
| 751 text_changed = TRUE; | |
| 752 } else if (!text_changed) { | |
| 753 PushUndoState(state, text, arena); | |
| 754 } | |
| 755 | |
| 756 char insert_str[2] = {(char)key, '\0'}; | |
| 757 int new_cursor; | |
| 758 InsertTextAtCursor(text, text_size, state->cursor_pos, insert_str, &new_cursor); | |
| 759 state->cursor_pos = new_cursor; | |
| 760 text_changed = TRUE; | |
| 761 } | |
| 762 key = GetCharPressed(); | |
| 763 } | |
| 764 } | |
| 765 | |
| 766 // Enter key | |
| 767 if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) { | |
| 768 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 769 state->selection_start != state->selection_end) { | |
| 770 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 771 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 772 PushUndoState(state, text, arena); | |
| 773 DeleteTextRange(text, sel_min, sel_max); | |
| 774 state->cursor_pos = sel_min; | |
| 775 state->selection_start = -1; | |
| 776 state->selection_end = -1; | |
| 777 } else { | |
| 778 PushUndoState(state, text, arena); | |
| 779 } | |
| 780 | |
| 781 int new_cursor; | |
| 782 InsertTextAtCursor(text, text_size, state->cursor_pos, "\n", &new_cursor); | |
| 783 state->cursor_pos = new_cursor; | |
| 784 text_changed = TRUE; | |
| 785 } | |
| 786 | |
| 787 // Backspace | |
| 788 if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { | |
| 789 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 790 state->selection_start != state->selection_end) { | |
| 791 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 792 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 793 PushUndoState(state, text, arena); | |
| 794 DeleteTextRange(text, sel_min, sel_max); | |
| 795 state->cursor_pos = sel_min; | |
| 796 state->selection_start = -1; | |
| 797 state->selection_end = -1; | |
| 798 text_changed = TRUE; | |
| 799 } else if (state->cursor_pos > 0) { | |
| 800 PushUndoState(state, text, arena); | |
| 801 DeleteTextRange(text, state->cursor_pos - 1, state->cursor_pos); | |
| 802 state->cursor_pos--; | |
| 803 text_changed = TRUE; | |
| 804 } | |
| 805 } | |
| 806 | |
| 807 // Delete key | |
| 808 if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) { | |
| 809 text_len = strlen(text); | |
| 810 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 811 state->selection_start != state->selection_end) { | |
| 812 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 813 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 814 PushUndoState(state, text, arena); | |
| 815 DeleteTextRange(text, sel_min, sel_max); | |
| 816 state->cursor_pos = sel_min; | |
| 817 state->selection_start = -1; | |
| 818 state->selection_end = -1; | |
| 819 text_changed = TRUE; | |
| 820 } else if (state->cursor_pos < text_len) { | |
| 821 PushUndoState(state, text, arena); | |
| 822 DeleteTextRange(text, state->cursor_pos, state->cursor_pos + 1); | |
| 823 text_changed = TRUE; | |
| 824 } | |
| 825 } | |
| 826 | |
| 827 // Tab key | |
| 828 if (IsKeyPressed(KEY_TAB)) { | |
| 829 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 830 state->selection_start != state->selection_end) { | |
| 831 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 832 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 833 PushUndoState(state, text, arena); | |
| 834 DeleteTextRange(text, sel_min, sel_max); | |
| 835 state->cursor_pos = sel_min; | |
| 836 state->selection_start = -1; | |
| 837 state->selection_end = -1; | |
| 838 } else { | |
| 839 PushUndoState(state, text, arena); | |
| 840 } | |
| 841 | |
| 842 int new_cursor; | |
| 843 InsertTextAtCursor(text, text_size, state->cursor_pos, " ", &new_cursor); | |
| 844 state->cursor_pos = new_cursor; | |
| 845 text_changed = TRUE; | |
| 846 } | |
| 847 | |
| 848 // Auto-scroll to keep cursor visible | |
| 849 Vector2 cursor_screen = GetCursorScreenPos(text, state->cursor_pos, bounds, | |
| 850 should_text_wrap, state->scroll_offset_y, | |
| 851 TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT); | |
| 852 | |
| 853 float visible_top = bounds.y + TEXT_AREA_PADDING; | |
| 854 float visible_bottom = bounds.y + bounds.height - TEXT_AREA_PADDING - TEXT_AREA_LINE_HEIGHT; | |
| 855 | |
| 856 if (cursor_screen.y < visible_top) { | |
| 857 state->scroll_offset_y -= visible_top - cursor_screen.y; | |
| 858 } else if (cursor_screen.y > visible_bottom) { | |
| 859 state->scroll_offset_y += cursor_screen.y - visible_bottom; | |
| 860 } | |
| 861 | |
| 862 state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll)); | |
| 863 } | |
| 864 | |
| 865 // Drawing | |
| 866 DrawRectangleRec(bounds, is_edit_mode ? DARKGRAY : (Color){40, 40, 40, 255}); | |
| 867 DrawRectangleLinesEx(bounds, 1, is_edit_mode ? WHITE : GRAY); | |
| 868 | |
| 869 BeginScissorMode((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height); | |
| 870 | |
| 871 float content_x = bounds.x + TEXT_AREA_PADDING; | |
| 872 float content_y = bounds.y + TEXT_AREA_PADDING - state->scroll_offset_y; | |
| 873 float content_width = bounds.width - TEXT_AREA_PADDING * 2; | |
| 874 | |
| 875 text_len = strlen(text); | |
| 876 | |
| 877 // Draw selection highlight | |
| 878 if (state->selection_start >= 0 && state->selection_end >= 0 && | |
| 879 state->selection_start != state->selection_end) { | |
| 880 int sel_min = TA_Min_Int(state->selection_start, state->selection_end); | |
| 881 int sel_max = TA_Max_Int(state->selection_start, state->selection_end); | |
| 882 | |
| 883 int line_char_start = 0; | |
| 884 int visual_line = 0; | |
| 885 | |
| 886 for (int i = 0; i <= text_len; i++) { | |
| 887 boolean is_end = (i == text_len); | |
| 888 boolean is_newline = (!is_end && text[i] == '\n'); | |
| 889 boolean should_draw_line = is_end || is_newline; | |
| 890 | |
| 891 if (should_text_wrap && !is_end && !is_newline) { | |
| 892 int line_width = MeasureTextRange(text, line_char_start, i + 1, TEXT_AREA_FONT_SIZE); | |
| 893 if (line_width > content_width && i > line_char_start) { | |
| 894 should_draw_line = TRUE; | |
| 895 } | |
| 896 } | |
| 897 | |
| 898 if (should_draw_line) { | |
| 899 int line_end = i; | |
| 900 | |
| 901 if (sel_min < line_end && sel_max > line_char_start) { | |
| 902 int highlight_start = TA_Max_Int(sel_min, line_char_start); | |
| 903 int highlight_end = TA_Min_Int(sel_max, line_end); | |
| 904 | |
| 905 float x1 = content_x + MeasureTextRange(text, line_char_start, highlight_start, TEXT_AREA_FONT_SIZE); | |
| 906 float x2 = content_x + MeasureTextRange(text, line_char_start, highlight_end, TEXT_AREA_FONT_SIZE); | |
| 907 float y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT; | |
| 908 | |
| 909 DrawRectangle((int)x1, (int)y, (int)(x2 - x1), TEXT_AREA_LINE_HEIGHT, | |
| 910 Fade(SKYBLUE, 0.5f)); | |
| 911 } | |
| 912 | |
| 913 visual_line++; | |
| 914 line_char_start = i + 1; | |
| 915 } | |
| 916 } | |
| 917 } | |
| 918 | |
| 919 // Draw text with wrapping support | |
| 920 if (should_text_wrap) { | |
| 921 int line_char_start = 0; | |
| 922 int visual_line = 0; | |
| 923 | |
| 924 for (int i = 0; i <= text_len; i++) { | |
| 925 boolean is_end = (i == text_len); | |
| 926 boolean is_newline = (!is_end && text[i] == '\n'); | |
| 927 boolean should_draw_line = is_end || is_newline; | |
| 928 | |
| 929 if (!is_end && !is_newline) { | |
| 930 int line_width = MeasureTextRange(text, line_char_start, i + 1, TEXT_AREA_FONT_SIZE); | |
| 931 if (line_width > content_width && i > line_char_start) { | |
| 932 should_draw_line = TRUE; | |
| 933 } | |
| 934 } | |
| 935 | |
| 936 if (should_draw_line && i > line_char_start) { | |
| 937 char line_buffer[1024]; | |
| 938 int line_len = TA_Min_Int(i - line_char_start, 1023); | |
| 939 strncpy(line_buffer, text + line_char_start, line_len); | |
| 940 line_buffer[line_len] = '\0'; | |
| 941 | |
| 942 Vector2 draw_text_vector = { | |
| 943 .x = content_x, | |
| 944 .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT | |
| 945 }; | |
| 946 DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector, | |
| 947 TEXT_AREA_FONT_SIZE, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE, WHITE); | |
| 948 | |
| 949 visual_line++; | |
| 950 line_char_start = is_newline ? i + 1 : i; | |
| 951 } else if (is_newline) { | |
| 952 visual_line++; | |
| 953 line_char_start = i + 1; | |
| 954 } | |
| 955 } | |
| 956 } else { | |
| 957 int line_start = 0; | |
| 958 int visual_line = 0; | |
| 959 | |
| 960 for (int i = 0; i <= text_len; i++) { | |
| 961 if (i == text_len || text[i] == '\n') { | |
| 962 if (i > line_start) { | |
| 963 char line_buffer[1024]; | |
| 964 int line_len = TA_Min_Int(i - line_start, 1023); | |
| 965 strncpy(line_buffer, text + line_start, line_len); | |
| 966 line_buffer[line_len] = '\0'; | |
| 967 | |
| 968 Vector2 draw_text_vector = { | |
| 969 .x = content_x, | |
| 970 .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT | |
| 971 }; | |
| 972 DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector , | |
| 973 TEXT_AREA_FONT_SIZE, TEXT_SIZE_DEFAULT/TEXT_AREA_FONT_SIZE, WHITE); | |
| 974 } | |
| 975 visual_line++; | |
| 976 line_start = i + 1; | |
| 977 } | |
| 978 } | |
| 979 } | |
| 980 | |
| 981 // Draw cursor | |
| 982 if (is_edit_mode && state->cursor_visible) { | |
| 983 Vector2 cursor_pos = GetCursorScreenPos(text, state->cursor_pos, bounds, | |
| 984 should_text_wrap, state->scroll_offset_y, | |
| 985 TEXT_AREA_FONT_SIZE, TEXT_AREA_LINE_HEIGHT); | |
| 986 | |
| 987 DrawRectangle((int)cursor_pos.x, (int)cursor_pos.y, | |
| 988 TEXT_AREA_CURSOR_WIDTH, TEXT_AREA_LINE_HEIGHT, WHITE); | |
| 989 } | |
| 990 | |
| 991 EndScissorMode(); | |
| 992 | |
| 993 // Draw scrollbar if needed | |
| 994 if (max_scroll > 0) { | |
| 995 float scrollbar_height = (visible_height / content_height) * visible_height; | |
| 996 float scrollbar_y = bounds.y + TEXT_AREA_PADDING + | |
| 997 (state->scroll_offset_y / max_scroll) * (visible_height - scrollbar_height); | |
| 998 | |
| 999 DrawRectangle((int)(bounds.x + bounds.width - 8), (int)scrollbar_y, | |
| 1000 6, (int)scrollbar_height, Fade(WHITE, 0.3f)); | |
| 1001 } | |
| 1002 | |
| 1003 return should_toggle_edit_mode; | |
| 1004 } | |
| 1005 | |
| 1006 void GuiTextAreaResetState(int id) { | |
| 1007 TextAreaState *state = GetTextAreaState(id); | |
| 1008 if (state) { | |
| 1009 if (state->undo_stack) { | |
| 1010 Dowa_Array_Free(state->undo_stack); | |
| 1011 state->undo_stack = NULL; | |
| 1012 } | |
| 1013 state->is_initialized = FALSE; | |
| 1014 } | |
| 1015 } | |
| 1016 | |
| 1017 void GuiTextAreaResetAllStates(void) { | |
| 1018 for (int i = 0; i < g_text_area_state_count; i++) { | |
| 1019 if (g_text_area_states[i].undo_stack) { | |
| 1020 Dowa_Array_Free(g_text_area_states[i].undo_stack); | |
| 1021 g_text_area_states[i].undo_stack = NULL; | |
| 1022 } | |
| 1023 g_text_area_states[i].is_initialized = FALSE; | |
| 1024 } | |
| 1025 g_text_area_state_count = 0; | |
| 1026 } | |
| 1027 | |
| 1028 // ============================================================================ | |
| 1029 // End TextArea Component | |
| 1030 // ============================================================================ | |
| 1031 | |
| 42 typedef Dowa_KV(char*, char*) INPUT_HASHMAP; | 1032 typedef Dowa_KV(char*, char*) INPUT_HASHMAP; |
| 43 | 1033 |
| 44 typedef struct { | 1034 typedef struct { |
| 45 char *data; | 1035 char *data; |
| 46 size_t size; | 1036 size_t size; |
| 47 } ResponseBuffer; | 1037 } ResponseBuffer; |
| 48 | 1038 |
| 49 typedef struct { | 1039 typedef struct { |
| 50 char *filename; | 1040 char *filename; |
| 51 char *title; | 1041 char *title; |
| 52 Rectangle rect; | 1042 Rectangle rect; |
| 53 long time_modified; | 1043 long time_modified; |
| 54 boolean deleted; | 1044 boolean deleted; |
| 55 } HistoryItem; | 1045 } HistoryItem; |
| 56 | 1046 |
| 65 TAB_BODY, | 1055 TAB_BODY, |
| 66 TAB_GET_PARAMS, | 1056 TAB_GET_PARAMS, |
| 67 TAB_WEBSOCKET, | 1057 TAB_WEBSOCKET, |
| 68 TAB_LENGTH | 1058 TAB_LENGTH |
| 69 } PostDog_Tab_Enum; | 1059 } PostDog_Tab_Enum; |
| 1060 | |
| 1061 // Text area IDs | |
| 1062 #define TEXT_AREA_ID_INPUT_HEADER 1 | |
| 1063 #define TEXT_AREA_ID_INPUT_BODY 2 | |
| 1064 #define TEXT_AREA_ID_INPUT_PARAMS 3 | |
| 1065 #define TEXT_AREA_ID_INPUT_WS 4 | |
| 1066 #define TEXT_AREA_ID_RESULT 5 | |
| 70 | 1067 |
| 71 static uint32 counter = 0; | 1068 static uint32 counter = 0; |
| 72 HistoryItem *history_items = NULL; | 1069 HistoryItem *history_items = NULL; |
| 73 HistoryItem *new_history_items = NULL; | 1070 HistoryItem *new_history_items = NULL; |
| 74 | 1071 |
| 78 char **input_body_array = NULL; | 1075 char **input_body_array = NULL; |
| 79 int active_method_dropdown = 0; | 1076 int active_method_dropdown = 0; |
| 80 int active_input_tab = 0; | 1077 int active_input_tab = 0; |
| 81 Seobeo_WebSocket *ws = NULL; | 1078 Seobeo_WebSocket *ws = NULL; |
| 82 boolean WS_BREAK = FALSE; | 1079 boolean WS_BREAK = FALSE; |
| 1080 pthread_t websocket_thread_id; | |
| 83 Color TEXT_COLOR = BLACK; | 1081 Color TEXT_COLOR = BLACK; |
| 1082 boolean LOADING = FALSE; | |
| 84 | 1083 |
| 85 int CompareHistoryItemsByDate(const void *a, const void *b) { | 1084 int CompareHistoryItemsByDate(const void *a, const void *b) { |
| 86 HistoryItem *itemA = (HistoryItem *)a; | 1085 HistoryItem *itemA = (HistoryItem *)a; |
| 87 HistoryItem *itemB = (HistoryItem *)b; | 1086 HistoryItem *itemB = (HistoryItem *)b; |
| 88 return (itemB->time_modified - itemA->time_modified); | 1087 return (itemB->time_modified - itemA->time_modified); |
| 185 bool Clicked(Vector2 mouse_position, Rectangle area) | 1184 bool Clicked(Vector2 mouse_position, Rectangle area) |
| 186 { | 1185 { |
| 187 return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); | 1186 return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); |
| 188 } | 1187 } |
| 189 | 1188 |
| 1189 // -------- END of UI ---- // | |
| 1190 | |
| 190 char *PostDog_Enum_To_String(int active_enum) | 1191 char *PostDog_Enum_To_String(int active_enum) |
| 191 { | 1192 { |
| 192 switch(active_enum) | 1193 switch(active_enum) |
| 193 { | 1194 { |
| 194 case 0: return "GET"; | 1195 case 0: return "GET"; |
| 224 { | 1225 { |
| 225 const char *method = PostDog_Enum_To_String(active_method_dropdown); | 1226 const char *method = PostDog_Enum_To_String(active_method_dropdown); |
| 226 size_t new_file_size = 1024 * 1024; | 1227 size_t new_file_size = 1024 * 1024; |
| 227 Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2); | 1228 Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2); |
| 228 char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024); | 1229 char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024); |
| 229 char *title = malloc(strlen(method) + strlen(url_input_text) + 2); | 1230 char *title = Dowa_Arena_Allocate(arena, strlen(method) + strlen(url_input_text) + 2); |
| 230 sprintf(title, "%s %s", method, url_input_text); | 1231 sprintf(title, "%s %s", method, url_input_text); |
| 231 snprintf( | 1232 snprintf( |
| 232 new_file, | 1233 new_file, |
| 233 new_file_size, | 1234 new_file_size, |
| 234 "%s\n" | 1235 "%s\n" |
| 257 ); | 1258 ); |
| 258 char *filename = Dowa_Arena_Allocate(arena, 1024); | 1259 char *filename = Dowa_Arena_Allocate(arena, 1024); |
| 259 if (!filename) | 1260 if (!filename) |
| 260 { | 1261 { |
| 261 perror("Error opening file"); | 1262 perror("Error opening file"); |
| 262 exit(EXIT_FAILURE); | 1263 exit(EXIT_FAILURE); |
| 263 } | 1264 } |
| 264 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37); | 1265 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37); |
| 265 if (!uuid4) | 1266 if (!uuid4) |
| 266 { | 1267 { |
| 267 perror("Error uuid"); | 1268 perror("Error uuid"); |
| 268 exit(EXIT_FAILURE); | 1269 exit(EXIT_FAILURE); |
| 269 } | 1270 } |
| 270 | 1271 |
| 271 int32 seed = (uint32)time(NULL) ^ counter++; | 1272 int32 seed = (uint32)time(NULL) ^ counter++; |
| 272 Dowa_String_UUID(seed, uuid4); | 1273 Dowa_String_UUID(seed, uuid4); |
| 273 snprintf(filename, 1024, "%s.txt", uuid4); | 1274 snprintf(filename, 1024, "%s.txt", uuid4); |
| 274 PostDog_History_CreateFile(filename, new_file); | 1275 PostDog_History_CreateFile(filename, new_file); |
| 275 | 1276 |
| 276 HistoryItem item = {0}; | 1277 HistoryItem item = {0}; |
| 277 item.filename = strdup(filename); | 1278 item.filename = strdup(filename); |
| 278 item.title = title; | 1279 item.title = strdup(title); |
| 279 item.deleted = FALSE; | 1280 item.deleted = FALSE; |
| 1281 | |
| 280 Dowa_Array_Push(new_history_items, item); | 1282 Dowa_Array_Push(new_history_items, item); |
| 281 | |
| 282 Dowa_Arena_Free(arena); | 1283 Dowa_Arena_Free(arena); |
| 283 } | 1284 } |
| 284 | 1285 |
| 285 int PostDog_Websocket_Send(void) | 1286 void PostDog_Websocket_Listen(void) |
| 286 { | 1287 { |
| 287 if (!ws) | |
| 288 ws = Seobeo_WebSocket_Connect(url_input_text); | |
| 289 | |
| 290 printf("URL %s\n", url_input_text); | |
| 291 if (Seobeo_WebSocket_Send_Text(ws, input_body_array[active_input_tab]) < 0) | |
| 292 printf("Failed to send message\n"); | |
| 293 | |
| 294 printf("Receiving responses...\n"); | |
| 295 int received = 0; | |
| 296 while (TRUE) | 1288 while (TRUE) |
| 297 { | 1289 { |
| 298 if (WS_BREAK) break; | 1290 if (WS_BREAK) break; |
| 299 | 1291 |
| 300 Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(ws); | 1292 Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(ws); |
| 301 if (p_msg) | 1293 if (p_msg) |
| 302 { | 1294 { |
| 303 if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT) | 1295 if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT) |
| 304 { | 1296 { |
| 305 printf("Response %d: %.*s\n", received + 1, (int)p_msg->length, (char*)p_msg->data); | 1297 printf("Response: %.*s\n", (int)p_msg->length, (char*)p_msg->data); |
| 306 snprintf(result_text + strlen(result_text), RESULT_BUFFER_LENGTH - strlen(result_text), | 1298 snprintf(result_text + strlen(result_text), RESULT_BUFFER_LENGTH - strlen(result_text), |
| 307 "\n%s", (char*)p_msg->data); | 1299 "\n%s", (char*)p_msg->data); |
| 308 received++; | |
| 309 } | 1300 } |
| 310 Seobeo_WebSocket_Message_Destroy(p_msg); | 1301 Seobeo_WebSocket_Message_Destroy(p_msg); |
| 311 } | 1302 } |
| 312 usleep(10000); | 1303 usleep(10000); |
| 1304 printf("Listening\n"); | |
| 313 } | 1305 } |
| 314 printf("Received %d/%d messages\n", received, 3); | 1306 return; |
| 1307 } | |
| 1308 | |
| 1309 void PostDog_Websocket_Connect(void) | |
| 1310 { | |
| 1311 ws = Seobeo_WebSocket_Connect(url_input_text); | |
| 1312 memset(result_text, 0, strlen(result_text)); | |
| 1313 } | |
| 1314 | |
| 1315 int PostDog_Websocket_Send(void) | |
| 1316 { | |
| 1317 if (Seobeo_WebSocket_Send_Text(ws, input_body_array[active_input_tab]) < 0) | |
| 1318 snprintf(result_text + strlen(result_text), RESULT_BUFFER_LENGTH - strlen(result_text), | |
| 1319 "Failed to send message\n"); | |
| 1320 else | |
| 1321 snprintf(result_text + strlen(result_text), RESULT_BUFFER_LENGTH - strlen(result_text), | |
| 1322 "\n%s", input_body_array[active_input_tab]); | |
| 1323 } | |
| 1324 | |
| 1325 void PostDog_Websocket_Destroy(pthread_t thread_id) | |
| 1326 { | |
| 315 Seobeo_WebSocket_Destroy(ws); | 1327 Seobeo_WebSocket_Destroy(ws); |
| 316 } | 1328 pthread_detach(thread_id); |
| 317 | 1329 } |
| 318 void *PostDog_Websocket_Thread(void *arg) | 1330 |
| 319 { | 1331 void *PostDog_Websocket_Start(void *arg) |
| 320 PostDog_Websocket_Send(); | 1332 { |
| 321 printf("Websocket request finished.\n"); | 1333 PostDog_Websocket_Connect(); |
| 1334 PostDog_Websocket_Listen(); | |
| 322 return NULL; | 1335 return NULL; |
| 323 } | 1336 } |
| 324 | 1337 |
| 325 void PostDog_Websocket_Thread_Send() | 1338 pthread_t PostDog_Websocket_Start_Thread() |
| 326 { | 1339 { |
| 327 pthread_t thread_id; | 1340 pthread_t thread_id; |
| 328 | 1341 |
| 329 if (pthread_create(&thread_id, NULL, PostDog_Websocket_Thread, NULL) != 0) | 1342 if (pthread_create(&thread_id, NULL, PostDog_Websocket_Start, NULL) != 0) |
| 330 { | 1343 { |
| 331 perror("Failed to create thread"); | 1344 perror("Failed to create thread"); |
| 332 return; | 1345 return 0; |
| 333 } | 1346 } |
| 334 pthread_detach(thread_id); | 1347 |
| 1348 return thread_id; | |
| 335 } | 1349 } |
| 336 | 1350 |
| 337 int PostDog_Http_Request(void) | 1351 int PostDog_Http_Request(void) |
| 338 { | 1352 { |
| 339 Seobeo_Client_Request *req = Seobeo_Client_Request_Create(url_input_text); | 1353 Seobeo_Client_Request *req = Seobeo_Client_Request_Create(url_input_text); |
| 340 Seobeo_Client_Response *res; | 1354 Seobeo_Client_Response *res; |
| 341 switch (active_method_dropdown) | 1355 switch (active_method_dropdown) |
| 342 { | 1356 { |
| 343 case 0: | 1357 case 0: |
| 344 { | 1358 { |
| 345 Seobeo_Client_Request_Set_Method(req, "GET"); | 1359 Seobeo_Client_Request_Set_Method(req, "GET"); |
| 346 break; | 1360 break; |
| 347 } | 1361 } |
| 348 case 1: | 1362 case 1: |
| 349 { | 1363 { |
| 350 Seobeo_Client_Request_Set_Method(req, "POST"); | 1364 Seobeo_Client_Request_Set_Method(req, "POST"); |
| 351 break; | 1365 break; |
| 352 } | 1366 } |
| 353 case 2: | 1367 case 2: |
| 373 if (strlen(line) > 0) | 1387 if (strlen(line) > 0) |
| 374 Seobeo_Client_Request_Add_Header_Array(req, line); | 1388 Seobeo_Client_Request_Add_Header_Array(req, line); |
| 375 line = strtok(NULL, "\n"); | 1389 line = strtok(NULL, "\n"); |
| 376 } | 1390 } |
| 377 | 1391 |
| 378 } | 1392 } |
| 379 Seobeo_Client_Request_Set_Follow_Redirects(req, TRUE, 5); // TODO: remove magic number; | 1393 Seobeo_Client_Request_Set_Follow_Redirects(req, TRUE, 5); // TODO: remove magic number; |
| 380 res = Seobeo_Client_Request_Execute(req); | 1394 res = Seobeo_Client_Request_Execute(req); |
| 381 | 1395 |
| 382 if (res == NULL) | 1396 if (res == NULL) |
| 383 snprintf(result_text, RESULT_BUFFER_LENGTH, "Error: Failed to send the request\n"); | 1397 snprintf(result_text, RESULT_BUFFER_LENGTH, "Error: Failed to send the request\n"); |
| 389 Seobeo_Client_Response_Destroy(res); | 1403 Seobeo_Client_Response_Destroy(res); |
| 390 PostDog_Request_SaveFile(); | 1404 PostDog_Request_SaveFile(); |
| 391 return 0; | 1405 return 0; |
| 392 } | 1406 } |
| 393 | 1407 |
| 1408 void *PostDog_Http_Thread(void *arg) | |
| 1409 { | |
| 1410 PostDog_Http_Request(); | |
| 1411 printf("HTTP request finished.\n"); | |
| 1412 LOADING = FALSE; | |
| 1413 return NULL; | |
| 1414 } | |
| 1415 | |
| 1416 void PostDog_Http_Thread_Request() | |
| 1417 { | |
| 1418 pthread_t thread_id; | |
| 1419 LOADING = TRUE; | |
| 1420 if (pthread_create(&thread_id, NULL, PostDog_Http_Thread, NULL) != 0) | |
| 1421 { | |
| 1422 perror("Failed to create thread"); | |
| 1423 return; | |
| 1424 } | |
| 1425 pthread_detach(thread_id); | |
| 1426 } | |
| 1427 | |
| 394 void PostDog_Update_URL(void) | 1428 void PostDog_Update_URL(void) |
| 395 { | 1429 { |
| 396 // Save existing query string if present | 1430 // Save existing query string if present |
| 397 char *question_mark = strchr(url_input_text, '?'); | 1431 char *question_mark = strchr(url_input_text, '?'); |
| 398 if (question_mark) | 1432 if (question_mark) |
| 448 url_input_text[0] = '\0'; | 1482 url_input_text[0] = '\0'; |
| 449 result_text[0] = '\0'; | 1483 result_text[0] = '\0'; |
| 450 active_method_dropdown = 0; | 1484 active_method_dropdown = 0; |
| 451 for (int i = 0; i < Dowa_Array_Length(input_body_array); i++) | 1485 for (int i = 0; i < Dowa_Array_Length(input_body_array); i++) |
| 452 input_body_array[i][0] = '\0'; | 1486 input_body_array[i][0] = '\0'; |
| 1487 | |
| 1488 // Reset text area states when clearing | |
| 1489 GuiTextAreaResetAllStates(); | |
| 453 } | 1490 } |
| 454 | 1491 |
| 455 void PostDog_Load_File(const char *filename) | 1492 void PostDog_Load_File(const char *filename) |
| 456 { | 1493 { |
| 457 char full_file_path[512] = {0}; | 1494 char full_file_path[512] = {0}; |
| 511 snprintf(result_text, strlen(values[i]) + 1, "%s", values[i]); | 1548 snprintf(result_text, strlen(values[i]) + 1, "%s", values[i]); |
| 512 break; | 1549 break; |
| 513 } | 1550 } |
| 514 } | 1551 } |
| 515 | 1552 |
| 1553 // Reset text area states when loading new file | |
| 1554 GuiTextAreaResetAllStates(); | |
| 1555 | |
| 516 Dowa_Arena_Free(init_arena); | 1556 Dowa_Arena_Free(init_arena); |
| 517 Dowa_Arena_Free(split_arena); | 1557 Dowa_Arena_Free(split_arena); |
| 518 } | 1558 } |
| 519 | 1559 |
| 520 Rectangle AddPadding(Rectangle rect, float padding) | 1560 Rectangle AddPadding(Rectangle rect, float padding) |
| 621 GuiSetFont(customFont); | 1661 GuiSetFont(customFont); |
| 622 GuiSetStyle(DEFAULT, TEXT_SIZE, 15); | 1662 GuiSetStyle(DEFAULT, TEXT_SIZE, 15); |
| 623 Image logo_original = LoadImage("postdog/epi_all_colors.png"); | 1663 Image logo_original = LoadImage("postdog/epi_all_colors.png"); |
| 624 ImageResize(&logo_original, 60, 60); | 1664 ImageResize(&logo_original, 60, 60); |
| 625 SetWindowIcon(logo_original); | 1665 SetWindowIcon(logo_original); |
| 626 Texture2D logo_texture = LoadTextureFromImage(logo_original); | 1666 Texture2D logo_texture = LoadTextureFromImage(logo_original); |
| 627 UnloadImage(logo_original); | 1667 UnloadImage(logo_original); |
| 1668 | |
| 1669 // Arena for text area undo states | |
| 1670 g_text_area_arena = Dowa_Arena_Create(1024 * 1024 * 8); // 8MB for undo states | |
| 628 | 1671 |
| 629 // -- Starting pos ---// | 1672 // -- Starting pos ---// |
| 630 Rectangle history_sidebar_rect = { 0 }; | 1673 Rectangle history_sidebar_rect = { 0 }; |
| 631 Dowa_Array_Reserve(history_items, 10); | 1674 Dowa_Array_Reserve(history_items, 10); |
| 632 Dowa_Array_Reserve(new_history_items, 10); | 1675 Dowa_Array_Reserve(new_history_items, 10); |
| 652 | 1695 |
| 653 snprintf(input_body_array[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json"); | 1696 snprintf(input_body_array[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json"); |
| 654 snprintf(input_body_array[TAB_BODY], HEADER_BUFFER_LENGTH, ""); | 1697 snprintf(input_body_array[TAB_BODY], HEADER_BUFFER_LENGTH, ""); |
| 655 | 1698 |
| 656 result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH); | 1699 result_text = (char *)malloc(sizeof(char) * RESULT_BUFFER_LENGTH); |
| 1700 result_text[0] = '\0'; | |
| 657 | 1701 |
| 658 bool method_edit = false; | 1702 bool method_edit = false; |
| 659 | 1703 |
| 660 int sendRequest; | 1704 int sendRequest; |
| 661 | 1705 |
| 662 // -- input --// | 1706 // -- input --// |
| 663 Rectangle input_area_rect = { 0 }; | 1707 Rectangle input_area_rect = { 0 }; |
| 664 Rectangle input_tab_rect = { 0 }; | 1708 Rectangle input_tab_rect = { 0 }; |
| 665 Rectangle input_tab_item_rect = { 0 }; | 1709 Rectangle input_tab_item_rect = { 0 }; |
| 666 Rectangle input_body_rect = { 0 }; | 1710 Rectangle input_body_rect = { 0 }; |
| 667 bool input_body_bool = false; | 1711 bool input_body_edit_mode = false; |
| 668 | 1712 |
| 669 // -- result --// | 1713 // -- result --// |
| 670 Rectangle result_area_rect = { 0 }; | 1714 Rectangle result_area_rect = { 0 }; |
| 671 Rectangle result_body_rect = { 0 }; | 1715 Rectangle result_body_rect = { 0 }; |
| 1716 bool result_body_edit_mode = false; | |
| 672 | 1717 |
| 673 // General styling. | 1718 // General styling. |
| 674 float padding = 10; // TODO make it % based? | 1719 float padding = 10; // TODO make it % based? |
| 675 int prev_input_tab = 0; | 1720 int prev_input_tab = 0; |
| 676 | 1721 |
| 677 // Scroll offsets | 1722 // Scroll offsets |
| 678 float history_scroll_offset = 0; | 1723 float history_scroll_offset = 0; |
| 679 float input_body_scroll_offset = 0; | |
| 680 float result_body_scroll_offset = 0; | |
| 681 | 1724 |
| 682 while (!WindowShouldClose()) | 1725 while (!WindowShouldClose()) |
| 683 { | 1726 { |
| 684 int screen_width = GetScreenWidth(); | 1727 int screen_width = GetScreenWidth(); |
| 685 int screen_height = GetScreenHeight(); | 1728 int screen_height = GetScreenHeight(); |
| 697 Rectangle content_area_rect = RightColumn(screen_rect, history_sidebar_rect, padding); | 1740 Rectangle content_area_rect = RightColumn(screen_rect, history_sidebar_rect, padding); |
| 698 Rectangle logo_area_rect = (Rectangle){ | 1741 Rectangle logo_area_rect = (Rectangle){ |
| 699 .x = history_sidebar_rect.x, | 1742 .x = history_sidebar_rect.x, |
| 700 .y = history_sidebar_rect.y, | 1743 .y = history_sidebar_rect.y, |
| 701 .width = history_sidebar_rect.width, | 1744 .width = history_sidebar_rect.width, |
| 702 .height = 80 | 1745 .height = 80 |
| 703 }; | 1746 }; |
| 704 | 1747 |
| 705 Rectangle history_list_area_rect = Below(logo_area_rect, padding); | 1748 Rectangle history_list_area_rect = Below(logo_area_rect, padding); |
| 706 history_list_area_rect.x += padding; | 1749 history_list_area_rect.x += padding; |
| 707 history_list_area_rect.width = history_sidebar_rect.width - (2 * padding); | 1750 history_list_area_rect.width = history_sidebar_rect.width - (2 * padding); |
| 708 history_list_area_rect.height = history_sidebar_rect.height - logo_area_rect.height - padding; | 1751 history_list_area_rect.height = history_sidebar_rect.height - logo_area_rect.height - padding; |
| 709 | 1752 |
| 710 int32 new_history_items_length = Dowa_Array_Length(new_history_items); | 1753 int32 new_history_items_length = Dowa_Array_Length(new_history_items); |
| 711 int32 history_item_length = Dowa_Array_Length(history_items); | 1754 int32 history_item_length = Dowa_Array_Length(history_items); |
| 714 | 1757 |
| 715 int32 number_of_skipped_items = 0; | 1758 int32 number_of_skipped_items = 0; |
| 716 for (int i = 0; i < total; i++) | 1759 for (int i = 0; i < total; i++) |
| 717 { | 1760 { |
| 718 HistoryItem *curr_history_items = i < new_history_items_length ? | 1761 HistoryItem *curr_history_items = i < new_history_items_length ? |
| 719 &new_history_items[i - new_history_items_length - 1] : &history_items[i - new_history_items_length]; | 1762 &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length]; |
| 720 | 1763 |
| 721 if (curr_history_items->deleted) | 1764 if (curr_history_items->deleted) |
| 722 { | 1765 { |
| 723 number_of_skipped_items++; | 1766 number_of_skipped_items++; |
| 724 continue; | 1767 continue; |
| 792 }; | 1835 }; |
| 793 | 1836 |
| 794 Vector2 mouse_position = GetMousePosition(); | 1837 Vector2 mouse_position = GetMousePosition(); |
| 795 float mouse_wheel = GetMouseWheelMove(); | 1838 float mouse_wheel = GetMouseWheelMove(); |
| 796 | 1839 |
| 797 // Reset input body scroll when tab changes | 1840 // Reset text area state when tab changes |
| 798 if (prev_input_tab != active_input_tab) | 1841 if (prev_input_tab != active_input_tab) |
| 799 { | 1842 { |
| 800 input_body_scroll_offset = 0; | |
| 801 prev_input_tab = active_input_tab; | 1843 prev_input_tab = active_input_tab; |
| 802 } | 1844 } |
| 803 | 1845 |
| 804 // Handle scroll wheel for history | 1846 // Handle scroll wheel for history |
| 805 if (InArea(mouse_position, history_list_area_rect) && mouse_wheel != 0) { | 1847 if (InArea(mouse_position, history_list_area_rect) && mouse_wheel != 0) { |
| 808 float max_scroll = (total * (item_height + padding)) - history_list_area_rect.height; | 1850 float max_scroll = (total * (item_height + padding)) - history_list_area_rect.height; |
| 809 if (history_scroll_offset > 0) history_scroll_offset = 0; | 1851 if (history_scroll_offset > 0) history_scroll_offset = 0; |
| 810 if (history_scroll_offset < -max_scroll && max_scroll > 0) history_scroll_offset = -max_scroll; | 1852 if (history_scroll_offset < -max_scroll && max_scroll > 0) history_scroll_offset = -max_scroll; |
| 811 } | 1853 } |
| 812 | 1854 |
| 813 // Handle scroll wheel for input body | 1855 // Reset |
| 814 if (InArea(mouse_position, input_body_rect) && mouse_wheel != 0) { | 1856 SetMouseCursor(MOUSE_CURSOR_DEFAULT); |
| 815 input_body_scroll_offset += mouse_wheel * 30; | 1857 |
| 816 if (input_body_scroll_offset > 0) input_body_scroll_offset = 0; | 1858 // TODO: Move all for loop rect up here so it does not flicker. |
| 817 } | 1859 if ( |
| 818 | 1860 InArea(mouse_position, result_area_rect) || |
| 819 // Handle scroll wheel for result body | 1861 InArea(mouse_position, input_tab_rect) || |
| 820 if (InArea(mouse_position, result_body_rect) && mouse_wheel != 0) { | 1862 InArea(mouse_position, url_input_bounds_rect) || |
| 821 result_body_scroll_offset += mouse_wheel * 30; | 1863 InArea(mouse_position, url_enter_button_rect) || |
| 822 if (result_body_scroll_offset > 0) result_body_scroll_offset = 0; | 1864 InArea(mouse_position, method_dropdown_rect) || |
| 823 } | 1865 InArea(mouse_position, logo_area_rect) |
| 824 | 1866 ) |
| 1867 SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); | |
| 1868 | |
| 1869 if (Clicked(mouse_position, logo_area_rect)) | |
| 1870 PostDog_Params_Reset(); | |
| 825 BeginDrawing(); | 1871 BeginDrawing(); |
| 826 ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); | 1872 ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); |
| 827 | 1873 |
| 828 // DrawRectangleRec(history_sidebar_rect, Fade(GRAY, 0.1f)); | 1874 // DrawRectangleRec(history_sidebar_rect, Fade(GRAY, 0.1f)); |
| 829 DrawRectangleRounded(history_sidebar_rect, 0.5, 1, Fade(BLUE, 0.1f)); | 1875 DrawRectangleRounded(history_sidebar_rect, 0.5, 1, Fade(BLUE, 0.1f)); |
| 843 DrawTexturePro(logo_texture, source_rect, dest_rect, (Vector2){0, 0}, 0.0f, WHITE); | 1889 DrawTexturePro(logo_texture, source_rect, dest_rect, (Vector2){0, 0}, 0.0f, WHITE); |
| 844 | 1890 |
| 845 BeginScissorMode(history_list_area_rect.x, history_list_area_rect.y, history_list_area_rect.width, history_list_area_rect.height); | 1891 BeginScissorMode(history_list_area_rect.x, history_list_area_rect.y, history_list_area_rect.width, history_list_area_rect.height); |
| 846 for (int i = 0; i < total; i++) | 1892 for (int i = 0; i < total; i++) |
| 847 { | 1893 { |
| 848 HistoryItem *curr_history_items = i < new_history_items_length ? | 1894 HistoryItem *curr_history_items = i < new_history_items_length ? |
| 849 &new_history_items[i - new_history_items_length - 1] : &history_items[i - new_history_items_length]; | 1895 &new_history_items[new_history_items_length - i - 1] : &history_items[i - new_history_items_length]; |
| 850 | 1896 |
| 851 if (curr_history_items->deleted) | 1897 if (curr_history_items->deleted) |
| 852 continue; | 1898 continue; |
| 853 | 1899 |
| 854 float diff = curr_history_items->rect.height*0.6; | 1900 float diff = curr_history_items->rect.height*0.6; |
| 855 DrawRectangleRounded(curr_history_items->rect, 0.5, 1, Fade(BLACK, 0.1f)); | 1901 DrawRectangleRounded(curr_history_items->rect, 0.5, 1, Fade(BLACK, 0.1f)); |
| 856 Rectangle filename_area_rect = curr_history_items->rect; | 1902 Rectangle filename_area_rect = curr_history_items->rect; |
| 857 | 1903 |
| 858 filename_area_rect.height -= diff; | 1904 filename_area_rect.height -= diff; |
| 859 Rectangle icon_area = Below(filename_area_rect, 0); | 1905 Rectangle icon_area = Below(filename_area_rect, 0); |
| 860 icon_area.height = diff; | 1906 icon_area.height = diff; |
| 861 | 1907 |
| 862 // DrawRectangleRec(filename_area_rect, Fade(BLUE, 0.1f)); | 1908 // DrawRectangleRec(filename_area_rect, Fade(BLUE, 0.1f)); |
| 863 // DrawRectangleRec(icon_area, Fade(YELLOW, 0.1f)); | 1909 // DrawRectangleRec(icon_area, Fade(YELLOW, 0.1f)); |
| 864 // DrawRectangleRec(AddPadding(filename_area_rect, 5), Fade(BLUE, 0.1f)); | 1910 // DrawRectangleRec(AddPadding(filename_area_rect, 5), Fade(BLUE, 0.1f)); |
| 865 | 1911 |
| 866 Rectangle icon_area_left_column = LeftColumn(icon_area, 0.5, 0); | 1912 Rectangle icon_area_left_column = LeftColumn(icon_area, 0.5, 0); |
| 867 Rectangle icon_area_right_column = RightColumn(icon_area, icon_area_left_column, 0); | 1913 Rectangle icon_area_right_column = RightColumn(icon_area, icon_area_left_column, 0); |
| 868 | 1914 |
| 869 GuiDrawText(curr_history_items->title, AddPaddingHorizontal(curr_history_items->rect, padding), TEXT_ALIGN_MIDDLE, BLACK); | 1915 filename_area_rect.y += 2*padding; |
| 1916 GuiDrawText(curr_history_items->title, AddPadding(filename_area_rect, padding), TEXT_ALIGN_CENTER, BLACK); | |
| 1917 if ( | |
| 1918 InArea(mouse_position, icon_area_left_column) || | |
| 1919 InArea(mouse_position, icon_area_right_column) | |
| 1920 ) | |
| 1921 SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); | |
| 1922 | |
| 870 if (GuiButton(AddPadding(icon_area_left_column, padding), "view")) | 1923 if (GuiButton(AddPadding(icon_area_left_column, padding), "view")) |
| 871 PostDog_Load_File(curr_history_items->filename); | 1924 PostDog_Load_File(curr_history_items->filename); |
| 872 if (GuiButton(AddPadding(icon_area_right_column, padding), "delete")) | 1925 if (GuiButton(AddPadding(icon_area_right_column, padding), "delete")) |
| 873 { | 1926 { |
| 874 if (!remove(PostDog_Construct_URL(curr_history_items->filename))) | 1927 if (!remove(PostDog_Construct_URL(curr_history_items->filename))) |
| 895 DrawRectangleRec(scrollbar_rect, Fade(WHITE, 0.5f)); | 1948 DrawRectangleRec(scrollbar_rect, Fade(WHITE, 0.5f)); |
| 896 } | 1949 } |
| 897 } | 1950 } |
| 898 | 1951 |
| 899 // URL area Rect | 1952 // URL area Rect |
| 900 GuiDrawText("URL: ", url_text_bounds_rect, TEXT_ALIGN_CENTER, BLACK); | 1953 GuiDrawText("URL: ", url_text_bounds_rect, TEXT_ALIGN_CENTER, BLACK); |
| 901 DrawRectangleRec(url_area_rect, Fade(BLACK, 0.1f)); | 1954 DrawRectangleRec(url_area_rect, Fade(BLACK, 0.1f)); |
| 1955 | |
| 902 if (GuiTextBox(url_input_bounds_rect, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit)) | 1956 if (GuiTextBox(url_input_bounds_rect, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit)) |
| 903 url_input_edit = !url_input_edit; | 1957 url_input_edit = !url_input_edit; |
| 904 | 1958 |
| 1959 if (url_input_edit) | |
| 1960 { | |
| 1961 if (IsKeyPressed(KEY_ENTER)) | |
| 1962 { | |
| 1963 PostDog_Http_Thread_Request(); | |
| 1964 url_input_edit = !url_input_edit; | |
| 1965 } | |
| 1966 } | |
| 1967 | |
| 905 sendRequest = GuiButton(url_enter_button_rect, "ENTER"); | 1968 sendRequest = GuiButton(url_enter_button_rect, "ENTER"); |
| 906 if (sendRequest) | 1969 if (sendRequest) |
| 907 PostDog_Http_Request(); | 1970 PostDog_Http_Thread_Request(); |
| 1971 | |
| 908 if (GuiDropdownBox(method_dropdown_rect, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit)) | 1972 if (GuiDropdownBox(method_dropdown_rect, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit)) |
| 909 method_edit = !method_edit; | 1973 method_edit = !method_edit; |
| 910 | 1974 |
| 911 // Input Tabs Rect | 1975 // Input Tabs Rect |
| 912 DrawRectangleRec(input_area_rect, Fade(BLUE, 0.1f)); | 1976 DrawRectangleRec(input_area_rect, Fade(BLUE, 0.1f)); |
| 913 DrawRectangleRec(input_tab_rect, Fade(DARKBLUE, 0.1f)); | 1977 DrawRectangleRec(input_tab_rect, Fade(DARKBLUE, 0.1f)); |
| 914 GuiSetStyle(TOGGLE, GROUP_PADDING, 0); | 1978 GuiSetStyle(TOGGLE, GROUP_PADDING, 0); |
| 915 GuiDrawRectangle(input_body_rect, 1, GetColor(GuiGetStyle(TEXTBOX, BORDER)), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); | 1979 |
| 916 | 1980 int text_area_id = TEXT_AREA_ID_INPUT_HEADER + active_input_tab; |
| 917 BeginScissorMode(input_body_rect.x, input_body_rect.y, input_body_rect.width, input_body_rect.height); | 1981 if (GuiTextArea(text_area_id, input_body_rect, input_body_array[active_input_tab], |
| 918 Rectangle scrolled_input_rect = AddPadding(input_body_rect, padding * 2); | 1982 BODY_BUFFER_LENGTH, input_body_edit_mode, TRUE, g_text_area_arena)) |
| 919 scrolled_input_rect.y += input_body_scroll_offset; | 1983 input_body_edit_mode = !input_body_edit_mode; |
| 920 scrolled_input_rect.height = MAX_SCROLL_HEIGHT; | 1984 |
| 921 if (active_input_tab != TAB_WEBSOCKET) | 1985 if (active_input_tab != TAB_WEBSOCKET) |
| 1986 { | |
| 1987 WS_BREAK = TRUE; | |
| 1988 if (websocket_thread_id) | |
| 1989 PostDog_Websocket_Destroy(websocket_thread_id); | |
| 1990 } | |
| 1991 else | |
| 1992 { | |
| 1993 WS_BREAK = FALSE; | |
| 1994 Rectangle temp = { | |
| 1995 .x = input_body_rect.x + input_body_rect.width - (3 * padding) - 100, | |
| 1996 .y = input_body_rect.y + input_body_rect.height - (3 * padding) - 20, | |
| 1997 .width = 100, | |
| 1998 .height = 40 | |
| 1999 }; | |
| 2000 if (GuiButton(temp, "Send") || (input_body_edit_mode && IsKeyDown(KEY_LEFT_SHIFT) && IsKeyDown(KEY_ENTER))) | |
| 922 { | 2001 { |
| 923 WS_BREAK = TRUE; | 2002 if (!ws) |
| 924 if (JUNE_GuiTextBox(scrolled_input_rect, input_body_array[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool)) | 2003 websocket_thread_id = PostDog_Websocket_Start_Thread(); |
| 925 input_body_bool = !input_body_bool; | 2004 usleep(10000); |
| 926 } | 2005 PostDog_Websocket_Send(); |
| 927 else | 2006 } |
| 928 { | |
| 929 boolean temp = true; | |
| 930 WS_BREAK = FALSE; | |
| 931 if (GuiTextInputBox( | |
| 932 input_body_rect, | |
| 933 "send message", ws != NULL ? "connected" : "start messaging", | |
| 934 "send", input_body_array[active_input_tab], BODY_BUFFER_LENGTH, &temp) == 1) | |
| 935 PostDog_Websocket_Thread_Send(); | |
| 936 } | |
| 937 EndScissorMode(); | |
| 938 | |
| 939 if (input_body_scroll_offset < 0) { | |
| 940 float scrollbar_height = 10; | |
| 941 float scrollbar_y = input_body_rect.y - (input_body_scroll_offset / MAX_SCROLL_HEIGHT) * (input_body_rect.height - scrollbar_height); | |
| 942 Rectangle scrollbar_rect = { | |
| 943 input_body_rect.x + input_body_rect.width - 5, | |
| 944 scrollbar_y, | |
| 945 5, | |
| 946 scrollbar_height | |
| 947 }; | |
| 948 DrawRectangleRec(scrollbar_rect, Fade(BLUE, 0.5f)); | |
| 949 } | 2007 } |
| 950 | 2008 |
| 2009 | |
| 951 GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab); | 2010 GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab); |
| 952 | |
| 953 PostDog_Update_URL(); | 2011 PostDog_Update_URL(); |
| 954 | 2012 |
| 955 // Result Rect | 2013 // TODO: Add animations. |
| 956 DrawRectangleRec(result_area_rect, Fade(GREEN, 0.1f)); | 2014 DrawRectangleRec(result_area_rect, LOADING ? Fade(RED, 0.1f) : Fade(GREEN, 0.1f)); |
| 957 DrawRectangleRec(result_body_rect, Fade(DARKGREEN, 0.1f)); | 2015 boolean result_toggle = GuiTextArea(TEXT_AREA_ID_RESULT, result_body_rect, result_text, |
| 958 | 2016 RESULT_BUFFER_LENGTH, result_body_edit_mode, TRUE, g_text_area_arena); |
| 959 // Create scrollable result body with offset | 2017 |
| 960 BeginScissorMode(result_body_rect.x, result_body_rect.y, result_body_rect.width, result_body_rect.height); | 2018 GuiToggleGroup(input_tab_item_rect, "Header;Body;Get Param;Websocket", &active_input_tab); |
| 961 Rectangle scrolled_result = result_body_rect; | 2019 if (result_toggle) |
| 962 scrolled_result.y += result_body_scroll_offset; | 2020 result_body_edit_mode = !result_body_edit_mode; |
| 963 scrolled_result.height = MAX_SCROLL_HEIGHT; | 2021 |
| 964 GuiTextBoxMulti(scrolled_result, result_text, RESULT_BUFFER_LENGTH, false); | |
| 965 EndScissorMode(); | |
| 966 | |
| 967 if (result_body_scroll_offset < 0) | |
| 968 { | |
| 969 float scrollbar_height = 10; | |
| 970 float scrollbar_y = result_body_rect.y - (result_body_scroll_offset / MAX_SCROLL_HEIGHT) * (result_body_rect.height - scrollbar_height); | |
| 971 Rectangle scrollbar_rect = { | |
| 972 result_body_rect.x + result_body_rect.width - 5, | |
| 973 scrollbar_y, | |
| 974 5, | |
| 975 scrollbar_height | |
| 976 }; | |
| 977 DrawRectangleRec(scrollbar_rect, Fade(GREEN, 0.5f)); | |
| 978 } | |
| 979 | |
| 980 if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) | |
| 981 { | |
| 982 SetClipboardText(url_input_text); | |
| 983 } | |
| 984 else if (input_body_bool && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) | |
| 985 { | |
| 986 SetClipboardText(input_body_array[active_input_tab]); | |
| 987 } | |
| 988 else if (InArea(mouse_position, result_body_rect)) | |
| 989 { | |
| 990 DrawRectangleRec(result_body_rect, Fade(GREEN, 0.3f)); | |
| 991 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C)) | |
| 992 SetClipboardText(result_text); | |
| 993 } | |
| 994 EndDrawing(); | 2022 EndDrawing(); |
| 995 } | 2023 } |
| 2024 | |
| 2025 GuiTextAreaResetAllStates(); | |
| 2026 Dowa_Arena_Free(g_text_area_arena); | |
| 996 CloseWindow(); | 2027 CloseWindow(); |
| 997 return 0; | 2028 return 0; |
| 998 } | 2029 } |