move from imgui to CLAY

This commit is contained in:
2026-02-25 15:20:47 -05:00
parent 6656b6d0b2
commit 12dae774e4
41 changed files with 6835 additions and 77320 deletions

53
src/base/base_arena.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "base/base_arena.h"
#include <stdlib.h>
Arena *arena_alloc(U64 cap) {
U8 *mem = (U8 *)malloc(sizeof(Arena) + cap);
if (!mem) return nullptr;
Arena *arena = (Arena *)mem;
arena->base = mem + sizeof(Arena);
arena->pos = 0;
arena->cap = cap;
return arena;
}
void arena_release(Arena *arena) {
if (arena) free(arena);
}
void *arena_push(Arena *arena, U64 size) {
U64 aligned = AlignPow2(size, 8);
if (arena->pos + aligned > arena->cap) {
Assert(!"Arena overflow");
return nullptr;
}
void *result = arena->base + arena->pos;
arena->pos += aligned;
MemoryZero(result, aligned);
return result;
}
void *arena_push_no_zero(Arena *arena, U64 size) {
U64 aligned = AlignPow2(size, 8);
if (arena->pos + aligned > arena->cap) {
Assert(!"Arena overflow");
return nullptr;
}
void *result = arena->base + arena->pos;
arena->pos += aligned;
return result;
}
U64 arena_pos(Arena *arena) {
return arena->pos;
}
void arena_pop_to(Arena *arena, U64 pos) {
if (pos < arena->pos) {
arena->pos = pos;
}
}
void arena_clear(Arena *arena) {
arena->pos = 0;
}

44
src/base/base_arena.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
// base_arena.h - Linear arena allocator
// Simplified from raddebugger's virtual-memory-backed arena to a malloc-based one.
// Suitable for per-frame scratch allocations and persistent state.
#include "base/base_core.h"
////////////////////////////////
// Arena type
struct Arena {
U8 *base;
U64 pos;
U64 cap;
};
// Temporary scope (save/restore position)
struct Temp {
Arena *arena;
U64 pos;
};
////////////////////////////////
// Arena functions
Arena *arena_alloc(U64 cap);
void arena_release(Arena *arena);
void *arena_push(Arena *arena, U64 size);
void *arena_push_no_zero(Arena *arena, U64 size);
U64 arena_pos(Arena *arena);
void arena_pop_to(Arena *arena, U64 pos);
void arena_clear(Arena *arena);
////////////////////////////////
// Temporary scope helpers
inline Temp temp_begin(Arena *arena) { return {arena, arena->pos}; }
inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
////////////////////////////////
// Push helper macros
#define push_array(arena, T, count) ((T *)arena_push((arena), sizeof(T) * (count)))
#define push_array_no_zero(arena, T, count) ((T *)arena_push_no_zero((arena), sizeof(T) * (count)))

176
src/base/base_core.h Normal file
View File

