Mercurial
diff third_party/raylib/custom.c @ 173:827c6ac504cd hg-web
Merged in default here.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 19 Jan 2026 18:59:10 -0800 |
| parents | 058de208e640 |
| children |
line wrap: on
line diff
--- a/third_party/raylib/custom.c Sat Jan 10 13:35:09 2026 -0800 +++ b/third_party/raylib/custom.c Mon Jan 19 18:59:10 2026 -0800 @@ -1,36 +1,79 @@ #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> -// -- forward declarations --// +// ============================================================================ +// COLOR SCHEME IMPLEMENTATION +// ============================================================================ + +// Global color scheme instance +ColorScheme g_colors = {0}; -// --- Default Behaviour that should be part of all raylib gui ---/ -void DefaultBehaviours(); -// --- Increase Font Sizes on key press --- // -void IncreaseFontSize(); -// --- Decrease Font Sizes on key press --- // -void DecreaseFontSize(); - - -void DefaultBehaviours() +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) { - if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) - GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); + 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) @@ -84,3 +127,465 @@ } +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; +}