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 }