ui improvements

This commit is contained in:
2026-03-03 15:06:03 -05:00
parent 6346dc8843
commit 9c81f21be7
4 changed files with 201 additions and 20 deletions

View File

@@ -142,6 +142,11 @@ static void build_browser_panel(B32 show) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top highlight (beveled edge)
CLAY(CLAY_ID("BrowserHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
.backgroundColor = g_theme.bg_lighter
) {}
CLAY_TEXT(CLAY_STRING("Instruments"), &g_text_config_normal);
}
}
@@ -169,6 +174,11 @@ static void build_main_panel(AppState *app) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top highlight (beveled edge)
CLAY(CLAY_ID("MainHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
.backgroundColor = g_theme.bg_lighter
) {}
// Section: Buttons
ui_label("LblButtons", "Buttons");
CLAY(CLAY_ID("ButtonRow"),
@@ -313,6 +323,11 @@ static void build_right_panel(AppState *app) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top highlight (beveled edge)
CLAY(CLAY_ID("PropsHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
.backgroundColor = g_theme.bg_lighter
) {}
CLAY_TEXT(CLAY_STRING("Details"), &g_text_config_normal);
}
} else {
@@ -326,6 +341,11 @@ static void build_right_panel(AppState *app) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top highlight (beveled edge)
CLAY(CLAY_ID("MidiHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
.backgroundColor = g_theme.bg_lighter
) {}
// Refresh button
Clay_ElementId refresh_eid = CLAY_ID("MidiRefreshBtn");
B32 refresh_hovered = Clay_PointerOver(refresh_eid);
@@ -504,6 +524,11 @@ static void build_log_panel(B32 show) {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top highlight (beveled edge)
CLAY(CLAY_ID("LogHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
.backgroundColor = g_theme.bg_lighter
) {}
CLAY_TEXT(CLAY_STRING("Output / Log"), &g_text_config_normal);
}
}

View File

@@ -42,6 +42,7 @@ void ui_set_theme(S32 theme_id) {
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.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{ 45, 45, 48, 255};
@@ -66,6 +67,7 @@ void ui_set_theme(S32 theme_id) {
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.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{218, 218, 222, 255};

View File

@@ -64,6 +64,7 @@ struct UI_Theme {
Clay_Color title_bar;
Clay_Color scrollbar_bg;
Clay_Color scrollbar_grab;
Clay_Color shadow; // Semi-transparent black for drop shadows
// Tab bar colors
Clay_Color tab_active_top;

View File

@@ -15,6 +15,95 @@ UI_WidgetState g_wstate = {};
static CustomIconData g_icon_pool[UI_MAX_ICONS_PER_FRAME];
static S32 g_icon_pool_count = 0;
// Gradient per-frame pool (for button/title bar/dropdown/input gradients)
#define UI_MAX_GRADIENTS_PER_FRAME 64
static CustomGradientData g_grad_pool[UI_MAX_GRADIENTS_PER_FRAME];
static S32 g_grad_pool_count = 0;
static CustomGradientData *alloc_gradient(Clay_Color top, Clay_Color bottom) {
if (g_grad_pool_count >= UI_MAX_GRADIENTS_PER_FRAME) return nullptr;
CustomGradientData *g = &g_grad_pool[g_grad_pool_count++];
g->type = CUSTOM_RENDER_VGRADIENT;
g->top_color = top;
g->bottom_color = bottom;
return g;
}
// Per-frame shadow layer ID counter (each shadow uses N unique IDs)
static S32 g_shadow_id_counter = 0;
// Emit a smooth multi-layer drop shadow as floating rects.
// bb: bounding box of the element to shadow (previous frame)
// ox/oy: directional offset (light direction)
// radius: max blur spread
// peak_alpha: total alpha at center when all layers stack
// z: zIndex for shadow layers
// attach_to_root: if true, use absolute positioning; if false, attach to parent_id
// parent_id: only used when attach_to_root=false
// parent_attach: attachment point on parent (e.g. LEFT_BOTTOM for dropdowns)
#define SHADOW_LAYERS 7
static void emit_shadow(Clay_BoundingBox bb, F32 ox, F32 oy, F32 radius,
float peak_alpha, int16_t z,
B32 attach_to_root, uint32_t parent_id,
Clay_FloatingAttachPointType parent_attach) {
if (bb.width <= 0) return;
float per_layer = peak_alpha / (float)SHADOW_LAYERS;
// Draw outermost first (largest, lowest alpha contribution), innermost last
for (S32 i = SHADOW_LAYERS - 1; i >= 0; i--) {
float t = (float)(i + 1) / (float)SHADOW_LAYERS; // 1/N .. 1.0
F32 expand = radius * t;
S32 sid = g_shadow_id_counter++;
Clay_ElementId shadow_eid = CLAY_IDI("Shadow", sid);
if (attach_to_root) {
CLAY(shadow_eid,
.layout = {
.sizing = {
.width = CLAY_SIZING_FIXED(bb.width + expand*2),
.height = CLAY_SIZING_FIXED(bb.height + expand*2),
},
},
.backgroundColor = {0, 0, 0, per_layer},
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS + expand),
.floating = {
.offset = { bb.x - expand + ox, bb.y - expand + oy },
.zIndex = z,
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = CLAY_ATTACH_POINT_LEFT_TOP,
},
.attachTo = CLAY_ATTACH_TO_ROOT,
}
) {}
} else {
CLAY(shadow_eid,
.layout = {
.sizing = {
.width = CLAY_SIZING_FIXED(bb.width + expand*2),
.height = CLAY_SIZING_FIXED(bb.height + expand*2),
},
},
.backgroundColor = {0, 0, 0, per_layer},
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS + expand),
.floating = {
.parentId = parent_id,
.offset = { -expand + ox, -expand + oy },
.zIndex = z,
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = parent_attach,
},
.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID,
}
) {}
}
}
}
void ui_widgets_init() {
g_wstate = {};
}
@@ -23,6 +112,8 @@ void ui_widgets_begin_frame(PlatformInput input) {
g_wstate.input = input;
g_wstate.mouse_clicked = (input.mouse_down && !input.was_mouse_down);
g_icon_pool_count = 0;
g_grad_pool_count = 0;
g_shadow_id_counter = 0;
g_wstate.cursor_blink += 1.0f / 60.0f;
g_wstate.text_input_count = 0;
g_wstate.tab_pressed = 0;
@@ -146,14 +237,20 @@ B32 ui_button(const char *id, const char *text) {
Clay_ElementId eid = WID(id);
B32 hovered = Clay_PointerOver(eid);
Clay_Color base = hovered ? g_theme.accent_hover : g_theme.accent;
Clay_Color top = {(float)Min((int)base.r+12,255), (float)Min((int)base.g+12,255), (float)Min((int)base.b+12,255), base.a};
Clay_Color bot = {(float)Max((int)base.r-15,0), (float)Max((int)base.g-15,0), (float)Max((int)base.b-15,0), base.a};
CustomGradientData *grad = alloc_gradient(top, bot);
CLAY(eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(WIDGET_BUTTON_HEIGHT) },
.padding = { uip(12), uip(12), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = hovered ? g_theme.accent_hover : g_theme.accent,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS)
.backgroundColor = base,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.custom = { .customData = grad },
) {
CLAY_TEXT(clay_str(text), &g_widget_text_config_btn);
}
@@ -598,6 +695,11 @@ B32 ui_text_input(const char *id, char *buf, S32 buf_size) {
Clay_Color bg = is_focused ? g_theme.bg_dark : g_theme.bg_medium;
Clay_Color border_color = is_focused ? g_theme.accent : g_theme.border;
// Inset effect: darker top, lighter bottom (recessed look)
Clay_Color inset_top = {(float)Max((int)bg.r-8,0), (float)Max((int)bg.g-8,0), (float)Max((int)bg.b-8,0), bg.a};
Clay_Color inset_bot = {(float)Min((int)bg.r+3,255), (float)Min((int)bg.g+3,255), (float)Min((int)bg.b+3,255), bg.a};
CustomGradientData *inset_grad = alloc_gradient(inset_top, inset_bot);
CLAY(eid,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WIDGET_INPUT_HEIGHT) },
@@ -607,7 +709,8 @@ B32 ui_text_input(const char *id, char *buf, S32 buf_size) {
},
.backgroundColor = bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.border = { .color = border_color, .width = { 1, 1, 1, 1 } }
.border = { .color = border_color, .width = { 1, 1, 1, 1 } },
.custom = { .customData = inset_grad },
) {
if (len == 0 && !is_focused) {
// Placeholder
@@ -712,6 +815,9 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
Clay_Color bg = is_open ? g_theme.bg_dark : g_theme.bg_medium;
if (header_hovered && !is_open) bg = g_theme.bg_lighter;
Clay_Color dd_top = {(float)Min((int)bg.r+6,255), (float)Min((int)bg.g+6,255), (float)Min((int)bg.b+6,255), bg.a};
CustomGradientData *dd_grad = alloc_gradient(dd_top, bg);
CLAY(eid,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WIDGET_DROPDOWN_HEIGHT) },
@@ -721,7 +827,8 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
},
.backgroundColor = bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.border = { .color = is_open ? g_theme.accent : g_theme.border, .width = { 1, 1, 1, 1 } }
.border = { .color = is_open ? g_theme.accent : g_theme.border, .width = { 1, 1, 1, 1 } },
.custom = { .customData = dd_grad },
) {
CLAY(text_eid,
.layout = {
@@ -756,6 +863,15 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
if (is_open) {
Clay_ElementId list_id = WIDI(id, 502);
float header_width = Clay_GetElementData(eid).boundingBox.width;
// Dropdown list shadow
{
Clay_BoundingBox dd_bb = Clay_GetElementData(list_id).boundingBox;
emit_shadow(dd_bb, uis(2), uis(2), uis(5),
g_theme.shadow.a, 1999,
0, eid.id, CLAY_ATTACH_POINT_LEFT_BOTTOM);
}
CLAY(list_id,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(.min = header_width), .height = CLAY_SIZING_FIT() },
@@ -875,6 +991,15 @@ S32 ui_modal(const char *id, const char *title, const char *message,
}
) {}
// Modal drop shadow
{
Clay_ElementId modal_box_id = WIDI(id, 2001);
Clay_BoundingBox modal_bb = Clay_GetElementData(modal_box_id).boundingBox;
emit_shadow(modal_bb, uis(3), uis(4), uis(10),
g_theme.shadow.a, 1000,
1, 0, CLAY_ATTACH_POINT_LEFT_TOP);
}
// Dialog box (centered)
CLAY(WIDI(id, 2001),
.layout = {
@@ -894,18 +1019,25 @@ S32 ui_modal(const char *id, const char *title, const char *message,
},
.border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } }
) {
// Title bar
CLAY(WIDI(id, 2002),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WINDOW_TITLE_HEIGHT) },
.padding = { uip(12), uip(12), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.title_bar,
.cornerRadius = { CORNER_RADIUS, CORNER_RADIUS, 0, 0 },
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) {
CLAY_TEXT(clay_str(title), &g_widget_text_config);
// Title bar (gradient: lighter top)
{
Clay_Color mtb = g_theme.title_bar;
Clay_Color mtb_top = {(float)Min((int)mtb.r+12,255), (float)Min((int)mtb.g+12,255), (float)Min((int)mtb.b+12,255), mtb.a};
CustomGradientData *mtb_grad = alloc_gradient(mtb_top, mtb);
CLAY(WIDI(id, 2002),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WINDOW_TITLE_HEIGHT) },
.padding = { uip(12), uip(12), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.title_bar,
.cornerRadius = { CORNER_RADIUS, CORNER_RADIUS, 0, 0 },
.border = { .color = g_theme.border, .width = { .bottom = 1 } },
.custom = { .customData = mtb_grad },
) {
CLAY_TEXT(clay_str(title), &g_widget_text_config);
}
}
// Message body
@@ -936,14 +1068,20 @@ S32 ui_modal(const char *id, const char *title, const char *message,
Clay_ElementId btn_id = WIDI(id, 2100 + i);
B32 btn_hovered = Clay_PointerOver(btn_id);
Clay_Color mbtn_base = btn_hovered ? g_theme.accent_hover : g_theme.accent;
Clay_Color mbtn_top = {(float)Min((int)mbtn_base.r+12,255), (float)Min((int)mbtn_base.g+12,255), (float)Min((int)mbtn_base.b+12,255), mbtn_base.a};
Clay_Color mbtn_bot = {(float)Max((int)mbtn_base.r-15,0), (float)Max((int)mbtn_base.g-15,0), (float)Max((int)mbtn_base.b-15,0), mbtn_base.a};
CustomGradientData *mbtn_grad = alloc_gradient(mbtn_top, mbtn_bot);
CLAY(btn_id,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(WIDGET_BUTTON_HEIGHT) },
.padding = { uip(16), uip(16), 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = btn_hovered ? g_theme.accent_hover : g_theme.accent,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS)
.backgroundColor = mbtn_base,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.custom = { .customData = mbtn_grad },
) {
CLAY_TEXT(clay_str(buttons[i]), &g_widget_text_config_btn);
}
@@ -1059,6 +1197,16 @@ B32 ui_window(const char *id, const char *title, B32 *open,
Clay_ElementId close_id = WIDI(id, 3001);
B32 close_hovered = Clay_PointerOver(close_id);
// Drop shadow
{
Clay_BoundingBox win_bb = Clay_GetElementData(eid).boundingBox;
// Use absolute position since window uses offset-based floating
Clay_BoundingBox shadow_bb = { slot->position.x, slot->position.y, win_bb.width, win_bb.height };
emit_shadow(shadow_bb, uis(3), uis(3), uis(8),
g_theme.shadow.a, (int16_t)(100 + slot->z_order - 1),
1, 0, CLAY_ATTACH_POINT_LEFT_TOP);
}
// Window floating element
CLAY(eid,
.layout = {
@@ -1079,7 +1227,11 @@ B32 ui_window(const char *id, const char *title, B32 *open,
},
.border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } }
) {
// Title bar
// Title bar (gradient: lighter top)
Clay_Color tb = g_theme.title_bar;
Clay_Color tb_top = {(float)Min((int)tb.r+12,255), (float)Min((int)tb.g+12,255), (float)Min((int)tb.b+12,255), tb.a};
CustomGradientData *tb_grad = alloc_gradient(tb_top, tb);
CLAY(title_bar_id,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WINDOW_TITLE_HEIGHT) },
@@ -1089,7 +1241,8 @@ B32 ui_window(const char *id, const char *title, B32 *open,
},
.backgroundColor = g_theme.title_bar,
.cornerRadius = { CORNER_RADIUS, CORNER_RADIUS, 0, 0 },
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
.border = { .color = g_theme.border, .width = { .bottom = 1 } },
.custom = { .customData = tb_grad },
) {
// Title text (grows to push close button right)
CLAY(WIDI(id, 3002),