Death to C++
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
// ui_core.cpp - Clay wrapper implementation
|
||||
// ui_core.c - Clay wrapper implementation
|
||||
// CLAY_IMPLEMENTATION must be defined exactly once before including clay.h.
|
||||
// In the unity build, ui_core.h (which includes clay.h) has #pragma once,
|
||||
// so we include clay.h directly here first to get the implementation compiled.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// MSVC: stub out Clay's debug view (uses CLAY() macros MSVC can't compile in C)
|
||||
#ifdef _MSC_VER
|
||||
#define CLAY_DISABLE_DEBUG_VIEW
|
||||
#endif
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "clay.h"
|
||||
|
||||
// MSVC C mode: bypass Clay's wrapper struct pattern for designated initializers.
|
||||
// Must come after clay.h so CLAY__CONFIG_WRAPPER is defined, then we override it.
|
||||
#ifdef _MSC_VER
|
||||
#undef CLAY__CONFIG_WRAPPER
|
||||
#define CLAY__CONFIG_WRAPPER(type, ...) ((type) { __VA_ARGS__ })
|
||||
#endif
|
||||
|
||||
#include "ui/ui_core.h"
|
||||
|
||||
////////////////////////////////
|
||||
@@ -18,7 +30,7 @@ F32 g_ui_scale = 1.0f;
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {};
|
||||
UI_Theme g_theme = {0};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
@@ -27,54 +39,54 @@ void ui_set_theme(S32 theme_id) {
|
||||
switch (theme_id) {
|
||||
default:
|
||||
case 0: // Dark
|
||||
g_theme.bg_dark = Clay_Color{ 26, 26, 26, 255};
|
||||
g_theme.bg_medium = Clay_Color{ 36, 36, 36, 255};
|
||||
g_theme.bg_light = Clay_Color{ 46, 46, 46, 255};
|
||||
g_theme.bg_lighter = Clay_Color{ 54, 54, 54, 255};
|
||||
g_theme.border = Clay_Color{ 52, 52, 52, 255};
|
||||
g_theme.text = Clay_Color{220, 220, 220, 255};
|
||||
g_theme.text_dim = Clay_Color{105, 105, 105, 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{ 44, 44, 44, 255};
|
||||
g_theme.disabled_text = Clay_Color{ 90, 90, 90, 255};
|
||||
g_theme.header_bg = Clay_Color{ 46, 46, 46, 255};
|
||||
g_theme.title_bar = Clay_Color{ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_bg = Clay_Color{ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_grab = Clay_Color{ 58, 58, 58, 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{ 40, 40, 40, 255};
|
||||
g_theme.tab_inactive_hover= Clay_Color{ 50, 50, 50, 255};
|
||||
g_theme.tab_text = Clay_Color{240, 240, 240, 255};
|
||||
g_theme.bg_dark = (Clay_Color){ 26, 26, 26, 255};
|
||||
g_theme.bg_medium = (Clay_Color){ 36, 36, 36, 255};
|
||||
g_theme.bg_light = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){ 54, 54, 54, 255};
|
||||
g_theme.border = (Clay_Color){ 52, 52, 52, 255};
|
||||
g_theme.text = (Clay_Color){220, 220, 220, 255};
|
||||
g_theme.text_dim = (Clay_Color){105, 105, 105, 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){ 44, 44, 44, 255};
|
||||
g_theme.disabled_text = (Clay_Color){ 90, 90, 90, 255};
|
||||
g_theme.header_bg = (Clay_Color){ 46, 46, 46, 255};
|
||||
g_theme.title_bar = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){ 22, 22, 22, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){ 58, 58, 58, 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){ 40, 40, 40, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){ 50, 50, 50, 255};
|
||||
g_theme.tab_text = (Clay_Color){240, 240, 240, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
|
||||
case 1: // Light
|
||||
g_theme.bg_dark = Clay_Color{195, 193, 190, 255};
|
||||
g_theme.bg_medium = Clay_Color{212, 210, 207, 255};
|
||||
g_theme.bg_light = Clay_Color{225, 223, 220, 255};
|
||||
g_theme.bg_lighter = Clay_Color{232, 230, 227, 255};
|
||||
g_theme.border = Clay_Color{178, 176, 173, 255};
|
||||
g_theme.text = Clay_Color{ 35, 33, 30, 255};
|
||||
g_theme.text_dim = Clay_Color{115, 113, 108, 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{185, 183, 180, 255};
|
||||
g_theme.disabled_text = Clay_Color{145, 143, 140, 255};
|
||||
g_theme.header_bg = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.title_bar = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.scrollbar_bg = Clay_Color{202, 200, 197, 255};
|
||||
g_theme.scrollbar_grab = Clay_Color{168, 166, 163, 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{205, 203, 200, 255};
|
||||
g_theme.tab_inactive_hover= Clay_Color{195, 193, 190, 255};
|
||||
g_theme.tab_text = Clay_Color{255, 255, 255, 255};
|
||||
g_theme.bg_dark = (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.bg_medium = (Clay_Color){212, 210, 207, 255};
|
||||
g_theme.bg_light = (Clay_Color){225, 223, 220, 255};
|
||||
g_theme.bg_lighter = (Clay_Color){232, 230, 227, 255};
|
||||
g_theme.border = (Clay_Color){178, 176, 173, 255};
|
||||
g_theme.text = (Clay_Color){ 35, 33, 30, 255};
|
||||
g_theme.text_dim = (Clay_Color){115, 113, 108, 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){185, 183, 180, 255};
|
||||
g_theme.disabled_text = (Clay_Color){145, 143, 140, 255};
|
||||
g_theme.header_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.title_bar = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_bg = (Clay_Color){202, 200, 197, 255};
|
||||
g_theme.scrollbar_grab = (Clay_Color){168, 166, 163, 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){205, 203, 200, 255};
|
||||
g_theme.tab_inactive_hover= (Clay_Color){195, 193, 190, 255};
|
||||
g_theme.tab_text = (Clay_Color){255, 255, 255, 255};
|
||||
g_theme.corner_radius = 4.0f;
|
||||
break;
|
||||
}
|
||||
@@ -90,9 +102,9 @@ void ui_set_accent(S32 accent_id) {
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
struct AccentColors {
|
||||
typedef struct AccentColors {
|
||||
Clay_Color accent, accent_hover, tab_top, tab_bottom, button_text, tab_text;
|
||||
};
|
||||
} AccentColors;
|
||||
|
||||
// Dark-mode palettes
|
||||
static const AccentColors dark_palettes[] = {
|
||||
@@ -134,13 +146,13 @@ void ui_set_accent(S32 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;
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -158,15 +170,15 @@ static void clay_error_handler(Clay_ErrorData error) {
|
||||
// Clay text measurement bridge
|
||||
// Clay calls this; we forward to the app's UI_MeasureTextFn
|
||||
|
||||
static UI_Context *g_measure_ctx = nullptr;
|
||||
static UI_Context *g_measure_ctx = NULL;
|
||||
|
||||
static Clay_Dimensions clay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *user_data) {
|
||||
UI_Context *ctx = (UI_Context *)user_data;
|
||||
if (!ctx || !ctx->measure_text_fn || text.length == 0) {
|
||||
return Clay_Dimensions{0, (F32)config->fontSize};
|
||||
return (Clay_Dimensions){0, (F32)config->fontSize};
|
||||
}
|
||||
Vec2F32 result = ctx->measure_text_fn(text.chars, text.length, (F32)config->fontSize, ctx->measure_text_user_data);
|
||||
return Clay_Dimensions{result.x, result.y};
|
||||
return (Clay_Dimensions){result.x, result.y};
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -179,12 +191,12 @@ UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
Clay_ErrorHandler err_handler = {};
|
||||
Clay_ErrorHandler err_handler = {0};
|
||||
err_handler.errorHandlerFunction = clay_error_handler;
|
||||
err_handler.userData = ctx;
|
||||
|
||||
ctx->clay_ctx = Clay_Initialize(clay_arena,
|
||||
Clay_Dimensions{viewport_w, viewport_h},
|
||||
(Clay_Dimensions){viewport_w, viewport_h},
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
@@ -204,9 +216,9 @@ void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
{
|
||||
g_measure_ctx = ctx;
|
||||
Clay_SetCurrentContext(ctx->clay_ctx);
|
||||
Clay_SetLayoutDimensions(Clay_Dimensions{viewport_w, viewport_h});
|
||||
Clay_SetPointerState(Clay_Vector2{mouse_pos.x, mouse_pos.y}, mouse_down != 0);
|
||||
Clay_UpdateScrollContainers(false, Clay_Vector2{scroll_delta.x, scroll_delta.y}, dt);
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions){viewport_w, viewport_h});
|
||||
Clay_SetPointerState((Clay_Vector2){mouse_pos.x, mouse_pos.y}, mouse_down != 0);
|
||||
Clay_UpdateScrollContainers(false, (Clay_Vector2){scroll_delta.x, scroll_delta.y}, dt);
|
||||
Clay_BeginLayout();
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ typedef Vec2F32 (*UI_MeasureTextFn)(const char *text, S32 length, F32 font_size,
|
||||
////////////////////////////////
|
||||
// UI Context
|
||||
|
||||
struct UI_Context {
|
||||
typedef struct UI_Context {
|
||||
Clay_Context *clay_ctx;
|
||||
void *clay_memory;
|
||||
|
||||
// Text measurement
|
||||
UI_MeasureTextFn measure_text_fn;
|
||||
void *measure_text_user_data;
|
||||
};
|
||||
} UI_Context;
|
||||
|
||||
////////////////////////////////
|
||||
// Lifecycle
|
||||
@@ -48,7 +48,7 @@ Vec2F32 ui_measure_text(const char *text, S32 length, F32 font_size);
|
||||
////////////////////////////////
|
||||
// Theme colors (convenience - 0-255 Clay_Color)
|
||||
|
||||
struct UI_Theme {
|
||||
typedef struct UI_Theme {
|
||||
Clay_Color bg_dark;
|
||||
Clay_Color bg_medium;
|
||||
Clay_Color bg_light;
|
||||
@@ -76,7 +76,7 @@ struct UI_Theme {
|
||||
|
||||
// Corner radius (unscaled pixels, applied via uis())
|
||||
F32 corner_radius;
|
||||
};
|
||||
} UI_Theme;
|
||||
|
||||
extern UI_Theme g_theme;
|
||||
extern S32 g_theme_id;
|
||||
@@ -116,30 +116,30 @@ static inline U16 uifs(F32 x) { return (U16)(x * g_ui_scale + 0.5f); }
|
||||
////////////////////////////////
|
||||
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
|
||||
|
||||
enum CustomRenderType {
|
||||
typedef enum CustomRenderType {
|
||||
CUSTOM_RENDER_VGRADIENT = 1,
|
||||
CUSTOM_RENDER_ICON = 2,
|
||||
CUSTOM_RENDER_ROTATED_ICON = 3,
|
||||
};
|
||||
} CustomRenderType;
|
||||
|
||||
struct CustomGradientData {
|
||||
typedef struct CustomGradientData {
|
||||
CustomRenderType type;
|
||||
Clay_Color top_color;
|
||||
Clay_Color bottom_color;
|
||||
};
|
||||
} CustomGradientData;
|
||||
|
||||
struct CustomIconData {
|
||||
typedef struct CustomIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
};
|
||||
} CustomIconData;
|
||||
|
||||
struct CustomRotatedIconData {
|
||||
typedef struct CustomRotatedIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
F32 angle_rad;
|
||||
};
|
||||
} CustomRotatedIconData;
|
||||
|
||||
////////////////////////////////
|
||||
// Font sizes
|
||||
|
||||
219
src/ui/ui_icons.c
Normal file
219
src/ui/ui_icons.c
Normal file
@@ -0,0 +1,219 @@
|
||||
// ui_icons.cpp - SVG icon rasterization via nanosvg
|
||||
|
||||
#include "ui/ui_icons.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#include "nanosvg.h"
|
||||
#define NANOSVGRAST_IMPLEMENTATION
|
||||
#include "nanosvgrast.h"
|
||||
|
||||
UI_IconInfo g_icons[UI_ICON_COUNT] = {0};
|
||||
|
||||
// Simple SVG icon sources (24x24 viewBox)
|
||||
static const char *g_icon_svgs[UI_ICON_COUNT] = {
|
||||
// UI_ICON_CLOSE - X mark
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M6 6 L18 18 M18 6 L6 18\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_CHECK - checkmark
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M5 12 L10 17 L19 7\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_CHEVRON_DOWN - downward arrow
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M7 10 L12 15 L17 10\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_KNOB - filled circle with indicator line pointing up (12 o'clock)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"white\" opacity=\"0.25\"/>"
|
||||
"<line x1=\"12\" y1=\"12\" x2=\"12\" y2=\"3\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_SLIDER_THUMB - solid body with grip ridges
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"2\" y=\"1\" width=\"20\" height=\"22\" rx=\"4\" fill=\"white\"/>"
|
||||
"<line x1=\"6\" y1=\"8\" x2=\"18\" y2=\"8\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"11\" x2=\"18\" y2=\"11\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"14\" x2=\"18\" y2=\"14\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"<line x1=\"6\" y1=\"17\" x2=\"18\" y2=\"17\" stroke=\"black\" stroke-width=\"1.2\" opacity=\"0.3\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_FADER - exact asset fader cap from assets/fader.svg
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"847 488 62.2 129.3\">"
|
||||
" <defs>"
|
||||
" <linearGradient id=\"linearGradient7718\" y2=\"528.75\" gradientUnits=\"userSpaceOnUse\" x2=\"87.866\" gradientTransform=\"matrix(1.0278,0,0,1,787.52,-27.904)\" y1=\"516.83\" x1=\"87.866\">"
|
||||
" <stop style=\"stop-color:#999999\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#999999;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7720\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0235,0,0,1,242.38,-1008.6)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7722\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.50643,256.46,265.42)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7724\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.42746,256.46,317.38)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7726\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1,0,0,0.26952,256.46,405.1)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7728\" y2=\"521.42\" gradientUnits=\"userSpaceOnUse\" x2=\"87.866\" gradientTransform=\"matrix(1.0364,0,0,0.96441,786.64,-1114.5)\" y1=\"516.83\" x1=\"87.866\">"
|
||||
" <stop style=\"stop-color:#999999\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#333333\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7730\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0364,0,0,0.96441,234.39,-1076.7)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7732\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0274,0,0,0.48841,240.25,-833.5)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7734\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0213,0,0,0.41225,243.85,-783.64)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" <linearGradient id=\"linearGradient7736\" y2=\"499.1\" gradientUnits=\"userSpaceOnUse\" x2=\"618.49\" gradientTransform=\"matrix(1.0122,0,0,0.25993,249.26,-698.66)\" y1=\"496.57\" x1=\"618.49\">"
|
||||
" <stop style=\"stop-color:#cccccc\" offset=\"0\"/>"
|
||||
" <stop style=\"stop-color:#cccccc;stop-opacity:0\" offset=\"1\"/>"
|
||||
" </linearGradient>"
|
||||
" </defs>"
|
||||
" <rect style=\"fill:#4d4d4d\" rx=\"3\" ry=\"3\" height=\"129.29\" width=\"62.143\" y=\"488.03\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#e6e6e6\" height=\"3.5355\" width=\"60.419\" y=\"552.42\" x=\"847.97\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7718)\" rx=\"2.148\" ry=\"2\" height=\"21.071\" width=\"60.613\" y=\"488.74\" x=\"847.72\"/>"
|
||||
" <rect style=\"fill:#333333\" height=\"10.119\" width=\"58.811\" y=\"540.26\" x=\"847.92\"/>"
|
||||
" <rect style=\"fill:#333333\" height=\"8.793\" width=\"61.133\" y=\"530.89\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#1a1a1a\" height=\"4.546\" width=\"61.133\" y=\"512.48\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#1a1a1a\" height=\"11.364\" width=\"61.133\" y=\"518.25\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7720)\" rx=\"1.024\" ry=\"0.64\" transform=\"scale(1,-1)\" height=\"2.261\" width=\"60.012\" y=\"-511.76\" x=\"847.72\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7722)\" rx=\"1\" ry=\"0.324\" height=\"1.145\" width=\"58.633\" y=\"517.03\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7724)\" rx=\"1\" ry=\"0.273\" height=\"0.967\" width=\"58.633\" y=\"529.76\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7726)\" rx=\"1\" ry=\"0.172\" height=\"0.609\" width=\"58.633\" y=\"539.01\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7728)\" transform=\"scale(1,-1)\" rx=\"2.166\" ry=\"1.929\" height=\"20.321\" width=\"61.118\" y=\"-616.24\" x=\"847.34\"/>"
|
||||
" <rect style=\"fill:#333333\" transform=\"scale(1,-1)\" height=\"9.759\" width=\"58.811\" y=\"-567.93\" x=\"847.92\"/>"
|
||||
" <rect style=\"fill:#666666\" transform=\"scale(1,-1)\" height=\"8.48\" width=\"61.133\" y=\"-576.96\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#808080\" transform=\"scale(1,-1)\" height=\"4.384\" width=\"61.133\" y=\"-594.72\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:#808080\" transform=\"scale(1,-1)\" height=\"10.96\" width=\"61.133\" y=\"-589.16\" x=\"847.03\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7730)\" transform=\"scale(1,-1)\" rx=\"1.036\" ry=\"0.617\" height=\"2.181\" width=\"60.767\" y=\"-597.6\" x=\"847.34\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7732)\" transform=\"scale(1,-1)\" rx=\"1.027\" ry=\"0.312\" height=\"1.104\" width=\"60.24\" y=\"-590.84\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7734)\" transform=\"scale(1,-1)\" rx=\"1.021\" ry=\"0.264\" height=\"0.932\" width=\"59.883\" y=\"-578.82\" x=\"847.89\"/>"
|
||||
" <rect style=\"fill:url(#linearGradient7736)\" transform=\"scale(1,-1)\" rx=\"1.012\" ry=\"0.166\" height=\"0.588\" width=\"59.347\" y=\"-569.52\" x=\"847.89\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_REWIND - skip to start (|<<)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"5\" width=\"2.5\" height=\"14\" rx=\"0.5\" fill=\"white\"/>"
|
||||
"<path d=\"M11 5 L11 19 L4 12 Z\" fill=\"white\"/>"
|
||||
"<path d=\"M20 5 L20 19 L13 12 Z\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_STOP - filled square
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" rx=\"1.5\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_PLAY - right-pointing triangle
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<path d=\"M6 4 L6 20 L20 12 Z\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_TRANSPORT_RECORD - filled circle
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<circle cx=\"12\" cy=\"12\" r=\"8\" fill=\"white\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_POP_OUT - box with arrow pointing out (top-right)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"6\" width=\"14\" height=\"14\" rx=\"2\" stroke=\"white\" stroke-width=\"2\" fill=\"none\"/>"
|
||||
"<path d=\"M14 3 L21 3 L21 10\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"<path d=\"M21 3 L12 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\"/>"
|
||||
"</svg>",
|
||||
|
||||
// UI_ICON_POP_IN - arrow pointing into a box (bottom-left)
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">"
|
||||
"<rect x=\"3\" y=\"6\" width=\"14\" height=\"14\" rx=\"2\" stroke=\"white\" stroke-width=\"2\" fill=\"none\"/>"
|
||||
"<path d=\"M21 3 L12 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\"/>"
|
||||
"<path d=\"M12 5 L12 12 L19 12\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\"/>"
|
||||
"</svg>",
|
||||
};
|
||||
|
||||
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
// Pack icons in a row
|
||||
S32 atlas_w = icon_size * UI_ICON_COUNT;
|
||||
S32 atlas_h = icon_size;
|
||||
|
||||
// Pad to power of 2 isn't necessary for correctness, but ensure minimum size
|
||||
if (atlas_w < 64) atlas_w = 64;
|
||||
if (atlas_h < 64) atlas_h = 64;
|
||||
|
||||
U8 *atlas = (U8 *)calloc(atlas_w * atlas_h * 4, 1);
|
||||
if (!atlas) return NULL;
|
||||
|
||||
NSVGrasterizer *rast = nsvgCreateRasterizer();
|
||||
if (!rast) { free(atlas); return NULL; }
|
||||
|
||||
S32 pen_x = 0;
|
||||
for (S32 i = 0; i < UI_ICON_COUNT; i++) {
|
||||
// nanosvg modifies the input string, so we need a mutable copy
|
||||
U64 svg_len = strlen(g_icon_svgs[i]);
|
||||
char *svg_copy = (char *)malloc(svg_len + 1);
|
||||
memcpy(svg_copy, g_icon_svgs[i], svg_len + 1);
|
||||
|
||||
NSVGimage *image = nsvgParse(svg_copy, "px", 96.0f);
|
||||
free(svg_copy);
|
||||
if (!image) continue;
|
||||
|
||||
// Calculate scale to fit icon_size
|
||||
F32 scale_x = (F32)icon_size / image->width;
|
||||
F32 scale_y = (F32)icon_size / image->height;
|
||||
F32 scale = scale_x < scale_y ? scale_x : scale_y;
|
||||
|
||||
// Rasterize to a temporary RGBA buffer
|
||||
U8 *tmp = (U8 *)calloc(icon_size * icon_size * 4, 1);
|
||||
if (tmp) {
|
||||
nsvgRasterize(rast, image, 0, 0, scale, tmp, icon_size, icon_size, icon_size * 4);
|
||||
|
||||
// Copy into atlas (nanosvg outputs RGBA, which is what we want)
|
||||
for (S32 y = 0; y < icon_size && y < atlas_h; y++) {
|
||||
for (S32 x = 0; x < icon_size && (pen_x + x) < atlas_w; x++) {
|
||||
S32 src_idx = (y * icon_size + x) * 4;
|
||||
S32 dst_idx = (y * atlas_w + pen_x + x) * 4;
|
||||
atlas[dst_idx + 0] = tmp[src_idx + 0];
|
||||
atlas[dst_idx + 1] = tmp[src_idx + 1];
|
||||
atlas[dst_idx + 2] = tmp[src_idx + 2];
|
||||
atlas[dst_idx + 3] = tmp[src_idx + 3];
|
||||
}
|
||||
}
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
S32 bmp_w = icon_size;
|
||||
S32 bmp_h = icon_size;
|
||||
|
||||
// Store UV and pixel info
|
||||
g_icons[i].u0 = (F32)pen_x / (F32)atlas_w;
|
||||
g_icons[i].v0 = 0.0f;
|
||||
g_icons[i].u1 = (F32)(pen_x + bmp_w) / (F32)atlas_w;
|
||||
g_icons[i].v1 = (F32)bmp_h / (F32)atlas_h;
|
||||
g_icons[i].w = (F32)bmp_w;
|
||||
g_icons[i].h = (F32)bmp_h;
|
||||
|
||||
pen_x += icon_size;
|
||||
nsvgDelete(image);
|
||||
}
|
||||
|
||||
nsvgDeleteRasterizer(rast);
|
||||
|
||||
*out_w = atlas_w;
|
||||
*out_h = atlas_h;
|
||||
return atlas;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
// ui_icons.cpp - SVG icon rasterization via lunasvg
|
||||
|
||||
#include "ui/ui_icons.h"
|
||||
#include <lunasvg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
UI_IconInfo g_icons[UI_ICON_COUNT] = {};
|
||||
|
||||
// Simple SVG icon sources (24x24 viewBox)
|
||||
static const char *g_icon_svgs[UI_ICON_COUNT] = {
|
||||
// UI_ICON_CLOSE - X mark
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M6 6 L18 18 M18 6 L6 18" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_CHECK - checkmark
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M5 12 L10 17 L19 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_CHEVRON_DOWN - downward arrow
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M7 10 L12 15 L17 10" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_KNOB - filled circle with indicator line pointing up (12 o'clock)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" fill="white" opacity="0.25"/>
|
||||
<line x1="12" y1="12" x2="12" y2="3" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_SLIDER_THUMB - solid body with grip ridges
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="2" y="1" width="20" height="22" rx="4" fill="white"/>
|
||||
<line x1="6" y1="8" x2="18" y2="8" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="11" x2="18" y2="11" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="14" x2="18" y2="14" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
<line x1="6" y1="17" x2="18" y2="17" stroke="black" stroke-width="1.2" opacity="0.3"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_FADER - exact asset fader cap from assets/fader.svg
|
||||
R"SVG(<svg xmlns="http://www.w3.org/2000/svg" viewBox="847 488 62.2 129.3">
|
||||
<defs>
|
||||
<linearGradient id="linearGradient7718" y2="528.75" gradientUnits="userSpaceOnUse" x2="87.866" gradientTransform="matrix(1.0278,0,0,1,787.52,-27.904)" y1="516.83" x1="87.866">
|
||||
<stop style="stop-color:#999999" offset="0"/>
|
||||
<stop style="stop-color:#999999;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7720" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0235,0,0,1,242.38,-1008.6)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7722" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.50643,256.46,265.42)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7724" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.42746,256.46,317.38)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7726" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1,0,0,0.26952,256.46,405.1)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7728" y2="521.42" gradientUnits="userSpaceOnUse" x2="87.866" gradientTransform="matrix(1.0364,0,0,0.96441,786.64,-1114.5)" y1="516.83" x1="87.866">
|
||||
<stop style="stop-color:#999999" offset="0"/>
|
||||
<stop style="stop-color:#333333" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7730" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0364,0,0,0.96441,234.39,-1076.7)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7732" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0274,0,0,0.48841,240.25,-833.5)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7734" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0213,0,0,0.41225,243.85,-783.64)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient7736" y2="499.1" gradientUnits="userSpaceOnUse" x2="618.49" gradientTransform="matrix(1.0122,0,0,0.25993,249.26,-698.66)" y1="496.57" x1="618.49">
|
||||
<stop style="stop-color:#cccccc" offset="0"/>
|
||||
<stop style="stop-color:#cccccc;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect style="fill:#4d4d4d" rx="3" ry="3" height="129.29" width="62.143" y="488.03" x="847.03"/>
|
||||
<rect style="fill:#e6e6e6" height="3.5355" width="60.419" y="552.42" x="847.97"/>
|
||||
<rect style="fill:url(#linearGradient7718)" rx="2.148" ry="2" height="21.071" width="60.613" y="488.74" x="847.72"/>
|
||||
<rect style="fill:#333333" height="10.119" width="58.811" y="540.26" x="847.92"/>
|
||||
<rect style="fill:#333333" height="8.793" width="61.133" y="530.89" x="847.03"/>
|
||||
<rect style="fill:#1a1a1a" height="4.546" width="61.133" y="512.48" x="847.03"/>
|
||||
<rect style="fill:#1a1a1a" height="11.364" width="61.133" y="518.25" x="847.03"/>
|
||||
<rect style="fill:url(#linearGradient7720)" rx="1.024" ry="0.64" transform="scale(1,-1)" height="2.261" width="60.012" y="-511.76" x="847.72"/>
|
||||
<rect style="fill:url(#linearGradient7722)" rx="1" ry="0.324" height="1.145" width="58.633" y="517.03" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7724)" rx="1" ry="0.273" height="0.967" width="58.633" y="529.76" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7726)" rx="1" ry="0.172" height="0.609" width="58.633" y="539.01" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7728)" transform="scale(1,-1)" rx="2.166" ry="1.929" height="20.321" width="61.118" y="-616.24" x="847.34"/>
|
||||
<rect style="fill:#333333" transform="scale(1,-1)" height="9.759" width="58.811" y="-567.93" x="847.92"/>
|
||||
<rect style="fill:#666666" transform="scale(1,-1)" height="8.48" width="61.133" y="-576.96" x="847.03"/>
|
||||
<rect style="fill:#808080" transform="scale(1,-1)" height="4.384" width="61.133" y="-594.72" x="847.03"/>
|
||||
<rect style="fill:#808080" transform="scale(1,-1)" height="10.96" width="61.133" y="-589.16" x="847.03"/>
|
||||
<rect style="fill:url(#linearGradient7730)" transform="scale(1,-1)" rx="1.036" ry="0.617" height="2.181" width="60.767" y="-597.6" x="847.34"/>
|
||||
<rect style="fill:url(#linearGradient7732)" transform="scale(1,-1)" rx="1.027" ry="0.312" height="1.104" width="60.24" y="-590.84" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7734)" transform="scale(1,-1)" rx="1.021" ry="0.264" height="0.932" width="59.883" y="-578.82" x="847.89"/>
|
||||
<rect style="fill:url(#linearGradient7736)" transform="scale(1,-1)" rx="1.012" ry="0.166" height="0.588" width="59.347" y="-569.52" x="847.89"/>
|
||||
</svg>)SVG",
|
||||
|
||||
// UI_ICON_TRANSPORT_REWIND - skip to start (|<<)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="5" width="2.5" height="14" rx="0.5" fill="white"/>
|
||||
<path d="M11 5 L11 19 L4 12 Z" fill="white"/>
|
||||
<path d="M20 5 L20 19 L13 12 Z" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_STOP - filled square
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="5" y="5" width="14" height="14" rx="1.5" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_PLAY - right-pointing triangle
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M6 4 L6 20 L20 12 Z" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_TRANSPORT_RECORD - filled circle
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="8" fill="white"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_OUT - box with arrow pointing out (top-right)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M14 3 L21 3 L21 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>)",
|
||||
|
||||
// UI_ICON_POP_IN - arrow pointing into a box (bottom-left)
|
||||
R"(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="3" y="6" width="14" height="14" rx="2" stroke="white" stroke-width="2" fill="none"/>
|
||||
<path d="M21 3 L12 12" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M12 5 L12 12 L19 12" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>)",
|
||||
};
|
||||
|
||||
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
// Pack icons in a row
|
||||
S32 atlas_w = icon_size * UI_ICON_COUNT;
|
||||
S32 atlas_h = icon_size;
|
||||
|
||||
// Pad to power of 2 isn't necessary for correctness, but ensure minimum size
|
||||
if (atlas_w < 64) atlas_w = 64;
|
||||
if (atlas_h < 64) atlas_h = 64;
|
||||
|
||||
U8 *atlas = (U8 *)calloc(atlas_w * atlas_h * 4, 1);
|
||||
if (!atlas) return nullptr;
|
||||
|
||||
S32 pen_x = 0;
|
||||
for (S32 i = 0; i < UI_ICON_COUNT; i++) {
|
||||
auto doc = lunasvg::Document::loadFromData(g_icon_svgs[i]);
|
||||
if (!doc) continue;
|
||||
|
||||
lunasvg::Bitmap bmp = doc->renderToBitmap(icon_size, icon_size);
|
||||
if (bmp.isNull()) continue;
|
||||
|
||||
// Copy BGRA premultiplied → RGBA straight (un-premultiply)
|
||||
U8 *src = bmp.data();
|
||||
S32 bmp_w = bmp.width();
|
||||
S32 bmp_h = bmp.height();
|
||||
S32 stride = bmp.stride();
|
||||
|
||||
for (S32 y = 0; y < bmp_h && y < atlas_h; y++) {
|
||||
for (S32 x = 0; x < bmp_w && (pen_x + x) < atlas_w; x++) {
|
||||
U8 *s = &src[y * stride + x * 4];
|
||||
S32 dst_idx = (y * atlas_w + pen_x + x) * 4;
|
||||
U8 b = s[0], g = s[1], r = s[2], a = s[3];
|
||||
if (a > 0 && a < 255) {
|
||||
r = (U8)((r * 255) / a);
|
||||
g = (U8)((g * 255) / a);
|
||||
b = (U8)((b * 255) / a);
|
||||
}
|
||||
atlas[dst_idx + 0] = r;
|
||||
atlas[dst_idx + 1] = g;
|
||||
atlas[dst_idx + 2] = b;
|
||||
atlas[dst_idx + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
// Store UV and pixel info
|
||||
g_icons[i].u0 = (F32)pen_x / (F32)atlas_w;
|
||||
g_icons[i].v0 = 0.0f;
|
||||
g_icons[i].u1 = (F32)(pen_x + bmp_w) / (F32)atlas_w;
|
||||
g_icons[i].v1 = (F32)bmp_h / (F32)atlas_h;
|
||||
g_icons[i].w = (F32)bmp_w;
|
||||
g_icons[i].h = (F32)bmp_h;
|
||||
|
||||
pen_x += icon_size;
|
||||
}
|
||||
|
||||
*out_w = atlas_w;
|
||||
*out_h = atlas_h;
|
||||
return atlas;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
// ui_icons.h - SVG icon definitions and atlas rasterization via lunasvg
|
||||
// ui_icons.h - SVG icon definitions and atlas rasterization via nanosvg
|
||||
|
||||
#include "base/base_inc.h"
|
||||
|
||||
enum UI_IconID {
|
||||
typedef enum UI_IconID {
|
||||
UI_ICON_CLOSE,
|
||||
UI_ICON_CHECK,
|
||||
UI_ICON_CHEVRON_DOWN,
|
||||
@@ -17,12 +17,12 @@ enum UI_IconID {
|
||||
UI_ICON_POP_OUT,
|
||||
UI_ICON_POP_IN,
|
||||
UI_ICON_COUNT
|
||||
};
|
||||
} UI_IconID;
|
||||
|
||||
struct UI_IconInfo {
|
||||
typedef struct UI_IconInfo {
|
||||
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
|
||||
F32 w, h; // pixel dimensions at rasterized size
|
||||
};
|
||||
} UI_IconInfo;
|
||||
|
||||
extern UI_IconInfo g_icons[UI_ICON_COUNT];
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Clay_Color piano_velocity_color(S32 velocity) {
|
||||
g = 175.0f + s * (50.0f - 175.0f);
|
||||
b = 80.0f + s * (40.0f - 80.0f);
|
||||
}
|
||||
return Clay_Color{r, g, b, 255};
|
||||
return (Clay_Color){r, g, b, 255};
|
||||
}
|
||||
|
||||
void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h) {
|
||||
@@ -54,7 +54,7 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{240, 240, 240, 255};
|
||||
bg = (Clay_Color){240, 240, 240, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
@@ -79,7 +79,7 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
} else if (mouse_held) {
|
||||
bg = g_theme.accent;
|
||||
} else {
|
||||
bg = Clay_Color{25, 25, 30, 255};
|
||||
bg = (Clay_Color){25, 25, 30, 255};
|
||||
}
|
||||
|
||||
CLAY(CLAY_IDI("PKey", note),
|
||||
@@ -5,9 +5,9 @@
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
|
||||
struct UI_PianoState {
|
||||
typedef struct UI_PianoState {
|
||||
S32 mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||
};
|
||||
} UI_PianoState;
|
||||
|
||||
B32 piano_is_black_key(S32 note);
|
||||
Clay_Color piano_velocity_color(S32 velocity);
|
||||
|
||||
@@ -12,7 +12,7 @@ PopupWindow *popup_find_by_flag(B32 *flag) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++)
|
||||
if (g_popups[i].alive && g_popups[i].open_flag == flag)
|
||||
return &g_popups[i];
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
@@ -21,16 +21,16 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style, B32 independent) {
|
||||
// Find free slot
|
||||
PopupWindow *popup = nullptr;
|
||||
PopupWindow *popup = NULL;
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
if (!g_popups[i].alive) { popup = &g_popups[i]; break; }
|
||||
}
|
||||
if (!popup) return nullptr;
|
||||
if (!popup) return NULL;
|
||||
|
||||
memset(popup, 0, sizeof(*popup));
|
||||
|
||||
// Create native popup window
|
||||
PlatformWindowDesc desc = {};
|
||||
PlatformWindowDesc desc = {0};
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
@@ -38,20 +38,20 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
if (!popup->platform_window) return nullptr;
|
||||
if (!popup->platform_window) return NULL;
|
||||
|
||||
// Create shared renderer
|
||||
S32 pw, ph;
|
||||
platform_get_size(popup->platform_window, &pw, &ph);
|
||||
|
||||
RendererDesc rdesc = {};
|
||||
RendererDesc rdesc = {0};
|
||||
rdesc.window_handle = platform_get_native_handle(popup->platform_window);
|
||||
rdesc.width = pw;
|
||||
rdesc.height = ph;
|
||||
popup->renderer = renderer_create_shared(parent_renderer, &rdesc);
|
||||
if (!popup->renderer) {
|
||||
platform_destroy_window(popup->platform_window);
|
||||
return nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create UI context
|
||||
@@ -67,7 +67,7 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
memset(&popup->wstate, 0, sizeof(popup->wstate));
|
||||
|
||||
platform_set_frame_callback(popup->platform_window, popup_frame_callback, popup);
|
||||
|
||||
@@ -81,7 +81,7 @@ void popup_close(PopupWindow *popup) {
|
||||
*popup->open_flag = 0;
|
||||
|
||||
popup->alive = 0;
|
||||
platform_set_frame_callback(popup->platform_window, nullptr, nullptr);
|
||||
platform_set_frame_callback(popup->platform_window, NULL, NULL);
|
||||
|
||||
ui_destroy(popup->ui_ctx);
|
||||
renderer_destroy(popup->renderer);
|
||||
@@ -151,15 +151,15 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
g_wstate = saved_wstate;
|
||||
|
||||
// Render
|
||||
renderer_end_frame(popup->renderer, render_commands);
|
||||
renderer_end_frame(popup->renderer, &render_commands);
|
||||
}
|
||||
|
||||
void popup_close_all() {
|
||||
void popup_close_all(void) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++)
|
||||
if (g_popups[i].alive) popup_close(&g_popups[i]);
|
||||
}
|
||||
|
||||
void popup_close_check() {
|
||||
void popup_close_check(void) {
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
if (g_popups[i].alive && platform_window_should_close(g_popups[i].platform_window))
|
||||
popup_close(&g_popups[i]);
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#define MAX_POPUP_WINDOWS 4
|
||||
|
||||
struct PopupWindow {
|
||||
typedef struct PopupWindow {
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
@@ -18,14 +18,14 @@ struct PopupWindow {
|
||||
S32 width, height;
|
||||
S32 last_w, last_h;
|
||||
const char *title;
|
||||
};
|
||||
} PopupWindow;
|
||||
|
||||
PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer,
|
||||
const char *title, B32 *open_flag,
|
||||
S32 width, S32 height,
|
||||
UI_WindowContentFn content_fn, void *user_data,
|
||||
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_POPUP,
|
||||
B32 independent = 0);
|
||||
PlatformWindowStyle style,
|
||||
B32 independent);
|
||||
void popup_close(PopupWindow *popup);
|
||||
PopupWindow *popup_find_by_flag(B32 *flag);
|
||||
void popup_do_frame(PopupWindow *popup, F32 dt);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
UI_WidgetState g_wstate = {};
|
||||
UI_WidgetState g_wstate = {0};
|
||||
|
||||
// Icon per-frame pool (forward declaration for begin_frame)
|
||||
#define UI_MAX_ICONS_PER_FRAME 32
|
||||
@@ -33,7 +33,7 @@ static char g_knob_text_bufs[UI_MAX_KNOB_TEXT_BUFS][32];
|
||||
static S32 g_knob_text_buf_count = 0;
|
||||
|
||||
static CustomGradientData *alloc_gradient(Clay_Color top, Clay_Color bottom) {
|
||||
if (g_grad_pool_count >= UI_MAX_GRADIENTS_PER_FRAME) return nullptr;
|
||||
if (g_grad_pool_count >= UI_MAX_GRADIENTS_PER_FRAME) return NULL;
|
||||
CustomGradientData *g = &g_grad_pool[g_grad_pool_count++];
|
||||
g->type = CUSTOM_RENDER_VGRADIENT;
|
||||
g->top_color = top;
|
||||
@@ -120,7 +120,7 @@ static void emit_shadow(Clay_BoundingBox bb, F32 ox, F32 oy, F32 radius,
|
||||
}
|
||||
|
||||
void ui_widgets_init() {
|
||||
g_wstate = {};
|
||||
memset(&g_wstate, 0, sizeof(g_wstate));
|
||||
}
|
||||
|
||||
void ui_widgets_begin_frame(PlatformInput input) {
|
||||
@@ -171,43 +171,43 @@ static void ensure_widget_text_configs() {
|
||||
if (g_widget_text_configs_scale == g_ui_scale) return;
|
||||
g_widget_text_configs_scale = g_ui_scale;
|
||||
|
||||
g_widget_text_config = {};
|
||||
memset(&g_widget_text_config, 0, sizeof(g_widget_text_config));
|
||||
g_widget_text_config.textColor = g_theme.text;
|
||||
g_widget_text_config.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
g_widget_text_config_dim = {};
|
||||
memset(&g_widget_text_config_dim, 0, sizeof(g_widget_text_config_dim));
|
||||
g_widget_text_config_dim.textColor = g_theme.text_dim;
|
||||
g_widget_text_config_dim.fontSize = FONT_SIZE_NORMAL;
|
||||
g_widget_text_config_dim.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Selected text: white on accent background (background set on parent element)
|
||||
g_widget_text_config_sel = {};
|
||||
g_widget_text_config_sel.textColor = Clay_Color{255, 255, 255, 255};
|
||||
memset(&g_widget_text_config_sel, 0, sizeof(g_widget_text_config_sel));
|
||||
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 = {};
|
||||
memset(&g_widget_text_config_btn, 0, sizeof(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 = {};
|
||||
memset(&g_widget_text_config_tab, 0, sizeof(g_widget_text_config_tab));
|
||||
g_widget_text_config_tab.textColor = g_theme.tab_text;
|
||||
g_widget_text_config_tab.fontSize = FONT_SIZE_TAB;
|
||||
g_widget_text_config_tab.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
// Inactive tab text (theme text color, smaller font)
|
||||
g_widget_text_config_tab_inactive = {};
|
||||
memset(&g_widget_text_config_tab_inactive, 0, sizeof(g_widget_text_config_tab_inactive));
|
||||
g_widget_text_config_tab_inactive.textColor = g_theme.text;
|
||||
g_widget_text_config_tab_inactive.fontSize = FONT_SIZE_TAB;
|
||||
g_widget_text_config_tab_inactive.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
}
|
||||
|
||||
static Clay_String clay_str(const char *s) {
|
||||
Clay_String r = {};
|
||||
Clay_String r = {0};
|
||||
r.isStaticallyAllocated = false;
|
||||
r.length = (S32)strlen(s);
|
||||
r.chars = s;
|
||||
@@ -926,7 +926,7 @@ 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 = {};
|
||||
Clay_CornerRadius item_radius = {0};
|
||||
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; }
|
||||
|
||||
@@ -1008,7 +1008,7 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
|
||||
CLAY_TEXT(lbl_str, &g_widget_text_config_tab);
|
||||
}
|
||||
} else {
|
||||
Clay_Color bg = hovered ? (Clay_Color)TAB_INACTIVE_HOVER : (Clay_Color)TAB_INACTIVE_BG;
|
||||
Clay_Color bg = hovered ? TAB_INACTIVE_HOVER : TAB_INACTIVE_BG;
|
||||
CLAY(tab_eid,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(TAB_HEIGHT) },
|
||||
@@ -1033,6 +1033,18 @@ S32 ui_tab_bar(const char *id, const char **labels, S32 count, S32 *selected) {
|
||||
////////////////////////////////
|
||||
// Shared value-edit helpers (used by knob, sliders, fader)
|
||||
|
||||
// Helper: delete selected range from knob edit buffer.
|
||||
// Returns new string length.
|
||||
static S32 ke_delete_sel(char *ebuf, S32 elen, S32 *ecur, S32 *esel0, S32 *esel1) {
|
||||
S32 lo = (*esel0 < *esel1 ? *esel0 : *esel1);
|
||||
S32 hi = (*esel0 < *esel1 ? *esel1 : *esel0);
|
||||
if (lo == hi) return elen;
|
||||
memmove(&ebuf[lo], &ebuf[hi], elen - hi + 1);
|
||||
*ecur = lo;
|
||||
*esel0 = *ecur; *esel1 = *ecur;
|
||||
return elen - (hi - lo);
|
||||
}
|
||||
|
||||
// Process keyboard input for value text editing.
|
||||
// Returns 0 = still editing, 1 = committed, 2 = cancelled.
|
||||
static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *changed) {
|
||||
@@ -1054,15 +1066,6 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
#define KE_SEL_HI() (*esel0 < *esel1 ? *esel1 : *esel0)
|
||||
#define KE_CLEAR_SEL() do { *esel0 = *ecur; *esel1 = *ecur; } while(0)
|
||||
|
||||
auto ke_delete_sel = [&]() -> S32 {
|
||||
S32 lo = KE_SEL_LO(), hi = KE_SEL_HI();
|
||||
if (lo == hi) return elen;
|
||||
memmove(&ebuf[lo], &ebuf[hi], elen - hi + 1);
|
||||
*ecur = lo;
|
||||
KE_CLEAR_SEL();
|
||||
return elen - (hi - lo);
|
||||
};
|
||||
|
||||
for (S32 k = 0; k < g_wstate.input.key_count; k++) {
|
||||
U8 key = g_wstate.input.keys[k];
|
||||
|
||||
@@ -1085,14 +1088,14 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
char tmp[32]; S32 n = hi - lo; if (n > 31) n = 31;
|
||||
memcpy(tmp, &ebuf[lo], n); tmp[n] = '\0';
|
||||
platform_clipboard_set(tmp);
|
||||
elen = ke_delete_sel();
|
||||
elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key == PKEY_V) {
|
||||
const char *clip = platform_clipboard_get();
|
||||
if (clip) {
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel();
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
S32 clip_len = (S32)strlen(clip);
|
||||
char filtered[32]; S32 flen = 0;
|
||||
for (S32 i = 0; i < clip_len && flen < 30; i++) {
|
||||
@@ -1117,14 +1120,14 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
if (key == PKEY_RETURN) { commit = 1; }
|
||||
else if (key == PKEY_ESCAPE) { cancel = 1; }
|
||||
else if (key == PKEY_BACKSPACE) {
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(); }
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1); }
|
||||
else if (*ecur > 0) {
|
||||
memmove(&ebuf[*ecur - 1], &ebuf[*ecur], elen - *ecur + 1);
|
||||
(*ecur)--; elen--;
|
||||
}
|
||||
KE_CLEAR_SEL();
|
||||
} else if (key == PKEY_DELETE) {
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(); }
|
||||
if (KE_HAS_SEL()) { elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1); }
|
||||
else if (*ecur < elen) {
|
||||
memmove(&ebuf[*ecur], &ebuf[*ecur + 1], elen - *ecur);
|
||||
elen--;
|
||||
@@ -1146,7 +1149,7 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
U16 ch = g_wstate.input.chars[c];
|
||||
B32 valid = (ch >= '0' && ch <= '9') || ch == '.' || ch == '-';
|
||||
if (valid) {
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel();
|
||||
if (KE_HAS_SEL()) elen = ke_delete_sel(ebuf, elen, ecur, esel0, esel1);
|
||||
if (elen < 30) {
|
||||
memmove(&ebuf[*ecur + 1], &ebuf[*ecur], elen - *ecur + 1);
|
||||
ebuf[*ecur] = (char)ch; (*ecur)++; elen++;
|
||||
@@ -1162,7 +1165,7 @@ static S32 value_edit_process_keys(F32 *value, F32 max_val, B32 is_signed, B32 *
|
||||
#undef KE_CLEAR_SEL
|
||||
|
||||
if (commit) {
|
||||
char *end = nullptr;
|
||||
char *end = NULL;
|
||||
F32 parsed = strtof(ebuf, &end);
|
||||
if (end != ebuf) {
|
||||
F32 lo = is_signed ? -max_val : 0.0f;
|
||||
@@ -1199,13 +1202,13 @@ static void value_edit_render(U32 hash, F32 width) {
|
||||
static char ke_dbuf_sel[32];
|
||||
static char ke_dbuf_after[32];
|
||||
|
||||
static Clay_TextElementConfig edit_cfg = {};
|
||||
static Clay_TextElementConfig edit_cfg = {0};
|
||||
edit_cfg.textColor = g_theme.text;
|
||||
edit_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
edit_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
static Clay_TextElementConfig edit_sel_cfg = {};
|
||||
edit_sel_cfg.textColor = Clay_Color{255, 255, 255, 255};
|
||||
static Clay_TextElementConfig edit_sel_cfg = {0};
|
||||
edit_sel_cfg.textColor = (Clay_Color){255, 255, 255, 255};
|
||||
edit_sel_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
edit_sel_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
|
||||
@@ -1266,7 +1269,7 @@ static Clay_ElementId value_edit_eid(U32 hash) {
|
||||
// Handle click-outside to commit the edit.
|
||||
static void value_edit_click_away(Clay_ElementId eid, F32 *value, F32 max_val, B32 is_signed, B32 *changed) {
|
||||
if (g_wstate.mouse_clicked && !Clay_PointerOver(eid)) {
|
||||
char *end = nullptr;
|
||||
char *end = NULL;
|
||||
F32 parsed = strtof(g_wstate.knob_edit_buf, &end);
|
||||
if (end != g_wstate.knob_edit_buf) {
|
||||
F32 lo = is_signed ? -max_val : 0.0f;
|
||||
@@ -1305,7 +1308,7 @@ static F32 value_normalize(F32 value, F32 max_val, B32 is_signed) {
|
||||
|
||||
// Format a value into a text buf from the pool. Returns null if pool exhausted.
|
||||
static char *value_format_text(F32 value, B32 is_signed, S32 *out_len) {
|
||||
if (g_knob_text_buf_count >= UI_MAX_KNOB_TEXT_BUFS) return nullptr;
|
||||
if (g_knob_text_buf_count >= UI_MAX_KNOB_TEXT_BUFS) return NULL;
|
||||
char *buf = g_knob_text_bufs[g_knob_text_buf_count++];
|
||||
if (is_signed) { *out_len = snprintf(buf, 32, "%+.1f", value); }
|
||||
else { *out_len = snprintf(buf, 32, "%.1f", value); }
|
||||
@@ -1364,7 +1367,7 @@ B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_s
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Layout: vertical column (knob → value text → label)
|
||||
CLAY(WID(id),
|
||||
@@ -1432,7 +1435,7 @@ B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_s
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig knob_val_cfg = {};
|
||||
static Clay_TextElementConfig knob_val_cfg = {0};
|
||||
knob_val_cfg.textColor = g_theme.text;
|
||||
knob_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
knob_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1507,7 +1510,7 @@ B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Dimmed accent for fill bar
|
||||
Clay_Color fill_color = g_theme.accent;
|
||||
@@ -1611,7 +1614,7 @@ B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig sl_val_cfg = {};
|
||||
static Clay_TextElementConfig sl_val_cfg = {0};
|
||||
sl_val_cfg.textColor = g_theme.text;
|
||||
sl_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
sl_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1683,7 +1686,7 @@ B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Dimmed accent for fill bar
|
||||
Clay_Color fill_color = g_theme.accent;
|
||||
@@ -1788,7 +1791,7 @@ B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig sl_val_cfg = {};
|
||||
static Clay_TextElementConfig sl_val_cfg = {0};
|
||||
sl_val_cfg.textColor = g_theme.text;
|
||||
sl_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
sl_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -1860,7 +1863,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
|
||||
// Format value text
|
||||
S32 val_len = 0;
|
||||
char *val_text = is_editing ? nullptr : value_format_text(*value, is_signed, &val_len);
|
||||
char *val_text = is_editing ? NULL : value_format_text(*value, is_signed, &val_len);
|
||||
|
||||
// Tick mark dimensions
|
||||
F32 tick_major_w = WIDGET_FADER_TICK_MAJOR_W;
|
||||
@@ -1954,7 +1957,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
CustomIconData *idata = &g_icon_pool[g_icon_pool_count++];
|
||||
idata->type = CUSTOM_RENDER_ICON;
|
||||
idata->icon_id = (S32)UI_ICON_FADER;
|
||||
idata->color = Clay_Color{255, 255, 255, 255};
|
||||
idata->color = (Clay_Color){255, 255, 255, 255};
|
||||
|
||||
F32 cap_y = (1.0f - normalized) * (track_h - cap_h);
|
||||
|
||||
@@ -2026,7 +2029,7 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
|
||||
B32 val_hovered = Clay_PointerOver(val_eid);
|
||||
|
||||
Clay_String val_str = { .isStaticallyAllocated = false, .length = val_len, .chars = val_text };
|
||||
static Clay_TextElementConfig fdr_val_cfg = {};
|
||||
static Clay_TextElementConfig fdr_val_cfg = {0};
|
||||
fdr_val_cfg.textColor = g_theme.text;
|
||||
fdr_val_cfg.fontSize = FONT_SIZE_SMALL;
|
||||
fdr_val_cfg.wrapMode = CLAY_TEXT_WRAP_NONE;
|
||||
@@ -16,7 +16,7 @@
|
||||
#define UI_WIDGET_MAX_DROPDOWN_ITEMS 32
|
||||
#define UI_WIDGET_MAX_TEXT_INPUTS 16
|
||||
|
||||
struct UI_KnobDragState {
|
||||
typedef struct UI_KnobDragState {
|
||||
U32 dragging_id; // Hash of the knob being dragged (0 = none)
|
||||
F32 drag_start_y; // Mouse Y when drag started
|
||||
F32 drag_start_x; // Mouse X when drag started (for h-slider)
|
||||
@@ -24,9 +24,9 @@ struct UI_KnobDragState {
|
||||
B32 was_shift; // Shift state last frame (to re-anchor on change)
|
||||
U32 last_click_id; // Knob hash of last click (for F64-click detection)
|
||||
S32 last_click_frame; // Frame number of last click
|
||||
};
|
||||
} UI_KnobDragState;
|
||||
|
||||
struct UI_WidgetState {
|
||||
typedef struct UI_WidgetState {
|
||||
// Text input focus
|
||||
U32 focused_id; // Clay element ID hash of the focused text input (0 = none)
|
||||
S32 cursor_pos; // Cursor position in focused text input
|
||||
@@ -59,7 +59,7 @@ struct UI_WidgetState {
|
||||
S32 knob_edit_cursor; // Cursor position in edit buffer
|
||||
S32 knob_edit_sel_start; // Selection anchor
|
||||
S32 knob_edit_sel_end; // Selection extent
|
||||
};
|
||||
} UI_WidgetState;
|
||||
|
||||
extern UI_WidgetState g_wstate;
|
||||
|
||||
@@ -118,13 +118,13 @@ typedef void (*UI_WindowContentFn)(void *user_data);
|
||||
// editable: if true, clicking the value text opens a text input for direct entry.
|
||||
// Hold Shift while dragging for fine control.
|
||||
// Returns true if value changed this frame.
|
||||
B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_knob(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// Horizontal slider. Drag left/right to change value.
|
||||
B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_slider_h(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// Vertical slider. Drag up/down to change value.
|
||||
B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
// DAW-style fader (vertical slider with fader cap icon).
|
||||
B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable = 0);
|
||||
B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
|
||||
|
||||
Reference in New Issue
Block a user