WIP: lunasvg implementation, things stopped working

This commit is contained in:
2026-03-03 14:01:22 -05:00
parent 19bf78d635
commit 2703bbd901
80 changed files with 38694 additions and 12 deletions

View File

@@ -113,6 +113,7 @@ static inline uint16_t uifs(float x) { return (uint16_t)(x * g_ui_scale + 0.5f);
enum CustomRenderType {
CUSTOM_RENDER_VGRADIENT = 1,
CUSTOM_RENDER_ICON = 2,
};
struct CustomGradientData {
@@ -121,6 +122,12 @@ struct CustomGradientData {
Clay_Color bottom_color;
};
struct CustomIconData {
CustomRenderType type; // CUSTOM_RENDER_ICON
S32 icon_id;
Clay_Color color;
};
////////////////////////////////
// Font sizes

76
src/ui/ui_icons.cpp Normal file
View File

@@ -0,0 +1,76 @@
// 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>)",
};
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, 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;
// Extract alpha channel from ARGB32 premultiplied into R8
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++) {
// ARGB32 premultiplied: bytes are B, G, R, A (little-endian)
U8 a = src[y * stride + x * 4 + 3];
atlas[y * atlas_w + pen_x + x] = 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;
}

23
src/ui/ui_icons.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
// ui_icons.h - SVG icon definitions and atlas rasterization via lunasvg
#include "base/base_inc.h"
enum UI_IconID {
UI_ICON_CLOSE,
UI_ICON_CHECK,
UI_ICON_CHEVRON_DOWN,
UI_ICON_COUNT
};
struct UI_IconInfo {
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
F32 w, h; // pixel dimensions at rasterized size
};
extern UI_IconInfo g_icons[UI_ICON_COUNT];
// Rasterizes all icons into an R8 atlas bitmap.
// Returns malloc'd data (caller frees). Sets *out_w, *out_h to atlas dimensions.
// icon_size is the pixel height to rasterize each icon at.
U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size);

View File

@@ -10,6 +10,11 @@
UI_WidgetState g_wstate = {};
// Icon per-frame pool (forward declaration for begin_frame)
#define UI_MAX_ICONS_PER_FRAME 32
static CustomIconData g_icon_pool[UI_MAX_ICONS_PER_FRAME];
static S32 g_icon_pool_count = 0;
void ui_widgets_init() {
g_wstate = {};
}
@@ -17,6 +22,7 @@ void ui_widgets_init() {
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_wstate.cursor_blink += 1.0f / 60.0f;
g_wstate.text_input_count = 0;
g_wstate.tab_pressed = 0;
@@ -96,6 +102,26 @@ static Clay_String clay_str(const char *s) {
#define WID(s) CLAY_SID(clay_str(s))
#define WIDI(s, i) CLAY_SIDI(clay_str(s), i)
////////////////////////////////
// Icon
void ui_icon(UI_IconID icon, F32 size, Clay_Color color) {
if (g_icon_pool_count >= UI_MAX_ICONS_PER_FRAME) return;
S32 idx = g_icon_pool_count;
CustomIconData *data = &g_icon_pool[g_icon_pool_count++];
data->type = CUSTOM_RENDER_ICON;
data->icon_id = (S32)icon;
data->color = color;
CLAY(CLAY_IDI("UIIcon", idx),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) },
},
.custom = { .customData = data }
) {}
}
////////////////////////////////
// Label
@@ -168,7 +194,7 @@ B32 ui_checkbox(const char *id, const char *label, B32 *value) {
.border = { .color = g_theme.border, .width = { 1, 1, 1, 1 } }
) {
if (*value) {
CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config);
ui_icon(UI_ICON_CHECK, WIDGET_CHECKBOX_SIZE * 0.75f, g_theme.text);
}
}
@@ -707,11 +733,11 @@ B32 ui_dropdown(const char *id, const char **options, S32 count, S32 *selected)
CLAY(WIDI(id, 501),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(uis(20)), .height = CLAY_SIZING_FIT() },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER },
.sizing = { .width = CLAY_SIZING_FIXED(uis(20)), .height = CLAY_SIZING_FIXED(uis(20)) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
}
) {
CLAY_TEXT(CLAY_STRING("v"), &g_widget_text_config_dim);
ui_icon(UI_ICON_CHEVRON_DOWN, uis(12), g_theme.text_dim);
}
}
@@ -1078,7 +1104,7 @@ B32 ui_window(const char *id, const char *title, B32 *open,
.backgroundColor = close_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS_SM)
) {
CLAY_TEXT(CLAY_STRING("x"), &g_widget_text_config_btn);
ui_icon(UI_ICON_CLOSE, uis(12), g_theme.button_text);
}
}

View File

@@ -7,6 +7,7 @@
// like which text field is focused or which dropdown is open.
#include "ui/ui_core.h"
#include "ui/ui_icons.h"
#include "platform/platform.h"
////////////////////////////////
@@ -89,6 +90,9 @@ void ui_text_input_reset_display_bufs();
// Widgets
// All IDs must be unique string literals (passed to CLAY_ID internally).
// Icon element (rendered via icon atlas)
void ui_icon(UI_IconID icon, F32 size, Clay_Color color);
// Simple label
void ui_label(const char *id, const char *text);