Mercurial
view third_party/raylib/custom.c @ 182:d6ab5921fedc
Merging in changes I had on my mac related to JSON parser and MPC endpoints.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 23 Jan 2026 21:09:49 -0800 |
| parents | 058de208e640 |
| children |
line wrap: on
line source
#include "third_party/raylib/include/raylib.h" #define RAYGUI_IMPLEMENTATION #include "third_party/raylib/include/raygui.h" #include "third_party/raylib/custom.h" #include <string.h> // ============================================================================ // COLOR SCHEME IMPLEMENTATION // ============================================================================ // Global color scheme instance ColorScheme g_colors = {0}; ColorScheme PostDog_DefaultColorScheme(void) { 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 }; } ColorScheme PostDog_DarkColorScheme(void) { 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 PostDog_SetColorScheme(ColorScheme scheme) { g_colors = scheme; } // ============================================================================ // FUNCTIONALITY HELPERS // ============================================================================ void DefaultBehaviours(void) { 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 --- // Rectangle RightOf(Rectangle ref, float padding) { return (Rectangle){ .x = ref.x + ref.width + padding, .y = ref.y, .width = 0, .height = ref.height }; } Rectangle Below(Rectangle ref, float padding) { return (Rectangle){ .x = ref.x, .y = ref.y + ref.height + padding, .width = ref.width, .height = 0 }; } Rectangle LeftColumn(Rectangle container, float ratio, float padding) { return (Rectangle){ .x = container.x + padding, .y = container.y + padding, .width = (container.width * ratio) - padding, .height = container.height - (2 * padding) }; } Rectangle RightColumn(Rectangle container, Rectangle leftCol, float padding) { return (Rectangle){ .x = leftCol.x + leftCol.width + padding, .y = container.y + padding, .width = container.width - leftCol.width - (3 * padding), .height = container.height - (2 * padding) }; } Rectangle HorizontalSplit(Rectangle container, float ratio) { return (Rectangle){ .x = container.x, .y = container.y, .width = container.width * ratio, .height = container.height }; } void DrawRectangleSelectiveRounded(Rectangle rec, float radius, int segments, Color color, boolean roundTL, boolean roundTR, boolean roundBR, boolean roundBL) { // 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); // 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); // 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); // 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); // 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) { return (Rectangle){ rect.x + padding, rect.y + padding, rect.width - (2 * padding), rect.height - (2 * padding) }; } Rectangle AddPaddingAll(Rectangle rect, float top, float right,float down, float left) { return (Rectangle){ rect.x + left, rect.y + top, rect.width - (right + left), rect.height - (top + down), }; } Rectangle AddPaddingHorizontal(Rectangle rect, float padding) { return (Rectangle){ rect.x + padding, rect.y, rect.width - (2 * padding), rect.height }; } Rectangle AddPaddingVertical(Rectangle rect, float padding) { return (Rectangle){ rect.x, rect.y + padding, rect.width, rect.height - (2 * padding) }; } 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; }