Format all
This commit is contained in:
31
.clang-format
Normal file
31
.clang-format
Normal file
@@ -0,0 +1,31 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
AlignConsecutiveDeclarations: true
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AlignConsecutiveMacros: true
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
Cpp11BracedListStyle: false
|
||||
ColumnLimit: 0
|
||||
@@ -5,19 +5,19 @@
|
||||
struct AudioEngine;
|
||||
|
||||
struct AudioDeviceInfo {
|
||||
char name[128];
|
||||
S32 id; // index into engine's device list
|
||||
char name[128];
|
||||
S32 id; // index into engine's device list
|
||||
};
|
||||
|
||||
AudioEngine *audio_create(void *hwnd);
|
||||
void audio_destroy(AudioEngine *engine);
|
||||
void audio_refresh_devices(AudioEngine *engine);
|
||||
S32 audio_get_device_count(AudioEngine *engine);
|
||||
AudioDeviceInfo*audio_get_device(AudioEngine *engine, S32 index);
|
||||
AudioEngine *audio_create(void *hwnd);
|
||||
void audio_destroy(AudioEngine *engine);
|
||||
void audio_refresh_devices(AudioEngine *engine);
|
||||
S32 audio_get_device_count(AudioEngine *engine);
|
||||
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index);
|
||||
|
||||
B32 audio_open_device(AudioEngine *engine, S32 index);
|
||||
void audio_close_device(AudioEngine *engine);
|
||||
B32 audio_open_device(AudioEngine *engine, S32 index);
|
||||
void audio_close_device(AudioEngine *engine);
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine);
|
||||
B32 audio_is_test_tone_playing(AudioEngine *engine);
|
||||
void audio_update(AudioEngine *engine, F32 dt);
|
||||
void audio_play_test_tone(AudioEngine *engine);
|
||||
B32 audio_is_test_tone_playing(AudioEngine *engine);
|
||||
void audio_update(AudioEngine *engine, F32 dt);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "audio/audio.h"
|
||||
#include <windows.h>
|
||||
#include <math.h>
|
||||
#include <objbase.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
|
||||
#define AUDIO_MAX_DEVICES 32
|
||||
#define AUDIO_MAX_CHANNELS 32
|
||||
@@ -13,21 +13,21 @@
|
||||
////////////////////////////////
|
||||
// ASIO type definitions (minimal set for a host)
|
||||
|
||||
typedef long ASIOError;
|
||||
typedef long ASIOBool;
|
||||
typedef long ASIOError;
|
||||
typedef long ASIOBool;
|
||||
typedef long long int ASIOSamples;
|
||||
typedef long long int ASIOTimeStamp;
|
||||
|
||||
enum {
|
||||
ASE_OK = 0,
|
||||
ASE_SUCCESS = 0x3f4847a0,
|
||||
ASE_NotPresent = -1000,
|
||||
ASE_HWMalfunction = -999,
|
||||
ASE_OK = 0,
|
||||
ASE_SUCCESS = 0x3f4847a0,
|
||||
ASE_NotPresent = -1000,
|
||||
ASE_HWMalfunction = -999,
|
||||
ASE_InvalidParameter = -998,
|
||||
ASE_InvalidMode = -997,
|
||||
ASE_SPNotAdvancing = -996,
|
||||
ASE_NoClock = -995,
|
||||
ASE_NoMemory = -994,
|
||||
ASE_InvalidMode = -997,
|
||||
ASE_SPNotAdvancing = -996,
|
||||
ASE_NoClock = -995,
|
||||
ASE_NoMemory = -994,
|
||||
};
|
||||
|
||||
enum ASIOSampleType {
|
||||
@@ -55,20 +55,20 @@ enum ASIOSampleType {
|
||||
};
|
||||
|
||||
struct ASIOClockSource {
|
||||
long index;
|
||||
long channel;
|
||||
long group;
|
||||
long index;
|
||||
long channel;
|
||||
long group;
|
||||
ASIOBool isCurrentSource;
|
||||
char name[32];
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct ASIOChannelInfo {
|
||||
long channel;
|
||||
ASIOBool isInput;
|
||||
ASIOBool isActive;
|
||||
long channelGroup;
|
||||
long channel;
|
||||
ASIOBool isInput;
|
||||
ASIOBool isActive;
|
||||
long channelGroup;
|
||||
ASIOSampleType type;
|
||||
char name[32];
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct ASIOBufferInfo {
|
||||
@@ -78,31 +78,31 @@ struct ASIOBufferInfo {
|
||||
};
|
||||
|
||||
struct ASIOTimeCode {
|
||||
F64 speed;
|
||||
ASIOSamples timeCodeSamples;
|
||||
unsigned long flags;
|
||||
char future[64];
|
||||
F64 speed;
|
||||
ASIOSamples timeCodeSamples;
|
||||
unsigned long flags;
|
||||
char future[64];
|
||||
};
|
||||
|
||||
struct AsioTimeInfo {
|
||||
F64 speed;
|
||||
ASIOTimeStamp systemTime;
|
||||
ASIOSamples samplePosition;
|
||||
F64 sampleRate;
|
||||
unsigned long flags;
|
||||
char reserved[12];
|
||||
F64 speed;
|
||||
ASIOTimeStamp systemTime;
|
||||
ASIOSamples samplePosition;
|
||||
F64 sampleRate;
|
||||
unsigned long flags;
|
||||
char reserved[12];
|
||||
};
|
||||
|
||||
struct ASIOTime {
|
||||
long reserved[4];
|
||||
AsioTimeInfo timeInfo;
|
||||
ASIOTimeCode timeCode;
|
||||
long reserved[4];
|
||||
AsioTimeInfo timeInfo;
|
||||
ASIOTimeCode timeCode;
|
||||
};
|
||||
|
||||
struct ASIOCallbacks {
|
||||
void (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess);
|
||||
void (*sampleRateDidChange)(F64 sRate);
|
||||
long (*asioMessage)(long selector, long value, void *message, F64 *opt);
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -124,35 +124,35 @@ enum {
|
||||
// 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;
|
||||
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;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Internal state
|
||||
|
||||
struct AsioDriverInfo {
|
||||
char name[128];
|
||||
char name[128];
|
||||
CLSID clsid;
|
||||
};
|
||||
|
||||
@@ -162,25 +162,25 @@ struct AudioEngine {
|
||||
// Device enumeration
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
AsioDriverInfo drivers[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
S32 device_count;
|
||||
|
||||
// Active driver
|
||||
IASIO *driver;
|
||||
S32 active_device_index; // -1 = none
|
||||
IASIO *driver;
|
||||
S32 active_device_index; // -1 = none
|
||||
|
||||
// Buffer state
|
||||
ASIOBufferInfo buffer_infos[AUDIO_MAX_CHANNELS];
|
||||
long num_output_channels;
|
||||
long num_input_channels;
|
||||
long buffer_size;
|
||||
F64 sample_rate;
|
||||
ASIOSampleType output_sample_type;
|
||||
ASIOCallbacks callbacks;
|
||||
ASIOBufferInfo buffer_infos[AUDIO_MAX_CHANNELS];
|
||||
long num_output_channels;
|
||||
long num_input_channels;
|
||||
long buffer_size;
|
||||
F64 sample_rate;
|
||||
ASIOSampleType output_sample_type;
|
||||
ASIOCallbacks callbacks;
|
||||
|
||||
// Test tone state (accessed from callback thread)
|
||||
volatile LONG test_tone_active;
|
||||
volatile LONG test_tone_samples_remaining;
|
||||
F64 test_tone_phase; // written only from callback thread
|
||||
volatile LONG test_tone_active;
|
||||
volatile LONG test_tone_samples_remaining;
|
||||
F64 test_tone_phase; // written only from callback thread
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -204,9 +204,9 @@ static void write_sample(void *dest, ASIOSampleType type, F64 value) {
|
||||
case ASIOSTInt24LSB: {
|
||||
S32 s = (S32)(value * 8388607.0);
|
||||
U8 *d = (U8 *)dest;
|
||||
d[0] = (U8)(s & 0xFF);
|
||||
d[1] = (U8)((s >> 8) & 0xFF);
|
||||
d[2] = (U8)((s >> 16) & 0xFF);
|
||||
d[0] = (U8)(s & 0xFF);
|
||||
d[1] = (U8)((s >> 8) & 0xFF);
|
||||
d[2] = (U8)((s >> 16) & 0xFF);
|
||||
} break;
|
||||
case ASIOSTInt32LSB: {
|
||||
S32 s = (S32)(value * 2147483647.0);
|
||||
@@ -229,12 +229,12 @@ static void write_sample(void *dest, ASIOSampleType type, F64 value) {
|
||||
|
||||
static int sample_type_size(ASIOSampleType type) {
|
||||
switch (type) {
|
||||
case ASIOSTInt16LSB: return 2;
|
||||
case ASIOSTInt24LSB: return 3;
|
||||
case ASIOSTInt32LSB: return 4;
|
||||
case ASIOSTInt16LSB: return 2;
|
||||
case ASIOSTInt24LSB: return 3;
|
||||
case ASIOSTInt32LSB: return 4;
|
||||
case ASIOSTFloat32LSB: return 4;
|
||||
case ASIOSTFloat64LSB: return 8;
|
||||
default: return 4;
|
||||
default: return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,9 +247,9 @@ static void asio_buffer_switch(long doubleBufferIndex, ASIOBool directProcess) {
|
||||
AudioEngine *engine = g_audio_engine;
|
||||
if (!engine) return;
|
||||
|
||||
long buf_size = engine->buffer_size;
|
||||
ASIOSampleType type = engine->output_sample_type;
|
||||
S32 bytes_per_sample = sample_type_size(type);
|
||||
long buf_size = engine->buffer_size;
|
||||
ASIOSampleType type = engine->output_sample_type;
|
||||
S32 bytes_per_sample = sample_type_size(type);
|
||||
|
||||
LONG tone_active = InterlockedCompareExchange(&engine->test_tone_active, 0, 0);
|
||||
|
||||
@@ -266,9 +266,9 @@ static void asio_buffer_switch(long doubleBufferIndex, ASIOBool directProcess) {
|
||||
}
|
||||
|
||||
if (tone_active) {
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
LONG remaining = InterlockedCompareExchange(&engine->test_tone_samples_remaining, 0, 0);
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
LONG remaining = InterlockedCompareExchange(&engine->test_tone_samples_remaining, 0, 0);
|
||||
long samples_to_gen = (remaining < buf_size) ? (long)remaining : buf_size;
|
||||
|
||||
for (long s = 0; s < buf_size; s++) {
|
||||
@@ -346,7 +346,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO", 0, KEY_READ, &asio_key) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
char subkey_name[256];
|
||||
char subkey_name[256];
|
||||
DWORD subkey_name_len;
|
||||
|
||||
for (DWORD i = 0; engine->device_count < AUDIO_MAX_DEVICES; i++) {
|
||||
@@ -359,9 +359,9 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
continue;
|
||||
|
||||
// Read CLSID string
|
||||
char clsid_str[64] = {};
|
||||
DWORD clsid_len = sizeof(clsid_str);
|
||||
DWORD type = 0;
|
||||
char clsid_str[64] = {};
|
||||
DWORD clsid_len = sizeof(clsid_str);
|
||||
DWORD type = 0;
|
||||
if (RegQueryValueExA(driver_key, "CLSID", nullptr, &type, (LPBYTE)clsid_str, &clsid_len) != ERROR_SUCCESS ||
|
||||
type != REG_SZ) {
|
||||
RegCloseKey(driver_key);
|
||||
@@ -369,7 +369,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
}
|
||||
|
||||
// Parse CLSID string to GUID
|
||||
CLSID clsid;
|
||||
CLSID clsid;
|
||||
wchar_t clsid_wide[64];
|
||||
MultiByteToWideChar(CP_ACP, 0, clsid_str, -1, clsid_wide, 64);
|
||||
if (CLSIDFromString(clsid_wide, &clsid) != S_OK) {
|
||||
@@ -379,7 +379,7 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
|
||||
S32 idx = engine->device_count++;
|
||||
strncpy_s(engine->devices[idx].name, sizeof(engine->devices[idx].name), subkey_name, _TRUNCATE);
|
||||
engine->devices[idx].id = idx;
|
||||
engine->devices[idx].id = idx;
|
||||
engine->drivers[idx].clsid = clsid;
|
||||
strncpy_s(engine->drivers[idx].name, sizeof(engine->drivers[idx].name), subkey_name, _TRUNCATE);
|
||||
|
||||
@@ -395,9 +395,9 @@ static void enumerate_asio_drivers(AudioEngine *engine) {
|
||||
AudioEngine *audio_create(void *hwnd) {
|
||||
AudioEngine *engine = new AudioEngine();
|
||||
memset(engine, 0, sizeof(*engine));
|
||||
engine->hwnd = hwnd;
|
||||
engine->hwnd = hwnd;
|
||||
engine->active_device_index = -1;
|
||||
g_audio_engine = engine;
|
||||
g_audio_engine = engine;
|
||||
|
||||
CoInitialize(nullptr);
|
||||
enumerate_asio_drivers(engine);
|
||||
@@ -433,11 +433,11 @@ 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,
|
||||
(void **)&driver);
|
||||
IASIO *driver = nullptr;
|
||||
HRESULT hr = CoCreateInstance(engine->drivers[index].clsid,
|
||||
nullptr, CLSCTX_INPROC_SERVER,
|
||||
engine->drivers[index].clsid,
|
||||
(void **)&driver);
|
||||
if (FAILED(hr) || !driver)
|
||||
return false;
|
||||
|
||||
@@ -475,8 +475,8 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
|
||||
// Query output channel sample type
|
||||
ASIOChannelInfo chan_info = {};
|
||||
chan_info.channel = 0;
|
||||
chan_info.isInput = 0;
|
||||
chan_info.channel = 0;
|
||||
chan_info.isInput = 0;
|
||||
if (driver->getChannelInfo(&chan_info) != ASE_OK) {
|
||||
driver->Release();
|
||||
return false;
|
||||
@@ -488,16 +488,16 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
// Setup buffer infos for output channels only
|
||||
memset(engine->buffer_infos, 0, sizeof(engine->buffer_infos));
|
||||
for (long ch = 0; ch < num_out; ch++) {
|
||||
engine->buffer_infos[ch].isInput = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
// Setup callbacks
|
||||
engine->callbacks.bufferSwitch = asio_buffer_switch;
|
||||
engine->callbacks.sampleRateDidChange = asio_sample_rate_changed;
|
||||
engine->callbacks.asioMessage = asio_message;
|
||||
engine->callbacks.bufferSwitch = asio_buffer_switch;
|
||||
engine->callbacks.sampleRateDidChange = asio_sample_rate_changed;
|
||||
engine->callbacks.asioMessage = asio_message;
|
||||
engine->callbacks.bufferSwitchTimeInfo = asio_buffer_switch_time_info;
|
||||
|
||||
// Create buffers
|
||||
@@ -507,22 +507,22 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
}
|
||||
|
||||
// Store state
|
||||
engine->driver = driver;
|
||||
engine->active_device_index = index;
|
||||
engine->num_output_channels = num_out;
|
||||
engine->num_input_channels = num_in;
|
||||
engine->buffer_size = preferred_size;
|
||||
engine->sample_rate = sample_rate;
|
||||
engine->output_sample_type = chan_info.type;
|
||||
engine->test_tone_active = 0;
|
||||
engine->driver = driver;
|
||||
engine->active_device_index = index;
|
||||
engine->num_output_channels = num_out;
|
||||
engine->num_input_channels = num_in;
|
||||
engine->buffer_size = preferred_size;
|
||||
engine->sample_rate = sample_rate;
|
||||
engine->output_sample_type = chan_info.type;
|
||||
engine->test_tone_active = 0;
|
||||
engine->test_tone_samples_remaining = 0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
|
||||
// Start the driver
|
||||
if (driver->start() != ASE_OK) {
|
||||
driver->disposeBuffers();
|
||||
driver->Release();
|
||||
engine->driver = nullptr;
|
||||
engine->driver = nullptr;
|
||||
engine->active_device_index = -1;
|
||||
return false;
|
||||
}
|
||||
@@ -542,16 +542,16 @@ void audio_close_device(AudioEngine *engine) {
|
||||
engine->driver->stop();
|
||||
engine->driver->disposeBuffers();
|
||||
engine->driver->Release();
|
||||
engine->driver = nullptr;
|
||||
engine->driver = nullptr;
|
||||
engine->active_device_index = -1;
|
||||
engine->test_tone_phase = 0.0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine) {
|
||||
if (!engine->driver) return;
|
||||
|
||||
engine->test_tone_phase = 0.0;
|
||||
LONG total_samples = (LONG)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
LONG total_samples = (LONG)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
InterlockedExchange(&engine->test_tone_samples_remaining, total_samples);
|
||||
InterlockedExchange(&engine->test_tone_active, 1);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "audio/audio.h"
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -18,20 +18,20 @@ struct CoreAudioDeviceInfo {
|
||||
};
|
||||
|
||||
struct AudioEngine {
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
CoreAudioDeviceInfo ca_devices[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
|
||||
CoreAudioDeviceInfo ca_devices[AUDIO_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
|
||||
AUGraph graph;
|
||||
AudioUnit output_unit;
|
||||
S32 active_device_index;
|
||||
F64 sample_rate;
|
||||
S32 num_channels;
|
||||
AUGraph graph;
|
||||
AudioUnit output_unit;
|
||||
S32 active_device_index;
|
||||
F64 sample_rate;
|
||||
S32 num_channels;
|
||||
|
||||
// Test tone state (accessed from audio render thread)
|
||||
_Atomic S32 test_tone_active;
|
||||
_Atomic S32 test_tone_samples_remaining;
|
||||
F64 test_tone_phase;
|
||||
_Atomic S32 test_tone_active;
|
||||
_Atomic S32 test_tone_samples_remaining;
|
||||
F64 test_tone_phase;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -39,14 +39,16 @@ struct AudioEngine {
|
||||
|
||||
static AudioEngine *g_audio_engine = nullptr;
|
||||
|
||||
static OSStatus audio_render_callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData)
|
||||
{
|
||||
(void)inRefCon; (void)ioActionFlags; (void)inTimeStamp; (void)inBusNumber;
|
||||
static OSStatus audio_render_callback(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) {
|
||||
(void)inRefCon;
|
||||
(void)ioActionFlags;
|
||||
(void)inTimeStamp;
|
||||
(void)inBusNumber;
|
||||
|
||||
AudioEngine *engine = g_audio_engine;
|
||||
if (!engine) {
|
||||
@@ -63,15 +65,15 @@ static OSStatus audio_render_callback(void *inRefCon,
|
||||
return noErr;
|
||||
}
|
||||
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
S32 remaining = atomic_load(&engine->test_tone_samples_remaining);
|
||||
F64 phase = engine->test_tone_phase;
|
||||
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
|
||||
S32 remaining = atomic_load(&engine->test_tone_samples_remaining);
|
||||
S32 samples_to_gen = (remaining < (S32)inNumberFrames) ? remaining : (S32)inNumberFrames;
|
||||
|
||||
// CoreAudio with kAudioFormatFlagIsNonInterleaved: each buffer = one channel
|
||||
for (UInt32 buf = 0; buf < ioData->mNumberBuffers; buf++) {
|
||||
F32 *out = (F32 *)ioData->mBuffers[buf].mData;
|
||||
F64 p = phase;
|
||||
F64 p = phase;
|
||||
for (UInt32 s = 0; s < inNumberFrames; s++) {
|
||||
if ((S32)s < samples_to_gen) {
|
||||
out[s] = (F32)(sin(p) * 0.5); // -6dB
|
||||
@@ -85,7 +87,8 @@ static OSStatus audio_render_callback(void *inRefCon,
|
||||
|
||||
// Advance phase using first channel's traversal
|
||||
phase += phase_inc * samples_to_gen;
|
||||
while (phase >= 2.0 * AUDIO_PI) phase -= 2.0 * AUDIO_PI;
|
||||
while (phase >= 2.0 * AUDIO_PI)
|
||||
phase -= 2.0 * AUDIO_PI;
|
||||
engine->test_tone_phase = phase;
|
||||
|
||||
S32 new_remaining = atomic_fetch_sub(&engine->test_tone_samples_remaining, samples_to_gen);
|
||||
@@ -154,8 +157,8 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
|
||||
CFStringRef name_ref = nullptr;
|
||||
UInt32 name_size = sizeof(name_ref);
|
||||
CFStringRef name_ref = nullptr;
|
||||
UInt32 name_size = sizeof(name_ref);
|
||||
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, nullptr, &name_size, &name_ref) != noErr)
|
||||
continue;
|
||||
|
||||
@@ -163,7 +166,7 @@ static void enumerate_output_devices(AudioEngine *engine) {
|
||||
CFStringGetCString(name_ref, engine->devices[idx].name, sizeof(engine->devices[idx].name),
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(name_ref);
|
||||
engine->devices[idx].id = idx;
|
||||
engine->devices[idx].id = idx;
|
||||
engine->ca_devices[idx].device_id = device_ids[i];
|
||||
}
|
||||
|
||||
@@ -219,9 +222,9 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
|
||||
// Add HAL output node
|
||||
AudioComponentDescription output_desc = {};
|
||||
output_desc.componentType = kAudioUnitType_Output;
|
||||
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
output_desc.componentType = kAudioUnitType_Output;
|
||||
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
|
||||
AUNode output_node;
|
||||
if (AUGraphAddNode(engine->graph, &output_desc, &output_node) != noErr) {
|
||||
@@ -244,7 +247,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
|
||||
// Set device
|
||||
if (AudioUnitSetProperty(engine->output_unit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
|
||||
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
return false;
|
||||
@@ -257,32 +260,32 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
kAudioObjectPropertyElementMain
|
||||
};
|
||||
Float64 sample_rate = 44100.0;
|
||||
UInt32 rate_size = sizeof(sample_rate);
|
||||
UInt32 rate_size = sizeof(sample_rate);
|
||||
AudioObjectGetPropertyData(device_id, &rate_prop, 0, nullptr, &rate_size, &sample_rate);
|
||||
engine->sample_rate = sample_rate;
|
||||
|
||||
// Set stream format: Float32, non-interleaved
|
||||
AudioStreamBasicDescription fmt = {};
|
||||
fmt.mSampleRate = sample_rate;
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
|
||||
fmt.mBitsPerChannel = 32;
|
||||
fmt.mChannelsPerFrame = 2;
|
||||
fmt.mFramesPerPacket = 1;
|
||||
fmt.mBytesPerFrame = 4;
|
||||
fmt.mBytesPerPacket = 4;
|
||||
engine->num_channels = 2;
|
||||
fmt.mSampleRate = sample_rate;
|
||||
fmt.mFormatID = kAudioFormatLinearPCM;
|
||||
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
|
||||
fmt.mBitsPerChannel = 32;
|
||||
fmt.mChannelsPerFrame = 2;
|
||||
fmt.mFramesPerPacket = 1;
|
||||
fmt.mBytesPerFrame = 4;
|
||||
fmt.mBytesPerPacket = 4;
|
||||
engine->num_channels = 2;
|
||||
|
||||
AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
|
||||
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
|
||||
|
||||
// Set render callback
|
||||
AURenderCallbackStruct cb = {};
|
||||
cb.inputProc = audio_render_callback;
|
||||
cb.inputProcRefCon = engine;
|
||||
cb.inputProc = audio_render_callback;
|
||||
cb.inputProcRefCon = engine;
|
||||
|
||||
if (AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) {
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) {
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
return false;
|
||||
@@ -302,7 +305,7 @@ B32 audio_open_device(AudioEngine *engine, S32 index) {
|
||||
}
|
||||
|
||||
engine->active_device_index = index;
|
||||
engine->test_tone_phase = 0.0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
atomic_store(&engine->test_tone_active, 0);
|
||||
atomic_store(&engine->test_tone_samples_remaining, 0);
|
||||
|
||||
@@ -318,17 +321,17 @@ void audio_close_device(AudioEngine *engine) {
|
||||
AUGraphStop(engine->graph);
|
||||
AUGraphUninitialize(engine->graph);
|
||||
DisposeAUGraph(engine->graph);
|
||||
engine->graph = nullptr;
|
||||
engine->output_unit = nullptr;
|
||||
engine->graph = nullptr;
|
||||
engine->output_unit = nullptr;
|
||||
engine->active_device_index = -1;
|
||||
engine->test_tone_phase = 0.0;
|
||||
engine->test_tone_phase = 0.0;
|
||||
}
|
||||
|
||||
void audio_play_test_tone(AudioEngine *engine) {
|
||||
if (!engine->graph) return;
|
||||
|
||||
engine->test_tone_phase = 0.0;
|
||||
S32 total_samples = (S32)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
S32 total_samples = (S32)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
|
||||
atomic_store(&engine->test_tone_samples_remaining, total_samples);
|
||||
atomic_store(&engine->test_tone_active, 1);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ Arena *arena_alloc(U64 cap) {
|
||||
U8 *mem = (U8 *)malloc(sizeof(Arena) + cap);
|
||||
if (!mem) return nullptr;
|
||||
Arena *arena = (Arena *)mem;
|
||||
arena->base = mem + sizeof(Arena);
|
||||
arena->pos = 0;
|
||||
arena->cap = cap;
|
||||
arena->base = mem + sizeof(Arena);
|
||||
arena->pos = 0;
|
||||
arena->cap = cap;
|
||||
return arena;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
// Arena type
|
||||
|
||||
struct Arena {
|
||||
U8 *base;
|
||||
U64 pos;
|
||||
U64 cap;
|
||||
U8 *base;
|
||||
U64 pos;
|
||||
U64 cap;
|
||||
};
|
||||
|
||||
// Temporary scope (save/restore position)
|
||||
@@ -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); }
|
||||
inline Temp temp_begin(Arena *arena) { return { arena, arena->pos }; }
|
||||
inline void temp_end(Temp temp) { arena_pop_to(temp.arena, temp.pos); }
|
||||
|
||||
////////////////////////////////
|
||||
// Push helper macros
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// base_core.h - Fundamental types, macros, and linked list helpers
|
||||
// Inspired by raddebugger's base_core.h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
////////////////////////////////
|
||||
// Codebase keywords
|
||||
|
||||
#ifndef __APPLE__
|
||||
#define internal static
|
||||
#define global static
|
||||
#define internal static
|
||||
#define global static
|
||||
#endif
|
||||
#define local_persist static
|
||||
|
||||
@@ -60,25 +60,26 @@ typedef double F64;
|
||||
#define Max(A, B) (((A) > (B)) ? (A) : (B))
|
||||
#define ClampTop(A, X) Min(A, X)
|
||||
#define ClampBot(X, B) Max(X, B)
|
||||
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) : (X))
|
||||
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) \
|
||||
: (X))
|
||||
|
||||
////////////////////////////////
|
||||
// Alignment / Sizing
|
||||
|
||||
#define AlignPow2(x, b) (((x) + (b) - 1) & (~((b) - 1)))
|
||||
#define AlignDownPow2(x, b) ((x) & (~((b) - 1)))
|
||||
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define AlignPow2(x, b) (((x) + (b)-1) & (~((b)-1)))
|
||||
#define AlignDownPow2(x, b) ((x) & (~((b)-1)))
|
||||
#define ArrayCount(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
////////////////////////////////
|
||||
// Memory macros
|
||||
|
||||
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
|
||||
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||
#define MemoryZero(s, z) memset((s), 0, (z))
|
||||
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
|
||||
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
|
||||
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||
#define MemorySet(dst, byte, size) memset((dst), (byte), (size))
|
||||
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||
#define MemoryZero(s, z) memset((s), 0, (z))
|
||||
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||
#define MemoryZeroArray(a) MemoryZero((a), sizeof(a))
|
||||
#define MemoryMatch(a, b, z) (MemoryCompare((a), (b), (z)) == 0)
|
||||
|
||||
////////////////////////////////
|
||||
// Pointer / integer casts
|
||||
@@ -109,25 +110,33 @@ typedef double F64;
|
||||
#define Glue_(A, B) A##B
|
||||
#define Glue(A, B) Glue_(A, B)
|
||||
|
||||
#define Swap(T, a, b) do { T t__ = a; a = b; b = t__; } while (0)
|
||||
#define Swap(T, a, b) \
|
||||
do { \
|
||||
T t__ = a; \
|
||||
a = b; \
|
||||
b = t__; \
|
||||
} while (0)
|
||||
|
||||
////////////////////////////////
|
||||
// Assert
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define Trap() __debugbreak()
|
||||
#define Trap() __debugbreak()
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
# define Trap() __builtin_trap()
|
||||
#define Trap() __builtin_trap()
|
||||
#else
|
||||
# define Trap() (*(volatile int *)0 = 0)
|
||||
#define Trap() (*(volatile int *)0 = 0)
|
||||
#endif
|
||||
|
||||
#define AssertAlways(x) do { if (!(x)) { Trap(); } } while (0)
|
||||
#define AssertAlways(x) \
|
||||
do { \
|
||||
if (!(x)) { Trap(); } \
|
||||
} while (0)
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define Assert(x) AssertAlways(x)
|
||||
#define Assert(x) AssertAlways(x)
|
||||
#else
|
||||
# define Assert(x) (void)(x)
|
||||
#define Assert(x) (void)(x)
|
||||
#endif
|
||||
|
||||
#define InvalidPath Assert(!"Invalid Path!")
|
||||
@@ -141,22 +150,17 @@ typedef double F64;
|
||||
#define SetNil(nil, p) ((p) = nil)
|
||||
|
||||
// Doubly-linked-list (with nil support)
|
||||
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
|
||||
(CheckNil(nil, f) ? \
|
||||
((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : \
|
||||
CheckNil(nil, p) ? \
|
||||
((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) : \
|
||||
((p) == (l)) ? \
|
||||
((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) : \
|
||||
(((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), \
|
||||
((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
|
||||
#define DLLInsert_NPZ(nil, f, l, p, n, next, prev) \
|
||||
(CheckNil(nil, f) ? ((f) = (l) = (n), SetNil(nil, (n)->next), SetNil(nil, (n)->prev)) : CheckNil(nil, p) ? ((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil, (n)->prev)) \
|
||||
: ((p) == (l)) ? ((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) \
|
||||
: (((!CheckNil(nil, p) && CheckNil(nil, (p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
|
||||
|
||||
#define DLLPushBack_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, f, l, l, n, next, prev)
|
||||
#define DLLPushFront_NPZ(nil, f, l, n, next, prev) DLLInsert_NPZ(nil, l, f, f, n, prev, next)
|
||||
|
||||
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
|
||||
(((n) == (f) ? (f) = (n)->next : (0)), \
|
||||
((n) == (l) ? (l) = (l)->prev : (0)), \
|
||||
#define DLLRemove_NPZ(nil, f, l, n, next, prev) \
|
||||
(((n) == (f) ? (f) = (n)->next : (0)), \
|
||||
((n) == (l) ? (l) = (l)->prev : (0)), \
|
||||
(CheckNil(nil, (n)->prev) ? (0) : ((n)->prev->next = (n)->next)), \
|
||||
(CheckNil(nil, (n)->next) ? (0) : ((n)->next->prev = (n)->prev)))
|
||||
|
||||
@@ -167,9 +171,7 @@ typedef double F64;
|
||||
|
||||
// Singly-linked queue (doubly-headed)
|
||||
#define SLLQueuePush_NZ(nil, f, l, n, next) \
|
||||
(CheckNil(nil, f) ? \
|
||||
((f) = (l) = (n), SetNil(nil, (n)->next)) : \
|
||||
((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
|
||||
(CheckNil(nil, f) ? ((f) = (l) = (n), SetNil(nil, (n)->next)) : ((l)->next = (n), (l) = (n), SetNil(nil, (n)->next)))
|
||||
|
||||
#define SLLQueuePush(f, l, n) SLLQueuePush_NZ(0, f, l, n, next)
|
||||
#define SLLQueuePushFront(f, l, n) (((n)->next = (f)), ((f) = (n)))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// base_inc.h - Umbrella include for the base layer
|
||||
// Include this one header to get all base types.
|
||||
|
||||
#include "base/base_core.h"
|
||||
#include "base/base_arena.h"
|
||||
#include "base/base_core.h"
|
||||
#include "base/base_math.h"
|
||||
#include "base/base_strings.h"
|
||||
|
||||
@@ -30,72 +30,87 @@ enum 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; };
|
||||
struct Vec2F32 {
|
||||
F32 x, y;
|
||||
};
|
||||
struct Vec2S32 {
|
||||
S32 x, y;
|
||||
};
|
||||
struct Vec3F32 {
|
||||
F32 x, y, z;
|
||||
};
|
||||
struct Vec4F32 {
|
||||
F32 x, y, z, w;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Range types
|
||||
|
||||
struct Rng1F32 { F32 min, max; };
|
||||
struct Rng1S64 { S64 min, max; };
|
||||
struct Rng2F32 { Vec2F32 p0, p1; };
|
||||
struct Rng1F32 {
|
||||
F32 min, max;
|
||||
};
|
||||
struct Rng1S64 {
|
||||
S64 min, max;
|
||||
};
|
||||
struct Rng2F32 {
|
||||
Vec2F32 p0, p1;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
// Constructors
|
||||
|
||||
static inline Vec2F32 v2f32(F32 x, F32 y) { return {x, y}; }
|
||||
static inline Vec2S32 v2s32(S32 x, S32 y) { return {x, y}; }
|
||||
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return {x, y, z}; }
|
||||
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return {x, y, z, w}; }
|
||||
static inline Rng1F32 rng1f32(F32 min, F32 max) { return {min, max}; }
|
||||
static inline Rng1S64 rng1s64(S64 min, S64 max) { return {min, max}; }
|
||||
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return {p0, p1}; }
|
||||
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return {{x0, y0}, {x1, y1}}; }
|
||||
static inline Vec2F32 v2f32(F32 x, F32 y) { return { x, y }; }
|
||||
static inline Vec2S32 v2s32(S32 x, S32 y) { return { x, y }; }
|
||||
static inline Vec3F32 v3f32(F32 x, F32 y, F32 z) { return { x, y, z }; }
|
||||
static inline Vec4F32 v4f32(F32 x, F32 y, F32 z, F32 w) { return { x, y, z, w }; }
|
||||
static inline Rng1F32 rng1f32(F32 min, F32 max) { return { min, max }; }
|
||||
static inline Rng1S64 rng1s64(S64 min, S64 max) { return { min, max }; }
|
||||
static inline Rng2F32 rng2f32(Vec2F32 p0, Vec2F32 p1) { return { p0, p1 }; }
|
||||
static inline Rng2F32 rng2f32p(F32 x0, F32 y0, F32 x1, F32 y1) { return { { x0, y0 }, { x1, y1 } }; }
|
||||
|
||||
////////////////////////////////
|
||||
// Vec2F32 operations
|
||||
|
||||
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return {a.x + b.x, a.y + b.y}; }
|
||||
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return {a.x - b.x, a.y - b.y}; }
|
||||
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return {a.x * b.x, a.y * b.y}; }
|
||||
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return {v.x * s, v.y * s}; }
|
||||
static inline Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) { return { a.x + b.x, a.y + b.y }; }
|
||||
static inline Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) { return { a.x - b.x, a.y - b.y }; }
|
||||
static inline Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) { return { a.x * b.x, a.y * b.y }; }
|
||||
static inline Vec2F32 scale_2f32(Vec2F32 v, F32 s) { return { v.x * s, v.y * s }; }
|
||||
|
||||
// Axis-indexed access
|
||||
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
|
||||
static inline F32 v2f32_axis(Vec2F32 v, Axis2 a) { return a == Axis2_X ? v.x : v.y; }
|
||||
static inline void v2f32_set_axis(Vec2F32 *v, Axis2 a, F32 val) {
|
||||
if (a == Axis2_X) v->x = val; else v->y = val;
|
||||
if (a == Axis2_X) v->x = val;
|
||||
else v->y = val;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Vec4F32 operations
|
||||
|
||||
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return {a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; }
|
||||
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return {v.x*s, v.y*s, v.z*s, v.w*s}; }
|
||||
static inline Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) { return { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; }
|
||||
static inline Vec4F32 scale_4f32(Vec4F32 v, F32 s) { return { v.x * s, v.y * s, v.z * s, v.w * s }; }
|
||||
static inline Vec4F32 lerp_4f32(Vec4F32 a, Vec4F32 b, F32 t) {
|
||||
return {a.x + (b.x - a.x)*t, a.y + (b.y - a.y)*t, a.z + (b.z - a.z)*t, a.w + (b.w - a.w)*t};
|
||||
return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t };
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Rng2F32 operations
|
||||
|
||||
static inline F32 rng2f32_width(Rng2F32 r) { return r.p1.x - r.p0.x; }
|
||||
static inline F32 rng2f32_height(Rng2F32 r) { return r.p1.y - r.p0.y; }
|
||||
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return {r.p1.x - r.p0.x, r.p1.y - r.p0.y}; }
|
||||
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return {(r.p0.x + r.p1.x)*0.5f, (r.p0.y + r.p1.y)*0.5f}; }
|
||||
static inline F32 rng2f32_width(Rng2F32 r) { return r.p1.x - r.p0.x; }
|
||||
static inline F32 rng2f32_height(Rng2F32 r) { return r.p1.y - r.p0.y; }
|
||||
static inline Vec2F32 rng2f32_dim(Rng2F32 r) { return { r.p1.x - r.p0.x, r.p1.y - r.p0.y }; }
|
||||
static inline Vec2F32 rng2f32_center(Rng2F32 r) { return { (r.p0.x + r.p1.x) * 0.5f, (r.p0.y + r.p1.y) * 0.5f }; }
|
||||
static inline B32 rng2f32_contains(Rng2F32 r, Vec2F32 p) {
|
||||
return p.x >= r.p0.x && p.x <= r.p1.x && p.y >= r.p0.y && p.y <= r.p1.y;
|
||||
}
|
||||
static inline Rng2F32 rng2f32_pad(Rng2F32 r, F32 p) {
|
||||
return {{r.p0.x - p, r.p0.y - p}, {r.p1.x + p, r.p1.y + p}};
|
||||
return { { r.p0.x - p, r.p0.y - p }, { r.p1.x + p, r.p1.y + p } };
|
||||
}
|
||||
static inline Rng2F32 rng2f32_shift(Rng2F32 r, Vec2F32 v) {
|
||||
return {{r.p0.x + v.x, r.p0.y + v.y}, {r.p1.x + v.x, r.p1.y + v.y}};
|
||||
return { { r.p0.x + v.x, r.p0.y + v.y }, { r.p1.x + v.x, r.p1.y + v.y } };
|
||||
}
|
||||
static inline Rng2F32 rng2f32_intersect(Rng2F32 a, Rng2F32 b) {
|
||||
return {{Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y)},
|
||||
{Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.y)}};
|
||||
return { { Max(a.p0.x, b.p0.x), Max(a.p0.y, b.p0.y) },
|
||||
{ Min(a.p1.x, b.p1.x), Min(a.p1.y, b.p1.y) } };
|
||||
}
|
||||
|
||||
// Axis-indexed range dimension
|
||||
@@ -107,4 +122,4 @@ static inline F32 rng2f32_dim_axis(Rng2F32 r, Axis2 a) {
|
||||
// F32 helpers
|
||||
|
||||
static inline F32 lerp_1f32(F32 a, F32 b, F32 t) { return a + (b - a) * t; }
|
||||
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }
|
||||
static inline F32 abs_f32(F32 x) { return x < 0 ? -x : x; }
|
||||
|
||||
@@ -12,20 +12,20 @@ Str8 str8_pushf(Arena *arena, const char *fmt, ...) {
|
||||
vsnprintf(buf, len + 1, fmt, args2);
|
||||
va_end(args2);
|
||||
|
||||
return {buf, (U64)len};
|
||||
return { buf, (U64)len };
|
||||
}
|
||||
|
||||
Str8 str8_push_copy(Arena *arena, Str8 s) {
|
||||
if (s.size == 0 || !s.str) return {nullptr, 0};
|
||||
if (s.size == 0 || !s.str) return { nullptr, 0 };
|
||||
char *buf = push_array_no_zero(arena, char, s.size + 1);
|
||||
MemoryCopy(buf, s.str, s.size);
|
||||
buf[s.size] = 0;
|
||||
return {buf, s.size};
|
||||
return { buf, s.size };
|
||||
}
|
||||
|
||||
void str8_list_push(Arena *arena, Str8List *list, Str8 s) {
|
||||
Str8Node *node = push_array(arena, Str8Node, 1);
|
||||
node->string = s;
|
||||
node->string = s;
|
||||
SLLQueuePush(list->first, list->last, node);
|
||||
list->count++;
|
||||
list->total_size += s.size;
|
||||
|
||||
@@ -31,9 +31,9 @@ struct Arena;
|
||||
////////////////////////////////
|
||||
// Constructors
|
||||
|
||||
static inline Str8 str8(const char *s, U64 len) { return {s, len}; }
|
||||
static inline Str8 str8_cstr(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
|
||||
static inline Str8 str8_lit(const char *s) { return {s, s ? (U64)strlen(s) : 0}; }
|
||||
static inline Str8 str8(const char *s, U64 len) { return { s, len }; }
|
||||
static inline Str8 str8_cstr(const char *s) { return { s, s ? (U64)strlen(s) : 0 }; }
|
||||
static inline Str8 str8_lit(const char *s) { return { s, s ? (U64)strlen(s) : 0 }; }
|
||||
static inline B32 str8_match(Str8 a, Str8 b) {
|
||||
if (a.size != b.size) return 0;
|
||||
return MemoryCompare(a.str, b.str, a.size) == 0;
|
||||
|
||||
1784
src/main.cpp
1784
src/main.cpp
File diff suppressed because it is too large
Load Diff
@@ -19,25 +19,25 @@ enum MenuCmd {
|
||||
|
||||
static void setup_menus(PlatformWindow *window) {
|
||||
PlatformMenuItem file_items[] = {
|
||||
{ "New", MENU_FILE_NEW },
|
||||
{ "Open...", MENU_FILE_OPEN },
|
||||
{ "Save", MENU_FILE_SAVE },
|
||||
{ "New", MENU_FILE_NEW },
|
||||
{ "Open...", MENU_FILE_OPEN },
|
||||
{ "Save", MENU_FILE_SAVE },
|
||||
{ "Save As...", MENU_FILE_SAVE_AS },
|
||||
{ nullptr, 0 },
|
||||
{ "Exit", MENU_FILE_EXIT },
|
||||
{ nullptr, 0 },
|
||||
{ "Exit", MENU_FILE_EXIT },
|
||||
};
|
||||
|
||||
PlatformMenuItem import_items[] = {
|
||||
{ "Audio...", MENU_IMPORT_AUDIO },
|
||||
{ "MIDI...", MENU_IMPORT_MIDI },
|
||||
{ "Audio...", MENU_IMPORT_AUDIO },
|
||||
{ "MIDI...", MENU_IMPORT_MIDI },
|
||||
};
|
||||
|
||||
PlatformMenuItem view_items[] = {
|
||||
{ "Browser", MENU_VIEW_BROWSER },
|
||||
{ "Browser", MENU_VIEW_BROWSER },
|
||||
{ "Properties", MENU_VIEW_PROPERTIES },
|
||||
{ "Log", MENU_VIEW_LOG },
|
||||
{ nullptr, 0 },
|
||||
{ "Demo", MENU_VIEW_DEMO },
|
||||
{ "Log", MENU_VIEW_LOG },
|
||||
{ nullptr, 0 },
|
||||
{ "Demo", MENU_VIEW_DEMO },
|
||||
{ "MIDI Devices", MENU_VIEW_MIDI_DEVICES },
|
||||
};
|
||||
|
||||
@@ -46,10 +46,10 @@ static void setup_menus(PlatformWindow *window) {
|
||||
};
|
||||
|
||||
PlatformMenu menus[] = {
|
||||
{ "File", file_items, sizeof(file_items) / sizeof(file_items[0]) },
|
||||
{ "Import", import_items, sizeof(import_items) / sizeof(import_items[0]) },
|
||||
{ "View", view_items, sizeof(view_items) / sizeof(view_items[0]) },
|
||||
{ "Preferences", prefs_items, sizeof(prefs_items) / sizeof(prefs_items[0]) },
|
||||
{ "File", file_items, sizeof(file_items) / sizeof(file_items[0]) },
|
||||
{ "Import", import_items, sizeof(import_items) / sizeof(import_items[0]) },
|
||||
{ "View", view_items, sizeof(view_items) / sizeof(view_items[0]) },
|
||||
{ "Preferences", prefs_items, sizeof(prefs_items) / sizeof(prefs_items[0]) },
|
||||
};
|
||||
|
||||
platform_set_menu(window, menus, sizeof(menus) / sizeof(menus[0]));
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
struct MidiEngine;
|
||||
|
||||
struct MidiDeviceInfo {
|
||||
char name[64];
|
||||
S32 id;
|
||||
B32 is_input;
|
||||
B32 active; // true when note(s) currently held
|
||||
B32 releasing; // true during release flash
|
||||
S32 velocity; // last note-on velocity (0-127)
|
||||
S32 note; // last MIDI note number (0-127)
|
||||
char name[64];
|
||||
S32 id;
|
||||
B32 is_input;
|
||||
B32 active; // true when note(s) currently held
|
||||
B32 releasing; // true during release flash
|
||||
S32 velocity; // last note-on velocity (0-127)
|
||||
S32 note; // last MIDI note number (0-127)
|
||||
};
|
||||
|
||||
MidiEngine *midi_create();
|
||||
void midi_destroy(MidiEngine *engine);
|
||||
void midi_refresh_devices(MidiEngine *engine);
|
||||
S32 midi_get_device_count(MidiEngine *engine);
|
||||
S32 midi_get_device_count(MidiEngine *engine);
|
||||
MidiDeviceInfo *midi_get_device(MidiEngine *engine, S32 index);
|
||||
|
||||
void midi_open_all_inputs(MidiEngine *engine);
|
||||
void midi_close_all_inputs(MidiEngine *engine);
|
||||
void midi_update(MidiEngine *engine, F32 dt);
|
||||
B32 midi_is_input_active(MidiEngine *engine, S32 device_index);
|
||||
void midi_open_all_inputs(MidiEngine *engine);
|
||||
void midi_close_all_inputs(MidiEngine *engine);
|
||||
void midi_update(MidiEngine *engine, F32 dt);
|
||||
B32 midi_is_input_active(MidiEngine *engine, S32 device_index);
|
||||
|
||||
// Per-note state: returns true if note (0-127) is currently held on any input device
|
||||
B32 midi_is_note_held(MidiEngine *engine, S32 note);
|
||||
B32 midi_is_note_held(MidiEngine *engine, S32 note);
|
||||
// Returns the last note-on velocity (0-127) for a given note, or 0 if not held
|
||||
S32 midi_get_note_velocity(MidiEngine *engine, S32 note);
|
||||
S32 midi_get_note_velocity(MidiEngine *engine, S32 note);
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
#include "midi/midi.h"
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <string.h>
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
|
||||
struct MidiEngine {
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
|
||||
MIDIClientRef client;
|
||||
MIDIPortRef input_port;
|
||||
MIDIClientRef client;
|
||||
MIDIPortRef input_port;
|
||||
|
||||
// Map: source endpoint index -> our device array index
|
||||
S32 source_to_device[MIDI_MAX_DEVICES];
|
||||
S32 source_to_device[MIDI_MAX_DEVICES];
|
||||
|
||||
// Set atomically from callback thread
|
||||
_Atomic S32 pending_note_on_vel[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_num[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_off[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 held_note_count[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_on_vel[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_num[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 pending_note_off[MIDI_MAX_DEVICES];
|
||||
_Atomic S32 held_note_count[MIDI_MAX_DEVICES];
|
||||
|
||||
// Per-note state (across all devices), set atomically from callback
|
||||
_Atomic S32 note_states[128]; // held count
|
||||
_Atomic S32 note_velocities[128]; // last note-on velocity
|
||||
_Atomic S32 note_states[128]; // held count
|
||||
_Atomic S32 note_velocities[128]; // last note-on velocity
|
||||
|
||||
// Main thread only
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -39,23 +39,26 @@ struct MidiEngine {
|
||||
static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
|
||||
(void)readProcRefCon;
|
||||
|
||||
MidiEngine *engine = (MidiEngine *)readProcRefCon;
|
||||
S32 device_idx = (S32)(intptr_t)srcConnRefCon;
|
||||
MidiEngine *engine = (MidiEngine *)readProcRefCon;
|
||||
S32 device_idx = (S32)(intptr_t)srcConnRefCon;
|
||||
if (!engine || device_idx < 0 || device_idx >= MIDI_MAX_DEVICES) return;
|
||||
|
||||
const MIDIPacket *packet = &pktlist->packet[0];
|
||||
for (UInt32 i = 0; i < pktlist->numPackets; i++) {
|
||||
// Parse MIDI bytes
|
||||
for (UInt16 j = 0; j < packet->length; ) {
|
||||
for (UInt16 j = 0; j < packet->length;) {
|
||||
U8 status = packet->data[j];
|
||||
|
||||
// Skip non-status bytes (running status not handled for simplicity)
|
||||
if (status < 0x80) { j++; continue; }
|
||||
if (status < 0x80) {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
|
||||
U8 kind = status & 0xF0;
|
||||
|
||||
if (kind == 0x90 && j + 2 < packet->length) {
|
||||
U8 note = packet->data[j + 1];
|
||||
U8 note = packet->data[j + 1];
|
||||
U8 velocity = packet->data[j + 2];
|
||||
j += 3;
|
||||
|
||||
@@ -92,7 +95,8 @@ static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefC
|
||||
} else if (kind == 0xF0) {
|
||||
// System messages — skip to end or next status byte
|
||||
j++;
|
||||
while (j < packet->length && packet->data[j] < 0x80) j++;
|
||||
while (j < packet->length && packet->data[j] < 0x80)
|
||||
j++;
|
||||
} else {
|
||||
j += 3; // Default: 3-byte message
|
||||
}
|
||||
@@ -126,9 +130,9 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
snprintf(dev->name, sizeof(dev->name), "MIDI Source %d", (S32)i);
|
||||
}
|
||||
|
||||
dev->id = engine->device_count;
|
||||
dev->id = engine->device_count;
|
||||
dev->is_input = true;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
|
||||
engine->source_to_device[i] = engine->device_count;
|
||||
engine->device_count++;
|
||||
@@ -152,9 +156,9 @@ static void enumerate_midi_devices(MidiEngine *engine) {
|
||||
snprintf(dev->name, sizeof(dev->name), "MIDI Dest %d", (S32)i);
|
||||
}
|
||||
|
||||
dev->id = engine->device_count;
|
||||
dev->id = engine->device_count;
|
||||
dev->is_input = false;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
|
||||
engine->device_count++;
|
||||
}
|
||||
@@ -185,7 +189,7 @@ void midi_open_all_inputs(MidiEngine *engine) {
|
||||
ItemCount num_sources = MIDIGetNumberOfSources();
|
||||
for (ItemCount i = 0; i < num_sources && (S32)i < MIDI_MAX_DEVICES; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetSource(i);
|
||||
S32 dev_idx = engine->source_to_device[i];
|
||||
S32 dev_idx = engine->source_to_device[i];
|
||||
MIDIPortConnectSource(engine->input_port, endpoint, (void *)(intptr_t)dev_idx);
|
||||
}
|
||||
}
|
||||
@@ -203,8 +207,8 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
||||
atomic_store(&engine->pending_note_off[i], 0);
|
||||
atomic_store(&engine->held_note_count[i], 0);
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
}
|
||||
for (S32 i = 0; i < 128; i++) {
|
||||
atomic_store(&engine->note_states[i], 0);
|
||||
@@ -216,36 +220,36 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
for (S32 i = 0; i < engine->device_count; i++) {
|
||||
if (!engine->devices[i].is_input) continue;
|
||||
|
||||
S32 vel = atomic_exchange(&engine->pending_note_on_vel[i], 0);
|
||||
S32 vel = atomic_exchange(&engine->pending_note_on_vel[i], 0);
|
||||
S32 note_p1 = atomic_exchange(&engine->pending_note_num[i], 0);
|
||||
if (vel > 0) engine->display_velocity[i] = vel;
|
||||
if (note_p1 > 0) engine->display_note[i] = note_p1 - 1;
|
||||
|
||||
S32 off = atomic_exchange(&engine->pending_note_off[i], 0);
|
||||
S32 off = atomic_exchange(&engine->pending_note_off[i], 0);
|
||||
S32 held = atomic_load(&engine->held_note_count[i]);
|
||||
|
||||
if (held > 0) {
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
} else if (off || (engine->devices[i].active && held <= 0)) {
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].releasing = true;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
}
|
||||
|
||||
if (engine->release_timers[i] > 0.0f) {
|
||||
engine->release_timers[i] -= dt;
|
||||
if (engine->release_timers[i] <= 0.0f) {
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
engine->devices[i].velocity = engine->display_velocity[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
#include "midi/midi.h"
|
||||
#include <windows.h>
|
||||
#include <mmeapi.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_MAX_DEVICES 64
|
||||
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||
|
||||
struct MidiEngine {
|
||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||
S32 device_count;
|
||||
S32 device_count;
|
||||
|
||||
HMIDIIN input_handles[MIDI_MAX_DEVICES];
|
||||
HMIDIIN input_handles[MIDI_MAX_DEVICES];
|
||||
|
||||
// Set atomically from callback thread
|
||||
volatile LONG pending_note_on_vel[MIDI_MAX_DEVICES]; // last note-on velocity (0 = consumed)
|
||||
volatile LONG pending_note_num[MIDI_MAX_DEVICES]; // last note-on note number + 1 (0 = consumed)
|
||||
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
||||
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
||||
volatile LONG pending_note_on_vel[MIDI_MAX_DEVICES]; // last note-on velocity (0 = consumed)
|
||||
volatile LONG pending_note_num[MIDI_MAX_DEVICES]; // last note-on note number + 1 (0 = consumed)
|
||||
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
||||
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
||||
|
||||
// Per-note state (across all devices), set atomically from callback
|
||||
volatile LONG note_states[128]; // held count
|
||||
volatile LONG note_velocities[128]; // last note-on velocity
|
||||
volatile LONG note_states[128]; // held count
|
||||
volatile LONG note_velocities[128]; // last note-on velocity
|
||||
|
||||
// Main thread only
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
S32 display_velocity[MIDI_MAX_DEVICES];
|
||||
S32 display_note[MIDI_MAX_DEVICES];
|
||||
F32 release_timers[MIDI_MAX_DEVICES];
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -34,7 +34,7 @@ struct MidiEngine {
|
||||
static MidiEngine *g_midi_engine = nullptr;
|
||||
|
||||
static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
(void)hMidiIn;
|
||||
(void)dwParam2;
|
||||
|
||||
@@ -91,10 +91,10 @@ void midi_open_all_inputs(MidiEngine *engine) {
|
||||
if (!dev->is_input) continue;
|
||||
if (engine->input_handles[i]) continue; // already open
|
||||
|
||||
HMIDIIN handle = nullptr;
|
||||
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||
(DWORD_PTR)midi_in_callback,
|
||||
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||
HMIDIIN handle = nullptr;
|
||||
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||
(DWORD_PTR)midi_in_callback,
|
||||
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||
if (res == MMSYSERR_NOERROR) {
|
||||
engine->input_handles[i] = handle;
|
||||
midiInStart(handle);
|
||||
@@ -110,15 +110,15 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
||||
engine->input_handles[i] = nullptr;
|
||||
}
|
||||
engine->pending_note_on_vel[i] = 0;
|
||||
engine->pending_note_num[i] = 0;
|
||||
engine->pending_note_off[i] = 0;
|
||||
engine->held_note_count[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->pending_note_num[i] = 0;
|
||||
engine->pending_note_off[i] = 0;
|
||||
engine->held_note_count[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
}
|
||||
for (S32 i = 0; i < 128; i++) {
|
||||
engine->note_states[i] = 0;
|
||||
engine->note_states[i] = 0;
|
||||
engine->note_velocities[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
if (!engine->devices[i].is_input) continue;
|
||||
|
||||
// Consume pending note-on velocity and note number
|
||||
LONG vel = InterlockedExchange(&engine->pending_note_on_vel[i], 0);
|
||||
LONG vel = InterlockedExchange(&engine->pending_note_on_vel[i], 0);
|
||||
LONG note_p1 = InterlockedExchange(&engine->pending_note_num[i], 0);
|
||||
if (vel > 0) {
|
||||
engine->display_velocity[i] = (S32)vel;
|
||||
@@ -144,29 +144,29 @@ void midi_update(MidiEngine *engine, F32 dt) {
|
||||
LONG held = engine->held_note_count[i];
|
||||
|
||||
if (held > 0) {
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].active = true;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
} else if (off || (engine->devices[i].active && held <= 0)) {
|
||||
// All notes just released — start release flash
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].active = false;
|
||||
engine->devices[i].releasing = true;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||
}
|
||||
|
||||
// Decay release flash timer
|
||||
if (engine->release_timers[i] > 0.0f) {
|
||||
engine->release_timers[i] -= dt;
|
||||
if (engine->release_timers[i] <= 0.0f) {
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->release_timers[i] = 0.0f;
|
||||
engine->devices[i].releasing = false;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
engine->display_velocity[i] = 0;
|
||||
engine->display_note[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
engine->devices[i].velocity = engine->display_velocity[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
engine->devices[i].note = engine->display_note[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,9 +197,9 @@ void midi_refresh_devices(MidiEngine *engine) {
|
||||
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);
|
||||
dev->id = (S32)i;
|
||||
dev->id = (S32)i;
|
||||
dev->is_input = true;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +209,9 @@ void midi_refresh_devices(MidiEngine *engine) {
|
||||
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);
|
||||
dev->id = (S32)i;
|
||||
dev->id = (S32)i;
|
||||
dev->is_input = false;
|
||||
dev->active = false;
|
||||
dev->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,45 +27,45 @@ enum {
|
||||
PKEY_V = 0x56,
|
||||
PKEY_X = 0x58,
|
||||
PKEY_0 = 0x30,
|
||||
PKEY_EQUAL = 0xBB, // '='/'+' (VK_OEM_PLUS)
|
||||
PKEY_MINUS = 0xBD, // '-'/'_' (VK_OEM_MINUS)
|
||||
PKEY_EQUAL = 0xBB, // '='/'+' (VK_OEM_PLUS)
|
||||
PKEY_MINUS = 0xBD, // '-'/'_' (VK_OEM_MINUS)
|
||||
};
|
||||
|
||||
struct PlatformInput {
|
||||
// Typed characters (UTF-16 code units, printable only)
|
||||
U16 chars[PLATFORM_MAX_CHARS_PER_FRAME];
|
||||
S32 char_count;
|
||||
S32 char_count;
|
||||
|
||||
// Key-down events (virtual key codes)
|
||||
U8 keys[PLATFORM_MAX_KEYS_PER_FRAME];
|
||||
S32 key_count;
|
||||
S32 key_count;
|
||||
|
||||
// Modifier state at time of last key event
|
||||
B32 ctrl_held;
|
||||
B32 shift_held;
|
||||
B32 ctrl_held;
|
||||
B32 shift_held;
|
||||
|
||||
// Mouse state (polled per frame)
|
||||
Vec2F32 mouse_pos;
|
||||
Vec2F32 scroll_delta;
|
||||
B32 mouse_down;
|
||||
B32 was_mouse_down;
|
||||
Vec2F32 mouse_pos;
|
||||
Vec2F32 scroll_delta;
|
||||
B32 mouse_down;
|
||||
B32 was_mouse_down;
|
||||
};
|
||||
|
||||
struct PlatformWindow;
|
||||
|
||||
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
|
||||
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
|
||||
};
|
||||
|
||||
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)
|
||||
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)
|
||||
};
|
||||
|
||||
enum PlatformMsgBoxType {
|
||||
@@ -75,14 +75,14 @@ enum PlatformMsgBoxType {
|
||||
};
|
||||
|
||||
struct PlatformMenuItem {
|
||||
const char *label; // nullptr = separator
|
||||
S32 id; // command ID (ignored for separators)
|
||||
const char *label; // nullptr = separator
|
||||
S32 id; // command ID (ignored for separators)
|
||||
};
|
||||
|
||||
struct PlatformMenu {
|
||||
const char *label;
|
||||
PlatformMenuItem *items;
|
||||
S32 item_count;
|
||||
const char *label;
|
||||
PlatformMenuItem *items;
|
||||
S32 item_count;
|
||||
};
|
||||
|
||||
// Called by the platform layer when the window needs a frame rendered
|
||||
@@ -92,13 +92,13 @@ typedef void (*PlatformFrameCallback)(void *user_data);
|
||||
|
||||
PlatformWindow *platform_create_window(PlatformWindowDesc *desc);
|
||||
void platform_destroy_window(PlatformWindow *window);
|
||||
B32 platform_poll_events(PlatformWindow *window);
|
||||
B32 platform_poll_events(PlatformWindow *window);
|
||||
void platform_get_size(PlatformWindow *window, S32 *w, S32 *h);
|
||||
void *platform_get_native_handle(PlatformWindow *window);
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
|
||||
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count);
|
||||
S32 platform_poll_menu_command(PlatformWindow *window);
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data);
|
||||
void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_count);
|
||||
S32 platform_poll_menu_command(PlatformWindow *window);
|
||||
|
||||
// Returns accumulated input since last call (keyboard events + polled mouse state), then clears the buffer.
|
||||
PlatformInput platform_get_input(PlatformWindow *window);
|
||||
@@ -116,8 +116,8 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
// Cursor shapes for resize handles
|
||||
enum PlatformCursor {
|
||||
PLATFORM_CURSOR_ARROW = 0,
|
||||
PLATFORM_CURSOR_SIZE_WE = 1, // horizontal resize
|
||||
PLATFORM_CURSOR_SIZE_NS = 2, // vertical resize
|
||||
PLATFORM_CURSOR_SIZE_WE = 1, // horizontal resize
|
||||
PLATFORM_CURSOR_SIZE_NS = 2, // vertical resize
|
||||
};
|
||||
|
||||
void platform_set_cursor(PlatformCursor cursor);
|
||||
|
||||
@@ -4,42 +4,53 @@
|
||||
|
||||
// macOS virtual key codes (avoids Carbon.h include)
|
||||
enum {
|
||||
kVK_ANSI_A = 0x00, kVK_ANSI_C = 0x08, kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_Return = 0x24, kVK_Tab = 0x30, kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35, kVK_ForwardDelete = 0x75,
|
||||
kVK_LeftArrow = 0x7B, kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D, kVK_UpArrow = 0x7E,
|
||||
kVK_Home = 0x73, kVK_End = 0x77,
|
||||
kVK_Command = 0x37, kVK_Shift = 0x38,
|
||||
kVK_RightShift = 0x3C, kVK_RightCommand = 0x36,
|
||||
kVK_ANSI_Equal = 0x18, kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_A = 0x00,
|
||||
kVK_ANSI_C = 0x08,
|
||||
kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35,
|
||||
kVK_ForwardDelete = 0x75,
|
||||
kVK_LeftArrow = 0x7B,
|
||||
kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D,
|
||||
kVK_UpArrow = 0x7E,
|
||||
kVK_Home = 0x73,
|
||||
kVK_End = 0x77,
|
||||
kVK_Command = 0x37,
|
||||
kVK_Shift = 0x38,
|
||||
kVK_RightShift = 0x3C,
|
||||
kVK_RightCommand = 0x36,
|
||||
kVK_ANSI_Equal = 0x18,
|
||||
kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
};
|
||||
|
||||
static U8 macos_keycode_to_pkey(U16 keycode) {
|
||||
switch (keycode) {
|
||||
case kVK_ANSI_A: return PKEY_A;
|
||||
case kVK_ANSI_C: return PKEY_C;
|
||||
case kVK_ANSI_V: return PKEY_V;
|
||||
case kVK_ANSI_X: return PKEY_X;
|
||||
case kVK_Return: return PKEY_RETURN;
|
||||
case kVK_ANSI_A: return PKEY_A;
|
||||
case kVK_ANSI_C: return PKEY_C;
|
||||
case kVK_ANSI_V: return PKEY_V;
|
||||
case kVK_ANSI_X: return PKEY_X;
|
||||
case kVK_Return: return PKEY_RETURN;
|
||||
case kVK_ANSI_KeypadEnter: return PKEY_RETURN;
|
||||
case kVK_Tab: return PKEY_TAB;
|
||||
case kVK_Delete: return PKEY_BACKSPACE;
|
||||
case kVK_ForwardDelete:return PKEY_DELETE;
|
||||
case kVK_Escape: return PKEY_ESCAPE;
|
||||
case kVK_LeftArrow: return PKEY_LEFT;
|
||||
case kVK_RightArrow: return PKEY_RIGHT;
|
||||
case kVK_UpArrow: return PKEY_UP;
|
||||
case kVK_DownArrow: return PKEY_DOWN;
|
||||
case kVK_Home: return PKEY_HOME;
|
||||
case kVK_End: return PKEY_END;
|
||||
case kVK_ANSI_Equal: return PKEY_EQUAL;
|
||||
case kVK_ANSI_Minus: return PKEY_MINUS;
|
||||
case kVK_ANSI_0: return PKEY_0;
|
||||
default: return 0;
|
||||
case kVK_Tab: return PKEY_TAB;
|
||||
case kVK_Delete: return PKEY_BACKSPACE;
|
||||
case kVK_ForwardDelete: return PKEY_DELETE;
|
||||
case kVK_Escape: return PKEY_ESCAPE;
|
||||
case kVK_LeftArrow: return PKEY_LEFT;
|
||||
case kVK_RightArrow: return PKEY_RIGHT;
|
||||
case kVK_UpArrow: return PKEY_UP;
|
||||
case kVK_DownArrow: return PKEY_DOWN;
|
||||
case kVK_Home: return PKEY_HOME;
|
||||
case kVK_End: return PKEY_END;
|
||||
case kVK_ANSI_Equal: return PKEY_EQUAL;
|
||||
case kVK_ANSI_Minus: return PKEY_MINUS;
|
||||
case kVK_ANSI_0: return PKEY_0;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,18 +69,23 @@ static PlatformWindow *g_main_window = nullptr;
|
||||
@end
|
||||
|
||||
@implementation ASmplAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification { (void)notification; }
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { (void)sender; return YES; }
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
}
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
(void)sender;
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASmplWindowDelegate : NSObject <NSWindowDelegate> {
|
||||
@public
|
||||
@public
|
||||
PlatformWindow *_platformWindow;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASmplView : NSView <NSTextInputClient> {
|
||||
@public
|
||||
@public
|
||||
PlatformWindow *_platformWindow;
|
||||
}
|
||||
@end
|
||||
@@ -81,10 +97,10 @@ struct PlatformWindow {
|
||||
NSWindow *ns_window;
|
||||
ASmplView *view;
|
||||
ASmplWindowDelegate *delegate;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
PlatformFrameCallback frame_callback;
|
||||
void *frame_callback_user_data;
|
||||
PlatformInput input;
|
||||
@@ -101,16 +117,19 @@ static void platform_macos_insert_text_pw(PlatformWindow *pw, const char *utf8)
|
||||
PlatformInput *ev = &pw->input;
|
||||
while (*utf8 && ev->char_count < PLATFORM_MAX_CHARS_PER_FRAME) {
|
||||
U8 c = (U8)*utf8;
|
||||
if (c < 32) { utf8++; continue; }
|
||||
if (c < 32) {
|
||||
utf8++;
|
||||
continue;
|
||||
}
|
||||
// Handle ASCII printable range (single-byte UTF-8)
|
||||
if (c < 0x80) {
|
||||
ev->chars[ev->char_count++] = (U16)c;
|
||||
utf8++;
|
||||
} else {
|
||||
// Skip multi-byte UTF-8 sequences for now (UI only handles ASCII)
|
||||
if (c < 0xE0) utf8 += 2;
|
||||
if (c < 0xE0) utf8 += 2;
|
||||
else if (c < 0xF0) utf8 += 3;
|
||||
else utf8 += 4;
|
||||
else utf8 += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,11 +162,11 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
- (void)windowDidResize:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
if (!_platformWindow) return;
|
||||
PlatformWindow *pw = _platformWindow;
|
||||
NSRect frame = [pw->view bounds];
|
||||
F32 scale = pw->backing_scale;
|
||||
pw->width = (S32)(frame.size.width * scale);
|
||||
pw->height = (S32)(frame.size.height * scale);
|
||||
PlatformWindow *pw = _platformWindow;
|
||||
NSRect frame = [pw->view bounds];
|
||||
F32 scale = pw->backing_scale;
|
||||
pw->width = (S32)(frame.size.width * scale);
|
||||
pw->height = (S32)(frame.size.height * scale);
|
||||
if (pw->frame_callback)
|
||||
pw->frame_callback(pw->frame_callback_user_data);
|
||||
}
|
||||
@@ -155,25 +174,45 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
|
||||
@implementation ASmplView
|
||||
|
||||
- (BOOL)acceptsFirstResponder { return YES; }
|
||||
- (BOOL)canBecomeKeyView { return YES; }
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)canBecomeKeyView {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Needed for NSTextInputClient
|
||||
- (BOOL)hasMarkedText { return NO; }
|
||||
- (NSRange)markedRange { return NSMakeRange(NSNotFound, 0); }
|
||||
- (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); }
|
||||
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
|
||||
(void)string; (void)selectedRange; (void)replacementRange;
|
||||
- (BOOL)hasMarkedText {
|
||||
return NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange {
|
||||
(void)string;
|
||||
(void)selectedRange;
|
||||
(void)replacementRange;
|
||||
}
|
||||
- (void)unmarkText {
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return @[];
|
||||
}
|
||||
- (void)unmarkText {}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText { return @[]; }
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
|
||||
(void)range; (void)actualRange;
|
||||
(void)range;
|
||||
(void)actualRange;
|
||||
return nil;
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return NSNotFound; }
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point {
|
||||
(void)point;
|
||||
return NSNotFound;
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
|
||||
(void)range; (void)actualRange;
|
||||
(void)range;
|
||||
(void)actualRange;
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
@@ -192,7 +231,7 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
platform_macos_key_down_pw(_platformWindow, [event keyCode], [event modifierFlags]);
|
||||
|
||||
// Feed into text input system for character generation
|
||||
[self interpretKeyEvents:@[event]];
|
||||
[self interpretKeyEvents:@[ event ]];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
@@ -210,8 +249,12 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
if (_platformWindow) _platformWindow->mouse_down_state = 0;
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event { (void)event; }
|
||||
- (void)mouseDragged:(NSEvent *)event { (void)event; }
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
(void)event;
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
(void)event;
|
||||
}
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
if (!_platformWindow) return;
|
||||
@@ -221,7 +264,10 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
_platformWindow->input.scroll_delta.y += dy;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return YES; }
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event {
|
||||
(void)event;
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -235,7 +281,7 @@ static void platform_macos_key_down_pw(PlatformWindow *pw, U16 keycode, NSEventM
|
||||
@implementation ASmplMenuTarget
|
||||
- (void)menuAction:(id)sender {
|
||||
if (!g_main_window) return;
|
||||
NSMenuItem *item = (NSMenuItem *)sender;
|
||||
NSMenuItem *item = (NSMenuItem *)sender;
|
||||
g_main_window->pending_menu_cmd = (S32)[item tag];
|
||||
}
|
||||
@end
|
||||
@@ -271,9 +317,9 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
|
||||
NSWindow *ns_window = [[NSWindow alloc]
|
||||
initWithContentRect:content_rect
|
||||
styleMask:style_mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
styleMask:style_mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
[ns_window setTitle:[NSString stringWithUTF8String:desc->title]];
|
||||
[ns_window center];
|
||||
@@ -290,16 +336,16 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
|
||||
PlatformWindow *window = new PlatformWindow();
|
||||
memset(window, 0, sizeof(*window));
|
||||
window->ns_window = ns_window;
|
||||
window->view = view;
|
||||
window->delegate = delegate;
|
||||
window->should_close = false;
|
||||
window->ns_window = ns_window;
|
||||
window->view = view;
|
||||
window->delegate = delegate;
|
||||
window->should_close = false;
|
||||
window->backing_scale = (F32)[ns_window backingScaleFactor];
|
||||
window->width = (S32)(desc->width * window->backing_scale);
|
||||
window->height = (S32)(desc->height * window->backing_scale);
|
||||
window->width = (S32)(desc->width * window->backing_scale);
|
||||
window->height = (S32)(desc->height * window->backing_scale);
|
||||
|
||||
// Wire up per-window pointers
|
||||
view->_platformWindow = window;
|
||||
view->_platformWindow = window;
|
||||
delegate->_platformWindow = window;
|
||||
|
||||
// Track main window for menu commands
|
||||
@@ -338,8 +384,8 @@ B32 platform_poll_events(PlatformWindow *window) {
|
||||
// When the app is not active (e.g. during Cmd+Tab away), block until
|
||||
// an event arrives instead of spinning. This keeps CPU near zero while
|
||||
// backgrounded and lets macOS handle the app-switch animation smoothly.
|
||||
BOOL is_active = [NSApp isActive];
|
||||
NSDate *deadline = is_active ? nil : [NSDate distantFuture];
|
||||
BOOL is_active = [NSApp isActive];
|
||||
NSDate *deadline = is_active ? nil : [NSDate distantFuture];
|
||||
|
||||
NSEvent *event;
|
||||
while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -366,7 +412,7 @@ void *platform_get_native_handle(PlatformWindow *window) {
|
||||
}
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback_user_data = user_data;
|
||||
}
|
||||
|
||||
@@ -380,7 +426,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
|
||||
// App menu (required on macOS)
|
||||
NSMenuItem *app_menu_item = [[NSMenuItem alloc] init];
|
||||
NSMenu *app_menu = [[NSMenu alloc] init];
|
||||
NSMenu *app_menu = [[NSMenu alloc] init];
|
||||
[app_menu addItemWithTitle:@"Quit autosample"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
@@ -389,8 +435,8 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
|
||||
for (S32 i = 0; i < menu_count; i++) {
|
||||
NSMenuItem *top_item = [[NSMenuItem alloc] init];
|
||||
NSMenu *submenu = [[NSMenu alloc] initWithTitle:
|
||||
[NSString stringWithUTF8String:menus[i].label]];
|
||||
NSMenu *submenu = [[NSMenu alloc] initWithTitle:
|
||||
[NSString stringWithUTF8String:menus[i].label]];
|
||||
|
||||
for (S32 j = 0; j < menus[i].item_count; j++) {
|
||||
PlatformMenuItem *item = &menus[i].items[j];
|
||||
@@ -399,7 +445,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
} else {
|
||||
NSMenuItem *ns_item = [[NSMenuItem alloc]
|
||||
initWithTitle:[NSString stringWithUTF8String:item->label]
|
||||
action:@selector(menuAction:)
|
||||
action:@selector(menuAction:)
|
||||
keyEquivalent:@""];
|
||||
[ns_item setTag:item->id];
|
||||
[ns_item setTarget:g_menu_target];
|
||||
@@ -415,7 +461,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
}
|
||||
|
||||
S32 platform_poll_menu_command(PlatformWindow *window) {
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
window->pending_menu_cmd = 0;
|
||||
return cmd;
|
||||
}
|
||||
@@ -425,21 +471,21 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
|
||||
// Poll mouse position (Cocoa uses bottom-left origin, flip Y)
|
||||
NSPoint mouse_in_window = [window->ns_window mouseLocationOutsideOfEventStream];
|
||||
NSRect view_bounds = [window->view bounds];
|
||||
F32 scale = window->backing_scale;
|
||||
result.mouse_pos = v2f32(
|
||||
NSRect view_bounds = [window->view bounds];
|
||||
F32 scale = window->backing_scale;
|
||||
result.mouse_pos = v2f32(
|
||||
(F32)mouse_in_window.x * scale,
|
||||
(F32)(view_bounds.size.height - mouse_in_window.y) * scale);
|
||||
|
||||
// Mouse button state
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = window->mouse_down_state;
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = window->mouse_down_state;
|
||||
window->prev_mouse_down = result.mouse_down;
|
||||
|
||||
// Poll current modifier state (so shift/ctrl are accurate even without key events)
|
||||
NSEventModifierFlags mods = [NSEvent modifierFlags];
|
||||
result.ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
|
||||
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
|
||||
result.ctrl_held = (mods & NSEventModifierFlagCommand) != 0;
|
||||
result.shift_held = (mods & NSEventModifierFlagShift) != 0;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
window->input = {};
|
||||
@@ -463,8 +509,8 @@ F32 platform_get_dpi_scale(PlatformWindow *window) {
|
||||
void platform_set_cursor(PlatformCursor cursor) {
|
||||
switch (cursor) {
|
||||
case PLATFORM_CURSOR_SIZE_WE: [[NSCursor resizeLeftRightCursor] set]; break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set]; break;
|
||||
default: [[NSCursor arrowCursor] set]; break;
|
||||
case PLATFORM_CURSOR_SIZE_NS: [[NSCursor resizeUpDownCursor] set]; break;
|
||||
default: [[NSCursor arrowCursor] set]; break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,8 +525,8 @@ const char *platform_clipboard_get() {
|
||||
static char buf[4096];
|
||||
buf[0] = '\0';
|
||||
|
||||
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
||||
NSString *str = [pb stringForType:NSPasteboardTypeString];
|
||||
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
||||
NSString *str = [pb stringForType:NSPasteboardTypeString];
|
||||
if (str) {
|
||||
const char *utf8 = [str UTF8String];
|
||||
if (utf8) {
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
|
||||
struct PlatformWindow {
|
||||
HWND hwnd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
HWND hwnd;
|
||||
B32 should_close;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 pending_menu_cmd;
|
||||
PlatformFrameCallback frame_callback;
|
||||
void *frame_callback_user_data;
|
||||
PlatformInput input;
|
||||
@@ -19,9 +19,9 @@ struct PlatformWindow {
|
||||
};
|
||||
|
||||
// Main window receives menu commands
|
||||
static PlatformWindow *g_main_window = nullptr;
|
||||
static HCURSOR g_current_cursor = nullptr;
|
||||
static B32 g_wndclass_registered = false;
|
||||
static PlatformWindow *g_main_window = nullptr;
|
||||
static HCURSOR g_current_cursor = nullptr;
|
||||
static B32 g_wndclass_registered = false;
|
||||
|
||||
static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
||||
PlatformWindow *pw = (PlatformWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
@@ -77,8 +77,8 @@ static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM
|
||||
if (pw) {
|
||||
RECT *suggested = (RECT *)lparam;
|
||||
SetWindowPos(hwnd, nullptr, suggested->left, suggested->top,
|
||||
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
return 0;
|
||||
case WM_CLOSE:
|
||||
@@ -101,7 +101,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
if (!g_wndclass_registered) {
|
||||
WNDCLASSEXW wc = {};
|
||||
WNDCLASSEXW wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = CS_CLASSDC;
|
||||
wc.lpfnWndProc = win32_wndproc;
|
||||
@@ -112,14 +112,14 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
g_wndclass_registered = true;
|
||||
}
|
||||
|
||||
UINT dpi = GetDpiForSystem();
|
||||
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
||||
int x = (screen_w - desc->width) / 2;
|
||||
int y = (screen_h - desc->height) / 2;
|
||||
UINT dpi = GetDpiForSystem();
|
||||
int screen_w = GetSystemMetrics(SM_CXSCREEN);
|
||||
int screen_h = GetSystemMetrics(SM_CYSCREEN);
|
||||
int x = (screen_w - desc->width) / 2;
|
||||
int y = (screen_h - desc->height) / 2;
|
||||
|
||||
DWORD style;
|
||||
HWND parent_hwnd = nullptr;
|
||||
HWND parent_hwnd = nullptr;
|
||||
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,8 +133,8 @@ 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);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, nullptr, 0);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, desc->title, -1, wtitle, wchar_count);
|
||||
|
||||
HWND hwnd = CreateWindowExW(
|
||||
@@ -143,8 +143,7 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
x, y,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
parent_hwnd, nullptr, GetModuleHandleW(nullptr), nullptr
|
||||
);
|
||||
parent_hwnd, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||
|
||||
_freea(wtitle);
|
||||
|
||||
@@ -152,10 +151,10 @@ PlatformWindow *platform_create_window(PlatformWindowDesc *desc) {
|
||||
return nullptr;
|
||||
|
||||
PlatformWindow *window = new PlatformWindow();
|
||||
window->hwnd = hwnd;
|
||||
window->should_close = false;
|
||||
window->width = desc->width;
|
||||
window->height = desc->height;
|
||||
window->hwnd = hwnd;
|
||||
window->should_close = false;
|
||||
window->width = desc->width;
|
||||
window->height = desc->height;
|
||||
|
||||
// Store PlatformWindow* on the HWND so WndProc can find it
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)window);
|
||||
@@ -206,7 +205,7 @@ void *platform_get_native_handle(PlatformWindow *window) {
|
||||
}
|
||||
|
||||
void platform_set_frame_callback(PlatformWindow *window, PlatformFrameCallback cb, void *user_data) {
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback = cb;
|
||||
window->frame_callback_user_data = user_data;
|
||||
}
|
||||
|
||||
@@ -221,16 +220,16 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
if (!item->label) {
|
||||
AppendMenuW(submenu, MF_SEPARATOR, 0, nullptr);
|
||||
} else {
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 0);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, item->label, -1, nullptr, 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);
|
||||
_freea(wlabel);
|
||||
}
|
||||
}
|
||||
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 0);
|
||||
wchar_t *wlabel = (wchar_t *)_malloca(wchar_count * sizeof(wchar_t));
|
||||
int wchar_count = MultiByteToWideChar(CP_UTF8, 0, menus[i].label, -1, nullptr, 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);
|
||||
_freea(wlabel);
|
||||
@@ -240,7 +239,7 @@ void platform_set_menu(PlatformWindow *window, PlatformMenu *menus, S32 menu_cou
|
||||
}
|
||||
|
||||
S32 platform_poll_menu_command(PlatformWindow *window) {
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
S32 cmd = window->pending_menu_cmd;
|
||||
window->pending_menu_cmd = 0;
|
||||
return cmd;
|
||||
}
|
||||
@@ -255,8 +254,8 @@ PlatformInput platform_get_input(PlatformWindow *window) {
|
||||
result.mouse_pos = v2f32((F32)cursor.x, (F32)cursor.y);
|
||||
|
||||
// Poll mouse button
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||
result.was_mouse_down = window->prev_mouse_down;
|
||||
result.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
|
||||
window->prev_mouse_down = result.mouse_down;
|
||||
|
||||
// Clear accumulated events for next frame
|
||||
@@ -283,7 +282,7 @@ 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;
|
||||
default: g_current_cursor = LoadCursor(nullptr, IDC_ARROW); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +324,7 @@ const char *platform_clipboard_get() {
|
||||
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, nullptr, nullptr);
|
||||
buf[len > 0 ? len - 1 : 0] = '\0'; // WideCharToMultiByte includes null in count
|
||||
GlobalUnlock(hmem);
|
||||
}
|
||||
@@ -339,32 +338,32 @@ S32 platform_message_box(PlatformWindow *parent, const char *title,
|
||||
const char *message, PlatformMsgBoxType type) {
|
||||
UINT mb_type;
|
||||
switch (type) {
|
||||
case PLATFORM_MSGBOX_OK: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_OK: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_OK_CANCEL: mb_type = MB_OKCANCEL; break;
|
||||
case PLATFORM_MSGBOX_YES_NO: mb_type = MB_YESNO; break;
|
||||
default: mb_type = MB_OK; break;
|
||||
case PLATFORM_MSGBOX_YES_NO: mb_type = MB_YESNO; break;
|
||||
default: mb_type = MB_OK; break;
|
||||
}
|
||||
|
||||
// Convert UTF-8 to wide strings
|
||||
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 0);
|
||||
wchar_t *wtitle = (wchar_t *)_malloca(title_wlen * sizeof(wchar_t));
|
||||
int title_wlen = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 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);
|
||||
wchar_t *wmsg = (wchar_t *)_malloca(msg_wlen * sizeof(wchar_t));
|
||||
int msg_wlen = MultiByteToWideChar(CP_UTF8, 0, message, -1, nullptr, 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;
|
||||
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
|
||||
HWND hwnd = parent ? parent->hwnd : nullptr;
|
||||
int result = MessageBoxW(hwnd, wmsg, wtitle, mb_type);
|
||||
|
||||
_freea(wmsg);
|
||||
_freea(wtitle);
|
||||
|
||||
switch (result) {
|
||||
case IDOK: return 0;
|
||||
case IDYES: return 0;
|
||||
case IDOK: return 0;
|
||||
case IDYES: return 0;
|
||||
case IDCANCEL: return 1;
|
||||
case IDNO: return 1;
|
||||
default: return -1;
|
||||
case IDNO: return 1;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ typedef struct Renderer Renderer;
|
||||
struct Clay_RenderCommandArray;
|
||||
|
||||
typedef struct RendererDesc {
|
||||
void *window_handle;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
void *window_handle;
|
||||
S32 width;
|
||||
S32 height;
|
||||
S32 frame_count;
|
||||
} RendererDesc;
|
||||
|
||||
Renderer *renderer_create(RendererDesc *desc);
|
||||
@@ -24,13 +24,13 @@ Renderer *renderer_create(RendererDesc *desc);
|
||||
// windows.
|
||||
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_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
|
||||
void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
void renderer_destroy(Renderer *renderer);
|
||||
B32 renderer_begin_frame(Renderer *renderer);
|
||||
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
|
||||
void renderer_set_clear_color(Renderer *renderer, F32 r, F32 g, F32 b);
|
||||
|
||||
// Text measurement callback compatible with UI_MeasureTextFn
|
||||
// Measures text of given length (NOT necessarily null-terminated) at font_size pixels.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,8 @@ F32 g_ui_scale = 1.0f;
|
||||
////////////////////////////////
|
||||
// Theme global
|
||||
|
||||
UI_Theme g_theme = {};
|
||||
S32 g_theme_id = 0;
|
||||
UI_Theme g_theme = {};
|
||||
S32 g_theme_id = 0;
|
||||
|
||||
void ui_set_theme(S32 theme_id) {
|
||||
g_theme_id = theme_id;
|
||||
@@ -27,55 +27,55 @@ 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.corner_radius = 4.0f;
|
||||
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.corner_radius = 4.0f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ S32 g_accent_id = 0;
|
||||
|
||||
void ui_set_accent(S32 accent_id) {
|
||||
g_accent_id = accent_id;
|
||||
B32 dark = (g_theme_id == 0);
|
||||
B32 dark = (g_theme_id == 0);
|
||||
|
||||
// Each palette: accent, accent_hover, tab_top, tab_bottom, button_text, tab_text
|
||||
struct AccentColors {
|
||||
@@ -97,44 +97,44 @@ void ui_set_accent(S32 accent_id) {
|
||||
// Dark-mode palettes
|
||||
static const AccentColors dark_palettes[] = {
|
||||
// 0: Blue
|
||||
{ {87,138,176,255}, {102,153,191,255}, {70,120,160,255}, {30,55,80,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
{ { 87, 138, 176, 255 }, { 102, 153, 191, 255 }, { 70, 120, 160, 255 }, { 30, 55, 80, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 1: Turquoise
|
||||
{ {60,160,155,255}, {75,175,170,255}, {50,140,135,255}, {25,70,68,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
{ { 60, 160, 155, 255 }, { 75, 175, 170, 255 }, { 50, 140, 135, 255 }, { 25, 70, 68, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 2: Orange
|
||||
{ {200,130,50,255}, {215,145,65,255}, {190,120,40,255}, {100,60,20,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
{ { 200, 130, 50, 255 }, { 215, 145, 65, 255 }, { 190, 120, 40, 255 }, { 100, 60, 20, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 3: Purple
|
||||
{ {130,100,180,255}, {145,115,195,255}, {120,90,170,255}, {60,45,85,255}, {224,224,224,255}, {240,240,240,255} },
|
||||
{ { 130, 100, 180, 255 }, { 145, 115, 195, 255 }, { 120, 90, 170, 255 }, { 60, 45, 85, 255 }, { 224, 224, 224, 255 }, { 240, 240, 240, 255 } },
|
||||
// 4: Pink
|
||||
{ {185,95,140,255}, {200,110,155,255}, {175,85,130,255}, {88,42,65,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
{ { 185, 95, 140, 255 }, { 200, 110, 155, 255 }, { 175, 85, 130, 255 }, { 88, 42, 65, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 5: Red
|
||||
{ {190,75,75,255}, {205,90,90,255}, {180,65,65,255}, {90,32,32,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
{ { 190, 75, 75, 255 }, { 205, 90, 90, 255 }, { 180, 65, 65, 255 }, { 90, 32, 32, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
// 6: Green
|
||||
{ {80,155,80,255}, {95,170,95,255}, {70,140,70,255}, {35,70,35,255}, {240,240,240,255}, {240,240,240,255} },
|
||||
{ { 80, 155, 80, 255 }, { 95, 170, 95, 255 }, { 70, 140, 70, 255 }, { 35, 70, 35, 255 }, { 240, 240, 240, 255 }, { 240, 240, 240, 255 } },
|
||||
};
|
||||
|
||||
// Light-mode palettes (slightly more saturated for contrast on light bg)
|
||||
static const AccentColors light_palettes[] = {
|
||||
// 0: Blue
|
||||
{ {50,110,170,255}, {65,125,185,255}, {70,130,180,255}, {50,100,150,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 50, 110, 170, 255 }, { 65, 125, 185, 255 }, { 70, 130, 180, 255 }, { 50, 100, 150, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 1: Turquoise
|
||||
{ {30,140,135,255}, {45,155,150,255}, {40,150,145,255}, {25,115,110,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 30, 140, 135, 255 }, { 45, 155, 150, 255 }, { 40, 150, 145, 255 }, { 25, 115, 110, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 2: Orange
|
||||
{ {190,115,25,255}, {205,130,40,255}, {195,120,30,255}, {155,90,15,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 190, 115, 25, 255 }, { 205, 130, 40, 255 }, { 195, 120, 30, 255 }, { 155, 90, 15, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 3: Purple
|
||||
{ {110,75,170,255}, {125,90,185,255}, {115,80,175,255}, {85,55,140,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 110, 75, 170, 255 }, { 125, 90, 185, 255 }, { 115, 80, 175, 255 }, { 85, 55, 140, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 4: Pink
|
||||
{ {175,70,125,255}, {190,85,140,255}, {180,75,130,255}, {140,50,100,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 175, 70, 125, 255 }, { 190, 85, 140, 255 }, { 180, 75, 130, 255 }, { 140, 50, 100, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 5: Red
|
||||
{ {185,55,55,255}, {200,70,70,255}, {190,60,60,255}, {150,40,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 185, 55, 55, 255 }, { 200, 70, 70, 255 }, { 190, 60, 60, 255 }, { 150, 40, 40, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
// 6: Green
|
||||
{ {55,140,55,255}, {70,155,70,255}, {60,145,60,255}, {40,110,40,255}, {255,255,255,255}, {255,255,255,255} },
|
||||
{ { 55, 140, 55, 255 }, { 70, 155, 70, 255 }, { 60, 145, 60, 255 }, { 40, 110, 40, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 } },
|
||||
};
|
||||
|
||||
S32 idx = accent_id;
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 6) idx = 6;
|
||||
|
||||
const AccentColors &c = dark ? dark_palettes[idx] : light_palettes[idx];
|
||||
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;
|
||||
@@ -148,7 +148,7 @@ void ui_set_accent(S32 accent_id) {
|
||||
|
||||
static void clay_error_handler(Clay_ErrorData error) {
|
||||
char buf[512];
|
||||
S32 len = error.errorText.length < 511 ? error.errorText.length : 511;
|
||||
S32 len = error.errorText.length < 511 ? error.errorText.length : 511;
|
||||
memcpy(buf, error.errorText.chars, len);
|
||||
buf[len] = '\0';
|
||||
fprintf(stderr, "[Clay Error] %s\n", buf);
|
||||
@@ -163,10 +163,10 @@ static UI_Context *g_measure_ctx = nullptr;
|
||||
static Clay_Dimensions clay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *user_data) {
|
||||
UI_Context *ctx = (UI_Context *)user_data;
|
||||
if (!ctx || !ctx->measure_text_fn || text.length == 0) {
|
||||
return Clay_Dimensions{0, (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 };
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -175,17 +175,17 @@ static Clay_Dimensions clay_measure_text(Clay_StringSlice text, Clay_TextElement
|
||||
UI_Context *ui_create(F32 viewport_w, F32 viewport_h) {
|
||||
UI_Context *ctx = (UI_Context *)calloc(1, sizeof(UI_Context));
|
||||
|
||||
U32 min_memory = Clay_MinMemorySize();
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
U32 min_memory = Clay_MinMemorySize();
|
||||
ctx->clay_memory = malloc(min_memory);
|
||||
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(min_memory, ctx->clay_memory);
|
||||
|
||||
Clay_ErrorHandler err_handler = {};
|
||||
Clay_ErrorHandler err_handler = {};
|
||||
err_handler.errorHandlerFunction = clay_error_handler;
|
||||
err_handler.userData = ctx;
|
||||
err_handler.userData = ctx;
|
||||
|
||||
ctx->clay_ctx = Clay_Initialize(clay_arena,
|
||||
Clay_Dimensions{viewport_w, viewport_h},
|
||||
err_handler);
|
||||
Clay_Dimensions{ viewport_w, viewport_h },
|
||||
err_handler);
|
||||
|
||||
Clay_SetMeasureTextFunction(clay_measure_text, ctx);
|
||||
|
||||
@@ -200,13 +200,12 @@ void ui_destroy(UI_Context *ctx) {
|
||||
|
||||
void ui_begin_frame(UI_Context *ctx, F32 viewport_w, F32 viewport_h,
|
||||
Vec2F32 mouse_pos, B32 mouse_down,
|
||||
Vec2F32 scroll_delta, F32 dt)
|
||||
{
|
||||
Vec2F32 scroll_delta, F32 dt) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -216,7 +215,7 @@ Clay_RenderCommandArray ui_end_frame(UI_Context *ctx) {
|
||||
}
|
||||
|
||||
void ui_set_measure_text_fn(UI_Context *ctx, UI_MeasureTextFn fn, void *user_data) {
|
||||
ctx->measure_text_fn = fn;
|
||||
ctx->measure_text_fn = fn;
|
||||
ctx->measure_text_user_data = user_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ typedef Vec2F32 (*UI_MeasureTextFn)(const char *text, S32 length, F32 font_size,
|
||||
// UI Context
|
||||
|
||||
struct UI_Context {
|
||||
Clay_Context *clay_ctx;
|
||||
void *clay_memory;
|
||||
Clay_Context *clay_ctx;
|
||||
void *clay_memory;
|
||||
|
||||
// Text measurement
|
||||
UI_MeasureTextFn measure_text_fn;
|
||||
@@ -65,28 +65,28 @@ struct UI_Theme {
|
||||
Clay_Color title_bar;
|
||||
Clay_Color scrollbar_bg;
|
||||
Clay_Color scrollbar_grab;
|
||||
Clay_Color shadow; // Semi-transparent black for drop shadows
|
||||
Clay_Color shadow; // Semi-transparent black for drop shadows
|
||||
|
||||
// Tab bar colors
|
||||
Clay_Color tab_active_top;
|
||||
Clay_Color tab_active_bottom;
|
||||
Clay_Color tab_inactive;
|
||||
Clay_Color tab_inactive_hover;
|
||||
Clay_Color tab_text; // Always light — readable on colored tab gradient
|
||||
Clay_Color tab_text; // Always light — readable on colored tab gradient
|
||||
|
||||
// Corner radius (unscaled pixels, applied via uis())
|
||||
F32 corner_radius;
|
||||
};
|
||||
|
||||
extern UI_Theme g_theme;
|
||||
extern S32 g_theme_id;
|
||||
extern S32 g_theme_id;
|
||||
|
||||
// Set theme by index: 0 = Dark, 1 = Light
|
||||
void ui_set_theme(S32 theme_id);
|
||||
|
||||
// Accent color palette: 0=Blue, 1=Turquoise, 2=Orange, 3=Purple, 4=Pink, 5=Red, 6=Green
|
||||
extern S32 g_accent_id;
|
||||
void ui_set_accent(S32 accent_id);
|
||||
void ui_set_accent(S32 accent_id);
|
||||
|
||||
////////////////////////////////
|
||||
// UI scale (Cmd+/Cmd- zoom)
|
||||
@@ -105,37 +105,37 @@ static inline U16 uifs(F32 x) { return (U16)(x * g_ui_scale + 0.5f); }
|
||||
////////////////////////////////
|
||||
// Tab styling
|
||||
|
||||
#define TAB_ACTIVE_TOP g_theme.tab_active_top
|
||||
#define TAB_ACTIVE_BOTTOM g_theme.tab_active_bottom
|
||||
#define TAB_INACTIVE_BG g_theme.tab_inactive
|
||||
#define TAB_INACTIVE_HOVER g_theme.tab_inactive_hover
|
||||
#define TAB_HEIGHT uis(26)
|
||||
#define TAB_CORNER_RADIUS CORNER_RADIUS
|
||||
#define TAB_PADDING_H uip(10)
|
||||
#define TAB_ACTIVE_TOP g_theme.tab_active_top
|
||||
#define TAB_ACTIVE_BOTTOM g_theme.tab_active_bottom
|
||||
#define TAB_INACTIVE_BG g_theme.tab_inactive
|
||||
#define TAB_INACTIVE_HOVER g_theme.tab_inactive_hover
|
||||
#define TAB_HEIGHT uis(26)
|
||||
#define TAB_CORNER_RADIUS CORNER_RADIUS
|
||||
#define TAB_PADDING_H uip(10)
|
||||
|
||||
////////////////////////////////
|
||||
// Custom render types (for gradient rects via CLAY_RENDER_COMMAND_TYPE_CUSTOM)
|
||||
|
||||
enum CustomRenderType {
|
||||
CUSTOM_RENDER_VGRADIENT = 1,
|
||||
CUSTOM_RENDER_ICON = 2,
|
||||
CUSTOM_RENDER_VGRADIENT = 1,
|
||||
CUSTOM_RENDER_ICON = 2,
|
||||
CUSTOM_RENDER_ROTATED_ICON = 3,
|
||||
};
|
||||
|
||||
struct CustomGradientData {
|
||||
CustomRenderType type;
|
||||
Clay_Color top_color;
|
||||
Clay_Color bottom_color;
|
||||
Clay_Color top_color;
|
||||
Clay_Color bottom_color;
|
||||
};
|
||||
|
||||
struct CustomIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
CustomRenderType type; // CUSTOM_RENDER_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
};
|
||||
|
||||
struct CustomRotatedIconData {
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
CustomRenderType type; // CUSTOM_RENDER_ROTATED_ICON
|
||||
S32 icon_id;
|
||||
Clay_Color color;
|
||||
F32 angle_rad;
|
||||
@@ -144,23 +144,23 @@ struct CustomRotatedIconData {
|
||||
////////////////////////////////
|
||||
// Font sizes
|
||||
|
||||
#define FONT_SIZE_NORMAL uifs(15)
|
||||
#define FONT_SIZE_SMALL uifs(12)
|
||||
#define FONT_SIZE_TAB uifs(13)
|
||||
#define FONT_SIZE_NORMAL uifs(15)
|
||||
#define FONT_SIZE_SMALL uifs(12)
|
||||
#define FONT_SIZE_TAB uifs(13)
|
||||
|
||||
////////////////////////////////
|
||||
// Widget sizing
|
||||
|
||||
#define WIDGET_BUTTON_HEIGHT uis(30)
|
||||
#define WIDGET_CHECKBOX_HEIGHT uis(28)
|
||||
#define WIDGET_CHECKBOX_SIZE uis(18)
|
||||
#define WIDGET_RADIO_OUTER uis(16)
|
||||
#define WIDGET_RADIO_INNER uis(8)
|
||||
#define WIDGET_INPUT_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_ITEM_H uis(28)
|
||||
#define WIDGET_KNOB_SIZE uis(48)
|
||||
#define WIDGET_KNOB_LABEL_GAP uip(4)
|
||||
#define WIDGET_BUTTON_HEIGHT uis(30)
|
||||
#define WIDGET_CHECKBOX_HEIGHT uis(28)
|
||||
#define WIDGET_CHECKBOX_SIZE uis(18)
|
||||
#define WIDGET_RADIO_OUTER uis(16)
|
||||
#define WIDGET_RADIO_INNER uis(8)
|
||||
#define WIDGET_INPUT_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_HEIGHT uis(30)
|
||||
#define WIDGET_DROPDOWN_ITEM_H uis(28)
|
||||
#define WIDGET_KNOB_SIZE uis(48)
|
||||
#define WIDGET_KNOB_LABEL_GAP uip(4)
|
||||
|
||||
#define WIDGET_SLIDER_H_WIDTH uis(160)
|
||||
#define WIDGET_SLIDER_H_TRACK_H uis(6)
|
||||
@@ -181,11 +181,11 @@ struct CustomRotatedIconData {
|
||||
////////////////////////////////
|
||||
// Corner radius (from theme)
|
||||
|
||||
#define CORNER_RADIUS uis(g_theme.corner_radius)
|
||||
#define CORNER_RADIUS uis(g_theme.corner_radius)
|
||||
|
||||
////////////////////////////////
|
||||
// Panel sizing
|
||||
|
||||
#define PANEL_BROWSER_WIDTH uis(200)
|
||||
#define PANEL_RIGHT_COL_WIDTH uis(250)
|
||||
#define PANEL_LOG_HEIGHT uis(180)
|
||||
#define PANEL_BROWSER_WIDTH uis(200)
|
||||
#define PANEL_RIGHT_COL_WIDTH uis(250)
|
||||
#define PANEL_LOG_HEIGHT uis(180)
|
||||
|
||||
@@ -163,16 +163,16 @@ U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 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();
|
||||
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];
|
||||
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];
|
||||
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);
|
||||
@@ -190,8 +190,8 @@ U8 *ui_icons_rasterize_atlas(S32 *out_w, S32 *out_h, S32 icon_size) {
|
||||
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;
|
||||
g_icons[i].w = (F32)bmp_w;
|
||||
g_icons[i].h = (F32)bmp_h;
|
||||
|
||||
pen_x += icon_size;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ enum UI_IconID {
|
||||
};
|
||||
|
||||
struct UI_IconInfo {
|
||||
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
|
||||
F32 w, h; // pixel dimensions at rasterized size
|
||||
F32 u0, v0, u1, v1; // UV coordinates in icon atlas
|
||||
F32 w, h; // pixel dimensions at rasterized size
|
||||
};
|
||||
|
||||
extern UI_IconInfo g_icons[UI_ICON_COUNT];
|
||||
|
||||
@@ -14,26 +14,25 @@ Clay_Color piano_velocity_color(S32 velocity) {
|
||||
F32 r, g, b;
|
||||
if (t < 0.5f) {
|
||||
F32 s = t * 2.0f;
|
||||
r = 40.0f + s * (76.0f - 40.0f);
|
||||
g = 120.0f + s * (175.0f - 120.0f);
|
||||
b = 220.0f + s * (80.0f - 220.0f);
|
||||
r = 40.0f + s * (76.0f - 40.0f);
|
||||
g = 120.0f + s * (175.0f - 120.0f);
|
||||
b = 220.0f + s * (80.0f - 220.0f);
|
||||
} else {
|
||||
F32 s = (t - 0.5f) * 2.0f;
|
||||
r = 76.0f + s * (220.0f - 76.0f);
|
||||
g = 175.0f + s * (50.0f - 175.0f);
|
||||
b = 80.0f + s * (40.0f - 80.0f);
|
||||
r = 76.0f + s * (220.0f - 76.0f);
|
||||
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) {
|
||||
Clay_ElementId piano_id = CLAY_ID("PianoContainer");
|
||||
CLAY(piano_id,
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT,
|
||||
}
|
||||
) {
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT,
|
||||
}) {
|
||||
// Compute black key size proportional to white keys
|
||||
F32 black_key_h = avail_h * PIANO_BLACK_H_PCT;
|
||||
if (black_key_h < uis(20)) black_key_h = uis(20);
|
||||
@@ -46,24 +45,22 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||
if (piano_is_black_key(note)) continue;
|
||||
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} 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),
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
},
|
||||
.backgroundColor = bg,
|
||||
.border = { .color = {190, 190, 190, 255}, .width = { .right = 1 } },
|
||||
) {}
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
},
|
||||
.backgroundColor = bg, .border = { .color = { 190, 190, 190, 255 }, .width = { .right = 1 } }, ) {}
|
||||
}
|
||||
|
||||
// Black keys (floating, attached to left white key)
|
||||
@@ -71,36 +68,33 @@ void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h)
|
||||
if (!piano_is_black_key(note)) continue;
|
||||
|
||||
Clay_ElementId parent_wkey = CLAY_IDI("PKey", note - 1);
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
B32 midi_held = midi_is_note_held(midi, note);
|
||||
B32 mouse_held = state->mouse_note == note;
|
||||
Clay_Color bg;
|
||||
if (midi_held) {
|
||||
bg = piano_velocity_color(midi_get_note_velocity(midi, note));
|
||||
} 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),
|
||||
.layout = {
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(black_key_w),
|
||||
.height = CLAY_SIZING_FIXED(black_key_h),
|
||||
},
|
||||
},
|
||||
.backgroundColor = bg,
|
||||
.cornerRadius = { .topLeft = 0, .topRight = 0, .bottomLeft = uis(2), .bottomRight = uis(2) },
|
||||
.floating = {
|
||||
.parentId = parent_wkey.id,
|
||||
.zIndex = 100,
|
||||
.attachPoints = {
|
||||
.element = CLAY_ATTACH_POINT_CENTER_TOP,
|
||||
.parent = CLAY_ATTACH_POINT_RIGHT_TOP,
|
||||
},
|
||||
.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID,
|
||||
},
|
||||
) {}
|
||||
.layout = {
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(black_key_w),
|
||||
.height = CLAY_SIZING_FIXED(black_key_h),
|
||||
},
|
||||
},
|
||||
.backgroundColor = bg, .cornerRadius = { .topLeft = 0, .topRight = 0, .bottomLeft = uis(2), .bottomRight = uis(2) }, .floating = {
|
||||
.parentId = parent_wkey.id,
|
||||
.zIndex = 100,
|
||||
.attachPoints = {
|
||||
.element = CLAY_ATTACH_POINT_CENTER_TOP,
|
||||
.parent = CLAY_ATTACH_POINT_RIGHT_TOP,
|
||||
},
|
||||
.attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID,
|
||||
}, ) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
#include "ui/ui_core.h"
|
||||
#include "midi/midi.h"
|
||||
#include "ui/ui_core.h"
|
||||
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
#define PIANO_FIRST_NOTE 21 // A0
|
||||
#define PIANO_LAST_NOTE 108 // C8
|
||||
|
||||
struct UI_PianoState {
|
||||
S32 mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||
S32 mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||
};
|
||||
|
||||
B32 piano_is_black_key(S32 note);
|
||||
|
||||
@@ -23,7 +23,10 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
// Find free slot
|
||||
PopupWindow *popup = nullptr;
|
||||
for (S32 i = 0; i < MAX_POPUP_WINDOWS; i++) {
|
||||
if (!g_popups[i].alive) { popup = &g_popups[i]; break; }
|
||||
if (!g_popups[i].alive) {
|
||||
popup = &g_popups[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!popup) return nullptr;
|
||||
|
||||
@@ -31,24 +34,24 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
|
||||
// Create native popup window
|
||||
PlatformWindowDesc desc = {};
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.style = style;
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
desc.title = title;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.style = style;
|
||||
desc.parent = parent_window;
|
||||
desc.independent = independent;
|
||||
popup->platform_window = platform_create_window(&desc);
|
||||
if (!popup->platform_window) return nullptr;
|
||||
|
||||
// Create shared renderer
|
||||
S32 pw, ph;
|
||||
platform_get_size(popup->platform_window, &pw, &ph);
|
||||
|
||||
RendererDesc rdesc = {};
|
||||
RendererDesc rdesc = {};
|
||||
rdesc.window_handle = platform_get_native_handle(popup->platform_window);
|
||||
rdesc.width = pw;
|
||||
rdesc.height = ph;
|
||||
popup->renderer = renderer_create_shared(parent_renderer, &rdesc);
|
||||
popup->renderer = renderer_create_shared(parent_renderer, &rdesc);
|
||||
if (!popup->renderer) {
|
||||
platform_destroy_window(popup->platform_window);
|
||||
return nullptr;
|
||||
@@ -58,16 +61,16 @@ PopupWindow *popup_open(PlatformWindow *parent_window, Renderer *parent_renderer
|
||||
popup->ui_ctx = ui_create((F32)pw, (F32)ph);
|
||||
ui_set_measure_text_fn(popup->ui_ctx, renderer_measure_text, popup->renderer);
|
||||
|
||||
popup->alive = 1;
|
||||
popup->open_flag = open_flag;
|
||||
popup->content_fn = content_fn;
|
||||
popup->alive = 1;
|
||||
popup->open_flag = open_flag;
|
||||
popup->content_fn = content_fn;
|
||||
popup->content_user_data = user_data;
|
||||
popup->width = width;
|
||||
popup->height = height;
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
popup->width = width;
|
||||
popup->height = height;
|
||||
popup->last_w = pw;
|
||||
popup->last_h = ph;
|
||||
popup->title = title;
|
||||
popup->wstate = {};
|
||||
|
||||
platform_set_frame_callback(popup->platform_window, popup_frame_callback, popup);
|
||||
|
||||
@@ -111,7 +114,7 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
if (input.ctrl_held) {
|
||||
if (input.keys[k] == PKEY_EQUAL) g_ui_scale *= 1.1f;
|
||||
if (input.keys[k] == PKEY_MINUS) g_ui_scale /= 1.1f;
|
||||
if (input.keys[k] == PKEY_0) g_ui_scale = 1.0f;
|
||||
if (input.keys[k] == PKEY_0) g_ui_scale = 1.0f;
|
||||
}
|
||||
}
|
||||
g_ui_scale = Clamp(0.5f, g_ui_scale, 3.0f);
|
||||
@@ -121,7 +124,7 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Swap widget state
|
||||
UI_WidgetState saved_wstate = g_wstate;
|
||||
g_wstate = popup->wstate;
|
||||
g_wstate = popup->wstate;
|
||||
|
||||
ui_widgets_begin_frame(input);
|
||||
|
||||
@@ -131,14 +134,13 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Background fill
|
||||
CLAY(CLAY_ID("PopupBg"),
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.padding = { uip(12), uip(12), uip(10), uip(10) },
|
||||
.childGap = uip(8),
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
.backgroundColor = g_theme.bg_medium,
|
||||
) {
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||
.padding = { uip(12), uip(12), uip(10), uip(10) },
|
||||
.childGap = uip(8),
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
},
|
||||
.backgroundColor = g_theme.bg_medium, ) {
|
||||
if (popup->content_fn) {
|
||||
popup->content_fn(popup->content_user_data);
|
||||
}
|
||||
@@ -148,7 +150,7 @@ void popup_do_frame(PopupWindow *popup, F32 dt) {
|
||||
|
||||
// Save widget state back
|
||||
popup->wstate = g_wstate;
|
||||
g_wstate = saved_wstate;
|
||||
g_wstate = saved_wstate;
|
||||
|
||||
// Render
|
||||
renderer_end_frame(popup->renderer, render_commands);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
#pragma once
|
||||
#include "ui/ui_core.h"
|
||||
#include "ui/ui_widgets.h"
|
||||
#include "platform/platform.h"
|
||||
#include "renderer/renderer.h"
|
||||
#include "ui/ui_core.h"
|
||||
#include "ui/ui_widgets.h"
|
||||
|
||||
#define MAX_POPUP_WINDOWS 4
|
||||
|
||||
struct PopupWindow {
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
Renderer *renderer;
|
||||
UI_Context *ui_ctx;
|
||||
UI_WidgetState wstate;
|
||||
B32 alive;
|
||||
B32 *open_flag; // e.g. &app->show_settings_window
|
||||
PlatformWindow *platform_window;
|
||||
Renderer *renderer;
|
||||
UI_Context *ui_ctx;
|
||||
UI_WidgetState wstate;
|
||||
UI_WindowContentFn content_fn;
|
||||
void *content_user_data;
|
||||
S32 width, height;
|
||||
S32 last_w, last_h;
|
||||
const char *title;
|
||||
void *content_user_data;
|
||||
S32 width, height;
|
||||
S32 last_w, last_h;
|
||||
const char *title;
|
||||
};
|
||||
|
||||
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 = PLATFORM_WINDOW_STYLE_POPUP,
|
||||
B32 independent = 0);
|
||||
void popup_close(PopupWindow *popup);
|
||||
PopupWindow *popup_find_by_flag(B32 *flag);
|
||||
void popup_do_frame(PopupWindow *popup, F32 dt);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@
|
||||
// The caller owns all data — the widget layer only stores transient UI state
|
||||
// like which text field is focused or which dropdown is open.
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "ui/ui_core.h"
|
||||
#include "ui/ui_icons.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
////////////////////////////////
|
||||
// Widget state (global, managed by widget layer)
|
||||
@@ -17,29 +17,29 @@
|
||||
#define UI_WIDGET_MAX_TEXT_INPUTS 16
|
||||
|
||||
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)
|
||||
F32 value_at_start; // Value when drag started
|
||||
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
|
||||
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)
|
||||
F32 value_at_start; // Value when drag started
|
||||
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
|
||||
};
|
||||
|
||||
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
|
||||
F32 cursor_blink; // Blink timer (seconds)
|
||||
U32 focused_id; // Clay element ID hash of the focused text input (0 = none)
|
||||
S32 cursor_pos; // Cursor position in focused text input
|
||||
F32 cursor_blink; // Blink timer (seconds)
|
||||
|
||||
// Text selection (sel_start == sel_end means no selection)
|
||||
S32 sel_start; // Selection anchor (where selection began)
|
||||
S32 sel_end; // Selection extent (moves with cursor)
|
||||
S32 sel_start; // Selection anchor (where selection began)
|
||||
S32 sel_end; // Selection extent (moves with cursor)
|
||||
|
||||
// Tab cycling: registered text input IDs in order of declaration
|
||||
U32 text_input_ids[UI_WIDGET_MAX_TEXT_INPUTS];
|
||||
S32 text_input_count;
|
||||
B32 tab_pressed; // True on the frame Tab was pressed
|
||||
U32 text_input_ids[UI_WIDGET_MAX_TEXT_INPUTS];
|
||||
S32 text_input_count;
|
||||
B32 tab_pressed; // True on the frame Tab was pressed
|
||||
|
||||
// Dropdown
|
||||
U32 open_dropdown_id; // Clay element ID hash of the open dropdown (0 = none)
|
||||
@@ -54,11 +54,11 @@ struct UI_WidgetState {
|
||||
UI_KnobDragState knob_drag;
|
||||
|
||||
// Knob text edit state
|
||||
U32 knob_edit_id; // Hash of knob in text edit mode (0 = none)
|
||||
char knob_edit_buf[32]; // Text buffer for numeric entry
|
||||
S32 knob_edit_cursor; // Cursor position in edit buffer
|
||||
S32 knob_edit_sel_start; // Selection anchor
|
||||
S32 knob_edit_sel_end; // Selection extent
|
||||
U32 knob_edit_id; // Hash of knob in text edit mode (0 = none)
|
||||
char knob_edit_buf[32]; // Text buffer for numeric entry
|
||||
S32 knob_edit_cursor; // Cursor position in edit buffer
|
||||
S32 knob_edit_sel_start; // Selection anchor
|
||||
S32 knob_edit_sel_end; // Selection extent
|
||||
};
|
||||
|
||||
extern UI_WidgetState g_wstate;
|
||||
|
||||
Reference in New Issue
Block a user