From 6346dc884347247636fcc8a3c285318a3c2781a3 Mon Sep 17 00:00:00 2001 From: Max Amundsen Date: Tue, 3 Mar 2026 14:34:41 -0500 Subject: [PATCH] Add standard radius --- src/main.cpp | 25 +++++++++++++++--- src/renderer/renderer_dx12.cpp | 47 ++++++++++++++++++++++------------ src/renderer/renderer_metal.mm | 46 ++++++++++++++++++++++----------- src/ui/ui_core.cpp | 2 ++ src/ui/ui_core.h | 14 +++++----- src/ui/ui_widgets.cpp | 36 ++++++++++++++++---------- 6 files changed, 114 insertions(+), 56 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ad3b23d..47d82db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,6 +103,9 @@ struct AppState { S32 accent_sel; S32 accent_prev; + // Corner radius selection + S32 radius_sel; + // Audio device selection S32 audio_device_sel; // dropdown index: 0 = None, 1+ = device S32 audio_device_prev; // previous selection for change detection @@ -333,7 +336,7 @@ static void build_right_panel(AppState *app) { .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = refresh_hovered ? g_theme.accent_hover : g_theme.bg_lighter, - .cornerRadius = CLAY_CORNER_RADIUS(uis(3)) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { CLAY_TEXT(CLAY_STRING("Refresh"), &g_text_config_normal); } @@ -433,7 +436,7 @@ static void build_right_panel(AppState *app) { .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = box_color, - .cornerRadius = CLAY_CORNER_RADIUS(uis(3)) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { CLAY_TEXT(note_str, box_txt); } @@ -507,6 +510,9 @@ static void build_log_panel(B32 show) { } //////////////////////////////// +// Corner radius presets (indexed by AppState::radius_sel) +static const F32 radius_values[] = { 0.0f, 4.0f, 6.0f, 10.0f }; + // Window content callbacks static void settings_window_content(void *user_data) { @@ -528,6 +534,17 @@ static void settings_window_content(void *user_data) { ui_dropdown("SettingsAccent", accent_options, 7, &app->accent_sel); } + ui_label("SettingsLblRadius", "Corner Radius"); + static const char *radius_options[] = { "None", "Small", "Medium", "Large" }; + // radius_values defined at file scope (used by theme change handler too) + CLAY(CLAY_ID("SettingsRadiusWrap"), + .layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(180)), .height = CLAY_SIZING_FIT() } } + ) { + if (ui_dropdown("SettingsRadius", radius_options, 4, &app->radius_sel)) { + g_theme.corner_radius = radius_values[app->radius_sel]; + } + } + ui_checkbox("SettingsVsync", "V-Sync", &app->settings_vsync); ui_checkbox("SettingsAutosave", "Autosave", &app->settings_autosave); @@ -590,7 +607,7 @@ static void settings_window_content(void *user_data) { .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = g_theme.disabled_bg, - .cornerRadius = CLAY_CORNER_RADIUS(uis(3)) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { static Clay_TextElementConfig disabled_text = {}; disabled_text.textColor = g_theme.disabled_text; @@ -723,6 +740,7 @@ static void do_frame(AppState *app) { if (app->settings_theme_sel != app->settings_theme_prev) { ui_set_theme(app->settings_theme_sel); ui_set_accent(app->accent_sel); // reapply accent on new base theme + g_theme.corner_radius = radius_values[app->radius_sel]; // preserve user radius app->settings_theme_prev = app->settings_theme_sel; // Refresh all text configs with new theme colors @@ -834,6 +852,7 @@ int main(int argc, char **argv) { app.show_log = 1; app.show_midi_devices = 1; app.demo_dropdown_sel = 1; // default to 48000 Hz + app.radius_sel = 1; // default to "Small" (4.0f) snprintf(app.demo_text_a, sizeof(app.demo_text_a), "My Instrument"); #ifdef __APPLE__ snprintf(app.demo_text_b, sizeof(app.demo_text_b), "~/Samples/output"); diff --git a/src/renderer/renderer_dx12.cpp b/src/renderer/renderer_dx12.cpp index d5bc789..37a7ccb 100644 --- a/src/renderer/renderer_dx12.cpp +++ b/src/renderer/renderer_dx12.cpp @@ -1183,22 +1183,37 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { float cb_norm = c.b / 255.f; float ca_norm = c.a / 255.f; - // Draw individual border sides as thin rects - if (border->width.top > 0) { - 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, 0, 0, 0, 1.0f); - } - 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, - cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); - } - if (border->width.left > 0) { - 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, 0, 0, 0, 1.0f); - } - 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, - cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + // Use SDF rounded border when corner radius is present and widths are uniform + Clay_CornerRadius cr = border->cornerRadius; + bool has_radius = cr.topLeft > 0 || cr.topRight > 0 || cr.bottomLeft > 0 || cr.bottomRight > 0; + bool uniform = border->width.top == border->width.bottom && + border->width.top == border->width.left && + border->width.top == border->width.right && + border->width.top > 0; + + if (has_radius && uniform) { + emit_rect(&batch, + bb.x, bb.y, bb.x + bb.width, bb.y + bb.height, + cr_norm, cg_norm, cb_norm, ca_norm, + cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft, + (float)border->width.top, 1.0f); + } else { + if (border->width.top > 0) { + 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, 0, 0, 0, 1.0f); + } + 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, + cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + } + if (border->width.left > 0) { + 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, 0, 0, 0, 1.0f); + } + 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, + cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + } } } break; diff --git a/src/renderer/renderer_metal.mm b/src/renderer/renderer_metal.mm index 0bde0cb..5cf22f1 100644 --- a/src/renderer/renderer_metal.mm +++ b/src/renderer/renderer_metal.mm @@ -756,21 +756,37 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) { float cb_norm = c.b / 255.f; float ca_norm = c.a / 255.f; - if (border->width.top > 0) { - 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, 0, 0, 0, 1.0f); - } - 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, - cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); - } - if (border->width.left > 0) { - 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, 0, 0, 0, 1.0f); - } - 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, - cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + // Use SDF rounded border when corner radius is present and widths are uniform + Clay_CornerRadius cr = border->cornerRadius; + bool has_radius = cr.topLeft > 0 || cr.topRight > 0 || cr.bottomLeft > 0 || cr.bottomRight > 0; + bool uniform = border->width.top == border->width.bottom && + border->width.top == border->width.left && + border->width.top == border->width.right && + border->width.top > 0; + + if (has_radius && uniform) { + emit_rect(&batch, + bb.x, bb.y, bb.x + bb.width, bb.y + bb.height, + cr_norm, cg_norm, cb_norm, ca_norm, + cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft, + (float)border->width.top, 1.0f); + } else { + if (border->width.top > 0) { + 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, 0, 0, 0, 1.0f); + } + 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, + cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + } + if (border->width.left > 0) { + 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, 0, 0, 0, 1.0f); + } + 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, + cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f); + } } } break; diff --git a/src/ui/ui_core.cpp b/src/ui/ui_core.cpp index a648f2f..923d053 100644 --- a/src/ui/ui_core.cpp +++ b/src/ui/ui_core.cpp @@ -47,6 +47,7 @@ void ui_set_theme(S32 theme_id) { 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}; + g_theme.corner_radius = 4.0f; break; case 1: // Light @@ -70,6 +71,7 @@ void ui_set_theme(S32 theme_id) { 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}; + g_theme.corner_radius = 4.0f; break; } } diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index d655d36..f75a23e 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -71,6 +71,9 @@ struct UI_Theme { Clay_Color tab_inactive; Clay_Color tab_inactive_hover; Clay_Color tab_text; // Always light — readable on colored tab gradient + + // Corner radius (unscaled pixels, applied via uis()) + F32 corner_radius; }; extern UI_Theme g_theme; @@ -105,7 +108,7 @@ static inline uint16_t uifs(float x) { return (uint16_t)(x * g_ui_scale + 0.5f); #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_CORNER_RADIUS CORNER_RADIUS #define TAB_PADDING_H uip(10) //////////////////////////////// @@ -147,20 +150,15 @@ struct CustomIconData { #define WIDGET_DROPDOWN_ITEM_H uis(28) //////////////////////////////// -// Corner radii +// Corner radius (from theme) -#define CORNER_RADIUS_SM uis(3) -#define CORNER_RADIUS_MD uis(6) -#define CORNER_RADIUS_ROUND uis(8) +#define CORNER_RADIUS uis(g_theme.corner_radius) //////////////////////////////// // 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) //////////////////////////////// diff --git a/src/ui/ui_widgets.cpp b/src/ui/ui_widgets.cpp index 532ec3b..8135c36 100644 --- a/src/ui/ui_widgets.cpp +++ b/src/ui/ui_widgets.cpp @@ -153,7 +153,7 @@ B32 ui_button(const char *id, const char *text) { .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = hovered ? g_theme.accent_hover : g_theme.accent, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { CLAY_TEXT(clay_str(text), &g_widget_text_config_btn); } @@ -190,11 +190,11 @@ B32 ui_checkbox(const char *id, const char *label, B32 *value) { .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = box_bg, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM), + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } } ) { if (*value) { - ui_icon(UI_ICON_CHECK, WIDGET_CHECKBOX_SIZE * 0.75f, g_theme.text); + ui_icon(UI_ICON_CHECK, WIDGET_CHECKBOX_SIZE * 0.75f, g_theme.button_text); } } @@ -256,7 +256,7 @@ B32 ui_radio_group(const char *id, const char **options, S32 count, S32 *selecte .layout = { .sizing = { .width = CLAY_SIZING_FIXED(WIDGET_RADIO_INNER), .height = CLAY_SIZING_FIXED(WIDGET_RADIO_INNER) }, }, - .backgroundColor = g_theme.text, + .backgroundColor = g_theme.button_text, .cornerRadius = CLAY_CORNER_RADIUS(uis(4)) ) {} } @@ -606,7 +606,7 @@ B32 ui_text_input(const char *id, char *buf, S32 buf_size) { .layoutDirection = CLAY_LEFT_TO_RIGHT, }, .backgroundColor = bg, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM), + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .border = { .color = border_color, .width = { 1, 1, 1, 1 } } ) { if (len == 0 && !is_focused) { @@ -720,7 +720,7 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected) .layoutDirection = CLAY_LEFT_TO_RIGHT, }, .backgroundColor = bg, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM), + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .border = { .color = is_open ? g_theme.accent : g_theme.border, .width = { 1, 1, 1, 1 } } ) { CLAY(text_eid, @@ -762,6 +762,7 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected) .layoutDirection = CLAY_TOP_TO_BOTTOM, }, .backgroundColor = g_theme.bg_dark, + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } }, .floating = { .parentId = eid.id, @@ -784,13 +785,18 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected) Clay_TextElementConfig *item_text = (is_item_selected && !item_hovered) ? &g_widget_text_config_btn : &g_widget_text_config; + Clay_CornerRadius item_radius = {}; + if (i == 0) { item_radius.topLeft = CORNER_RADIUS; item_radius.topRight = CORNER_RADIUS; } + if (i == count - 1) { item_radius.bottomLeft = CORNER_RADIUS; item_radius.bottomRight = CORNER_RADIUS; } + CLAY(item_id, .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(WIDGET_DROPDOWN_ITEM_H) }, .padding = { uip(8), uip(8), 0, 0 }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, - .backgroundColor = item_bg + .backgroundColor = item_bg, + .cornerRadius = item_radius ) { CLAY_TEXT(clay_str(options[i]), item_text); } @@ -876,7 +882,7 @@ S32 ui_modal(const char *id, const char *title, const char *message, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, .backgroundColor = g_theme.bg_medium, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_MD), + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .floating = { .zIndex = 1001, .attachPoints = { @@ -896,7 +902,7 @@ S32 ui_modal(const char *id, const char *title, const char *message, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = g_theme.title_bar, - .cornerRadius = { MODAL_CORNER_RADIUS, MODAL_CORNER_RADIUS, 0, 0 }, + .cornerRadius = { CORNER_RADIUS, CORNER_RADIUS, 0, 0 }, .border = { .color = g_theme.border, .width = { .bottom = 1 } } ) { CLAY_TEXT(clay_str(title), &g_widget_text_config); @@ -937,7 +943,7 @@ S32 ui_modal(const char *id, const char *title, const char *message, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = btn_hovered ? g_theme.accent_hover : g_theme.accent, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { CLAY_TEXT(clay_str(buttons[i]), &g_widget_text_config_btn); } @@ -1060,7 +1066,7 @@ B32 ui_window(const char *id, const char *title, B32 *open, .layoutDirection = CLAY_TOP_TO_BOTTOM, }, .backgroundColor = g_theme.bg_medium, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_MD), + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS), .floating = { .offset = { slot->position.x, slot->position.y }, .zIndex = (int16_t)(100 + slot->z_order), @@ -1082,7 +1088,7 @@ B32 ui_window(const char *id, const char *title, B32 *open, .layoutDirection = CLAY_LEFT_TO_RIGHT, }, .backgroundColor = g_theme.title_bar, - .cornerRadius = { WINDOW_CORNER_RADIUS, WINDOW_CORNER_RADIUS, 0, 0 }, + .cornerRadius = { CORNER_RADIUS, CORNER_RADIUS, 0, 0 }, .border = { .color = g_theme.border, .width = { .bottom = 1 } } ) { // Title text (grows to push close button right) @@ -1095,14 +1101,16 @@ B32 ui_window(const char *id, const char *title, B32 *open, } // Close button - Clay_Color close_bg = close_hovered ? Clay_Color{200, 60, 60, 255} : Clay_Color{120, 50, 50, 255}; + Clay_Color close_bg = g_theme_id == 1 + ? (close_hovered ? Clay_Color{220, 50, 50, 255} : Clay_Color{200, 70, 70, 255}) + : (close_hovered ? Clay_Color{200, 60, 60, 255} : Clay_Color{120, 50, 50, 255}); CLAY(close_id, .layout = { .sizing = { .width = CLAY_SIZING_FIXED(uis(20)), .height = CLAY_SIZING_FIXED(uis(20)) }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = close_bg, - .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM) + .cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS) ) { ui_icon(UI_ICON_CLOSE, uis(12), g_theme.button_text); }