@@ -0,0 +1,176 @@
#pragma once
// base_core.h - Fundamental types, macros, and linked list helpers
// Inspired by raddebugger's base_core.h
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
////////////////////////////////
// Codebase keywords
#define internal static
#define global static
#define local_persist static
////////////////////////////////
// Base types
typedef uint8_t U8;
typedef uint16_t U16;
typedef uint32_t U32;
typedef uint64_t U64;
typedef int8_t S8;
typedef int16_t S16;
typedef int32_t S32;
typedef int64_t S64;
typedef S32 B32;
typedef float F32;
typedef double F64;
////////////////////////////////
// Limits
#define max_U8 0xFF
#define max_U16 0xFFFF
#define max_U32 0xFFFFFFFF
#define max_U64 0xFFFFFFFFFFFFFFFFull
#define max_S8 0x7F
#define max_S16 0x7FFF
#define max_S32 0x7FFFFFFF
#define max_S64 0x7FFFFFFFFFFFFFFFll
////////////////////////////////
// Units
#define KB(n) (((U64)(n)) << 10)
#define MB(n) (((U64)(n)) << 20)
#define GB(n) (((U64)(n)) << 30)
////////////////////////////////
// Clamps, Mins, Maxes
#define Min(A, B) (((A) < (B)) ? (A) : (B))
#define Max(A, B) (((A) > (B)) ? (A) : (B))
#define ClampTop(A, X) Min(A, X)
#define ClampBot(X, B) Max(X, B)
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) : (X))
////////////////////////////////
// Alignment / Sizing
#define AlignPow2(x, b) (((x) + (b) - 1) & (~((b) - 1)))
#define AlignDownPow2(x, b) ((x) & (~((b) - 1)))
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
////////////////////////////////
// Memory macros
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
#define MemoryZero(s, z) memset((s), 0, (z))
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
////////////////////////////////
// Pointer / integer casts
#define IntFromPtr(ptr) ((U64)(ptr))
#define PtrFromInt(i) (void *)(i)
#define OffsetOf(T, m) IntFromPtr(&(((T *)0)->m))
////////////////////////////////
// Member access
#define CastFromMember(T, m, ptr) (T *)(((U8 *)(ptr)) - OffsetOf(T, m))
////////////////////////////////
// For-Loop construct macros
#define DeferLoop(begin, end) for (int _i_ = ((begin), 0); !_i_; _i_ += 1, (end))
#define DeferLoopChecked(begin, end) for (int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end))
#define EachIndex(it, count) (U64 it = 0; it < (count); it += 1)
#define EachElement(it, array) (U64 it = 0; it < ArrayCount(array); it += 1)
////////////////////////////////
// Glue / Stringify
#define Stringify_(S) #S
#define Stringify(S) Stringify_(S)
#define Glue_(A, B) A##B
#define Glue(A, B) Glue_(A, B)
#define Swap(T, a, b) do { T t__ = a; a = b; b = t__; } while (0)
////////////////////////////////
// Assert
#if defined(_MSC_VER)
# define Trap() __debugbreak()
#elif defined(__clang__) || defined(__GNUC__)
# define Trap() __builtin_trap()
#else
# define Trap() (*(volatile int *)0 = 0)
#endif
#define AssertAlways(x) do { if (!(x)) { Trap(); } } while (0)
#ifdef _DEBUG
# define Assert(x) AssertAlways(x)
#else
# define Assert(x) (void)(x)
#endif
#define InvalidPath Assert(!"Invalid Path!")
#define NotImplemented Assert(!"Not Implemented!")
////////////////////////////////
// Linked list macros
// Nil-aware doubly-linked-list operations
#define CheckNil(nil, p) ((p) == 0 || (p) == nil)
#define SetNil(nil, p) ((p) = nil)
// Doubly-linked-list (with nil support)
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
(CheckNil(nil, f) ? \
((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : \
CheckNil(nil, p) ? \
((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) : \
((p) == (l)) ? \
((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) : \
(((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), \
((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
#define DLLPushBack_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, f, l, l, n, next, prev)
#define DLLPushFront_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, l, f, f, n, prev, next)
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
(((n) == (f) ? (f) = (n)->next : (0)), \
((n) == (l) ? (l) = (l)->prev : (0)), \
(CheckNil(nil, (n)->prev) ? (0) : ((n)->prev->next = (n)->next)), \
(CheckNil(nil, (n)->next) ? (0) : ((n)->next->prev = (n)->prev)))
// Convenience wrappers using 0 as nil
#define DLLPushBack(f, l, n) DLLPushBack_NPZ(0, f, l, n, next, prev)
#define DLLPushFront(f, l, n) DLLPushFront_NPZ(0, f, l, n, next, prev)
#define DLLRemove(f, l, n) DLLRemove_NPZ(0, f, l, n, next, prev)
// Singly-linked queue (doubly-headed)
#define SLLQueuePush_NZ(nil, f, l, n, next) \
(CheckNil(nil, f) ? \
((f) = (l) = (n), SetNil(nil, (n)->next)) : \
((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
#define SLLQueuePush(f, l, n) SLLQueuePush_NZ(0, f, l, n, next)
#define SLLQueuePushFront(f, l, n) (((n)->next = (f)), ((f) = (n)))
#define SLLQueuePop(f, l) ((f) == (l) ? ((f) = 0, (l) = 0) : ((f) = (f)->next))
// Singly-linked stack
#define SLLStackPush(f, n) ((n)->next = (f), (f) = (n))
#define SLLStackPop(f) ((f) = (f)->next)

3
src/base/base_inc.cpp Normal file
View File

@@ -0,0 +1,3 @@
// base_inc.cpp - Unity build for the base layer
#include "base/base_arena.cpp"
#include "base/base_strings.cpp"

8
src/base/base_inc.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
// base_inc.h - Umbrella include for the base layer
// Include this one header to get all base types.
#include "base/base_core.h"
#include "base/base_arena.h"
#include "base/base_math.h"
#include "base/base_strings.h"

110
src/base/base_math.h Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
// base_math.h - Vector, range, and color types
// Inspired by raddebugger's base_math.h
#include "base/base_core.h"
////////////////////////////////
// Axis enum
enum Axis2 {
Axis2_X = 0,
Axis2_Y = 1,
Axis2_COUNT,
};
enum Side {
Side_Min = 0,
Side_Max = 1,
Side_COUNT,
};
enum Corner {
Corner_00 = 0, // top-left
Corner_01 = 1, // top-right
Corner_10 = 2, // bottom-left
Corner_11 = 3, // bottom-right
Corner_COUNT,
};
////////////////////////////////
// Vector types
struct Vec2F32 { F32 x, y; };
struct Vec2S32 { S32 x, y; };
struct Vec3F32 { F32 x, y, z; };
struct Vec4F32 { F32 x, y, z, w; };
////////////////////////////////
// Range types
struct Rng1F32 { F32 min, max; };
struct Rng1S64 { S64 min, max; };
struct Rng2F32 { Vec2F32 p0, p1; };
////////////////////////////////
// Constructors
static inline Vec2F32 v2f32(F32 x, F32 y) { return {x, y}; }
static inline Vec2S32 v2s32(S32 x, S32 y) { return {x, y}; }
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return {x, y, z}; }
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return {x, y, z, w}; }
static inline Rng1F32 rng1f32(F32 min, F32 max) { return {min, max}; }
static inline Rng1S64 rng1s64(S64 min, S64 max) { return {min, max}; }
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return {p0, p1}; }
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return {{x0, y0}, {x1, y1}}; }
////////////////////////////////
// Vec2F32 operations
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return {a.x + b.x, a.y + b.y}; }
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return {a.x - b.x, a.y - b.y}; }
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return {a.x * b.x, a.y * b.y}; }
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return {v.x * s, v.y * s}; }
// Axis-indexed access
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 val) {
if (a == Axis2_X) v->x = val; else v->y = val;
}
////////////////////////////////
// Vec4F32 operations
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return {a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; }
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return {v.x*s, v.y*s, v.z*s, v.w*s}; }
static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
return {a.x + (b.x - a.x)*t, a.y + (b.y - a.y)*t, a.z + (b.z - a.z)*t, a.w + (b.w - a.w)*t};
}
////////////////////////////////
// Rng2F32 operations
static inline F32 rng2f32_width(Rng2F32 r) { return r.p1.x - r.p0.x; }
static inline F32 rng2f32_height(Rng2F32 r) { return r.p1.y - r.p0.y; }
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return {r.p1.x - r.p0.x, r.p1.y - r.p0.y}; }
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return {(r.p0.x + r.p1.x)*0.5f, (r.p0.y + r.p1.y)*0.5f}; }
static inline B32 rng2f32_contains(Rng2F32 r, Vec2F32 p) {
return p.x >= r.p0.x && p.x <= r.p1.x && p.y >= r.p0.y && p.y <= r.p1.y;
}
static inline Rng2F32 rng2f32_pad(Rng2F32 r, F32 p) {
return {{r.p0.x - p, r.p0.y - p}, {r.p1.x + p, r.p1.y + p}};
}
static inline Rng2F32 rng2f32_shift(Rng2F32 r, Vec2F32 v) {
return {{r.p0.x + v.x, r.p0.y + v.y}, {r.p1.x + v.x, r.p1.y + v.y}};
}
static inline Rng2F32 rng2f32_intersect(Rng2F32 a, Rng2F32 b) {
return {{Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y)},
{Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.y)}};
}
// Axis-indexed range dimension
static inline F32 rng2f32_dim_axis(Rng2F32 r, Axis2 a) {
return a == Axis2_X ? (r.p1.x - r.p0.x) : (r.p1.y - r.p0.y);
}
////////////////////////////////
// F32 helpers
static inline F32 lerp_1f32(F32 a, F32 b, F32 t) { return a + (b - a) * t; }
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }

