239
src/ui/ui_core.c
Normal file
239
src/ui/ui_core.c
Normal file
@@ -0,0 +1,239 @@
|
||||
// ui_core.c - Clay wrapper implementation
|
||||
// CLAY_IMPLEMENTATION must be defined exactly once before including clay.h.
|
||||
// In the unity build, ui_core.h (which includes clay.h) has #pragma once,
|
||||
// so we include clay.h directly here first to get the implementation compiled.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// MSVC: stub out Clay's debug view (uses CLAY() macros MSVC can't compile in C)
|
||||
#ifdef _MSC_VER
|
||||
#define CLAY_DISABLE_DEBUG_VIEW
|
||||
#endif
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "clay.h"
|
||||
|
||||
// MSVC C mode: bypass Clay's wrapper struct pattern for designated initializers.
|
||||
// Must come after clay.h so CLAY__CONFIG_WRAPPER is defined, then we override it.
|
||||
#ifdef _MSC_VER
|
||||
#undef CLAY__CONFIG_WRAPPER
|
||||
#define CLAY__CONFIG_WRAPPER(type, ...) ((type) { __VA_ARGS__ })
|
||||
#endif
|
||||
|
||||
#include "ui/ui_core.h"
|
||||
|
||||
////////////////////////////////
|
||||
// UI scale global
|
||||
|
||||
F32 g_ui_scale = 1.0f;
|
||||
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {0};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
g_theme_id = theme_id;
|
||||
|
||||
switch (theme_id) {
|
||||
default:
|
||||
case 0: // Dark
|
||||
g_theme.bg_dark = (Clay_Color){ 26, 26, 26, 255};
|
||||
g_theme.bg_medium = (Clay_Color){ 36, 36, 36, 255};
|
||||
g_theme.bg_light = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){ 54, 54, 54, 255};
|
||||
g_theme.border = (Clay_Color){ 52, 52, 52, 255};
|
||||
g_theme.text = (Clay_Color){220, 220, 220, 255};
|
||||
g_theme.text_dim = (Clay_Color){105, 105, 105, 255};
|
||||
g_theme.accent = (Clay_Color){ 87, 138, 176, 255};
|
||||
g_theme.accent_hover = (Clay_Color){102, 153, 191, 255};
|
||||
g_theme.button_text = (Clay_Color){224, 224, 224, 255};
|
||||
g_theme.disabled_bg = (Clay_Color){ 44, 44, 44, 255};
|
||||
g_theme.disabled_text = (Clay_Color){ 90, 90, 90, 255};
|
||||
g_theme.header_bg = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.title_bar = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){ 58, 58, 58, 255};
|
||||
g_theme.shadow = (Clay_Color){ 0, 0, 0, 30};
|
||||
g_theme.tab_active_top = (Clay_Color){ 70, 120, 160, 255};
|
||||
g_theme.tab_active_bottom= (Clay_Color){ 30, 55, 80, 255};
|
||||
g_theme.tab_inactive = (Clay_Color){ 40, 40, 40, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){ 50, 50, 50, 255};
|
||||
g_theme.tab_text = (Clay_Color){240, 240, 240, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
|
||||
case 1: // Light
|
||||
g_theme.bg_dark = (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.bg_medium = (Clay_Color){212, 210, 207, 255};
|
||||
g_theme.bg_light = (Clay_Color){225, 223, 220, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){232, 230, 227, 255};
|
||||
g_theme.border = (Clay_Color){178, 176, 173, 255};
|
||||
g_theme.text = (Clay_Color){ 35, 33, 30, 255};
|
||||
g_theme.text_dim = (Clay_Color){115, 113, 108, 255};
|
||||
g_theme.accent = (Clay_Color){ 50, 110, 170, 255};
|
||||
g_theme.accent_hover = (Clay_Color){ 65, 125, 185, 255};
|
||||
g_theme.button_text = (Clay_Color){255, 255, 255, 255};
|
||||
g_theme.disabled_bg = (Clay_Color){185, 183, 180, 255};
|
||||
g_theme.disabled_text = (Clay_Color){145, 143, 140, 255};
|
||||
g_theme.header_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.title_bar = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){168, 166, 163, 255};
|
||||
g_theme.shadow = (Clay_Color){ 0, 0, 0, 18};
|
||||
g_theme.tab_active_top = (Clay_Color){ 70, 130, 180, 255};
|
||||
g_theme.tab_active_bottom= (Clay_Color){ 50, 100, 150, 255};
|
||||
g_theme.tab_inactive = (Clay_Color){205, 203, 200, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.tab_text = (Clay_Color){255, 255, 255, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Accent palette
|
||||
|
||||
S32 g_accent_id = 0;
|
||||
|
||||
void ui_set_accent(S32 accent_id) {
|
||||
g_accent_id = accent_id;
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
typedef struct AccentColors {
|
||||
Clay_Color accent, accent_hover, tab_top, tab_bottom, button_text, tab_text;
|
||||
} AccentColors;
|
||||
|
||||
// Dark-mode palettes
|
||||
static const AccentColors dark_palettes[] = {
|
||||
// 0: Blue
|
||||
{ {87,138,176,255}, {102,153,191,255}, {70,120,160,255}, {30,55,80,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 1: Turquoise
|
||||
{ {60,160,155,255}, {75,175,170,255}, {50,140,135,255}, {25,70,68,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 2: Orange
|
||||
{ {200,130,50,255}, {215,145,65,255}, {190,120,40,255}, {100,60,20,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 3: Purple
|
||||
{ {130,100,180,255}, {145,115,195,255}, {120,90,170,255}, {60,45,85,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
// 4: Pink
|
||||
{ {185,95,140,255}, {200,110,155,255}, {175,85,130,255}, {88,42,65,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 5: Red
|
||||
{ {190,75,75,255}, {205,90,90,255}, {180,65,65,255}, {90,32,32,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
// 6: Green
|
||||
{ {80,155,80,255}, {95,170,95,255}, {70,140,70,255}, {35,70,35,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
};
|
||||
|
||||
// Light-mode palettes (slightly more saturated for contrast on light bg)
|
||||
static const AccentColors light_palettes[] = {
|
||||
// 0: Blue
|
||||
{ {50,110,170,255}, {65,125,185,255}, {70,130,180,255}, {50,100,150,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 1: Turquoise
|
||||
{ {30,140,135,255}, {45,155,150,255}, {40,150,145,255}, {25,115,110,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 2: Orange
|
||||
{ {190,115,25,255}, {205,130,40,255}, {195,120,30,255}, {155,90,15,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 3: Purple
|
||||
{ {110,75,170,255}, {125,90,185,255}, {115,80,175,255}, {85,55,140,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 4: Pink
|
||||
{ {175,70,125,255}, {190,85,140,255}, {180,75,130,255}, {140,50,100,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 5: Red
|
||||
{ {185,55,55,255}, {200,70,70,255}, {190,60,60,255}, {150,40,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
// 6: Green
|
||||
{ {55,140,55,255}, {70,155,70,255}, {60,145,60,255}, {40,110,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
};
|
||||
|
||||
S32 idx = accent_id;
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 6) idx = 6;
|
||||
|
||||
const AccentColors *c = dark ? &dark_palettes[idx] : &light_palettes[idx];
|
||||
g_theme.accent = c->accent;
|
||||
g_theme.accent_hover = c->accent_hover;
|
||||
g_theme.tab_active_top = c->tab_top;
|
||||
g_theme.tab_active_bottom = c->tab_bottom;
|
||||
g_theme.button_text = c->button_text;
|
||||
g_theme.tab_text = c->tab_text;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay error handler
|
||||
|
||||
static void clay_error_handler(Clay_ErrorData error) {
|
||||
char buf[512];
|
||||
S32 len = error.errorText.length < 511 ? error.errorText.length : 511;
|
||||
memcpy(buf, error.errorText.chars, len);
|
||||
buf[len] = '\0';
|
||||
fprintf(stderr, "[Clay Error] %s\n", buf);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Clay text measurement bridge
|
||||
// Clay calls this; we forward to the app's UI_MeasureTextFn
|
||||
|
||||
static UI_Context *g_measure_ctx = NULL;
|
||||
|
||||
static Clay_Dimensions clay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *user_data) {
|
||||
UI_Context *ctx = (UI_Context *)user_data;
|
||||
if (!ctx || !ctx->measure_text_fn || text.length == 0) {
|
||||
return (Clay_Dimensions){0, (F32)config->fontSize};
|
||||
}
|
||||
Vec2F32 result = ctx->measure_text_fn(text.chars, text.length, (F32)config->fontSize, ctx->measure_text_user_data);
|
||||
return (Clay_Dimensions){result.x, result.y};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Lifecycle
|
||||
|
||||
UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
UI_Context *ctx = (UI_Context *)calloc(1, sizeof(UI_Context));
|
||||
|
||||
U32 min_memory = Clay_MinMemorySize();
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
Clay_ErrorHandler err_handler = {0};
|
||||
err_handler.errorHandlerFunction = clay_error_handler;
|
||||
err_handler.userData = ctx;
|
||||
|
||||
ctx->clay_ctx = Clay_Initialize(clay_arena,
|
||||
(Clay_Dimensions){viewport_w, viewport_h},
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ui_destroy(UI_Context *ctx) {
|
||||
if (!ctx) return;
|
||||
free(ctx->clay_memory);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
Vec2F32 mouse_pos, B32 mouse_down,
|
||||
Vec2F32 scroll_delta, F32 dt)
|
||||
{
|
||||
g_measure_ctx = ctx;
|
||||
Clay_SetCurrentContext(ctx->clay_ctx);
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions){viewport_w, viewport_h});
|
||||
Clay_SetPointerState((Clay_Vector2){mouse_pos.x, mouse_pos.y}, mouse_down != 0);
|
||||
Clay_UpdateScrollContainers(false, (Clay_Vector2){scroll_delta.x, scroll_delta.y}, dt);
|
||||
Clay_BeginLayout();
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray ui_end_frame(UI_Context *ctx) {
|
||||
(void)ctx;
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
||||
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data) {
|
||||
ctx->measure_text_fn = fn;
|
||||
ctx->measure_text_user_data = user_data;
|
||||
}
|
||||
|
||||
Vec2F32 ui_measure_text(const char *text, S32 length, F32 font_size) {
|
||||
if (!g_measure_ctx || !g_measure_ctx->measure_text_fn || length == 0)
|
||||
return v2f32(0, font_size);
|
||||
return g_measure_ctx->measure_text_fn(text, length, font_size, g_measure_ctx->measure_text_user_data);
|
||||
}
|
||||
Reference in New Issue
Block a user