ui improvements
This commit is contained in:
25
src/main.cpp
25
src/main.cpp
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user