Mercurial
diff third_party/raylib/custom.c @ 163:058de208e640
[Config] Adding os ignore files.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Mon, 19 Jan 2026 04:52:02 -0800 |
| parents | 87d8d3eb3491 |
| children |
line wrap: on
line diff
--- a/third_party/raylib/custom.c Mon Jan 19 04:51:50 2026 -0800 +++ b/third_party/raylib/custom.c Mon Jan 19 04:52:02 2026 -0800 @@ -2,25 +2,77 @@ #define RAYGUI_IMPLEMENTATION #include "third_party/raylib/include/raygui.h" #include "third_party/raylib/custom.h" +#include <string.h> -// -- forward declarations --// -void DefaultBehaviours() +// ============================================================================ +// COLOR SCHEME IMPLEMENTATION +// ============================================================================ + +// Global color scheme instance +ColorScheme g_colors = {0}; + +ColorScheme PostDog_DefaultColorScheme(void) { - // Font sizes - IncreaseFontSize(); - DecreaseFontSize(); + return (ColorScheme){ + .primary = (Color){255, 140, 0, 255}, // Orange + .secondary = (Color){255, 248, 231, 255}, // Beige/Egg white + .background = (Color){255, 255, 255, 255}, // White + .text = (Color){30, 30, 30, 255}, // Near black + .text_light = (Color){255, 255, 255, 255}, // White + .border = (Color){200, 200, 200, 255}, // Light gray + .highlight = (Color){255, 180, 100, 255}, // Light orange + .success = (Color){76, 175, 80, 255}, // Green + .error = (Color){244, 67, 54, 255}, // Red + .muted = (Color){158, 158, 158, 255}, // Gray + }; } -void IncreaseFontSize() +ColorScheme PostDog_DarkColorScheme(void) { - if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL)) - GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1); + return (ColorScheme){ + .primary = (Color){255, 140, 0, 255}, // Orange + .secondary = (Color){45, 45, 48, 255}, // Dark gray + .background = (Color){30, 30, 30, 255}, // Near black + .text = (Color){240, 240, 240, 255}, // Near white + .text_light = (Color){255, 255, 255, 255}, // White + .border = (Color){70, 70, 70, 255}, // Dark gray + .highlight = (Color){255, 180, 100, 255}, // Light orange + .success = (Color){76, 175, 80, 255}, // Green + .error = (Color){244, 67, 54, 255}, // Red + .muted = (Color){100, 100, 100, 255}, // Gray + }; +} + +void PostDog_InitColorScheme(void) +{ + g_colors = PostDog_DefaultColorScheme(); } -void DecreaseFontSize() +void PostDog_SetColorScheme(ColorScheme scheme) +{ + g_colors = scheme; +} + +// ============================================================================ +// FUNCTIONALITY HELPERS +// ============================================================================ + +void DefaultBehaviours(void) { - if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) - GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); + IncreaseFontSize(); + DecreaseFontSize(); +} + +void IncreaseFontSize(void) +{ + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1); +} + +void DecreaseFontSize(void) +{ + if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) + GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); } // --- Layout helper --- // @@ -74,29 +126,62 @@ }; } + void DrawRectangleSelectiveRounded(Rectangle rec, float radius, int segments, Color color, boolean roundTL, boolean roundTR, boolean roundBR, boolean roundBL) { - DrawRectangle(rec.x + radius, rec.y + radius, rec.width - 2*radius, rec.height - 2*radius, color); - // Top edge (excluding rounded corners) - DrawRectangle(rec.x + (roundTL ? radius : 0), rec.y, rec.width - (roundTL ? radius : 0) - (roundTR ? radius : 0), radius, color); - // Bottom edge - DrawRectangle(rec.x + (roundBL ? radius : 0), rec.y + rec.height - radius, rec.width - (roundBL ? radius : 0) - (roundBR ? radius : 0), radius, color); - // Left edge - DrawRectangle(rec.x, rec.y + (roundTL ? radius : 0), radius, rec.height - (roundTL ? radius : 0) - (roundBL ? radius : 0), color); - // Right edge - DrawRectangle(rec.x + rec.width - radius, rec.y + (roundTR ? radius : 0), radius, rec.height - (roundTR ? radius : 0) - (roundBR ? radius : 0), color); + // Clamp radius to valid range + float maxR = fminf(rec.width, rec.height) * 0.5f; + radius = fminf(fmaxf(radius, 0.0f), maxR); + + // Fill center + DrawRectangleRec((Rectangle){ + rec.x + radius, rec.y + radius, + rec.width - 2.0f * radius, rec.height - 2.0f * radius + }, color); + + // + // |--------| + // | | + // | | + // |--------| + + // Top edge + float topStart = rec.x + (roundTL ? radius : 0.0f); + float topWidth = rec.width - (roundTL ? radius : 0.0f) - (roundTR ? radius : 0.0f); + if (topWidth > 0.0f) + DrawRectangleRec((Rectangle){ topStart, rec.y, topWidth, radius }, color); - if (roundTL) - DrawCircleSector((Vector2){rec.x + radius, rec.y + radius}, radius, 180, 270, segments, color); + // Bottom edge + float botStart = rec.x + (roundBL ? radius : 0.0f); + float botWidth = rec.width - (roundBL ? radius : 0.0f) - (roundBR ? radius : 0.0f); + if (botWidth > 0.0f) + DrawRectangleRec((Rectangle){ botStart, rec.y + rec.height - radius, botWidth, radius }, color); - if (roundTR) - DrawCircleSector((Vector2){rec.x + rec.width - radius, rec.y + radius}, radius, 270, 360, segments, color); + // Left edge + float leftStart = rec.y + (roundTL ? radius : 0.0f); + float leftHeight = rec.height - (roundTL ? radius : 0.0f) - (roundBL ? radius : 0.0f); + if (leftHeight > 0.0f) + DrawRectangleRec((Rectangle){ rec.x, leftStart, radius, leftHeight - radius}, color); - if (roundBR) - DrawCircleSector((Vector2){rec.x + rec.width - radius, rec.y + rec.height - radius}, radius, 0, 90, segments, color); + // Right edge + float rightStart = rec.y + (roundTR ? radius : 0.0f); + float rightHeight = rec.height - (roundTR ? radius : 0.0f) - (roundBR ? radius : 0.0f); + if (rightHeight > 0.0f) + DrawRectangleRec((Rectangle){ rec.x + rec.width - radius, rightStart, radius, rightHeight - radius }, color); - if (roundBL) - DrawCircleSector((Vector2){rec.x + radius, rec.y + rec.height - radius}, radius, 90, 180, segments, color); + // Corners (arcs) + if (roundTL) { + DrawCircleSector((Vector2){ rec.x + radius, rec.y + radius }, radius, 180.0f, 270.0f, segments, color); + } + if (roundTR) { + DrawCircleSector((Vector2){ rec.x + rec.width - radius, rec.y + radius }, radius, 270.0f, 360.0f, segments, color); + } + if (roundBR) { + DrawCircleSector((Vector2){ rec.x + rec.width - radius, rec.y + rec.height - radius }, radius, 0.0f, 90.0f, segments, color); + } + if (roundBL) { + DrawCircleSector((Vector2){ rec.x + radius, rec.y + rec.height - radius }, radius, 90.0f, 180.0f, segments, color); + } } Rectangle AddPadding(Rectangle rect, float padding) @@ -141,985 +226,366 @@ }; } -// // --- Components ---/ -// #define TEXT_SIZE_DEFAULT 10 -// #define TEXT_AREA_LINE_HEIGHT 20 -// #define TEXT_AREA_PADDING 8 -// #define TEXT_AREA_CURSOR_WIDTH 2 -// #define TEXT_AREA_MAX_UNDO_STATES 64 -// #define TEXT_AREA_MAX_INSTANCES 8 -// -// typedef struct { -// char *text; -// int cursor_pos; -// int selection_start; -// int selection_end; -// } TextAreaUndoEntry; -// -// typedef struct { -// int id; // Unique ID for this text area -// int cursor_pos; // Current cursor position in text -// int selection_start; // Selection start (-1 if no selection) -// int selection_end; // Selection end (-1 if no selection) -// float scroll_offset_y; // Vertical scroll offset -// float scroll_offset_x; // Horizontal scroll offset (for non-wrap mode) -// boolean is_selecting; // Currently dragging to select -// boolean is_initialized; // State has been initialized -// -// // Undo history -// TextAreaUndoEntry *undo_stack; // Dowa array of undo entries -// int undo_index; // Current position in undo stack -// -// // Internal tracking -// double last_blink_time; -// boolean cursor_visible; -// } TextAreaState; -// -// static TextAreaState g_text_area_states[TEXT_AREA_MAX_INSTANCES] = {0}; -// static int g_text_area_state_count = 0; -// static char *g_clipboard_text = NULL; -// static Dowa_Arena *g_text_area_arena = NULL; -// -// // Helper functions -// static int TA_Min_Int(int a, int b) { return a < b ? a : b; } -// static int TA_Max_Int(int a, int b) { return a > b ? a : b; } -// static float TA_Min_Float(float a, float b) { return a < b ? a : b; } -// static float TA_Max_Float(float a, float b) { return a > b ? a : b; } -// -// static TextAreaState* GetTextAreaState(int id) { -// for (int i = 0; i < g_text_area_state_count; i++) { -// if (g_text_area_states[i].id == id && g_text_area_states[i].is_initialized) { -// return &g_text_area_states[i]; -// } -// } -// return NULL; -// } -// -// static TextAreaState* CreateTextAreaState(int id) { -// if (g_text_area_state_count >= TEXT_AREA_MAX_INSTANCES) -// return &g_text_area_states[0]; // Reuse first slot -// -// TextAreaState *state = &g_text_area_states[g_text_area_state_count++]; -// memset(state, 0, sizeof(TextAreaState)); -// state->id = id; -// state->selection_start = -1; -// state->selection_end = -1; -// state->undo_index = -1; -// state->cursor_visible = TRUE; -// state->is_initialized = TRUE; -// return state; -// } -// -// static void GetLineAndColumn(const char *text, int pos, int *out_line, int *out_column) { -// int line = 0; -// int column = 0; -// for (int i = 0; i < pos && text[i] != '\0'; i++) -// { -// if (text[i] == '\n') -// { -// line++; -// column = 0; -// } -// else -// column++; -// } -// *out_line = line; -// *out_column = column; -// } -// -// static int GetPosFromLineColumn(const char *text, int line, int column) { -// int current_line = 0; -// int current_col = 0; -// int i = 0; -// -// while (text[i] != '\0') -// { -// if (current_line == line && current_col == column) -// return i; -// -// if (text[i] == '\n') -// { -// if (current_line == line) -// return i; -// current_line++; -// current_col = 0; -// } -// else -// current_col++; -// i++; -// } -// return i; -// } -// -// static int GetLineStart(const char *text, int pos) { -// int i = pos; -// while (i > 0 && text[i - 1] != '\n') -// i--; -// return i; -// } -// -// static int GetLineEnd(const char *text, int pos) { -// int i = pos; -// int len = strlen(text); -// while (i < len && text[i] != '\n') -// i++; -// return i; -// } -// -// static int CountLines(const char *text) { -// int count = 1; -// for (int i = 0; text[i] != '\0'; i++) -// if (text[i] == '\n') count++; -// return count; -// } -// -// static int MeasureTextRange(const char *text, int start, int end, int font_size) { -// if (start >= end) return 0; -// -// char temp[1024]; -// int len = TA_Min_Int(end - start, 1023); -// strncpy(temp, text + start, len); -// temp[len] = '\0'; -// -// return MeasureTextEx(GuiGetFont(), temp, font_size, TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE)).x; -// } -// -// static int GetCharIndexFromPos(const char *text, Rectangle bounds, Vector2 pos, -// boolean wrap, float scroll_y, int font_size, int line_height) { -// if (!text || strlen(text) == 0) return 0; -// -// float content_x = bounds.x + TEXT_AREA_PADDING; -// float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y; -// float content_width = bounds.width - TEXT_AREA_PADDING * 2; -// -// int text_len = strlen(text); -// -// float click_line_y = (pos.y - content_y) / line_height; -// int target_visual_line = (int)click_line_y; -// if (target_visual_line < 0) target_visual_line = 0; -// -// int current_visual_line = 0; -// int i = 0; -// int line_char_start = 0; -// -// while (i <= text_len) { -// boolean is_newline = (i < text_len && text[i] == '\n'); -// -// if (wrap && i > line_char_start) { -// int line_width = MeasureTextRange(text, line_char_start, i, font_size); -// if (line_width > content_width && i > line_char_start + 1) { -// int wrap_pos = i - 1; -// for (int j = i - 1; j > line_char_start; j--) { -// if (text[j] == ' ') { -// wrap_pos = j; -// break; -// } -// } -// -// if (current_visual_line == target_visual_line) { -// float click_x = pos.x - content_x; -// int best_pos = line_char_start; -// float best_dist = 99999; -// -// for (int k = line_char_start; k <= wrap_pos; k++) { -// int char_x = MeasureTextRange(text, line_char_start, k, font_size); -// float dist = (float)(click_x - char_x); -// if (dist < 0) dist = -dist; -// if (dist < best_dist) { -// best_dist = dist; -// best_pos = k; -// } -// } -// return best_pos; -// } -// -// current_visual_line++; -// line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; -// i = line_char_start; -// continue; -// } -// } -// -// if (is_newline || i == text_len) { -// if (current_visual_line == target_visual_line || i == text_len) { -// float click_x = pos.x - content_x; -// int line_end = i; -// int best_pos = line_char_start; -// float best_dist = 99999; -// -// for (int k = line_char_start; k <= line_end; k++) { -// int char_x = MeasureTextRange(text, line_char_start, k, font_size); -// float dist = (float)(click_x - char_x); -// if (dist < 0) dist = -dist; -// if (dist < best_dist) { -// best_dist = dist; -// best_pos = k; -// } -// } -// return TA_Min_Int(best_pos, text_len); -// } -// -// current_visual_line++; -// line_char_start = i + 1; -// } -// -// i++; -// } -// -// return text_len; -// } -// -// static Vector2 GetCursorScreenPos(const char *text, int cursor_pos, Rectangle bounds, -// boolean wrap, float scroll_y, int font_size, int line_height) { -// float content_x = bounds.x + TEXT_AREA_PADDING; -// float content_y = bounds.y + TEXT_AREA_PADDING - scroll_y; -// float content_width = bounds.width - TEXT_AREA_PADDING * 2; -// -// if (!text || cursor_pos == 0) { -// return (Vector2){content_x, content_y}; -// } -// -// int text_len = strlen(text); -// cursor_pos = TA_Min_Int(cursor_pos, text_len); -// -// int visual_line = 0; -// int line_char_start = 0; -// -// for (int i = 0; i <= cursor_pos; i++) { -// if (i == cursor_pos) { -// float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); -// float y = content_y + visual_line * line_height; -// return (Vector2){x, y}; -// } -// -// if (text[i] == '\n') { -// visual_line++; -// line_char_start = i + 1; -// } else if (wrap) { -// int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size); -// if (line_width > content_width && i > line_char_start) { -// int wrap_pos = i; -// for (int j = i; j > line_char_start; j--) { -// if (text[j] == ' ') { -// wrap_pos = j; -// break; -// } -// } -// -// if (cursor_pos <= wrap_pos) { -// float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); -// float y = content_y + visual_line * line_height; -// return (Vector2){x, y}; -// } -// -// visual_line++; -// line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; -// } -// } -// } -// -// float x = content_x + MeasureTextRange(text, line_char_start, cursor_pos, font_size); -// float y = content_y + visual_line * line_height; -// return (Vector2){x, y}; -// } -// -// static float GetContentHeight(const char *text, Rectangle bounds, boolean wrap, -// int font_size, int line_height) { -// if (!text || strlen(text) == 0) return line_height; -// -// float content_width = bounds.width - TEXT_AREA_PADDING * 2; -// int visual_lines = 1; -// int line_char_start = 0; -// int text_len = strlen(text); -// -// for (int i = 0; i <= text_len; i++) { -// if (i == text_len || text[i] == '\n') { -// visual_lines++; -// line_char_start = i + 1; -// } else if (wrap) { -// int line_width = MeasureTextRange(text, line_char_start, i + 1, font_size); -// if (line_width > content_width && i > line_char_start) { -// int wrap_pos = i; -// for (int j = i; j > line_char_start; j--) { -// if (text[j] == ' ') { -// wrap_pos = j; -// break; -// } -// } -// visual_lines++; -// line_char_start = (text[wrap_pos] == ' ') ? wrap_pos + 1 : wrap_pos; -// } -// } -// } -// -// return visual_lines * line_height; -// } -// -// static void PushUndoState(TextAreaState *state, const char *text, Dowa_Arena *arena) { -// TextAreaUndoEntry entry; -// entry.text = Dowa_String_Copy_Arena((char*)text, arena); -// entry.cursor_pos = state->cursor_pos; -// entry.selection_start = state->selection_start; -// entry.selection_end = state->selection_end; -// -// if (state->undo_index < (int)Dowa_Array_Length(state->undo_stack) - 1) { -// dowa__header(state->undo_stack)->length = state->undo_index + 1; -// } -// -// Dowa_Array_Push_Arena(state->undo_stack, entry, arena); -// state->undo_index = Dowa_Array_Length(state->undo_stack) - 1; -// -// if (Dowa_Array_Length(state->undo_stack) > TEXT_AREA_MAX_UNDO_STATES) { -// for (int i = 0; i < (int)Dowa_Array_Length(state->undo_stack) - 1; i++) { -// state->undo_stack[i] = state->undo_stack[i + 1]; -// } -// dowa__header(state->undo_stack)->length--; -// state->undo_index--; -// } -// } -// -// static boolean PerformUndo(TextAreaState *state, char *text, int text_size) { -// if (state->undo_index <= 0) return FALSE; -// -// state->undo_index--; -// TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index]; -// -// strncpy(text, entry->text, text_size - 1); -// text[text_size - 1] = '\0'; -// state->cursor_pos = entry->cursor_pos; -// state->selection_start = entry->selection_start; -// state->selection_end = entry->selection_end; -// -// return TRUE; -// } -// -// static boolean PerformRedo(TextAreaState *state, char *text, int text_size) { -// if (state->undo_index >= (int)Dowa_Array_Length(state->undo_stack) - 1) return FALSE; -// -// state->undo_index++; -// TextAreaUndoEntry *entry = &state->undo_stack[state->undo_index]; -// -// strncpy(text, entry->text, text_size - 1); -// text[text_size - 1] = '\0'; -// state->cursor_pos = entry->cursor_pos; -// state->selection_start = entry->selection_start; -// state->selection_end = entry->selection_end; -// -// return TRUE; -// } -// -// static void InsertTextAtCursor(char *text, int text_size, int cursor_pos, -// const char *insert_text, int *new_cursor_pos) { -// int text_len = strlen(text); -// int insert_len = strlen(insert_text); -// -// if (text_len + insert_len >= text_size - 1) { -// insert_len = text_size - 1 - text_len; -// if (insert_len <= 0) return; -// } -// -// memmove(text + cursor_pos + insert_len, -// text + cursor_pos, -// text_len - cursor_pos + 1); -// -// memcpy(text + cursor_pos, insert_text, insert_len); -// -// *new_cursor_pos = cursor_pos + insert_len; -// } -// -// static void DeleteTextRange(char *text, int start, int end) { -// if (start >= end) return; -// int text_len = strlen(text); -// if (start < 0) start = 0; -// if (end > text_len) end = text_len; -// -// memmove(text + start, text + end, text_len - end + 1); -// } -// -// static char* GetSelectedText(const char *text, int sel_start, int sel_end, Dowa_Arena *arena) { -// if (sel_start < 0 || sel_end < 0 || sel_start >= sel_end) return NULL; -// -// int len = sel_end - sel_start; -// char *result = Dowa_Arena_Allocate(arena, len + 1); -// strncpy(result, text + sel_start, len); -// result[len] = '\0'; -// return result; -// } -// -// boolean GuiTextArea(int id, Rectangle bounds, char *text, int text_size, boolean is_edit_mode, -// boolean should_text_wrap, Dowa_Arena *arena) { -// boolean should_toggle_edit_mode = FALSE; -// -// // Get or create state for this text area -// TextAreaState *state = GetTextAreaState(id); -// if (!state) -// { -// state = CreateTextAreaState(id); -// state->cursor_pos = strlen(text); -// state->last_blink_time = GetTime(); -// PushUndoState(state, text, arena); -// } -// -// int text_len = strlen(text); -// Vector2 mouse_pos = GetMousePosition(); -// boolean mouse_in_bounds = CheckCollisionPointRec(mouse_pos, bounds); -// -// // Handle click to enter/exit edit mode -// if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { -// if (mouse_in_bounds && !is_edit_mode) { -// should_toggle_edit_mode = TRUE; -// } else if (!mouse_in_bounds && is_edit_mode) { -// should_toggle_edit_mode = TRUE; -// } -// } -// -// // Content area -// float content_height = GetContentHeight(text, bounds, should_text_wrap, -// GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT); -// float visible_height = bounds.height - TEXT_AREA_PADDING * 2; -// float max_scroll = TA_Max_Float(0, content_height - visible_height); -// -// // Handle scrolling -// float wheel = GetMouseWheelMove(); -// if (mouse_in_bounds && wheel != 0) { -// state->scroll_offset_y -= wheel * TEXT_AREA_LINE_HEIGHT * 3; -// state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll)); -// } -// -// if (is_edit_mode) { -// boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || -// IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER); -// boolean shift_pressed = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); -// boolean text_changed = FALSE; -// -// double current_time = GetTime(); -// if (current_time - state->last_blink_time > 0.5) { -// state->cursor_visible = !state->cursor_visible; -// state->last_blink_time = current_time; -// } -// -// // Mouse Selection -// if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mouse_in_bounds) { -// int click_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap, -// state->scroll_offset_y, GuiGetStyle(DEFAULT, TEXT_SIZE), -// TEXT_AREA_LINE_HEIGHT); -// state->cursor_pos = click_pos; -// state->selection_start = -1; -// state->selection_end = -1; -// state->is_selecting = TRUE; -// state->cursor_visible = TRUE; -// state->last_blink_time = current_time; -// } -// -// if (state->is_selecting && IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { -// int drag_pos = GetCharIndexFromPos(text, bounds, mouse_pos, should_text_wrap, -// state->scroll_offset_y, GuiGetStyle(DEFAULT, TEXT_SIZE), -// TEXT_AREA_LINE_HEIGHT); -// if (drag_pos != state->cursor_pos) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// } -// state->selection_end = drag_pos; -// } -// } -// -// if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { -// state->is_selecting = FALSE; -// if (state->selection_start >= 0 && state->selection_end >= 0) { -// if (state->selection_start > state->selection_end) { -// int temp = state->selection_start; -// state->selection_start = state->selection_end; -// state->selection_end = temp; -// } -// if (state->selection_start == state->selection_end) { -// state->selection_start = -1; -// state->selection_end = -1; -// } -// } -// } -// -// // Ctrl+A: Select All -// if (ctrl_pressed && IsKeyPressed(KEY_A)) { -// state->selection_start = 0; -// state->selection_end = text_len; -// state->cursor_pos = text_len; -// } -// -// // Ctrl+C: Copy -// if (ctrl_pressed && IsKeyPressed(KEY_C)) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// char *selected = GetSelectedText(text, sel_min, sel_max, arena); -// if (selected) { -// if (g_clipboard_text) free(g_clipboard_text); -// g_clipboard_text = strdup(selected); -// SetClipboardText(g_clipboard_text); -// } -// } -// } -// -// // Ctrl+X: Cut -// if (ctrl_pressed && IsKeyPressed(KEY_X)) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// char *selected = GetSelectedText(text, sel_min, sel_max, arena); -// if (selected) { -// if (g_clipboard_text) free(g_clipboard_text); -// g_clipboard_text = strdup(selected); -// SetClipboardText(g_clipboard_text); -// } -// -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// text_changed = TRUE; -// } -// } -// -// // Ctrl+V: Paste -// if (ctrl_pressed && IsKeyPressed(KEY_V)) { -// const char *clipboard = GetClipboardText(); -// if (clipboard && strlen(clipboard) > 0) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// } else { -// PushUndoState(state, text, arena); -// } -// -// int new_cursor; -// InsertTextAtCursor(text, text_size, state->cursor_pos, clipboard, &new_cursor); -// state->cursor_pos = new_cursor; -// text_changed = TRUE; -// } -// } -// -// // Ctrl+Z: Undo / Ctrl+Shift+Z: Redo -// if (ctrl_pressed && IsKeyPressed(KEY_Z)) { -// if (shift_pressed) { -// PerformRedo(state, text, text_size); -// } else { -// PerformUndo(state, text, text_size); -// } -// } -// -// // Arrow Keys Navigation -// if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { -// if (state->selection_start >= 0 && !shift_pressed) { -// state->cursor_pos = TA_Min_Int(state->selection_start, state->selection_end); -// state->selection_start = -1; -// state->selection_end = -1; -// } else if (state->cursor_pos > 0) { -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->cursor_pos--; -// state->selection_end = state->cursor_pos; -// } else { -// state->cursor_pos--; -// } -// } -// state->cursor_visible = TRUE; -// state->last_blink_time = current_time; -// } -// -// if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { -// if (state->selection_start >= 0 && !shift_pressed) { -// state->cursor_pos = TA_Max_Int(state->selection_start, state->selection_end); -// state->selection_start = -1; -// state->selection_end = -1; -// } else if (state->cursor_pos < text_len) { -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->cursor_pos++; -// state->selection_end = state->cursor_pos; -// } else { -// state->cursor_pos++; -// } -// } -// state->cursor_visible = TRUE; -// state->last_blink_time = current_time; -// } -// -// if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { -// int line, col; -// GetLineAndColumn(text, state->cursor_pos, &line, &col); -// if (line > 0) { -// int new_pos = GetPosFromLineColumn(text, line - 1, col); -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->selection_end = new_pos; -// } else { -// state->selection_start = -1; -// state->selection_end = -1; -// } -// state->cursor_pos = new_pos; -// } -// state->cursor_visible = TRUE; -// state->last_blink_time = current_time; -// } -// -// if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) -// { -// int line, col; -// GetLineAndColumn(text, state->cursor_pos, &line, &col); -// int total_lines = CountLines(text); -// if (line < total_lines - 1) { -// int new_pos = GetPosFromLineColumn(text, line + 1, col); -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->selection_end = new_pos; -// } else { -// state->selection_start = -1; -// state->selection_end = -1; -// } -// state->cursor_pos = new_pos; -// } -// state->cursor_visible = TRUE; -// state->last_blink_time = current_time; -// } -// -// // Home/End keys -// if (IsKeyPressed(KEY_HOME)) { -// int line_start = GetLineStart(text, state->cursor_pos); -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->selection_end = line_start; -// } else { -// state->selection_start = -1; -// state->selection_end = -1; -// } -// state->cursor_pos = line_start; -// } -// -// if (IsKeyPressed(KEY_END)) { -// int line_end = GetLineEnd(text, state->cursor_pos); -// if (shift_pressed) { -// if (state->selection_start < 0) { -// state->selection_start = state->cursor_pos; -// state->selection_end = state->cursor_pos; -// } -// state->selection_end = line_end; -// } else { -// state->selection_start = -1; -// state->selection_end = -1; -// } -// state->cursor_pos = line_end; -// } -// -// // Text Input -// if (!ctrl_pressed) { -// int key = GetCharPressed(); -// while (key > 0) { -// if (key >= 32 && key <= 126) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// text_changed = TRUE; -// } else if (!text_changed) { -// PushUndoState(state, text, arena); -// } -// -// char insert_str[2] = {(char)key, '\0'}; -// int new_cursor; -// InsertTextAtCursor(text, text_size, state->cursor_pos, insert_str, &new_cursor); -// state->cursor_pos = new_cursor; -// text_changed = TRUE; -// } -// key = GetCharPressed(); -// } -// } -// -// // Enter key -// if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// } else { -// PushUndoState(state, text, arena); -// } -// -// int new_cursor; -// InsertTextAtCursor(text, text_size, state->cursor_pos, "\n", &new_cursor); -// state->cursor_pos = new_cursor; -// text_changed = TRUE; -// } -// -// // Backspace -// if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// text_changed = TRUE; -// } else if (state->cursor_pos > 0) { -// PushUndoState(state, text, arena); -// DeleteTextRange(text, state->cursor_pos - 1, state->cursor_pos); -// state->cursor_pos--; -// text_changed = TRUE; -// } -// } -// -// // Delete key -// if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) { -// text_len = strlen(text); -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// text_changed = TRUE; -// } else if (state->cursor_pos < text_len) { -// PushUndoState(state, text, arena); -// DeleteTextRange(text, state->cursor_pos, state->cursor_pos + 1); -// text_changed = TRUE; -// } -// } -// -// // Tab key -// if (IsKeyPressed(KEY_TAB)) { -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// PushUndoState(state, text, arena); -// DeleteTextRange(text, sel_min, sel_max); -// state->cursor_pos = sel_min; -// state->selection_start = -1; -// state->selection_end = -1; -// } else { -// PushUndoState(state, text, arena); -// } -// -// int new_cursor; -// InsertTextAtCursor(text, text_size, state->cursor_pos, " ", &new_cursor); -// state->cursor_pos = new_cursor; -// text_changed = TRUE; -// } -// -// // Auto-scroll to keep cursor visible -// Vector2 cursor_screen = GetCursorScreenPos(text, state->cursor_pos, bounds, -// should_text_wrap, state->scroll_offset_y, -// GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT); -// -// float visible_top = bounds.y + TEXT_AREA_PADDING; -// float visible_bottom = bounds.y + bounds.height - TEXT_AREA_PADDING - TEXT_AREA_LINE_HEIGHT; -// -// if (cursor_screen.y < visible_top) { -// state->scroll_offset_y -= visible_top - cursor_screen.y; -// } else if (cursor_screen.y > visible_bottom) { -// state->scroll_offset_y += cursor_screen.y - visible_bottom; -// } -// -// state->scroll_offset_y = TA_Max_Float(0, TA_Min_Float(state->scroll_offset_y, max_scroll)); -// } -// -// // Drawing -// DrawRectangleRec(bounds, is_edit_mode ? DARKGRAY : (Color){40, 40, 40, 255}); -// DrawRectangleLinesEx(bounds, 1, is_edit_mode ? WHITE : GRAY); -// -// BeginScissorMode((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height); -// -// float content_x = bounds.x + TEXT_AREA_PADDING; -// float content_y = bounds.y + TEXT_AREA_PADDING - state->scroll_offset_y; -// float content_width = bounds.width - TEXT_AREA_PADDING * 2; -// -// text_len = strlen(text); -// -// // Draw selection highlight -// if (state->selection_start >= 0 && state->selection_end >= 0 && -// state->selection_start != state->selection_end) { -// int sel_min = TA_Min_Int(state->selection_start, state->selection_end); -// int sel_max = TA_Max_Int(state->selection_start, state->selection_end); -// -// int line_char_start = 0; -// int visual_line = 0; -// -// for (int i = 0; i <= text_len; i++) { -// boolean is_end = (i == text_len); -// boolean is_newline = (!is_end && text[i] == '\n'); -// boolean should_draw_line = is_end || is_newline; -// -// if (should_text_wrap && !is_end && !is_newline) { -// int line_width = MeasureTextRange(text, line_char_start, i + 1, GuiGetStyle(DEFAULT, TEXT_SIZE)); -// if (line_width > content_width && i > line_char_start) { -// should_draw_line = TRUE; -// } -// } -// -// if (should_draw_line) { -// int line_end = i; -// -// if (sel_min < line_end && sel_max > line_char_start) { -// int highlight_start = TA_Max_Int(sel_min, line_char_start); -// int highlight_end = TA_Min_Int(sel_max, line_end); -// -// float x1 = content_x + MeasureTextRange(text, line_char_start, highlight_start, GuiGetStyle(DEFAULT, TEXT_SIZE)); -// float x2 = content_x + MeasureTextRange(text, line_char_start, highlight_end, GuiGetStyle(DEFAULT, TEXT_SIZE)); -// float y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT; -// -// DrawRectangle((int)x1, (int)y, (int)(x2 - x1), TEXT_AREA_LINE_HEIGHT, -// Fade(SKYBLUE, 0.5f)); -// } -// -// visual_line++; -// line_char_start = i + 1; -// } -// } -// } -// -// // Draw text with wrapping support -// if (should_text_wrap) { -// int line_char_start = 0; -// int visual_line = 0; -// -// for (int i = 0; i <= text_len; i++) { -// boolean is_end = (i == text_len); -// boolean is_newline = (!is_end && text[i] == '\n'); -// boolean should_draw_line = is_end || is_newline; -// -// if (!is_end && !is_newline) { -// int line_width = MeasureTextRange(text, line_char_start, i + 1, GuiGetStyle(DEFAULT, TEXT_SIZE)); -// if (line_width > content_width && i > line_char_start) { -// should_draw_line = TRUE; -// } -// } -// -// if (should_draw_line && i > line_char_start) { -// char line_buffer[1024]; -// int line_len = TA_Min_Int(i - line_char_start, 1023); -// strncpy(line_buffer, text + line_char_start, line_len); -// line_buffer[line_len] = '\0'; -// -// Vector2 draw_text_vector = { -// .x = content_x, -// .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT -// }; -// DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector, -// GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE), WHITE); -// -// visual_line++; -// line_char_start = is_newline ? i + 1 : i; -// } else if (is_newline) { -// visual_line++; -// line_char_start = i + 1; -// } -// } -// } else { -// int line_start = 0; -// int visual_line = 0; -// -// for (int i = 0; i <= text_len; i++) { -// if (i == text_len || text[i] == '\n') { -// if (i > line_start) { -// char line_buffer[1024]; -// int line_len = TA_Min_Int(i - line_start, 1023); -// strncpy(line_buffer, text + line_start, line_len); -// line_buffer[line_len] = '\0'; -// -// Vector2 draw_text_vector = { -// .x = content_x, -// .y = content_y + visual_line * TEXT_AREA_LINE_HEIGHT -// }; -// DrawTextEx(GuiGetFont(), line_buffer, draw_text_vector , -// GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_SIZE_DEFAULT/GuiGetStyle(DEFAULT, TEXT_SIZE), WHITE); -// } -// visual_line++; -// line_start = i + 1; -// } -// } -// } -// -// // Draw cursor -// if (is_edit_mode && state->cursor_visible) { -// Vector2 cursor_pos = GetCursorScreenPos(text, state->cursor_pos, bounds, -// should_text_wrap, state->scroll_offset_y, -// GuiGetStyle(DEFAULT, TEXT_SIZE), TEXT_AREA_LINE_HEIGHT); -// -// DrawRectangle((int)cursor_pos.x, (int)cursor_pos.y, -// TEXT_AREA_CURSOR_WIDTH, TEXT_AREA_LINE_HEIGHT, WHITE); -// } -// -// EndScissorMode(); -// -// // Draw scrollbar if needed -// if (max_scroll > 0) { -// float scrollbar_height = (visible_height / content_height) * visible_height; -// float scrollbar_y = bounds.y + TEXT_AREA_PADDING + -// (state->scroll_offset_y / max_scroll) * (visible_height - scrollbar_height); -// -// DrawRectangle((int)(bounds.x + bounds.width - 8), (int)scrollbar_y, -// 6, (int)scrollbar_height, Fade(WHITE, 0.3f)); -// } -// -// return should_toggle_edit_mode; -// } -// -// void GuiTextAreaResetState(int id) { -// TextAreaState *state = GetTextAreaState(id); -// if (state) { -// if (state->undo_stack) { -// Dowa_Array_Free(state->undo_stack); -// state->undo_stack = NULL; -// } -// state->is_initialized = FALSE; -// } -// } -// -// void GuiTextAreaResetAllStates(void) { -// for (int i = 0; i < g_text_area_state_count; i++) { -// if (g_text_area_states[i].undo_stack) { -// Dowa_Array_Free(g_text_area_states[i].undo_stack); -// g_text_area_states[i].undo_stack = NULL; -// } -// g_text_area_states[i].is_initialized = FALSE; -// } -// g_text_area_state_count = 0; -// } +Rectangle VerticalSplit(Rectangle container, float ratio) +{ + return (Rectangle){ + .x = container.x, + .y = container.y, + .width = container.width, + .height = container.height * ratio + }; +} + +// ============================================================================ +// DRAWING HELPERS - Additional +// ============================================================================ + +void DrawRectangleSelectiveRoundedLines(Rectangle rec, float radius, int segments, Color color, + boolean roundTL, boolean roundTR, boolean roundBR, boolean roundBL) +{ + // Top edge + DrawLine(rec.x + (roundTL ? radius : 0), rec.y, + rec.x + rec.width - (roundTR ? radius : 0), rec.y, color); + // Bottom edge + DrawLine(rec.x + (roundBL ? radius : 0), rec.y + rec.height, + rec.x + rec.width - (roundBR ? radius : 0), rec.y + rec.height, color); + // Left edge + DrawLine(rec.x, rec.y + (roundTL ? radius : 0), + rec.x, rec.y + rec.height - (roundBL ? radius : 0), color); + // Right edge + DrawLine(rec.x + rec.width, rec.y + (roundTR ? radius : 0), + rec.x + rec.width, rec.y + rec.height - (roundBR ? radius : 0), color); + + // Corner arcs + if (roundTL) + DrawCircleSectorLines((Vector2){rec.x + radius, rec.y + radius}, radius, 180, 270, segments, color); + if (roundTR) + DrawCircleSectorLines((Vector2){rec.x + rec.width - radius, rec.y + radius}, radius, 270, 360, segments, color); + if (roundBR) + DrawCircleSectorLines((Vector2){rec.x + rec.width - radius, rec.y + rec.height - radius}, radius, 0, 90, segments, color); + if (roundBL) + DrawCircleSectorLines((Vector2){rec.x + radius, rec.y + rec.height - radius}, radius, 90, 180, segments, color); +} + +// ============================================================================ +// CUSTOM TAB COMPONENT IMPLEMENTATION +// ============================================================================ + +boolean PostDog_TabBar(Rectangle bounds, TabConfig config) +{ + if (config.count <= 0 || config.labels == NULL || config.active_tab == NULL) + return FALSE; + + boolean changed = FALSE; + float tab_width = (bounds.width - config.padding * (config.count - 1)) / config.count; + Vector2 mouse = GetMousePosition(); + + for (int i = 0; i < config.count; i++) + { + Rectangle tab_rect = { + .x = bounds.x + i * (tab_width + config.padding), + .y = bounds.y, + .width = tab_width, + .height = bounds.height + }; + + boolean is_active = (i == *config.active_tab); + boolean is_hovered = CheckCollisionPointRec(mouse, tab_rect); + Color bg_color = is_active ? config.active_color : config.inactive_color; + Color txt_color = is_active ? config.active_text_color : config.text_color; + + // Hover effect + if (is_hovered && !is_active) + bg_color = Fade(config.active_color, 0.5f); + + // Draw tab background with rounded top corners + DrawRectangleSelectiveRounded(tab_rect, config.corner_radius, 8, bg_color, + TRUE, TRUE, FALSE, FALSE); + + // Draw tab label + int text_width = MeasureText(config.labels[i], GuiGetStyle(DEFAULT, TEXT_SIZE)); + Vector2 text_pos = { + tab_rect.x + (tab_rect.width - text_width) / 2, + tab_rect.y + (tab_rect.height - GuiGetStyle(DEFAULT, TEXT_SIZE)) / 2 + }; + DrawText(config.labels[i], text_pos.x, text_pos.y, GuiGetStyle(DEFAULT, TEXT_SIZE), txt_color); + + // Handle click + if (is_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && !is_active) + { + *config.active_tab = i; + changed = TRUE; + } + } + + return changed; +} + +boolean PostDog_TabBarSimple(Rectangle bounds, const char **labels, int count, int *active_tab) +{ + TabConfig config = { + .labels = labels, + .count = count, + .active_tab = active_tab, + .padding = 2, + .corner_radius = 8, + .active_color = g_colors.primary, + .inactive_color = g_colors.secondary, + .text_color = g_colors.text, + .active_text_color = g_colors.text_light, + }; + return PostDog_TabBar(bounds, config); +} + +// ============================================================================ +// SPLIT PANEL IMPLEMENTATION +// ============================================================================ + +void PostDog_SplitPanel(Rectangle bounds, SplitPanelConfig config, + Rectangle *out_left, Rectangle *out_right) +{ + float left_width = bounds.width * config.split_ratio - config.gap / 2; + float right_width = bounds.width * (1.0f - config.split_ratio) - config.gap / 2; + + if (out_left) + { + *out_left = (Rectangle){ + .x = bounds.x, + .y = bounds.y, + .width = left_width, + .height = bounds.height + }; + } + + if (out_right) + { + *out_right = (Rectangle){ + .x = bounds.x + left_width + config.gap, + .y = bounds.y, + .width = right_width, + .height = bounds.height + }; + } +} + +void PostDog_SplitPanelDraw(Rectangle bounds, SplitPanelConfig config) +{ + Rectangle left_rect, right_rect; + PostDog_SplitPanel(bounds, config, &left_rect, &right_rect); + + // Draw left panel with rounded left corners + DrawRectangleSelectiveRounded(left_rect, config.corner_radius, 8, config.left_color, + TRUE, FALSE, FALSE, TRUE); + + // Draw right panel with rounded right corners + DrawRectangleSelectiveRounded(right_rect, config.corner_radius, 8, config.right_color, + FALSE, TRUE, TRUE, FALSE); +} + +// ============================================================================ +// COMBINED INPUT COMPONENTS IMPLEMENTATION +// ============================================================================ + +// Helper: Parse semicolon-separated items and return item at index +static const char* GetDropdownItem(const char *items, int index, char *buffer, int buffer_size) +{ + int current_index = 0; + int start = 0; + int len = strlen(items); + + for (int i = 0; i <= len; i++) + { + if (items[i] == ';' || items[i] == '\0') + { + if (current_index == index) + { + int item_len = i - start; + if (item_len >= buffer_size) item_len = buffer_size - 1; + strncpy(buffer, items + start, item_len); + buffer[item_len] = '\0'; + return buffer; + } + current_index++; + start = i + 1; + } + } + buffer[0] = '\0'; + return buffer; +} + +// Helper: Count items in semicolon-separated string +static int CountDropdownItems(const char *items) +{ + if (!items || items[0] == '\0') return 0; + int count = 1; + for (int i = 0; items[i]; i++) + if (items[i] == ';') count++; + return count; +} + +boolean PostDog_DropdownTextBox(Rectangle bounds, DropdownTextBoxConfig config) +{ + boolean result = FALSE; + + // Calculate dropdown width + float dropdown_w = config.dropdown_width; + if (dropdown_w <= 1.0f) + dropdown_w = bounds.width * dropdown_w; + + // Calculate rectangles + Rectangle dropdown_rect = { + .x = bounds.x, + .y = bounds.y, + .width = dropdown_w, + .height = bounds.height + }; + + Rectangle textbox_rect = { + .x = bounds.x + dropdown_w, + .y = bounds.y, + .width = bounds.width - dropdown_w, + .height = bounds.height + }; + + // Draw combined background + DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8, config.background_color, + TRUE, FALSE, FALSE, TRUE); + DrawRectangleSelectiveRounded(textbox_rect, config.corner_radius, 8, config.background_color, + FALSE, TRUE, TRUE, FALSE); + + // Draw separator line + DrawLine(dropdown_rect.x + dropdown_rect.width, dropdown_rect.y + 4, + dropdown_rect.x + dropdown_rect.width, dropdown_rect.y + dropdown_rect.height - 4, + config.border_color); + + // Draw border + DrawRectangleSelectiveRoundedLines(bounds, config.corner_radius, 8, config.border_color, + TRUE, TRUE, TRUE, TRUE); + + // Custom dropdown with per-item colors + Vector2 mouse_pos = GetMousePosition(); + int item_count = config.item_count > 0 ? config.item_count : CountDropdownItems(config.dropdown_items); + int font_size = GuiGetStyle(DEFAULT, TEXT_SIZE); + char item_buffer[64]; + + // Get current selected item + GetDropdownItem(config.dropdown_items, *config.dropdown_active, item_buffer, sizeof(item_buffer)); + Color current_color = (config.item_colors && *config.dropdown_active < item_count) + ? config.item_colors[*config.dropdown_active] + : config.text_color; + + // Draw selected item in dropdown area + Rectangle dropdown_inner = AddPadding(dropdown_rect, 6); + boolean dropdown_hovered = CheckCollisionPointRec(mouse_pos, dropdown_rect); + + // Highlight on hover + if (dropdown_hovered && !(*config.dropdown_edit_mode)) + { + DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8, + Fade(current_color, 0.5f), TRUE, FALSE, FALSE, TRUE); + } + + // Draw selected text with color + int text_width = MeasureText(item_buffer, font_size); + DrawText(item_buffer, + dropdown_inner.x + (dropdown_inner.width - text_width) / 2, + dropdown_inner.y + (dropdown_inner.height - font_size) / 2, + font_size, current_color); + DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8, + Fade(current_color, 0.3f), TRUE, FALSE, FALSE, TRUE); + + // Draw dropdown arrow + float arrow_size = 6; + float arrow_x = dropdown_rect.x + dropdown_rect.width - 14; + float arrow_y = dropdown_rect.y + dropdown_rect.height / 2; + DrawTriangle( + (Vector2){arrow_x, arrow_y - arrow_size/2}, + (Vector2){arrow_x + arrow_size, arrow_y - arrow_size/2}, + (Vector2){arrow_x + arrow_size/2, arrow_y + arrow_size/2}, + config.text_color); + + // Handle dropdown click + if (dropdown_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + *config.dropdown_edit_mode = !(*config.dropdown_edit_mode); + } + + // Draw dropdown list when open + if (*config.dropdown_edit_mode) + { + float item_height = bounds.height; + Rectangle list_rect = { + .x = dropdown_rect.x, + .y = dropdown_rect.y + dropdown_rect.height + 2, + .width = dropdown_rect.width, + .height = item_height * item_count + }; + + // Background + DrawRectangleRounded(list_rect, 0.1f, 8, config.background_color); + DrawRectangleRoundedLines(list_rect, 0.1f, 8, config.border_color); + + // Draw each item + for (int i = 0; i < item_count; i++) + { + Rectangle item_rect = { + .x = list_rect.x, + .y = list_rect.y + i * item_height, + .width = list_rect.width, + .height = item_height + }; + + GetDropdownItem(config.dropdown_items, i, item_buffer, sizeof(item_buffer)); + Color item_color = (config.item_colors && i < item_count) + ? config.item_colors[i] + : config.text_color; + + boolean item_hovered = CheckCollisionPointRec(mouse_pos, item_rect); + + // Highlight hovered item + if (item_hovered) + DrawRectangleRec(item_rect, Fade(item_color, 0.15f)); + + // Highlight selected item + DrawRectangleRec(item_rect, Fade(item_color, 0.2f)); + + // Draw item text + text_width = MeasureText(item_buffer, font_size); + DrawText(item_buffer, + item_rect.x + (item_rect.width - text_width) / 2, + item_rect.y + (item_rect.height - font_size) / 2, + font_size, item_color); + + // Handle item click + if (item_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + *config.dropdown_active = i; + *config.dropdown_edit_mode = FALSE; + } + } + + // Close dropdown if clicked outside + if (!CheckCollisionPointRec(mouse_pos, list_rect) && + !CheckCollisionPointRec(mouse_pos, dropdown_rect) && + IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + *config.dropdown_edit_mode = FALSE; + } + } + + // Draw text box (using raygui) + Rectangle textbox_inner = AddPadding(textbox_rect, 4); + boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER); + if (*config.text_edit_mode && ctrl_pressed && IsKeyPressed(KEY_V)) + { + const char *clipboard = GetClipboardText(); + snprintf(config.text_buffer, config.text_buffer_size, "%s", clipboard); + } + if (GuiTextBox(textbox_inner, config.text_buffer, config.text_buffer_size, *config.text_edit_mode)) + { + *config.text_edit_mode = !(*config.text_edit_mode); + result = TRUE; + } + + return result; +}