add themes and colors :D

This commit is contained in:
2026-03-03 12:12:41 -05:00
parent bae74d7a96
commit 3173fabfc1
9 changed files with 308 additions and 116 deletions

View File

@@ -19,20 +19,122 @@ F32 g_ui_scale = 1.0f;
// Theme global
UI_Theme g_theme = {};
S32 g_theme_id = 0;
void ui_init_theme() {
g_theme.bg_dark = Clay_Color{ 30, 30, 33, 255};
g_theme.bg_medium = Clay_Color{ 41, 41, 43, 255};
g_theme.bg_light = Clay_Color{ 51, 51, 56, 255};
g_theme.bg_lighter = Clay_Color{ 56, 56, 61, 255};
g_theme.border = Clay_Color{ 56, 56, 61, 255};
g_theme.text = Clay_Color{224, 224, 224, 255};
g_theme.text_dim = Clay_Color{112, 112, 112, 255};
g_theme.accent = Clay_Color{ 87, 138, 176, 255};
g_theme.accent_hover = Clay_Color{102, 153, 191, 255};
g_theme.title_bar = Clay_Color{ 26, 26, 28, 255};
g_theme.scrollbar_bg = Clay_Color{ 26, 26, 28, 255};
g_theme.scrollbar_grab= Clay_Color{ 61, 61, 66, 255};
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{ 30, 30, 33, 255};
g_theme.bg_medium = Clay_Color{ 41, 41, 43, 255};
g_theme.bg_light = Clay_Color{ 51, 51, 56, 255};
g_theme.bg_lighter = Clay_Color{ 56, 56, 61, 255};
g_theme.border = Clay_Color{ 56, 56, 61, 255};
g_theme.text = Clay_Color{224, 224, 224, 255};
g_theme.text_dim = Clay_Color{112, 112, 112, 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{ 50, 50, 50, 255};
g_theme.disabled_text = Clay_Color{100, 100, 100, 255};
g_theme.title_bar = Clay_Color{ 26, 26, 28, 255};
g_theme.scrollbar_bg = Clay_Color{ 26, 26, 28, 255};
g_theme.scrollbar_grab = Clay_Color{ 61, 61, 66, 255};
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{ 45, 45, 48, 255};
g_theme.tab_inactive_hover= Clay_Color{ 55, 55, 60, 255};
g_theme.tab_text = Clay_Color{240, 240, 240, 255};
break;
case 1: // Light
g_theme.bg_dark = Clay_Color{210, 210, 215, 255};
g_theme.bg_medium = Clay_Color{230, 230, 233, 255};
g_theme.bg_light = Clay_Color{242, 242, 245, 255};
g_theme.bg_lighter = Clay_Color{248, 248, 250, 255};
g_theme.border = Clay_Color{190, 190, 195, 255};
g_theme.text = Clay_Color{ 30, 30, 35, 255};
g_theme.text_dim = Clay_Color{120, 120, 130, 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{195, 195, 200, 255};
g_theme.disabled_text = Clay_Color{150, 150, 155, 255};
g_theme.title_bar = Clay_Color{220, 220, 225, 255};
g_theme.scrollbar_bg = Clay_Color{220, 220, 225, 255};
g_theme.scrollbar_grab = Clay_Color{180, 180, 185, 255};
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{218, 218, 222, 255};
g_theme.tab_inactive_hover= Clay_Color{205, 205, 210, 255};
g_theme.tab_text = Clay_Color{255, 255, 255, 255};
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
struct AccentColors {
Clay_Color accent, accent_hover, tab_top, tab_bottom, button_text, tab_text;
};
// 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;
}
////////////////////////////////

View File

@@ -58,11 +58,107 @@ struct UI_Theme {
Clay_Color text_dim;
Clay_Color accent;
Clay_Color accent_hover;
Clay_Color button_text;
Clay_Color disabled_bg;
Clay_Color disabled_text;
Clay_Color title_bar;
Clay_Color scrollbar_bg;
Clay_Color scrollbar_grab;
// Tab bar colors
Clay_Color tab_active_top;
Clay_Color tab_active_bottom;
Clay_Color tab_inactive;
Clay_Color tab_inactive_hover;
Clay_Color tab_text; // Always light — readable on colored tab gradient
};
extern UI_Theme g_theme;
extern S32 g_theme_id;
void ui_init_theme();
// Set theme by index: 0 = Dark, 1 = Light
void ui_set_theme(S32 theme_id);
// Accent color palette: 0=Blue, 1=Turquoise, 2=Orange, 3=Purple, 4=Pink, 5=Red, 6=Green
extern S32 g_accent_id;
void ui_set_accent(S32 accent_id);
////////////////////////////////
// UI scale (Cmd+/Cmd- zoom)
extern F32 g_ui_scale;
// Scale a float value (for CLAY_SIZING_FIXED, corner radii, etc.)
static inline float uis(float x) { return x * g_ui_scale; }
// Scale to uint16_t (for Clay_Padding, childGap, etc.)
static inline uint16_t uip(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); }
// Scale to uint16_t font size
static inline uint16_t uifs(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); }
////////////////////////////////
// Tab styling
#define TAB_ACTIVE_TOP g_theme.tab_active_top
#define TAB_ACTIVE_BOTTOM g_theme.tab_active_bottom
#define TAB_INACTIVE_BG g_theme.tab_inactive
#define TAB_INACTIVE_HOVER g_theme.tab_inactive_hover
#define TAB_HEIGHT uis(26)
#define TAB_CORNER_RADIUS uis(5)
#define TAB_PADDING_H uip(10)
////////////////////////////////
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
enum CustomRenderType {
CUSTOM_RENDER_VGRADIENT = 1,
};
struct CustomGradientData {
CustomRenderType type;
Clay_Color top_color;
Clay_Color bottom_color;
};
////////////////////////////////
// Font sizes
#define FONT_SIZE_NORMAL uifs(15)
#define FONT_SIZE_SMALL uifs(12)
////////////////////////////////
// Widget sizing
#define WIDGET_BUTTON_HEIGHT uis(30)
#define WIDGET_CHECKBOX_HEIGHT uis(28)
#define WIDGET_CHECKBOX_SIZE uis(18)
#define WIDGET_RADIO_OUTER uis(16)
#define WIDGET_RADIO_INNER uis(8)
#define WIDGET_INPUT_HEIGHT uis(30)
#define WIDGET_DROPDOWN_HEIGHT uis(30)
#define WIDGET_DROPDOWN_ITEM_H uis(28)
////////////////////////////////
// Corner radii
#define CORNER_RADIUS_SM uis(3)
#define CORNER_RADIUS_MD uis(6)
#define CORNER_RADIUS_ROUND uis(8)
////////////////////////////////
// Modal / window styling
#define MODAL_OVERLAY_COLOR Clay_Color{ 0, 0, 0, 120}
#define MODAL_WIDTH uis(400)
#define MODAL_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_TITLE_HEIGHT uis(32)
////////////////////////////////
// Panel sizing
#define PANEL_BROWSER_WIDTH uis(200)
#define PANEL_RIGHT_COL_WIDTH uis(250)
#define PANEL_LOG_HEIGHT uis(180)

