add proper tab implementation

This commit is contained in:
2026-03-03 02:17:44 -05:00
parent 7902db6ec7
commit de6c7754cb
4 changed files with 403 additions and 201 deletions

View File

@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(cd /c/Users/mta/projects/autosample && ./nob.exe debug 2>&1)",
"Bash(cd /c/Users/mta/projects/autosample && rm -f build/*.pdb build/*.obj build/*.ilk && ./nob.exe debug 2>&1)"
]
}
}

View File

@@ -6,6 +6,7 @@
#include "renderer/renderer.h" #include "renderer/renderer.h"
#include "midi/midi.h" #include "midi/midi.h"
#include "ui/ui_core.h" #include "ui/ui_core.h"
#include "ui/ui_theme.h"
#include "ui/ui_widgets.h" #include "ui/ui_widgets.h"
// [cpp] // [cpp]
@@ -57,6 +58,9 @@ struct AppState {
LARGE_INTEGER freq; LARGE_INTEGER freq;
LARGE_INTEGER last_time; LARGE_INTEGER last_time;
// Tab state
S32 right_panel_tab; // 0 = Properties, 1 = MIDI Devices
// Demo widget state // Demo widget state
B32 demo_checkbox_a; B32 demo_checkbox_a;
B32 demo_checkbox_b; B32 demo_checkbox_b;
@@ -78,18 +82,65 @@ struct AppState {
//////////////////////////////// ////////////////////////////////
// Panel builders // Panel builders
static void build_panel_title_bar(Clay_ElementId id, Clay_String title) { static CustomGradientData g_tab_gradient = {
CLAY(id, CUSTOM_RENDER_VGRADIENT, TAB_ACTIVE_TOP, TAB_ACTIVE_BOTTOM
};
static S32 build_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
S32 id_len = (S32)strlen(id);
Clay_String id_str = { .isStaticallyAllocated = false, .length = id_len, .chars = id };
Clay_ElementId row_eid = Clay__HashString(id_str, 0);
CLAY(row_eid,
.layout = { .layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(24) }, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
.padding = { 8, 8, 0, 0 }, .padding = { 0, 0, 4, 0 },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .bottom = 1 } },
) {
for (S32 i = 0; i < count; i++) {
Clay_ElementId tab_eid = Clay__HashStringWithOffset(id_str, (uint32_t)i, 0);
B32 is_active = (i == *selected);
B32 hovered = Clay_PointerOver(tab_eid);
S32 lbl_len = (S32)strlen(labels[i]);
Clay_String lbl_str = { .isStaticallyAllocated = false, .length = lbl_len, .chars = labels[i] };
if (is_active) {
CLAY(tab_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(TAB_HEIGHT) },
.padding = { TAB_PADDING_H, TAB_PADDING_H, 0, 6 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
}, },
.backgroundColor = g_theme.title_bar, .cornerRadius = { .topLeft = TAB_CORNER_RADIUS, .topRight = TAB_CORNER_RADIUS, .bottomLeft = 0, .bottomRight = 0 },
.border = { .color = g_theme.border, .width = { .bottom = 1 } } .custom = { .customData = &g_tab_gradient },
) { ) {
CLAY_TEXT(title, &g_text_config_title); CLAY_TEXT(lbl_str, &g_text_config_title);
} }
} else {
Clay_Color bg = hovered ? (Clay_Color)TAB_INACTIVE_HOVER : (Clay_Color)TAB_INACTIVE_BG;
CLAY(tab_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(TAB_HEIGHT) },
.padding = { TAB_PADDING_H, TAB_PADDING_H, 0, 6 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = bg,
.cornerRadius = { .topLeft = TAB_CORNER_RADIUS, .topRight = TAB_CORNER_RADIUS, .bottomLeft = 0, .bottomRight = 0 },
) {
CLAY_TEXT(lbl_str, &g_text_config_title);
}
}
if (hovered && g_wstate.mouse_clicked) {
*selected = i;
}
}
}
return *selected;
} }
static void build_browser_panel(B32 show) { static void build_browser_panel(B32 show) {
@@ -103,7 +154,11 @@ static void build_browser_panel(B32 show) {
.backgroundColor = g_theme.bg_medium, .backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .right = 1 } } .border = { .color = g_theme.border, .width = { .right = 1 } }
) { ) {
build_panel_title_bar(CLAY_ID("BrowserTitleBar"), CLAY_STRING("Browser")); {
S32 sel = 0;
static const char *browser_tabs[] = { "Browser" };
build_tab_bar("BrowserTabRow", browser_tabs, 1, &sel);
}
CLAY(CLAY_ID("BrowserContent"), CLAY(CLAY_ID("BrowserContent"),
.layout = { .layout = {
@@ -126,7 +181,11 @@ static void build_main_panel(AppState *app) {
}, },
.backgroundColor = g_theme.bg_light .backgroundColor = g_theme.bg_light
) { ) {
build_panel_title_bar(CLAY_ID("MainTitleBar"), CLAY_STRING("Main")); {
S32 sel = 0;
static const char *main_tabs[] = { "Main" };
build_tab_bar("MainTabRow", main_tabs, 1, &sel);
}
CLAY(CLAY_ID("MainContent"), CLAY(CLAY_ID("MainContent"),
.layout = { .layout = {
@@ -258,19 +317,20 @@ static void build_main_panel(AppState *app) {
} }
} }
static void build_properties_panel(B32 show) { static void build_right_panel(AppState *app) {
if (!show) return; static const char *right_tabs[] = { "Properties", "MIDI Devices" };
CLAY(CLAY_ID("PropertiesPanel"), CLAY(CLAY_ID("RightPanel"),
.layout = { .layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM, .layoutDirection = CLAY_TOP_TO_BOTTOM,
}, },
.backgroundColor = g_theme.bg_medium, .backgroundColor = g_theme.bg_medium
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) { ) {
build_panel_title_bar(CLAY_ID("PropertiesTitleBar"), CLAY_STRING("Properties")); build_tab_bar("RightTabs", right_tabs, 2, &app->right_panel_tab);
if (app->right_panel_tab == 0) {
// Properties content
CLAY(CLAY_ID("PropertiesContent"), CLAY(CLAY_ID("PropertiesContent"),
.layout = { .layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
@@ -281,21 +341,9 @@ static void build_properties_panel(B32 show) {
) { ) {
CLAY_TEXT(CLAY_STRING("Details"), &g_text_config_normal); CLAY_TEXT(CLAY_STRING("Details"), &g_text_config_normal);
} }
} } else {
} // MIDI Devices content
MidiEngine *midi = app->midi;
static void build_midi_panel(B32 show, MidiEngine *midi) {
if (!show) return;
CLAY(CLAY_ID("MidiPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium
) {
build_panel_title_bar(CLAY_ID("MidiTitleBar"), CLAY_STRING("MIDI Devices"));
CLAY(CLAY_ID("MidiContent"), CLAY(CLAY_ID("MidiContent"),
.layout = { .layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
@@ -455,6 +503,7 @@ static void build_midi_panel(B32 show, MidiEngine *midi) {
} }
} }
} }
}
static void build_log_panel(B32 show) { static void build_log_panel(B32 show) {
if (!show) return; if (!show) return;
@@ -467,7 +516,11 @@ static void build_log_panel(B32 show) {
.backgroundColor = g_theme.bg_medium, .backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .top = 1 } } .border = { .color = g_theme.border, .width = { .top = 1 } }
) { ) {
build_panel_title_bar(CLAY_ID("LogTitleBar"), CLAY_STRING("Log")); {
S32 sel = 0;
static const char *log_tabs[] = { "Log" };
build_tab_bar("LogTabRow", log_tabs, 1, &sel);
}
CLAY(CLAY_ID("LogContent"), CLAY(CLAY_ID("LogContent"),
.layout = { .layout = {
@@ -535,8 +588,7 @@ static void build_ui(AppState *app) {
}, },
.border = { .color = g_theme.border, .width = { .left = 1 } } .border = { .color = g_theme.border, .width = { .left = 1 } }
) { ) {
build_properties_panel(app->show_props); build_right_panel(app);
build_midi_panel(app->show_midi_devices, app->midi);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
#include "renderer/renderer.h" #include "renderer/renderer.h"
#include "ui/ui_core.h" #include "ui/ui_core.h"
#include "ui/ui_theme.h"
#include <d3d12.h> #include <d3d12.h>
#include <dxgi1_5.h> #include <dxgi1_5.h>
@@ -41,7 +42,7 @@ struct UIVertex {
float col[4]; float col[4];
float rect_min[2]; float rect_min[2];
float rect_max[2]; float rect_max[2];
float corner_radius; float corner_radii[4]; // TL, TR, BR, BL
float border_thickness; float border_thickness;
float softness; float softness;
float mode; // 0 = rect SDF, 1 = textured float mode; // 0 = rect SDF, 1 = textured
@@ -66,7 +67,7 @@ struct VSInput {
float4 col : COLOR0; float4 col : COLOR0;
float2 rect_min : TEXCOORD1; float2 rect_min : TEXCOORD1;
float2 rect_max : TEXCOORD2; float2 rect_max : TEXCOORD2;
float corner_radius : TEXCOORD3; float4 corner_radii : TEXCOORD3;
float border_thickness : TEXCOORD4; float border_thickness : TEXCOORD4;
float softness : TEXCOORD5; float softness : TEXCOORD5;
float mode : TEXCOORD6; float mode : TEXCOORD6;
@@ -78,7 +79,7 @@ struct PSInput {
float4 col : COLOR0; float4 col : COLOR0;
float2 rect_min : TEXCOORD1; float2 rect_min : TEXCOORD1;
float2 rect_max : TEXCOORD2; float2 rect_max : TEXCOORD2;
float corner_radius : TEXCOORD3; float4 corner_radii : TEXCOORD3;
float border_thickness : TEXCOORD4; float border_thickness : TEXCOORD4;
float softness : TEXCOORD5; float softness : TEXCOORD5;
float mode : TEXCOORD6; float mode : TEXCOORD6;
@@ -102,7 +103,7 @@ PSInput VSMain(VSInput input) {
output.col = input.col; output.col = input.col;
output.rect_min = input.rect_min; output.rect_min = input.rect_min;
output.rect_max = input.rect_max; output.rect_max = input.rect_max;
output.corner_radius = input.corner_radius; output.corner_radii = input.corner_radii;
output.border_thickness = input.border_thickness; output.border_thickness = input.border_thickness;
output.softness = input.softness; output.softness = input.softness;
output.mode = input.mode; output.mode = input.mode;
@@ -126,7 +127,10 @@ float4 PSMain(PSInput input) : SV_TARGET {
float2 pixel_pos = input.pos.xy; float2 pixel_pos = input.pos.xy;
float2 rect_center = (input.rect_min + input.rect_max) * 0.5; float2 rect_center = (input.rect_min + input.rect_max) * 0.5;
float2 rect_half_size = (input.rect_max - input.rect_min) * 0.5; float2 rect_half_size = (input.rect_max - input.rect_min) * 0.5;
float radius = input.corner_radius; // corner_radii = (TL, TR, BR, BL) — select radius by quadrant
float radius = (pixel_pos.x < rect_center.x)
? ((pixel_pos.y < rect_center.y) ? input.corner_radii.x : input.corner_radii.w)
: ((pixel_pos.y < rect_center.y) ? input.corner_radii.y : input.corner_radii.z);
float softness = max(input.softness, 0.5); float softness = max(input.softness, 0.5);
float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius); float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);
@@ -692,7 +696,7 @@ static bool create_ui_pipeline(Renderer *r) {
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(UIVertex, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(UIVertex, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(UIVertex, rect_min), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(UIVertex, rect_min), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 2, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(UIVertex, rect_max), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 2, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(UIVertex, rect_max), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 3, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, corner_radius), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(UIVertex, corner_radii), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 4, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, border_thickness), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 4, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, border_thickness), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 5, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, softness), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 5, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, softness), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 6, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, mode), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 6, DXGI_FORMAT_R32_FLOAT, 0, offsetof(UIVertex, mode), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
@@ -809,7 +813,8 @@ static void emit_quad(DrawBatch *batch,
float u0, float v0, float u1, float v1, float u0, float v0, float u1, float v1,
float cr, float cg, float cb, float ca, float cr, float cg, float cb, float ca,
float rmin_x, float rmin_y, float rmax_x, float rmax_y, float rmin_x, float rmin_y, float rmax_x, float rmax_y,
float corner_radius, float border_thickness, float softness, float mode) float cr_tl, float cr_tr, float cr_br, float cr_bl,
float border_thickness, float softness, float mode)
{ {
if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES) if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
return; return;
@@ -833,7 +838,8 @@ static void emit_quad(DrawBatch *batch,
v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca; v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca;
v[i].rect_min[0] = rmin_x; v[i].rect_min[1] = rmin_y; v[i].rect_min[0] = rmin_x; v[i].rect_min[1] = rmin_y;
v[i].rect_max[0] = rmax_x; v[i].rect_max[1] = rmax_y; v[i].rect_max[0] = rmax_x; v[i].rect_max[1] = rmax_y;
v[i].corner_radius = corner_radius; v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
v[i].border_thickness = border_thickness; v[i].border_thickness = border_thickness;
v[i].softness = softness; v[i].softness = softness;
v[i].mode = mode; v[i].mode = mode;
@@ -850,13 +856,60 @@ static void emit_quad(DrawBatch *batch,
static void emit_rect(DrawBatch *batch, static void emit_rect(DrawBatch *batch,
float x0, float y0, float x1, float y1, float x0, float y0, float x1, float y1,
float cr, float cg, float cb, float ca, float cr, float cg, float cb, float ca,
float corner_radius, float border_thickness, float softness) float cr_tl, float cr_tr, float cr_br, float cr_bl,
float border_thickness, float softness)
{ {
emit_quad(batch, x0, y0, x1, y1, emit_quad(batch, x0, y0, x1, y1,
0, 0, 0, 0, 0, 0, 0, 0,
cr, cg, cb, ca, cr, cg, cb, ca,
x0, y0, x1, y1, x0, y0, x1, y1,
corner_radius, border_thickness, softness, 0.0f); cr_tl, cr_tr, cr_br, cr_bl,
border_thickness, softness, 0.0f);
}
static void emit_rect_vgradient(DrawBatch *batch,
float x0, float y0, float x1, float y1,
float tr, float tg, float tb, float ta,
float br, float bg, float bb_, float ba,
float cr_tl, float cr_tr, float cr_br, float cr_bl,
float softness)
{
if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
return;
U32 base = batch->vertex_count;
UIVertex *v = &batch->vertices[base];
float pad = softness + 1.0f;
float px0 = x0 - pad, py0 = y0 - pad, px1 = x1 + pad, py1 = y1 + pad;
v[0].pos[0] = px0; v[0].pos[1] = py0; v[0].uv[0] = 0; v[0].uv[1] = 0;
v[1].pos[0] = px1; v[1].pos[1] = py0; v[1].uv[0] = 0; v[1].uv[1] = 0;
v[2].pos[0] = px1; v[2].pos[1] = py1; v[2].uv[0] = 0; v[2].uv[1] = 0;
v[3].pos[0] = px0; v[3].pos[1] = py1; v[3].uv[0] = 0; v[3].uv[1] = 0;
// Top vertices get top color, bottom vertices get bottom color
v[0].col[0] = tr; v[0].col[1] = tg; v[0].col[2] = tb; v[0].col[3] = ta;
v[1].col[0] = tr; v[1].col[1] = tg; v[1].col[2] = tb; v[1].col[3] = ta;
v[2].col[0] = br; v[2].col[1] = bg; v[2].col[2] = bb_; v[2].col[3] = ba;
v[3].col[0] = br; v[3].col[1] = bg; v[3].col[2] = bb_; v[3].col[3] = ba;
for (int i = 0; i < 4; i++) {
v[i].rect_min[0] = x0; v[i].rect_min[1] = y0;
v[i].rect_max[0] = x1; v[i].rect_max[1] = y1;
v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
v[i].border_thickness = 0;
v[i].softness = softness;
v[i].mode = 0;
}
U32 *idx = &batch->indices[batch->index_count];
idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;
batch->vertex_count += 4;
batch->index_count += 6;
} }
static void emit_text_glyphs(DrawBatch *batch, Renderer *r, static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
@@ -901,7 +954,8 @@ static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
g->u0, g->v0, g->u1, g->v1, g->u0, g->v0, g->u1, g->v1,
cr, cg, cb, ca, cr, cg, cb, ca,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1.0f); 0, 0, 0, 0,
0, 0, 1.0f);
x += g->x_advance * scale; x += g->x_advance * scale;
} }
@@ -1075,13 +1129,12 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData *rect = &cmd->renderData.rectangle; Clay_RectangleRenderData *rect = &cmd->renderData.rectangle;
Clay_Color c = rect->backgroundColor; Clay_Color c = rect->backgroundColor;
// Use average corner radius for SDF
float cr = (rect->cornerRadius.topLeft + rect->cornerRadius.topRight +
rect->cornerRadius.bottomLeft + rect->cornerRadius.bottomRight) * 0.25f;
emit_rect(&batch, emit_rect(&batch,
bb.x, bb.y, bb.x + bb.width, bb.y + bb.height, bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
c.r / 255.f, c.g / 255.f, c.b / 255.f, c.a / 255.f, c.r / 255.f, c.g / 255.f, c.b / 255.f, c.a / 255.f,
cr, 0, 1.0f); rect->cornerRadius.topLeft, rect->cornerRadius.topRight,
rect->cornerRadius.bottomRight, rect->cornerRadius.bottomLeft,
0, 1.0f);
} break; } break;
case CLAY_RENDER_COMMAND_TYPE_BORDER: { case CLAY_RENDER_COMMAND_TYPE_BORDER: {
@@ -1095,19 +1148,19 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
// Draw individual border sides as thin rects // Draw individual border sides as thin rects
if (border->width.top > 0) { if (border->width.top > 0) {
emit_rect(&batch, bb.x, bb.y, bb.x + bb.width, bb.y + border->width.top, emit_rect(&batch, bb.x, bb.y, bb.x + bb.width, bb.y + border->width.top,
cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 1.0f); cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
} }
if (border->width.bottom > 0) { if (border->width.bottom > 0) {
emit_rect(&batch, bb.x, bb.y + bb.height - border->width.bottom, bb.x + bb.width, bb.y + bb.height, emit_rect(&batch, bb.x, bb.y + bb.height - border->width.bottom, bb.x + bb.width, bb.y + bb.height,
cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 1.0f); cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
} }
if (border->width.left > 0) { if (border->width.left > 0) {
emit_rect(&batch, bb.x, bb.y, bb.x + border->width.left, bb.y + bb.height, emit_rect(&batch, bb.x, bb.y, bb.x + border->width.left, bb.y + bb.height,
cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 1.0f); cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
} }
if (border->width.right > 0) { if (border->width.right > 0) {
emit_rect(&batch, bb.x + bb.width - border->width.right, bb.y, bb.x + bb.width, bb.y + bb.height, emit_rect(&batch, bb.x + bb.width - border->width.right, bb.y, bb.x + bb.width, bb.y + bb.height,
cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 1.0f); cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
} }
} break; } break;
@@ -1137,8 +1190,26 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
r->command_list->RSSetScissorRects(1, &full_scissor); r->command_list->RSSetScissorRects(1, &full_scissor);
} break; } break;
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Clay_CustomRenderData *custom = &cmd->renderData.custom;
if (custom->customData) {
CustomRenderType type = *(CustomRenderType *)custom->customData;
if (type == CUSTOM_RENDER_VGRADIENT) {
CustomGradientData *grad = (CustomGradientData *)custom->customData;
Clay_Color tc = grad->top_color;
Clay_Color bc = grad->bottom_color;
emit_rect_vgradient(&batch,
bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
tc.r / 255.f, tc.g / 255.f, tc.b / 255.f, tc.a / 255.f,
bc.r / 255.f, bc.g / 255.f, bc.b / 255.f, bc.a / 255.f,
custom->cornerRadius.topLeft, custom->cornerRadius.topRight,
custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft,
1.0f);
}
}
} break;
case CLAY_RENDER_COMMAND_TYPE_IMAGE: case CLAY_RENDER_COMMAND_TYPE_IMAGE:
case CLAY_RENDER_COMMAND_TYPE_CUSTOM:
default: default:
break; break;
} }

71
src/ui/ui_theme.h Normal file
View File

@@ -0,0 +1,71 @@
#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"
////////////////////////////////
// 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 26
#define TAB_CORNER_RADIUS 5
#define TAB_PADDING_H 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 15
#define FONT_SIZE_SMALL 12
////////////////////////////////
// Widget sizing
#define WIDGET_BUTTON_HEIGHT 30
#define WIDGET_CHECKBOX_HEIGHT 28
#define WIDGET_CHECKBOX_SIZE 18
#define WIDGET_RADIO_OUTER 16
#define WIDGET_RADIO_INNER 8
#define WIDGET_INPUT_HEIGHT 30
#define WIDGET_DROPDOWN_HEIGHT 30
#define WIDGET_DROPDOWN_ITEM_H 28
////////////////////////////////
// Corner radii
#define CORNER_RADIUS_SM 3
#define CORNER_RADIUS_MD 6
#define CORNER_RADIUS_ROUND 8
////////////////////////////////
// Modal / window styling
#define MODAL_OVERLAY_COLOR Clay_Color{ 0, 0, 0, 120}
#define MODAL_WIDTH 400
#define MODAL_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_CORNER_RADIUS CORNER_RADIUS_MD
#define WINDOW_TITLE_HEIGHT 32
////////////////////////////////
// Panel sizing
#define PANEL_BROWSER_WIDTH 200
#define PANEL_RIGHT_COL_WIDTH 250
#define PANEL_LOG_HEIGHT 180