32
src/base/base_strings.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "base/base_strings.h"
#include "base/base_arena.h"
Str8 str8_pushf(Arena *arena, const char *fmt, ...) {
va_list args, args2;
va_start(args, fmt);
va_copy(args2, args);
int len = vsnprintf(nullptr, 0, fmt, args);
va_end(args);
char *buf = push_array(arena, char, len + 1);
vsnprintf(buf, len + 1, fmt, args2);
va_end(args2);
return {buf, (U64)len};
}
Str8 str8_push_copy(Arena *arena, Str8 s) {
if (s.size == 0 || !s.str) return {nullptr, 0};
char *buf = push_array_no_zero(arena, char, s.size + 1);
MemoryCopy(buf, s.str, s.size);
buf[s.size] = 0;
return {buf, s.size};
}
void str8_list_push(Arena *arena, Str8List *list, Str8 s) {
Str8Node *node = push_array(arena, Str8Node, 1);
node->string = s;
SLLQueuePush(list->first, list->last, node);
list->count++;
list->total_size += s.size;
}

49
src/base/base_strings.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
// base_strings.h - Simple length-delimited string type
// Inspired by raddebugger's String8
#include "base/base_core.h"
////////////////////////////////
// String types
struct Str8 {
const char *str;
U64 size;
};
struct Str8Node {
Str8Node *next;
Str8 string;
};
struct Str8List {
Str8Node *first;
Str8Node *last;
U64 count;
U64 total_size;
};
////////////////////////////////
// Forward declaration for Arena
struct Arena;
////////////////////////////////
// Constructors
static inline Str8 str8(const char *s, U64 len) { return {s, len}; }
static inline Str8 str8_cstr(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
static inline Str8 str8_lit(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
static inline B32 str8_match(Str8 a, Str8 b) {
if (a.size != b.size) return 0;
return MemoryCompare(a.str, b.str, a.size) == 0;
}
static inline B32 str8_is_empty(Str8 s) { return s.size == 0 || s.str == nullptr; }
////////////////////////////////
// String operations (require arena)
Str8 str8_pushf(Arena *arena, const char *fmt, ...);
Str8 str8_push_copy(Arena *arena, Str8 s);
void str8_list_push(Arena *arena, Str8List *list, Str8 s);

View File

@@ -1,44 +1,357 @@
// Unity build - include all src files here
// -mta
// [h]
#include "base/base_inc.h"
#include "platform/platform.h"
#include "renderer/renderer.h"
#include "midi/midi.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <iostream>
#include "ui/ui_core.h"
#include "ui/ui_widgets.h"
// [cpp]
#include "base/base_inc.cpp"
#include "ui/ui_core.cpp"
#include "ui/ui_widgets.cpp"
#include "platform/platform_win32.cpp"
#include "renderer/renderer_dx12.cpp"
#include "midi/midi_win32.cpp"
#include "menus.cpp"
#include "theme.cpp"
////////////////////////////////
// Win32 input gathering
static void build_default_layout(ImGuiID dockspace_id) {
ImGui::DockBuilderRemoveNode(dockspace_id);
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size);
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
ImGuiID center = dockspace_id;
ImGuiID left = ImGui::DockBuilderSplitNode(center, ImGuiDir_Left, 0.15f, nullptr, &center);
ImGuiID right = ImGui::DockBuilderSplitNode(center, ImGuiDir_Right, 0.20f, nullptr, &center);
ImGuiID bottom = ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, 0.25f, nullptr, &center);
struct InputState {
Vec2F32 mouse;
Vec2F32 scroll_delta;
B32 mouse_down;
B32 was_mouse_down;
};
ImGui::DockBuilderDockWindow("Browser", left);
ImGui::DockBuilderDockWindow("Main", center);
ImGui::DockBuilderDockWindow("Properties", right);
ImGui::DockBuilderDockWindow("MIDI Devices", right);
ImGui::DockBuilderDockWindow("Log", bottom);
static InputState g_input;
ImGui::DockBuilderFinish(dockspace_id);
static void input_gather(void *window_handle) {
HWND hwnd = (HWND)window_handle;
// Mouse position
POINT cursor;
GetCursorPos(&cursor);
ScreenToClient(hwnd, &cursor);
g_input.mouse = v2f32((F32)cursor.x, (F32)cursor.y);
// Mouse button
g_input.was_mouse_down = g_input.mouse_down;
g_input.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
// Scroll (TODO: hook WM_MOUSEWHEEL for real scroll deltas)
g_input.scroll_delta = v2f32(0, 0);
}
////////////////////////////////
// Clay text config helpers
static Clay_TextElementConfig g_text_config_normal;
static Clay_TextElementConfig g_text_config_title;
static Clay_TextElementConfig g_text_config_dim;
static void init_text_configs() {
g_text_config_normal = {};
g_text_config_normal.textColor = g_theme.text;
g_text_config_normal.fontSize = 15;
g_text_config_normal.wrapMode = CLAY_TEXT_WRAP_NONE;
g_text_config_title = {};
g_text_config_title.textColor = g_theme.text;
g_text_config_title.fontSize = 15;
g_text_config_title.wrapMode = CLAY_TEXT_WRAP_NONE;
g_text_config_dim = {};
g_text_config_dim.textColor = g_theme.text_dim;
g_text_config_dim.fontSize = 15;
g_text_config_dim.wrapMode = CLAY_TEXT_WRAP_NONE;
}
////////////////////////////////
// Panel builders
static void build_panel_title_bar(Clay_ElementId id, Clay_String title) {
CLAY(id,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(24) },
.padding = { 8, 8, 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.title_bar,
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) {
CLAY_TEXT(title, &g_text_config_title);
}
}
static void build_browser_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("BrowserPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(200), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .right = 1 } }
) {
build_panel_title_bar(CLAY_ID("BrowserTitleBar"), CLAY_STRING("Browser"));
CLAY(CLAY_ID("BrowserContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Instruments"), &g_text_config_normal);
}
}
}
static void build_main_panel() {
CLAY(CLAY_ID("MainPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_light
) {
build_panel_title_bar(CLAY_ID("MainTitleBar"), CLAY_STRING("Main"));
CLAY(CLAY_ID("MainContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Main content area"), &g_text_config_normal);
}
}
}
static void build_properties_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("PropertiesPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) {
build_panel_title_bar(CLAY_ID("PropertiesTitleBar"), CLAY_STRING("Properties"));
CLAY(CLAY_ID("PropertiesContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Details"), &g_text_config_normal);
}
}
}
static void build_midi_panel(B32 show, MidiEngine *midi) {
if (!show) return;
CLAY(CLAY_ID("MidiPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium
) {
build_panel_title_bar(CLAY_ID("MidiTitleBar"), CLAY_STRING("MIDI Devices"));
CLAY(CLAY_ID("MidiContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Refresh button
CLAY(CLAY_ID("MidiRefreshBtn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(28) },
.padding = { 12, 12, 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = Clay_Hovered() ? g_theme.accent_hover : g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(3)
) {
CLAY_TEXT(CLAY_STRING("Refresh"), &g_text_config_normal);
}
// Device list - use static buffers so strings persist for Clay rendering
static char device_bufs[64][128];
int32_t device_count = midi_get_device_count(midi);
for (int32_t i = 0; i < device_count && i < 64; i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
int len = snprintf(device_bufs[i], sizeof(device_bufs[i]), "[%s] %s", dev->is_input ? "IN" : "OUT", dev->name);
Clay_String device_str = { .isStaticallyAllocated = false, .length = len, .chars = device_bufs[i] };
CLAY(CLAY_IDI("MidiDevice", i),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
.padding = { 4, 4, 2, 2 },
}
) {
CLAY_TEXT(device_str, &g_text_config_normal);
}
}
if (device_count == 0) {
CLAY_TEXT(CLAY_STRING("No MIDI devices found"), &g_text_config_dim);
}
}
}
}
static void build_log_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("LogPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(180) },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .top = 1 } }
) {
build_panel_title_bar(CLAY_ID("LogTitleBar"), CLAY_STRING("Log"));
CLAY(CLAY_ID("LogContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Output / Log"), &g_text_config_normal);
}
}
}
////////////////////////////////
// App state — all mutable state the frame function needs
struct AppState {
PlatformWindow *window;
Renderer *renderer;
MidiEngine *midi;
UI_Context *ui;
S32 last_w, last_h;
B32 show_browser;
B32 show_props;
B32 show_log;
B32 show_midi_devices;
LARGE_INTEGER freq;
LARGE_INTEGER last_time;
};
////////////////////////////////
// Build the full UI layout for one frame
static void build_ui(AppState *app) {
CLAY(CLAY_ID("Root"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top row: browser | main | right column
CLAY(CLAY_ID("TopRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
build_browser_panel(app->show_browser);
build_main_panel();
if (app->show_props || app->show_midi_devices) {
CLAY(CLAY_ID("RightColumn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.border = { .color = g_theme.border, .width = { .left = 1 } }
) {
build_properties_panel(app->show_props);
build_midi_panel(app->show_midi_devices, app->midi);
}
}
}
build_log_panel(app->show_log);
}
}
////////////////////////////////
// Render one frame: resize if needed, build UI, submit to GPU.
// Called from the main loop and from WM_SIZE during live resize.
static void do_frame(AppState *app) {
// Timing
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
F32 dt = (F32)(now.QuadPart - app->last_time.QuadPart) / (F32)app->freq.QuadPart;
app->last_time = now;
if (dt > 0.1f) dt = 0.1f;
// Resize
S32 w, h;
platform_get_size(app->window, &w, &h);
if (w != app->last_w || h != app->last_h) {
renderer_resize(app->renderer, w, h);
app->last_w = w;
app->last_h = h;
}
if (!renderer_begin_frame(app->renderer))
return;
// Gather input
input_gather(platform_get_native_handle(app->window));
// Build UI with Clay
ui_begin_frame(app->ui, (F32)w, (F32)h, g_input.mouse, g_input.mouse_down,
g_input.scroll_delta, dt);
build_ui(app);
Clay_RenderCommandArray render_commands = ui_end_frame(app->ui);
// Render
renderer_end_frame(app->renderer, render_commands);
}
////////////////////////////////
// Platform frame callback for live resize
static void frame_callback(void *user_data) {
do_frame((AppState *)user_data);
}
////////////////////////////////
// Entry point
int main(int argc, char **argv) {
std::cout << "Hello" << std::endl;
(void)argc;
(void)argv;
@@ -47,7 +360,7 @@ int main(int argc, char **argv) {
if (!window)
return 1;
int32_t w, h;
S32 w, h;
platform_get_size(window, &w, &h);
RendererDesc renderer_desc = {};
@@ -62,99 +375,48 @@ int main(int argc, char **argv) {
MidiEngine *midi = midi_create();
setup_theme();
// Initialize UI (Clay)
ui_init_theme();
UI_Context *ui = ui_create((F32)w, (F32)h);
ui_set_measure_text_fn(ui, renderer_measure_text, renderer);
init_text_configs();
setup_menus(window);
int32_t last_w = w, last_h = h;
bool show_demo = true;
bool show_browser = true;
bool show_props = true;
bool show_log = true;
bool show_midi_devices = true;
bool first_frame = true;
AppState app = {};
app.window = window;
app.renderer = renderer;
app.midi = midi;
app.ui = ui;
app.last_w = w;
app.last_h = h;
app.show_browser = 1;
app.show_props = 1;
app.show_log = 1;
app.show_midi_devices = 1;
QueryPerformanceFrequency(&app.freq);
QueryPerformanceCounter(&app.last_time);
platform_set_frame_callback(window, frame_callback, &app);
while (platform_poll_events(window)) {
// Menu commands
int32_t menu_cmd = platform_poll_menu_command(window);
switch (menu_cmd) {
case MENU_FILE_EXIT: platform_destroy_window(window); return 0;
case MENU_VIEW_BROWSER: show_browser = !show_browser; break;
case MENU_VIEW_PROPERTIES:show_props = !show_props; break;
case MENU_VIEW_LOG: show_log = !show_log; break;
case MENU_VIEW_DEMO: show_demo = !show_demo; break;
case MENU_VIEW_MIDI_DEVICES: show_midi_devices = !show_midi_devices; break;
case MENU_FILE_EXIT: goto exit_app;
case MENU_VIEW_BROWSER: app.show_browser = !app.show_browser; break;
case MENU_VIEW_PROPERTIES:app.show_props = !app.show_props; break;
case MENU_VIEW_LOG: app.show_log = !app.show_log; break;
case MENU_VIEW_MIDI_DEVICES: app.show_midi_devices = !app.show_midi_devices; break;
default: break;
}
platform_get_size(window, &w, &h);
if (w != last_w || h != last_h) {
renderer_resize(renderer, w, h);
last_w = w;
last_h = h;
}
if (!renderer_begin_frame(renderer))
continue;
// Full-window dockspace
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
ImGui::DockSpaceOverViewport(dockspace_id, ImGui::GetMainViewport());
if (first_frame) {
build_default_layout(dockspace_id);
first_frame = false;
}
// Left panel
if (show_browser) {
ImGui::Begin("Browser", &show_browser);
ImGui::Text("Instruments");
ImGui::Separator();
ImGui::End();
}
// Main content
ImGui::Begin("Main");
ImGui::Text("Main content area");
ImGui::End();
// Right panel
if (show_props) {
ImGui::Begin("Properties", &show_props);
ImGui::Text("Details");
ImGui::Separator();
ImGui::End();
}
// MIDI Devices panel
if (show_midi_devices) {
ImGui::Begin("MIDI Devices", &show_midi_devices);
if (ImGui::Button("Refresh"))
midi_refresh_devices(midi);
ImGui::Separator();
for (int32_t i = 0; i < midi_get_device_count(midi); i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
ImGui::Text("[%s] %s", dev->is_input ? "IN" : "OUT", dev->name);
}
if (midi_get_device_count(midi) == 0)
ImGui::TextDisabled("No MIDI devices found");
ImGui::End();
}
// Bottom panel
if (show_log) {
ImGui::Begin("Log", &show_log);
ImGui::Text("Output / Log");
ImGui::Separator();
ImGui::End();
}
// Demo window
if (show_demo)
ImGui::ShowDemoWindow(&show_demo);
renderer_end_frame(renderer);
do_frame(&app);
}
exit_app:
midi_destroy(midi);
ui_destroy(ui);
renderer_destroy(renderer);
platform_destroy_window(window);
return 0;

View File

@@ -22,11 +22,17 @@ struct PlatformMenu {
int32_t item_count;
};
// Called by the platform layer when the window needs a frame rendered
// (e.g., during a live resize). user_data is the pointer passed to
// platform_set_frame_callback.
typedef void (*PlatformFrameCallback)(void *user_data);
PlatformWindow *platform_create_window(PlatformWindowDesc *desc);
void platform_destroy_window(PlatformWindow *window);
bool platform_poll_events(PlatformWindow *window);
void platform_get_size(PlatformWindow *window, int32_t *w, int32_t *h);
void *platform_get_native_handle(PlatformWindow *window);
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu_count);
int32_t platform_poll_menu_command(PlatformWindow *window);

View File

@@ -6,29 +6,29 @@
#include <windows.h>
#include <malloc.h>
#include "imgui_impl_win32.h"
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
struct PlatformWindow {
HWND hwnd;
bool should_close;
int32_t width;
int32_t height;
int32_t pending_menu_cmd;
PlatformFrameCallback frame_callback;
void *frame_callback_user_data;
};
static PlatformWindow *g_current_window = nullptr;
static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam))
return true;
switch (msg) {
case WM_SIZE:
if (g_current_window && wparam != SIZE_MINIMIZED) {
g_current_window->width = (int32_t)LOWORD(lparam);
g_current_window->height = (int32_t)HIWORD(lparam);
// Render a frame during the modal resize loop so the UI
// stays responsive instead of showing a stretched image.
if (g_current_window->frame_callback) {
g_current_window->frame_callback(g_current_window->frame_callback_user_data);
}
}
return 0;
case WM_COMMAND:
@@ -133,6 +133,11 @@ void *platform_get_native_handle(PlatformWindow *window) {
return (void *)window->hwnd;
}
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
window->frame_callback = cb;
window->frame_callback_user_data = user_data;
}
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, int32_t menu_count) {
HMENU menu_bar = CreateMenu();

View File

@@ -4,6 +4,7 @@
#include <stdbool.h>
struct Renderer;
struct Clay_RenderCommandArray;
struct RendererDesc {
void *window_handle = nullptr;
@@ -15,5 +16,11 @@ struct RendererDesc {
Renderer *renderer_create(RendererDesc *desc);
void renderer_destroy(Renderer *renderer);
bool renderer_begin_frame(Renderer *renderer);
void renderer_end_frame(Renderer *renderer);
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray render_commands);
void renderer_resize(Renderer *renderer, int32_t width, int32_t height);
// Text measurement callback compatible with UI_MeasureTextFn
// Measures text of given length (NOT necessarily null-terminated) at font_size pixels.
// user_data should be the Renderer pointer.
struct Vec2F32;
Vec2F32 renderer_measure_text(const char *text, int32_t length, float font_size, void *user_data);

File diff suppressed because it is too large Load Diff

View File

@@ -1,104 +0,0 @@
#include "imgui.h"
static void setup_theme() {
ImGuiIO &io = ImGui::GetIO();
// Load Segoe UI from Windows system fonts
ImFontConfig font_cfg = {};
font_cfg.OversampleH = 2;
font_cfg.OversampleV = 1;
font_cfg.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 15.0f, &font_cfg);
// DAW-style dark theme
ImGuiStyle &s = ImGui::GetStyle();
// Geometry
s.WindowPadding = ImVec2(8, 8);
s.FramePadding = ImVec2(6, 4);
s.ItemSpacing = ImVec2(8, 4);
s.ItemInnerSpacing = ImVec2(4, 4);
s.ScrollbarSize = 12.0f;
s.GrabMinSize = 8.0f;
s.WindowBorderSize = 1.0f;
s.FrameBorderSize = 0.0f;
s.TabBorderSize = 0.0f;
s.WindowRounding = 2.0f;
s.FrameRounding = 2.0f;
s.GrabRounding = 2.0f;
s.TabRounding = 2.0f;
s.ScrollbarRounding = 2.0f;
ImVec4 *c = s.Colors;
// Backgrounds
c[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.13f, 1.00f);
c[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.13f, 1.00f);
c[ImGuiCol_PopupBg] = ImVec4(0.15f, 0.15f, 0.16f, 1.00f);
// Borders
c[ImGuiCol_Border] = ImVec4(0.22f, 0.22f, 0.24f, 1.00f);
c[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// Text
c[ImGuiCol_Text] = ImVec4(0.88f, 0.88f, 0.88f, 1.00f);
c[ImGuiCol_TextDisabled] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
// Headers (collapsing headers, menu bar items)
c[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.22f, 1.00f);
c[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.28f, 0.30f, 1.00f);
c[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.24f, 0.26f, 1.00f);
// Buttons
c[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.24f, 1.00f);
c[ImGuiCol_ButtonHovered] = ImVec4(0.30f, 0.30f, 0.33f, 1.00f);
c[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f);
// Frame backgrounds (inputs, checkboxes, sliders)
c[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.17f, 1.00f);
c[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.20f, 0.22f, 1.00f);
c[ImGuiCol_FrameBgActive] = ImVec4(0.18f, 0.18f, 0.20f, 1.00f);
// Tabs
c[ImGuiCol_Tab] = ImVec4(0.16f, 0.16f, 0.17f, 1.00f);
c[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.28f, 0.30f, 1.00f);
c[ImGuiCol_TabSelected] = ImVec4(0.20f, 0.20f, 0.22f, 1.00f);
c[ImGuiCol_TabSelectedOverline] = ImVec4(0.34f, 0.54f, 0.69f, 1.00f);
c[ImGuiCol_TabDimmed] = ImVec4(0.12f, 0.12f, 0.13f, 1.00f);
c[ImGuiCol_TabDimmedSelected] = ImVec4(0.16f, 0.16f, 0.17f, 1.00f);
// Title bar
c[ImGuiCol_TitleBg] = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
c[ImGuiCol_TitleBgActive] = ImVec4(0.13f, 0.13f, 0.14f, 1.00f);
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
// Scrollbar
c[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.24f, 0.26f, 1.00f);
c[ImGuiCol_ScrollbarGrabHovered]= ImVec4(0.30f, 0.30f, 0.33f, 1.00f);
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.34f, 0.34f, 0.37f, 1.00f);
// Slider grab
c[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.54f, 0.69f, 1.00f);
c[ImGuiCol_SliderGrabActive] = ImVec4(0.40f, 0.60f, 0.75f, 1.00f);
// Checkmark, accent
c[ImGuiCol_CheckMark] = ImVec4(0.34f, 0.54f, 0.69f, 1.00f);
// Separator
c[ImGuiCol_Separator] = ImVec4(0.22f, 0.22f, 0.24f, 1.00f);
c[ImGuiCol_SeparatorHovered] = ImVec4(0.34f, 0.54f, 0.69f, 1.00f);
c[ImGuiCol_SeparatorActive] = ImVec4(0.34f, 0.54f, 0.69f, 1.00f);
// Resize grip
c[ImGuiCol_ResizeGrip] = ImVec4(0.22f, 0.22f, 0.24f, 0.50f);
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.34f, 0.54f, 0.69f, 0.67f);
c[ImGuiCol_ResizeGripActive] = ImVec4(0.34f, 0.54f, 0.69f, 0.95f);
// Docking
c[ImGuiCol_DockingPreview] = ImVec4(0.34f, 0.54f, 0.69f, 0.70f);
c[ImGuiCol_DockingEmptyBg] = ImVec4(0.10f, 0.10f, 0.11f, 1.00f);
// Menu bar
c[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.15f, 1.00f);
}

107
src/ui/ui_core.cpp Normal file
View File

@@ -0,0 +1,107 @@
// ui_core.cpp - 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>
#define CLAY_IMPLEMENTATION
#include "clay.h"
#include "ui/ui_core.h"
////////////////////////////////
// Theme global
UI_Theme g_theme = {};
void ui_init_theme() {
g_theme.bg_dark = Clay_Color{ 30, 30, 33, 255};
g_theme.bg_medium = Clay_Color{ 41, 41, 43, 255};
g_theme.bg_light = Clay_Color{ 51, 51, 56, 255};
g_theme.bg_lighter = Clay_Color{ 56, 56, 61, 255};
g_theme.border = Clay_Color{ 56, 56, 61, 255};
g_theme.text = Clay_Color{224, 224, 224, 255};
g_theme.text_dim = Clay_Color{112, 112, 112, 255};
g_theme.accent = Clay_Color{ 87, 138, 176, 255};
g_theme.accent_hover = Clay_Color{102, 153, 191, 255};
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};
}
////////////////////////////////
// Clay error handler
static void clay_error_handler(Clay_ErrorData error) {
char buf[512];
int len = error.errorText.length < 511 ? error.errorText.length : 511;
memcpy(buf, error.errorText.chars, len);
buf[len] = '\0';
fprintf(stderr, "[Clay Error] %s\n", buf);
}
////////////////////////////////
// Clay text measurement bridge
// Clay calls this; we forward to the app's UI_MeasureTextFn
static UI_Context *g_measure_ctx = nullptr;
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, (float)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};
}
////////////////////////////////
// Lifecycle
UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
UI_Context *ctx = (UI_Context *)calloc(1, sizeof(UI_Context));
uint32_t min_memory = Clay_MinMemorySize();
ctx->clay_memory = malloc(min_memory);
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
Clay_ErrorHandler err_handler = {};
err_handler.errorHandlerFunction = clay_error_handler;
err_handler.userData = ctx;
ctx->clay_ctx = Clay_Initialize(clay_arena,
Clay_Dimensions{viewport_w, viewport_h},
err_handler);
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
return ctx;
}
void ui_destroy(UI_Context *ctx) {
if (!ctx) return;
free(ctx->clay_memory);
free(ctx);
}
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
Vec2F32 mouse_pos, B32 mouse_down,
Vec2F32 scroll_delta, F32 dt)
{
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(true, Clay_Vector2{scroll_delta.x, scroll_delta.y}, dt);
Clay_BeginLayout();
}
Clay_RenderCommandArray ui_end_frame(UI_Context *ctx) {
(void)ctx;
return Clay_EndLayout();
}
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data) {
ctx->measure_text_fn = fn;
ctx->measure_text_user_data = user_data;
}