View File

@@ -1,85 +0,0 @@
#pragma once
// ui_theme.h - Theme constants and defines for the UI layer.
// Centralizes colors, sizing, padding, and radius values used across the app.
#include "clay.h"
////////////////////////////////
// UI scale (Cmd+/Cmd- zoom)
extern F32 g_ui_scale;
// Scale a float value (for CLAY_SIZING_FIXED, corner radii, etc.)
static inline float uis(float x) { return x * g_ui_scale; }
// Scale to uint16_t (for Clay_Padding, childGap, etc.)
static inline uint16_t uip(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); }
// Scale to uint16_t font size
static inline uint16_t uifs(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); }
////////////////////////////////
// Tab styling
#define TAB_ACTIVE_TOP Clay_Color{ 70, 120, 160, 255}
#define TAB_ACTIVE_BOTTOM Clay_Color{ 30, 55, 80, 255}
#define TAB_INACTIVE_BG Clay_Color{ 45, 45, 48, 255}
#define TAB_INACTIVE_HOVER Clay_Color{ 55, 55, 60, 255}
#define TAB_HEIGHT uis(26)
#define TAB_CORNER_RADIUS uis(5)
#define TAB_PADDING_H uip(10)
////////////////////////////////
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
enum CustomRenderType {
CUSTOM_RENDER_VGRADIENT = 1,
};
struct CustomGradientData {
CustomRenderType type;
Clay_Color top_color;
Clay_Color bottom_color;
};
////////////////////////////////
// Font sizes
#define FONT_SIZE_NORMAL uifs(15)
#define FONT_SIZE_SMALL uifs(12)
////////////////////////////////
// Widget sizing
#define WIDGET_BUTTON_HEIGHT uis(30)
#define WIDGET_CHECKBOX_HEIGHT uis(28)
#define WIDGET_CHECKBOX_SIZE uis(18)
#define WIDGET_RADIO_OUTER uis(16)
#define WIDGET_RADIO_INNER uis(8)
#define WIDGET_INPUT_HEIGHT uis(30)
#define WIDGET_DROPDOWN_HEIGHT uis(30)
#define WIDGET_DROPDOWN_ITEM_H uis(28)
////////////////////////////////
// Corner radii
#define CORNER_RADIUS_SM uis(3)
#define CORNER_RADIUS_MD uis(6)
#define CORNER_RADIUS_ROUND uis(8)
////////////////////////////////
// Modal / window styling
#define MODAL_OVERLAY_COLOR Clay_Color{ 0, 0, 0, 120}
#define MODAL_WIDTH uis(400)
#define MODAL_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_TITLE_HEIGHT uis(32)
////////////////////////////////
// Panel sizing
#define PANEL_BROWSER_WIDTH uis(200)
#define PANEL_RIGHT_COL_WIDTH uis(250)
#define PANEL_LOG_HEIGHT uis(180)

View File

