view third_party/raylib/custom.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
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;
}