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