Reapply "Death to C++"

This reverts commit 13f856cfbc.
This commit is contained in:
2026-03-12 16:30:04 -04:00
parent d5d2f6db8e
commit 8695f69282
80 changed files with 5671 additions and 39268 deletions

View File

@@ -2,12 +2,12 @@
#include "base/base_core.h"
struct AudioEngine;
typedef struct AudioEngine AudioEngine;
struct AudioDeviceInfo {
typedef struct AudioDeviceInfo {
char name[128];
S32 id; // index into engine's device list
};
} AudioDeviceInfo;
AudioEngine *audio_create(void *hwnd);
void audio_destroy(AudioEngine *engine);

View File

@@ -30,7 +30,7 @@ enum {
ASE_NoMemory = -994,
};
enum ASIOSampleType {
typedef enum ASIOSampleType {
ASIOSTInt16MSB = 0,
ASIOSTInt24MSB = 1,
ASIOSTInt32MSB = 2,
@@ -52,59 +52,59 @@ enum ASIOSampleType {
ASIOSTInt32LSB18 = 25,
ASIOSTInt32LSB20 = 26,
ASIOSTInt32LSB24 = 27,
};
} ASIOSampleType;
struct ASIOClockSource {
typedef struct ASIOClockSource {
long index;
long channel;
long group;
ASIOBool isCurrentSource;
char name[32];
};
} ASIOClockSource;
struct ASIOChannelInfo {
typedef struct ASIOChannelInfo {
long channel;
ASIOBool isInput;
ASIOBool isActive;
long channelGroup;
ASIOSampleType type;
char name[32];
};
} ASIOChannelInfo;
struct ASIOBufferInfo {
typedef struct ASIOBufferInfo {
ASIOBool isInput;
long channelNum;
void *buffers[2]; // F64 buffer
};
} ASIOBufferInfo;
struct ASIOTimeCode {
typedef struct ASIOTimeCode {
F64 speed;
ASIOSamples timeCodeSamples;
unsigned long flags;
char future[64];
};
} ASIOTimeCode;
struct AsioTimeInfo {
typedef struct AsioTimeInfo {
F64 speed;
ASIOTimeStamp systemTime;
ASIOSamples samplePosition;
F64 sampleRate;
unsigned long flags;
char reserved[12];
};
} AsioTimeInfo;
struct ASIOTime {
typedef struct ASIOTime {
long reserved[4];
AsioTimeInfo timeInfo;
ASIOTimeCode timeCode;
};
} ASIOTime;
struct ASIOCallbacks {
typedef struct ASIOCallbacks {
void (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess);
void (*sampleRateDidChange)(F64 sRate);
long (*asioMessage)(long selector, long value, void *message, F64 *opt);
ASIOTime *(*bufferSwitchTimeInfo)(ASIOTime *params, long doubleBufferIndex, ASIOBool directProcess);
};
} ASIOCallbacks;
// ASIO message selectors
enum {
@@ -120,41 +120,49 @@ enum {
};
////////////////////////////////
// IASIO COM interface
// IASIO COM interface (C vtable)
// Standard ASIO vtable — inherits IUnknown
class IASIO : public IUnknown {
public:
virtual ASIOBool init(void *sysHandle) = 0;
virtual void getDriverName(char *name) = 0;
virtual long getDriverVersion() = 0;
virtual void getErrorMessage(char *string) = 0;
virtual ASIOError start() = 0;
virtual ASIOError stop() = 0;
virtual ASIOError getChannels(long *numInputChannels, long *numOutputChannels) = 0;
virtual ASIOError getLatencies(long *inputLatency, long *outputLatency) = 0;
virtual ASIOError getBufferSize(long *minSize, long *maxSize, long *preferredSize, long *granularity) = 0;
virtual ASIOError canSampleRate(F64 sampleRate) = 0;
virtual ASIOError getSampleRate(F64 *sampleRate) = 0;
virtual ASIOError setSampleRate(F64 sampleRate) = 0;
virtual ASIOError getClockSources(ASIOClockSource *clocks, long *numSources) = 0;
virtual ASIOError setClockSource(long reference) = 0;
virtual ASIOError getSamplePosition(ASIOSamples *sPos, ASIOTimeStamp *tStamp) = 0;
virtual ASIOError getChannelInfo(ASIOChannelInfo *info) = 0;
virtual ASIOError createBuffers(ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks) = 0;
virtual ASIOError disposeBuffers() = 0;
virtual ASIOError controlPanel() = 0;
virtual ASIOError future(long selector, void *opt) = 0;
virtual ASIOError outputReady() = 0;
};
typedef struct IASIOVtbl {
// IUnknown
HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *this_, REFIID riid, void **ppvObject);
ULONG (STDMETHODCALLTYPE *AddRef)(void *this_);
ULONG (STDMETHODCALLTYPE *Release)(void *this_);
// IASIO
ASIOBool (*init)(void *this_, void *sysHandle);
void (*getDriverName)(void *this_, char *name);
long (*getDriverVersion)(void *this_);
void (*getErrorMessage)(void *this_, char *string);
ASIOError (*start)(void *this_);
ASIOError (*stop)(void *this_);
ASIOError (*getChannels)(void *this_, long *numInputChannels, long *numOutputChannels);
ASIOError (*getLatencies)(void *this_, long *inputLatency, long *outputLatency);
ASIOError (*getBufferSize)(void *this_, long *minSize, long *maxSize, long *preferredSize, long *granularity);
ASIOError (*canSampleRate)(void *this_, F64 sampleRate);
ASIOError (*getSampleRate)(void *this_, F64 *sampleRate);
ASIOError (*setSampleRate)(void *this_, F64 sampleRate);
ASIOError (*getClockSources)(void *this_, ASIOClockSource *clocks, long *numSources);
ASIOError (*setClockSource)(void *this_, long reference);
ASIOError (*getSamplePosition)(void *this_, ASIOSamples *sPos, ASIOTimeStamp *tStamp);
ASIOError (*getChannelInfo)(void *this_, ASIOChannelInfo *info);
ASIOError (*createBuffers)(void *this_, ASIOBufferInfo *bufferInfos, long numChannels, long bufferSize, ASIOCallbacks *callbacks);
ASIOError (*disposeBuffers)(void *this_);
ASIOError (*controlPanel)(void *this_);
ASIOError (*future)(void *this_, long selector, void *opt);
ASIOError (*outputReady)(void *this_);
} IASIOVtbl;
typedef struct IASIO {
IASIOVtbl *lpVtbl;
} IASIO;
////////////////////////////////
// Internal state
struct AsioDriverInfo {
typedef struct AsioDriverInfo {
char name[128];
CLSID clsid;
};
} AsioDriverInfo;
struct AudioEngine {
void *hwnd; // HWND for ASIO init
@@ -186,7 +194,7 @@ struct AudioEngine {
////////////////////////////////
// Global engine pointer for ASIO callbacks (standard pattern — callbacks have no user-data param)
static AudioEngine *g_audio_engine = nullptr;
static AudioEngine *g_audio_engine = NULL;
////////////////////////////////
// Sample writing helper
@@ -351,7 +359,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
for (DWORD i = 0; engine->device_count < AUDIO_MAX_DEVICES; i++) {
subkey_name_len = sizeof(subkey_name);
if (RegEnumKeyExA(asio_key, i, subkey_name, &subkey_name_len, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
if (RegEnumKeyExA(asio_key, i, subkey_name, &subkey_name_len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
HKEY driver_key;
@@ -359,10 +367,10 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
continue;
// Read CLSID string
char clsid_str[64] = {};
char clsid_str[64] = {0};
DWORD clsid_len = sizeof(clsid_str);
DWORD type = 0;
if (RegQueryValueExA(driver_key, "CLSID", nullptr, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
if (RegQueryValueExA(driver_key, "CLSID", NULL, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
type != REG_SZ) {
RegCloseKey(driver_key);
continue;
@@ -393,21 +401,20 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
// Public API
AudioEngine *audio_create(void *hwnd) {
AudioEngine *engine = new AudioEngine();
memset(engine, 0, sizeof(*engine));
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
engine->hwnd = hwnd;
engine->active_device_index = -1;
g_audio_engine = engine;
CoInitialize(nullptr);
CoInitialize(NULL);
enumerate_asio_drivers(engine);
return engine;
}
void audio_destroy(AudioEngine *engine) {
audio_close_device(engine);
if (g_audio_engine == engine) g_audio_engine = nullptr;
delete engine;
if (g_audio_engine == engine) g_audio_engine = NULL;
free(engine);
}
void audio_refresh_devices(AudioEngine *engine) {
@@ -421,7 +428,7 @@ S32 audio_get_device_count(AudioEngine *engine) {
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
if (index < 0 || index >= engine->device_count)
return nullptr;
return NULL;
return &engine->devices[index];
}
@@ -433,52 +440,52 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
return false;
// Create COM instance of the ASIO driver
IASIO *driver = nullptr;
HRESULT hr = CoCreateInstance(engine->drivers[index].clsid,
nullptr, CLSCTX_INPROC_SERVER,
engine->drivers[index].clsid,
IASIO *driver = NULL;
HRESULT hr = CoCreateInstance(&engine->drivers[index].clsid,
NULL, CLSCTX_INPROC_SERVER,
&engine->drivers[index].clsid,
(void **)&driver);
if (FAILED(hr) || !driver)
return false;
// Initialize the driver
if (!driver->init(engine->hwnd)) {
driver->Release();
if (!driver->lpVtbl->init(driver, engine->hwnd)) {
driver->lpVtbl->Release(driver);
return false;
}
// Query channel counts
long num_in = 0, num_out = 0;
if (driver->getChannels(&num_in, &num_out) != ASE_OK || num_out <= 0) {
driver->Release();
if (driver->lpVtbl->getChannels(driver, &num_in, &num_out) != ASE_OK || num_out <= 0) {
driver->lpVtbl->Release(driver);
return false;
}
// Query buffer size
long min_size = 0, max_size = 0, preferred_size = 0, granularity = 0;
if (driver->getBufferSize(&min_size, &max_size, &preferred_size, &granularity) != ASE_OK) {
driver->Release();
if (driver->lpVtbl->getBufferSize(driver, &min_size, &max_size, &preferred_size, &granularity) != ASE_OK) {
driver->lpVtbl->Release(driver);
return false;
}
// Query sample rate
F64 sample_rate = 0;
if (driver->getSampleRate(&sample_rate) != ASE_OK || sample_rate <= 0) {
if (driver->lpVtbl->getSampleRate(driver, &sample_rate) != ASE_OK || sample_rate <= 0) {
// Try setting a common rate
if (driver->setSampleRate(44100.0) == ASE_OK) {
if (driver->lpVtbl->setSampleRate(driver, 44100.0) == ASE_OK) {
sample_rate = 44100.0;
} else {
driver->Release();
driver->lpVtbl->Release(driver);
return false;
}
}
// Query output channel sample type
ASIOChannelInfo chan_info = {};
ASIOChannelInfo chan_info = {0};
chan_info.channel = 0;
chan_info.isInput = 0;
if (driver->getChannelInfo(&chan_info) != ASE_OK) {
driver->Release();
if (driver->lpVtbl->getChannelInfo(driver, &chan_info) != ASE_OK) {
driver->lpVtbl->Release(driver);
return false;
}
@@ -490,8 +497,8 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
for (long ch = 0; ch < num_out; ch++) {
engine->buffer_infos[ch].isInput = 0;
engine->buffer_infos[ch].channelNum = ch;
engine->buffer_infos[ch].buffers[0] = nullptr;
engine->buffer_infos[ch].buffers[1] = nullptr;
engine->buffer_infos[ch].buffers[0] = NULL;
engine->buffer_infos[ch].buffers[1] = NULL;
}
// Setup callbacks
@@ -501,8 +508,8 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
engine->callbacks.bufferSwitchTimeInfo = asio_buffer_switch_time_info;
// Create buffers
if (driver->createBuffers(engine->buffer_infos, num_out, preferred_size, &engine->callbacks) != ASE_OK) {
driver->Release();
if (driver->lpVtbl->createBuffers(driver, engine->buffer_infos, num_out, preferred_size, &engine->callbacks) != ASE_OK) {
driver->lpVtbl->Release(driver);
return false;
}
@@ -519,16 +526,16 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
engine->test_tone_phase = 0.0;
// Start the driver
if (driver->start() != ASE_OK) {
driver->disposeBuffers();
driver->Release();
engine->driver = nullptr;
if (driver->lpVtbl->start(driver) != ASE_OK) {
driver->lpVtbl->disposeBuffers(driver);
driver->lpVtbl->Release(driver);
engine->driver = NULL;
engine->active_device_index = -1;
return false;
}
// Notify driver that output is ready (optional, some drivers benefit)
driver->outputReady();
driver->lpVtbl->outputReady(driver);
return true;
}
@@ -539,10 +546,10 @@ void audio_close_device(AudioEngine *engine) {
InterlockedExchange(&engine->test_tone_active, 0);
InterlockedExchange(&engine->test_tone_samples_remaining, 0);
engine->driver->stop();
engine->driver->disposeBuffers();
engine->driver->Release();
engine->driver = nullptr;
engine->driver->lpVtbl->stop(engine->driver);
engine->driver->lpVtbl->disposeBuffers(engine->driver);
engine->driver->lpVtbl->Release(engine->driver);
engine->driver = NULL;
engine->active_device_index = -1;
engine->test_tone_phase = 0.0;
}

View File

@@ -13,9 +13,9 @@
#define AUDIO_TEST_TONE_SEC 2.0
#define AUDIO_PI 3.14159265358979323846
struct CoreAudioDeviceInfo {
typedef struct CoreAudioDeviceInfo {
AudioDeviceID device_id;
};
} CoreAudioDeviceInfo;
struct AudioEngine {
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
@@ -37,7 +37,7 @@ struct AudioEngine {
////////////////////////////////
// Audio render callback
static AudioEngine *g_audio_engine = nullptr;
static AudioEngine *g_audio_engine = NULL;
static OSStatus audio_render_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
@@ -110,14 +110,14 @@ static void enumerate_output_devices(AudioEngine *engine) {
};
UInt32 data_size = 0;
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, nullptr, &data_size) != noErr)
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size) != noErr)
return;
S32 device_count = (S32)(data_size / sizeof(AudioDeviceID));
if (device_count <= 0) return;
AudioDeviceID *device_ids = (AudioDeviceID *)malloc(data_size);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, nullptr, &data_size, device_ids) != noErr) {
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, device_ids) != noErr) {
free(device_ids);
return;
}
@@ -131,11 +131,11 @@ static void enumerate_output_devices(AudioEngine *engine) {
};
UInt32 stream_size = 0;
if (AudioObjectGetPropertyDataSize(device_ids[i], &stream_prop, 0, nullptr, &stream_size) != noErr)
if (AudioObjectGetPropertyDataSize(device_ids[i], &stream_prop, 0, NULL, &stream_size) != noErr)
continue;
AudioBufferList *buf_list = (AudioBufferList *)malloc(stream_size);
if (AudioObjectGetPropertyData(device_ids[i], &stream_prop, 0, nullptr, &stream_size, buf_list) != noErr) {
if (AudioObjectGetPropertyData(device_ids[i], &stream_prop, 0, NULL, &stream_size, buf_list) != noErr) {
free(buf_list);
continue;
}
@@ -154,9 +154,9 @@ static void enumerate_output_devices(AudioEngine *engine) {
kAudioObjectPropertyElementMain
};
CFStringRef name_ref = nullptr;
CFStringRef name_ref = NULL;
UInt32 name_size = sizeof(name_ref);
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, nullptr, &name_size, &name_ref) != noErr)
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, NULL, &name_size, &name_ref) != noErr)
continue;
S32 idx = engine->device_count++;
@@ -176,8 +176,7 @@ static void enumerate_output_devices(AudioEngine *engine) {
AudioEngine *audio_create(void *hwnd) {
(void)hwnd;
AudioEngine *engine = new AudioEngine();
memset(engine, 0, sizeof(*engine));
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
engine->active_device_index = -1;
atomic_store(&engine->test_tone_active, 0);
atomic_store(&engine->test_tone_samples_remaining, 0);
@@ -189,8 +188,8 @@ AudioEngine *audio_create(void *hwnd) {
void audio_destroy(AudioEngine *engine) {
audio_close_device(engine);
if (g_audio_engine == engine) g_audio_engine = nullptr;
delete engine;
if (g_audio_engine == engine) g_audio_engine = NULL;
free(engine);
}
void audio_refresh_devices(AudioEngine *engine) {
@@ -203,7 +202,7 @@ S32 audio_get_device_count(AudioEngine *engine) {
}
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
if (index < 0 || index >= engine->device_count) return nullptr;
if (index < 0 || index >= engine->device_count) return NULL;
return &engine->devices[index];
}
@@ -218,7 +217,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
if (NewAUGraph(&engine->graph) != noErr) return false;
// Add HAL output node
AudioComponentDescription output_desc = {};
AudioComponentDescription output_desc = {0};
output_desc.componentType = kAudioUnitType_Output;
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
@@ -226,19 +225,19 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
AUNode output_node;
if (AUGraphAddNode(engine->graph, &output_desc, &output_node) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
if (AUGraphOpen(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
if (AUGraphNodeInfo(engine->graph, output_node, nullptr, &engine->output_unit) != noErr) {
if (AUGraphNodeInfo(engine->graph, output_node, NULL, &engine->output_unit) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
@@ -246,7 +245,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
if (AudioUnitSetProperty(engine->output_unit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
@@ -258,11 +257,11 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
};
Float64 sample_rate = 44100.0;
UInt32 rate_size = sizeof(sample_rate);
AudioObjectGetPropertyData(device_id, &rate_prop, 0, nullptr, &rate_size, &sample_rate);
AudioObjectGetPropertyData(device_id, &rate_prop, 0, NULL, &rate_size, &sample_rate);
engine->sample_rate = sample_rate;
// Set stream format: Float32, non-interleaved
AudioStreamBasicDescription fmt = {};
AudioStreamBasicDescription fmt = {0};
fmt.mSampleRate = sample_rate;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
@@ -277,27 +276,27 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
// Set render callback
AURenderCallbackStruct cb = {};
AURenderCallbackStruct cb = {0};
cb.inputProc = audio_render_callback;
cb.inputProcRefCon = engine;
if (AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
// Initialize and start
if (AUGraphInitialize(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
if (AUGraphStart(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->graph = NULL;
return false;
}
@@ -318,8 +317,8 @@ void audio_close_device(AudioEngine *engine) {
AUGraphStop(engine->graph);
AUGraphUninitialize(engine->graph);
DisposeAUGraph(engine->graph);
engine->graph = nullptr;
engine->output_unit = nullptr;
engine->graph = NULL;
engine->output_unit = NULL;
engine->active_device_index = -1;
engine->test_tone_phase = 0.0;
}

View File

@@ -3,7 +3,7 @@
Arena *arena_alloc(U64 cap) {
U8 *mem = (U8 *)malloc(sizeof(Arena) + cap);
if (!mem) return nullptr;
if (!mem) return NULL;
Arena *arena = (Arena *)mem;
arena->base = mem + sizeof(Arena);
arena->pos = 0;
@@ -19,7 +19,7 @@ void *arena_push(Arena *arena, U64 size) {
U64 aligned = AlignPow2(size, 8);
if (arena->pos + aligned > arena->cap) {
Assert(!"Arena overflow");
return nullptr;
return NULL;
}
void *result = arena->base + arena->pos;
arena->pos += aligned;
@@ -31,7 +31,7 @@ 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;
return NULL;
}
void *result = arena->base + arena->pos;
arena->pos += aligned;

View File

@@ -8,17 +8,17 @@
////////////////////////////////
// Arena type
struct Arena {
typedef struct Arena {
U8 *base;
U64 pos;
U64 cap;
};
} Arena;
// Temporary scope (save/restore position)
struct Temp {
typedef struct Temp {
Arena *arena;
U64 pos;
};
} Temp;
////////////////////////////////
// Arena functions
@@ -34,8 +34,8 @@ 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); }
static inline Temp temp_begin(Arena *arena) { Temp t = {arena, arena->pos}; return t; }
static inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
////////////////////////////////
// Push helper macros

View File

@@ -7,6 +7,7 @@
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
////////////////////////////////
// Codebase keywords
@@ -17,7 +18,7 @@
#endif
#define local_persist static
#define trvke true
#define trvke 1
////////////////////////////////
// Base types

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

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

View File

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

View File

@@ -7,60 +7,60 @@
////////////////////////////////
// Axis enum
enum Axis2 {
typedef enum Axis2 {
Axis2_X = 0,
Axis2_Y = 1,
Axis2_COUNT,
};
} Axis2;
enum Side {
typedef enum Side {
Side_Min = 0,
Side_Max = 1,
Side_COUNT,
};
} Side;
enum Corner {
typedef enum Corner {
Corner_00 = 0, // top-left
Corner_01 = 1, // top-right
Corner_10 = 2, // bottom-left
Corner_11 = 3, // bottom-right
Corner_COUNT,
};
} Corner;
////////////////////////////////
// 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; };
typedef struct Vec2F32 { F32 x, y; } Vec2F32;
typedef struct Vec2S32 { S32 x, y; } Vec2S32;
typedef struct Vec3F32 { F32 x, y, z; } Vec3F32;
typedef struct Vec4F32 { F32 x, y, z, w; } Vec4F32;
////////////////////////////////
// Range types
struct Rng1F32 { F32 min, max; };
struct Rng1S64 { S64 min, max; };
struct Rng2F32 { Vec2F32 p0, p1; };
typedef struct Rng1F32 { F32 min, max; } Rng1F32;
typedef struct Rng1S64 { S64 min, max; } Rng1S64;
typedef struct Rng2F32 { Vec2F32 p0, p1; } Rng2F32;
////////////////////////////////
// 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}}; }
static inline Vec2F32 v2f32(F32 x, F32 y) { return (Vec2F32){x, y}; }
static inline Vec2S32 v2s32(S32 x, S32 y) { return (Vec2S32){x, y}; }
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return (Vec3F32){x, y, z}; }
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return (Vec4F32){x, y, z, w}; }
static inline Rng1F32 rng1f32(F32 min, F32 max) { return (Rng1F32){min, max}; }
static inline Rng1S64 rng1s64(S64 min, S64 max) { return (Rng1S64){min, max}; }
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return (Rng2F32){p0, p1}; }
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return (Rng2F32){{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}; }
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x + b.x, a.y + b.y}; }
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x - b.x, a.y - b.y}; }
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return (Vec2F32){a.x * b.x, a.y * b.y}; }
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return (Vec2F32){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; }
@@ -71,10 +71,10 @@ static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 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 add_4f32(Vec4F32 a, Vec4F32 b) { return (Vec4F32){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 (Vec4F32){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};
return (Vec4F32){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};
}
////////////////////////////////
@@ -82,19 +82,19 @@ static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
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 Vec2F32 rng2f32_dim(Rng2F32 r) { return (Vec2F32){r.p1.x - r.p0.x, r.p1.y - r.p0.y}; }
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return (Vec2F32){(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}};
return (Rng2F32){{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}};
return (Rng2F32){{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)},
return (Rng2F32){{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)}};
}

View File

@@ -5,22 +5,24 @@ Str8 str8_pushf(Arena *arena, const char *fmt, ...) {
va_list args, args2;
va_start(args, fmt);
va_copy(args2, args);
S32 len = vsnprintf(nullptr, 0, fmt, args);
S32 len = vsnprintf(NULL, 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 r = {buf, (U64)len};
return r;
}
Str8 str8_push_copy(Arena *arena, Str8 s) {
if (s.size == 0 || !s.str) return {nullptr, 0};
if (s.size == 0 || !s.str) { Str8 r = {NULL, 0}; return r; }
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};
Str8 r = {buf, s.size};
return r;
}
void str8_list_push(Arena *arena, Str8List *list, Str8 s) {

View File

@@ -7,38 +7,38 @@
////////////////////////////////
// String types
struct Str8 {
typedef struct Str8 {
const char *str;
U64 size;
};
} Str8;
struct Str8Node {
Str8Node *next;
Str8 string;
};
typedef struct Str8Node {
struct Str8Node *next;
Str8 string;
} Str8Node;
struct Str8List {
typedef struct Str8List {
Str8Node *first;
Str8Node *last;
U64 count;
U64 total_size;
};
} Str8List;
////////////////////////////////
// Forward declaration for Arena
struct Arena;
typedef struct Arena 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 Str8 str8(const char *s, U64 len) { Str8 r = {s, len}; return r; }
static inline Str8 str8_cstr(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
static inline Str8 str8_lit(const char *s) { Str8 r = {s, s ? (U64)strlen(s) : 0}; return r; }
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; }
static inline B32 str8_is_empty(Str8 s) { return s.size == 0 || s.str == NULL; }
////////////////////////////////
// String operations (require arena)

View File

@@ -15,25 +15,25 @@
#include "ui/ui_popups.h"
#include "ui/ui_piano.h"
// [cpp]
#include "base/base_inc.cpp"
#include "ui/ui_core.cpp"
#include "ui/ui_icons.cpp"
#include "ui/ui_widgets.cpp"
#include "ui/ui_popups.cpp"
#include "ui/ui_piano.cpp"
// [c]
#include "base/base_inc.c"
#include "ui/ui_core.c"
#include "ui/ui_icons.c"
#include "ui/ui_widgets.c"
#include "ui/ui_popups.c"
#include "ui/ui_piano.c"
#ifdef __APPLE__
#include "platform/platform_macos.mm"
#include "renderer/renderer_metal.mm"
#include "midi/midi_coremidi.cpp"
#include "audio/audio_coreaudio.cpp"
#include "platform/platform_macos.m"
#include "renderer/renderer_metal.m"
#include "midi/midi_coremidi.c"
#include "audio/audio_coreaudio.c"
#else
#include "platform/platform_win32.cpp"
#include "renderer/renderer_vulkan.cpp"
#include "midi/midi_win32.cpp"
#include "audio/audio_asio.cpp"
#include "platform/platform_win32.c"
#include "renderer/renderer_vulkan.c"
#include "midi/midi_win32.c"
#include "audio/audio_asio.c"
#endif
#include "menus.cpp"
#include "menus.c"
////////////////////////////////
// Clay text config helpers
@@ -43,17 +43,17 @@ static Clay_TextElementConfig g_text_config_title;
static Clay_TextElementConfig g_text_config_dim;
static void init_text_configs() {
g_text_config_normal = {};
memset(&g_text_config_normal, 0, sizeof(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 = {};
memset(&g_text_config_title, 0, sizeof(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 = {};
memset(&g_text_config_dim, 0, sizeof(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;
@@ -62,7 +62,7 @@ static void init_text_configs() {
////////////////////////////////
// App state — all mutable state the frame function needs
struct AppState {
typedef struct AppState {
PlatformWindow *window;
Renderer *renderer;
MidiEngine *midi;
@@ -164,7 +164,7 @@ struct AppState {
S32 patch_tab; // 0 = Matrix, 1 = Graph
B32 patch_matrix[32][33]; // internal routing: 32 inputs x (Master + 32 outputs)
B32 hw_matrix[32][32]; // hardware routing: 32 channel outputs x 32 hw outputs
};
} AppState;
////////////////////////////////
////////////////////////////////
@@ -466,7 +466,7 @@ static void build_main_panel(AppState *app) {
// Thumb color: highlight on hover or drag
Clay_Color thumb_color = g_theme.scrollbar_grab;
if (app->scrollbar_dragging || thumb_hovered) {
thumb_color = Clay_Color{
thumb_color = (Clay_Color){
(F32)Min((S32)thumb_color.r + 30, 255),
(F32)Min((S32)thumb_color.g + 30, 255),
(F32)Min((S32)thumb_color.b + 30, 255),
@@ -582,8 +582,8 @@ static void build_right_panel(AppState *app) {
static char note_bufs[64][8];
static char vel_bufs[64][8];
static Clay_TextElementConfig box_text_config;
box_text_config = {};
box_text_config.textColor = Clay_Color{255, 255, 255, 255};
memset(&box_text_config, 0, sizeof(box_text_config));
box_text_config.textColor = (Clay_Color){255, 255, 255, 255};
box_text_config.fontSize = FONT_SIZE_SMALL;
box_text_config.wrapMode = CLAY_TEXT_WRAP_NONE;
@@ -602,9 +602,9 @@ static void build_right_panel(AppState *app) {
if (dev->active) {
box_color = piano_velocity_color(dev->velocity);
} else if (dev->releasing) {
box_color = Clay_Color{255, 255, 255, 255};
box_color = (Clay_Color){255, 255, 255, 255};
} else {
box_color = Clay_Color{60, 60, 60, 255};
box_color = (Clay_Color){60, 60, 60, 255};
}
// Box text: note name when held, "OFF" when releasing, "---" when idle
@@ -624,7 +624,7 @@ static void build_right_panel(AppState *app) {
Clay_TextElementConfig *box_txt = &box_text_config;
static Clay_TextElementConfig box_text_dark;
box_text_dark = box_text_config;
box_text_dark.textColor = Clay_Color{30, 30, 30, 255};
box_text_dark.textColor = (Clay_Color){30, 30, 30, 255};
if (dev->releasing) box_txt = &box_text_dark;
// Velocity text
@@ -746,7 +746,7 @@ static void build_log_panel(AppState *app) {
}
////////////////////////////////
// Corner radius presets (indexed by AppState::radius_sel)
// Corner radius presets
static const F32 radius_values[] = { 0.0f, 4.0f, 6.0f, 10.0f };
// Window content callbacks
@@ -818,7 +818,7 @@ static void settings_window_content(void *user_data) {
.backgroundColor = g_theme.disabled_bg,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS)
) {
static Clay_TextElementConfig disabled_text = {};
static Clay_TextElementConfig disabled_text = {0};
disabled_text.textColor = g_theme.disabled_text;
disabled_text.fontSize = FONT_SIZE_NORMAL;
disabled_text.wrapMode = CLAY_TEXT_WRAP_NONE;
@@ -976,22 +976,22 @@ static void build_header_bar(AppState *app) {
Clay_Color bar_bg = g_theme.header_bg;
Clay_Color border_bot = {(F32)Max((S32)bar_bg.r - 12, 0), (F32)Max((S32)bar_bg.g - 12, 0), (F32)Max((S32)bar_bg.b - 12, 0), 255};
static Clay_TextElementConfig header_btn_active_text = {};
header_btn_active_text.textColor = Clay_Color{255, 255, 255, 255};
static Clay_TextElementConfig header_btn_active_text = {0};
header_btn_active_text.textColor = (Clay_Color){255, 255, 255, 255};
header_btn_active_text.fontSize = FONT_SIZE_NORMAL;
header_btn_active_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_clock_text = {};
static Clay_TextElementConfig header_clock_text = {0};
header_clock_text.textColor = g_theme.text;
header_clock_text.fontSize = uifs(22);
header_clock_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_indicator_text = {};
static Clay_TextElementConfig header_indicator_text = {0};
header_indicator_text.textColor = g_theme.text;
header_indicator_text.fontSize = FONT_SIZE_NORMAL;
header_indicator_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig header_indicator_label = {};
static Clay_TextElementConfig header_indicator_label = {0};
header_indicator_label.textColor = g_theme.text_dim;
header_indicator_label.fontSize = FONT_SIZE_SMALL;
header_indicator_label.wrapMode = CLAY_TEXT_WRAP_NONE;
@@ -1056,7 +1056,7 @@ static void build_header_bar(AppState *app) {
},
.backgroundColor = g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
) { ui_icon(UI_ICON_TRANSPORT_RECORD, uis(14), Clay_Color{200, 60, 60, 255}); }
) { ui_icon(UI_ICON_TRANSPORT_RECORD, uis(14), (Clay_Color){200, 60, 60, 255}); }
}
// Spacer
@@ -1410,28 +1410,28 @@ static void build_patch_view(AppState *app) {
#define MATRIX_OUTPUTS 33 // Master + Out 1..32
#define HW_OUTPUTS 32
static Clay_TextElementConfig matrix_hdr_text = {};
static Clay_TextElementConfig matrix_hdr_text = {0};
matrix_hdr_text.textColor = g_theme.text_dim;
matrix_hdr_text.fontSize = FONT_SIZE_SMALL;
matrix_hdr_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig matrix_axis_text = {};
static Clay_TextElementConfig matrix_axis_text = {0};
matrix_axis_text.textColor = g_theme.text;
matrix_axis_text.fontSize = FONT_SIZE_SMALL;
matrix_axis_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig matrix_title_text = {};
static Clay_TextElementConfig matrix_title_text = {0};
matrix_title_text.textColor = g_theme.text;
matrix_title_text.fontSize = FONT_SIZE_NORMAL;
matrix_title_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig cell_x_text = {};
cell_x_text.textColor = Clay_Color{255, 255, 255, 255};
static Clay_TextElementConfig cell_x_text = {0};
cell_x_text.textColor = (Clay_Color){255, 255, 255, 255};
cell_x_text.fontSize = FONT_SIZE_SMALL;
cell_x_text.wrapMode = CLAY_TEXT_WRAP_NONE;
static Clay_TextElementConfig fb_text = {};
fb_text.textColor = Clay_Color{80, 80, 80, 255};
static Clay_TextElementConfig fb_text = {0};
fb_text.textColor = (Clay_Color){80, 80, 80, 255};
fb_text.fontSize = FONT_SIZE_SMALL;
fb_text.wrapMode = CLAY_TEXT_WRAP_NONE;
@@ -1549,7 +1549,7 @@ static void build_patch_view(AppState *app) {
Clay_Color cell_bg;
if (is_feedback) {
cell_bg = Clay_Color{
cell_bg = (Clay_Color){
(F32)Max((S32)g_theme.bg_dark.r - 10, 0),
(F32)Max((S32)g_theme.bg_dark.g - 10, 0),
(F32)Max((S32)g_theme.bg_dark.b - 10, 0), 255
@@ -1561,7 +1561,7 @@ static void build_patch_view(AppState *app) {
} else if ((s + d) % 2 == 0) {
cell_bg = g_theme.bg_dark;
} else {
cell_bg = Clay_Color{
cell_bg = (Clay_Color){
(F32)Min((S32)g_theme.bg_dark.r + 6, 255),
(F32)Min((S32)g_theme.bg_dark.g + 6, 255),
(F32)Min((S32)g_theme.bg_dark.b + 6, 255), 255
@@ -1569,7 +1569,7 @@ static void build_patch_view(AppState *app) {
}
if (d == 0 && !active && !cell_hovered && !is_feedback) {
cell_bg = Clay_Color{
cell_bg = (Clay_Color){
(F32)Min((S32)cell_bg.r + 10, 255),
(F32)Min((S32)cell_bg.g + 10, 255),
(F32)Min((S32)cell_bg.b + 12, 255), 255
@@ -1699,7 +1699,7 @@ static void build_patch_view(AppState *app) {
} else if ((s + d) % 2 == 0) {
cell_bg = g_theme.bg_dark;
} else {
cell_bg = Clay_Color{
cell_bg = (Clay_Color){
(F32)Min((S32)g_theme.bg_dark.r + 6, 255),
(F32)Min((S32)g_theme.bg_dark.g + 6, 255),
(F32)Min((S32)g_theme.bg_dark.b + 6, 255), 255
@@ -1933,7 +1933,7 @@ static void do_frame(AppState *app) {
Clay_RenderCommandArray render_commands = ui_end_frame(app->ui);
// Render
renderer_end_frame(app->renderer, render_commands);
renderer_end_frame(app->renderer, &render_commands);
}
////////////////////////////////
@@ -1950,7 +1950,7 @@ int main(int argc, char **argv) {
(void)argc;
(void)argv;
PlatformWindowDesc window_desc = {};
PlatformWindowDesc window_desc = platform_window_desc_default();
PlatformWindow *window = platform_create_window(&window_desc);
if (!window)
return 1;
@@ -1958,7 +1958,7 @@ int main(int argc, char **argv) {
S32 w, h;
platform_get_size(window, &w, &h);
RendererDesc renderer_desc = {};
RendererDesc renderer_desc = {0};
renderer_desc.window_handle = platform_get_native_handle(window);
renderer_desc.width = w;
renderer_desc.height = h;
@@ -1990,7 +1990,7 @@ int main(int argc, char **argv) {
}
}
AppState app = {};
AppState app = {0};
app.window = window;
app.renderer = renderer;
app.midi = midi;
@@ -2060,9 +2060,9 @@ int main(int argc, char **argv) {
// Open popup windows when flags transition to 1
if (app.show_settings_window && !popup_find_by_flag(&app.show_settings_window))
popup_open(window, renderer, "Preferences", &app.show_settings_window, 480, 400, settings_window_content, &app);
popup_open(window, renderer, "Preferences", &app.show_settings_window, 480, 400, settings_window_content, &app, PLATFORM_WINDOW_STYLE_POPUP, 0);
if (app.show_about_window && !popup_find_by_flag(&app.show_about_window))
popup_open(window, renderer, "About", &app.show_about_window, 260, 200, about_window_content, nullptr);
popup_open(window, renderer, "About", &app.show_about_window, 260, 200, about_window_content, NULL, PLATFORM_WINDOW_STYLE_POPUP, 0);
if (app.show_mix_popout && !popup_find_by_flag(&app.show_mix_popout))
popup_open(window, renderer, "Mix", &app.show_mix_popout, 900, 600, mix_popout_content, &app, PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE, 1);
if (app.show_patch_popout && !popup_find_by_flag(&app.show_patch_popout))
@@ -2082,7 +2082,7 @@ int main(int argc, char **argv) {
}
popup_close_all();
platform_set_frame_callback(window, nullptr, nullptr);
platform_set_frame_callback(window, NULL, NULL);
audio_destroy(audio);
midi_destroy(midi);
ui_destroy(ui);

View File

@@ -1,6 +1,6 @@
#include "platform/platform.h"
enum MenuCmd {
typedef enum MenuCmd {
MENU_NONE = 0,
MENU_FILE_NEW,
MENU_FILE_OPEN,
@@ -15,7 +15,7 @@ enum MenuCmd {
MENU_VIEW_DEMO,
MENU_VIEW_MIDI_DEVICES,
MENU_PREFERENCES_EDIT,
};
} MenuCmd;
static void setup_menus(PlatformWindow *window) {
PlatformMenuItem file_items[] = {
@@ -23,7 +23,7 @@ static void setup_menus(PlatformWindow *window) {
{ "Open...", MENU_FILE_OPEN },
{ "Save", MENU_FILE_SAVE },
{ "Save As...", MENU_FILE_SAVE_AS },
{ nullptr, 0 },
{ NULL, 0 },
{ "Exit", MENU_FILE_EXIT },
};
@@ -36,7 +36,7 @@ static void setup_menus(PlatformWindow *window) {
{ "Browser", MENU_VIEW_BROWSER },
{ "Properties", MENU_VIEW_PROPERTIES },
{ "Log", MENU_VIEW_LOG },
{ nullptr, 0 },
{ NULL, 0 },
{ "Demo", MENU_VIEW_DEMO },
{ "MIDI Devices", MENU_VIEW_MIDI_DEVICES },
};

View File

@@ -2,9 +2,9 @@
#include "base/base_core.h"
struct MidiEngine;
typedef struct MidiEngine MidiEngine;
struct MidiDeviceInfo {
typedef struct MidiDeviceInfo {
char name[64];
S32 id;
B32 is_input;
@@ -12,9 +12,9 @@ struct MidiDeviceInfo {
B32 releasing; // true during release flash
S32 velocity; // last note-on velocity (0-127)
S32 note; // last MIDI note number (0-127)
};
} MidiDeviceInfo;
MidiEngine *midi_create();
MidiEngine *midi_create(void);
void midi_destroy(MidiEngine *engine);
void midi_refresh_devices(MidiEngine *engine);
S32 midi_get_device_count(MidiEngine *engine);

View File

@@ -3,6 +3,7 @@
#include <CoreFoundation/CoreFoundation.h>
#include <string.h>
#include <stdatomic.h>
#include <stdlib.h>
#define MIDI_MAX_DEVICES 64
#define MIDI_RELEASE_FLASH_DURATION 0.15f
@@ -113,7 +114,7 @@ static void enumerate_midi_devices(MidiEngine *engine) {
for (ItemCount i = 0; i < num_sources && engine->device_count < MIDI_MAX_DEVICES; i++) {
MIDIEndpointRef endpoint = MIDIGetSource((ItemCount)i);
CFStringRef name_ref = nullptr;
CFStringRef name_ref = NULL;
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
MidiDeviceInfo *dev = &engine->devices[engine->device_count];
@@ -139,7 +140,7 @@ static void enumerate_midi_devices(MidiEngine *engine) {
for (ItemCount i = 0; i < num_dests && engine->device_count < MIDI_MAX_DEVICES; i++) {
MIDIEndpointRef endpoint = MIDIGetDestination((ItemCount)i);
CFStringRef name_ref = nullptr;
CFStringRef name_ref = NULL;
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
MidiDeviceInfo *dev = &engine->devices[engine->device_count];
@@ -163,11 +164,10 @@ static void enumerate_midi_devices(MidiEngine *engine) {
////////////////////////////////
// Public API
MidiEngine *midi_create() {
MidiEngine *engine = new MidiEngine();
memset(engine, 0, sizeof(*engine));
MidiEngine *midi_create(void) {
MidiEngine *engine = (MidiEngine *)calloc(1, sizeof(MidiEngine));
MIDIClientCreate(CFSTR("autosample"), nullptr, nullptr, &engine->client);
MIDIClientCreate(CFSTR("autosample"), NULL, NULL, &engine->client);
MIDIInputPortCreate(engine->client, CFSTR("Input"), midi_read_callback, engine, &engine->input_port);
midi_refresh_devices(engine);
@@ -178,7 +178,7 @@ void midi_destroy(MidiEngine *engine) {
midi_close_all_inputs(engine);
if (engine->input_port) MIDIPortDispose(engine->input_port);
if (engine->client) MIDIClientDispose(engine->client);
delete engine;
free(engine);
}
void midi_open_all_inputs(MidiEngine *engine) {
@@ -276,6 +276,6 @@ S32 midi_get_device_count(MidiEngine *engine) {
}
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index) {
if (index < 0 || index >= engine->device_count) return nullptr;
if (index < 0 || index >= engine->device_count) return NULL;
return &engine->devices[index];
}

View File

@@ -2,6 +2,7 @@
#include <windows.h>
#include <mmeapi.h>
#include <string.h>
#include <stdlib.h>
#define MIDI_MAX_DEVICES 64
#define MIDI_RELEASE_FLASH_DURATION 0.15f
@@ -31,7 +32,7 @@ struct MidiEngine {
////////////////////////////////
// MIDI input callback — called from Win32 MIDI driver thread
static MidiEngine *g_midi_engine = nullptr;
static MidiEngine *g_midi_engine = NULL;
static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
@@ -71,9 +72,8 @@ static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwIn
}
}
MidiEngine *midi_create() {
MidiEngine *engine = new MidiEngine();
memset(engine, 0, sizeof(*engine));
MidiEngine *midi_create(void) {
MidiEngine *engine = (MidiEngine *)calloc(1, sizeof(MidiEngine));
g_midi_engine = engine;
midi_refresh_devices(engine);
return engine;
@@ -81,8 +81,8 @@ MidiEngine *midi_create() {
void midi_destroy(MidiEngine *engine) {
midi_close_all_inputs(engine);
if (g_midi_engine == engine) g_midi_engine = nullptr;
delete engine;
if (g_midi_engine == engine) g_midi_engine = NULL;
free(engine);
}
void midi_open_all_inputs(MidiEngine *engine) {
@@ -91,7 +91,7 @@ void midi_open_all_inputs(MidiEngine *engine) {
if (!dev->is_input) continue;
if (engine->input_handles[i]) continue; // already open
HMIDIIN handle = nullptr;
HMIDIIN handle = NULL;
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
(DWORD_PTR)midi_in_callback,
(DWORD_PTR)i, CALLBACK_FUNCTION);
@@ -107,7 +107,7 @@ void midi_close_all_inputs(MidiEngine *engine) {
if (engine->input_handles[i]) {
midiInStop(engine->input_handles[i]);
midiInClose(engine->input_handles[i]);
engine->input_handles[i] = nullptr;
engine->input_handles[i] = NULL;
}
engine->pending_note_on_vel[i] = 0;
engine->pending_note_num[i] = 0;
@@ -193,7 +193,7 @@ void midi_refresh_devices(MidiEngine *engine) {
UINT num_in = midiInGetNumDevs();
for (UINT i = 0; i < num_in && engine->device_count < MIDI_MAX_DEVICES; i++) {
MIDIINCAPSA caps = {};
MIDIINCAPSA caps = {0};
if (midiInGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
MidiDeviceInfo *dev = &engine->devices[engine->device_count++];
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
@@ -205,7 +205,7 @@ void midi_refresh_devices(MidiEngine *engine) {
UINT num_out = midiOutGetNumDevs();
for (UINT i = 0; i < num_out && engine->device_count < MIDI_MAX_DEVICES; i++) {
MIDIOUTCAPSA caps = {};
MIDIOUTCAPSA caps = {0};
if (midiOutGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
MidiDeviceInfo *dev = &engine->devices[engine->device_count++];
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
@@ -224,6 +224,6 @@ S32 midi_get_device_count(MidiEngine *engine) {
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index) {
if (index < 0 || index >= engine->device_count)
return nullptr;
return NULL;
return &engine->devices[index];
}

View File

@@ -31,7 +31,7 @@ enum {
PKEY_MINUS = 0xBD, // '-'/'_' (VK_OEM_MINUS)
};
struct PlatformInput {
typedef struct PlatformInput {
// Typed characters (UTF-16 code units, printable only)
U16 chars[PLATFORM_MAX_CHARS_PER_FRAME];
S32 char_count;
@@ -49,41 +49,50 @@ struct PlatformInput {
Vec2F32 scroll_delta;
B32 mouse_down;
B32 was_mouse_down;
};
} PlatformInput;
struct PlatformWindow;
typedef struct PlatformWindow PlatformWindow;
enum PlatformWindowStyle {
typedef enum PlatformWindowStyle {
PLATFORM_WINDOW_STYLE_NORMAL = 0,
PLATFORM_WINDOW_STYLE_POPUP = 1, // utility panel, owned by parent, fixed size
PLATFORM_WINDOW_STYLE_POPUP_RESIZABLE = 2, // utility panel, owned by parent, resizable
};
} PlatformWindowStyle;
struct PlatformWindowDesc {
const char *title = "autosample";
S32 width = 1280;
S32 height = 720;
PlatformWindowStyle style = PLATFORM_WINDOW_STYLE_NORMAL;
PlatformWindow *parent = nullptr;
B32 independent = 0; // if true, don't attach as child (independent top-level window)
};
typedef struct PlatformWindowDesc {
const char *title;
S32 width;
S32 height;
PlatformWindowStyle style;
PlatformWindow *parent;
B32 independent; // if true, don't attach as child (independent top-level window)
} PlatformWindowDesc;
enum PlatformMsgBoxType {
// Helper to create a default PlatformWindowDesc
static inline PlatformWindowDesc platform_window_desc_default(void) {
PlatformWindowDesc d = {0};
d.title = "autosample";
d.width = 1280;
d.height = 720;
return d;
}
typedef enum PlatformMsgBoxType {
PLATFORM_MSGBOX_OK = 0,
PLATFORM_MSGBOX_OK_CANCEL = 1,
PLATFORM_MSGBOX_YES_NO = 2,
};
} PlatformMsgBoxType;
struct PlatformMenuItem {
const char *label; // nullptr = separator
typedef struct PlatformMenuItem {
const char *label; // NULL = separator
S32 id; // command ID (ignored for separators)
};
} PlatformMenuItem;
struct PlatformMenu {
typedef struct PlatformMenu {
const char *label;
PlatformMenuItem *items;
S32 item_count;
};
} PlatformMenu;
// 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
@@ -114,11 +123,11 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
const char *message, PlatformMsgBoxType type);
// Cursor shapes for resize handles
enum PlatformCursor {
typedef enum PlatformCursor {
PLATFORM_CURSOR_ARROW = 0,
PLATFORM_CURSOR_SIZE_WE = 1, // horizontal resize
PLATFORM_CURSOR_SIZE_NS = 2, // vertical resize
};
} PlatformCursor;
void platform_set_cursor(PlatformCursor cursor);
@@ -128,6 +137,6 @@ F32 platform_get_dpi_scale(PlatformWindow *window);
// Clipboard operations (null-terminated UTF-8 strings).
// platform_clipboard_set copies text to the system clipboard.
// platform_clipboard_get returns a pointer to a static buffer (valid until next call), or nullptr.
// platform_clipboard_get returns a pointer to a static buffer (valid until next call), or NULL.
void platform_clipboard_set(const char *text);
const char *platform_clipboard_get();
const char *platform_clipboard_get(void);

View File

@@ -1,6 +1,8 @@
#include "platform/platform.h"
#import <Cocoa/Cocoa.h>
#include <stdlib.h>
#include <string.h>
// macOS virtual key codes (avoids Carbon.h include)
enum {
@@ -46,10 +48,8 @@ static U8 macos_keycode_to_pkey(U16 keycode) {
////////////////////////////////
// Forward declarations
struct PlatformWindow;
// Main window receives menu commands
static PlatformWindow *g_main_window = nullptr;
static PlatformWindow *g_main_window = NULL;
////////////////////////////////
// Objective-C helper classes
@@ -77,7 +77,7 @@ static PlatformWindow *g_main_window = nullptr;
////////////////////////////////
// PlatformWindow struct
struct PlatformWindow {
typedef struct PlatformWindow {
NSWindow *ns_window;
ASmplView *view;
ASmplWindowDelegate *delegate;
@@ -91,7 +91,7 @@ struct PlatformWindow {
B32 prev_mouse_down;
B32 mouse_down_state;
F32 backing_scale;
};
} PlatformWindow;
////////////////////////////////
// C callback helpers (called from ObjC via PlatformWindow pointer)
@@ -288,8 +288,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
ASmplWindowDelegate *delegate = [[ASmplWindowDelegate alloc] init];
[ns_window setDelegate:delegate];
PlatformWindow *window = new PlatformWindow();
memset(window, 0, sizeof(*window));
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
window->ns_window = ns_window;
window->view = view;
window->delegate = delegate;
@@ -329,8 +328,8 @@ void platform_destroy_window(PlatformWindow *window) {
[window->ns_window close];
if (g_main_window == window)
g_main_window = nullptr;
delete window;
g_main_window = NULL;
free(window);
}
B32 platform_poll_events(PlatformWindow *window) {
@@ -442,7 +441,7 @@ PlatformInput platform_get_input(PlatformWindow *window) {
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
// Clear accumulated events for next frame
window->input = {};
memset(&window->input, 0, sizeof(window->input));
return result;
}
@@ -491,7 +490,7 @@ const char *platform_clipboard_get() {
}
}
return buf[0] ? buf : nullptr;
return buf[0] ? buf : NULL;
}
S32 platform_message_box(PlatformWindow *parent, const char *title,

View File

@@ -5,8 +5,10 @@
#endif
#include <windows.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
struct PlatformWindow {
typedef struct PlatformWindow {
HWND hwnd;
B32 should_close;
S32 width;
@@ -16,11 +18,11 @@ struct PlatformWindow {
void *frame_callback_user_data;
PlatformInput input;
B32 prev_mouse_down;
};
} PlatformWindow;
// Main window receives menu commands
static PlatformWindow *g_main_window = nullptr;
static HCURSOR g_current_cursor = nullptr;
static PlatformWindow *g_main_window = NULL;
static HCURSOR g_current_cursor = NULL;
static B32 g_wndclass_registered = false;
static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
@@ -69,14 +71,14 @@ static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
case WM_SETCURSOR:
// When the cursor is in our client area, use the app-set cursor.
if (LOWORD(lparam) == HTCLIENT) {
SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(nullptr, IDC_ARROW));
SetCursor(g_current_cursor ? g_current_cursor : LoadCursor(NULL, IDC_ARROW));
return TRUE;
}
break;
case WM_DPICHANGED:
if (pw) {
RECT *suggested = (RECT *)lparam;
SetWindowPos(hwnd, nullptr, suggested->left, suggested->top,
SetWindowPos(hwnd, NULL, suggested->left, suggested->top,
suggested->right - suggested->left, suggested->bottom - suggested->top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
@@ -101,12 +103,12 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!g_wndclass_registered) {
WNDCLASSEXW wc = {};
WNDCLASSEXW wc = {0};
wc.cbSize = sizeof(wc);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = win32_wndproc;
wc.hInstance = GetModuleHandleW(nullptr);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = GetModuleHandleW(NULL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"autosample_wc";
RegisterClassExW(&wc);
g_wndclass_registered = true;
@@ -119,7 +121,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
int y = (screen_h - desc->height) / 2;
DWORD style;
HWND parent_hwnd = nullptr;
HWND parent_hwnd = NULL;
if (desc->style == PLATFORM_WINDOW_STYLE_POPUP) {
style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
if (desc->parent && !desc->independent) parent_hwnd = desc->parent->hwnd;
@@ -133,7 +135,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
RECT rect = { 0, 0, (LONG)desc->width, (LONG)desc->height };
AdjustWindowRectExForDpi(&rect, style, FALSE, 0, dpi);
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, nullptr, 0);
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, NULL, 0);
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);
@@ -143,15 +145,15 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
x, y,
rect.right - rect.left,
rect.bottom - rect.top,
parent_hwnd, nullptr, GetModuleHandleW(nullptr), nullptr
parent_hwnd, NULL, GetModuleHandleW(NULL), NULL
);
_freea(wtitle);
if (!hwnd)
return nullptr;
return NULL;
PlatformWindow *window = new PlatformWindow();
PlatformWindow *window = (PlatformWindow *)calloc(1, sizeof(PlatformWindow));
window->hwnd = hwnd;
window->should_close = false;
window->width = desc->width;
@@ -179,14 +181,14 @@ void platform_destroy_window(PlatformWindow *window) {
}
if (g_main_window == window)
g_main_window = nullptr;
g_main_window = NULL;
delete window;
free(window);
}
B32 platform_poll_events(PlatformWindow *window) {
MSG msg;
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
if (msg.message == WM_QUIT) {
@@ -219,9 +221,9 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
for (S32 j = 0; j < menus[i].item_count; j++) {
PlatformMenuItem *item = &menus[i].items[j];
if (!item->label) {
AppendMenuW(submenu, MF_SEPARATOR, 0, nullptr);
AppendMenuW(submenu, MF_SEPARATOR, 0, NULL);
} else {
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 0);
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, NULL, 0);
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, item->label, -1, wlabel, wchar_count);
AppendMenuW(submenu, MF_STRING, (UINT_PTR)item->id, wlabel);
@@ -229,7 +231,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
}
}
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 0);
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, NULL, 0);
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, wlabel, wchar_count);
AppendMenuW(menu_bar, MF_POPUP, (UINT_PTR)submenu, wlabel);
@@ -260,7 +262,7 @@ PlatformInput platform_get_input(PlatformWindow *window) {
window->prev_mouse_down = result.mouse_down;
// Clear accumulated events for next frame
window->input = {};
memset(&window->input, 0, sizeof(window->input));
return result;
}
@@ -281,9 +283,9 @@ F32 platform_get_dpi_scale(PlatformWindow *window) {
void platform_set_cursor(PlatformCursor cursor) {
switch (cursor) {
case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(nullptr, IDC_SIZEWE); break;
case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(nullptr, IDC_SIZENS); break;
default: g_current_cursor = LoadCursor(nullptr, IDC_ARROW); break;
case PLATFORM_CURSOR_SIZE_WE: g_current_cursor = LoadCursor(NULL, IDC_SIZEWE); break;
case PLATFORM_CURSOR_SIZE_NS: g_current_cursor = LoadCursor(NULL, IDC_SIZENS); break;
default: g_current_cursor = LoadCursor(NULL, IDC_ARROW); break;
}
}
@@ -293,7 +295,7 @@ void platform_clipboard_set(const char *text) {
if (len == 0) return;
// Convert UTF-8 to wide string for Windows clipboard
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, nullptr, 0);
int wlen = MultiByteToWideChar(CP_UTF8, 0, text, len, NULL, 0);
if (wlen == 0) return;
HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
@@ -304,7 +306,7 @@ void platform_clipboard_set(const char *text) {
wbuf[wlen] = L'\0';
GlobalUnlock(hmem);
HWND hwnd = g_main_window ? g_main_window->hwnd : nullptr;
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
if (OpenClipboard(hwnd)) {
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, hmem);
@@ -318,21 +320,21 @@ const char *platform_clipboard_get() {
static char buf[4096];
buf[0] = '\0';
HWND hwnd = g_main_window ? g_main_window->hwnd : nullptr;
if (!OpenClipboard(hwnd)) return nullptr;
HWND hwnd = g_main_window ? g_main_window->hwnd : NULL;
if (!OpenClipboard(hwnd)) return NULL;
HGLOBAL hmem = GetClipboardData(CF_UNICODETEXT);
if (hmem) {
wchar_t *wbuf = (wchar_t *)GlobalLock(hmem);
if (wbuf) {
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, nullptr, nullptr);
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, sizeof(buf) - 1, NULL, NULL);
buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
GlobalUnlock(hmem);
}
}
CloseClipboard();
return buf[0] ? buf : nullptr;
return buf[0] ? buf : NULL;
}
S32 platform_message_box(PlatformWindow *parent, const char *title,
@@ -346,15 +348,15 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
}
// Convert UTF-8 to wide strings
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 0);
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
wchar_t *wtitle = (wchar_t *)_malloca(title_wlen * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, title, -1, wtitle, title_wlen);
int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, nullptr, 0);
int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0);
wchar_t *wmsg = (wchar_t *)_malloca(msg_wlen * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, message, -1, wmsg, msg_wlen);
HWND hwnd = parent ? parent->hwnd : nullptr;
HWND hwnd = parent ? parent->hwnd : NULL;
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
_freea(wmsg);

View File

@@ -1,13 +1,10 @@
#pragma once
#include "base/base_core.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "base/base_math.h"
#include "clay.h"
typedef struct Renderer Renderer;
struct Clay_RenderCommandArray;
typedef struct RendererDesc {
void *window_handle;
@@ -26,7 +23,7 @@ Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc);
void renderer_destroy(Renderer *renderer);
B32 renderer_begin_frame(Renderer *renderer);
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray render_commands);
void renderer_end_frame(Renderer *renderer, Clay_RenderCommandArray *render_commands);
void renderer_resize(Renderer *renderer, S32 width, S32 height);
void renderer_set_font_scale(Renderer *renderer, F32 scale);
void renderer_sync_from_parent(Renderer *renderer); // sync shared font atlas from parent
@@ -39,7 +36,3 @@ Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void
// Upload an RGBA8 icon atlas texture for icon rendering (4 bytes per pixel)
void renderer_create_icon_atlas(Renderer *renderer, const U8 *data, S32 w, S32 h);
#ifdef __cplusplus
}
#endif

View File

@@ -6,6 +6,8 @@
#import <QuartzCore/CAMetalLayer.h>
#import <Cocoa/Cocoa.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
// FreeType headers temporarily undefine `internal` macro (base_core.h: #define internal static)
// because FreeType uses `internal` as a struct field name.
@@ -30,7 +32,7 @@
////////////////////////////////
// Vertex format matches DX12 UIVertex exactly
struct UIVertex {
typedef struct UIVertex {
float pos[2];
float uv[2];
float col[4];
@@ -40,120 +42,119 @@ struct UIVertex {
float border_thickness;
float softness;
float mode; // 0 = rect SDF, 1 = textured
};
} UIVertex;
////////////////////////////////
// Glyph info
struct GlyphInfo {
typedef struct GlyphInfo {
F32 u0, v0, u1, v1;
F32 w, h;
F32 x_advance;
};
} GlyphInfo;
////////////////////////////////
// Metal shader (MSL) port of HLSL SDF shader
static const char *g_shader_msl = R"(
#include <metal_stdlib>
using namespace metal;
struct Vertex {
float2 pos [[attribute(0)]];
float2 uv [[attribute(1)]];
float4 col [[attribute(2)]];
float2 rect_min [[attribute(3)]];
float2 rect_max [[attribute(4)]];
float4 corner_radii [[attribute(5)]];
float border_thickness [[attribute(6)]];
float softness [[attribute(7)]];
float mode [[attribute(8)]];
};
struct Fragment {
float4 pos [[position]];
float2 uv;
float4 col;
float2 rect_min;
float2 rect_max;
float4 corner_radii;
float border_thickness;
float softness;
float mode;
};
struct Constants {
float2 viewport_size;
};
vertex Fragment vertex_main(Vertex in [[stage_in]],
constant Constants &cb [[buffer(1)]]) {
Fragment out;
float2 ndc;
ndc.x = (in.pos.x / cb.viewport_size.x) * 2.0 - 1.0;
ndc.y = 1.0 - (in.pos.y / cb.viewport_size.y) * 2.0;
out.pos = float4(ndc, 0.0, 1.0);
out.uv = in.uv;
out.col = in.col;
out.rect_min = in.rect_min;
out.rect_max = in.rect_max;
out.corner_radii = in.corner_radii;
out.border_thickness = in.border_thickness;
out.softness = in.softness;
out.mode = in.mode;
return out;
}
float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {
float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
}
fragment float4 fragment_main(Fragment in [[stage_in]],
texture2d<float> font_tex [[texture(0)]],
sampler font_smp [[sampler(0)]]) {
float4 col = in.col;
if (in.mode > 1.5) {
float4 tex = font_tex.sample(font_smp, in.uv);
col *= tex;
} else if (in.mode > 0.5) {
float alpha = font_tex.sample(font_smp, in.uv).r;
col.a *= alpha;
} else {
float2 pixel_pos = in.pos.xy;
float2 rect_center = (in.rect_min + in.rect_max) * 0.5;
float2 rect_half_size = (in.rect_max - in.rect_min) * 0.5;
float radius = (pixel_pos.x < rect_center.x)
? ((pixel_pos.y < rect_center.y) ? in.corner_radii.x : in.corner_radii.w)
: ((pixel_pos.y < rect_center.y) ? in.corner_radii.y : in.corner_radii.z);
float softness = max(in.softness, 0.5);
float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);
if (in.border_thickness > 0) {
float inner_dist = dist + in.border_thickness;
float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);
float inner_alpha = smoothstep(-softness, softness, inner_dist);
col.a *= outer_alpha * inner_alpha;
} else {
col.a *= 1.0 - smoothstep(-softness, softness, dist);
}
}
// Dither to reduce gradient banding (interleaved gradient noise)
float dither = fract(52.9829189 * fract(dot(in.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;
col.rgb += dither / 255.0;
if (col.a < 0.002) discard_fragment();
return col;
}
)";
static const char *g_shader_msl =
"#include <metal_stdlib>\n"
"using namespace metal;\n"
"\n"
"struct Vertex {\n"
" float2 pos [[attribute(0)]];\n"
" float2 uv [[attribute(1)]];\n"
" float4 col [[attribute(2)]];\n"
" float2 rect_min [[attribute(3)]];\n"
" float2 rect_max [[attribute(4)]];\n"
" float4 corner_radii [[attribute(5)]];\n"
" float border_thickness [[attribute(6)]];\n"
" float softness [[attribute(7)]];\n"
" float mode [[attribute(8)]];\n"
"};\n"
"\n"
"struct Fragment {\n"
" float4 pos [[position]];\n"
" float2 uv;\n"
" float4 col;\n"
" float2 rect_min;\n"
" float2 rect_max;\n"
" float4 corner_radii;\n"
" float border_thickness;\n"
" float softness;\n"
" float mode;\n"
"};\n"
"\n"
"struct Constants {\n"
" float2 viewport_size;\n"
"};\n"
"\n"
"vertex Fragment vertex_main(Vertex in [[stage_in]],\n"
" constant Constants &cb [[buffer(1)]]) {\n"
" Fragment out;\n"
" float2 ndc;\n"
" ndc.x = (in.pos.x / cb.viewport_size.x) * 2.0 - 1.0;\n"
" ndc.y = 1.0 - (in.pos.y / cb.viewport_size.y) * 2.0;\n"
" out.pos = float4(ndc, 0.0, 1.0);\n"
" out.uv = in.uv;\n"
" out.col = in.col;\n"
" out.rect_min = in.rect_min;\n"
" out.rect_max = in.rect_max;\n"
" out.corner_radii = in.corner_radii;\n"
" out.border_thickness = in.border_thickness;\n"
" out.softness = in.softness;\n"
" out.mode = in.mode;\n"
" return out;\n"
"}\n"
"\n"
"float rounded_rect_sdf(float2 sample_pos, float2 rect_center, float2 rect_half_size, float radius) {\n"
" float2 d = abs(sample_pos - rect_center) - rect_half_size + float2(radius, radius);\n"
" return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;\n"
"}\n"
"\n"
"fragment float4 fragment_main(Fragment in [[stage_in]],\n"
" texture2d<float> font_tex [[texture(0)]],\n"
" sampler font_smp [[sampler(0)]]) {\n"
" float4 col = in.col;\n"
"\n"
" if (in.mode > 1.5) {\n"
" float4 tex = font_tex.sample(font_smp, in.uv);\n"
" col *= tex;\n"
" } else if (in.mode > 0.5) {\n"
" float alpha = font_tex.sample(font_smp, in.uv).r;\n"
" col.a *= alpha;\n"
" } else {\n"
" float2 pixel_pos = in.pos.xy;\n"
" float2 rect_center = (in.rect_min + in.rect_max) * 0.5;\n"
" float2 rect_half_size = (in.rect_max - in.rect_min) * 0.5;\n"
" float radius = (pixel_pos.x < rect_center.x)\n"
" ? ((pixel_pos.y < rect_center.y) ? in.corner_radii.x : in.corner_radii.w)\n"
" : ((pixel_pos.y < rect_center.y) ? in.corner_radii.y : in.corner_radii.z);\n"
" float softness = max(in.softness, 0.5);\n"
" float dist = rounded_rect_sdf(pixel_pos, rect_center, rect_half_size, radius);\n"
"\n"
" if (in.border_thickness > 0) {\n"
" float inner_dist = dist + in.border_thickness;\n"
" float outer_alpha = 1.0 - smoothstep(-softness, softness, dist);\n"
" float inner_alpha = smoothstep(-softness, softness, inner_dist);\n"
" col.a *= outer_alpha * inner_alpha;\n"
" } else {\n"
" col.a *= 1.0 - smoothstep(-softness, softness, dist);\n"
" }\n"
" }\n"
"\n"
" // Dither to reduce gradient banding (interleaved gradient noise)\n"
" float dither = fract(52.9829189 * fract(dot(in.pos.xy, float2(0.06711056, 0.00583715)))) - 0.5;\n"
" col.rgb += dither / 255.0;\n"
"\n"
" if (col.a < 0.002) discard_fragment();\n"
" return col;\n"
"}\n";
////////////////////////////////
// Renderer struct
struct Renderer {
Renderer *parent; // non-null for shared renderers
typedef struct Renderer {
struct Renderer *parent; // non-null for shared renderers
S32 width;
S32 height;
S32 frame_count;
@@ -190,7 +191,7 @@ struct Renderer {
// Clear color
F32 clear_r, clear_g, clear_b;
};
} Renderer;
////////////////////////////////
// Font atlas (FreeType)
@@ -302,12 +303,12 @@ Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void
////////////////////////////////
// Draw batch and quad emission (same logic as DX12)
struct DrawBatch {
typedef struct DrawBatch {
UIVertex *vertices;
U32 *indices;
U32 vertex_count;
U32 index_count;
};
} DrawBatch;
static void emit_quad(DrawBatch *batch,
F32 x0, F32 y0, F32 x1, F32 y1,
@@ -519,8 +520,7 @@ static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
// Public API
Renderer *renderer_create(RendererDesc *desc) {
Renderer *r = new Renderer();
memset(r, 0, sizeof(*r));
Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));
r->width = desc->width;
r->height = desc->height;
@@ -532,7 +532,7 @@ Renderer *renderer_create(RendererDesc *desc) {
[view setWantsLayer:YES];
r->device = MTLCreateSystemDefaultDevice();
if (!r->device) { delete r; return nullptr; }
if (!r->device) { free(r); return NULL; }
r->command_queue = [r->device newCommandQueue];
@@ -559,8 +559,8 @@ Renderer *renderer_create(RendererDesc *desc) {
[NSString stringWithUTF8String:g_shader_msl] options:nil error:&error];
if (!library) {
NSLog(@"Metal shader compile error: %@", error);
delete r;
return nullptr;
free(r);
return NULL;
}
id<MTLFunction> vert_fn = [library newFunctionWithName:@"vertex_main"];
@@ -615,8 +615,8 @@ Renderer *renderer_create(RendererDesc *desc) {
r->pipeline_state = [r->device newRenderPipelineStateWithDescriptor:pipe_desc error:&error];
if (!r->pipeline_state) {
NSLog(@"Metal pipeline error: %@", error);
delete r;
return nullptr;
free(r);
return NULL;
}
// Create F64-buffered vertex/index buffers
@@ -630,8 +630,8 @@ Renderer *renderer_create(RendererDesc *desc) {
// FreeType + font atlas
init_freetype(r);
if (!create_font_atlas(r, 22.0f)) {
delete r;
return nullptr;
free(r);
return NULL;
}
// Default clear color (dark theme bg_dark)
@@ -643,10 +643,9 @@ Renderer *renderer_create(RendererDesc *desc) {
}
Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
if (!parent) return nullptr;
if (!parent) return NULL;
Renderer *r = new Renderer();
memset(r, 0, sizeof(*r));
Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));
r->parent = parent;
r->width = desc->width;
@@ -711,7 +710,7 @@ void renderer_destroy(Renderer *r) {
if (r->ft_face) FT_Done_Face(r->ft_face);
if (r->ft_lib) FT_Done_FreeType(r->ft_lib);
}
delete r;
free(r);
}
B32 renderer_begin_frame(Renderer *r) {
@@ -741,7 +740,47 @@ B32 renderer_begin_frame(Renderer *r) {
return true;
}
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
// Helper context for batch flushing (replaces C++ lambdas)
typedef struct FlushCtx {
DrawBatch *batch;
U32 *flush_index_start;
S32 *bound_texture;
Renderer *r;
U32 buf_idx;
id<MTLRenderCommandEncoder> encoder;
} FlushCtx;
static void flush_batch(FlushCtx *ctx) {
U32 index_count = ctx->batch->index_count - *ctx->flush_index_start;
if (index_count == 0) return;
[ctx->encoder setVertexBuffer:ctx->r->vertex_buffers[ctx->buf_idx] offset:0 atIndex:0];
[ctx->encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
indexCount:index_count
indexType:MTLIndexTypeUInt32
indexBuffer:ctx->r->index_buffers[ctx->buf_idx]
indexBufferOffset:*ctx->flush_index_start * sizeof(U32)];
*ctx->flush_index_start = ctx->batch->index_count;
}
static void bind_font_texture(FlushCtx *ctx) {
if (*ctx->bound_texture != 0) {
flush_batch(ctx);
[ctx->encoder setFragmentTexture:ctx->r->font_texture atIndex:0];
*ctx->bound_texture = 0;
}
}
static void bind_icon_texture(FlushCtx *ctx) {
if (*ctx->bound_texture != 1 && ctx->r->icon_texture) {
flush_batch(ctx);
[ctx->encoder setFragmentTexture:ctx->r->icon_texture atIndex:0];
*ctx->bound_texture = 1;
}
}
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
@autoreleasepool {
id<CAMetalDrawable> drawable = r->current_drawable;
r->current_drawable = nil;
@@ -762,7 +801,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
[encoder setFragmentSamplerState:r->font_sampler atIndex:0];
// Viewport
MTLViewport viewport = {};
MTLViewport viewport = {0};
viewport.width = (F64)r->width;
viewport.height = (F64)r->height;
viewport.zfar = 1.0;
@@ -777,8 +816,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
[encoder setVertexBytes:constants length:sizeof(constants) atIndex:1];
// Process Clay render commands
if (render_commands.length > 0) {
DrawBatch batch = {};
if (render_commands->length > 0) {
DrawBatch batch = {0};
batch.vertices = (UIVertex *)[r->vertex_buffers[buf_idx] contents];
batch.indices = (U32 *)[r->index_buffers[buf_idx] contents];
batch.vertex_count = 0;
@@ -788,37 +827,15 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
S32 bound_texture = 0;
U32 flush_index_start = 0;
auto flush_batch = [&]() {
U32 index_count = batch.index_count - flush_index_start;
if (index_count == 0) return;
FlushCtx fctx;
fctx.batch = &batch;
fctx.flush_index_start = &flush_index_start;
fctx.bound_texture = &bound_texture;
fctx.r = r;
fctx.buf_idx = buf_idx;
fctx.encoder = encoder;
[encoder setVertexBuffer:r->vertex_buffers[buf_idx] offset:0 atIndex:0];
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
indexCount:index_count
indexType:MTLIndexTypeUInt32
indexBuffer:r->index_buffers[buf_idx]
indexBufferOffset:flush_index_start * sizeof(U32)];
flush_index_start = batch.index_count;
};
auto bind_font_texture = [&]() {
if (bound_texture != 0) {
flush_batch();
[encoder setFragmentTexture:r->font_texture atIndex:0];
bound_texture = 0;
}
};
auto bind_icon_texture = [&]() {
if (bound_texture != 1 && r->icon_texture) {
flush_batch();
[encoder setFragmentTexture:r->icon_texture atIndex:0];
bound_texture = 1;
}
};
for (S32 i = 0; i < render_commands.length; i++) {
for (S32 i = 0; i < render_commands->length; i++) {
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i);
Clay_BoundingBox bb = cmd->boundingBox;
@@ -877,7 +894,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
} break;
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
bind_font_texture();
bind_font_texture(&fctx);
Clay_TextRenderData *text = &cmd->renderData.text;
emit_text_glyphs(&batch, r, bb, text->textColor,
text->stringContents.chars, text->stringContents.length,
@@ -885,7 +902,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
} break;
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
flush_batch();
flush_batch(&fctx);
NSUInteger sx = (NSUInteger)Max(bb.x, 0.f);
NSUInteger sy = (NSUInteger)Max(bb.y, 0.f);
NSUInteger sw = (NSUInteger)Min(bb.width, (F32)r->width - (F32)sx);
@@ -897,7 +914,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
} break;
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
flush_batch();
flush_batch(&fctx);
[encoder setScissorRect:full_scissor];
} break;
@@ -906,7 +923,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
if (custom->customData) {
CustomRenderType type = *(CustomRenderType *)custom->customData;
if (type == CUSTOM_RENDER_VGRADIENT) {
bind_font_texture();
bind_font_texture(&fctx);
CustomGradientData *grad = (CustomGradientData *)custom->customData;
Clay_Color tc = grad->top_color;
Clay_Color bc = grad->bottom_color;
@@ -918,7 +935,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft,
1.0f);
} else if (type == CUSTOM_RENDER_ICON) {
bind_icon_texture();
bind_icon_texture(&fctx);
CustomIconData *icon = (CustomIconData *)custom->customData;
Clay_Color c = icon->color;
F32 cr = c.r / 255.f, cg = c.g / 255.f;
@@ -932,7 +949,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
0, 0, 0, 0,
0, 0, 2.0f);
} else if (type == CUSTOM_RENDER_ROTATED_ICON) {
bind_icon_texture();
bind_icon_texture(&fctx);
CustomRotatedIconData *ri = (CustomRotatedIconData *)custom->customData;
Clay_Color c = ri->color;
F32 cr = c.r / 255.f, cg = c.g / 255.f;
@@ -953,7 +970,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
}
}
flush_batch();
flush_batch(&fctx);
}
[encoder endEncoding];

View File

@@ -14,12 +14,6 @@
#define VK_USE_PLATFORM_WIN32_KHR
#include <vulkan/vulkan.h>
// VMA — single-file implementation in this translation unit
#define VMA_IMPLEMENTATION
#define VMA_STATIC_VULKAN_FUNCTIONS 1
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
#include <vma/vk_mem_alloc.h>
// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
// because FreeType uses `internal` as a struct field name.
#undef internal
@@ -33,6 +27,13 @@
#include "renderer/ui_vert.spv.h"
#include "renderer/ui_frag.spv.h"
// MSVC ICE workaround: prevent inlining of large Vulkan setup functions
#ifdef _MSC_VER
#define VK_NOINLINE __declspec(noinline)
#else
#define VK_NOINLINE
#endif
#define NUM_BACK_BUFFERS 2
#define MAX_VERTICES (64 * 1024)
#define MAX_INDICES (MAX_VERTICES * 3)
@@ -95,8 +96,6 @@ struct Renderer {
VkDevice device;
VkQueue graphics_queue;
U32 queue_family;
VmaAllocator allocator;
// Surface & swap chain
VkSurfaceKHR surface;
VkSwapchainKHR swap_chain;
@@ -130,16 +129,16 @@ struct Renderer {
// Per-frame vertex/index buffers (double-buffered, persistently mapped)
VkBuffer vertex_buffers[NUM_BACK_BUFFERS];
VmaAllocation vertex_allocs[NUM_BACK_BUFFERS];
VkDeviceMemory vertex_memory[NUM_BACK_BUFFERS];
void *vb_mapped[NUM_BACK_BUFFERS];
VkBuffer index_buffers[NUM_BACK_BUFFERS];
VmaAllocation index_allocs[NUM_BACK_BUFFERS];
VkDeviceMemory index_memory[NUM_BACK_BUFFERS];
void *ib_mapped[NUM_BACK_BUFFERS];
// Font atlas
VkImage font_image;
VmaAllocation font_alloc;
VkDeviceMemory font_memory;
VkImageView font_view;
GlyphInfo glyphs[GLYPH_COUNT];
F32 font_atlas_size;
@@ -147,7 +146,7 @@ struct Renderer {
// Icon atlas
VkImage icon_image;
VmaAllocation icon_alloc;
VkDeviceMemory icon_memory;
VkImageView icon_view;
// FreeType
@@ -242,7 +241,7 @@ static void transition_image(VkCommandBuffer cb, VkImage image,
////////////////////////////////
// Vulkan infrastructure
static B32 create_instance(Renderer *r) {
static VK_NOINLINE B32 create_instance(Renderer *r) {
VkApplicationInfo app_info = {0};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = "autosample";
@@ -296,7 +295,7 @@ static B32 create_instance(Renderer *r) {
return 1;
}
static B32 create_surface(Renderer *r) {
static VK_NOINLINE B32 create_surface(Renderer *r) {
VkWin32SurfaceCreateInfoKHR ci = {0};
ci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
ci.hinstance = GetModuleHandle(NULL);
@@ -304,7 +303,7 @@ static B32 create_surface(Renderer *r) {
return vkCreateWin32SurfaceKHR(r->instance, &ci, NULL, &r->surface) == VK_SUCCESS;
}
static B32 pick_physical_device(Renderer *r) {
static VK_NOINLINE B32 pick_physical_device(Renderer *r) {
U32 count = 0;
vkEnumeratePhysicalDevices(r->instance, &count, NULL);
if (count == 0) return 0;
@@ -326,7 +325,7 @@ static B32 pick_physical_device(Renderer *r) {
return 1;
}
static B32 find_queue_family(Renderer *r) {
static VK_NOINLINE B32 find_queue_family(Renderer *r) {
U32 count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &count, NULL);
VkQueueFamilyProperties props[64];
@@ -344,7 +343,7 @@ static B32 find_queue_family(Renderer *r) {
return 0;
}
static B32 create_device(Renderer *r) {
static VK_NOINLINE B32 create_device(Renderer *r) {
float priority = 1.0f;
VkDeviceQueueCreateInfo queue_ci = {0};
queue_ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
@@ -368,16 +367,17 @@ static B32 create_device(Renderer *r) {
return 1;
}
static B32 create_vma(Renderer *r) {
VmaAllocatorCreateInfo ci = {0};
ci.physicalDevice = r->physical_device;
ci.device = r->device;
ci.instance = r->instance;
ci.vulkanApiVersion = VK_API_VERSION_1_2;
return vmaCreateAllocator(&ci, &r->allocator) == VK_SUCCESS;
static U32 find_memory_type(VkPhysicalDevice phys_dev, U32 type_filter, VkMemoryPropertyFlags props) {
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(phys_dev, &mem_props);
for (U32 i = 0; i < mem_props.memoryTypeCount; i++) {
if ((type_filter & (1 << i)) && (mem_props.memoryTypes[i].propertyFlags & props) == props)
return i;
}
return 0; // fallback
}
static B32 create_swap_chain(Renderer *r) {
static VK_NOINLINE B32 create_swap_chain(Renderer *r) {
VkSurfaceCapabilitiesKHR caps;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(r->physical_device, r->surface, &caps);
@@ -468,7 +468,7 @@ static B32 create_swap_chain(Renderer *r) {
return 1;
}
static B32 create_render_pass(Renderer *r) {
static VK_NOINLINE B32 create_render_pass(Renderer *r) {
VkAttachmentDescription color_attach = {0};
color_attach.format = r->swap_chain_format;
color_attach.samples = VK_SAMPLE_COUNT_1_BIT;
@@ -508,7 +508,7 @@ static B32 create_render_pass(Renderer *r) {
return vkCreateRenderPass(r->device, &ci, NULL, &r->render_pass) == VK_SUCCESS;
}
static B32 create_framebuffers(Renderer *r) {
static VK_NOINLINE B32 create_framebuffers(Renderer *r) {
for (U32 i = 0; i < NUM_BACK_BUFFERS; i++) {
VkFramebufferCreateInfo ci = {0};
ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
@@ -524,7 +524,7 @@ static B32 create_framebuffers(Renderer *r) {
return 1;
}
static B32 create_command_resources(Renderer *r) {
static VK_NOINLINE B32 create_command_resources(Renderer *r) {
VkCommandPoolCreateInfo pool_ci = {0};
pool_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
@@ -564,7 +564,7 @@ static B32 create_command_resources(Renderer *r) {
return 1;
}
static B32 create_sampler(Renderer *r) {
static VK_NOINLINE B32 create_sampler(Renderer *r) {
VkSamplerCreateInfo ci = {0};
ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
ci.magFilter = VK_FILTER_LINEAR;
@@ -576,7 +576,7 @@ static B32 create_sampler(Renderer *r) {
return vkCreateSampler(r->device, &ci, NULL, &r->sampler) == VK_SUCCESS;
}
static B32 create_descriptor_resources(Renderer *r) {
static VK_NOINLINE B32 create_descriptor_resources(Renderer *r) {
// Descriptor set layout: single combined image sampler at binding 0
VkDescriptorSetLayoutBinding binding = {0};
binding.binding = 0;
@@ -626,7 +626,7 @@ static VkShaderModule create_shader_module(Renderer *r, const U32 *code, size_t
return module;
}
static B32 create_pipeline(Renderer *r) {
static VK_NOINLINE B32 create_pipeline(Renderer *r) {
VkShaderModule vert_module = create_shader_module(r, ui_vert_spv, sizeof(ui_vert_spv));
VkShaderModule frag_module = create_shader_module(r, ui_frag_spv, sizeof(ui_frag_spv));
if (!vert_module || !frag_module) return 0;
@@ -647,7 +647,7 @@ static B32 create_pipeline(Renderer *r) {
binding.stride = sizeof(UIVertex);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attrs[9] = {};
VkVertexInputAttributeDescription attrs[9] = {0};
attrs[0].location = 0; attrs[0].binding = 0; attrs[0].format = VK_FORMAT_R32G32_SFLOAT; attrs[0].offset = offsetof(UIVertex, pos);
attrs[1].location = 1; attrs[1].binding = 0; attrs[1].format = VK_FORMAT_R32G32_SFLOAT; attrs[1].offset = offsetof(UIVertex, uv);
attrs[2].location = 2; attrs[2].binding = 0; attrs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT; attrs[2].offset = offsetof(UIVertex, col);
@@ -753,29 +753,42 @@ static B32 create_pipeline(Renderer *r) {
return result == VK_SUCCESS;
}
static B32 create_ui_buffers(Renderer *r) {
static VK_NOINLINE B32 create_ui_buffers(Renderer *r) {
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
// Vertex buffer
VkBufferCreateInfo buf_ci = {0};
buf_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buf_ci.size = MAX_VERTICES * sizeof(UIVertex);
buf_ci.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationCreateInfo alloc_ci = {0};
alloc_ci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VmaAllocationInfo alloc_info;
if (vmaCreateBuffer(r->allocator, &buf_ci, &alloc_ci,
&r->vertex_buffers[i], &r->vertex_allocs[i], &alloc_info) != VK_SUCCESS)
if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->vertex_buffers[i]) != VK_SUCCESS)
return 0;
r->vb_mapped[i] = alloc_info.pMappedData;
VkMemoryRequirements mem_req;
vkGetBufferMemoryRequirements(r->device, r->vertex_buffers[i], &mem_req);
VkMemoryAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->vertex_memory[i]) != VK_SUCCESS)
return 0;
vkBindBufferMemory(r->device, r->vertex_buffers[i], r->vertex_memory[i], 0);
vkMapMemory(r->device, r->vertex_memory[i], 0, buf_ci.size, 0, &r->vb_mapped[i]);
// Index buffer
buf_ci.size = MAX_INDICES * sizeof(U32);
buf_ci.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
if (vmaCreateBuffer(r->allocator, &buf_ci, &alloc_ci,
&r->index_buffers[i], &r->index_allocs[i], &alloc_info) != VK_SUCCESS)
if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->index_buffers[i]) != VK_SUCCESS)
return 0;
r->ib_mapped[i] = alloc_info.pMappedData;
vkGetBufferMemoryRequirements(r->device, r->index_buffers[i], &mem_req);
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->index_memory[i]) != VK_SUCCESS)
return 0;
vkBindBufferMemory(r->device, r->index_buffers[i], r->index_memory[i], 0);
vkMapMemory(r->device, r->index_memory[i], 0, buf_ci.size, 0, &r->ib_mapped[i]);
}
return 1;
}
@@ -807,7 +820,7 @@ static void init_freetype(Renderer *r) {
FT_New_Memory_Face(r->ft_lib, font_inter_data, font_inter_size, 0, &r->ft_face);
}
static B32 create_font_atlas(Renderer *r, F32 font_size) {
static VK_NOINLINE B32 create_font_atlas(Renderer *r, F32 font_size) {
S32 pixel_size = (S32)(font_size + 0.5f);
r->font_atlas_size = font_size;
@@ -875,13 +888,24 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
VmaAllocationCreateInfo alloc_ci = {0};
alloc_ci.usage = VMA_MEMORY_USAGE_GPU_ONLY;
if (vmaCreateImage(r->allocator, &img_ci, &alloc_ci, &r->font_image, &r->font_alloc, NULL) != VK_SUCCESS) {
if (vkCreateImage(r->device, &img_ci, NULL, &r->font_image) != VK_SUCCESS) {
free(atlas_data);
return 0;
}
{
VkMemoryRequirements mem_req;
vkGetImageMemoryRequirements(r->device, r->font_image, &mem_req);
VkMemoryAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->font_memory) != VK_SUCCESS) {
free(atlas_data);
return 0;
}
vkBindImageMemory(r->device, r->font_image, r->font_memory, 0);
}
// Staging buffer
VkBufferCreateInfo staging_ci = {0};
@@ -889,15 +913,23 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
staging_ci.size = FONT_ATLAS_W * FONT_ATLAS_H;
staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo staging_alloc_ci = {0};
staging_alloc_ci.usage = VMA_MEMORY_USAGE_CPU_ONLY;
staging_alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer staging_buf;
VmaAllocation staging_alloc;
VmaAllocationInfo staging_info;
vmaCreateBuffer(r->allocator, &staging_ci, &staging_alloc_ci, &staging_buf, &staging_alloc, &staging_info);
memcpy(staging_info.pMappedData, atlas_data, FONT_ATLAS_W * FONT_ATLAS_H);
VkDeviceMemory staging_memory;
void *staging_mapped;
vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
{
VkMemoryRequirements mem_req;
vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
VkMemoryAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
}
memcpy(staging_mapped, atlas_data, FONT_ATLAS_W * FONT_ATLAS_H);
free(atlas_data);
// Copy via command buffer
@@ -923,7 +955,8 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
end_one_shot(r, cb);
vmaDestroyBuffer(r->allocator, staging_buf, staging_alloc);
vkDestroyBuffer(r->device, staging_buf, NULL);
vkFreeMemory(r->device, staging_memory, NULL);
// Image view
VkImageViewCreateInfo view_ci = {0};
@@ -1223,7 +1256,6 @@ Renderer *renderer_create(RendererDesc *desc) {
if (!pick_physical_device(r)) goto fail;
if (!find_queue_family(r)) goto fail;
if (!create_device(r)) goto fail;
if (!create_vma(r)) goto fail;
if (!create_command_resources(r)) goto fail;
if (!create_sampler(r)) goto fail;
if (!create_descriptor_resources(r)) goto fail;
@@ -1261,18 +1293,17 @@ Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
r->device = parent->device;
r->graphics_queue = parent->graphics_queue;
r->queue_family = parent->queue_family;
r->allocator = parent->allocator;
r->render_pass = parent->render_pass;
r->pipeline_layout = parent->pipeline_layout;
r->pipeline = parent->pipeline;
r->descriptor_set_layout = parent->descriptor_set_layout;
r->sampler = parent->sampler;
r->font_image = parent->font_image;
r->font_alloc = parent->font_alloc;
r->font_memory = parent->font_memory;
r->font_view = parent->font_view;
r->font_descriptor_set = parent->font_descriptor_set;
r->icon_image = parent->icon_image;
r->icon_alloc = parent->icon_alloc;
r->icon_memory = parent->icon_memory;
r->icon_view = parent->icon_view;
r->icon_descriptor_set = parent->icon_descriptor_set;
r->descriptor_pool = parent->descriptor_pool;
@@ -1308,10 +1339,14 @@ void renderer_destroy(Renderer *r) {
// Per-window resources
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
if (r->vertex_buffers[i])
vmaDestroyBuffer(r->allocator, r->vertex_buffers[i], r->vertex_allocs[i]);
if (r->index_buffers[i])
vmaDestroyBuffer(r->allocator, r->index_buffers[i], r->index_allocs[i]);
if (r->vertex_buffers[i]) {
vkDestroyBuffer(r->device, r->vertex_buffers[i], NULL);
vkFreeMemory(r->device, r->vertex_memory[i], NULL);
}
if (r->index_buffers[i]) {
vkDestroyBuffer(r->device, r->index_buffers[i], NULL);
vkFreeMemory(r->device, r->index_memory[i], NULL);
}
}
for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
@@ -1333,9 +1368,9 @@ void renderer_destroy(Renderer *r) {
// Shared resources only freed by root renderer
if (!r->parent) {
if (r->font_view) vkDestroyImageView(r->device, r->font_view, NULL);
if (r->font_image) vmaDestroyImage(r->allocator, r->font_image, r->font_alloc);
if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); }
if (r->icon_view) vkDestroyImageView(r->device, r->icon_view, NULL);
if (r->icon_image) vmaDestroyImage(r->allocator, r->icon_image, r->icon_alloc);
if (r->icon_image) { vkDestroyImage(r->device, r->icon_image, NULL); vkFreeMemory(r->device, r->icon_memory, NULL); }
if (r->pipeline) vkDestroyPipeline(r->device, r->pipeline, NULL);
if (r->pipeline_layout) vkDestroyPipelineLayout(r->device, r->pipeline_layout, NULL);
@@ -1347,7 +1382,6 @@ void renderer_destroy(Renderer *r) {
if (r->ft_face) FT_Done_Face(r->ft_face);
if (r->ft_lib) FT_Done_FreeType(r->ft_lib);
if (r->allocator) vmaDestroyAllocator(r->allocator);
if (r->device) vkDestroyDevice(r->device, NULL);
#ifdef _DEBUG
@@ -1385,7 +1419,7 @@ B32 renderer_begin_frame(Renderer *r) {
return 1;
}
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
U32 frame_idx = r->frame_index % NUM_BACK_BUFFERS;
FrameContext *fc = &r->frames[frame_idx];
@@ -1440,7 +1474,7 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
vkCmdSetScissor(cb, 0, 1, &scissor);
// Process Clay render commands
if (render_commands.length > 0) {
if (render_commands->length > 0) {
DrawBatch batch = {0};
batch.vertices = (UIVertex *)r->vb_mapped[frame_idx];
batch.indices = (U32 *)r->ib_mapped[frame_idx];
@@ -1448,8 +1482,8 @@ void renderer_end_frame(Renderer *r, Clay_RenderCommandArray render_commands) {
S32 bound_texture = 0; // 0 = font, 1 = icon
U32 flush_index_start = 0;
for (S32 i = 0; i < render_commands.length; i++) {
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&render_commands, i);
for (S32 i = 0; i < render_commands->length; i++) {
Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(render_commands, i);
Clay_BoundingBox bb = cmd->boundingBox;
switch (cmd->commandType) {
@@ -1655,10 +1689,18 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
VmaAllocationCreateInfo alloc_ci = {0};
alloc_ci.usage = VMA_MEMORY_USAGE_GPU_ONLY;
vmaCreateImage(r->allocator, &img_ci, &alloc_ci, &r->icon_image, &r->icon_alloc, NULL);
vkCreateImage(r->device, &img_ci, NULL, &r->icon_image);
{
VkMemoryRequirements mem_req;
vkGetImageMemoryRequirements(r->device, r->icon_image, &mem_req);
VkMemoryAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
vkAllocateMemory(r->device, &alloc_info, NULL, &r->icon_memory);
vkBindImageMemory(r->device, r->icon_image, r->icon_memory, 0);
}
// Staging buffer
VkBufferCreateInfo staging_ci = {0};
@@ -1666,15 +1708,23 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
staging_ci.size = (VkDeviceSize)w * h * 4;
staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo staging_alloc_ci = {0};
staging_alloc_ci.usage = VMA_MEMORY_USAGE_CPU_ONLY;
staging_alloc_ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer staging_buf;
VmaAllocation staging_alloc;
VmaAllocationInfo staging_info;
vmaCreateBuffer(r->allocator, &staging_ci, &staging_alloc_ci, &staging_buf, &staging_alloc, &staging_info);
memcpy(staging_info.pMappedData, data, (size_t)w * h * 4);
VkDeviceMemory staging_memory;
void *staging_mapped;
vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
{
VkMemoryRequirements mem_req;
vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
VkMemoryAllocateInfo alloc_info = {0};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_req.size;
alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
}
memcpy(staging_mapped, data, (size_t)w * h * 4);
VkCommandBuffer cb = begin_one_shot(r);
@@ -1697,7 +1747,8 @@ void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
end_one_shot(r, cb);
vmaDestroyBuffer(r->allocator, staging_buf, staging_alloc);
vkDestroyBuffer(r->device, staging_buf, NULL);
vkFreeMemory(r->device, staging_memory, NULL);
// Image view
VkImageViewCreateInfo view_ci = {0};
@@ -1756,7 +1807,7 @@ void renderer_set_font_scale(Renderer *r, F32 scale) {
if (fabsf(target_size - r->font_atlas_size) < 0.1f) return;
vkDeviceWaitIdle(r->device);
if (r->font_view) { vkDestroyImageView(r->device, r->font_view, NULL); r->font_view = VK_NULL_HANDLE; }
if (r->font_image) { vmaDestroyImage(r->allocator, r->font_image, r->font_alloc); r->font_image = VK_NULL_HANDLE; }
if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); r->font_image = VK_NULL_HANDLE; }
create_font_atlas(r, target_size);
}

View File

@@ -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();
}

View File

@@ -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
View 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;
}

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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),

View File

@@ -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);

View File

@@ -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]);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);