65
src/ui/ui_core.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
// ui_core.h - Thin wrapper around Clay (https://github.com/nicbarker/clay)
// Provides Clay initialization, per-frame lifecycle, and text measurement bridging.
#include "base/base_inc.h"
#include "clay.h"
////////////////////////////////
// Text measurement callback (provided by renderer)
// This is our app-level callback; we adapt it to Clay's signature internally.
typedef Vec2F32 (*UI_MeasureTextFn)(const char *text, S32 length, F32 font_size, void *user_data);
////////////////////////////////
// UI Context
struct UI_Context {
Clay_Context *clay_ctx;
void *clay_memory;
// Text measurement
UI_MeasureTextFn measure_text_fn;
void *measure_text_user_data;
};
////////////////////////////////
// Lifecycle
UI_Context *ui_create(F32 viewport_w, F32 viewport_h);
void ui_destroy(UI_Context *ctx);
// Call each frame before declaring layout
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
Vec2F32 mouse_pos, B32 mouse_down,
Vec2F32 scroll_delta, F32 dt);
// Call after layout is declared; returns Clay's render command array
Clay_RenderCommandArray ui_end_frame(UI_Context *ctx);
////////////////////////////////
// Text measurement
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data);
////////////////////////////////
// Theme colors (convenience - 0-255 Clay_Color)
struct UI_Theme {
Clay_Color bg_dark;
Clay_Color bg_medium;
Clay_Color bg_light;
Clay_Color bg_lighter;
Clay_Color border;
Clay_Color text;
Clay_Color text_dim;
Clay_Color accent;
Clay_Color accent_hover;
Clay_Color title_bar;
Clay_Color scrollbar_bg;
Clay_Color scrollbar_grab;
};
extern UI_Theme g_theme;
void ui_init_theme();

2
src/ui/ui_widgets.cpp Normal file
View File

@@ -0,0 +1,2 @@
// ui_widgets.cpp - Removed: Clay handles all layout and widgets directly.
// This file is kept empty for the unity build include order.

3
src/ui/ui_widgets.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
// ui_widgets.h - Removed: Clay handles all layout and widgets directly.
// This file is kept empty for the unity build include order.