@@ -43,8 +43,14 @@ void ui_widgets_begin_frame(PlatformInput input) {
static Clay_TextElementConfig g_widget_text_config;
static Clay_TextElementConfig g_widget_text_config_dim;
static Clay_TextElementConfig g_widget_text_config_sel;
static Clay_TextElementConfig g_widget_text_config_btn;
static Clay_TextElementConfig g_widget_text_config_tab;
static F32 g_widget_text_configs_scale = 0;
void ui_widgets_theme_changed() {
g_widget_text_configs_scale = 0;
}
static void ensure_widget_text_configs() {
if (g_widget_text_configs_scale == g_ui_scale) return;
g_widget_text_configs_scale = g_ui_scale;
@@ -64,6 +70,18 @@ static void ensure_widget_text_configs() {
g_widget_text_config_sel.textColor = Clay_Color{255, 255, 255, 255};
g_widget_text_config_sel.fontSize = FONT_SIZE_NORMAL;
g_widget_text_config_sel.wrapMode = CLAY_TEXT_WRAP_NONE;
// Button text (always readable on accent background)
g_widget_text_config_btn = {};
g_widget_text_config_btn.textColor = g_theme.button_text;
g_widget_text_config_btn.fontSize = FONT_SIZE_NORMAL;
g_widget_text_config_btn.wrapMode = CLAY_TEXT_WRAP_NONE;
// Tab text (always light — readable on colored tab gradient)
g_widget_text_config_tab = {};
g_widget_text_config_tab.textColor = g_theme.tab_text;
g_widget_text_config_tab.fontSize = FONT_SIZE_NORMAL;
g_widget_text_config_tab.wrapMode = CLAY_TEXT_WRAP_NONE;
}
static Clay_String clay_str(const char *s) {
@@ -111,7 +129,7 @@ B32 ui_button(const char *id, const char *text) {
.backgroundColor = hovered ? g_theme.accent_hover : g_theme.accent,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM)
) {
CLAY_TEXT(clay_str(text), &g_widget_text_config);
CLAY_TEXT(clay_str(text), &g_widget_text_config_btn);
}
return (hovered && g_wstate.mouse_clicked) ? 1 : 0;
@@ -737,6 +755,9 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
Clay_Color item_bg = is_item_selected ? g_theme.accent : g_theme.bg_dark;
if (item_hovered) item_bg = g_theme.bg_lighter;
Clay_TextElementConfig *item_text = (is_item_selected && !item_hovered)
? &g_widget_text_config_btn : &g_widget_text_config;
CLAY(item_id,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WIDGET_DROPDOWN_ITEM_H) },
@@ -745,7 +766,7 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
},
.backgroundColor = item_bg
) {
CLAY_TEXT(clay_str(options[i]), &g_widget_text_config);
CLAY_TEXT(clay_str(options[i]), item_text);
}
if (item_hovered && g_wstate.mouse_clicked) {
@@ -892,7 +913,7 @@ S32 ui_modal(const char *id, const char *title, const char *message,
.backgroundColor = btn_hovered ? g_theme.accent_hover : g_theme.accent,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM)
) {
CLAY_TEXT(clay_str(buttons[i]), &g_widget_text_config);
CLAY_TEXT(clay_str(buttons[i]), &g_widget_text_config_btn);
}
if (btn_hovered && g_wstate.mouse_clicked) {
@@ -1057,7 +1078,7 @@ B32 ui_window(const char *id, const char *title, B32 *open,
.backgroundColor = close_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM)
) {
CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config);
CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config_btn);
}
}
@@ -1088,11 +1109,13 @@ B32 ui_window(const char *id, const char *title, B32 *open,
////////////////////////////////
// Tab bar
static CustomGradientData g_tab_gradient = {
CUSTOM_RENDER_VGRADIENT, TAB_ACTIVE_TOP, TAB_ACTIVE_BOTTOM
};
static CustomGradientData g_tab_gradient;
S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
g_tab_gradient.type = CUSTOM_RENDER_VGRADIENT;
g_tab_gradient.top_color = TAB_ACTIVE_TOP;
g_tab_gradient.bottom_color = TAB_ACTIVE_BOTTOM;
Clay_String id_str = clay_str(id);
Clay_ElementId row_eid = Clay__HashString(id_str, 0);
@@ -1122,7 +1145,7 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
.cornerRadius = { .topLeft = TAB_CORNER_RADIUS, .topRight = TAB_CORNER_RADIUS, .bottomLeft = 0, .bottomRight = 0 },
.custom = { .customData = &g_tab_gradient },
) {
CLAY_TEXT(lbl_str, &g_widget_text_config);
CLAY_TEXT(lbl_str, &g_widget_text_config_tab);
}
} else {
Clay_Color bg = hovered ? (Clay_Color)TAB_INACTIVE_HOVER : (Clay_Color)TAB_INACTIVE_BG;

View File

@@ -78,6 +78,9 @@ void ui_widgets_init();
// Call each frame before building widgets. Pass in the frame's input.
void ui_widgets_begin_frame(PlatformInput input);
// Call after changing theme to force text config refresh
void ui_widgets_theme_changed();
// Reset per-frame text input display buffer allocator (called by begin_frame, but
// can also be called manually if needed)
void ui_text_input_reset_display_bufs();