From c116f4665e5cda55e8a765ccd25a7d55a8ac3ad1 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Thu, 11 Dec 2025 10:39:24 -0600 Subject: [PATCH] initial add --- audio.c | 418 +++++++++++ camera.c | 195 +++++ cell.toml | 2 + clipboard.c | 90 +++ events.c | 547 ++++++++++++++ examples/lenna.ce | 127 ++++ gamepad.c | 502 +++++++++++++ gpu.c | 1724 +++++++++++++++++++++++++++++++++++++++++++++ haptic.c | 212 ++++++ hidapi.c | 280 ++++++++ input.c | 780 ++++++++++++++++++++ joystick.c | 393 +++++++++++ keyboard.c | 143 ++++ mouse.c | 109 +++ pen.c | 83 +++ render.c | 250 +++++++ sdl.c | 333 +++++++++ sdl.h | 109 +++ sensor.c | 156 ++++ surface.c | 1109 +++++++++++++++++++++++++++++ touch.c | 73 ++ tray.c | 255 +++++++ video.c | 759 ++++++++++++++++++++ 23 files changed, 8649 insertions(+) create mode 100644 audio.c create mode 100644 camera.c create mode 100644 cell.toml create mode 100644 clipboard.c create mode 100644 events.c create mode 100644 examples/lenna.ce create mode 100644 gamepad.c create mode 100644 gpu.c create mode 100644 haptic.c create mode 100644 hidapi.c create mode 100644 input.c create mode 100644 joystick.c create mode 100644 keyboard.c create mode 100644 mouse.c create mode 100644 pen.c create mode 100644 render.c create mode 100644 sdl.c create mode 100644 sdl.h create mode 100644 sensor.c create mode 100644 surface.c create mode 100644 touch.c create mode 100644 tray.c create mode 100644 video.c diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..292643d --- /dev/null +++ b/audio.c @@ -0,0 +1,418 @@ +#include +#include +#include "cell.h" + +#define countof(x) (sizeof(x)/sizeof((x)[0])) + +// Helper functions +double js2number(JSContext *js, JSValue v); +int js2bool(JSContext *js, JSValue v); + +// Free functions for finalizers +void SDL_AudioStream_free(JSRuntime *rt, SDL_AudioStream *stream) { + SDL_DestroyAudioStream(stream); +} + +// Class definitions +QJSCLASS(SDL_AudioStream,) + +// Conversion functions +SDL_AudioFormat js2SDL_AudioFormat(JSContext *js, JSValue v) { + int fmt = js2number(js, v); + return (SDL_AudioFormat)fmt; +} + +JSValue SDL_AudioFormat2js(JSContext *js, SDL_AudioFormat fmt) { + return JS_NewInt32(js, (int)fmt); +} + +SDL_AudioDeviceID js2SDL_AudioDeviceID(JSContext *js, JSValue v) { + return (SDL_AudioDeviceID)js2number(js, v); +} + +JSValue SDL_AudioDeviceID2js(JSContext *js, SDL_AudioDeviceID id) { + return JS_NewInt32(js, (int)id); +} + +SDL_AudioSpec js2SDL_AudioSpec(JSContext *js, JSValue v) { + SDL_AudioSpec spec = {0}; + JS_GETPROP(js, spec.format, v, format, SDL_AudioFormat) + JS_GETPROP(js, spec.channels, v, channels, number) + JS_GETPROP(js, spec.freq, v, freq, number) + return spec; +} + +JSValue SDL_AudioSpec2js(JSContext *js, SDL_AudioSpec spec) { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "format", SDL_AudioFormat2js(js, spec.format)); + JS_SetPropertyStr(js, obj, "channels", JS_NewInt32(js, spec.channels)); + JS_SetPropertyStr(js, obj, "freq", JS_NewInt32(js, spec.freq)); + return obj; +} + +// Enum mappings for audio formats (simplified) +JSValue js_get_audio_drivers(JSContext *js, JSValue self, int argc, JSValue *argv) { + int count = SDL_GetNumAudioDrivers(); + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + const char *driver = SDL_GetAudioDriver(i); + JS_SetPropertyUint32(js, arr, i, JS_NewString(js, driver)); + } + return arr; +} + +JSValue js_get_current_audio_driver(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *driver = SDL_GetCurrentAudioDriver(); + return driver ? JS_NewString(js, driver) : JS_NULL; +} + +JSValue js_get_audio_playback_devices(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(NULL); + if (!devices) return JS_NULL; + JSValue arr = JS_NewArray(js); + for (int i = 0; devices[i]; i++) { + JS_SetPropertyUint32(js, arr, i, SDL_AudioDeviceID2js(js, devices[i])); + } + SDL_free(devices); + return arr; +} + +JSValue js_get_audio_recording_devices(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID *devices = SDL_GetAudioRecordingDevices(NULL); + if (!devices) return JS_NULL; + JSValue arr = JS_NewArray(js); + for (int i = 0; devices[i]; i++) { + JS_SetPropertyUint32(js, arr, i, SDL_AudioDeviceID2js(js, devices[i])); + } + SDL_free(devices); + return arr; +} + +JSValue js_get_audio_device_name(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + const char *name = SDL_GetAudioDeviceName(devid); + return name ? JS_NewString(js, name) : JS_NULL; +} + +JSValue js_is_audio_device_playback(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + return JS_NewBool(js, SDL_IsAudioDevicePlayback(devid)); +} + +JSValue js_is_audio_device_physical(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + return JS_NewBool(js, SDL_IsAudioDevicePhysical(devid)); +} + +JSValue js_get_audio_device_format(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_AudioSpec spec; + if (!SDL_GetAudioDeviceFormat(devid, &spec, NULL)) { + return JS_NULL; + } + return SDL_AudioSpec2js(js, spec); +} + +JSValue js_open_audio_device_stream(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_AudioSpec spec = {0}; + if (argc > 1) { + spec = js2SDL_AudioSpec(js, argv[1]); + } + SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(devid, &spec, NULL, NULL); + if (!stream) { + return JS_ThrowInternalError(js, "Failed to open audio device stream: %s", SDL_GetError()); + } + return SDL_AudioStream2js(js, stream); +} + +JSValue js_create_audio_stream(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioSpec src_spec = js2SDL_AudioSpec(js, argv[0]); + SDL_AudioSpec dst_spec = js2SDL_AudioSpec(js, argv[1]); + SDL_AudioStream *stream = SDL_CreateAudioStream(&src_spec, &dst_spec); + if (!stream) { + return JS_ThrowInternalError(js, "Failed to create audio stream: %s", SDL_GetError()); + } + return SDL_AudioStream2js(js, stream); +} + +JSC_CCALL(audio_stream_put_data, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (data == -1) + return JS_EXCEPTION; + if (!data) + return JS_ThrowReferenceError(js, "invalid audio stream data"); + if (!SDL_PutAudioStreamData(stream, data, len)) + return JS_ThrowInternalError(js, "Failed to put audio stream data: %s", SDL_GetError()); +) + +JSC_CCALL(audio_stream_get_data, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + int len = js2number(js, argv[0]); + void *data = malloc(len); + int got = SDL_GetAudioStreamData(stream, data, len); + if (got < 0) { + free(data); + ret = JS_ThrowInternalError(js, "Failed to get audio stream data: %s", SDL_GetError()); + } else { + ret = js_new_blob_stoned_copy(js, data, got); + free(data); + } +) + +JSC_CCALL(audio_stream_available, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewInt32(js, SDL_GetAudioStreamAvailable(stream)); +) + +JSC_CCALL(audio_stream_queued, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewInt32(js, SDL_GetAudioStreamQueued(stream)); +) + +JSC_CCALL(audio_stream_flush, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_FlushAudioStream(stream); +) + +JSC_CCALL(audio_stream_clear, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_ClearAudioStream(stream); +) + +JSC_CCALL(audio_stream_bind, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + if (!SDL_BindAudioStream(devid, stream)) { + ret = JS_ThrowInternalError(js, "Failed to bind audio stream: %s", SDL_GetError()); + } +) + +JSC_CCALL(audio_stream_unbind, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_UnbindAudioStream(stream); +) + +JSC_CCALL(audio_stream_get_format, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioSpec src, dst; + if (!SDL_GetAudioStreamFormat(stream, &src, &dst)) { + ret = JS_NULL; + } else { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "src", SDL_AudioSpec2js(js, src)); + JS_SetPropertyStr(js, obj, "dst", SDL_AudioSpec2js(js, dst)); + ret = obj; + } +) + +JSC_CCALL(audio_stream_get_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream); + ret = SDL_AudioDeviceID2js(js, devid); +) + +JSValue js_audio_stream_get_gain(JSContext *js, JSValue self) { + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + return JS_NewFloat64(js, SDL_GetAudioStreamGain(stream)); +} + +JSValue js_audio_stream_set_gain(JSContext *js, JSValue self, JSValue val) { + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + float gain = js2number(js, val); + if (!SDL_SetAudioStreamGain(stream, gain)) { + return JS_ThrowInternalError(js, "Failed to set audio stream gain: %s", SDL_GetError()); + } + return JS_NULL; +} + +JSValue js_audio_stream_get_frequency_ratio(JSContext *js, JSValue self) { + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + return JS_NewFloat64(js, SDL_GetAudioStreamFrequencyRatio(stream)); +} + +JSValue js_audio_stream_set_frequency_ratio(JSContext *js, JSValue self, JSValue val) { + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + float ratio = js2number(js, val); + if (!SDL_SetAudioStreamFrequencyRatio(stream, ratio)) { + return JS_ThrowInternalError(js, "Failed to set audio stream frequency ratio: %s", SDL_GetError()); + } + return JS_NULL; +} + +JSC_CCALL(audio_device_pause, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_PauseAudioDevice(devid); +) + +JSC_CCALL(audio_device_resume, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_ResumeAudioDevice(devid); +) + +JSC_CCALL(audio_device_paused, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + ret = JS_NewBool(js, SDL_AudioDevicePaused(devid)); +) + +JSC_CCALL(audio_stream_device_paused, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewBool(js, SDL_AudioStreamDevicePaused(stream)); +) + +JSC_CCALL(audio_stream_pause_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_PauseAudioStreamDevice(stream); +) + +JSC_CCALL(audio_stream_resume_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_ResumeAudioStreamDevice(stream); +) + +JSC_CCALL(audio_device_close, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_CloseAudioDevice(devid); +) + +// Helper to open a stream on the default playback or recording device +// open_stream("playback") or open_stream("recording") +JSValue js_open_stream(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *type = JS_ToCString(js, argv[0]); + if (!type) return JS_EXCEPTION; + + SDL_AudioDeviceID devid; + if (strcmp(type, "playback") == 0) { + devid = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } else if (strcmp(type, "recording") == 0) { + devid = SDL_AUDIO_DEVICE_DEFAULT_RECORDING; + } else { + JS_FreeCString(js, type); + return JS_ThrowTypeError(js, "open_stream: type must be 'playback' or 'recording'"); + } + JS_FreeCString(js, type); + + // Create stream with default spec (will be set by set_format) + SDL_AudioSpec spec = {0}; + spec.format = SDL_AUDIO_F32; + spec.channels = 2; + spec.freq = 44100; + + SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(devid, &spec, NULL, NULL); + if (!stream) { + return JS_ThrowInternalError(js, "Failed to open audio stream: %s", SDL_GetError()); + } + return SDL_AudioStream2js(js, stream); +} + +JSValue js_load_wav(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *path = JS_ToCString(js, argv[0]); + SDL_AudioSpec spec; + Uint8 *data; + Uint32 len; + if (!SDL_LoadWAV(path, &spec, &data, &len)) { + JS_FreeCString(js, path); + return JS_ThrowInternalError(js, "Failed to load WAV: %s", SDL_GetError()); + } + JS_FreeCString(js, path); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "spec", SDL_AudioSpec2js(js, spec)); + JS_SetPropertyStr(js, obj, "data", js_new_blob_stoned_copy(js, data, len)); + SDL_free(data); + return obj; +} + +JSC_CCALL(convert_audio_samples, + SDL_AudioSpec src_spec = js2SDL_AudioSpec(js, argv[0]); + SDL_AudioSpec dst_spec = js2SDL_AudioSpec(js, argv[1]); + size_t src_len; + void *src_data = js_get_blob_data(js, &src_len, argv[2]); + if (src_data == -1 || !src_data) { + ret = JS_EXCEPTION; + } else { + Uint8 *dst_data = NULL; + int dst_len = 0; + if (!SDL_ConvertAudioSamples(&src_spec, src_data, (int)src_len, &dst_spec, &dst_data, &dst_len)) { + ret = JS_ThrowInternalError(js, "Failed to convert audio samples: %s", SDL_GetError()); + } else { + ret = js_new_blob_stoned_copy(js, dst_data, dst_len); + SDL_free(dst_data); + } + } +) + +JSC_CCALL(mix_audio, + SDL_AudioFormat format = js2SDL_AudioFormat(js, argv[0]); + size_t dst_len, src_len; + void *dst = js_get_blob_data(js, &dst_len, argv[1]); + if (dst == -1 || !dst) + return JS_EXCEPTION; + void *src = js_get_blob_data(js, &src_len, argv[2]); + if (src == -1 || !src) + return JS_EXCEPTION; + if (dst_len == 0) + return JS_ThrowInternalError(js, "No destination audio data provided"); + if (src_len == 0) + return JS_ThrowInternalError(js, "No source audio data provided"); + if (dst_len != src_len) + return JS_ThrowInternalError(js, "Source and destination audio data must be the same length"); + float volume = js2number(js, argv[3]); + SDL_MixAudio(dst, src, format, dst_len, volume); +) + +// Function list for SDL_AudioStream +static const JSCFunctionListEntry js_SDL_AudioStream_funcs[] = { + JS_CFUNC_DEF("put", 1, js_audio_stream_put_data), + JS_CFUNC_DEF("get", 1, js_audio_stream_get_data), + JS_CFUNC_DEF("available", 0, js_audio_stream_available), + JS_CFUNC_DEF("queued", 0, js_audio_stream_queued), + JS_CFUNC_DEF("flush", 0, js_audio_stream_flush), + JS_CFUNC_DEF("clear", 0, js_audio_stream_clear), + JS_CFUNC_DEF("bind", 1, js_audio_stream_bind), + JS_CFUNC_DEF("unbind", 0, js_audio_stream_unbind), + JS_CFUNC_DEF("get_format", 0, js_audio_stream_get_format), + JS_CFUNC_DEF("get_device", 0, js_audio_stream_get_device), + JS_CGETSET_DEF("gain", js_audio_stream_get_gain, js_audio_stream_set_gain), + JS_CGETSET_DEF("frequency_ratio", js_audio_stream_get_frequency_ratio, js_audio_stream_set_frequency_ratio), + JS_CFUNC_DEF("pause_device", 0, js_audio_stream_pause_device), + JS_CFUNC_DEF("resume_device", 0, js_audio_stream_resume_device), + JS_CFUNC_DEF("device_paused", 0, js_audio_stream_device_paused), +}; + +// Main function list +static const JSCFunctionListEntry js_sdl_audio_funcs[] = { + JS_CFUNC_DEF("get_drivers", 0, js_get_audio_drivers), + JS_CFUNC_DEF("get_current_driver", 0, js_get_current_audio_driver), + JS_CFUNC_DEF("get_playback_devices", 0, js_get_audio_playback_devices), + JS_CFUNC_DEF("get_recording_devices", 0, js_get_audio_recording_devices), + JS_CFUNC_DEF("get_device_name", 1, js_get_audio_device_name), + JS_CFUNC_DEF("is_playback_device", 1, js_is_audio_device_playback), + JS_CFUNC_DEF("is_physical_device", 1, js_is_audio_device_physical), + JS_CFUNC_DEF("get_device_format", 1, js_get_audio_device_format), + JS_CFUNC_DEF("open_device_stream", 1, js_open_audio_device_stream), + JS_CFUNC_DEF("open_stream", 1, js_open_stream), + JS_CFUNC_DEF("create_stream", 2, js_create_audio_stream), + JS_CFUNC_DEF("pause_device", 1, js_audio_device_pause), + JS_CFUNC_DEF("resume_device", 1, js_audio_device_resume), + JS_CFUNC_DEF("device_paused", 1, js_audio_device_paused), + JS_CFUNC_DEF("close_device", 1, js_audio_device_close), + JS_CFUNC_DEF("load_wav", 1, js_load_wav), + JS_CFUNC_DEF("convert_samples", 3, js_convert_audio_samples), + JS_CFUNC_DEF("mix_audio", 4, js_mix_audio), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_AUDIO); + JS_NewClassID(&js_SDL_AudioStream_id); + JS_NewClass(JS_GetRuntime(js), js_SDL_AudioStream_id, &js_SDL_AudioStream_class); + JSValue proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, proto, js_SDL_AudioStream_funcs, countof(js_SDL_AudioStream_funcs)); + JS_SetClassProto(js, js_SDL_AudioStream_id, proto); + + JSValue export = JS_NewObject(js); + JS_SetPropertyFunctionList(js, export, js_sdl_audio_funcs, countof(js_sdl_audio_funcs)); + return export; +) diff --git a/camera.c b/camera.c new file mode 100644 index 0000000..d227665 --- /dev/null +++ b/camera.c @@ -0,0 +1,195 @@ +#include "cell.h" +#include +#include "sdl.h" + +// Camera position enum to string +static const char *camera_position_to_string(SDL_CameraPosition pos) { + switch (pos) { + case SDL_CAMERA_POSITION_FRONT_FACING: return "front"; + case SDL_CAMERA_POSITION_BACK_FACING: return "back"; + default: return "unknown"; + } +} + +// SDL_Camera class +void SDL_Camera_free(JSRuntime *rt, SDL_Camera *camera) { + if (camera) SDL_CloseCamera(camera); +} + +QJSCLASS(SDL_Camera,) + +// SDL_GetNumCameraDrivers() -> number +JSC_CCALL(camera_get_num_drivers, + return JS_NewInt32(js, SDL_GetNumCameraDrivers()); +) + +// SDL_GetCameraDriver(index) -> string +JSC_CCALL(camera_get_driver, + int index; + JS_ToInt32(js, &index, argv[0]); + const char *name = SDL_GetCameraDriver(index); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetCurrentCameraDriver() -> string +JSC_CCALL(camera_get_current_driver, + const char *name = SDL_GetCurrentCameraDriver(); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetCameras() -> array of camera IDs +JSC_CCALL(camera_get_cameras, + int count = 0; + SDL_CameraID *cameras = SDL_GetCameras(&count); + if (!cameras) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, cameras[i])); + } + SDL_free(cameras); + return arr; +) + +// SDL_GetCameraSupportedFormats(id) -> array of format objects +JSC_CCALL(camera_get_supported_formats, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + + int count = 0; + SDL_CameraSpec **specs = SDL_GetCameraSupportedFormats(id, &count); + if (!specs) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + SDL_CameraSpec *spec = specs[i]; + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "format", SDL_PixelFormat2js(js, spec->format)); + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, spec->width)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, spec->height)); + JS_SetPropertyStr(js, obj, "framerate_numerator", JS_NewInt32(js, spec->framerate_numerator)); + JS_SetPropertyStr(js, obj, "framerate_denominator", JS_NewInt32(js, spec->framerate_denominator)); + JS_SetPropertyUint32(js, arr, i, obj); + } + SDL_free(specs); + return arr; +) + +// SDL_GetCameraName(id) -> string +JSC_CCALL(camera_get_name, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetCameraName(id); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetCameraPosition(id) -> string +JSC_CCALL(camera_get_position, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_CameraPosition pos = SDL_GetCameraPosition(id); + return JS_NewString(js, camera_position_to_string(pos)); +) + +// SDL_OpenCamera(id, spec) -> Camera object +JSC_CCALL(camera_open, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + + SDL_CameraSpec spec_val, *spec = NULL; + if (argc > 1 && !JS_IsNull(argv[1])) { + JSValue format_val = JS_GetPropertyStr(js, argv[1], "format"); + spec_val.format = js2SDL_PixelFormat(js, format_val); + JS_FreeValue(js, format_val); + + JS_GETATOM(js, spec_val.width, argv[1], width, number); + JS_GETATOM(js, spec_val.height, argv[1], height, number); + JS_GETATOM(js, spec_val.framerate_numerator, argv[1], framerate_numerator, number); + JS_GETATOM(js, spec_val.framerate_denominator, argv[1], framerate_denominator, number); + spec = &spec_val; + } + + SDL_Camera *camera = SDL_OpenCamera(id, spec); + if (!camera) return JS_NULL; + return SDL_Camera2js(js, camera); +) + +// Camera instance methods +JSC_CCALL(camera_get_permission_state, + SDL_Camera *camera = js2SDL_Camera(js, self); + return JS_NewInt32(js, SDL_GetCameraPermissionState(camera)); +) + +JSC_CCALL(camera_get_id, + SDL_Camera *camera = js2SDL_Camera(js, self); + return JS_NewUint32(js, SDL_GetCameraID(camera)); +) + +JSC_CCALL(camera_get_format, + SDL_Camera *camera = js2SDL_Camera(js, self); + SDL_CameraSpec spec; + if (!SDL_GetCameraFormat(camera, &spec)) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "format", SDL_PixelFormat2js(js, spec.format)); + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, spec.width)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, spec.height)); + JS_SetPropertyStr(js, obj, "framerate_numerator", JS_NewInt32(js, spec.framerate_numerator)); + JS_SetPropertyStr(js, obj, "framerate_denominator", JS_NewInt32(js, spec.framerate_denominator)); + return obj; +) + +JSC_CCALL(camera_acquire_frame, + SDL_Camera *camera = js2SDL_Camera(js, self); + Uint64 timestamp; + SDL_Surface *surface = SDL_AcquireCameraFrame(camera, ×tamp); + if (!surface) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "surface", SDL_Surface2js(js, surface)); + JS_SetPropertyStr(js, obj, "timestamp", JS_NewInt64(js, timestamp)); + return obj; +) + +JSC_CCALL(camera_release_frame, + SDL_Camera *camera = js2SDL_Camera(js, self); + SDL_Surface *surface = js2SDL_Surface(js, argv[0]); + SDL_ReleaseCameraFrame(camera, surface); + return JS_NULL; +) + +JSC_CCALL(camera_close, + SDL_Camera *camera = js2SDL_Camera(js, self); + SDL_CloseCamera(camera); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Camera_funcs[] = { + MIST_FUNC_DEF(camera, get_permission_state, 0), + MIST_FUNC_DEF(camera, get_id, 0), + MIST_FUNC_DEF(camera, get_format, 0), + MIST_FUNC_DEF(camera, acquire_frame, 0), + MIST_FUNC_DEF(camera, release_frame, 1), + MIST_FUNC_DEF(camera, close, 0), +}; + +static const JSCFunctionListEntry js_camera_funcs[] = { + MIST_FUNC_DEF(camera, get_num_drivers, 0), + MIST_FUNC_DEF(camera, get_driver, 1), + MIST_FUNC_DEF(camera, get_current_driver, 0), + MIST_FUNC_DEF(camera, get_cameras, 0), + MIST_FUNC_DEF(camera, get_supported_formats, 1), + MIST_FUNC_DEF(camera, get_name, 1), + MIST_FUNC_DEF(camera, get_position, 1), + MIST_FUNC_DEF(camera, open, 2), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_CAMERA); + QJSCLASSPREP_FUNCS(SDL_Camera); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_camera_funcs, countof(js_camera_funcs)); + + return ret; +) diff --git a/cell.toml b/cell.toml new file mode 100644 index 0000000..bdf5a84 --- /dev/null +++ b/cell.toml @@ -0,0 +1,2 @@ +[compilation] +LDFLAGS = "-lSDL3" diff --git a/clipboard.c b/clipboard.c new file mode 100644 index 0000000..fa6b130 --- /dev/null +++ b/clipboard.c @@ -0,0 +1,90 @@ +#include "cell.h" +#include + +// SDL_SetClipboardText(text) -> bool +JSC_CCALL(clipboard_set_text, + const char *text = JS_ToCString(js, argv[0]); + if (!text) return JS_EXCEPTION; + bool result = SDL_SetClipboardText(text); + JS_FreeCString(js, text); + return JS_NewBool(js, result); +) + +// SDL_GetClipboardText() -> string +JSC_CCALL(clipboard_get_text, + char *text = SDL_GetClipboardText(); + if (!text) return JS_NewString(js, ""); + JSValue result = JS_NewString(js, text); + SDL_free(text); + return result; +) + +// SDL_HasClipboardText() -> bool +JSC_CCALL(clipboard_has_text, + return JS_NewBool(js, SDL_HasClipboardText()); +) + +// SDL_SetPrimarySelectionText(text) -> bool +JSC_CCALL(clipboard_set_primary_text, + const char *text = JS_ToCString(js, argv[0]); + if (!text) return JS_EXCEPTION; + bool result = SDL_SetPrimarySelectionText(text); + JS_FreeCString(js, text); + return JS_NewBool(js, result); +) + +// SDL_GetPrimarySelectionText() -> string +JSC_CCALL(clipboard_get_primary_text, + char *text = SDL_GetPrimarySelectionText(); + if (!text) return JS_NewString(js, ""); + JSValue result = JS_NewString(js, text); + SDL_free(text); + return result; +) + +// SDL_HasPrimarySelectionText() -> bool +JSC_CCALL(clipboard_has_primary_text, + return JS_NewBool(js, SDL_HasPrimarySelectionText()); +) + +// SDL_ClearClipboardData() -> bool +JSC_CCALL(clipboard_clear, + return JS_NewBool(js, SDL_ClearClipboardData()); +) + +// SDL_HasClipboardData(mime_type) -> bool +JSC_CCALL(clipboard_has_data, + const char *mime_type = JS_ToCString(js, argv[0]); + if (!mime_type) return JS_EXCEPTION; + bool result = SDL_HasClipboardData(mime_type); + JS_FreeCString(js, mime_type); + return JS_NewBool(js, result); +) + +// SDL_GetClipboardMimeTypes() -> array of strings +JSC_CCALL(clipboard_get_mime_types, + size_t count = 0; + char **types = SDL_GetClipboardMimeTypes(&count); + if (!types) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (size_t i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewString(js, types[i])); + } + SDL_free(types); + return arr; +) + +static const JSCFunctionListEntry js_clipboard_funcs[] = { + MIST_FUNC_DEF(clipboard, set_text, 1), + MIST_FUNC_DEF(clipboard, get_text, 0), + MIST_FUNC_DEF(clipboard, has_text, 0), + MIST_FUNC_DEF(clipboard, set_primary_text, 1), + MIST_FUNC_DEF(clipboard, get_primary_text, 0), + MIST_FUNC_DEF(clipboard, has_primary_text, 0), + MIST_FUNC_DEF(clipboard, clear, 0), + MIST_FUNC_DEF(clipboard, has_data, 1), + MIST_FUNC_DEF(clipboard, get_mime_types, 0), +}; + +CELL_USE_FUNCS(js_clipboard_funcs) diff --git a/events.c b/events.c new file mode 100644 index 0000000..1ab6377 --- /dev/null +++ b/events.c @@ -0,0 +1,547 @@ +#include "cell.h" +#include + +// Event type to string conversion +static const char *event_type_to_string(Uint32 type) { + switch (type) { + // Application events + case SDL_EVENT_QUIT: return "quit"; + case SDL_EVENT_TERMINATING: return "terminating"; + case SDL_EVENT_LOW_MEMORY: return "low_memory"; + case SDL_EVENT_WILL_ENTER_BACKGROUND: return "will_enter_background"; + case SDL_EVENT_DID_ENTER_BACKGROUND: return "did_enter_background"; + case SDL_EVENT_WILL_ENTER_FOREGROUND: return "will_enter_foreground"; + case SDL_EVENT_DID_ENTER_FOREGROUND: return "did_enter_foreground"; + case SDL_EVENT_LOCALE_CHANGED: return "locale_changed"; + case SDL_EVENT_SYSTEM_THEME_CHANGED: return "system_theme_changed"; + + // Display events + case SDL_EVENT_DISPLAY_ORIENTATION: return "display_orientation"; + case SDL_EVENT_DISPLAY_ADDED: return "display_added"; + case SDL_EVENT_DISPLAY_REMOVED: return "display_removed"; + case SDL_EVENT_DISPLAY_MOVED: return "display_moved"; + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: return "display_content_scale_changed"; + + // Window events + case SDL_EVENT_WINDOW_SHOWN: return "window_shown"; + case SDL_EVENT_WINDOW_HIDDEN: return "window_hidden"; + case SDL_EVENT_WINDOW_EXPOSED: return "window_exposed"; + case SDL_EVENT_WINDOW_MOVED: return "window_moved"; + case SDL_EVENT_WINDOW_RESIZED: return "window_resized"; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: return "window_pixel_size_changed"; + case SDL_EVENT_WINDOW_MINIMIZED: return "window_minimized"; + case SDL_EVENT_WINDOW_MAXIMIZED: return "window_maximized"; + case SDL_EVENT_WINDOW_RESTORED: return "window_restored"; + case SDL_EVENT_WINDOW_MOUSE_ENTER: return "window_mouse_enter"; + case SDL_EVENT_WINDOW_MOUSE_LEAVE: return "window_mouse_leave"; + case SDL_EVENT_WINDOW_FOCUS_GAINED: return "window_focus_gained"; + case SDL_EVENT_WINDOW_FOCUS_LOST: return "window_focus_lost"; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: return "window_close_requested"; + case SDL_EVENT_WINDOW_HIT_TEST: return "window_hit_test"; + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: return "window_iccprof_changed"; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: return "window_display_changed"; + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: return "window_display_scale_changed"; + case SDL_EVENT_WINDOW_OCCLUDED: return "window_occluded"; + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: return "window_enter_fullscreen"; + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: return "window_leave_fullscreen"; + case SDL_EVENT_WINDOW_DESTROYED: return "window_destroyed"; + + // Keyboard events + case SDL_EVENT_KEY_DOWN: return "key_down"; + case SDL_EVENT_KEY_UP: return "key_up"; + case SDL_EVENT_TEXT_EDITING: return "text_editing"; + case SDL_EVENT_TEXT_INPUT: return "text_input"; + case SDL_EVENT_KEYMAP_CHANGED: return "keymap_changed"; + case SDL_EVENT_KEYBOARD_ADDED: return "keyboard_added"; + case SDL_EVENT_KEYBOARD_REMOVED: return "keyboard_removed"; + + // Mouse events + case SDL_EVENT_MOUSE_MOTION: return "mouse_motion"; + case SDL_EVENT_MOUSE_BUTTON_DOWN: return "mouse_button_down"; + case SDL_EVENT_MOUSE_BUTTON_UP: return "mouse_button_up"; + case SDL_EVENT_MOUSE_WHEEL: return "mouse_wheel"; + case SDL_EVENT_MOUSE_ADDED: return "mouse_added"; + case SDL_EVENT_MOUSE_REMOVED: return "mouse_removed"; + + // Joystick events + case SDL_EVENT_JOYSTICK_AXIS_MOTION: return "joystick_axis_motion"; + case SDL_EVENT_JOYSTICK_BALL_MOTION: return "joystick_ball_motion"; + case SDL_EVENT_JOYSTICK_HAT_MOTION: return "joystick_hat_motion"; + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: return "joystick_button_down"; + case SDL_EVENT_JOYSTICK_BUTTON_UP: return "joystick_button_up"; + case SDL_EVENT_JOYSTICK_ADDED: return "joystick_added"; + case SDL_EVENT_JOYSTICK_REMOVED: return "joystick_removed"; + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: return "joystick_battery_updated"; + case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: return "joystick_update_complete"; + + // Gamepad events + case SDL_EVENT_GAMEPAD_AXIS_MOTION: return "gamepad_axis_motion"; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: return "gamepad_button_down"; + case SDL_EVENT_GAMEPAD_BUTTON_UP: return "gamepad_button_up"; + case SDL_EVENT_GAMEPAD_ADDED: return "gamepad_added"; + case SDL_EVENT_GAMEPAD_REMOVED: return "gamepad_removed"; + case SDL_EVENT_GAMEPAD_REMAPPED: return "gamepad_remapped"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: return "gamepad_touchpad_down"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: return "gamepad_touchpad_motion"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: return "gamepad_touchpad_up"; + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: return "gamepad_sensor_update"; + case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: return "gamepad_update_complete"; + case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: return "gamepad_steam_handle_updated"; + + // Touch events + case SDL_EVENT_FINGER_DOWN: return "finger_down"; + case SDL_EVENT_FINGER_UP: return "finger_up"; + case SDL_EVENT_FINGER_MOTION: return "finger_motion"; + + // Clipboard events + case SDL_EVENT_CLIPBOARD_UPDATE: return "clipboard_update"; + + // Drag and drop events + case SDL_EVENT_DROP_FILE: return "drop_file"; + case SDL_EVENT_DROP_TEXT: return "drop_text"; + case SDL_EVENT_DROP_BEGIN: return "drop_begin"; + case SDL_EVENT_DROP_COMPLETE: return "drop_complete"; + case SDL_EVENT_DROP_POSITION: return "drop_position"; + + // Audio events + case SDL_EVENT_AUDIO_DEVICE_ADDED: return "audio_device_added"; + case SDL_EVENT_AUDIO_DEVICE_REMOVED: return "audio_device_removed"; + case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: return "audio_device_format_changed"; + + // Sensor events + case SDL_EVENT_SENSOR_UPDATE: return "sensor_update"; + + // Pen events + case SDL_EVENT_PEN_PROXIMITY_IN: return "pen_proximity_in"; + case SDL_EVENT_PEN_PROXIMITY_OUT: return "pen_proximity_out"; + case SDL_EVENT_PEN_DOWN: return "pen_down"; + case SDL_EVENT_PEN_UP: return "pen_up"; + case SDL_EVENT_PEN_BUTTON_DOWN: return "pen_button_down"; + case SDL_EVENT_PEN_BUTTON_UP: return "pen_button_up"; + case SDL_EVENT_PEN_MOTION: return "pen_motion"; + case SDL_EVENT_PEN_AXIS: return "pen_axis"; + + // Camera events + case SDL_EVENT_CAMERA_DEVICE_ADDED: return "camera_device_added"; + case SDL_EVENT_CAMERA_DEVICE_REMOVED: return "camera_device_removed"; + case SDL_EVENT_CAMERA_DEVICE_APPROVED: return "camera_device_approved"; + case SDL_EVENT_CAMERA_DEVICE_DENIED: return "camera_device_denied"; + + // Render events + case SDL_EVENT_RENDER_TARGETS_RESET: return "render_targets_reset"; + case SDL_EVENT_RENDER_DEVICE_RESET: return "render_device_reset"; + case SDL_EVENT_RENDER_DEVICE_LOST: return "render_device_lost"; + + default: return "unknown"; + } +} + +// Mouse button to string +static const char *mouse_button_to_string(Uint8 button) { + switch (button) { + case SDL_BUTTON_LEFT: return "left"; + case SDL_BUTTON_MIDDLE: return "middle"; + case SDL_BUTTON_RIGHT: return "right"; + case SDL_BUTTON_X1: return "x1"; + case SDL_BUTTON_X2: return "x2"; + default: return "unknown"; + } +} + +// Convert SDL_Event to JS object +static JSValue event_to_js(JSContext *js, SDL_Event *event) { + JSValue obj = JS_NewObject(js); + + JS_SetPropertyStr(js, obj, "type", JS_NewString(js, event_type_to_string(event->type))); + JS_SetPropertyStr(js, obj, "timestamp", JS_NewInt64(js, event->common.timestamp)); + + switch (event->type) { + // Window events + case SDL_EVENT_WINDOW_SHOWN: + case SDL_EVENT_WINDOW_HIDDEN: + case SDL_EVENT_WINDOW_EXPOSED: + case SDL_EVENT_WINDOW_MOVED: + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + case SDL_EVENT_WINDOW_MOUSE_ENTER: + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + case SDL_EVENT_WINDOW_HIT_TEST: + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + case SDL_EVENT_WINDOW_OCCLUDED: + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + case SDL_EVENT_WINDOW_DESTROYED: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->window.windowID)); + JS_SetPropertyStr(js, obj, "data1", JS_NewInt32(js, event->window.data1)); + JS_SetPropertyStr(js, obj, "data2", JS_NewInt32(js, event->window.data2)); + break; + + // Keyboard events + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->key.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->key.which)); + JS_SetPropertyStr(js, obj, "scancode", JS_NewInt32(js, event->key.scancode)); + JS_SetPropertyStr(js, obj, "key", JS_NewUint32(js, event->key.key)); + JS_SetPropertyStr(js, obj, "mod", JS_NewUint32(js, event->key.mod)); + JS_SetPropertyStr(js, obj, "repeat", JS_NewBool(js, event->key.repeat)); + JS_SetPropertyStr(js, obj, "down", JS_NewBool(js, event->key.down)); + break; + + // Text input + case SDL_EVENT_TEXT_INPUT: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->text.windowID)); + JS_SetPropertyStr(js, obj, "text", JS_NewString(js, event->text.text)); + break; + + // Text editing + case SDL_EVENT_TEXT_EDITING: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->edit.windowID)); + JS_SetPropertyStr(js, obj, "text", JS_NewString(js, event->edit.text)); + JS_SetPropertyStr(js, obj, "start", JS_NewInt32(js, event->edit.start)); + JS_SetPropertyStr(js, obj, "length", JS_NewInt32(js, event->edit.length)); + break; + + // Mouse motion + case SDL_EVENT_MOUSE_MOTION: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->motion.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->motion.which)); + JS_SetPropertyStr(js, obj, "state", JS_NewUint32(js, event->motion.state)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->motion.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->motion.y)); + JS_SetPropertyStr(js, obj, "xrel", JS_NewFloat64(js, event->motion.xrel)); + JS_SetPropertyStr(js, obj, "yrel", JS_NewFloat64(js, event->motion.yrel)); + break; + + // Mouse button + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->button.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->button.which)); + JS_SetPropertyStr(js, obj, "button", JS_NewString(js, mouse_button_to_string(event->button.button))); + JS_SetPropertyStr(js, obj, "button_num", JS_NewUint32(js, event->button.button)); + JS_SetPropertyStr(js, obj, "down", JS_NewBool(js, event->button.down)); + JS_SetPropertyStr(js, obj, "clicks", JS_NewUint32(js, event->button.clicks)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->button.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->button.y)); + break; + + // Mouse wheel + case SDL_EVENT_MOUSE_WHEEL: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->wheel.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->wheel.which)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->wheel.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->wheel.y)); + JS_SetPropertyStr(js, obj, "direction", JS_NewString(js, + event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? "flipped" : "normal")); + JS_SetPropertyStr(js, obj, "mouse_x", JS_NewFloat64(js, event->wheel.mouse_x)); + JS_SetPropertyStr(js, obj, "mouse_y", JS_NewFloat64(js, event->wheel.mouse_y)); + break; + + // Joystick axis + case SDL_EVENT_JOYSTICK_AXIS_MOTION: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->jaxis.which)); + JS_SetPropertyStr(js, obj, "axis", JS_NewUint32(js, event->jaxis.axis)); + JS_SetPropertyStr(js, obj, "value", JS_NewInt32(js, event->jaxis.value)); + break; + + // Joystick ball + case SDL_EVENT_JOYSTICK_BALL_MOTION: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->jball.which)); + JS_SetPropertyStr(js, obj, "ball", JS_NewUint32(js, event->jball.ball)); + JS_SetPropertyStr(js, obj, "xrel", JS_NewInt32(js, event->jball.xrel)); + JS_SetPropertyStr(js, obj, "yrel", JS_NewInt32(js, event->jball.yrel)); + break; + + // Joystick hat + case SDL_EVENT_JOYSTICK_HAT_MOTION: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->jhat.which)); + JS_SetPropertyStr(js, obj, "hat", JS_NewUint32(js, event->jhat.hat)); + JS_SetPropertyStr(js, obj, "value", JS_NewUint32(js, event->jhat.value)); + break; + + // Joystick button + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EVENT_JOYSTICK_BUTTON_UP: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->jbutton.which)); + JS_SetPropertyStr(js, obj, "button", JS_NewUint32(js, event->jbutton.button)); + JS_SetPropertyStr(js, obj, "down", JS_NewBool(js, event->jbutton.down)); + break; + + // Joystick device + case SDL_EVENT_JOYSTICK_ADDED: + case SDL_EVENT_JOYSTICK_REMOVED: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->jdevice.which)); + break; + + // Gamepad axis + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->gaxis.which)); + JS_SetPropertyStr(js, obj, "axis", JS_NewUint32(js, event->gaxis.axis)); + JS_SetPropertyStr(js, obj, "value", JS_NewInt32(js, event->gaxis.value)); + break; + + // Gamepad button + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->gbutton.which)); + JS_SetPropertyStr(js, obj, "button", JS_NewUint32(js, event->gbutton.button)); + JS_SetPropertyStr(js, obj, "down", JS_NewBool(js, event->gbutton.down)); + break; + + // Gamepad device + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_REMAPPED: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->gdevice.which)); + break; + + // Gamepad touchpad + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->gtouchpad.which)); + JS_SetPropertyStr(js, obj, "touchpad", JS_NewInt32(js, event->gtouchpad.touchpad)); + JS_SetPropertyStr(js, obj, "finger", JS_NewInt32(js, event->gtouchpad.finger)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->gtouchpad.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->gtouchpad.y)); + JS_SetPropertyStr(js, obj, "pressure", JS_NewFloat64(js, event->gtouchpad.pressure)); + break; + + // Gamepad sensor + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->gsensor.which)); + JS_SetPropertyStr(js, obj, "sensor", JS_NewInt32(js, event->gsensor.sensor)); + { + JSValue data = JS_NewArray(js); + for (int i = 0; i < 3; i++) { + JS_SetPropertyUint32(js, data, i, JS_NewFloat64(js, event->gsensor.data[i])); + } + JS_SetPropertyStr(js, obj, "data", data); + } + JS_SetPropertyStr(js, obj, "sensor_timestamp", JS_NewInt64(js, event->gsensor.sensor_timestamp)); + break; + + // Touch finger + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_MOTION: + JS_SetPropertyStr(js, obj, "touch_id", JS_NewInt64(js, event->tfinger.touchID)); + JS_SetPropertyStr(js, obj, "finger_id", JS_NewInt64(js, event->tfinger.fingerID)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->tfinger.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->tfinger.y)); + JS_SetPropertyStr(js, obj, "dx", JS_NewFloat64(js, event->tfinger.dx)); + JS_SetPropertyStr(js, obj, "dy", JS_NewFloat64(js, event->tfinger.dy)); + JS_SetPropertyStr(js, obj, "pressure", JS_NewFloat64(js, event->tfinger.pressure)); + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->tfinger.windowID)); + break; + + // Drop events + case SDL_EVENT_DROP_FILE: + case SDL_EVENT_DROP_TEXT: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->drop.windowID)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->drop.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->drop.y)); + if (event->drop.data) + JS_SetPropertyStr(js, obj, "data", JS_NewString(js, event->drop.data)); + break; + + case SDL_EVENT_DROP_BEGIN: + case SDL_EVENT_DROP_COMPLETE: + case SDL_EVENT_DROP_POSITION: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->drop.windowID)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->drop.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->drop.y)); + break; + + // Audio device + case SDL_EVENT_AUDIO_DEVICE_ADDED: + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->adevice.which)); + JS_SetPropertyStr(js, obj, "recording", JS_NewBool(js, event->adevice.recording)); + break; + + // Sensor update + case SDL_EVENT_SENSOR_UPDATE: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->sensor.which)); + { + JSValue data = JS_NewArray(js); + for (int i = 0; i < 6; i++) { + JS_SetPropertyUint32(js, data, i, JS_NewFloat64(js, event->sensor.data[i])); + } + JS_SetPropertyStr(js, obj, "data", data); + } + JS_SetPropertyStr(js, obj, "sensor_timestamp", JS_NewInt64(js, event->sensor.sensor_timestamp)); + break; + + // Pen events + case SDL_EVENT_PEN_PROXIMITY_IN: + case SDL_EVENT_PEN_PROXIMITY_OUT: + case SDL_EVENT_PEN_DOWN: + case SDL_EVENT_PEN_UP: + case SDL_EVENT_PEN_MOTION: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->pmotion.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->pmotion.which)); + JS_SetPropertyStr(js, obj, "pen_state", JS_NewUint32(js, event->pmotion.pen_state)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->pmotion.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->pmotion.y)); + break; + + case SDL_EVENT_PEN_BUTTON_DOWN: + case SDL_EVENT_PEN_BUTTON_UP: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->pbutton.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->pbutton.which)); + JS_SetPropertyStr(js, obj, "pen_state", JS_NewUint32(js, event->pbutton.pen_state)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->pbutton.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->pbutton.y)); + JS_SetPropertyStr(js, obj, "button", JS_NewUint32(js, event->pbutton.button)); + JS_SetPropertyStr(js, obj, "down", JS_NewBool(js, event->pbutton.down)); + break; + + case SDL_EVENT_PEN_AXIS: + JS_SetPropertyStr(js, obj, "window_id", JS_NewUint32(js, event->paxis.windowID)); + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->paxis.which)); + JS_SetPropertyStr(js, obj, "pen_state", JS_NewUint32(js, event->paxis.pen_state)); + JS_SetPropertyStr(js, obj, "x", JS_NewFloat64(js, event->paxis.x)); + JS_SetPropertyStr(js, obj, "y", JS_NewFloat64(js, event->paxis.y)); + JS_SetPropertyStr(js, obj, "axis", JS_NewUint32(js, event->paxis.axis)); + JS_SetPropertyStr(js, obj, "value", JS_NewFloat64(js, event->paxis.value)); + break; + + // Camera device + case SDL_EVENT_CAMERA_DEVICE_ADDED: + case SDL_EVENT_CAMERA_DEVICE_REMOVED: + case SDL_EVENT_CAMERA_DEVICE_APPROVED: + case SDL_EVENT_CAMERA_DEVICE_DENIED: + JS_SetPropertyStr(js, obj, "which", JS_NewUint32(js, event->cdevice.which)); + break; + + // Display events + case SDL_EVENT_DISPLAY_ORIENTATION: + case SDL_EVENT_DISPLAY_ADDED: + case SDL_EVENT_DISPLAY_REMOVED: + case SDL_EVENT_DISPLAY_MOVED: + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: + JS_SetPropertyStr(js, obj, "display_id", JS_NewUint32(js, event->display.displayID)); + JS_SetPropertyStr(js, obj, "data1", JS_NewInt32(js, event->display.data1)); + break; + + default: + break; + } + + return obj; +} + +// SDL_PumpEvents() +JSC_CCALL(events_pump, + SDL_PumpEvents(); + return JS_NULL; +) + +// SDL_PollEvent() -> event object or null +JSC_CCALL(events_poll, + SDL_Event event; + if (SDL_PollEvent(&event)) { + return event_to_js(js, &event); + } + return JS_NULL; +) + +// SDL_WaitEvent(timeout_ms) -> event object or null +JSC_CCALL(events_wait, + SDL_Event event; + int timeout = -1; + if (argc > 0) JS_ToInt32(js, &timeout, argv[0]); + + bool result; + if (timeout < 0) { + result = SDL_WaitEvent(&event); + } else { + result = SDL_WaitEventTimeout(&event, timeout); + } + + if (result) { + return event_to_js(js, &event); + } + return JS_NULL; +) + +// SDL_PeepEvents(count, action, min_type, max_type) -> array of events +JSC_CCALL(events_peep, + int count = 10; + if (argc > 0) JS_ToInt32(js, &count, argv[0]); + + SDL_Event *events = malloc(count * sizeof(SDL_Event)); + if (!events) return JS_ThrowOutOfMemory(js); + + int num = SDL_PeepEvents(events, count, SDL_PEEKEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); + if (num < 0) { + free(events); + return JS_NewArray(js); + } + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < num; i++) { + JS_SetPropertyUint32(js, arr, i, event_to_js(js, &events[i])); + } + free(events); + return arr; +) + +// SDL_HasEvent(type) -> bool +JSC_CCALL(events_has, + uint32_t type; + JS_ToUint32(js, &type, argv[0]); + return JS_NewBool(js, SDL_HasEvent(type)); +) + +// SDL_FlushEvent(type) +JSC_CCALL(events_flush, + uint32_t type; + JS_ToUint32(js, &type, argv[0]); + SDL_FlushEvent(type); + return JS_NULL; +) + +// SDL_FlushEvents(min_type, max_type) +JSC_CCALL(events_flush_range, + uint32_t min_type, max_type; + JS_ToUint32(js, &min_type, argv[0]); + JS_ToUint32(js, &max_type, argv[1]); + SDL_FlushEvents(min_type, max_type); + return JS_NULL; +) + +// Get all pending events as array +JSC_CCALL(events_get_all, + JSValue arr = JS_NewArray(js); + SDL_Event event; + int i = 0; + while (SDL_PollEvent(&event)) { + JS_SetPropertyUint32(js, arr, i++, event_to_js(js, &event)); + } + return arr; +) + +static const JSCFunctionListEntry js_events_funcs[] = { + MIST_FUNC_DEF(events, pump, 0), + MIST_FUNC_DEF(events, poll, 0), + MIST_FUNC_DEF(events, wait, 1), + MIST_FUNC_DEF(events, peep, 1), + MIST_FUNC_DEF(events, has, 1), + MIST_FUNC_DEF(events, flush, 1), + MIST_FUNC_DEF(events, flush_range, 2), + MIST_FUNC_DEF(events, get_all, 0), +}; + +CELL_USE_FUNCS(js_events_funcs) diff --git a/examples/lenna.ce b/examples/lenna.ce new file mode 100644 index 0000000..b62002d --- /dev/null +++ b/examples/lenna.ce @@ -0,0 +1,127 @@ +/* + * Lenna Image Viewer - SDL3 Example + * + * Demonstrates loading an image from cell-image and rendering it with SDL3. + * Use WASD to move the image around the window. + * + * Usage: cell run examples/lenna.ce + */ + +var io = use('fd') +var video = use('video') +var render = use('render') +var surface = use('surface') +var events = use('events') +var keyboard = use('keyboard') +var png = use('gitea.pockle.world/john/cell-image/png') +var time = use('time') + +// Create window +var win = new video.window({ + title: "Lenna - WASD to move", + width: 640, + height: 480 +}) + +// Create renderer +var renderer = new render(win) + +renderer.win = win + +// Load lenna.png using cell-image +log.console("Loading lenna.png...") +var img_blob = io.slurp("lenna.png") +if (!img_blob) { + log.console("Error: Could not load lenna.png") + $_.stop() +} + +var img = png.decode(img_blob) +if (!img) { + log.console("Error: Could not decode lenna.png") + $_.stop() +} + +log.console("Image loaded: " + text(img.width) + "x" + text(img.height)) + +// Create SDL surface from image data +var surf = new surface({ + width: img.width, + height: img.height, + format: "rgba32", + pixels: img.pixels, + pitch: img.pitch +}) + +// Create texture from surface +var tex = renderer.load_texture(surf) +log.console("Texture created: " + text(tex.width) + "x" + text(tex.height)) + +// Image position (centered initially) +var img_x = (640 - img.width) / 2 +var img_y = (480 - img.height) / 2 +var speed = 200 // pixels per second + +// Track key states +var keys = { + w: false, + a: false, + s: false, + d: false +} + +var running = true +var last_time = time.number() + +function frame() { + if (!running) { + log.console("Exiting...") + $_.stop() + return + } + + // Calculate delta time + var now = time.number() + var dt = now - last_time + last_time = now + + // Process events + var ev + while ((ev = events.poll()) != null) { + if (ev.type == "quit" || ev.type == "window_close_requested") { + running = false + return + } + if (ev.type == "key_down" || ev.type == "key_up") { + var key_name = keyboard.get_key_name(ev.key).toLowerCase() + var pressed = ev.type == "key_down" + if (key_name == "w") keys.w = pressed + if (key_name == "a") keys.a = pressed + if (key_name == "s") keys.s = pressed + if (key_name == "d") keys.d = pressed + if (key_name == "escape" && pressed) running = false + } + } + + // Update position based on keys + if (keys.w) img_y -= speed * dt + if (keys.s) img_y += speed * dt + if (keys.a) img_x -= speed * dt + if (keys.d) img_x += speed * dt + + // Clear screen (dark gray) + renderer.draw_color([0.2, 0.2, 0.2, 1]) + renderer.clear() + + // Draw texture at current position + renderer.texture(tex, { x: img_x, y: img_y, width: tex.width, height: tex.height }, null, null) + + // Present + renderer.present() + + // Schedule next frame + $_.delay(frame, 1/60) +} + +log.console("Starting render loop... Press ESC to exit, WASD to move") +frame() diff --git a/gamepad.c b/gamepad.c new file mode 100644 index 0000000..8c6236c --- /dev/null +++ b/gamepad.c @@ -0,0 +1,502 @@ +#include "cell.h" +#include + +// Gamepad type enum to string +static const char *gamepad_type_to_string(SDL_GamepadType type) { + switch (type) { + case SDL_GAMEPAD_TYPE_STANDARD: return "standard"; + case SDL_GAMEPAD_TYPE_XBOX360: return "xbox360"; + case SDL_GAMEPAD_TYPE_XBOXONE: return "xboxone"; + case SDL_GAMEPAD_TYPE_PS3: return "ps3"; + case SDL_GAMEPAD_TYPE_PS4: return "ps4"; + case SDL_GAMEPAD_TYPE_PS5: return "ps5"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: return "switch_pro"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: return "joycon_left"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: return "joycon_right"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return "joycon_pair"; + default: return "unknown"; + } +} + +// Gamepad button enum to string +static const char *gamepad_button_to_string(SDL_GamepadButton button) { + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: return "south"; + case SDL_GAMEPAD_BUTTON_EAST: return "east"; + case SDL_GAMEPAD_BUTTON_WEST: return "west"; + case SDL_GAMEPAD_BUTTON_NORTH: return "north"; + case SDL_GAMEPAD_BUTTON_BACK: return "back"; + case SDL_GAMEPAD_BUTTON_GUIDE: return "guide"; + case SDL_GAMEPAD_BUTTON_START: return "start"; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: return "left_stick"; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: return "right_stick"; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: return "left_shoulder"; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: return "right_shoulder"; + case SDL_GAMEPAD_BUTTON_DPAD_UP: return "dpad_up"; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: return "dpad_down"; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: return "dpad_left"; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: return "dpad_right"; + case SDL_GAMEPAD_BUTTON_MISC1: return "misc1"; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: return "right_paddle1"; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: return "left_paddle1"; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: return "right_paddle2"; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: return "left_paddle2"; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: return "touchpad"; + case SDL_GAMEPAD_BUTTON_MISC2: return "misc2"; + case SDL_GAMEPAD_BUTTON_MISC3: return "misc3"; + case SDL_GAMEPAD_BUTTON_MISC4: return "misc4"; + case SDL_GAMEPAD_BUTTON_MISC5: return "misc5"; + case SDL_GAMEPAD_BUTTON_MISC6: return "misc6"; + default: return "invalid"; + } +} + +static SDL_GamepadButton string_to_gamepad_button(const char *str) { + if (!str) return SDL_GAMEPAD_BUTTON_INVALID; + if (!strcmp(str, "south")) return SDL_GAMEPAD_BUTTON_SOUTH; + if (!strcmp(str, "east")) return SDL_GAMEPAD_BUTTON_EAST; + if (!strcmp(str, "west")) return SDL_GAMEPAD_BUTTON_WEST; + if (!strcmp(str, "north")) return SDL_GAMEPAD_BUTTON_NORTH; + if (!strcmp(str, "back")) return SDL_GAMEPAD_BUTTON_BACK; + if (!strcmp(str, "guide")) return SDL_GAMEPAD_BUTTON_GUIDE; + if (!strcmp(str, "start")) return SDL_GAMEPAD_BUTTON_START; + if (!strcmp(str, "left_stick")) return SDL_GAMEPAD_BUTTON_LEFT_STICK; + if (!strcmp(str, "right_stick")) return SDL_GAMEPAD_BUTTON_RIGHT_STICK; + if (!strcmp(str, "left_shoulder")) return SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + if (!strcmp(str, "right_shoulder")) return SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + if (!strcmp(str, "dpad_up")) return SDL_GAMEPAD_BUTTON_DPAD_UP; + if (!strcmp(str, "dpad_down")) return SDL_GAMEPAD_BUTTON_DPAD_DOWN; + if (!strcmp(str, "dpad_left")) return SDL_GAMEPAD_BUTTON_DPAD_LEFT; + if (!strcmp(str, "dpad_right")) return SDL_GAMEPAD_BUTTON_DPAD_RIGHT; + if (!strcmp(str, "misc1")) return SDL_GAMEPAD_BUTTON_MISC1; + if (!strcmp(str, "touchpad")) return SDL_GAMEPAD_BUTTON_TOUCHPAD; + return SDL_GAMEPAD_BUTTON_INVALID; +} + +// Gamepad axis enum to string +static const char *gamepad_axis_to_string(SDL_GamepadAxis axis) { + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: return "leftx"; + case SDL_GAMEPAD_AXIS_LEFTY: return "lefty"; + case SDL_GAMEPAD_AXIS_RIGHTX: return "rightx"; + case SDL_GAMEPAD_AXIS_RIGHTY: return "righty"; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: return "left_trigger"; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: return "right_trigger"; + default: return "invalid"; + } +} + +static SDL_GamepadAxis string_to_gamepad_axis(const char *str) { + if (!str) return SDL_GAMEPAD_AXIS_INVALID; + if (!strcmp(str, "leftx")) return SDL_GAMEPAD_AXIS_LEFTX; + if (!strcmp(str, "lefty")) return SDL_GAMEPAD_AXIS_LEFTY; + if (!strcmp(str, "rightx")) return SDL_GAMEPAD_AXIS_RIGHTX; + if (!strcmp(str, "righty")) return SDL_GAMEPAD_AXIS_RIGHTY; + if (!strcmp(str, "left_trigger")) return SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + if (!strcmp(str, "right_trigger")) return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + return SDL_GAMEPAD_AXIS_INVALID; +} + +// Button label enum to string +static const char *button_label_to_string(SDL_GamepadButtonLabel label) { + switch (label) { + case SDL_GAMEPAD_BUTTON_LABEL_A: return "a"; + case SDL_GAMEPAD_BUTTON_LABEL_B: return "b"; + case SDL_GAMEPAD_BUTTON_LABEL_X: return "x"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: return "y"; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: return "cross"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: return "circle"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: return "square"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: return "triangle"; + default: return "unknown"; + } +} + +// SDL_Gamepad class +void SDL_Gamepad_free(JSRuntime *rt, SDL_Gamepad *gamepad) { + if (gamepad) SDL_CloseGamepad(gamepad); +} + +QJSCLASS(SDL_Gamepad,) + +// SDL_AddGamepadMapping(mapping) -> int +JSC_SCALL(gamepad_add_mapping, + const char *mapping = JS_ToCString(js, argv[0]); + if (!mapping) return JS_EXCEPTION; + int result = SDL_AddGamepadMapping(mapping); + JS_FreeCString(js, mapping); + return JS_NewInt32(js, result); +) + +// SDL_AddGamepadMappingsFromFile(file) -> int +JSC_SCALL(gamepad_add_mappings_from_file, + const char *file = JS_ToCString(js, argv[0]); + if (!file) return JS_EXCEPTION; + int result = SDL_AddGamepadMappingsFromFile(file); + JS_FreeCString(js, file); + return JS_NewInt32(js, result); +) + +// SDL_GetGamepads() -> array of gamepad IDs +JSC_CCALL(gamepad_get_gamepads, + int count = 0; + SDL_JoystickID *gamepads = SDL_GetGamepads(&count); + if (!gamepads) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, gamepads[i])); + } + SDL_free(gamepads); + return arr; +) + +// SDL_IsGamepad(id) -> bool +JSC_CCALL(gamepad_is_gamepad, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewBool(js, SDL_IsGamepad(id)); +) + +// SDL_GetGamepadNameForID(id) -> string +JSC_CCALL(gamepad_get_name_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetGamepadNameForID(id); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetGamepadPathForID(id) -> string +JSC_CCALL(gamepad_get_path_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *path = SDL_GetGamepadPathForID(id); + return path ? JS_NewString(js, path) : JS_NULL; +) + +// SDL_GetGamepadPlayerIndexForID(id) -> number +JSC_CCALL(gamepad_get_player_index_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewInt32(js, SDL_GetGamepadPlayerIndexForID(id)); +) + +// SDL_GetGamepadTypeForID(id) -> string +JSC_CCALL(gamepad_get_type_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_GamepadType type = SDL_GetGamepadTypeForID(id); + return JS_NewString(js, gamepad_type_to_string(type)); +) + +// SDL_OpenGamepad(id) -> Gamepad object +JSC_CCALL(gamepad_open, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Gamepad *gamepad = SDL_OpenGamepad(id); + if (!gamepad) return JS_NULL; + return SDL_Gamepad2js(js, gamepad); +) + +// SDL_GetGamepadFromID(id) -> Gamepad object +JSC_CCALL(gamepad_get_from_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Gamepad *gamepad = SDL_GetGamepadFromID(id); + if (!gamepad) return JS_NULL; + return SDL_Gamepad2js(js, gamepad); +) + +// SDL_GetGamepadFromPlayerIndex(player_index) -> Gamepad object +JSC_CCALL(gamepad_get_from_player_index, + int player_index; + JS_ToInt32(js, &player_index, argv[0]); + SDL_Gamepad *gamepad = SDL_GetGamepadFromPlayerIndex(player_index); + if (!gamepad) return JS_NULL; + return SDL_Gamepad2js(js, gamepad); +) + +// SDL_GetGamepadButtonFromString(str) -> button enum +JSC_SCALL(gamepad_button_from_string, + SDL_GamepadButton button = SDL_GetGamepadButtonFromString(str); + return JS_NewString(js, gamepad_button_to_string(button)); +) + +// SDL_GetGamepadAxisFromString(str) -> axis enum +JSC_SCALL(gamepad_axis_from_string, + SDL_GamepadAxis axis = SDL_GetGamepadAxisFromString(str); + return JS_NewString(js, gamepad_axis_to_string(axis)); +) + +// Gamepad instance methods +JSC_CCALL(gamepad_get_name, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *name = SDL_GetGamepadName(gamepad); + return name ? JS_NewString(js, name) : JS_NULL; +) + +JSC_CCALL(gamepad_get_path, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *path = SDL_GetGamepadPath(gamepad); + return path ? JS_NewString(js, path) : JS_NULL; +) + +JSC_CCALL(gamepad_get_type, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + SDL_GamepadType type = SDL_GetGamepadType(gamepad); + return JS_NewString(js, gamepad_type_to_string(type)); +) + +JSC_CCALL(gamepad_get_player_index, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewInt32(js, SDL_GetGamepadPlayerIndex(gamepad)); +) + +JSC_CCALL(gamepad_set_player_index, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int player_index; + JS_ToInt32(js, &player_index, argv[0]); + return JS_NewBool(js, SDL_SetGamepadPlayerIndex(gamepad, player_index)); +) + +JSC_CCALL(gamepad_get_vendor, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewUint32(js, SDL_GetGamepadVendor(gamepad)); +) + +JSC_CCALL(gamepad_get_product, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewUint32(js, SDL_GetGamepadProduct(gamepad)); +) + +JSC_CCALL(gamepad_get_product_version, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewUint32(js, SDL_GetGamepadProductVersion(gamepad)); +) + +JSC_CCALL(gamepad_get_serial, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *serial = SDL_GetGamepadSerial(gamepad); + return serial ? JS_NewString(js, serial) : JS_NULL; +) + +JSC_CCALL(gamepad_get_id, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewUint32(js, SDL_GetGamepadID(gamepad)); +) + +JSC_CCALL(gamepad_connected, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewBool(js, SDL_GamepadConnected(gamepad)); +) + +JSC_CCALL(gamepad_get_axis, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *axis_str = JS_ToCString(js, argv[0]); + if (!axis_str) return JS_EXCEPTION; + SDL_GamepadAxis axis = string_to_gamepad_axis(axis_str); + JS_FreeCString(js, axis_str); + return JS_NewInt32(js, SDL_GetGamepadAxis(gamepad, axis)); +) + +JSC_CCALL(gamepad_get_button, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *button_str = JS_ToCString(js, argv[0]); + if (!button_str) return JS_EXCEPTION; + SDL_GamepadButton button = string_to_gamepad_button(button_str); + JS_FreeCString(js, button_str); + return JS_NewBool(js, SDL_GetGamepadButton(gamepad, button)); +) + +JSC_CCALL(gamepad_get_button_label, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + const char *button_str = JS_ToCString(js, argv[0]); + if (!button_str) return JS_EXCEPTION; + SDL_GamepadButton button = string_to_gamepad_button(button_str); + JS_FreeCString(js, button_str); + SDL_GamepadButtonLabel label = SDL_GetGamepadButtonLabel(gamepad, button); + return JS_NewString(js, button_label_to_string(label)); +) + +JSC_CCALL(gamepad_get_num_touchpads, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + return JS_NewInt32(js, SDL_GetNumGamepadTouchpads(gamepad)); +) + +JSC_CCALL(gamepad_get_num_touchpad_fingers, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int touchpad; + JS_ToInt32(js, &touchpad, argv[0]); + return JS_NewInt32(js, SDL_GetNumGamepadTouchpadFingers(gamepad, touchpad)); +) + +JSC_CCALL(gamepad_get_touchpad_finger, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int touchpad, finger; + JS_ToInt32(js, &touchpad, argv[0]); + JS_ToInt32(js, &finger, argv[1]); + + bool down; + float x, y, pressure; + if (!SDL_GetGamepadTouchpadFinger(gamepad, touchpad, finger, &down, &x, &y, &pressure)) + return JS_NULL; + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "down", JS_NewBool(js, down)); + JS_SetPropertyStr(js, result, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, result, "y", JS_NewFloat64(js, y)); + JS_SetPropertyStr(js, result, "pressure", JS_NewFloat64(js, pressure)); + return result; +) + +JSC_CCALL(gamepad_has_sensor, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int type; + JS_ToInt32(js, &type, argv[0]); + return JS_NewBool(js, SDL_GamepadHasSensor(gamepad, (SDL_SensorType)type)); +) + +JSC_CCALL(gamepad_set_sensor_enabled, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int type; + JS_ToInt32(js, &type, argv[0]); + bool enabled = JS_ToBool(js, argv[1]); + return JS_NewBool(js, SDL_SetGamepadSensorEnabled(gamepad, (SDL_SensorType)type, enabled)); +) + +JSC_CCALL(gamepad_sensor_enabled, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int type; + JS_ToInt32(js, &type, argv[0]); + return JS_NewBool(js, SDL_GamepadSensorEnabled(gamepad, (SDL_SensorType)type)); +) + +JSC_CCALL(gamepad_get_sensor_data, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int type; + JS_ToInt32(js, &type, argv[0]); + int num_values = 3; + if (argc > 1) JS_ToInt32(js, &num_values, argv[1]); + + float *data = malloc(num_values * sizeof(float)); + if (!data) return JS_ThrowOutOfMemory(js); + + if (!SDL_GetGamepadSensorData(gamepad, (SDL_SensorType)type, data, num_values)) { + free(data); + return JS_NULL; + } + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < num_values; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewFloat64(js, data[i])); + } + free(data); + return arr; +) + +JSC_CCALL(gamepad_rumble, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + uint32_t low_freq, high_freq, duration_ms; + JS_ToUint32(js, &low_freq, argv[0]); + JS_ToUint32(js, &high_freq, argv[1]); + JS_ToUint32(js, &duration_ms, argv[2]); + return JS_NewBool(js, SDL_RumbleGamepad(gamepad, low_freq, high_freq, duration_ms)); +) + +JSC_CCALL(gamepad_rumble_triggers, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + uint32_t left, right, duration_ms; + JS_ToUint32(js, &left, argv[0]); + JS_ToUint32(js, &right, argv[1]); + JS_ToUint32(js, &duration_ms, argv[2]); + return JS_NewBool(js, SDL_RumbleGamepadTriggers(gamepad, left, right, duration_ms)); +) + +JSC_CCALL(gamepad_set_led, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int r, g, b; + JS_ToInt32(js, &r, argv[0]); + JS_ToInt32(js, &g, argv[1]); + JS_ToInt32(js, &b, argv[2]); + return JS_NewBool(js, SDL_SetGamepadLED(gamepad, r, g, b)); +) + +JSC_CCALL(gamepad_get_power_info, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + int percent; + SDL_PowerState state = SDL_GetGamepadPowerInfo(gamepad, &percent); + + JSValue result = JS_NewObject(js); + const char *state_str; + switch (state) { + case SDL_POWERSTATE_ON_BATTERY: state_str = "on_battery"; break; + case SDL_POWERSTATE_NO_BATTERY: state_str = "no_battery"; break; + case SDL_POWERSTATE_CHARGING: state_str = "charging"; break; + case SDL_POWERSTATE_CHARGED: state_str = "charged"; break; + default: state_str = "unknown"; break; + } + JS_SetPropertyStr(js, result, "state", JS_NewString(js, state_str)); + JS_SetPropertyStr(js, result, "percent", JS_NewInt32(js, percent)); + return result; +) + +JSC_CCALL(gamepad_close, + SDL_Gamepad *gamepad = js2SDL_Gamepad(js, self); + SDL_CloseGamepad(gamepad); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Gamepad_funcs[] = { + MIST_FUNC_DEF(gamepad, get_name, 0), + MIST_FUNC_DEF(gamepad, get_path, 0), + MIST_FUNC_DEF(gamepad, get_type, 0), + MIST_FUNC_DEF(gamepad, get_player_index, 0), + MIST_FUNC_DEF(gamepad, set_player_index, 1), + MIST_FUNC_DEF(gamepad, get_vendor, 0), + MIST_FUNC_DEF(gamepad, get_product, 0), + MIST_FUNC_DEF(gamepad, get_product_version, 0), + MIST_FUNC_DEF(gamepad, get_serial, 0), + MIST_FUNC_DEF(gamepad, get_id, 0), + MIST_FUNC_DEF(gamepad, connected, 0), + MIST_FUNC_DEF(gamepad, get_axis, 1), + MIST_FUNC_DEF(gamepad, get_button, 1), + MIST_FUNC_DEF(gamepad, get_button_label, 1), + MIST_FUNC_DEF(gamepad, get_num_touchpads, 0), + MIST_FUNC_DEF(gamepad, get_num_touchpad_fingers, 1), + MIST_FUNC_DEF(gamepad, get_touchpad_finger, 2), + MIST_FUNC_DEF(gamepad, has_sensor, 1), + MIST_FUNC_DEF(gamepad, set_sensor_enabled, 2), + MIST_FUNC_DEF(gamepad, sensor_enabled, 1), + MIST_FUNC_DEF(gamepad, get_sensor_data, 2), + MIST_FUNC_DEF(gamepad, rumble, 3), + MIST_FUNC_DEF(gamepad, rumble_triggers, 3), + MIST_FUNC_DEF(gamepad, set_led, 3), + MIST_FUNC_DEF(gamepad, get_power_info, 0), + MIST_FUNC_DEF(gamepad, close, 0), +}; + +static const JSCFunctionListEntry js_gamepad_funcs[] = { + MIST_FUNC_DEF(gamepad, add_mapping, 1), + MIST_FUNC_DEF(gamepad, add_mappings_from_file, 1), + MIST_FUNC_DEF(gamepad, get_gamepads, 0), + MIST_FUNC_DEF(gamepad, is_gamepad, 1), + MIST_FUNC_DEF(gamepad, get_name_for_id, 1), + MIST_FUNC_DEF(gamepad, get_path_for_id, 1), + MIST_FUNC_DEF(gamepad, get_player_index_for_id, 1), + MIST_FUNC_DEF(gamepad, get_type_for_id, 1), + MIST_FUNC_DEF(gamepad, open, 1), + MIST_FUNC_DEF(gamepad, get_from_id, 1), + MIST_FUNC_DEF(gamepad, get_from_player_index, 1), + MIST_FUNC_DEF(gamepad, button_from_string, 1), + MIST_FUNC_DEF(gamepad, axis_from_string, 1), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_GAMEPAD); + QJSCLASSPREP_FUNCS(SDL_Gamepad); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_gamepad_funcs, countof(js_gamepad_funcs)); + + return ret; +) diff --git a/gpu.c b/gpu.c new file mode 100644 index 0000000..ac3c22e --- /dev/null +++ b/gpu.c @@ -0,0 +1,1724 @@ +#include "sdl.h" +#include "quickjs.h" + +#include +#include "cell.h" + +// Macro for GPU wrapper classes that need cleanup +#define QJSCLASSGPUWRAPPER(WRAPPERTYPE, SDLTYPE) \ +typedef struct { \ + SDL_GPUDevice *device; \ + JSValue js_device; \ + SDL_##SDLTYPE *type; \ +} WRAPPERTYPE; \ +JSClassID js_SDL_##SDLTYPE##_id; \ +static void js_SDL_##SDLTYPE##_finalizer(JSRuntime *rt, JSValue val) { \ + WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \ + JS_FreeValueRT(rt, wrapper->js_device); \ + if (wrapper && wrapper->device && wrapper->type) \ + SDL_Release##SDLTYPE(wrapper->device, wrapper->type); \ + free(wrapper); \ +} \ +static JSClassDef js_SDL_##SDLTYPE##_class = { \ + .class_name = #SDLTYPE, \ + .finalizer = js_SDL_##SDLTYPE##_finalizer, \ +}; \ +SDL_##SDLTYPE *js2SDL_##SDLTYPE(JSContext *js, JSValue val) { \ + if (JS_GetClassID(val) != js_SDL_##SDLTYPE##_id) return NULL; \ + WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \ + return wrapper ? wrapper->type : NULL; \ +} \ +JSValue SDL_##SDLTYPE##2js(JSContext *js, JSValue device, SDL_##SDLTYPE *member) { \ + WRAPPERTYPE *wrapper = malloc(sizeof(WRAPPERTYPE)); \ + wrapper->js_device = JS_DupValue(js,device); \ + wrapper->device = js2SDL_GPUDevice(js, device); \ + wrapper->type = member; \ + JSValue j = JS_NewObjectClass(js, js_SDL_##SDLTYPE##_id); \ + JS_SetOpaque(j, wrapper); \ + return j; \ +} + +// Simple string conversion helper +const char *js2cstring(JSContext *js, JSValue v) +{ + return JS_ToCString(js, v); +} + +// GPU Free functions +void SDL_GPUDevice_free(JSRuntime *rt, SDL_GPUDevice *d) +{ + SDL_DestroyGPUDevice(d); +} + +void SDL_GPUCommandBuffer_free(JSRuntime *rt, void *w) +{ +} + +void gpu_command_buffer_wrapper_free(JSRuntime *rt, void *w) +{ +} + +// Moved to jsffi.c - extern declaration + +void SDL_GPUComputePass_free(JSRuntime *rt, SDL_GPUComputePass *c) { } +void SDL_GPUCopyPass_free(JSRuntime *rt, SDL_GPUCopyPass *c) { } +void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) { } + +// GPU Class definitions +QJSCLASS(SDL_GPUDevice,) +QJSCLASSGPUWRAPPER(gpu_buffer_wrapper, GPUBuffer) +QJSCLASSGPUWRAPPER(gpu_compute_pipeline_wrapper, GPUComputePipeline) +QJSCLASSGPUWRAPPER(gpu_graphics_pipeline_wrapper, GPUGraphicsPipeline) +QJSCLASSGPUWRAPPER(gpu_sampler_wrapper, GPUSampler) +QJSCLASSGPUWRAPPER(gpu_shader_wrapper, GPUShader) +QJSCLASSGPUWRAPPER(gpu_texture_wrapper, GPUTexture) +QJSCLASSGPUWRAPPER(gpu_transfer_buffer_wrapper, GPUTransferBuffer) +QJSCLASSGPUWRAPPER(gpu_fence_wrapper, GPUFence) +QJSCLASS(SDL_GPUCommandBuffer,) +QJSCLASS(SDL_GPUComputePass,) +QJSCLASS(SDL_GPUCopyPass,) +QJSCLASS(SDL_GPURenderPass,) + +// GPU type conversion functions +SDL_GPUGraphicsPipelineTargetInfo js2SDL_GPUGraphicsPipelineTargetInfo(JSContext *js, JSValue v) +{ + SDL_GPUGraphicsPipelineTargetInfo info = {0}; + return info; +} + +SDL_GPUSampleCount js2SDL_GPUSampleCount(JSContext *js, JSValue v) +{ + int n = js2number(js,v); + switch(n) { + case 1: return SDL_GPU_SAMPLECOUNT_1; + case 2: return SDL_GPU_SAMPLECOUNT_2; + case 4: return SDL_GPU_SAMPLECOUNT_4; + case 8: return SDL_GPU_SAMPLECOUNT_8; + } + return SDL_GPU_SAMPLECOUNT_1; +} + +// X-macro enum definition system +#define ENUM_MAPPING_TABLE(ENUM) \ + static const struct { int value; const char *name; } ENUM##_mapping[] + +#define JS2ENUM(NAME) \ +int js2##NAME(JSContext *js, JSValue v) { \ + if (JS_IsNull(v)) return 0; \ + const char *str = JS_ToCString(js, v); \ + if (!str) return 0; \ + for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \ + if(!strcmp(NAME##_mapping[i].name, str)) { \ + JS_FreeCString(js, str); \ + return NAME##_mapping[i].value; \ + } \ + JS_FreeCString(js, str); \ + return 0; \ +} \ +JSValue NAME##2js(JSContext *js, int enumval) { \ + for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \ + if(NAME##_mapping[i].value == enumval) \ + return JS_NewString(js, NAME##_mapping[i].name); \ + return JS_NULL; \ +} + +// Enum conversion tables using x-macro technique +ENUM_MAPPING_TABLE(SDL_GPUSwapchainComposition) = { + {SDL_GPU_SWAPCHAINCOMPOSITION_SDR, "sdr"}, + {SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR, "linear"}, + {SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR, "hdr"}, + {SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084, "hdr10"} +}; + +JS2ENUM(SDL_GPUSwapchainComposition) + +ENUM_MAPPING_TABLE(SDL_FlipMode) = { + {SDL_FLIP_NONE, "none"}, + {SDL_FLIP_HORIZONTAL, "horizontal"}, + {SDL_FLIP_VERTICAL, "vertical"} +}; + +JS2ENUM(SDL_FlipMode) + +SDL_FColor js2SDL_FColor(JSContext *js, JSValue v) +{ + colorf color = js2color(js,v); + return (SDL_FColor){color.x, color.y, color.z, color.w}; +} + +ENUM_MAPPING_TABLE(SDL_GPUBlendFactor) = { + {SDL_GPU_BLENDFACTOR_INVALID, "invalid"}, + {SDL_GPU_BLENDFACTOR_ZERO, "zero"}, + {SDL_GPU_BLENDFACTOR_ONE, "one"}, + {SDL_GPU_BLENDFACTOR_SRC_COLOR, "src_color"}, + {SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR, "one_minus_src_color"}, + {SDL_GPU_BLENDFACTOR_DST_COLOR, "dst_color"}, + {SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR, "one_minus_dst_color"}, + {SDL_GPU_BLENDFACTOR_SRC_ALPHA, "src_alpha"}, + {SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "one_minus_src_alpha"}, + {SDL_GPU_BLENDFACTOR_DST_ALPHA, "dst_alpha"}, + {SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA, "one_minus_dst_alpha"}, + {SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, "constant_color"}, + {SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR, "one_minus_constant_color"}, + {SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE, "src_alpha_saturate"} +}; + +JS2ENUM(SDL_GPUBlendFactor) + +ENUM_MAPPING_TABLE(SDL_GPUBlendOp) = { + {SDL_GPU_BLENDOP_INVALID, "invalid"}, + {SDL_GPU_BLENDOP_ADD, "add"}, + {SDL_GPU_BLENDOP_SUBTRACT, "subtract"}, + {SDL_GPU_BLENDOP_REVERSE_SUBTRACT, "reverse_subtract"}, + {SDL_GPU_BLENDOP_MIN, "min"}, + {SDL_GPU_BLENDOP_MAX, "max"} +}; + +JS2ENUM(SDL_GPUBlendOp) + + +ENUM_MAPPING_TABLE(SDL_GPUCompareOp) = { + {SDL_GPU_COMPAREOP_INVALID, "invalid"}, + {SDL_GPU_COMPAREOP_NEVER, "never"}, + {SDL_GPU_COMPAREOP_LESS, "less"}, + {SDL_GPU_COMPAREOP_EQUAL, "equal"}, + {SDL_GPU_COMPAREOP_LESS_OR_EQUAL, "less_or_equal"}, + {SDL_GPU_COMPAREOP_GREATER, "greater"}, + {SDL_GPU_COMPAREOP_NOT_EQUAL, "not_equal"}, + {SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, "greater_or_equal"}, + {SDL_GPU_COMPAREOP_ALWAYS, "always"} +}; + +JS2ENUM(SDL_GPUCompareOp) + +ENUM_MAPPING_TABLE(SDL_GPUCullMode) = { + {SDL_GPU_CULLMODE_NONE, "none"}, + {SDL_GPU_CULLMODE_FRONT, "front"}, + {SDL_GPU_CULLMODE_BACK, "back"} +}; + +JS2ENUM(SDL_GPUCullMode) + +ENUM_MAPPING_TABLE(SDL_GPUFillMode) = { + {SDL_GPU_FILLMODE_FILL, "fill"}, + {SDL_GPU_FILLMODE_LINE, "line"} +}; + +JS2ENUM(SDL_GPUFillMode) + +ENUM_MAPPING_TABLE(SDL_GPUFilter) = { + {SDL_GPU_FILTER_NEAREST, "nearest"}, + {SDL_GPU_FILTER_LINEAR, "linear"} +}; + +JS2ENUM(SDL_GPUFilter) + +ENUM_MAPPING_TABLE(SDL_GPUFrontFace) = { + {SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE, "counter_clockwise"}, + {SDL_GPU_FRONTFACE_CLOCKWISE, "clockwise"} +}; + +JS2ENUM(SDL_GPUFrontFace) + +ENUM_MAPPING_TABLE(SDL_GPULoadOp) = { + {SDL_GPU_LOADOP_LOAD, "load"}, + {SDL_GPU_LOADOP_CLEAR, "clear"}, + {SDL_GPU_LOADOP_DONT_CARE, "dont_care"} +}; + +JS2ENUM(SDL_GPULoadOp) + +ENUM_MAPPING_TABLE(SDL_GPUPresentMode) = { + {SDL_GPU_PRESENTMODE_VSYNC, "vsync"}, + {SDL_GPU_PRESENTMODE_IMMEDIATE, "immediate"}, + {SDL_GPU_PRESENTMODE_MAILBOX, "mailbox"} +}; + +JS2ENUM(SDL_GPUPresentMode) + +ENUM_MAPPING_TABLE(SDL_GPUPrimitiveType) = { + {SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, "triangle"}, + {SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP, "trianglestrip"}, + {SDL_GPU_PRIMITIVETYPE_LINELIST, "line"}, + {SDL_GPU_PRIMITIVETYPE_LINESTRIP, "linestrip"}, + {SDL_GPU_PRIMITIVETYPE_POINTLIST, "point"} +}; + +JS2ENUM(SDL_GPUPrimitiveType) + +ENUM_MAPPING_TABLE(SDL_GPUSamplerAddressMode) = { + {SDL_GPU_SAMPLERADDRESSMODE_REPEAT, "repeat"}, + {SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT, "mirrored_repeat"}, + {SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, "clamp"} +}; + +JS2ENUM(SDL_GPUSamplerAddressMode) + +ENUM_MAPPING_TABLE(SDL_GPUSamplerMipmapMode) = { + {SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, "nearest"}, + {SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, "linear"} +}; + +JS2ENUM(SDL_GPUSamplerMipmapMode) + +ENUM_MAPPING_TABLE(SDL_GPUStencilOp) = { + {SDL_GPU_STENCILOP_INVALID, "invalid"}, + {SDL_GPU_STENCILOP_KEEP, "keep"}, + {SDL_GPU_STENCILOP_ZERO, "zero"}, + {SDL_GPU_STENCILOP_REPLACE, "replace"}, + {SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP, "increment_and_clamp"}, + {SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP, "decrement_and_clamp"}, + {SDL_GPU_STENCILOP_INVERT, "invert"}, + {SDL_GPU_STENCILOP_INCREMENT_AND_WRAP, "increment_and_wrap"}, + {SDL_GPU_STENCILOP_DECREMENT_AND_WRAP, "decrement_and_wrap"} +}; + +JS2ENUM(SDL_GPUStencilOp) + +ENUM_MAPPING_TABLE(SDL_GPUTextureType) = { + {SDL_GPU_TEXTURETYPE_2D, "2d"}, + {SDL_GPU_TEXTURETYPE_2D_ARRAY, "2d array"}, + {SDL_GPU_TEXTURETYPE_3D, "3d"}, + {SDL_GPU_TEXTURETYPE_CUBE, "cube"}, + {SDL_GPU_TEXTURETYPE_CUBE_ARRAY, "cube array"} +}; + +JS2ENUM(SDL_GPUTextureType) + +ENUM_MAPPING_TABLE(SDL_GPUStoreOp) = { + {SDL_GPU_STOREOP_STORE, "store"}, + {SDL_GPU_STOREOP_DONT_CARE, "dont_care"}, + {SDL_GPU_STOREOP_RESOLVE, "resolve"}, + {SDL_GPU_STOREOP_RESOLVE_AND_STORE, "resolve_and_store"} +}; + +JS2ENUM(SDL_GPUStoreOp) + +ENUM_MAPPING_TABLE(SDL_GPUTextureFormat) = { + {SDL_GPU_TEXTUREFORMAT_INVALID, "invalid"}, + {SDL_GPU_TEXTUREFORMAT_A8_UNORM, "a8"}, + {SDL_GPU_TEXTUREFORMAT_R8_UNORM, "r8"}, + {SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, "rg8"}, + {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, "rgba8"}, + {SDL_GPU_TEXTUREFORMAT_R16_UNORM, "r16"}, + {SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, "rg16"}, + {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, "rgba16"}, + {SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, "r10g10b10a2"}, + {SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, "b5g6r5"}, + {SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM, "b5g5r5a1"}, + {SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM, "b4g4r4a4"}, + {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, "bgra8"}, + {SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM, "bc1"}, + {SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM, "bc2"}, + {SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM, "bc3"}, + {SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM, "bc4"}, + {SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM, "bc5"}, + {SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM, "bc7"}, + {SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT, "bc6h float"}, + {SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT, "bc6h ufloat"}, + {SDL_GPU_TEXTUREFORMAT_R8_SNORM, "r8 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, "rg8 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM, "rgba8 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R16_SNORM, "r16 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, "rg16 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM, "rgba16 snorm"}, + {SDL_GPU_TEXTUREFORMAT_R16_FLOAT, "r16 float"}, + {SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, "rg16 float"}, + {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, "rgba16 float"}, + {SDL_GPU_TEXTUREFORMAT_R32_FLOAT, "r32 float"}, + {SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, "rg32 float"}, + {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, "rgba32 float"}, + {SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, "r11g11b10"}, + {SDL_GPU_TEXTUREFORMAT_R8_UINT, "r8 uint"}, + {SDL_GPU_TEXTUREFORMAT_R8G8_UINT, "rg8 uint"}, + {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT, "rgba8 uint"}, + {SDL_GPU_TEXTUREFORMAT_R16_UINT, "r16 uint"}, + {SDL_GPU_TEXTUREFORMAT_R16G16_UINT, "rg16 uint"}, + {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT, "rgba16 uint"}, + {SDL_GPU_TEXTUREFORMAT_R32_UINT, "r32 uint"}, + {SDL_GPU_TEXTUREFORMAT_R32G32_UINT, "rg32 uint"}, + {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT, "rgba32 uint"}, + {SDL_GPU_TEXTUREFORMAT_R8_INT, "r8 int"}, + {SDL_GPU_TEXTUREFORMAT_R8G8_INT, "rg8 int"}, + {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, "rgba8 int"}, + {SDL_GPU_TEXTUREFORMAT_R16_INT, "r16 int"}, + {SDL_GPU_TEXTUREFORMAT_R16G16_INT, "rg16 int"}, + {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT, "rgba16 int"}, + {SDL_GPU_TEXTUREFORMAT_R32_INT, "r32 int"}, + {SDL_GPU_TEXTUREFORMAT_R32G32_INT, "rg32 int"}, + {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT, "rgba32 int"}, + {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, "rgba8 srgb"}, + {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, "b8g8r8a8 srgb"}, + {SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB, "bc1 srgb"}, + {SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB, "bc2 srgb"}, + {SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB, "bc3 srgb"}, + {SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB, "bc7 srgb"}, + {SDL_GPU_TEXTUREFORMAT_D16_UNORM, "d16"}, + {SDL_GPU_TEXTUREFORMAT_D24_UNORM, "d24"}, + {SDL_GPU_TEXTUREFORMAT_D32_FLOAT, "d32 float"}, + {SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, "d24 s8"}, + {SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT, "d32 float s8"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM, "astc 4x4"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM, "astc 5x4"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM, "astc 5x5"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM, "astc 6x5"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM, "astc 6x6"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM, "astc 8x5"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM, "astc 8x6"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM, "astc 8x8"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM, "astc 10x5"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM, "astc 10x6"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM, "astc 10x8"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM, "astc 10x10"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM, "astc 12x10"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM, "astc 12x12"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB, "astc 4x4 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB, "astc 5x4 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB, "astc 5x5 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB, "astc 6x5 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB, "astc 6x6 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB, "astc 8x5 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB, "astc 8x6 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB, "astc 8x8 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB, "astc 10x5 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB, "astc 10x6 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB, "astc 10x8 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB, "astc 10x10 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB, "astc 12x10 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB, "astc 12x12 srgb"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT, "astc 4x4 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT, "astc 5x4 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT, "astc 5x5 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT, "astc 6x5 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT, "astc 6x6 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT, "astc 8x5 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT, "astc 8x6 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT, "astc 8x8 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT, "astc 10x5 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT, "astc 10x6 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT, "astc 10x8 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT, "astc 10x10 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT, "astc 12x10 float"}, + {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT, "astc 12x12 float"} +}; + +SDL_GPUColorTargetInfo js2SDL_GPUColorTargetInfo(JSContext *js, JSValue v) +{ + SDL_GPUColorTargetInfo info = {0}; + JS_GETPROP(js, info.texture, v, texture, SDL_GPUTexture) + JS_GETPROP(js,info.mip_level,v,mip_level,number) + JS_GETPROP(js,info.layer_or_depth_plane, v, layer, number) + JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp) + JS_GETPROP(js,info.store_op,v,store,SDL_GPUStoreOp) + JS_GETPROP(js,info.resolve_mip_level,v,resolve_mip_level,number) + JS_GETPROP(js,info.resolve_layer,v,resolve_layer,number) + JS_GETPROP(js,info.clear_color,v,clear_color,SDL_FColor) + + return info; +} + +SDL_GPUTextureSamplerBinding js2SDL_GPUTextureSamplerBinding(JSContext *js, JSValue v) +{ + SDL_GPUTextureSamplerBinding b; + JS_GETPROP(js, b.texture, v, texture, SDL_GPUTexture) + JS_GETPROP(js, b.sampler, v, sampler, SDL_GPUSampler) + return b; +} + +SDL_GPUBufferBinding js2SDL_GPUBufferBinding(JSContext *js, JSValue v) +{ + SDL_GPUBufferBinding binding = {0}; + JS_GETPROP(js, binding.buffer, v, buffer, SDL_GPUBuffer) + JS_GETPROP(js, binding.offset, v, offset, number) + return binding; +} + +JS2ENUM(SDL_GPUTextureFormat) + +SDL_GPUColorTargetBlendState js2SDL_GPUColorTargetBlendState(JSContext *js, JSValue v) +{ + SDL_GPUColorTargetBlendState state = {0}; + JS_GETPROP(js,state.src_color_blendfactor,v,src_rgb,SDL_GPUBlendFactor); + JS_GETPROP(js,state.dst_color_blendfactor,v,dst_rgb,SDL_GPUBlendFactor); + JS_GETPROP(js,state.src_alpha_blendfactor,v,src_alpha,SDL_GPUBlendFactor); + JS_GETPROP(js,state.dst_alpha_blendfactor,v,dst_alpha,SDL_GPUBlendFactor); + JS_GETPROP(js,state.color_blend_op,v,op_rgb,SDL_GPUBlendOp) + JS_GETPROP(js,state.alpha_blend_op,v,op_alpha,SDL_GPUBlendOp) + JS_GETPROP(js,state.enable_blend,v,enabled,bool) + return state; +} + +ENUM_MAPPING_TABLE(SDL_GPUShaderFormat) = { + {SDL_GPU_SHADERFORMAT_PRIVATE, "private"}, + {SDL_GPU_SHADERFORMAT_SPIRV, "spv"}, + {SDL_GPU_SHADERFORMAT_DXBC, "dxbc"}, + {SDL_GPU_SHADERFORMAT_DXIL, "dxil"}, + {SDL_GPU_SHADERFORMAT_MSL, "msl"}, + {SDL_GPU_SHADERFORMAT_METALLIB, "metallib"} +}; + +JS2ENUM(SDL_GPUShaderFormat) + +ENUM_MAPPING_TABLE(SDL_GPUVertexInputRate) = { + {SDL_GPU_VERTEXINPUTRATE_VERTEX, "vertex"}, + {SDL_GPU_VERTEXINPUTRATE_INSTANCE, "instance"} +}; + +JS2ENUM(SDL_GPUVertexInputRate) + +ENUM_MAPPING_TABLE(SDL_GPUTransferBufferUsage) = { + {SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, "upload"}, + {SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD, "download"} +}; + +JS2ENUM(SDL_GPUTransferBufferUsage) + +ENUM_MAPPING_TABLE(SDL_GPUVertexElementFormat) = { + {SDL_GPU_VERTEXELEMENTFORMAT_INVALID, "invalid"}, + {SDL_GPU_VERTEXELEMENTFORMAT_INT, "int"}, + {SDL_GPU_VERTEXELEMENTFORMAT_INT2, "int2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_INT3, "int3"}, + {SDL_GPU_VERTEXELEMENTFORMAT_INT4, "int4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UINT, "uint"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UINT2, "uint2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UINT3, "uint3"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UINT4, "uint4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT, "float"}, + {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, "float2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, "float3"}, + {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, "float4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_BYTE2, "byte2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_BYTE4, "byte4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2, "ubyte2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4, "ubyte4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM, "byte2_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM, "byte4_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM, "ubyte2_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM, "ubyte4_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_SHORT2, "short2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_SHORT4, "short4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_USHORT2, "ushort2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_USHORT4, "ushort4"}, + {SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM, "short2_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM, "short4_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM, "ushort2_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM, "ushort4_norm"}, + {SDL_GPU_VERTEXELEMENTFORMAT_HALF2, "half2"}, + {SDL_GPU_VERTEXELEMENTFORMAT_HALF4, "half4"} +}; + +JS2ENUM(SDL_GPUVertexElementFormat) + +SDL_GPUColorTargetDescription js2SDL_GPUColorTargetDescription(JSContext *js, JSValue v) +{ + SDL_GPUColorTargetDescription dsc = {0}; + JS_GETPROP(js,dsc.format,v,format,SDL_GPUTextureFormat) + JS_GETPROP(js,dsc.blend_state,v,blend,SDL_GPUColorTargetBlendState) + return dsc; +} + +SDL_GPUStencilOpState js2SDL_GPUStencilOpState(JSContext *js, JSValue v) +{ + SDL_GPUStencilOpState state; + memset(&state, 0, sizeof(state)); + + JSValue compare_val = JS_GetPropertyStr(js, v, "compare"); + if(!JS_IsNull(compare_val)) state.compare_op = js2SDL_GPUCompareOp(js, compare_val); + JS_FreeValue(js, compare_val); + + JSValue fail_val = JS_GetPropertyStr(js, v, "fail"); + if(!JS_IsNull(fail_val)) state.fail_op = js2SDL_GPUStencilOp(js, fail_val); + JS_FreeValue(js, fail_val); + + JSValue depth_fail_val = JS_GetPropertyStr(js, v, "depth_fail"); + if(!JS_IsNull(depth_fail_val)) state.depth_fail_op = js2SDL_GPUStencilOp(js, depth_fail_val); + JS_FreeValue(js, depth_fail_val); + + JSValue pass_val = JS_GetPropertyStr(js, v, "pass"); + if(!JS_IsNull(pass_val)) state.pass_op = js2SDL_GPUStencilOp(js, pass_val); + JS_FreeValue(js, pass_val); + + return state; +} + +JSC_CCALL(gpu_claim_window, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); + SDL_Window *win = js2SDL_Window(js, argv[0]); + if (!SDL_ClaimWindowForGPUDevice(gpu,win)) + return JS_ThrowInternalError(js, "couldn't claim window for GPU device: %s", SDL_GetError()); +) + +JSC_CCALL(gpu_set_swapchain, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self); + SDL_Window *window = js2SDL_Window(js, argv[0]); + if (!SDL_SetGPUSwapchainParameters(gpu, window, js2SDL_GPUSwapchainComposition(js,argv[1]), js2SDL_GPUPresentMode(js,argv[2]))) + return JS_ThrowReferenceError(js, "Could not set: %s\n", SDL_GetError()); +) + +static JSValue js_gpu_graphics_pipeline_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) + return JS_ThrowTypeError(js, "graphics pipeline constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + JSValue pipe = argv[1]; + if (!JS_IsObject(pipe)) + return JS_ThrowTypeError(js, "gpu_pipeline argument must be an object"); + + SDL_GPUGraphicsPipelineCreateInfo info = {0}; + + JSValue vbd_val = JS_GetPropertyStr(js, pipe, "vertex_buffer_descriptions"); + Uint32 vbd_len = JS_ArrayLength(js,vbd_val); + SDL_GPUVertexBufferDescription vbd[vbd_len]; + for (Uint32 i = 0; i < vbd_len; i++) { + JSValue elem = JS_GetPropertyUint32(js, vbd_val, i); + if (JS_IsObject(elem)) { + JSValue slot_val = JS_GetPropertyStr(js, elem, "slot"); + JSValue pitch_val = JS_GetPropertyStr(js, elem, "pitch"); + JSValue rate_val = JS_GetPropertyStr(js, elem, "input_rate"); + JSValue step_val = JS_GetPropertyStr(js, elem, "instance_step_rate"); + + Uint32 slot = 0; + JS_ToUint32(js, &slot, slot_val); + JS_FreeValue(js, slot_val); + + Uint32 pitch = 0; + JS_ToUint32(js, &pitch, pitch_val); + JS_FreeValue(js, pitch_val); + + SDL_GPUVertexInputRate input_rate = js2SDL_GPUVertexInputRate(js, rate_val); + JS_FreeValue(js, rate_val); + + Uint32 step_rate = 0; + JS_ToUint32(js, &step_rate, step_val); + JS_FreeValue(js, step_val); + + vbd[i].slot = slot; + vbd[i].pitch = pitch; + vbd[i].input_rate = input_rate; + vbd[i].instance_step_rate = step_rate; + } + JS_FreeValue(js, elem); + } + JS_FreeValue(js, vbd_val); + + JSValue vat_val = JS_GetPropertyStr(js, pipe, "vertex_attributes"); + Uint32 vat_len = JS_ArrayLength(js,vat_val); + + SDL_GPUVertexAttribute vat[vat_len]; + for (Uint32 i = 0; i < vat_len; i++) { + JSValue elem = JS_GetPropertyUint32(js, vat_val, i); + if (JS_IsObject(elem)) { + JSValue loc_val = JS_GetPropertyStr(js, elem, "location"); + JSValue slot_val = JS_GetPropertyStr(js, elem, "buffer_slot"); + JSValue fmt_val = JS_GetPropertyStr(js, elem, "format"); + JSValue off_val = JS_GetPropertyStr(js, elem, "offset"); + + Uint32 location = 0; + JS_ToUint32(js, &location, loc_val); + JS_FreeValue(js, loc_val); + + Uint32 buffer_slot = 0; + JS_ToUint32(js, &buffer_slot, slot_val); + JS_FreeValue(js, slot_val); + + SDL_GPUVertexElementFormat format = js2SDL_GPUVertexElementFormat(js, fmt_val); + JS_FreeValue(js, fmt_val); + + Uint32 offset = 0; + JS_ToUint32(js, &offset, off_val); + JS_FreeValue(js, off_val); + + vat[i].location = location; + vat[i].buffer_slot = buffer_slot; + vat[i].format = format; + vat[i].offset = offset; + } + JS_FreeValue(js, elem); + } + JS_FreeValue(js, vat_val); + + info.vertex_input_state = (SDL_GPUVertexInputState){ + .vertex_buffer_descriptions = vbd, + .num_vertex_buffers = vbd_len, + .vertex_attributes = vat, + .num_vertex_attributes = vat_len + }; + + JS_GETPROP(js,info.vertex_shader, pipe, vertex,SDL_GPUShader) + JS_GETPROP(js, info.fragment_shader, pipe, fragment, SDL_GPUShader) + + JS_GETPROP(js, info.primitive_type, pipe, primitive, SDL_GPUPrimitiveType) + + JS_GETPROP(js, info.rasterizer_state.fill_mode, pipe, fill, SDL_GPUFillMode) + JS_GETPROP(js, info.rasterizer_state.cull_mode, pipe, cull, SDL_GPUCullMode) + JS_GETPROP(js, info.rasterizer_state.front_face, pipe, face, SDL_GPUFrontFace) + + JSValue depth_val = JS_GetPropertyStr(js, pipe, "depth"); + if (JS_IsObject(depth_val)) { + JS_GETPROP(js,info.depth_stencil_state.compare_op, depth_val, compare, SDL_GPUCompareOp) + JS_GETPROP(js,info.depth_stencil_state.enable_depth_test, depth_val, test, bool) + JS_GETPROP(js,info.depth_stencil_state.enable_depth_write, depth_val, write, bool) + JS_GETPROP(js,info.rasterizer_state.depth_bias_constant_factor, depth_val, bias, number) + JS_GETPROP(js,info.rasterizer_state.depth_bias_slope_factor, depth_val,bias_slope_scale, number) + JS_GETPROP(js,info.rasterizer_state.depth_bias_clamp, depth_val,bias_clamp_val, number) + } + JS_FreeValue(js, depth_val); + + JSValue stencil_val = JS_GetPropertyStr(js, pipe, "stencil"); + if (JS_IsObject(stencil_val)) { + JS_GETPROP(js,info.depth_stencil_state.enable_stencil_test, stencil_val, enabled, bool) + if (info.depth_stencil_state.enable_stencil_test) { + JS_GETPROP(js, info.depth_stencil_state.front_stencil_state, stencil_val, front, SDL_GPUStencilOpState) + JS_GETPROP(js, info.depth_stencil_state.back_stencil_state, stencil_val, back, SDL_GPUStencilOpState) + + JSValue compare_mask_val = JS_GetPropertyStr(js, stencil_val, "compare_mask"); + uint32_t tmp; + JS_ToUint32(js, &tmp, compare_mask_val); + info.depth_stencil_state.compare_mask = tmp; + JS_FreeValue(js, compare_mask_val); + + // Write Mask + JSValue write_mask_val = JS_GetPropertyStr(js, stencil_val, "write_mask"); + JS_ToUint32(js, &tmp, write_mask_val); + info.depth_stencil_state.write_mask = tmp; + } + } + JS_FreeValue(js, stencil_val); + + JSValue js_tar = JS_GetPropertyStr(js,pipe,"target"); + + SDL_GPUGraphicsPipelineTargetInfo target_info = {0}; + JSValue color_tars = JS_GetPropertyStr(js,js_tar,"color_targets"); + target_info.num_color_targets = JS_ArrayLength(js,color_tars); + SDL_GPUColorTargetDescription dsc[target_info.num_color_targets]; + target_info.color_target_descriptions = dsc; + + for (int i = 0; i < target_info.num_color_targets; i++) { + JSValue c = JS_GetPropertyUint32(js,color_tars,i); + dsc[i] = js2SDL_GPUColorTargetDescription(js,c); + JS_FreeValue(js,c); + } + JS_FreeValue(js,color_tars); + + JS_GETPROP(js,target_info.depth_stencil_format,js_tar,depth,SDL_GPUTextureFormat); + if (target_info.depth_stencil_format) target_info.has_depth_stencil_target = 1; + + info.target_info = target_info; + + JS_FreeValue(js, js_tar); + + // Create the pipeline + SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info); + if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline: %s", SDL_GetError()); + + return SDL_GPUGraphicsPipeline2js(js, argv[0], pipeline); +} + +// Standalone sampler constructor: new sdl_gpu.sampler(device, config) +static JSValue js_gpu_sampler_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) return JS_ThrowTypeError(js, "sampler constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + SDL_GPUSamplerCreateInfo info = {0}; + JSValue sampler = argv[1]; + + JS_GETPROP(js,info.min_filter,sampler,min_filter,SDL_GPUFilter) + JS_GETPROP(js,info.mag_filter,sampler,mag_filter,SDL_GPUFilter) + JS_GETPROP(js,info.mipmap_mode, sampler, mipmap, SDL_GPUSamplerMipmapMode) + JS_GETPROP(js,info.address_mode_u, sampler, u, SDL_GPUSamplerAddressMode) + JS_GETPROP(js,info.address_mode_v, sampler, v, SDL_GPUSamplerAddressMode) + JS_GETPROP(js,info.address_mode_w, sampler, w, SDL_GPUSamplerAddressMode) + JS_GETPROP(js,info.mip_lod_bias, sampler, mip_bias, number) + JS_GETPROP(js,info.max_anisotropy, sampler, max_anisotropy, number) + JS_GETPROP(js,info.compare_op,sampler,compare_op,SDL_GPUCompareOp) + JS_GETPROP(js,info.min_lod,sampler,min_lod,number) + JS_GETPROP(js,info.max_lod,sampler,max_lod,number) + JS_GETPROP(js, info.enable_anisotropy, sampler, anistropy, bool) + JS_GETPROP(js, info.enable_compare, sampler, compare, bool) + + // Create the sampler + SDL_GPUSampler *sdl_sampler = SDL_CreateGPUSampler(gpu, &info); + if (!sdl_sampler) return JS_ThrowInternalError(js, "Failed to create GPU sampler: %s", SDL_GetError()); + + return SDL_GPUSampler2js(js, argv[0], sdl_sampler); +} + +JSC_CCALL(gpu_driver, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); + ret = JS_NewString(js, SDL_GetGPUDeviceDriver(gpu)); +) + +// Standalone shader constructor: new sdl_gpu.shader(device, config) +static JSValue js_gpu_shader_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2 || !JS_IsObject(argv[1])) + return JS_ThrowTypeError(js, "shader constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + JSValue obj = argv[1]; + + SDL_GPUShaderCreateInfo info = {0}; + + JSValue code_val = JS_GetPropertyStr(js, obj, "code"); + size_t code_size; + void *code_data = js_get_blob_data(js, &code_size, code_val); + JS_FreeValue(js, code_val); + if (code_data == -1) + return JS_EXCEPTION; + if (!code_data) + return JS_ThrowTypeError(js, "empty code"); + + JSValue stage_val = JS_GetPropertyStr(js, obj, "stage"); + const char *stage_str = JS_ToCString(js, stage_val); + if (stage_str) { + if (!strcmp(stage_str, "vertex")) info.stage = SDL_GPU_SHADERSTAGE_VERTEX; + else if (!strcmp(stage_str, "fragment")) info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; + JS_FreeCString(js, stage_str); + } // TODO: Make this its own function + JS_FreeValue(js, stage_val); + + // num_samplers + JS_GETPROP(js, info.num_samplers, obj, num_samplers, number) + JS_GETPROP(js, info.num_storage_textures, obj, num_textures, number) + JS_GETPROP(js, info.num_storage_buffers, obj, num_storage_buffers, number) + JS_GETPROP(js, info.num_uniform_buffers, obj, num_uniform_buffers, number) + JS_GETPROP(js, info.format, obj, format, SDL_GPUShaderFormat) + + JSValue entry_val = JS_GetPropertyStr(js,obj,"entrypoint"); + info.entrypoint = JS_ToCString(js,entry_val); + JS_FreeValue(js,entry_val); + + info.code_size = code_size; + info.code = code_data; + info.props = 0; // No extension properties by default + + SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &info); + + JS_FreeCString(js,info.entrypoint); + if (!shader) + return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError()); + + return SDL_GPUShader2js(js, argv[0], shader); +} + +JSC_CCALL(gpu_acquire_cmd_buffer, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self); + SDL_GPUCommandBuffer *cb = SDL_AcquireGPUCommandBuffer(gpu); + if (!cb) return JS_ThrowReferenceError(js,"Unable to acquire command buffer: %s", SDL_GetError()); + return SDL_GPUCommandBuffer2js(js, cb); +) + +JSC_CCALL(gpu_wait_for_fences, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); + int n = JS_ArrayLength(js,argv[0]); + SDL_GPUFence *fences[n]; + for (int i = 0; i < n; i++) { + JSValue a = JS_GetPropertyUint32(js,argv[0],i); + fences[i] = js2SDL_GPUFence(js,a); + JS_FreeValue(js,a); + } + + int wait_all = JS_ToBool(js,argv[1]); + return JS_NewBool(js,SDL_WaitForGPUFences(gpu,wait_all,fences,n)); +) + +JSC_CCALL(gpu_query_fence, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); + SDL_GPUFence *fence = js2SDL_GPUFence(js,argv[0]); + return JS_NewBool(js,SDL_QueryGPUFence(gpu,fence)); +) + +JSC_CCALL(gpu_shader_format, + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); + SDL_GPUShaderFormat fmt = SDL_GetGPUShaderFormats(gpu); + if (!fmt) return JS_ThrowReferenceError(js, "Shader format available invalid."); + + JSValue arr = JS_NewArray(js); + int i = 0; + if (fmt & SDL_GPU_SHADERFORMAT_PRIVATE) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, ".private")); + if (fmt & SDL_GPU_SHADERFORMAT_SPIRV) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "spv")); + if (fmt & SDL_GPU_SHADERFORMAT_DXBC) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxbc")); + if (fmt & SDL_GPU_SHADERFORMAT_DXIL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxil")); + if (fmt & SDL_GPU_SHADERFORMAT_MSL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "msl")); + if (fmt & SDL_GPU_SHADERFORMAT_METALLIB) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "metallib")); + return arr; +) + +// Standalone compute pipeline constructor: new sdl_gpu.compute_pipeline(device, config) +static JSValue js_gpu_compute_pipeline_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) return JS_ThrowTypeError(js, "compute pipeline constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + SDL_GPUComputePipelineCreateInfo info = {0}; + JSValue pipe = argv[1]; + JS_GETPROP(js, info.num_samplers, pipe, num_samplers, number) + JS_GETPROP(js,info.num_readonly_storage_textures,pipe,num_readonly_storage_textures,number) + JS_GETPROP(js,info.num_readonly_storage_buffers,pipe,num_readonly_storage_buffers,number) + JS_GETPROP(js,info.num_readwrite_storage_textures,pipe,num_readwrite_storage_textures,number) + JS_GETPROP(js,info.num_readwrite_storage_buffers,pipe,num_readwrite_storage_buffers,number) + JS_GETPROP(js,info.threadcount_x,pipe,threadcount_x,number) + JS_GETPROP(js,info.threadcount_y,pipe,threadcount_y,number) + JS_GETPROP(js,info.threadcount_z,pipe,threadcount_z,number) + JS_GETPROP(js,info.entrypoint,pipe,entrypoint,cstring) + + JSValue shader = JS_GetPropertyStr(js,pipe,"shader"); + info.code = js_get_blob_data(js,&info.code_size, shader); + JS_FreeValue(js,shader); + if (info.code == -1) + return JS_EXCEPTION; + if (!info.code) + return JS_ThrowTypeError(js, "compute pipeline shader must be a valid blob"); + + SDL_GPUComputePipeline *pipeline = SDL_CreateGPUComputePipeline(gpu, &info); + JS_FreeCString(js,info.entrypoint); + if (!pipeline) return JS_ThrowReferenceError(js,"Could not create compute pipeline: %s", SDL_GetError()); + return SDL_GPUComputePipeline2js(js, argv[0], pipeline); +} + +int JS_GETBOOL(JSContext *js, JSValue obj, const char *prop) +{ + JSValue v = JS_GetPropertyStr(js,obj,prop); + int ret = JS_ToBool(js,v); + JS_FreeValue(js,v); + return ret; +} + +// Standalone buffer constructor: new sdl_gpu.buffer(device, config) +static JSValue js_gpu_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) return JS_ThrowTypeError(js, "buffer constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + SDL_GPUBufferCreateInfo info = {0}; + JSValue config = argv[1]; + + // Get size - this is required + JS_GETPROP(js, info.size, config, size, number) + if (info.size == 0) return JS_ThrowTypeError(js, "Buffer size must be greater than 0"); + + // Parse usage flags + if (JS_GETBOOL(js, config, "vertex")) + info.usage |= SDL_GPU_BUFFERUSAGE_VERTEX; + if (JS_GETBOOL(js, config, "index")) + info.usage |= SDL_GPU_BUFFERUSAGE_INDEX; + if (JS_GETBOOL(js, config, "indirect")) + info.usage |= SDL_GPU_BUFFERUSAGE_INDIRECT; + if (JS_GETBOOL(js, config, "graphics_storage_read")) + info.usage |= SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; + if (JS_GETBOOL(js, config, "compute_storage_read")) + info.usage |= SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ; + if (JS_GETBOOL(js, config, "compute_storage_write")) + info.usage |= SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE; + + if (info.usage == 0) return JS_ThrowTypeError(js, "Buffer must have at least one usage flag"); + + SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(gpu, &info); + if (!buffer) return JS_ThrowReferenceError(js, "Unable to create buffer: %s", SDL_GetError()); + + return SDL_GPUBuffer2js(js, argv[0], buffer); +} + +static JSValue js_gpu_transfer_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) return JS_ThrowTypeError(js, "transfer_buffer constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + SDL_GPUTransferBufferCreateInfo info = {0}; + JSValue config = argv[1]; + + // Get size - this is required + JS_GETPROP(js, info.size, config, size, number) + if (info.size == 0) return JS_ThrowTypeError(js, "Transfer buffer size must be greater than 0"); + + JS_GETPROP(js, info.usage, config, usage, SDL_GPUTransferBufferUsage) + + SDL_GPUTransferBuffer *buffer = SDL_CreateGPUTransferBuffer(gpu, &info); + if (!buffer) return JS_ThrowReferenceError(js, "Unable to create transfer buffer: %s", SDL_GetError()); + + return SDL_GPUTransferBuffer2js(js, argv[0], buffer); +} + +// Standalone texture constructor: new sdl_gpu.texture(device, config) +static JSValue js_gpu_texture_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 2) return JS_ThrowTypeError(js, "texture constructor requires device and config parameters"); + + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device"); + + SDL_GPUTextureCreateInfo info = {0}; + JSValue fmt = argv[1]; + + JS_GETPROP(js, info.width, fmt, width, number) + JS_GETPROP(js, info.height, fmt, height, number) + JS_GETPROP(js, info.layer_count_or_depth, fmt, layers, number) + JS_GETPROP(js, info.num_levels, fmt, mip_levels, number) + JS_GETPROP(js, info.sample_count, fmt, samples, number) + JS_GETPROP(js, info.type, fmt, type, SDL_GPUTextureType) + JS_GETPROP(js, info.format, fmt, format, SDL_GPUTextureFormat) + + // Parse usage flags + if (JS_GETBOOL(js, fmt, "sampler")) + info.usage |= SDL_GPU_TEXTUREUSAGE_SAMPLER; + if (JS_GETBOOL(js, fmt, "color_target")) + info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; + if (JS_GETBOOL(js, fmt, "depth_target")) + info.usage |= SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; + if (JS_GETBOOL(js, fmt, "read")) + info.usage |= (SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ); + if (JS_GETBOOL(js, fmt, "write")) + info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE; + if (JS_GETBOOL(js, fmt, "readwrite")) + info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE; + + SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu, &info); + if (!tex) return JS_ThrowReferenceError(js, "Unable to create texture: %s", SDL_GetError()); + + JSValue jstex = SDL_GPUTexture2js(js, argv[0], tex); + JS_SetPropertyStr(js, jstex, "width", number2js(js, info.width)); + JS_SetPropertyStr(js, jstex, "height", number2js(js, info.height)); + JS_SetPropertyStr(js, jstex, "dim", vec22js(js, (vec2){info.width, info.height})); + return jstex; +} + +JSC_CCALL(gpu_swapchain_format, + SDL_GPUDevice *dev = js2SDL_GPUDevice(js, self); + SDL_Window *win = js2SDL_Window(js, argv[0]); + return SDL_GPUTextureFormat2js(js, SDL_GetGPUSwapchainTextureFormat(dev, win)); +) + +static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { + MIST_FUNC_DEF(gpu, claim_window, 1), + MIST_FUNC_DEF(gpu, set_swapchain, 3), + MIST_FUNC_DEF(gpu, swapchain_format, 1), + MIST_FUNC_DEF(gpu, driver, 0), + MIST_FUNC_DEF(gpu, acquire_cmd_buffer, 0), + MIST_FUNC_DEF(gpu, wait_for_fences, 2), + MIST_FUNC_DEF(gpu, query_fence, 1), + MIST_FUNC_DEF(gpu, shader_format, 0), +}; + +JSC_CCALL(renderpass_bind_pipeline, + SDL_GPURenderPass *r = js2SDL_GPURenderPass(js,self); + if (!r) return JS_ThrowInternalError(js, "Invalid render pass"); + SDL_GPUGraphicsPipeline *pipe = js2SDL_GPUGraphicsPipeline(js,argv[0]); + if (!pipe) return JS_ThrowInternalError(js, "Invalid graphics pipeline"); + SDL_BindGPUGraphicsPipeline(r,pipe); +) + +JSC_CCALL(renderpass_draw_indexed, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + if (!pass) return JS_ThrowInternalError(js, "Invalid render pass"); + SDL_DrawGPUIndexedPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4])); +) + +JSC_CCALL(renderpass_draw, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + if (!pass) return JS_ThrowInternalError(js, "Invalid render pass"); + SDL_DrawGPUPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3])); +) + +JSC_CCALL(renderpass_bind_buffers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first = js2number(js,argv[0]); + JSValue buffers = argv[1]; + int len = JS_ArrayLength(js,buffers); + SDL_GPUBufferBinding bindings[len]; + for (int i = 0; i < len; i++) { + JSValue buffer = JS_GetPropertyUint32(js,buffers,i); + bindings[i] = js2SDL_GPUBufferBinding(js, buffer); + JS_FreeValue(js,buffer); + } + + SDL_BindGPUVertexBuffers( + pass, + first, + bindings, + len); +) + +JSC_CCALL(renderpass_bind_index_buffer, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + SDL_GPUBufferBinding bind = js2SDL_GPUBufferBinding(js, argv[0]); + int elen = js2number(js,argv[1]); + SDL_BindGPUIndexBuffer(pass,&bind,elen == 16 ? SDL_GPU_INDEXELEMENTSIZE_16BIT : SDL_GPU_INDEXELEMENTSIZE_32BIT); +) + +JSC_CCALL(renderpass_end, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + if (!pass) return JS_ThrowInternalError(js, "Invalid render pass"); + SDL_EndGPURenderPass(pass); +) + +JSC_CCALL(renderpass_bind_samplers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first_slot = js2number(js,argv[1]); + JSValue arr = argv[2]; + int num = JS_ArrayLength(js,arr); + SDL_GPUTextureSamplerBinding binds[num]; + for (int i = 0; i < num; i++) { + JSValue val = JS_GetPropertyUint32(js,arr,i); + binds[i] = js2SDL_GPUTextureSamplerBinding(js, val); + JS_FreeValue(js,val); + } + int vertex = JS_ToBool(js,argv[0]); + if (vertex) + SDL_BindGPUVertexSamplers(pass, first_slot, binds, num); + else + SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num); +) + +JSC_CCALL(renderpass_bind_storage_buffers, + +) + +JSC_CCALL(renderpass_bind_storage_textures, + +) + +JSC_CCALL(renderpass_viewport, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + rect r = js2rect(js,argv[0]); + SDL_GPUViewport vp ={0}; + vp.x = r.x; + vp.y = r.y; + vp.w = r.w; + vp.h = r.h; + vp.min_depth = 0; + vp.max_depth = 1; + SDL_SetGPUViewport(pass,&vp); +) + +JSC_CCALL(renderpass_scissor, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + rect r = js2rect(js,argv[0]); + SDL_Rect rr; + rr.x = r.x; + rr.y = r.y; + rr.w = r.w; + rr.h = r.h; + SDL_SetGPUScissor(pass,&rr); +) + +JSC_CCALL(renderpass_bind_vertex_samplers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first_slot = js2number(js,argv[0]); + JSValue arr = argv[1]; + int num = JS_ArrayLength(js,arr); + SDL_GPUTextureSamplerBinding binds[num]; + for (int i = 0; i < num; i++) { + JSValue val = JS_GetPropertyUint32(js,arr,i); + binds[i] = js2SDL_GPUTextureSamplerBinding(js,val); + JS_FreeValue(js,val); + } + SDL_BindGPUVertexSamplers(pass, first_slot, binds, num); +) + +JSC_CCALL(renderpass_bind_fragment_samplers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first_slot = js2number(js,argv[0]); + JSValue arr = argv[1]; + int num = JS_ArrayLength(js,arr); + SDL_GPUTextureSamplerBinding binds[num]; + for (int i = 0; i < num; i++) { + JSValue val = JS_GetPropertyUint32(js,arr,i); + binds[i] = js2SDL_GPUTextureSamplerBinding(js,val); + JS_FreeValue(js,val); + } + SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num); +) + +JSC_CCALL(renderpass_bind_vertex_buffers, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self); + int first_slot = js2number(js,argv[0]); + JSValue arr = argv[1]; + int num = JS_ArrayLength(js,arr); + SDL_GPUBufferBinding binds[num]; + for (int i = 0; i < num; i++) { + JSValue val = JS_GetPropertyUint32(js,arr,i); + JS_GETPROP(js, binds[i].buffer, val, buffer, SDL_GPUBuffer) + JS_GETPROP(js, binds[i].offset, val, offset, number) + JS_FreeValue(js,val); + } + SDL_BindGPUVertexBuffers(pass, first_slot, binds, num); +) + +JSC_CCALL(renderpass_set_stencil_reference, + SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js, self); + Uint8 ref = js2number(js,argv[0]); + SDL_SetGPUStencilReference(pass, ref); +) + +static const JSCFunctionListEntry js_SDL_GPURenderPass_funcs[] = { + MIST_FUNC_DEF(renderpass, bind_pipeline, 1), + MIST_FUNC_DEF(renderpass, viewport, 1), + MIST_FUNC_DEF(renderpass, scissor, 1), + MIST_FUNC_DEF(renderpass, draw, 4), + MIST_FUNC_DEF(renderpass, draw_indexed, 5), + MIST_FUNC_DEF(renderpass, end, 0), + MIST_FUNC_DEF(renderpass, set_stencil_reference, 1), + MIST_FUNC_DEF(renderpass, bind_index_buffer, 1), + MIST_FUNC_DEF(renderpass, bind_buffers, 2), + MIST_FUNC_DEF(renderpass, bind_samplers, 3), + MIST_FUNC_DEF(renderpass, bind_vertex_samplers, 2), + MIST_FUNC_DEF(renderpass, bind_fragment_samplers, 2), + MIST_FUNC_DEF(renderpass, bind_vertex_buffers, 2), + MIST_FUNC_DEF(renderpass, bind_storage_buffers, 2), + MIST_FUNC_DEF(renderpass, bind_storage_textures, 2), +}; + +JSC_CCALL(cmd_render_pass, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + + if (!JS_IsObject(argv[0])) return JS_ThrowTypeError(js, "render_pass: Expected a render pass descriptor object"); + JSValue passObj = argv[0]; + + JSValue colorTargetsVal = JS_GetPropertyStr(js, passObj, "color_targets"); + if (!JS_IsArray(js, colorTargetsVal)) + return JS_ThrowTypeError(js, "render_pass: colorTargets must be an array"); + + uint32_t colorCount = JS_ArrayLength(js, colorTargetsVal); + SDL_GPUColorTargetInfo colortars[colorCount]; + SDL_GPUDepthStencilTargetInfo depthtar; + int has_depth = 0; + + // Fill colorInfos from JS array + for (uint32_t i = 0; i < colorCount; i++) { + JSValue ctargetVal = JS_GetPropertyUint32(js, colorTargetsVal, i); + colortars[i] = js2SDL_GPUColorTargetInfo(js,ctargetVal); + JS_FreeValue(js, ctargetVal); + } + + // Optional depth_stencil + JSValue depthval = JS_GetPropertyStr(js, passObj, "depth_stencil"); + if (!JS_IsNull(depthval)) { + has_depth = 1; + JS_GETPROP(js, depthtar.texture, depthval, texture, SDL_GPUTexture) + JS_GETPROP(js, depthtar.load_op, depthval, load, SDL_GPULoadOp) + JS_GETPROP(js, depthtar.store_op, depthval, store, SDL_GPUStoreOp) + JS_GETPROP(js, depthtar.stencil_load_op, depthval, stencil_load, SDL_GPULoadOp) + JS_GETPROP(js, depthtar.stencil_store_op, depthval, stencil_store, SDL_GPUStoreOp) + JS_GETPROP(js,depthtar.clear_depth, depthval, clear, number) + JS_GETPROP(js,depthtar.clear_stencil,depthval,clear_stencil,number) + } + JS_FreeValue(js, depthval); + + SDL_GPURenderPass *pass = SDL_BeginGPURenderPass( + cmds, + colortars, + colorCount, + has_depth ? &depthtar : NULL + ); + + JS_FreeValue(js, colorTargetsVal); + + if (!pass) return JS_ThrowInternalError(js, "render_pass: Failed to begin render pass: %s", SDL_GetError()); + + return SDL_GPURenderPass2js(js, pass); +) + +JSC_CCALL(cmd_copy_pass, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(cmds); + if (!pass) return JS_ThrowInternalError(js, "copy_pass: Failed to begin copy pass: %s", SDL_GetError()); + return SDL_GPUCopyPass2js(js, pass); +) + +JSC_CCALL(cmd_push_vertex_uniform_data, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + int slot; + JS_ToInt32(js, &slot, argv[0]); + size_t buf_size; + void *data = js_get_blob_data(js, &buf_size, argv[1]); + if (data == -1) + return JS_EXCEPTION; + if (!data) + return JS_ThrowTypeError(js, "uniform data must be a valid blob"); + SDL_PushGPUVertexUniformData(cmds, slot, data, buf_size); +) + +JSC_CCALL(cmd_push_fragment_uniform_data, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + int slot; + JS_ToInt32(js, &slot, argv[0]); + size_t buf_size; + void *data = js_get_blob_data(js, &buf_size, argv[1]); + if (data == -1) + return JS_EXCEPTION; + if (!data) + return JS_ThrowTypeError(js, "uniform data must be a valid blob"); + SDL_PushGPUFragmentUniformData(cmds, slot, data, buf_size); +) + +JSC_CCALL(cmd_push_compute_uniform_data, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + int slot; + JS_ToInt32(js, &slot, argv[0]); + size_t buf_size; + void *data = js_get_blob_data(js, &buf_size, argv[1]); + if (data == -1) + return JS_EXCEPTION; + if (!data) + return JS_ThrowTypeError(js, "uniform data must be a valid blob"); + SDL_PushGPUComputeUniformData(cmds, slot, data, buf_size); +) + +JSC_CCALL(cmd_submit, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self); + SDL_SubmitGPUCommandBuffer(cmds); +) + +JSC_SCALL(cmd_push_debug_group, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_PushGPUDebugGroup(cmd,str); +) + +JSC_CCALL(cmd_pop_debug_group, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_PopGPUDebugGroup(cmd); +) + +JSC_SCALL(cmd_debug_label, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_InsertGPUDebugLabel(cmd, str); +) + +SDL_GPUBlitRegion js2SDL_GPUBlitRegion(JSContext *js, JSValue v) +{ + SDL_GPUBlitRegion info = {0}; + if (JS_GetClassID(v) == js_SDL_GPUTexture_id) { + // texture path + JSValue tex = v; + info.texture = js2SDL_GPUTexture(js,tex); + info.mip_level = 0, + info.layer_or_depth_plane = 0; + info.x = 0; + info.y = 0; + JS_GETPROP(js,info.w,tex,width,number) + JS_GETPROP(js,info.h,tex,height,number) + return info; + } + JSValue tex = JS_GetPropertyStr(js, v, "texture"); + info.texture = js2SDL_GPUTexture(js,tex); + JS_GETPROP(js,info.mip_level,v,mip_level, number) + JS_GETPROP(js,info.mip_level,v,layer,number) + JS_GETPROP(js,info.x,v,x,number) + JS_GETPROP(js,info.y,v,y,number) + JS_GETPROP(js,info.w,v,width,number) + JS_GETPROP(js,info.h,v,height,number) + + if (!info.w) JS_GETPROP(js,info.w,tex,width,number) + if (!info.h) JS_GETPROP(js,info.h,tex,height,number) + + JS_FreeValue(js,tex); + return info; +} + +JSC_CCALL(cmd_blit, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_GPUBlitInfo info = {0}; + JSValue v = argv[0]; + JS_GETPROP(js,info.source,v,src,SDL_GPUBlitRegion) + JS_GETPROP(js,info.destination,v,dst,SDL_GPUBlitRegion) + JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp) + JS_GETPROP(js,info.flip_mode,v,flip,SDL_FlipMode) + JS_GETPROP(js,info.filter,v,filter,SDL_GPUFilter) + JS_GETPROP(js,info.clear_color,v,color,SDL_FColor) + SDL_BlitGPUTexture(cmd,&info); +) + +JSC_CCALL(cmd_cancel, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_CancelGPUCommandBuffer(cmd); +) + +JSC_CCALL(cmd_swapchain_pass, + SDL_GPUCommandBuffer *cmdbuf = js2SDL_GPUCommandBuffer(js, self); + SDL_Window *window = js2SDL_Window(js, argv[0]); + + SDL_GPUTexture* swapchainTexture; + if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, window, &swapchainTexture, NULL, NULL)) { + return JS_ThrowReferenceError(js, "WaitAndAcquireGPUSwapchainTexture failed: %s", SDL_GetError()); + } + + if (swapchainTexture == NULL) { + return JS_ThrowReferenceError(js, "Failed to acquire swapchain texture"); + } + + SDL_GPUColorTargetInfo colorTargetInfo = { 0 }; + colorTargetInfo.texture = swapchainTexture; + colorTargetInfo.clear_color = (SDL_FColor){ 0.0f, 0.0f, 0.0f, 1.0f }; + colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; + colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; + + SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(cmdbuf, &colorTargetInfo, 1, NULL); + if (!renderPass) { + return JS_ThrowReferenceError(js, "Failed to begin render pass: %s", SDL_GetError()); + } + + return SDL_GPURenderPass2js(js, renderPass); +) + +JSC_CCALL(cmd_compute_pass, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + JSValue textures = argv[0]; + JSValue buffers = argv[1]; + + int t_n = JS_ArrayLength(js,textures); + SDL_GPUStorageTextureReadWriteBinding t_bind[t_n]; + for (int i = 0; i < t_n; i++) { + JSValue T = JS_GetPropertyUint32(js,textures,i); + JS_GETPROP(js, t_bind[i].texture, T, texture, SDL_GPUTexture) + JS_GETPROP(js,t_bind[i].mip_level,T,mip, number) + JS_GETPROP(js,t_bind[i].layer,T,layer, number) + JS_FreeValue(js,T); + } + + int b_n = JS_ArrayLength(js,buffers); + SDL_GPUStorageBufferReadWriteBinding b_bind[b_n]; + for (int i = 0; i < b_n; i++) { + JSValue T = JS_GetPropertyUint32(js,buffers,i); + JS_GETPROP(js,b_bind[i].buffer, T,buffer,SDL_GPUBuffer) + } + + SDL_GPUComputePass *pass = SDL_BeginGPUComputePass(cmd,t_bind,t_n,b_bind,b_n); + if (!pass) return JS_ThrowReferenceError(js, "Unable to begin compute pass: %s", SDL_GetError()); + return SDL_GPUComputePass2js(js,pass); +) + +JSC_CCALL(cmd_generate_mipmaps, + SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self); + SDL_GPUTexture *tex = js2SDL_GPUTexture(js,argv[0]); + SDL_GenerateMipmapsForGPUTexture(cmd,tex); +) + +static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = { + MIST_FUNC_DEF(cmd, render_pass, 1), + MIST_FUNC_DEF(cmd, copy_pass, 0), + MIST_FUNC_DEF(cmd, compute_pass, 2), + MIST_FUNC_DEF(cmd, push_vertex_uniform_data, 2), + MIST_FUNC_DEF(cmd, push_fragment_uniform_data, 2), + MIST_FUNC_DEF(cmd, push_compute_uniform_data, 2), + MIST_FUNC_DEF(cmd, generate_mipmaps, 1), + MIST_FUNC_DEF(cmd, submit, 0), + MIST_FUNC_DEF(cmd, cancel, 0), + MIST_FUNC_DEF(cmd, push_debug_group, 1), + MIST_FUNC_DEF(cmd, pop_debug_group, 0), + MIST_FUNC_DEF(cmd, debug_label, 1), + MIST_FUNC_DEF(cmd, blit, 1), + MIST_FUNC_DEF(cmd, swapchain_pass, 1), +}; + +JSC_CCALL(copypass_end, + SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self); + SDL_EndGPUCopyPass(pass); +) + +JSC_CCALL(copypass_upload_to_buffer, + SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self); + JSValue transfer_loc = argv[0]; + JSValue buffer_region = argv[1]; + + SDL_GPUTransferBufferLocation transfer_info = {0}; + JS_GETPROP(js, transfer_info.transfer_buffer, transfer_loc, transfer_buffer, SDL_GPUTransferBuffer) + JS_GETPROP(js, transfer_info.offset, transfer_loc, offset, number) + + SDL_GPUBufferRegion buffer_info = {0}; + JS_GETPROP(js, buffer_info.buffer, buffer_region, buffer, SDL_GPUBuffer) + JS_GETPROP(js, buffer_info.offset, buffer_region, offset, number) + JS_GETPROP(js, buffer_info.size, buffer_region, size, number) + + bool cycle = argc > 2 ? JS_ToBool(js, argv[2]) : false; + + SDL_UploadToGPUBuffer(pass, &transfer_info, &buffer_info, cycle); +) + +JSC_CCALL(copypass_upload_to_texture, + SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self); + JSValue transfer_info_val = argv[0]; + JSValue texture_region_val = argv[1]; + + SDL_GPUTextureTransferInfo transfer_info = {0}; + JS_GETPROP(js, transfer_info.transfer_buffer, transfer_info_val, transfer_buffer, SDL_GPUTransferBuffer) + JS_GETPROP(js, transfer_info.offset, transfer_info_val, offset, number) + JS_GETPROP(js, transfer_info.pixels_per_row, transfer_info_val, pixels_per_row, number) + JS_GETPROP(js, transfer_info.rows_per_layer, transfer_info_val, rows_per_layer, number) + + SDL_GPUTextureRegion texture_region = {0}; + JS_GETPROP(js, texture_region.texture, texture_region_val, texture, SDL_GPUTexture) + JS_GETPROP(js, texture_region.mip_level, texture_region_val, mip_level, number) + JS_GETPROP(js, texture_region.layer, texture_region_val, layer, number) + JS_GETPROP(js, texture_region.x, texture_region_val, x, number) + JS_GETPROP(js, texture_region.y, texture_region_val, y, number) + JS_GETPROP(js, texture_region.z, texture_region_val, z, number) + JS_GETPROP(js, texture_region.w, texture_region_val, w, number) + JS_GETPROP(js, texture_region.h, texture_region_val, h, number) + JS_GETPROP(js, texture_region.d, texture_region_val, d, number) + + bool cycle = JS_ToBool(js, argv[2]); + + SDL_UploadToGPUTexture(pass, &transfer_info, &texture_region, cycle); +) + +JSC_CCALL(copypass_buffer_to_buffer, + SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self); + SDL_GPUBuffer *b1 = js2SDL_GPUBuffer(js,argv[0]); + SDL_GPUBuffer *b2 = js2SDL_GPUBuffer(js,argv[1]); + size_t size = js2number(js,argv[2]); + bool cycle = js2bool(js,argv[3]); + SDL_CopyGPUBufferToBuffer(pass,b1,b2,size,cycle); +) + +static const JSCFunctionListEntry js_SDL_GPUCopyPass_funcs[] = { + MIST_FUNC_DEF(copypass, end, 0), + MIST_FUNC_DEF(copypass, upload_to_buffer, 3), + MIST_FUNC_DEF(copypass, upload_to_texture, 3), + MIST_FUNC_DEF(copypass, buffer_to_buffer, 4), +}; + +JSC_SCALL(buffer_name, + SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js,self); + SDL_GPUDevice *gpu; + JS_GETPROP(js, gpu, self, gpu, SDL_GPUDevice) + SDL_SetGPUBufferName(gpu,buffer,str); +) + +static const JSCFunctionListEntry js_SDL_GPUBuffer_funcs[] = { + MIST_FUNC_DEF(buffer, name, 1), +}; + +JSC_SCALL(texture_name, + SDL_GPUTexture *texture = js2SDL_GPUTexture(js,self); + SDL_GPUDevice *gpu; + JS_GETPROP(js, gpu, self, gpu, SDL_GPUDevice) + SDL_SetGPUTextureName(gpu,texture,str); +) + +static const JSCFunctionListEntry js_SDL_GPUTexture_funcs[] = { + MIST_FUNC_DEF(texture, name, 1), +}; + +JSC_CCALL(transferbuffer_copy_blob, + SDL_GPUTransferBuffer *buffer = js2SDL_GPUTransferBuffer(js, self); + SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]); + JSValue blob = argv[1]; + + size_t blob_size; + void *blob_data = js_get_blob_data(js, &blob_size, blob); + if (blob_data == -1) + return JS_EXCEPTION; + if (!blob_data) + return JS_ThrowTypeError(js, "copy_blob: Expected a blob"); + + void *mapped_data = SDL_MapGPUTransferBuffer(gpu, buffer, false); + if (!mapped_data) return JS_ThrowReferenceError(js, "copy_blob: Failed to map transfer buffer: %s", SDL_GetError()); + + SDL_memcpy(mapped_data, blob_data, blob_size); + + // Unmap the transfer buffer + SDL_UnmapGPUTransferBuffer(gpu, buffer); + + return JS_UNINITIALIZED; +) + +static const JSCFunctionListEntry js_SDL_GPUTransferBuffer_funcs[] = { + MIST_FUNC_DEF(transferbuffer, copy_blob, 2), +}; + +JSC_CCALL(compute_dispatch, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + SDL_DispatchGPUCompute(pass,js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2])); +) + +JSC_CCALL(compute_end, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + SDL_EndGPUComputePass(pass); +) + +JSC_CCALL(compute_pipeline, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + SDL_GPUComputePipeline *pipeline = js2SDL_GPUComputePipeline(js,argv[0]); + SDL_BindGPUComputePipeline(pass,pipeline); +) + +JSC_CCALL(compute_samplers, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + JSValue samplers = argv[0]; + int n = JS_ArrayLength(js,samplers); + SDL_GPUTextureSamplerBinding b[n]; + for (int i = 0; i < n; i++) { + JSValue s = JS_GetPropertyUint32(js,samplers,i); + b[i] = js2SDL_GPUTextureSamplerBinding(js,s); + JS_FreeValue(js,s); + } + SDL_BindGPUComputeSamplers(pass,js2number(js,argv[1]),b,n); +) + +JSC_CCALL(compute_storage_textures, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + JSValue textures = argv[0]; + int n = JS_ArrayLength(js,textures); + SDL_GPUTexture *b[n]; + for (int i = 0; i < n; i++) { + JSValue s = JS_GetPropertyUint32(js,textures,i); + b[i] = js2SDL_GPUTexture(js,s); + JS_FreeValue(js,s); + } + SDL_BindGPUComputeStorageTextures(pass,js2number(js,argv[1]),b,n); +) + +JSC_CCALL(compute_storage_buffers, + SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); + JSValue buffers = argv[0]; + int n = JS_ArrayLength(js,buffers); + SDL_GPUBuffer *b[n]; + for (int i = 0; i < n; i++) { + JSValue s = JS_GetPropertyUint32(js,buffers,i); + b[i] = js2SDL_GPUBuffer(js,s); + JS_FreeValue(js,s); + } + SDL_BindGPUComputeStorageBuffers(pass,js2number(js,argv[1]),b,n); +) + +static const JSCFunctionListEntry js_SDL_GPUComputePass_funcs[] = { + MIST_FUNC_DEF(compute, dispatch, 3), + MIST_FUNC_DEF(compute, end, 0), + MIST_FUNC_DEF(compute, pipeline, 1), + MIST_FUNC_DEF(compute, samplers, 2), + MIST_FUNC_DEF(compute, storage_buffers, 2), + MIST_FUNC_DEF(compute, storage_textures, 2), +}; + +// Empty function arrays for classes with no methods yet + +// GPU device constructor function +static JSValue js_gpu_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) +{ + SDL_PropertiesID props = SDL_CreateProperties(); + + // Handle properties object if provided + if (argc > 0 && JS_IsObject(argv[0])) { + JSValue opts = argv[0]; + + // Helper function to check and set boolean properties + #define SET_BOOL_PROP(js_name, sdl_prop) do { \ + JSValue val = JS_GetPropertyStr(js, opts, js_name); \ + if (!JS_IsNull(val)) { \ + SDL_SetBooleanProperty(props, sdl_prop, JS_ToBool(js, val)); \ + } \ + JS_FreeValue(js, val); \ + } while(0) + + SET_BOOL_PROP("debug", SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN); + SET_BOOL_PROP("lowpower", SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN); + + // Shader format properties + SET_BOOL_PROP("shaders_private", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN); + SET_BOOL_PROP("shaders_spirv", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN); + SET_BOOL_PROP("shaders_dxbc", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN); + SET_BOOL_PROP("shaders_dxil", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN); + SET_BOOL_PROP("shaders_msl", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN); + SET_BOOL_PROP("shaders_metallib", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN); + + #undef SET_BOOL_PROP + + // Handle string properties + JSValue name_val = JS_GetPropertyStr(js, opts, "name"); + if (!JS_IsNull(name_val)) { + const char *name = JS_ToCString(js, name_val); + if (name) { + SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name); + JS_FreeCString(js, name); + } + } + JS_FreeValue(js, name_val); + + // D3D12 semantic name + JSValue semantic_val = JS_GetPropertyStr(js, opts, "d3d12_semantic_name"); + if (!JS_IsNull(semantic_val)) { + const char *semantic = JS_ToCString(js, semantic_val); + if (semantic) { + SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, semantic); + JS_FreeCString(js, semantic); + } + } + JS_FreeValue(js, semantic_val); + } + + // Create GPU device with properties + SDL_GPUDevice *device = SDL_CreateGPUDeviceWithProperties(props); + SDL_DestroyProperties(props); + + if (!device) { + return JS_ThrowReferenceError(js, "Failed to create GPU device: %s", SDL_GetError()); + } + + // Global device storage removed - device now passed as parameter + + // Create the GPU device JS object + JSValue device_obj = SDL_GPUDevice2js(js, device); + + return device_obj; +} + +// Function to register module +CELL_USE_INIT( + JSValue ret = JS_NewObject(js); + + // Initialize classes + QJSCLASSPREP_FUNCS(SDL_GPUDevice) + QJSCLASSPREP_FUNCS(SDL_GPUBuffer) + QJSCLASSPREP_FUNCS(SDL_GPUTexture) + QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer) + QJSCLASSPREP_FUNCS(SDL_GPUCommandBuffer) + QJSCLASSPREP_FUNCS(SDL_GPUComputePass) + QJSCLASSPREP_FUNCS(SDL_GPUCopyPass) + QJSCLASSPREP_FUNCS(SDL_GPURenderPass) + + QJSCLASSPREP_NO_FUNCS(SDL_GPUFence) + QJSCLASSPREP_NO_FUNCS(SDL_GPUComputePipeline) + QJSCLASSPREP_NO_FUNCS(SDL_GPUGraphicsPipeline) + QJSCLASSPREP_NO_FUNCS(SDL_GPUSampler) + QJSCLASSPREP_NO_FUNCS(SDL_GPUShader) + + // Create GPU constructor + JSValue gpu_ctor = JS_NewCFunction2(js, js_gpu_constructor, "gpu", 1, JS_CFUNC_constructor, 0); + + // Set prototype on constructor + JS_SetConstructor(js, gpu_ctor, SDL_GPUDevice_proto); + + // Set constructor in exports + JS_SetPropertyStr(js, ret, "gpu", gpu_ctor); + + // Add GPU object constructors + JSValue sampler_ctor = JS_NewCFunction2(js, js_gpu_sampler_constructor, "sampler", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "sampler", sampler_ctor); + + JSValue shader_ctor = JS_NewCFunction2(js, js_gpu_shader_constructor, "shader", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "shader", shader_ctor); + + JSValue graphics_pipeline_ctor = JS_NewCFunction2(js, js_gpu_graphics_pipeline_constructor, "graphics_pipeline", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "graphics_pipeline", graphics_pipeline_ctor); + + JSValue compute_pipeline_ctor = JS_NewCFunction2(js, js_gpu_compute_pipeline_constructor, "compute_pipeline", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "compute_pipeline", compute_pipeline_ctor); + + JSValue buffer_ctor = JS_NewCFunction2(js, js_gpu_buffer_constructor, "buffer", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "buffer", buffer_ctor); + + JSValue transfer_buffer_ctor = JS_NewCFunction2(js, js_gpu_transfer_buffer_constructor, "transfer_buffer", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "transfer_buffer", transfer_buffer_ctor); + + JSValue texture_ctor = JS_NewCFunction2(js, js_gpu_texture_constructor, "texture", 2, JS_CFUNC_constructor, 0); + JS_SetPropertyStr(js, ret, "texture", texture_ctor); + + return ret; +) diff --git a/haptic.c b/haptic.c new file mode 100644 index 0000000..548cf4f --- /dev/null +++ b/haptic.c @@ -0,0 +1,212 @@ +#include "cell.h" +#include + +// Forward declaration for joystick function +extern SDL_Joystick *js2SDL_Joystick(JSContext *js, JSValue val); + +// SDL_Haptic class +void SDL_Haptic_free(JSRuntime *rt, SDL_Haptic *haptic) { + if (haptic) SDL_CloseHaptic(haptic); +} + +QJSCLASS(SDL_Haptic,) + +// SDL_GetHaptics() -> array of haptic IDs +JSC_CCALL(haptic_get_haptics, + int count = 0; + SDL_HapticID *haptics = SDL_GetHaptics(&count); + if (!haptics) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, haptics[i])); + } + SDL_free(haptics); + return arr; +) + +// SDL_GetHapticNameForID(id) -> string +JSC_CCALL(haptic_get_name_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetHapticNameForID(id); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_OpenHaptic(id) -> Haptic object +JSC_CCALL(haptic_open, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Haptic *haptic = SDL_OpenHaptic(id); + if (!haptic) return JS_NULL; + return SDL_Haptic2js(js, haptic); +) + +// SDL_OpenHapticFromJoystick(joystick) -> Haptic object +JSC_CCALL(haptic_open_from_joystick, + SDL_Joystick *joystick = js2SDL_Joystick(js, argv[0]); + if (!joystick) return JS_NULL; + SDL_Haptic *haptic = SDL_OpenHapticFromJoystick(joystick); + if (!haptic) return JS_NULL; + return SDL_Haptic2js(js, haptic); +) + +// SDL_OpenHapticFromMouse() -> Haptic object +JSC_CCALL(haptic_open_from_mouse, + SDL_Haptic *haptic = SDL_OpenHapticFromMouse(); + if (!haptic) return JS_NULL; + return SDL_Haptic2js(js, haptic); +) + +// SDL_IsMouseHaptic() -> bool +JSC_CCALL(haptic_is_mouse_haptic, + return JS_NewBool(js, SDL_IsMouseHaptic()); +) + +// Haptic instance methods +JSC_CCALL(haptic_get_id, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewUint32(js, SDL_GetHapticID(haptic)); +) + +JSC_CCALL(haptic_get_name, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + const char *name = SDL_GetHapticName(haptic); + return name ? JS_NewString(js, name) : JS_NULL; +) + +JSC_CCALL(haptic_get_features, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewUint32(js, SDL_GetHapticFeatures(haptic)); +) + +JSC_CCALL(haptic_get_num_axes, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewInt32(js, SDL_GetNumHapticAxes(haptic)); +) + +JSC_CCALL(haptic_get_max_effects, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewInt32(js, SDL_GetMaxHapticEffects(haptic)); +) + +JSC_CCALL(haptic_get_max_effects_playing, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewInt32(js, SDL_GetMaxHapticEffectsPlaying(haptic)); +) + +JSC_CCALL(haptic_rumble_supported, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_HapticRumbleSupported(haptic)); +) + +JSC_CCALL(haptic_init_rumble, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_InitHapticRumble(haptic)); +) + +JSC_CCALL(haptic_play_rumble, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + float strength = js2number(js, argv[0]); + uint32_t length; + JS_ToUint32(js, &length, argv[1]); + return JS_NewBool(js, SDL_PlayHapticRumble(haptic, strength, length)); +) + +JSC_CCALL(haptic_stop_rumble, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_StopHapticRumble(haptic)); +) + +JSC_CCALL(haptic_set_gain, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + int gain; + JS_ToInt32(js, &gain, argv[0]); + return JS_NewBool(js, SDL_SetHapticGain(haptic, gain)); +) + +JSC_CCALL(haptic_set_autocenter, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + int autocenter; + JS_ToInt32(js, &autocenter, argv[0]); + return JS_NewBool(js, SDL_SetHapticAutocenter(haptic, autocenter)); +) + +JSC_CCALL(haptic_pause, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_PauseHaptic(haptic)); +) + +JSC_CCALL(haptic_resume, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_ResumeHaptic(haptic)); +) + +JSC_CCALL(haptic_stop_all, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + return JS_NewBool(js, SDL_StopHapticEffects(haptic)); +) + +JSC_CCALL(haptic_close, + SDL_Haptic *haptic = js2SDL_Haptic(js, self); + SDL_CloseHaptic(haptic); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Haptic_funcs[] = { + MIST_FUNC_DEF(haptic, get_id, 0), + MIST_FUNC_DEF(haptic, get_name, 0), + MIST_FUNC_DEF(haptic, get_features, 0), + MIST_FUNC_DEF(haptic, get_num_axes, 0), + MIST_FUNC_DEF(haptic, get_max_effects, 0), + MIST_FUNC_DEF(haptic, get_max_effects_playing, 0), + MIST_FUNC_DEF(haptic, rumble_supported, 0), + MIST_FUNC_DEF(haptic, init_rumble, 0), + MIST_FUNC_DEF(haptic, play_rumble, 2), + MIST_FUNC_DEF(haptic, stop_rumble, 0), + MIST_FUNC_DEF(haptic, set_gain, 1), + MIST_FUNC_DEF(haptic, set_autocenter, 1), + MIST_FUNC_DEF(haptic, pause, 0), + MIST_FUNC_DEF(haptic, resume, 0), + MIST_FUNC_DEF(haptic, stop_all, 0), + MIST_FUNC_DEF(haptic, close, 0), +}; + +static const JSCFunctionListEntry js_haptic_funcs[] = { + MIST_FUNC_DEF(haptic, get_haptics, 0), + MIST_FUNC_DEF(haptic, get_name_for_id, 1), + MIST_FUNC_DEF(haptic, open, 1), + MIST_FUNC_DEF(haptic, open_from_joystick, 1), + MIST_FUNC_DEF(haptic, open_from_mouse, 0), + MIST_FUNC_DEF(haptic, is_mouse_haptic, 0), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_HAPTIC); + QJSCLASSPREP_FUNCS(SDL_Haptic); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_haptic_funcs, countof(js_haptic_funcs)); + + // Export feature constants + JS_SetPropertyStr(js, ret, "CONSTANT", JS_NewUint32(js, SDL_HAPTIC_CONSTANT)); + JS_SetPropertyStr(js, ret, "SINE", JS_NewUint32(js, SDL_HAPTIC_SINE)); + JS_SetPropertyStr(js, ret, "SQUARE", JS_NewUint32(js, SDL_HAPTIC_SQUARE)); + JS_SetPropertyStr(js, ret, "TRIANGLE", JS_NewUint32(js, SDL_HAPTIC_TRIANGLE)); + JS_SetPropertyStr(js, ret, "SAWTOOTHUP", JS_NewUint32(js, SDL_HAPTIC_SAWTOOTHUP)); + JS_SetPropertyStr(js, ret, "SAWTOOTHDOWN", JS_NewUint32(js, SDL_HAPTIC_SAWTOOTHDOWN)); + JS_SetPropertyStr(js, ret, "RAMP", JS_NewUint32(js, SDL_HAPTIC_RAMP)); + JS_SetPropertyStr(js, ret, "SPRING", JS_NewUint32(js, SDL_HAPTIC_SPRING)); + JS_SetPropertyStr(js, ret, "DAMPER", JS_NewUint32(js, SDL_HAPTIC_DAMPER)); + JS_SetPropertyStr(js, ret, "INERTIA", JS_NewUint32(js, SDL_HAPTIC_INERTIA)); + JS_SetPropertyStr(js, ret, "FRICTION", JS_NewUint32(js, SDL_HAPTIC_FRICTION)); + JS_SetPropertyStr(js, ret, "LEFTRIGHT", JS_NewUint32(js, SDL_HAPTIC_LEFTRIGHT)); + JS_SetPropertyStr(js, ret, "CUSTOM", JS_NewUint32(js, SDL_HAPTIC_CUSTOM)); + JS_SetPropertyStr(js, ret, "GAIN", JS_NewUint32(js, SDL_HAPTIC_GAIN)); + JS_SetPropertyStr(js, ret, "AUTOCENTER", JS_NewUint32(js, SDL_HAPTIC_AUTOCENTER)); + JS_SetPropertyStr(js, ret, "STATUS", JS_NewUint32(js, SDL_HAPTIC_STATUS)); + JS_SetPropertyStr(js, ret, "PAUSE", JS_NewUint32(js, SDL_HAPTIC_PAUSE)); + JS_SetPropertyStr(js, ret, "INFINITY", JS_NewUint32(js, SDL_HAPTIC_INFINITY)); + + return ret; +) diff --git a/hidapi.c b/hidapi.c new file mode 100644 index 0000000..f617405 --- /dev/null +++ b/hidapi.c @@ -0,0 +1,280 @@ +#include "cell.h" +#include + +// SDL_hid_device class +void SDL_hid_device_free(JSRuntime *rt, SDL_hid_device *dev) { + if (dev) SDL_hid_close(dev); +} + +QJSCLASS(SDL_hid_device,) + +// Bus type to string +static const char *bus_type_to_string(SDL_hid_bus_type type) { + switch (type) { + case SDL_HID_API_BUS_USB: return "usb"; + case SDL_HID_API_BUS_BLUETOOTH: return "bluetooth"; + case SDL_HID_API_BUS_I2C: return "i2c"; + case SDL_HID_API_BUS_SPI: return "spi"; + default: return "unknown"; + } +} + +// Helper to convert device info to JS object +static JSValue device_info_to_js(JSContext *js, SDL_hid_device_info *info) { + JSValue obj = JS_NewObject(js); + + if (info->path) + JS_SetPropertyStr(js, obj, "path", JS_NewString(js, info->path)); + JS_SetPropertyStr(js, obj, "vendor_id", JS_NewUint32(js, info->vendor_id)); + JS_SetPropertyStr(js, obj, "product_id", JS_NewUint32(js, info->product_id)); + JS_SetPropertyStr(js, obj, "release_number", JS_NewUint32(js, info->release_number)); + JS_SetPropertyStr(js, obj, "usage_page", JS_NewUint32(js, info->usage_page)); + JS_SetPropertyStr(js, obj, "usage", JS_NewUint32(js, info->usage)); + JS_SetPropertyStr(js, obj, "interface_number", JS_NewInt32(js, info->interface_number)); + JS_SetPropertyStr(js, obj, "interface_class", JS_NewInt32(js, info->interface_class)); + JS_SetPropertyStr(js, obj, "interface_subclass", JS_NewInt32(js, info->interface_subclass)); + JS_SetPropertyStr(js, obj, "interface_protocol", JS_NewInt32(js, info->interface_protocol)); + JS_SetPropertyStr(js, obj, "bus_type", JS_NewString(js, bus_type_to_string(info->bus_type))); + + // Convert wide strings to UTF-8 + if (info->serial_number) { + char buf[256]; + wcstombs(buf, info->serial_number, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + JS_SetPropertyStr(js, obj, "serial_number", JS_NewString(js, buf)); + } + if (info->manufacturer_string) { + char buf[256]; + wcstombs(buf, info->manufacturer_string, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + JS_SetPropertyStr(js, obj, "manufacturer", JS_NewString(js, buf)); + } + if (info->product_string) { + char buf[256]; + wcstombs(buf, info->product_string, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + JS_SetPropertyStr(js, obj, "product", JS_NewString(js, buf)); + } + + return obj; +} + +// SDL_hid_init() -> int +JSC_CCALL(hidapi_init, + return JS_NewInt32(js, SDL_hid_init()); +) + +// SDL_hid_exit() -> int +JSC_CCALL(hidapi_exit, + return JS_NewInt32(js, SDL_hid_exit()); +) + +// SDL_hid_device_change_count() -> number +JSC_CCALL(hidapi_device_change_count, + return JS_NewUint32(js, SDL_hid_device_change_count()); +) + +// SDL_hid_enumerate(vendor_id, product_id) -> array of device info +JSC_CCALL(hidapi_enumerate, + uint32_t vendor_id = 0, product_id = 0; + if (argc > 0) JS_ToUint32(js, &vendor_id, argv[0]); + if (argc > 1) JS_ToUint32(js, &product_id, argv[1]); + + SDL_hid_device_info *devs = SDL_hid_enumerate(vendor_id, product_id); + if (!devs) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + int i = 0; + for (SDL_hid_device_info *cur = devs; cur; cur = cur->next) { + JS_SetPropertyUint32(js, arr, i++, device_info_to_js(js, cur)); + } + + SDL_hid_free_enumeration(devs); + return arr; +) + +// SDL_hid_open(vendor_id, product_id) -> device +JSC_CCALL(hidapi_open, + uint32_t vendor_id, product_id; + JS_ToUint32(js, &vendor_id, argv[0]); + JS_ToUint32(js, &product_id, argv[1]); + + SDL_hid_device *dev = SDL_hid_open(vendor_id, product_id, NULL); + if (!dev) return JS_NULL; + return SDL_hid_device2js(js, dev); +) + +// SDL_hid_open_path(path) -> device +JSC_SCALL(hidapi_open_path, + const char *path = JS_ToCString(js, argv[0]); + if (!path) return JS_EXCEPTION; + SDL_hid_device *dev = SDL_hid_open_path(path); + JS_FreeCString(js, path); + if (!dev) return JS_NULL; + return SDL_hid_device2js(js, dev); +) + +// Device instance methods +JSC_CCALL(hidapi_write, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (!data || data == (void*)-1) return JS_ThrowTypeError(js, "Expected ArrayBuffer"); + return JS_NewInt32(js, SDL_hid_write(dev, data, len)); +) + +JSC_CCALL(hidapi_read, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + int length = 64; + if (argc > 0) JS_ToInt32(js, &length, argv[0]); + + unsigned char *buf = malloc(length); + if (!buf) return JS_ThrowOutOfMemory(js); + + int result = SDL_hid_read(dev, buf, length); + if (result < 0) { + free(buf); + return JS_NULL; + } + + ret = js_new_blob_stoned_copy(js, buf, result); + free(buf); + return ret; +) + +JSC_CCALL(hidapi_read_timeout, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + int length = 64, timeout = -1; + if (argc > 0) JS_ToInt32(js, &length, argv[0]); + if (argc > 1) JS_ToInt32(js, &timeout, argv[1]); + + unsigned char *buf = malloc(length); + if (!buf) return JS_ThrowOutOfMemory(js); + + int result = SDL_hid_read_timeout(dev, buf, length, timeout); + if (result < 0) { + free(buf); + return JS_NULL; + } + + ret = js_new_blob_stoned_copy(js, buf, result); + free(buf); + return ret; +) + +JSC_CCALL(hidapi_set_nonblocking, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + int nonblock = JS_ToBool(js, argv[0]); + return JS_NewInt32(js, SDL_hid_set_nonblocking(dev, nonblock)); +) + +JSC_CCALL(hidapi_send_feature_report, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (!data || data == (void*)-1) return JS_ThrowTypeError(js, "Expected ArrayBuffer"); + return JS_NewInt32(js, SDL_hid_send_feature_report(dev, data, len)); +) + +JSC_CCALL(hidapi_get_feature_report, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + int length = 64; + if (argc > 0) JS_ToInt32(js, &length, argv[0]); + + unsigned char *buf = malloc(length); + if (!buf) return JS_ThrowOutOfMemory(js); + + // First byte is report number + buf[0] = 0; + if (argc > 1) { + int report_num; + JS_ToInt32(js, &report_num, argv[1]); + buf[0] = report_num; + } + + int result = SDL_hid_get_feature_report(dev, buf, length); + if (result < 0) { + free(buf); + return JS_NULL; + } + + ret = js_new_blob_stoned_copy(js, buf, result); + free(buf); + return ret; +) + +JSC_CCALL(hidapi_get_input_report, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + int length = 64; + if (argc > 0) JS_ToInt32(js, &length, argv[0]); + + unsigned char *buf = malloc(length); + if (!buf) return JS_ThrowOutOfMemory(js); + + buf[0] = 0; + if (argc > 1) { + int report_num; + JS_ToInt32(js, &report_num, argv[1]); + buf[0] = report_num; + } + + int result = SDL_hid_get_input_report(dev, buf, length); + if (result < 0) { + free(buf); + return JS_NULL; + } + + ret = js_new_blob_stoned_copy(js, buf, result); + free(buf); + return ret; +) + +JSC_CCALL(hidapi_get_device_info, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + SDL_hid_device_info *info = SDL_hid_get_device_info(dev); + if (!info) return JS_NULL; + return device_info_to_js(js, info); +) + +JSC_CCALL(hidapi_close, + SDL_hid_device *dev = js2SDL_hid_device(js, self); + SDL_hid_close(dev); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_hid_device_funcs[] = { + MIST_FUNC_DEF(hidapi, write, 1), + MIST_FUNC_DEF(hidapi, read, 1), + MIST_FUNC_DEF(hidapi, read_timeout, 2), + MIST_FUNC_DEF(hidapi, set_nonblocking, 1), + MIST_FUNC_DEF(hidapi, send_feature_report, 1), + MIST_FUNC_DEF(hidapi, get_feature_report, 2), + MIST_FUNC_DEF(hidapi, get_input_report, 2), + MIST_FUNC_DEF(hidapi, get_device_info, 0), + MIST_FUNC_DEF(hidapi, close, 0), +}; + +static const JSCFunctionListEntry js_hidapi_funcs[] = { + MIST_FUNC_DEF(hidapi, init, 0), + MIST_FUNC_DEF(hidapi, exit, 0), + MIST_FUNC_DEF(hidapi, device_change_count, 0), + MIST_FUNC_DEF(hidapi, enumerate, 2), + MIST_FUNC_DEF(hidapi, open, 2), + MIST_FUNC_DEF(hidapi, open_path, 1), +}; + +CELL_USE_INIT( + QJSCLASSPREP_FUNCS(SDL_hid_device); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_hidapi_funcs, countof(js_hidapi_funcs)); + + // Export bus type constants + JS_SetPropertyStr(js, ret, "BUS_UNKNOWN", JS_NewInt32(js, SDL_HID_API_BUS_UNKNOWN)); + JS_SetPropertyStr(js, ret, "BUS_USB", JS_NewInt32(js, SDL_HID_API_BUS_USB)); + JS_SetPropertyStr(js, ret, "BUS_BLUETOOTH", JS_NewInt32(js, SDL_HID_API_BUS_BLUETOOTH)); + JS_SetPropertyStr(js, ret, "BUS_I2C", JS_NewInt32(js, SDL_HID_API_BUS_I2C)); + JS_SetPropertyStr(js, ret, "BUS_SPI", JS_NewInt32(js, SDL_HID_API_BUS_SPI)); + + return ret; +) diff --git a/input.c b/input.c new file mode 100644 index 0000000..51b11aa --- /dev/null +++ b/input.c @@ -0,0 +1,780 @@ +#include "cell.h" +#include "wota.h" + +#include + +// Internal keymod function for input module +static JSValue js_keymod(JSContext *js) +{ + SDL_Keymod modstate = SDL_GetModState(); + JSValue ret = JS_NewObject(js); + if (SDL_KMOD_CTRL & modstate) + JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1)); + if (SDL_KMOD_SHIFT & modstate) + JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1)); + if (SDL_KMOD_ALT & modstate) + JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1)); + if (SDL_KMOD_GUI & modstate) + JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1)); + if (SDL_KMOD_NUM & modstate) + JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1)); + if (SDL_KMOD_CAPS & modstate) + JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1)); + if (SDL_KMOD_SCROLL & modstate) + JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1)); + if (SDL_KMOD_MODE & modstate) + JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1)); + + return ret; +} + +// INPUT FUNCTIONS +JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0]))) + +JSC_CCALL(input_mouse_show, + if (JS_ToBool(js,argv[0])) + SDL_ShowCursor(); + else + SDL_HideCursor(); +) + +JSC_CCALL(input_keyname, + return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0]))); +) + +JSC_CCALL(input_keymod, + return js_keymod(js); +) + +JSC_CCALL(input_mousestate, + float x,y; + SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y); + JSValue m = JS_NewObject(js); + JS_SetPropertyStr(js,m,"x", number2js(js,x)); + JS_SetPropertyStr(js,m,"y", number2js(js,y)); + + if (flags & SDL_BUTTON_LMASK) + JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_MMASK) + JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_RMASK) + JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X1MASK) + JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1)); + if (flags & SDL_BUTTON_X2MASK) + JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1)); + + return m; +) + +// Event processing functions (moved from cell.c) + +const char* event_type_to_string(Uint32 event_type) { + switch (event_type) { + // Application events + case SDL_EVENT_QUIT: return "quit"; + case SDL_EVENT_TERMINATING: return "terminating"; + case SDL_EVENT_LOW_MEMORY: return "low_memory"; + case SDL_EVENT_WILL_ENTER_BACKGROUND: return "will_enter_background"; + case SDL_EVENT_DID_ENTER_BACKGROUND: return "did_enter_background"; + case SDL_EVENT_WILL_ENTER_FOREGROUND: return "will_enter_foreground"; + case SDL_EVENT_DID_ENTER_FOREGROUND: return "did_enter_foreground"; + case SDL_EVENT_LOCALE_CHANGED: return "locale_changed"; + case SDL_EVENT_SYSTEM_THEME_CHANGED: return "system_theme_changed"; + + // Display events + case SDL_EVENT_DISPLAY_ORIENTATION: return "display_orientation"; + case SDL_EVENT_DISPLAY_ADDED: return "display_added"; + case SDL_EVENT_DISPLAY_REMOVED: return "display_removed"; + case SDL_EVENT_DISPLAY_MOVED: return "display_moved"; + case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: return "display_desktop_mode_changed"; + case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: return "display_current_mode_changed"; + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: return "display_content_scale_changed"; + + // Window events + case SDL_EVENT_WINDOW_SHOWN: return "window_shown"; + case SDL_EVENT_WINDOW_HIDDEN: return "window_hidden"; + case SDL_EVENT_WINDOW_EXPOSED: return "window_exposed"; + case SDL_EVENT_WINDOW_MOVED: return "window_moved"; + case SDL_EVENT_WINDOW_RESIZED: return "window_resized"; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: return "window_pixel_size_changed"; + case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: return "window_metal_view_resized"; + case SDL_EVENT_WINDOW_MINIMIZED: return "window_minimized"; + case SDL_EVENT_WINDOW_MAXIMIZED: return "window_maximized"; + case SDL_EVENT_WINDOW_RESTORED: return "window_restored"; + case SDL_EVENT_WINDOW_MOUSE_ENTER: return "window_mouse_enter"; + case SDL_EVENT_WINDOW_MOUSE_LEAVE: return "window_mouse_leave"; + case SDL_EVENT_WINDOW_FOCUS_GAINED: return "window_focus_gained"; + case SDL_EVENT_WINDOW_FOCUS_LOST: return "window_focus_lost"; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: return "window_close_requested"; + case SDL_EVENT_WINDOW_HIT_TEST: return "window_hit_test"; + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: return "window_iccprof_changed"; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: return "window_display_changed"; + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: return "window_display_scale_changed"; + case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: return "window_safe_area_changed"; + case SDL_EVENT_WINDOW_OCCLUDED: return "window_occluded"; + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: return "window_enter_fullscreen"; + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: return "window_leave_fullscreen"; + case SDL_EVENT_WINDOW_DESTROYED: return "window_destroyed"; + case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: return "window_hdr_state_changed"; + + // Keyboard events + case SDL_EVENT_KEY_DOWN: return "key_down"; + case SDL_EVENT_KEY_UP: return "key_up"; + case SDL_EVENT_TEXT_EDITING: return "text_editing"; + case SDL_EVENT_TEXT_INPUT: return "text_input"; + case SDL_EVENT_KEYMAP_CHANGED: return "keymap_changed"; + case SDL_EVENT_KEYBOARD_ADDED: return "keyboard_added"; + case SDL_EVENT_KEYBOARD_REMOVED: return "keyboard_removed"; + case SDL_EVENT_TEXT_EDITING_CANDIDATES: return "text_editing_candidates"; + + // Mouse events + case SDL_EVENT_MOUSE_MOTION: return "mouse_motion"; + case SDL_EVENT_MOUSE_BUTTON_DOWN: return "mouse_button_down"; + case SDL_EVENT_MOUSE_BUTTON_UP: return "mouse_button_up"; + case SDL_EVENT_MOUSE_WHEEL: return "mouse_wheel"; + case SDL_EVENT_MOUSE_ADDED: return "mouse_added"; + case SDL_EVENT_MOUSE_REMOVED: return "mouse_removed"; + + // Joystick events + case SDL_EVENT_JOYSTICK_AXIS_MOTION: return "joystick_axis_motion"; + case SDL_EVENT_JOYSTICK_BALL_MOTION: return "joystick_ball_motion"; + case SDL_EVENT_JOYSTICK_HAT_MOTION: return "joystick_hat_motion"; + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: return "joystick_button_down"; + case SDL_EVENT_JOYSTICK_BUTTON_UP: return "joystick_button_up"; + case SDL_EVENT_JOYSTICK_ADDED: return "joystick_added"; + case SDL_EVENT_JOYSTICK_REMOVED: return "joystick_removed"; + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: return "joystick_battery_updated"; + case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: return "joystick_update_complete"; + + // Gamepad events + case SDL_EVENT_GAMEPAD_AXIS_MOTION: return "gamepad_axis_motion"; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: return "gamepad_button_down"; + case SDL_EVENT_GAMEPAD_BUTTON_UP: return "gamepad_button_up"; + case SDL_EVENT_GAMEPAD_ADDED: return "gamepad_added"; + case SDL_EVENT_GAMEPAD_REMOVED: return "gamepad_removed"; + case SDL_EVENT_GAMEPAD_REMAPPED: return "gamepad_remapped"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: return "gamepad_touchpad_down"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: return "gamepad_touchpad_motion"; + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: return "gamepad_touchpad_up"; + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: return "gamepad_sensor_update"; + case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: return "gamepad_update_complete"; + case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: return "gamepad_steam_handle_updated"; + + // Touch events + case SDL_EVENT_FINGER_DOWN: return "finger_down"; + case SDL_EVENT_FINGER_UP: return "finger_up"; + case SDL_EVENT_FINGER_MOTION: return "finger_motion"; + + // Clipboard events + case SDL_EVENT_CLIPBOARD_UPDATE: return "clipboard_update"; + + // Drag and drop events + case SDL_EVENT_DROP_FILE: return "drop_file"; + case SDL_EVENT_DROP_TEXT: return "drop_text"; + case SDL_EVENT_DROP_BEGIN: return "drop_begin"; + case SDL_EVENT_DROP_COMPLETE: return "drop_complete"; + case SDL_EVENT_DROP_POSITION: return "drop_position"; + + // Audio device events + case SDL_EVENT_AUDIO_DEVICE_ADDED: return "audio_device_added"; + case SDL_EVENT_AUDIO_DEVICE_REMOVED: return "audio_device_removed"; + case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: return "audio_device_format_changed"; + + // Sensor events + case SDL_EVENT_SENSOR_UPDATE: return "sensor_update"; + + // Pen events + case SDL_EVENT_PEN_PROXIMITY_IN: return "pen_proximity_in"; + case SDL_EVENT_PEN_PROXIMITY_OUT: return "pen_proximity_out"; + case SDL_EVENT_PEN_DOWN: return "pen_down"; + case SDL_EVENT_PEN_UP: return "pen_up"; + case SDL_EVENT_PEN_BUTTON_DOWN: return "pen_button_down"; + case SDL_EVENT_PEN_BUTTON_UP: return "pen_button_up"; + case SDL_EVENT_PEN_MOTION: return "pen_motion"; + case SDL_EVENT_PEN_AXIS: return "pen_axis"; + + // Camera events + case SDL_EVENT_CAMERA_DEVICE_ADDED: return "camera_device_added"; + case SDL_EVENT_CAMERA_DEVICE_REMOVED: return "camera_device_removed"; + case SDL_EVENT_CAMERA_DEVICE_APPROVED: return "camera_device_approved"; + case SDL_EVENT_CAMERA_DEVICE_DENIED: return "camera_device_denied"; + + // Render events + case SDL_EVENT_RENDER_TARGETS_RESET: return "render_targets_reset"; + case SDL_EVENT_RENDER_DEVICE_RESET: return "render_device_reset"; + case SDL_EVENT_RENDER_DEVICE_LOST: return "render_device_lost"; + + // User event (assuming it should be included) + case SDL_EVENT_USER: return "user"; + + default: return "unknown"; + } +} + +const char* mouse_button_to_string(int mouse) { + switch (mouse) { + case SDL_BUTTON_LEFT: return "left"; + case SDL_BUTTON_MIDDLE: return "middle"; + case SDL_BUTTON_RIGHT: return "right"; + case SDL_BUTTON_X1: return "x1"; + case SDL_BUTTON_X2: return "x2"; + default: return "left"; + } +} + +static void wota_write_vec2(WotaBuffer *wb, double x, double y) { + // We'll store as WOTA_ARR of length 2, then two numbers + wota_write_array(wb, 2); + wota_write_number(wb, x); + wota_write_number(wb, y); +} + +static int event2wota_count_props(const SDL_Event *event) +{ + // We always store at least "type" and "timestamp". + int count = 2; + + switch (event->type) { + + case SDL_EVENT_AUDIO_DEVICE_ADDED: + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + count += 2; // which, recording + break; + + case SDL_EVENT_DISPLAY_ORIENTATION: + case SDL_EVENT_DISPLAY_ADDED: + case SDL_EVENT_DISPLAY_REMOVED: + case SDL_EVENT_DISPLAY_MOVED: + case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: + case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: + count += 3; // which, orientation/data1, data2 + break; + + case SDL_EVENT_MOUSE_MOTION: + count += 5; + break; + + case SDL_EVENT_MOUSE_WHEEL: + // window, which, scroll, mouse => 4 extra + count += 4; + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + // window, which, down, button, clicks, mouse => 6 extra + count += 6; + break; + + case SDL_EVENT_SENSOR_UPDATE: + // which, sensor_timestamp => 2 extra + count += 2; + break; + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + // window, which, down, repeat, key, scancode, mod => 7 extra + count += 7; + break; + + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + // touch, finger, pos, d_pos, pressure, window => 6 extra + count += 6; + break; + + case SDL_EVENT_DROP_BEGIN: + case SDL_EVENT_DROP_FILE: + case SDL_EVENT_DROP_TEXT: + case SDL_EVENT_DROP_COMPLETE: + case SDL_EVENT_DROP_POSITION: + // window, pos, data, source => 4 extra + count += 4; + break; + + case SDL_EVENT_TEXT_INPUT: + // window, text, mod => 3 extra + count += 3; + break; + + case SDL_EVENT_CAMERA_DEVICE_APPROVED: + case SDL_EVENT_CAMERA_DEVICE_REMOVED: + case SDL_EVENT_CAMERA_DEVICE_ADDED: + case SDL_EVENT_CAMERA_DEVICE_DENIED: + // which => 1 extra + count += 1; + break; + + case SDL_EVENT_CLIPBOARD_UPDATE: + // owner => 1 extra + count += 1; + break; + + /* Window events that only need 'which' */ + case SDL_EVENT_WINDOW_EXPOSED: + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + // which => 1 extra + count += 1; + break; + + /* Window events that need data1 and data2 */ + case SDL_EVENT_WINDOW_SHOWN: + case SDL_EVENT_WINDOW_HIDDEN: + case SDL_EVENT_WINDOW_MOVED: + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + case SDL_EVENT_WINDOW_MOUSE_ENTER: + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + case SDL_EVENT_WINDOW_HIT_TEST: + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + case SDL_EVENT_WINDOW_OCCLUDED: + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + case SDL_EVENT_WINDOW_DESTROYED: + case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: + // which, x/width/display_index, y/height => 3 extra + count += 3; + break; + + case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: + // which, x, y, width, height => 5 extra + count += 5; + break; + + case SDL_EVENT_JOYSTICK_ADDED: + case SDL_EVENT_JOYSTICK_REMOVED: + case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: + // which => 1 extra + count += 1; + break; + + case SDL_EVENT_JOYSTICK_AXIS_MOTION: + // which, axis, value => 3 extra + count += 3; + break; + + case SDL_EVENT_JOYSTICK_BALL_MOTION: + // which, ball, rel => 3 extra + count += 3; + break; + + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EVENT_JOYSTICK_BUTTON_UP: + // which, button, down => 3 extra + count += 3; + break; + + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_REMAPPED: + case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: + case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: + // which => 1 extra + count += 1; + break; + + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + // which, axis, value => 3 extra + count += 3; + break; + + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + // which, button, down => 3 extra + count += 3; + break; + + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + // which, touchpad, finger, pos, pressure => 5 extra + count += 5; + break; + + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: + // which, sensor, sensor_timestamp => 3 extra + count += 3; + break; + + case SDL_EVENT_USER: + // cb => 1 extra + count += 1; + break; + } + + return count; +} + +static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) { + wota_write_record(wb, (unsigned long long)c); + wota_write_text(wb, "type"); + wota_write_text(wb, event_type_to_string(e->type)); + wota_write_text(wb, "timestamp"); + wota_write_number(wb, (double)e->common.timestamp); + switch(e->type) { + case SDL_EVENT_AUDIO_DEVICE_ADDED: + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->adevice.which); + wota_write_text(wb, "recording"); + wota_write_sym(wb, e->adevice.recording ? WOTA_TRUE : WOTA_FALSE); + break; + case SDL_EVENT_DISPLAY_ORIENTATION: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->display.displayID); + wota_write_text(wb, "orientation"); + wota_write_number(wb, (double)e->display.data1); + wota_write_text(wb, "data2"); + wota_write_number(wb, (double)e->display.data2); + break; + case SDL_EVENT_DISPLAY_ADDED: + case SDL_EVENT_DISPLAY_REMOVED: + case SDL_EVENT_DISPLAY_MOVED: + case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: + case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->display.displayID); + wota_write_text(wb, "data1"); + wota_write_number(wb, (double)e->display.data1); + wota_write_text(wb, "data2"); + wota_write_number(wb, (double)e->display.data2); + break; + case SDL_EVENT_MOUSE_MOTION: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->motion.windowID); + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->motion.which); + wota_write_text(wb, "state"); + wota_write_number(wb, (double)e->motion.state); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->motion.x, (double)e->motion.y); + wota_write_text(wb, "d_pos"); + wota_write_vec2(wb, (double)e->motion.xrel, (double)e->motion.yrel); + break; + case SDL_EVENT_MOUSE_WHEEL: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->wheel.windowID); + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->wheel.which); + wota_write_text(wb, "scroll"); + wota_write_vec2(wb, (double)e->wheel.x, (double)e->wheel.y); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->wheel.mouse_x, (double)e->wheel.mouse_y); + break; + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->button.windowID); + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->button.which); + wota_write_text(wb, "down"); + wota_write_sym(wb, e->button.down ? WOTA_TRUE : WOTA_FALSE); + wota_write_text(wb, "button"); + wota_write_text(wb, mouse_button_to_string(e->button.button)); + wota_write_text(wb, "clicks"); + wota_write_number(wb, (double)e->button.clicks); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->button.x, (double)e->button.y); + break; + case SDL_EVENT_SENSOR_UPDATE: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->sensor.which); + wota_write_text(wb, "sensor_timestamp"); + wota_write_number(wb, (double)e->sensor.sensor_timestamp); + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->key.windowID); + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->key.which); + wota_write_text(wb, "down"); + wota_write_sym(wb, e->key.down ? WOTA_TRUE : WOTA_FALSE); + wota_write_text(wb, "repeat"); + wota_write_sym(wb, e->key.repeat ? WOTA_TRUE : WOTA_FALSE); + wota_write_text(wb, "key"); + wota_write_number(wb, (double)e->key.key); + wota_write_text(wb, "scancode"); + wota_write_number(wb, (double)e->key.scancode); + wota_write_text(wb, "mod"); + wota_write_number(wb, (double)e->key.mod); + break; + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + wota_write_text(wb, "touch"); + wota_write_number(wb, (double)e->tfinger.touchID); + wota_write_text(wb, "finger"); + wota_write_number(wb, (double)e->tfinger.fingerID); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->tfinger.x, (double)e->tfinger.y); + wota_write_text(wb, "d_pos"); + wota_write_vec2(wb, (double)e->tfinger.dx, (double)e->tfinger.dy); + wota_write_text(wb, "pressure"); + wota_write_number(wb, (double)e->tfinger.pressure); + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->key.windowID); + break; + case SDL_EVENT_DROP_BEGIN: + case SDL_EVENT_DROP_FILE: + case SDL_EVENT_DROP_TEXT: + case SDL_EVENT_DROP_COMPLETE: + case SDL_EVENT_DROP_POSITION: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->drop.windowID); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->drop.x, (double)e->drop.y); + wota_write_text(wb, "data"); + wota_write_text(wb, e->drop.data ? e->drop.data : ""); + wota_write_text(wb, "source"); + wota_write_text(wb, e->drop.source ? e->drop.source : ""); + break; + case SDL_EVENT_TEXT_INPUT: + wota_write_text(wb, "window"); + wota_write_number(wb, (double)e->text.windowID); + wota_write_text(wb, "text"); + wota_write_text(wb, e->text.text); + wota_write_text(wb, "mod"); + wota_write_number(wb, 0); + break; + case SDL_EVENT_CAMERA_DEVICE_APPROVED: + case SDL_EVENT_CAMERA_DEVICE_REMOVED: + case SDL_EVENT_CAMERA_DEVICE_ADDED: + case SDL_EVENT_CAMERA_DEVICE_DENIED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->cdevice.which); + break; + case SDL_EVENT_CLIPBOARD_UPDATE: + wota_write_text(wb, "owner"); + wota_write_sym(wb, e->clipboard.owner ? WOTA_TRUE : WOTA_FALSE); + break; + case SDL_EVENT_WINDOW_EXPOSED: + case SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EVENT_WINDOW_FOCUS_LOST: + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + break; + case SDL_EVENT_WINDOW_SHOWN: + case SDL_EVENT_WINDOW_HIDDEN: + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + case SDL_EVENT_WINDOW_MOUSE_ENTER: + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + case SDL_EVENT_WINDOW_HIT_TEST: + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: + case SDL_EVENT_WINDOW_OCCLUDED: + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + case SDL_EVENT_WINDOW_DESTROYED: + case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "data1"); + wota_write_number(wb, (double)e->window.data1); + wota_write_text(wb, "data2"); + wota_write_number(wb, (double)e->window.data2); + break; + case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: + { + SDL_Window *window = SDL_GetWindowFromID(e->window.windowID); + SDL_Rect safe_area = {0, 0, 0, 0}; + if (window && SDL_GetWindowSafeArea(window, &safe_area)) { + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "x"); + wota_write_number(wb, (double)safe_area.x); + wota_write_text(wb, "y"); + wota_write_number(wb, (double)safe_area.y); + wota_write_text(wb, "width"); + wota_write_number(wb, (double)safe_area.w); + wota_write_text(wb, "height"); + wota_write_number(wb, (double)safe_area.h); + } else { + // Fallback to original behavior if SDL_GetWindowSafeArea fails + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "data1"); + wota_write_number(wb, (double)e->window.data1); + wota_write_text(wb, "data2"); + wota_write_number(wb, (double)e->window.data2); + } + } + break; + case SDL_EVENT_WINDOW_MOVED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "x"); + wota_write_number(wb, (double)e->window.data1); + wota_write_text(wb, "y"); + wota_write_number(wb, (double)e->window.data2); + break; + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "width"); + wota_write_number(wb, (double)e->window.data1); + wota_write_text(wb, "height"); + wota_write_number(wb, (double)e->window.data2); + break; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->window.windowID); + wota_write_text(wb, "display_index"); + wota_write_number(wb, (double)e->window.data1); + wota_write_text(wb, "data2"); + wota_write_number(wb, (double)e->window.data2); + break; + case SDL_EVENT_JOYSTICK_ADDED: + case SDL_EVENT_JOYSTICK_REMOVED: + case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->jdevice.which); + break; + case SDL_EVENT_JOYSTICK_AXIS_MOTION: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->jaxis.which); + wota_write_text(wb, "axis"); + wota_write_number(wb, (double)e->jaxis.axis); + wota_write_text(wb, "value"); + wota_write_number(wb, (double)e->jaxis.value); + break; + case SDL_EVENT_JOYSTICK_BALL_MOTION: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->jball.which); + wota_write_text(wb, "ball"); + wota_write_number(wb, (double)e->jball.ball); + wota_write_text(wb, "rel"); + wota_write_vec2(wb, (double)e->jball.xrel, (double)e->jball.yrel); + break; + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EVENT_JOYSTICK_BUTTON_UP: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->jbutton.which); + wota_write_text(wb, "button"); + wota_write_number(wb, (double)e->jbutton.button); + wota_write_text(wb, "down"); + wota_write_sym(wb, e->jbutton.down ? WOTA_TRUE : WOTA_FALSE); + break; + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EVENT_GAMEPAD_REMAPPED: + case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: + case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->gdevice.which); + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->gaxis.which); + wota_write_text(wb, "axis"); + wota_write_text(wb, SDL_GetGamepadStringForAxis(e->gaxis.axis)); + wota_write_text(wb, "value"); + // Normalize axis values + double normalized_value; + if (e->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || + e->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { + // Triggers: 0 to 32767 -> 0 to 1 + normalized_value = (double)e->gaxis.value / 32767.0; + } else { + // Thumbsticks: -32768 to 32767 -> -1 to 1 + normalized_value = (double)e->gaxis.value / 32767.0; + } + wota_write_number(wb, normalized_value); + break; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->gbutton.which); + wota_write_text(wb, "button"); + wota_write_text(wb, SDL_GetGamepadStringForButton(e->gbutton.button)); + wota_write_text(wb, "down"); + wota_write_sym(wb, e->gbutton.down ? WOTA_TRUE : WOTA_FALSE); + break; + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->gtouchpad.which); + wota_write_text(wb, "touchpad"); + wota_write_number(wb, (double)e->gtouchpad.touchpad); + wota_write_text(wb, "finger"); + wota_write_number(wb, (double)e->gtouchpad.finger); + wota_write_text(wb, "pos"); + wota_write_vec2(wb, (double)e->gtouchpad.x, (double)e->gtouchpad.y); + wota_write_text(wb, "pressure"); + wota_write_number(wb, (double)e->gtouchpad.pressure); + break; + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: + wota_write_text(wb, "which"); + wota_write_number(wb, (double)e->gsensor.which); + wota_write_text(wb, "sensor"); + wota_write_number(wb, (double)e->gsensor.sensor); + wota_write_text(wb, "sensor_timestamp"); + wota_write_number(wb, (double)e->gsensor.sensor_timestamp); + break; + case SDL_EVENT_USER: + wota_write_text(wb, "cb"); + wota_write_number(wb, (double)(uintptr_t)e->user.data1); + break; + } +} + +static WotaBuffer event2wota(const SDL_Event *event) { + WotaBuffer wb; + wota_buffer_init(&wb, 8); + int n = event2wota_count_props(event); + event2wota_write(&wb, event, n); + return wb; +} + +// Get all events directly from SDL event queue +JSC_CCALL(input_get_events, + JSValue events_array = JS_NewArray(js); + SDL_Event event; + int event_count = 0; + + while (SDL_PollEvent(&event)) { +// gui_input(&event); + + WotaBuffer wb = event2wota(&event); + JSValue event_obj = wota2value(js, wb.data); + JS_SetPropertyUint32(js, events_array, event_count, event_obj); + wota_buffer_free(&wb); + event_count++; + } + + return events_array; +) + +JSC_CCALL(input_gamepad_id_to_type, + int id = js2number(js, argv[0]); + return JS_NewString(js, SDL_GetGamepadStringForType(SDL_GetGamepadTypeForID(id))); +) + +static const JSCFunctionListEntry js_input_funcs[] = { + MIST_FUNC_DEF(input, mouse_show, 1), + MIST_FUNC_DEF(input, mouse_lock, 1), + MIST_FUNC_DEF(input, keyname, 1), + MIST_FUNC_DEF(input, keymod, 0), + MIST_FUNC_DEF(input, mousestate, 0), + MIST_FUNC_DEF(input, get_events, 0), + MIST_FUNC_DEF(input, gamepad_id_to_type, 1), +}; + +CELL_USE_FUNCS(js_input_funcs) diff --git a/joystick.c b/joystick.c new file mode 100644 index 0000000..8399a4b --- /dev/null +++ b/joystick.c @@ -0,0 +1,393 @@ +#include "cell.h" +#include + +// Joystick type enum to string +static const char *joystick_type_to_string(SDL_JoystickType type) { + switch (type) { + case SDL_JOYSTICK_TYPE_GAMEPAD: return "gamepad"; + case SDL_JOYSTICK_TYPE_WHEEL: return "wheel"; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: return "arcade_stick"; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: return "flight_stick"; + case SDL_JOYSTICK_TYPE_DANCE_PAD: return "dance_pad"; + case SDL_JOYSTICK_TYPE_GUITAR: return "guitar"; + case SDL_JOYSTICK_TYPE_DRUM_KIT: return "drum_kit"; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: return "arcade_pad"; + case SDL_JOYSTICK_TYPE_THROTTLE: return "throttle"; + default: return "unknown"; + } +} + +// Connection state to string +static const char *connection_state_to_string(SDL_JoystickConnectionState state) { + switch (state) { + case SDL_JOYSTICK_CONNECTION_WIRED: return "wired"; + case SDL_JOYSTICK_CONNECTION_WIRELESS: return "wireless"; + case SDL_JOYSTICK_CONNECTION_UNKNOWN: return "unknown"; + default: return "invalid"; + } +} + +// SDL_Joystick class +void SDL_Joystick_free(JSRuntime *rt, SDL_Joystick *joystick) { + if (joystick) SDL_CloseJoystick(joystick); +} + +QJSCLASS(SDL_Joystick,) + +// SDL_LockJoysticks() +JSC_CCALL(joystick_lock, + SDL_LockJoysticks(); + return JS_NULL; +) + +// SDL_UnlockJoysticks() +JSC_CCALL(joystick_unlock, + SDL_UnlockJoysticks(); + return JS_NULL; +) + +// SDL_HasJoystick() -> bool +JSC_CCALL(joystick_has, + return JS_NewBool(js, SDL_HasJoystick()); +) + +// SDL_GetJoysticks() -> array of joystick IDs +JSC_CCALL(joystick_get_joysticks, + int count = 0; + SDL_JoystickID *joysticks = SDL_GetJoysticks(&count); + if (!joysticks) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, joysticks[i])); + } + SDL_free(joysticks); + return arr; +) + +// SDL_GetJoystickNameForID(id) -> string +JSC_CCALL(joystick_get_name_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetJoystickNameForID(id); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetJoystickPathForID(id) -> string +JSC_CCALL(joystick_get_path_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *path = SDL_GetJoystickPathForID(id); + return path ? JS_NewString(js, path) : JS_NULL; +) + +// SDL_GetJoystickPlayerIndexForID(id) -> number +JSC_CCALL(joystick_get_player_index_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewInt32(js, SDL_GetJoystickPlayerIndexForID(id)); +) + +// SDL_GetJoystickGUIDForID(id) -> string +JSC_CCALL(joystick_get_guid_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_GUID guid = SDL_GetJoystickGUIDForID(id); + char buf[64]; + SDL_GUIDToString(guid, buf, sizeof(buf)); + return JS_NewString(js, buf); +) + +// SDL_GetJoystickVendorForID(id) -> number +JSC_CCALL(joystick_get_vendor_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewUint32(js, SDL_GetJoystickVendorForID(id)); +) + +// SDL_GetJoystickProductForID(id) -> number +JSC_CCALL(joystick_get_product_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewUint32(js, SDL_GetJoystickProductForID(id)); +) + +// SDL_GetJoystickProductVersionForID(id) -> number +JSC_CCALL(joystick_get_product_version_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewUint32(js, SDL_GetJoystickProductVersionForID(id)); +) + +// SDL_GetJoystickTypeForID(id) -> string +JSC_CCALL(joystick_get_type_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_JoystickType type = SDL_GetJoystickTypeForID(id); + return JS_NewString(js, joystick_type_to_string(type)); +) + +// SDL_OpenJoystick(id) -> Joystick object +JSC_CCALL(joystick_open, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Joystick *joystick = SDL_OpenJoystick(id); + if (!joystick) return JS_NULL; + return SDL_Joystick2js(js, joystick); +) + +// SDL_GetJoystickFromID(id) -> Joystick object (does not take ownership) +JSC_CCALL(joystick_get_from_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Joystick *joystick = SDL_GetJoystickFromID(id); + if (!joystick) return JS_NULL; + // Note: This returns an existing joystick, not a new one + // We should not free it when the JS object is garbage collected + return SDL_Joystick2js(js, joystick); +) + +// SDL_GetJoystickFromPlayerIndex(player_index) -> Joystick object +JSC_CCALL(joystick_get_from_player_index, + int player_index; + JS_ToInt32(js, &player_index, argv[0]); + SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(player_index); + if (!joystick) return JS_NULL; + return SDL_Joystick2js(js, joystick); +) + +// Joystick instance methods +JSC_CCALL(joystick_get_name, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + const char *name = SDL_GetJoystickName(joystick); + return name ? JS_NewString(js, name) : JS_NULL; +) + +JSC_CCALL(joystick_get_path, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + const char *path = SDL_GetJoystickPath(joystick); + return path ? JS_NewString(js, path) : JS_NULL; +) + +JSC_CCALL(joystick_get_player_index, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewInt32(js, SDL_GetJoystickPlayerIndex(joystick)); +) + +JSC_CCALL(joystick_set_player_index, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int player_index; + JS_ToInt32(js, &player_index, argv[0]); + return JS_NewBool(js, SDL_SetJoystickPlayerIndex(joystick, player_index)); +) + +JSC_CCALL(joystick_get_guid, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + SDL_GUID guid = SDL_GetJoystickGUID(joystick); + char buf[64]; + SDL_GUIDToString(guid, buf, sizeof(buf)); + return JS_NewString(js, buf); +) + +JSC_CCALL(joystick_get_vendor, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewUint32(js, SDL_GetJoystickVendor(joystick)); +) + +JSC_CCALL(joystick_get_product, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewUint32(js, SDL_GetJoystickProduct(joystick)); +) + +JSC_CCALL(joystick_get_product_version, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewUint32(js, SDL_GetJoystickProductVersion(joystick)); +) + +JSC_CCALL(joystick_get_serial, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + const char *serial = SDL_GetJoystickSerial(joystick); + return serial ? JS_NewString(js, serial) : JS_NULL; +) + +JSC_CCALL(joystick_get_type, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + SDL_JoystickType type = SDL_GetJoystickType(joystick); + return JS_NewString(js, joystick_type_to_string(type)); +) + +JSC_CCALL(joystick_get_id, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewUint32(js, SDL_GetJoystickID(joystick)); +) + +JSC_CCALL(joystick_get_num_axes, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewInt32(js, SDL_GetNumJoystickAxes(joystick)); +) + +JSC_CCALL(joystick_get_num_balls, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewInt32(js, SDL_GetNumJoystickBalls(joystick)); +) + +JSC_CCALL(joystick_get_num_hats, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewInt32(js, SDL_GetNumJoystickHats(joystick)); +) + +JSC_CCALL(joystick_get_num_buttons, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + return JS_NewInt32(js, SDL_GetNumJoystickButtons(joystick)); +) + +JSC_CCALL(joystick_get_axis, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int axis; + JS_ToInt32(js, &axis, argv[0]); + return JS_NewInt32(js, SDL_GetJoystickAxis(joystick, axis)); +) + +JSC_CCALL(joystick_get_ball, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int ball; + JS_ToInt32(js, &ball, argv[0]); + int dx, dy; + if (!SDL_GetJoystickBall(joystick, ball, &dx, &dy)) return JS_NULL; + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "dx", JS_NewInt32(js, dx)); + JS_SetPropertyStr(js, result, "dy", JS_NewInt32(js, dy)); + return result; +) + +JSC_CCALL(joystick_get_hat, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int hat; + JS_ToInt32(js, &hat, argv[0]); + return JS_NewUint32(js, SDL_GetJoystickHat(joystick, hat)); +) + +JSC_CCALL(joystick_get_button, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int button; + JS_ToInt32(js, &button, argv[0]); + return JS_NewBool(js, SDL_GetJoystickButton(joystick, button)); +) + +JSC_CCALL(joystick_rumble, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + uint32_t low_freq, high_freq, duration_ms; + JS_ToUint32(js, &low_freq, argv[0]); + JS_ToUint32(js, &high_freq, argv[1]); + JS_ToUint32(js, &duration_ms, argv[2]); + return JS_NewBool(js, SDL_RumbleJoystick(joystick, low_freq, high_freq, duration_ms)); +) + +JSC_CCALL(joystick_rumble_triggers, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + uint32_t left, right, duration_ms; + JS_ToUint32(js, &left, argv[0]); + JS_ToUint32(js, &right, argv[1]); + JS_ToUint32(js, &duration_ms, argv[2]); + return JS_NewBool(js, SDL_RumbleJoystickTriggers(joystick, left, right, duration_ms)); +) + +JSC_CCALL(joystick_set_led, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int r, g, b; + JS_ToInt32(js, &r, argv[0]); + JS_ToInt32(js, &g, argv[1]); + JS_ToInt32(js, &b, argv[2]); + return JS_NewBool(js, SDL_SetJoystickLED(joystick, r, g, b)); +) + +JSC_CCALL(joystick_get_connection_state, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + SDL_JoystickConnectionState state = SDL_GetJoystickConnectionState(joystick); + return JS_NewString(js, connection_state_to_string(state)); +) + +JSC_CCALL(joystick_get_power_info, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + int percent; + SDL_PowerState state = SDL_GetJoystickPowerInfo(joystick, &percent); + + JSValue result = JS_NewObject(js); + const char *state_str; + switch (state) { + case SDL_POWERSTATE_ON_BATTERY: state_str = "on_battery"; break; + case SDL_POWERSTATE_NO_BATTERY: state_str = "no_battery"; break; + case SDL_POWERSTATE_CHARGING: state_str = "charging"; break; + case SDL_POWERSTATE_CHARGED: state_str = "charged"; break; + default: state_str = "unknown"; break; + } + JS_SetPropertyStr(js, result, "state", JS_NewString(js, state_str)); + JS_SetPropertyStr(js, result, "percent", JS_NewInt32(js, percent)); + return result; +) + +JSC_CCALL(joystick_close, + SDL_Joystick *joystick = js2SDL_Joystick(js, self); + SDL_CloseJoystick(joystick); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Joystick_funcs[] = { + MIST_FUNC_DEF(joystick, get_name, 0), + MIST_FUNC_DEF(joystick, get_path, 0), + MIST_FUNC_DEF(joystick, get_player_index, 0), + MIST_FUNC_DEF(joystick, set_player_index, 1), + MIST_FUNC_DEF(joystick, get_guid, 0), + MIST_FUNC_DEF(joystick, get_vendor, 0), + MIST_FUNC_DEF(joystick, get_product, 0), + MIST_FUNC_DEF(joystick, get_product_version, 0), + MIST_FUNC_DEF(joystick, get_serial, 0), + MIST_FUNC_DEF(joystick, get_type, 0), + MIST_FUNC_DEF(joystick, get_id, 0), + MIST_FUNC_DEF(joystick, get_num_axes, 0), + MIST_FUNC_DEF(joystick, get_num_balls, 0), + MIST_FUNC_DEF(joystick, get_num_hats, 0), + MIST_FUNC_DEF(joystick, get_num_buttons, 0), + MIST_FUNC_DEF(joystick, get_axis, 1), + MIST_FUNC_DEF(joystick, get_ball, 1), + MIST_FUNC_DEF(joystick, get_hat, 1), + MIST_FUNC_DEF(joystick, get_button, 1), + MIST_FUNC_DEF(joystick, rumble, 3), + MIST_FUNC_DEF(joystick, rumble_triggers, 3), + MIST_FUNC_DEF(joystick, set_led, 3), + MIST_FUNC_DEF(joystick, get_connection_state, 0), + MIST_FUNC_DEF(joystick, get_power_info, 0), + MIST_FUNC_DEF(joystick, close, 0), +}; + +static const JSCFunctionListEntry js_joystick_funcs[] = { + MIST_FUNC_DEF(joystick, lock, 0), + MIST_FUNC_DEF(joystick, unlock, 0), + MIST_FUNC_DEF(joystick, has, 0), + MIST_FUNC_DEF(joystick, get_joysticks, 0), + MIST_FUNC_DEF(joystick, get_name_for_id, 1), + MIST_FUNC_DEF(joystick, get_path_for_id, 1), + MIST_FUNC_DEF(joystick, get_player_index_for_id, 1), + MIST_FUNC_DEF(joystick, get_guid_for_id, 1), + MIST_FUNC_DEF(joystick, get_vendor_for_id, 1), + MIST_FUNC_DEF(joystick, get_product_for_id, 1), + MIST_FUNC_DEF(joystick, get_product_version_for_id, 1), + MIST_FUNC_DEF(joystick, get_type_for_id, 1), + MIST_FUNC_DEF(joystick, open, 1), + MIST_FUNC_DEF(joystick, get_from_id, 1), + MIST_FUNC_DEF(joystick, get_from_player_index, 1), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_JOYSTICK); + QJSCLASSPREP_FUNCS(SDL_Joystick); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_joystick_funcs, countof(js_joystick_funcs)); + + // Export axis constants + JS_SetPropertyStr(js, ret, "AXIS_MAX", JS_NewInt32(js, SDL_JOYSTICK_AXIS_MAX)); + JS_SetPropertyStr(js, ret, "AXIS_MIN", JS_NewInt32(js, SDL_JOYSTICK_AXIS_MIN)); + + return ret; +) diff --git a/keyboard.c b/keyboard.c new file mode 100644 index 0000000..dee9091 --- /dev/null +++ b/keyboard.c @@ -0,0 +1,143 @@ +#include "cell.h" +#include + +// SDL_HasKeyboard() -> bool +JSC_CCALL(keyboard_has, + return JS_NewBool(js, SDL_HasKeyboard()); +) + +// SDL_GetKeyboards() -> array of keyboard IDs +JSC_CCALL(keyboard_get_keyboards, + int count = 0; + SDL_KeyboardID *keyboards = SDL_GetKeyboards(&count); + if (!keyboards) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, keyboards[i])); + } + SDL_free(keyboards); + return arr; +) + +// SDL_GetKeyboardNameForID(id) -> string +JSC_SCALL(keyboard_get_name, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetKeyboardNameForID(id); + return name ? JS_NewString(js, name) : JS_NewString(js, ""); +) + +// SDL_GetKeyboardState() -> object with key states +JSC_CCALL(keyboard_get_state, + int numkeys = 0; + const bool *state = SDL_GetKeyboardState(&numkeys); + if (!state) return JS_NULL; + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < numkeys; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewBool(js, state[i])); + } + return arr; +) + +// SDL_ResetKeyboard() +JSC_CCALL(keyboard_reset, + SDL_ResetKeyboard(); + return JS_NULL; +) + +// SDL_GetModState() -> number (bitmask) +JSC_CCALL(keyboard_get_mod_state, + return JS_NewUint32(js, SDL_GetModState()); +) + +// SDL_SetModState(modstate) +JSC_CCALL(keyboard_set_mod_state, + uint32_t modstate; + JS_ToUint32(js, &modstate, argv[0]); + SDL_SetModState((SDL_Keymod)modstate); + return JS_NULL; +) + +// SDL_GetKeyFromScancode(scancode, modstate, key_event) -> keycode +JSC_CCALL(keyboard_get_key_from_scancode, + int scancode; + uint32_t modstate = 0; + int key_event = 0; + JS_ToInt32(js, &scancode, argv[0]); + if (argc > 1) JS_ToUint32(js, &modstate, argv[1]); + if (argc > 2) key_event = JS_ToBool(js, argv[2]); + return JS_NewUint32(js, SDL_GetKeyFromScancode((SDL_Scancode)scancode, (SDL_Keymod)modstate, key_event)); +) + +// SDL_GetScancodeFromKey(key) -> scancode +JSC_CCALL(keyboard_get_scancode_from_key, + uint32_t key; + JS_ToUint32(js, &key, argv[0]); + SDL_Keymod modstate; + SDL_Scancode scancode = SDL_GetScancodeFromKey((SDL_Keycode)key, &modstate); + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "scancode", JS_NewInt32(js, scancode)); + JS_SetPropertyStr(js, result, "modstate", JS_NewUint32(js, modstate)); + return result; +) + +// SDL_GetScancodeName(scancode) -> string +JSC_CCALL(keyboard_get_scancode_name, + int scancode; + JS_ToInt32(js, &scancode, argv[0]); + const char *name = SDL_GetScancodeName((SDL_Scancode)scancode); + return JS_NewString(js, name ? name : ""); +) + +// SDL_GetScancodeFromName(name) -> scancode +JSC_SCALL(keyboard_get_scancode_from_name, + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + SDL_Scancode scancode = SDL_GetScancodeFromName(name); + JS_FreeCString(js, name); + return JS_NewInt32(js, scancode); +) + +// SDL_GetKeyName(key) -> string +JSC_CCALL(keyboard_get_key_name, + uint32_t key; + JS_ToUint32(js, &key, argv[0]); + const char *name = SDL_GetKeyName((SDL_Keycode)key); + return JS_NewString(js, name ? name : ""); +) + +// SDL_GetKeyFromName(name) -> keycode +JSC_SCALL(keyboard_get_key_from_name, + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + SDL_Keycode key = SDL_GetKeyFromName(name); + JS_FreeCString(js, name); + return JS_NewUint32(js, key); +) + +// SDL_HasScreenKeyboardSupport() -> bool +JSC_CCALL(keyboard_has_screen_support, + return JS_NewBool(js, SDL_HasScreenKeyboardSupport()); +) + +static const JSCFunctionListEntry js_keyboard_funcs[] = { + MIST_FUNC_DEF(keyboard, has, 0), + MIST_FUNC_DEF(keyboard, get_keyboards, 0), + MIST_FUNC_DEF(keyboard, get_name, 1), + MIST_FUNC_DEF(keyboard, get_state, 0), + MIST_FUNC_DEF(keyboard, reset, 0), + MIST_FUNC_DEF(keyboard, get_mod_state, 0), + MIST_FUNC_DEF(keyboard, set_mod_state, 1), + MIST_FUNC_DEF(keyboard, get_key_from_scancode, 3), + MIST_FUNC_DEF(keyboard, get_scancode_from_key, 1), + MIST_FUNC_DEF(keyboard, get_scancode_name, 1), + MIST_FUNC_DEF(keyboard, get_scancode_from_name, 1), + MIST_FUNC_DEF(keyboard, get_key_name, 1), + MIST_FUNC_DEF(keyboard, get_key_from_name, 1), + MIST_FUNC_DEF(keyboard, has_screen_support, 0), +}; + +CELL_USE_FUNCS(js_keyboard_funcs) diff --git a/mouse.c b/mouse.c new file mode 100644 index 0000000..d8dc811 --- /dev/null +++ b/mouse.c @@ -0,0 +1,109 @@ +#include "cell.h" +#include + +// SDL_HasMouse() -> bool +JSC_CCALL(mouse_has, + return JS_NewBool(js, SDL_HasMouse()); +) + +// SDL_GetMice() -> array of mouse IDs +JSC_CCALL(mouse_get_mice, + int count = 0; + SDL_MouseID *mice = SDL_GetMice(&count); + if (!mice) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, mice[i])); + } + SDL_free(mice); + return arr; +) + +// SDL_GetMouseNameForID(id) -> string +JSC_CCALL(mouse_get_name, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetMouseNameForID(id); + return name ? JS_NewString(js, name) : JS_NewString(js, ""); +) + +// SDL_GetMouseState() -> {x, y, buttons} +JSC_CCALL(mouse_get_state, + float x, y; + SDL_MouseButtonFlags buttons = SDL_GetMouseState(&x, &y); + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, result, "y", JS_NewFloat64(js, y)); + JS_SetPropertyStr(js, result, "buttons", JS_NewUint32(js, buttons)); + return result; +) + +// SDL_GetGlobalMouseState() -> {x, y, buttons} +JSC_CCALL(mouse_get_global_state, + float x, y; + SDL_MouseButtonFlags buttons = SDL_GetGlobalMouseState(&x, &y); + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, result, "y", JS_NewFloat64(js, y)); + JS_SetPropertyStr(js, result, "buttons", JS_NewUint32(js, buttons)); + return result; +) + +// SDL_GetRelativeMouseState() -> {x, y, buttons} +JSC_CCALL(mouse_get_relative_state, + float x, y; + SDL_MouseButtonFlags buttons = SDL_GetRelativeMouseState(&x, &y); + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "x", JS_NewFloat64(js, x)); + JS_SetPropertyStr(js, result, "y", JS_NewFloat64(js, y)); + JS_SetPropertyStr(js, result, "buttons", JS_NewUint32(js, buttons)); + return result; +) + +// SDL_WarpMouseGlobal(x, y) -> bool +JSC_CCALL(mouse_warp_global, + float x = js2number(js, argv[0]); + float y = js2number(js, argv[1]); + return JS_NewBool(js, SDL_WarpMouseGlobal(x, y)); +) + +// SDL_CaptureMouse(enabled) -> bool +JSC_CCALL(mouse_capture, + bool enabled = JS_ToBool(js, argv[0]); + return JS_NewBool(js, SDL_CaptureMouse(enabled)); +) + +// SDL_ShowCursor() -> bool +JSC_CCALL(mouse_show_cursor, + return JS_NewBool(js, SDL_ShowCursor()); +) + +// SDL_HideCursor() -> bool +JSC_CCALL(mouse_hide_cursor, + return JS_NewBool(js, SDL_HideCursor()); +) + +// SDL_CursorVisible() -> bool +JSC_CCALL(mouse_cursor_visible, + return JS_NewBool(js, SDL_CursorVisible()); +) + +static const JSCFunctionListEntry js_mouse_funcs[] = { + MIST_FUNC_DEF(mouse, has, 0), + MIST_FUNC_DEF(mouse, get_mice, 0), + MIST_FUNC_DEF(mouse, get_name, 1), + MIST_FUNC_DEF(mouse, get_state, 0), + MIST_FUNC_DEF(mouse, get_global_state, 0), + MIST_FUNC_DEF(mouse, get_relative_state, 0), + MIST_FUNC_DEF(mouse, warp_global, 2), + MIST_FUNC_DEF(mouse, capture, 1), + MIST_FUNC_DEF(mouse, show_cursor, 0), + MIST_FUNC_DEF(mouse, hide_cursor, 0), + MIST_FUNC_DEF(mouse, cursor_visible, 0), +}; + +CELL_USE_FUNCS(js_mouse_funcs) diff --git a/pen.c b/pen.c new file mode 100644 index 0000000..96d5b5b --- /dev/null +++ b/pen.c @@ -0,0 +1,83 @@ +#include "cell.h" +#include + +// Pen axis enum to string +static const char *pen_axis_to_string(SDL_PenAxis axis) { + switch (axis) { + case SDL_PEN_AXIS_PRESSURE: return "pressure"; + case SDL_PEN_AXIS_XTILT: return "xtilt"; + case SDL_PEN_AXIS_YTILT: return "ytilt"; + case SDL_PEN_AXIS_DISTANCE: return "distance"; + case SDL_PEN_AXIS_ROTATION: return "rotation"; + case SDL_PEN_AXIS_SLIDER: return "slider"; + case SDL_PEN_AXIS_TANGENTIAL_PRESSURE: return "tangential_pressure"; + default: return "unknown"; + } +} + +static SDL_PenAxis string_to_pen_axis(const char *str) { + if (!str) return SDL_PEN_AXIS_PRESSURE; + if (!strcmp(str, "pressure")) return SDL_PEN_AXIS_PRESSURE; + if (!strcmp(str, "xtilt")) return SDL_PEN_AXIS_XTILT; + if (!strcmp(str, "ytilt")) return SDL_PEN_AXIS_YTILT; + if (!strcmp(str, "distance")) return SDL_PEN_AXIS_DISTANCE; + if (!strcmp(str, "rotation")) return SDL_PEN_AXIS_ROTATION; + if (!strcmp(str, "slider")) return SDL_PEN_AXIS_SLIDER; + if (!strcmp(str, "tangential_pressure")) return SDL_PEN_AXIS_TANGENTIAL_PRESSURE; + return SDL_PEN_AXIS_PRESSURE; +} + +// Get pen axis count +JSC_CCALL(pen_get_axis_count, + return JS_NewInt32(js, SDL_PEN_AXIS_COUNT); +) + +// Get pen axis name +JSC_CCALL(pen_get_axis_name, + int axis; + JS_ToInt32(js, &axis, argv[0]); + return JS_NewString(js, pen_axis_to_string((SDL_PenAxis)axis)); +) + +// Get pen axis from name +JSC_SCALL(pen_get_axis_from_name, + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + SDL_PenAxis axis = string_to_pen_axis(name); + JS_FreeCString(js, name); + return JS_NewInt32(js, axis); +) + +// Pen input flags helpers +JSC_CCALL(pen_input_down, + return JS_NewUint32(js, SDL_PEN_INPUT_DOWN); +) + +JSC_CCALL(pen_input_button_1, + return JS_NewUint32(js, SDL_PEN_INPUT_BUTTON_1); +) + +JSC_CCALL(pen_input_button_2, + return JS_NewUint32(js, SDL_PEN_INPUT_BUTTON_2); +) + +JSC_CCALL(pen_input_button_3, + return JS_NewUint32(js, SDL_PEN_INPUT_BUTTON_3); +) + +JSC_CCALL(pen_input_eraser_tip, + return JS_NewUint32(js, SDL_PEN_INPUT_ERASER_TIP); +) + +static const JSCFunctionListEntry js_pen_funcs[] = { + MIST_FUNC_DEF(pen, get_axis_count, 0), + MIST_FUNC_DEF(pen, get_axis_name, 1), + MIST_FUNC_DEF(pen, get_axis_from_name, 1), + MIST_FUNC_DEF(pen, input_down, 0), + MIST_FUNC_DEF(pen, input_button_1, 0), + MIST_FUNC_DEF(pen, input_button_2, 0), + MIST_FUNC_DEF(pen, input_button_3, 0), + MIST_FUNC_DEF(pen, input_eraser_tip, 0), +}; + +CELL_USE_FUNCS(js_pen_funcs) diff --git a/render.c b/render.c new file mode 100644 index 0000000..c520674 --- /dev/null +++ b/render.c @@ -0,0 +1,250 @@ +#include "cell.h" +#include + +#include +#include +#include +#include "sdl.h" +#include + +void SDL_Renderer_free(JSRuntime *rt, SDL_Renderer *renderer) { + if (renderer) SDL_DestroyRenderer(renderer); +} + +void SDL_Texture_free(JSRuntime *rt, SDL_Texture *texture) { + if (texture) SDL_DestroyTexture(texture); +} + +QJSCLASS(SDL_Renderer,) +QJSCLASS(SDL_Texture,) + +JSC_CCALL(SDL_Renderer_clear, + SDL_Renderer *renderer = js2SDL_Renderer(js,self); + SDL_RenderClear(renderer); +) + +JSC_CCALL(SDL_Renderer_present, + SDL_Renderer *ren = js2SDL_Renderer(js,self); + SDL_RenderPresent(ren); +) + +JSC_CCALL(SDL_Renderer_draw_color, + SDL_Renderer *renderer = js2SDL_Renderer(js,self); + colorf color = js2color(js,argv[0]); + SDL_SetRenderDrawColorFloat(renderer, color.x, color.y, color.z, color.w); +) + +JSC_CCALL(SDL_Renderer_rect, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (!JS_IsNull(argv[1])) { + colorf color = js2color(js,argv[1]); + SDL_SetRenderDrawColorFloat(r, color.x, color.y, color.z, color.w); + } + + if (JS_IsArray(js,argv[0])) { + int len = JS_ArrayLength(js,argv[0]); + rect rects[len]; + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js,argv[0],i); + rects[i] = js2rect(js,val); + JS_FreeValue(js,val); + } + SDL_RenderFillRects(r,rects,len); + } else { + rect rect_var = js2rect(js,argv[0]); + SDL_RenderFillRect(r,&rect_var); + } +) + +JSC_CCALL(SDL_Renderer_fillrect, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (!JS_IsNull(argv[1])) { + colorf color = js2color(js,argv[1]); + SDL_SetRenderDrawColorFloat(r, color.x, color.y, color.z, color.w); + } + + if (JS_IsArray(js,argv[0])) { + int len = JS_ArrayLength(js,argv[0]); + rect rects[len]; + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js,argv[0],i); + rects[i] = js2rect(js,val); + JS_FreeValue(js,val); + } + SDL_RenderFillRects(r,rects,len); + } else { + rect rect_var = js2rect(js,argv[0]); + SDL_RenderFillRect(r,&rect_var); + } + return JS_NULL; +) + +JSC_CCALL(renderer_texture, + SDL_Renderer *renderer = js2SDL_Renderer(js,self); + SDL_Texture *tex = js2SDL_Texture(js,argv[0]); + rect dst = js2rect(js,argv[1]); + + if (!JS_IsNull(argv[3])) { + colorf color = js2color(js,argv[3]); + SDL_SetTextureColorModFloat(tex, color.x, color.y, color.z); + SDL_SetTextureAlphaModFloat(tex,color.w); + } + if (JS_IsNull(argv[2])) + SDL_RenderTexture(renderer,tex,NULL,&dst); + else { + rect src = js2rect(js,argv[2]); + SDL_RenderTextureRotated(renderer, tex, &src, &dst, 0, NULL, SDL_FLIP_NONE); + } +) + +JSC_CCALL(renderer_load_texture, + SDL_Renderer *renderer = js2SDL_Renderer(js, self); + SDL_Surface *surf = js2SDL_Surface(js, argv[0]); + SDL_Texture *tex = SDL_CreateTextureFromSurface(renderer, surf); + if (!tex) return JS_ThrowReferenceError(js, "Could not create texture from surface: %s", SDL_GetError()); + ret = SDL_Texture2js(js, tex); + JS_SetPropertyStr(js, ret, "width", number2js(js,tex->w)); + JS_SetPropertyStr(js,ret,"height", number2js(js,tex->h)); +) + +JSC_CCALL(renderer_get_image, + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_Surface *surf; + rect rect; + if (JS_IsNull(argv[0])) + surf = SDL_RenderReadPixels(r,NULL); + else { + rect = js2rect(js,argv[0]); + surf = SDL_RenderReadPixels(r,&rect); + } + if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer"); + return SDL_Surface2js(js,surf); +) + +JSC_CCALL(renderer_line, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (!JS_IsNull(argv[1])) { + colorf color = js2color(js,argv[1]); + SDL_SetRenderDrawColorFloat(r, color.x, color.y, color.z, color.w); + } + + if (JS_IsArray(js,argv[0])) { + int len = JS_ArrayLength(js,argv[0]); + vec2 points[len]; + assert(sizeof(vec2) == sizeof(SDL_FPoint)); + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js,argv[0],i); + points[i] = js2vec2(js,val); + JS_FreeValue(js,val); + } + SDL_RenderLines(r,points,len); + } +) + +JSC_CCALL(renderer_point, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (!JS_IsNull(argv[1])) { + colorf color = js2color(js,argv[1]); + SDL_SetRenderDrawColorFloat(r, color.x, color.y, color.z, color.w); + } + + if (JS_IsArray(js,argv[0])) { + int len = JS_ArrayLength(js,argv[0]); + vec2 points[len]; + assert(sizeof(vec2) ==sizeof(SDL_FPoint)); + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js,argv[0],i); + points[i] = js2vec2(js,val); + JS_FreeValue(js,val); + } + SDL_RenderPoints(r, points, len); + return JS_NULL; + } + + vec2 point = js2vec2(js,argv[0]); + SDL_RenderPoint(r,point.x,point.y); +) + +JSC_CCALL(renderer_logical_size, + SDL_Renderer *r = js2SDL_Renderer(js,self); + vec2 v = js2vec2(js,argv[0]); + SDL_SetRenderLogicalPresentation(r,v.x,v.y,SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); +) + +JSC_CCALL(renderer_viewport, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(argv[0])) + SDL_SetRenderViewport(r,NULL); + else { + rect view = js2rect(js,argv[0]); + SDL_SetRenderViewport(r,&view); + } + return JS_NULL; +) + +JSC_CCALL(renderer_clip, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(argv[0])) + SDL_SetRenderClipRect(r,NULL); + else { + rect view = js2rect(js,argv[0]); + SDL_SetRenderClipRect(r,&view); + } +) + +JSC_CCALL(renderer_scale, + SDL_Renderer *r = js2SDL_Renderer(js,self); + vec2 v = js2vec2(js,argv[0]); + SDL_SetRenderScale(r, v.x, v.y); +) + +JSC_CCALL(renderer_vsync, + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_SetRenderVSync(r,js2number(js,argv[0])); +) + +JSC_CCALL(renderer_target, + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(argv[0])) + SDL_SetRenderTarget(r, NULL); + else { + SDL_Texture *tex = js2SDL_Texture(js,argv[0]); + SDL_SetRenderTarget(r,tex); + } +) + +static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { + JS_CFUNC_DEF("clear", 0, js_SDL_Renderer_clear), + JS_CFUNC_DEF("present", 0, js_SDL_Renderer_present), + JS_CFUNC_DEF("draw_color", 1, js_SDL_Renderer_draw_color), + JS_CFUNC_DEF("rect", 2, js_SDL_Renderer_rect), + JS_CFUNC_DEF("fillrect", 2, js_SDL_Renderer_fillrect), + JS_CFUNC_DEF("line", 2, js_renderer_line), + JS_CFUNC_DEF("point", 2, js_renderer_point), + JS_CFUNC_DEF("texture", 4, js_renderer_texture), + JS_CFUNC_DEF("get_image", 1, js_renderer_get_image), + JS_CFUNC_DEF("scale", 1, js_renderer_scale), + JS_CFUNC_DEF("logical_size", 1, js_renderer_logical_size), + JS_CFUNC_DEF("viewport", 1, js_renderer_viewport), + JS_CFUNC_DEF("clip", 1, js_renderer_clip), + JS_CFUNC_DEF("vsync", 1, js_renderer_vsync), + JS_CFUNC_DEF("target", 1, js_renderer_target), + JS_CFUNC_DEF("load_texture", 1, js_renderer_load_texture), +}; + +JSC_CCALL(SDL_Renderer_constructor, + if (argc < 1) return JS_ThrowTypeError(js, "Renderer constructor requires a window argument"); + SDL_Window *win = js2SDL_Window(js, argv[0]); + SDL_Renderer *r = SDL_CreateRenderer(win, NULL); + if (!r) return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError()); + SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); + return SDL_Renderer2js(js, r); +) + +CELL_USE_INIT( + SDL_Init(SDL_INIT_VIDEO); + JSValue renderer_ctor = QJSCLASSPREP_FUNCS_CTOR(SDL_Renderer,1); + QJSCLASSPREP_NO_FUNCS(SDL_Texture) + + return renderer_ctor; +) diff --git a/sdl.c b/sdl.c new file mode 100644 index 0000000..e75607d --- /dev/null +++ b/sdl.c @@ -0,0 +1,333 @@ +#include "cell.h" +#include "sdl.h" +#include + +colorf js2color(JSContext *js,JSValue v) { + if (JS_IsNull(v)) return (colorf){1,1,1,1}; + + colorf color = {1,1,1,1}; // Default to white + + if (JS_IsArray(js, v)) { + // Handle array format: [r, g, b, a] + JSValue c[4]; + for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i); + + color.x = js2number(js,c[0]); + color.y = js2number(js,c[1]); + color.z = js2number(js,c[2]); + color.w = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]); + + for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]); + } else if (JS_IsObject(v)) { + JS_GETPROP(js, color.x, v, r, number) + JS_GETPROP(js, color.y, v, g, number) + JS_GETPROP(js, color.z, v, b, number) + JS_GETPROP(js, color.w, v, a, number) + } + + return color; +} + +JSValue color2js(JSContext *js, colorf color) +{ + JSValue arr = JS_NewArray(js); + JS_SetPropertyUint32(js, arr,0,number2js(js,(double)color.x)); + JS_SetPropertyUint32(js, arr,1,number2js(js,(double)color.y)); + JS_SetPropertyUint32(js, arr,2,number2js(js,(double)color.z)); + JS_SetPropertyUint32(js, arr,3,number2js(js,(double)color.w)); + return arr; +} + +vec2 js2vec2(JSContext *js,JSValue v) +{ + vec2 v2; + + // Check if it's an array + if (JS_IsArray(js, v)) { + { JSValue val = JS_GetPropertyUint32(js,v,0); v2.x = js2number(js, val); JS_FreeValue(js,val); } + { JSValue val = JS_GetPropertyUint32(js,v,1); v2.y = js2number(js, val); JS_FreeValue(js,val); } + } else { + // Try to get x,y properties from object + JSValue x_val = JS_GetPropertyStr(js, v, "x"); + JSValue y_val = JS_GetPropertyStr(js, v, "y"); + + v2.x = js2number(js, x_val); + v2.y = js2number(js, y_val); + + JS_FreeValue(js, x_val); + JS_FreeValue(js, y_val); + } + + return v2; +} + +vec3 js2vec3(JSContext *js,JSValue v) +{ + vec3 v3; + { JSValue val = JS_GetPropertyUint32(js, v,0); v3.x = js2number(js, val); JS_FreeValue(js,val); } + { JSValue val = JS_GetPropertyUint32(js, v,1); v3.y = js2number(js, val); JS_FreeValue(js,val); } + { JSValue val = JS_GetPropertyUint32(js, v,2); v3.z = js2number(js, val); JS_FreeValue(js,val); } + return v3; +} + +float *js2floats(JSContext *js, JSValue v, size_t *len) +{ + *len = JS_ArrayLength(js,v); + float *arr = malloc(sizeof(float)* *len); + for (int i = 0; i < *len; i++) + { JSValue val = JS_GetPropertyUint32(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); } + return arr; +} + +double *js2doubles(JSContext *js, JSValue v, size_t *len) +{ + *len = JS_ArrayLength(js,v); + double *arr = malloc(sizeof(double)* *len); + for (int i = 0; i < *len; i++) + { JSValue val = JS_GetPropertyUint32(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); } + return arr; +} + +vec3 js2vec3f(JSContext *js, JSValue v) +{ + vec3 vec; + if (JS_IsArray(js, v)) + return js2vec3(js,v); + else + vec.x = vec.y = vec.z = js2number(js,v); + return vec; +} + +JSValue vec32js(JSContext *js, vec3 v) +{ + JSValue array = JS_NewArray(js); + JS_SetPropertyUint32(js, array,0,number2js(js,v.x)); + JS_SetPropertyUint32(js, array,1,number2js(js,v.y)); + JS_SetPropertyUint32(js, array,2,number2js(js,v.z)); + return array; +} + +JSValue vec3f2js(JSContext *js, vec3 v) +{ + return vec32js(js,v); +} + +JSValue quat2js(JSContext *js, quat q) +{ + JSValue arr = JS_NewArray(js); + JS_SetPropertyUint32(js, arr, 0, number2js(js,q.x)); + JS_SetPropertyUint32(js, arr,1,number2js(js,q.y)); + JS_SetPropertyUint32(js, arr,2,number2js(js,q.z)); + JS_SetPropertyUint32(js, arr,3,number2js(js,q.w)); + return arr; +} + +vec4 js2vec4(JSContext *js, JSValue v) +{ + vec4_union v4; + for (int i = 0; i < 4; i++) + { JSValue val = JS_GetPropertyUint32(js, v,i); v4.e[i] = js2number(js, val); JS_FreeValue(js,val); } + return (vec4){v4.x, v4.y, v4.z, v4.w}; +} + +double arr_vec_length(JSContext *js,JSValue v) +{ + int len = JS_ArrayLength(js,v); + switch(len) { + case 2: { + vec2 v2 = js2vec2(js,v); + return sqrt(v2.x*v2.x + v2.y*v2.y); + } + case 3: { + vec3 v3 = js2vec3(js,v); + return sqrt(v3.x*v3.x + v3.y*v3.y + v3.z*v3.z); + } + case 4: { + vec4 v4 = js2vec4(js,v); + return sqrt(v4.x*v4.x + v4.y*v4.y + v4.z*v4.z + v4.w*v4.w); + } + } + + double sum = 0; + for (int i = 0; i < len; i++) + { JSValue val = JS_GetPropertyUint32(js, v, i); double num = js2number(js, val); JS_FreeValue(js,val); sum += pow(num, 2); } + + return sqrt(sum); +} + +quat js2quat(JSContext *js,JSValue v) +{ + vec4_union v4; + for (int i = 0; i < 4; i++) + { JSValue val = JS_GetPropertyUint32(js, v,i); v4.e[i] = js2number(js, val); JS_FreeValue(js,val); } + return (quat){v4.x, v4.y, v4.z, v4.w}; +} + +JSValue vec42js(JSContext *js, vec4 v) +{ + JSValue array = JS_NewArray(js); + JS_SetPropertyUint32(js, array,0,number2js(js,v.x)); + JS_SetPropertyUint32(js, array,1,number2js(js,v.y)); + JS_SetPropertyUint32(js, array,2,number2js(js,v.z)); + JS_SetPropertyUint32(js, array,3,number2js(js,v.w)); + return array; +} + +vec2 *js2cpvec2arr(JSContext *js,JSValue v) { + int n = JS_ArrayLength(js,v); + vec2 *arr = malloc(sizeof(vec2) * n); + + for (int i = 0; i < n; i++) { + JSValue ii = JS_GetPropertyUint32(js,v,i); + arr[i] = js2vec2(js,ii); + JS_FreeValue(js,ii); + } + + return arr; +} + +rect js2rect(JSContext *js,JSValue v) { + if (JS_IsNull(v)) return (rect){0,0,1,1}; + rect rect; + JS_GETATOM(js,rect.x,v,x,number) + JS_GETATOM(js,rect.y,v,y,number) + JS_GETATOM(js,rect.w,v,width,number) + JS_GETATOM(js,rect.h,v,height,number) + float anchor_x, anchor_y; + JS_GETATOM(js, anchor_x, v, anchor_x, number) + JS_GETATOM(js, anchor_y, v, anchor_y, number) + + rect.y -= anchor_y*rect.h; + rect.x -= anchor_x*rect.w; + + return rect; +} + +static JSValue floats2array(JSContext *js, float *vals, size_t len) { + JSValue arr = JS_NewArray(js); + for (size_t i = 0; i < len; i++) { + JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i])); + } + return arr; +} + +lrtb js2lrtb(JSContext *js, JSValue v) +{ + lrtb ret = {0}; + JS_GETATOM(js,ret.l,v,l,number) + JS_GETATOM(js,ret.r,v,r,number) + JS_GETATOM(js,ret.b,v,b,number) + JS_GETATOM(js,ret.t,v,t,number) + return ret; +} + +JSValue vec22js(JSContext *js,vec2 v) +{ + JSValue array = JS_NewArray(js); + JS_SetPropertyUint32(js, array,0,number2js(js,v.x)); + JS_SetPropertyUint32(js, array,1,number2js(js,v.y)); + return array; +} + +JSValue vecarr2js(JSContext *js,vec2 *points, int n) { + JSValue array = JS_NewArray(js); + for (int i = 0; i < n; i++) + JS_SetPropertyUint32(js, array,i,vec22js(js,points[i])); + + return array; +} + +JSValue rect2js(JSContext *js,rect rect) { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "x", number2js(js, rect.x)); + JS_SetPropertyStr(js, obj, "y", number2js(js, rect.y)); + JS_SetPropertyStr(js, obj, "width", number2js(js, rect.w)); + JS_SetPropertyStr(js, obj, "height", number2js(js, rect.h)); + return obj; +} + +float *rgba2floats(float *r, struct rgba c) +{ + r[0] = (float)c.r / RGBA_MAX; + r[1] = (float)c.g / RGBA_MAX; + r[2] = (float)c.b / RGBA_MAX; + r[3] = (float)c.a / RGBA_MAX; + return r; +} + +JSValue angle2js(JSContext *js, double angle) { + return number2js(js, angle); +} + +double js2angle(JSContext *js, JSValue v) { + return js2number(js, v); +} + +// Pixel format enum conversion using the new system +ENUM_MAPPING_TABLE(SDL_PixelFormat) = { + {SDL_PIXELFORMAT_UNKNOWN, "unknown"}, + {SDL_PIXELFORMAT_INDEX1LSB, "index1lsb"}, + {SDL_PIXELFORMAT_INDEX1MSB, "index1msb"}, + {SDL_PIXELFORMAT_INDEX2LSB, "index2lsb"}, + {SDL_PIXELFORMAT_INDEX2MSB, "index2msb"}, + {SDL_PIXELFORMAT_INDEX4LSB, "index4lsb"}, + {SDL_PIXELFORMAT_INDEX4MSB, "index4msb"}, + {SDL_PIXELFORMAT_INDEX8, "index8"}, + {SDL_PIXELFORMAT_RGB332, "rgb332"}, + {SDL_PIXELFORMAT_XRGB4444, "xrgb4444"}, + {SDL_PIXELFORMAT_XBGR4444, "xbgr4444"}, + {SDL_PIXELFORMAT_XRGB1555, "xrgb1555"}, + {SDL_PIXELFORMAT_XBGR1555, "xbgr1555"}, + {SDL_PIXELFORMAT_ARGB4444, "argb4444"}, + {SDL_PIXELFORMAT_RGBA4444, "rgba4444"}, + {SDL_PIXELFORMAT_ABGR4444, "abgr4444"}, + {SDL_PIXELFORMAT_BGRA4444, "bgra4444"}, + {SDL_PIXELFORMAT_ARGB1555, "argb1555"}, + {SDL_PIXELFORMAT_RGBA5551, "rgba5551"}, + {SDL_PIXELFORMAT_ABGR1555, "abgr1555"}, + {SDL_PIXELFORMAT_BGRA5551, "bgra5551"}, + {SDL_PIXELFORMAT_RGB565, "rgb565"}, + {SDL_PIXELFORMAT_BGR565, "bgr565"}, + {SDL_PIXELFORMAT_RGB24, "rgb24"}, + {SDL_PIXELFORMAT_BGR24, "bgr24"}, + {SDL_PIXELFORMAT_XRGB8888, "xrgb8888"}, + {SDL_PIXELFORMAT_RGBX8888, "rgbx8888"}, + {SDL_PIXELFORMAT_XBGR8888, "xbgr8888"}, + {SDL_PIXELFORMAT_BGRX8888, "bgrx8888"}, + {SDL_PIXELFORMAT_ARGB8888, "argb8888"}, + {SDL_PIXELFORMAT_RGBA8888, "rgba8888"}, + {SDL_PIXELFORMAT_ABGR8888, "abgr8888"}, + {SDL_PIXELFORMAT_BGRA8888, "bgra8888"}, + {SDL_PIXELFORMAT_XRGB2101010, "xrgb2101010"}, + {SDL_PIXELFORMAT_XBGR2101010, "xbgr2101010"}, + {SDL_PIXELFORMAT_ARGB2101010, "argb2101010"}, + {SDL_PIXELFORMAT_ABGR2101010, "abgr2101010"}, + {SDL_PIXELFORMAT_RGB48, "rgb48"}, + {SDL_PIXELFORMAT_BGR48, "bgr48"}, + {SDL_PIXELFORMAT_RGBA64, "rgba64"}, + {SDL_PIXELFORMAT_ARGB64, "argb64"}, + {SDL_PIXELFORMAT_BGRA64, "bgra64"}, + {SDL_PIXELFORMAT_ABGR64, "abgr64"}, + {SDL_PIXELFORMAT_RGB48_FLOAT, "rgb48_float"}, + {SDL_PIXELFORMAT_BGR48_FLOAT, "bgr48_float"}, + {SDL_PIXELFORMAT_RGBA64_FLOAT, "rgba64_float"}, + {SDL_PIXELFORMAT_ARGB64_FLOAT, "argb64_float"}, + {SDL_PIXELFORMAT_BGRA64_FLOAT, "bgra64_float"}, + {SDL_PIXELFORMAT_ABGR64_FLOAT, "abgr64_float"}, + {SDL_PIXELFORMAT_RGB96_FLOAT, "rgb96_float"}, + {SDL_PIXELFORMAT_BGR96_FLOAT, "bgr96_float"}, + {SDL_PIXELFORMAT_RGBA128_FLOAT, "rgba128_float"}, + {SDL_PIXELFORMAT_ARGB128_FLOAT, "argb128_float"}, + {SDL_PIXELFORMAT_BGRA128_FLOAT, "bgra128_float"}, + {SDL_PIXELFORMAT_ABGR128_FLOAT, "abgr128_float"}, + {SDL_PIXELFORMAT_YV12, "yv12"}, + {SDL_PIXELFORMAT_IYUV, "iyuv"}, + {SDL_PIXELFORMAT_YUY2, "yuy2"}, + {SDL_PIXELFORMAT_UYVY, "uyvy"}, + {SDL_PIXELFORMAT_YVYU, "yvyu"}, + {SDL_PIXELFORMAT_NV12, "nv12"}, + {SDL_PIXELFORMAT_NV21, "nv21"}, + {SDL_PIXELFORMAT_P010, "p010"}, + {SDL_PIXELFORMAT_RGBA32, "rgba32"} +}; +JS2ENUM(SDL_PixelFormat) diff --git a/sdl.h b/sdl.h new file mode 100644 index 0000000..bde49e0 --- /dev/null +++ b/sdl.h @@ -0,0 +1,109 @@ +#ifndef QJS_SDL_H +#define QJS_SDL_H + +#include +#include "cell.h" + +// Simple vector types to replace HandmadeMath dependency +typedef struct { + float x, y; +} vec2; + +typedef struct { + float x, y, z; +} vec3; + +typedef struct { + float x, y, z, w; +} vec4; + +typedef struct { + float x, y, z, w; +} quat; + +typedef union { + struct { float x, y, z, w; }; + float e[4]; +} vec4_union; + +SDL_Window *js2SDL_Window(JSContext *js, JSValue v); +JSValue SDL_Window2js(JSContext *js, SDL_Window *w); + +SDL_PixelFormat str2pixelformat(const char *str); +SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v); +JSValue pixelformat2js(JSContext *js, SDL_PixelFormat format); +const char *pixelformat2str(SDL_PixelFormat format); + +// New enum system functions +int js2SDL_PixelFormat(JSContext *js, JSValue v); +JSValue SDL_PixelFormat2js(JSContext *js, int enumval); +SDL_Colorspace str2colorspace(const char *str); +SDL_Colorspace js2colorspace(JSContext *js, JSValue v); +JSValue colorspace2js(JSContext *js, SDL_Colorspace colorspace); +const char *colorspace2str(SDL_Colorspace colorspace); + +// SDL Scale Mode functions +SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v); +JSValue SDL_ScaleMode2js(JSContext *js, SDL_ScaleMode mode); + +// Surface type +typedef struct SDL_Surface SDL_Surface; +SDL_Surface *js2SDL_Surface(JSContext *js, JSValue v); +JSValue SDL_Surface2js(JSContext *js, SDL_Surface *s); + +struct lrtb { + float l; + float r; + float t; + float b; +}; +typedef struct lrtb lrtb; + +lrtb js2lrtb(JSContext *js, JSValue v); +JSValue lrtb2js(JSContext *js, lrtb r); + +struct text_vert { + vec2 pos; + vec2 uv; + vec4 color; +}; + +typedef struct text_vert text_vert; + +JSValue quads_to_mesh(JSContext *js, text_vert *argv); + +typedef vec4 colorf; +typedef SDL_FRect rect; +typedef SDL_Rect irect; + +// Common conversion functions used across modules +JSValue rect2js(JSContext *js, rect r); +JSValue vec22js(JSContext *js, vec2 v); +JSValue vec32js(JSContext *js, vec3 v); +JSValue vec42js(JSContext *js, vec4 v); +JSValue quat2js(JSContext *js, quat q); +JSValue color2js(JSContext *js, colorf c); +JSValue angle2js(JSContext *js, double a); + +rect js2rect(JSContext *js, JSValue v); +vec2 js2vec2(JSContext *js, JSValue v); +vec3 js2vec3(JSContext *js, JSValue v); +vec4 js2vec4(JSContext *js, JSValue v); +quat js2quat(JSContext *js, JSValue v); +colorf js2color(JSContext *js, JSValue v); +double js2angle(JSContext *js, JSValue v); + +#define RGBA_MAX 255 + +struct rgba { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +}; + +typedef struct rgba rgba; + +float *rgba2floats(float *r, struct rgba c); + +#endif diff --git a/sensor.c b/sensor.c new file mode 100644 index 0000000..be3de92 --- /dev/null +++ b/sensor.c @@ -0,0 +1,156 @@ +#include "cell.h" +#include + +// Sensor type enum to string +static const char *sensor_type_to_string(SDL_SensorType type) { + switch (type) { + case SDL_SENSOR_ACCEL: return "accel"; + case SDL_SENSOR_GYRO: return "gyro"; + case SDL_SENSOR_ACCEL_L: return "accel_l"; + case SDL_SENSOR_GYRO_L: return "gyro_l"; + case SDL_SENSOR_ACCEL_R: return "accel_r"; + case SDL_SENSOR_GYRO_R: return "gyro_r"; + case SDL_SENSOR_UNKNOWN: return "unknown"; + default: return "invalid"; + } +} + +// SDL_Sensor class +void SDL_Sensor_free(JSRuntime *rt, SDL_Sensor *sensor) { + if (sensor) SDL_CloseSensor(sensor); +} + +QJSCLASS(SDL_Sensor,) + +// SDL_GetSensors() -> array of sensor IDs +JSC_CCALL(sensor_get_sensors, + int count = 0; + SDL_SensorID *sensors = SDL_GetSensors(&count); + if (!sensors) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, sensors[i])); + } + SDL_free(sensors); + return arr; +) + +// SDL_GetSensorNameForID(id) -> string +JSC_CCALL(sensor_get_name_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + const char *name = SDL_GetSensorNameForID(id); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetSensorTypeForID(id) -> string +JSC_CCALL(sensor_get_type_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_SensorType type = SDL_GetSensorTypeForID(id); + return JS_NewString(js, sensor_type_to_string(type)); +) + +// SDL_GetSensorNonPortableTypeForID(id) -> number +JSC_CCALL(sensor_get_non_portable_type_for_id, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + return JS_NewInt32(js, SDL_GetSensorNonPortableTypeForID(id)); +) + +// SDL_OpenSensor(id) -> Sensor object +JSC_CCALL(sensor_open, + uint32_t id; + JS_ToUint32(js, &id, argv[0]); + SDL_Sensor *sensor = SDL_OpenSensor(id); + if (!sensor) return JS_NULL; + return SDL_Sensor2js(js, sensor); +) + +// Sensor methods +JSC_CCALL(sensor_get_name, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + const char *name = SDL_GetSensorName(sensor); + return name ? JS_NewString(js, name) : JS_NULL; +) + +JSC_CCALL(sensor_get_type, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + SDL_SensorType type = SDL_GetSensorType(sensor); + return JS_NewString(js, sensor_type_to_string(type)); +) + +JSC_CCALL(sensor_get_non_portable_type, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + return JS_NewInt32(js, SDL_GetSensorNonPortableType(sensor)); +) + +JSC_CCALL(sensor_get_id, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + return JS_NewUint32(js, SDL_GetSensorID(sensor)); +) + +JSC_CCALL(sensor_get_data, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + int num_values = 3; + if (argc > 0) JS_ToInt32(js, &num_values, argv[0]); + + float *data = malloc(num_values * sizeof(float)); + if (!data) return JS_ThrowOutOfMemory(js); + + if (!SDL_GetSensorData(sensor, data, num_values)) { + free(data); + return JS_NULL; + } + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < num_values; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewFloat64(js, data[i])); + } + free(data); + return arr; +) + +JSC_CCALL(sensor_close, + SDL_Sensor *sensor = js2SDL_Sensor(js, self); + SDL_CloseSensor(sensor); + return JS_NULL; +) + +// SDL_UpdateSensors() +JSC_CCALL(sensor_update, + SDL_UpdateSensors(); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Sensor_funcs[] = { + MIST_FUNC_DEF(sensor, get_name, 0), + MIST_FUNC_DEF(sensor, get_type, 0), + MIST_FUNC_DEF(sensor, get_non_portable_type, 0), + MIST_FUNC_DEF(sensor, get_id, 0), + MIST_FUNC_DEF(sensor, get_data, 1), + MIST_FUNC_DEF(sensor, close, 0), +}; + +static const JSCFunctionListEntry js_sensor_funcs[] = { + MIST_FUNC_DEF(sensor, get_sensors, 0), + MIST_FUNC_DEF(sensor, get_name_for_id, 1), + MIST_FUNC_DEF(sensor, get_type_for_id, 1), + MIST_FUNC_DEF(sensor, get_non_portable_type_for_id, 1), + MIST_FUNC_DEF(sensor, open, 1), + MIST_FUNC_DEF(sensor, update, 0), +}; + +CELL_USE_INIT( + SDL_Init(SDL_INIT_SENSOR); + QJSCLASSPREP_FUNCS(SDL_Sensor); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_sensor_funcs, countof(js_sensor_funcs)); + + // Export standard gravity constant + JS_SetPropertyStr(js, ret, "STANDARD_GRAVITY", JS_NewFloat64(js, SDL_STANDARD_GRAVITY)); + + return ret; +) diff --git a/surface.c b/surface.c new file mode 100644 index 0000000..552a0fd --- /dev/null +++ b/surface.c @@ -0,0 +1,1109 @@ +#include "cell.h" +#include +#include +#include +#include +#include +#include "sdl.h" + +// Stub implementations for stb_dxt functions +#define STB_DXT_HIGHQUAL 1 +#define STB_DXT_NORMAL 0 + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) { + // Stub implementation - just fill with zeros + memset(dest, 0, alpha ? 16 : 8); +} + +void stb_compress_bc4_block(unsigned char *dest, const unsigned char *src) { + // Stub implementation - just fill with zeros + memset(dest, 0, 8); +} + +void stb_compress_bc5_block(unsigned char *dest, const unsigned char *src) { + // Stub implementation - just fill with zeros + memset(dest, 0, 16); +} + +irect js2irect(JSContext *js, JSValue v) +{ + if (JS_IsNull(v)) return (irect){0,0,1,1}; + irect rect; + JS_GETATOM(js,rect.x,v,x,number) + JS_GETATOM(js,rect.y,v,y,number) + JS_GETATOM(js,rect.w,v,width,number) + JS_GETATOM(js,rect.h,v,height,number) + float anchor_x, anchor_y; + JS_GETATOM(js, anchor_x, v, anchor_x, number) + JS_GETATOM(js, anchor_y, v, anchor_y, number) + + rect.y -= anchor_y*rect.h; + rect.x -= anchor_x*rect.w; + + return rect; +} + +JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt) +{ + return SDL_PixelFormat2js(js, fmt); +} + +SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v) +{ + return js2SDL_PixelFormat(js, v); +} + +typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry; + +static const scale_entry k_scale_table[] = { + { "nearest", SDL_SCALEMODE_NEAREST }, + { "linear", SDL_SCALEMODE_LINEAR }, + { NULL, SDL_SCALEMODE_LINEAR } /* fallback */ +}; + +static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){ + const scale_entry *it; + for(it = k_scale_table; it->name; ++it) + if(it->mode == mode) break; + return JS_NewString(js, it->name ? it->name : "nearest"); +} + +SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){ + if(JS_IsNull(v)) return SDL_SCALEMODE_NEAREST; + const char *s = JS_ToCString(js, v); + if(!s) return SDL_SCALEMODE_NEAREST; + const scale_entry *it; + for(it = k_scale_table; it->name; ++it) + if(!strcmp(it->name, s)) break; + JS_FreeCString(js, s); + return it->mode; +} + +// SDL_Surface free function +void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) { + if (s->flags & SDL_SURFACE_PREALLOCATED) + free(s->pixels); + SDL_DestroySurface(s); +} + +// Class definition +QJSCLASS(SDL_Surface,) + +// SDL_Surface methods +JSC_CCALL(surface_blit, + SDL_Surface *dst = js2SDL_Surface(js, self); + + irect dr = {0}, *pdr = NULL; + if(!JS_IsNull(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; } + + SDL_Surface *src = js2SDL_Surface(js, argv[1]); + if (!src) + return JS_ThrowReferenceError(js, "Argument must be a surface."); + + irect sr = {0}, *psr = NULL; + if(!JS_IsNull(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; } + + SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]); + + SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE); + + SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode); +) + +JSC_CCALL(surface_scale, + SDL_Surface *src = js2SDL_Surface(js,self); + vec2 wh = js2vec2(js,argv[0]); + SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]); + SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode); + ret = SDL_Surface2js(js,new); +) + +static SDL_PixelFormatDetails pdetails = { + .format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format + .bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits + .bytes_per_pixel = 4, // 4 bytes per pixel + .padding = {0, 0}, // Unused padding + .Rmask = 0xFF000000, // Red mask + .Gmask = 0x00FF0000, // Green mask + .Bmask = 0x0000FF00, // Blue mask + .Amask = 0x000000FF, // Alpha mask + .Rbits = 8, // 8 bits for Red + .Gbits = 8, // 8 bits for Green + .Bbits = 8, // 8 bits for Blue + .Abits = 8, // 8 bits for Alpha + .Rshift = 24, // Red shift + .Gshift = 16, // Green shift + .Bshift = 8, // Blue shift + .Ashift = 0 // Alpha shift +}; + +JSC_CCALL(surface_fill, + SDL_Surface *src = js2SDL_Surface(js,self); + colorf color = js2color(js,argv[0]); + rect r = { + .x = 0, + .y = 0, + .w = src->w, + .h = src->h + }; + SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.x*255,color.y*255,color.z*255,color.w*255)); +) + +JSC_CCALL(surface_rect, + SDL_Surface *dst = js2SDL_Surface(js,self); + rect r = js2rect(js,argv[0]); + colorf color = js2color(js,argv[1]); + SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.x*255,color.y*255,color.z*255,color.w*255)); +) + +JSC_CCALL(surface_convert, + SDL_Surface *surf = js2SDL_Surface(js,self); + SDL_PixelFormat fmt = js2pixelformat(js, argv[0]); + SDL_Surface *dst = SDL_ConvertSurface(surf, fmt); + if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError()); + + return SDL_Surface2js(js, dst); +) + +JSC_CCALL(surface_dup, + SDL_Surface *surf = js2SDL_Surface(js,self); + SDL_Surface *conv = SDL_DuplicateSurface(surf); + if (!conv) + return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError()); + + return SDL_Surface2js(js,conv); +) + +JSC_CCALL(surface_pixels, + SDL_Surface *surf = js2SDL_Surface(js, self); + + int locked = 0; + if (SDL_MUSTLOCK(surf)) + if (SDL_LockSurface(surf) < 0) + return JS_ThrowReferenceError(js, "Lock surface failed: %s", SDL_GetError()); + + size_t byte_size; + if (SDL_ISPIXELFORMAT_FOURCC(surf->format)) { + /* Planar/YUV formats: use BitsPerPixel to compute true size */ + printf("FOURCC!!! Bits is %d\n", SDL_BYTESPERPIXEL(surf->format)); + byte_size = (size_t)surf->pitch * surf->h * SDL_BYTESPERPIXEL(surf->format); + } else + byte_size = (size_t)surf->pitch * surf->h; + + ret = js_new_blob_stoned_copy(js, surf->pixels, byte_size); + + if (locked) SDL_UnlockSurface(surf); +) + +JSValue js_surface_get_width(JSContext *js, JSValue self) { + SDL_Surface *s = js2SDL_Surface(js,self); + return JS_NewFloat64(js, s->w); +} + +JSValue js_surface_get_height(JSContext *js, JSValue self) { + SDL_Surface *s = js2SDL_Surface(js,self); + return JS_NewFloat64(js, s->h); +} + +JSValue js_surface_get_format(JSContext *js, JSValue self) { + SDL_Surface *s = js2SDL_Surface(js,self); + return pixelformat2js(js, s->format); +} + +JSValue js_surface_get_pitch(JSContext *js, JSValue self) { + SDL_Surface *s = js2SDL_Surface(js,self); + return JS_NewFloat64(js, s->pitch); +} + +JSC_CCALL(surface_toJSON, + SDL_Surface *surf = js2SDL_Surface(js,self); + + // Create the result object + JSValue obj = JS_NewObject(js); + + // Add width and height + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h)); + + // Add format + JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format)); + + // Add pitch + JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch)); + + // Lock surface if needed + int locked = 0; + if (SDL_MUSTLOCK(surf)) { + if (SDL_LockSurface(surf) < 0) { + JS_FreeValue(js, obj); + return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError()); + } + locked = 1; + } + + // Add pixels as ArrayBuffer + size_t byte_size = surf->pitch * surf->h; + JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size); + JS_SetPropertyStr(js, obj, "pixels", pixels); + + // Unlock if we locked + if (locked) + SDL_UnlockSurface(surf); + + return obj; +) + +// Check for integer overflow in size calculations +static int check_size_overflow(size_t a, size_t b, size_t c, size_t *result) +{ + if (a > SIZE_MAX / b) return 1; + size_t temp = a * b; + if (temp > SIZE_MAX / c) return 1; + *result = temp * c; + return 0; +} + +// Helper function for BC1/BC3 compression +static JSValue compress_bc_common(JSContext *js, JSValueConst *argv, int argc, int alpha_mode, const char *format_name) +{ + if (argc < 1) + return JS_ThrowTypeError(js, "compress_%s requires an object argument", format_name); + + // Check if width/height properties exist + JSValue width_val = JS_GetPropertyStr(js, argv[0], "width"); + JSValue height_val = JS_GetPropertyStr(js, argv[0], "height"); + + if (JS_IsNull(width_val) || JS_IsNull(height_val)) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "compress_%s requires width and height properties", format_name); + } + + int width, height; + if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "width and height must be numbers"); + } + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + if (width < 1 || height < 1) + return JS_ThrowRangeError(js, "width and height must be at least 1"); + + if (width % 4 != 0 || height % 4 != 0) + return JS_ThrowRangeError(js, "Width and height must be multiples of 4 for BC compression"); + + // Get pixel format + JSValue format_val = JS_GetPropertyStr(js, argv[0], "format"); + SDL_PixelFormat format = js2pixelformat(js, format_val); + JS_FreeValue(js, format_val); + + if (format == SDL_PIXELFORMAT_UNKNOWN) + return JS_ThrowTypeError(js, "Invalid or missing pixel format"); + + // Get pixels + JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); + size_t pixel_len; + void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); + + if (!pixel_data || pixel_data == -1) { + JS_FreeValue(js, pixels_val); + return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer"); + } + + // Validate buffer size + int bytes_per_pixel = SDL_BYTESPERPIXEL(format); + size_t required_size; + if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Image dimensions too large"); + } + + if (pixel_len < required_size) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)", + width, height, required_size, pixel_len); + } + + // Get high quality mode (default true) + int high_quality = 1; + if (argc > 1) { + high_quality = JS_ToBool(js, argv[1]); + } + + int mode = high_quality ? STB_DXT_HIGHQUAL : STB_DXT_NORMAL; + + // Calculate output size with overflow check + int blocks_x = width / 4; + int blocks_y = height / 4; + int bytes_per_block = (alpha_mode == 0) ? 8 : 16; // BC1=8, BC3=16 + size_t output_size; + if (check_size_overflow(blocks_x, blocks_y, bytes_per_block, &output_size)) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Output dimensions too large"); + } + + // Allocate output buffer + unsigned char *output = malloc(output_size); + if (!output) { + JS_FreeValue(js, pixels_val); + return JS_ThrowOutOfMemory(js); + } + + // Allocate RGBA conversion buffer + size_t rgba_size; + if (check_size_overflow(width, height, 4, &rgba_size)) { + free(output); + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "RGBA buffer size too large"); + } + + unsigned char *rgba_data = malloc(rgba_size); + if (!rgba_data) { + free(output); + JS_FreeValue(js, pixels_val); + return JS_ThrowOutOfMemory(js); + } + + // Convert to RGBA using SDL + int convert_result = SDL_ConvertPixels( + width, height, + format, pixel_data, width * bytes_per_pixel, + SDL_PIXELFORMAT_RGBA32, rgba_data, width * 4 + ); + + JS_FreeValue(js, pixels_val); + + if (convert_result != 0) { + free(output); + free(rgba_data); + return JS_ThrowInternalError(js, "Failed to convert pixels: %s", SDL_GetError()); + } + + // Compress blocks + for (int by = 0; by < blocks_y; by++) { + for (int bx = 0; bx < blocks_x; bx++) { + unsigned char block[64]; // 4x4 RGBA = 64 bytes + + // Extract 4x4 block + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + int src_x = bx * 4 + x; + int src_y = by * 4 + y; + int src_idx = (src_y * width + src_x) * 4; + int dst_idx = (y * 4 + x) * 4; + + block[dst_idx + 0] = rgba_data[src_idx + 0]; + block[dst_idx + 1] = rgba_data[src_idx + 1]; + block[dst_idx + 2] = rgba_data[src_idx + 2]; + block[dst_idx + 3] = rgba_data[src_idx + 3]; + } + } + + // Compress block + int output_idx = (by * blocks_x + bx) * bytes_per_block; + stb_compress_dxt_block(output + output_idx, block, alpha_mode, mode); + } + } + + free(rgba_data); + + // Create result object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height)); + JS_SetPropertyStr(js, result, "format", JS_NewString(js, format_name)); + JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, blocks_x * bytes_per_block)); + + JSValue compressed_pixels = js_new_blob_stoned_copy(js, output, output_size); + free(output); // Free the output buffer after copying to blob + JS_SetPropertyStr(js, result, "pixels", compressed_pixels); + + return result; +} + +// BC1/DXT1 compression +JSC_CCALL(surface_compress_bc1, + return compress_bc_common(js, argv, argc, 0, "bc1"); +) + +// BC3/DXT5 compression +JSC_CCALL(surface_compress_bc3, + return compress_bc_common(js, argv, argc, 1, "bc3"); +) + +// Generic helper for BC4/BC5 channel compression +static JSValue compress_bc_channels(JSContext *js, JSValueConst *argv, int argc, + int num_channels, const char *format_name, + void (*compress_func)(unsigned char *dest, const unsigned char *src)) +{ + if (argc < 1) + return JS_ThrowTypeError(js, "compress_%s requires an object argument", format_name); + + // Check if width/height properties exist + JSValue width_val = JS_GetPropertyStr(js, argv[0], "width"); + JSValue height_val = JS_GetPropertyStr(js, argv[0], "height"); + + if (JS_IsNull(width_val) || JS_IsNull(height_val)) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "compress_%s requires width and height properties", format_name); + } + + int width, height; + if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "width and height must be numbers"); + } + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + if (width < 1 || height < 1) + return JS_ThrowRangeError(js, "width and height must be at least 1"); + + if (width % 4 != 0 || height % 4 != 0) + return JS_ThrowRangeError(js, "Width and height must be multiples of 4 for BC compression"); + + // Get pixel format + JSValue format_val = JS_GetPropertyStr(js, argv[0], "format"); + SDL_PixelFormat format = js2pixelformat(js, format_val); + JS_FreeValue(js, format_val); + + if (format == SDL_PIXELFORMAT_UNKNOWN) + return JS_ThrowTypeError(js, "Invalid or missing pixel format"); + + // Get pixels + JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); + size_t pixel_len; + void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); + + if (!pixel_data || pixel_data == -1) { + JS_FreeValue(js, pixels_val); + return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer"); + } + + // Validate buffer size + int bytes_per_pixel = SDL_BYTESPERPIXEL(format); + if (bytes_per_pixel < num_channels) { + JS_FreeValue(js, pixels_val); + return JS_ThrowTypeError(js, "%s compression requires a format with at least %d channel(s)", + format_name, num_channels); + } + + size_t required_size; + if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Image dimensions too large"); + } + + if (pixel_len < required_size) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)", + width, height, required_size, pixel_len); + } + + // Calculate output size with overflow check + int blocks_x = width / 4; + int blocks_y = height / 4; + int bytes_per_block = (num_channels == 1) ? 8 : 16; // BC4=8, BC5=16 + size_t output_size; + if (check_size_overflow(blocks_x, blocks_y, bytes_per_block, &output_size)) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Output dimensions too large"); + } + + // Allocate output buffer + unsigned char *output = malloc(output_size); + if (!output) { + JS_FreeValue(js, pixels_val); + return JS_ThrowOutOfMemory(js); + } + + // Allocate channel extraction buffer + size_t channel_size; + if (check_size_overflow(width, height, num_channels, &channel_size)) { + free(output); + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Channel buffer size too large"); + } + + unsigned char *channel_data = malloc(channel_size); + if (!channel_data) { + free(output); + JS_FreeValue(js, pixels_val); + return JS_ThrowOutOfMemory(js); + } + + // Extract channels + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int src_idx = (y * width + x) * bytes_per_pixel; + int dst_idx = (y * width + x) * num_channels; + + // Extract first 'num_channels' channels + for (int c = 0; c < num_channels; c++) { + channel_data[dst_idx + c] = ((unsigned char*)pixel_data)[src_idx + c]; + } + } + } + + JS_FreeValue(js, pixels_val); + + // Compress blocks + for (int by = 0; by < blocks_y; by++) { + for (int bx = 0; bx < blocks_x; bx++) { + unsigned char block[32]; // Max 4x4 * 2 channels = 32 bytes + + // Extract 4x4 block + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + int src_x = bx * 4 + x; + int src_y = by * 4 + y; + int src_idx = (src_y * width + src_x) * num_channels; + int dst_idx = (y * 4 + x) * num_channels; + + for (int c = 0; c < num_channels; c++) { + block[dst_idx + c] = channel_data[src_idx + c]; + } + } + } + + // Compress block + int output_idx = (by * blocks_x + bx) * bytes_per_block; + compress_func(output + output_idx, block); + } + } + + free(channel_data); + + // Create result object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height)); + JS_SetPropertyStr(js, result, "format", JS_NewString(js, format_name)); + JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, blocks_x * bytes_per_block)); + + JSValue compressed_pixels = js_new_blob_stoned_copy(js, output, output_size); + free(output); // Free the output buffer after copying to blob + JS_SetPropertyStr(js, result, "pixels", compressed_pixels); + + return result; +} + +// BC4 compression (single channel) +JSC_CCALL(surface_compress_bc4, + return compress_bc_channels(js, argv, argc, 1, "bc4", stb_compress_bc4_block); +) + +// BC5 compression (two channels) +JSC_CCALL(surface_compress_bc5, + return compress_bc_channels(js, argv, argc, 2, "bc5", stb_compress_bc5_block); +) + +// Constructor for SDL_Surface +JSC_CCALL(surface_constructor, + if (argc < 1) + return JS_ThrowTypeError(js, "Surface constructor requires an object argument"); + + // Get width and height + int width, height; + JS_GETATOM(js, width, argv[0], width, number) + JS_GETATOM(js, height, argv[0], height, number) + + if (!width || !height) + return JS_ThrowTypeError(js, "Surface constructor requires width and height properties"); + + // Check for pixel format + SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA32; // default + JSValue format_val = JS_GetPropertyStr(js, argv[0], "format"); + if (!JS_IsNull(format_val)) { + format = js2pixelformat(js, format_val); + } + JS_FreeValue(js, format_val); + + // Check for pixel data + JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); + if (!JS_IsNull(pixels_val)) { + // Create surface from pixel data + size_t len; + void *raw = js_get_blob_data(js, &len, pixels_val); + + if (raw == -1) { + JS_FreeValue(js, pixels_val); + return JS_EXCEPTION; + } + + if (!raw) { + JS_FreeValue(js, pixels_val); + return JS_ThrowTypeError(js, "no data"); + } + + int pitch; + JSValue pitch_val = JS_GetPropertyStr(js, argv[0], "pitch"); + if (!JS_IsNull(pitch_val)) { + pitch = js2number(js, pitch_val); + JS_FreeValue(js, pitch_val); + } else { + // Calculate pitch based on format + int bytes_per_pixel = SDL_BYTESPERPIXEL(format); + pitch = width * bytes_per_pixel; + } + + // Copy the pixel data + void *pixels_copy = malloc(len); + if (!pixels_copy) { + JS_FreeValue(js, pixels_val); + return JS_ThrowOutOfMemory(js); + } + memcpy(pixels_copy, raw, len); + + SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch); + if (!surface) { + free(pixels_copy); + JS_FreeValue(js, pixels_val); + return JS_ThrowInternalError(js, "Failed to create surface from pixels: %s", SDL_GetError()); + } + + JS_FreeValue(js, pixels_val); + ret = SDL_Surface2js(js, surface); + } else { + // Create blank surface + SDL_Surface *surface = SDL_CreateSurface(width, height, format); + if (!surface) + return JS_ThrowInternalError(js, "Failed to create surface: %s", SDL_GetError()); + + ret = SDL_Surface2js(js, surface); + } +) + +static const JSCFunctionListEntry js_SDL_Surface_funcs[] = { + MIST_FUNC_DEF(surface, blit, 4), + MIST_FUNC_DEF(surface, scale, 1), + MIST_FUNC_DEF(surface, fill,1), + MIST_FUNC_DEF(surface, rect,2), + MIST_FUNC_DEF(surface, dup, 0), + MIST_FUNC_DEF(surface, pixels, 0), + MIST_FUNC_DEF(surface, convert, 1), + MIST_FUNC_DEF(surface, toJSON, 0), + JS_CGETSET_DEF("width", js_surface_get_width, NULL), + JS_CGETSET_DEF("height", js_surface_get_height, NULL), + JS_CGETSET_DEF("format", js_surface_get_format, NULL), + JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL), +}; + +// Helper function to create SDL_Surface from image object +static SDL_Surface* image_to_surface(JSContext *js, JSValue img_obj) +{ + // Get width and height + JSValue width_val = JS_GetPropertyStr(js, img_obj, "width"); + JSValue height_val = JS_GetPropertyStr(js, img_obj, "height"); + + if (JS_IsNull(width_val) || JS_IsNull(height_val)) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return NULL; + } + + int width, height; + if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return NULL; + } + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + // Get format + JSValue format_val = JS_GetPropertyStr(js, img_obj, "format"); + SDL_PixelFormat format = js2pixelformat(js, format_val); + JS_FreeValue(js, format_val); + + if (format == SDL_PIXELFORMAT_UNKNOWN) + format = SDL_PIXELFORMAT_RGBA32; // default + + // Get pixels + JSValue pixels_val = JS_GetPropertyStr(js, img_obj, "pixels"); + size_t pixel_len; + void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); + + if (pixel_data == -1) { + JS_FreeValue(js, pixels_val); + return NULL; + } + + if (!pixel_data) { + JS_FreeValue(js, pixels_val); + return NULL; + } + + // Get pitch (optional) + int pitch; + JSValue pitch_val = JS_GetPropertyStr(js, img_obj, "pitch"); + if (!JS_IsNull(pitch_val)) { + pitch = js2number(js, pitch_val); + JS_FreeValue(js, pitch_val); + } else { + pitch = width * SDL_BYTESPERPIXEL(format); + } + + // Create a copy of pixel data since SDL_Surface will own it + void *pixels_copy = malloc(pixel_len); + if (!pixels_copy) { + JS_FreeValue(js, pixels_val); + return NULL; + } + memcpy(pixels_copy, pixel_data, pixel_len); + JS_FreeValue(js, pixels_val); + + SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch); + if (!surface) { + free(pixels_copy); + return NULL; + } + + return surface; +} + +// Helper function to convert SDL_Surface back to image object +static JSValue surface_to_image(JSContext *js, SDL_Surface *surf) +{ + JSValue obj = JS_NewObject(js); + + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h)); + JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format)); + JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch)); + + // Lock surface if needed + int locked = 0; + if (SDL_MUSTLOCK(surf)) { + if (SDL_LockSurface(surf) < 0) { + JS_FreeValue(js, obj); + return JS_NULL; + } + locked = 1; + } + + // Add pixels as stoned blob + size_t byte_size = surf->pitch * surf->h; + JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size); + JS_SetPropertyStr(js, obj, "pixels", pixels); + + // Unlock if we locked + if (locked) + SDL_UnlockSurface(surf); + + // Add depth and hdr properties for completeness + JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(surf->format))); + JS_SetPropertyStr(js, obj, "hdr", JS_FALSE); + + return obj; +} + +// Scale function for image objects +JSC_CCALL(surface_scale_img, + if (argc < 2) + return JS_ThrowTypeError(js, "scale requires image and options objects"); + + SDL_Surface *src = image_to_surface(js, argv[0]); + if (!src) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + // Get width and height from options + JSValue width_val = JS_GetPropertyStr(js, argv[1], "width"); + JSValue height_val = JS_GetPropertyStr(js, argv[1], "height"); + + int new_width = src->w, new_height = src->h; + if (!JS_IsNull(width_val)) + JS_ToInt32(js, &new_width, width_val); + if (!JS_IsNull(height_val)) + JS_ToInt32(js, &new_height, height_val); + + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + // Get scale mode + JSValue mode_val = JS_GetPropertyStr(js, argv[1], "mode"); + SDL_ScaleMode mode = js2SDL_ScaleMode(js, mode_val); + JS_FreeValue(js, mode_val); + + SDL_Surface *dst = SDL_ScaleSurface(src, new_width, new_height, mode); + SDL_DestroySurface(src); + + if (!dst) + return JS_ThrowInternalError(js, "Scale failed: %s", SDL_GetError()); + + JSValue result = surface_to_image(js, dst); + SDL_DestroySurface(dst); + return result; +) + +// Fill function for image objects +JSC_CCALL(surface_fill_img, + if (argc < 2) + return JS_ThrowTypeError(js, "fill requires image and color"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + colorf color = js2color(js, argv[1]); + rect r = { + .x = 0, + .y = 0, + .w = surf->w, + .h = surf->h + }; + + SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.x*255, color.y*255, color.z*255, color.w*255)); + + JSValue result = surface_to_image(js, surf); + SDL_DestroySurface(surf); + return result; +) + +// Rect function for image objects +JSC_CCALL(surface_rect_img, + if (argc < 3) + return JS_ThrowTypeError(js, "rect requires image, rectangle, and color"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + rect r = js2rect(js, argv[1]); + colorf color = js2color(js, argv[2]); + + SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.x*255, color.y*255, color.z*255, color.w*255)); + + JSValue result = surface_to_image(js, surf); + SDL_DestroySurface(surf); + return result; +) + +// Blit function for image objects +JSC_CCALL(surface_blit_img, + if (argc < 2) + return JS_ThrowTypeError(js, "blit requires destination and source images"); + + SDL_Surface *dst = image_to_surface(js, argv[0]); + if (!dst) + return JS_ThrowTypeError(js, "First argument must be a valid destination image"); + + SDL_Surface *src = image_to_surface(js, argv[1]); + if (!src) { + SDL_DestroySurface(dst); + return JS_ThrowTypeError(js, "Second argument must be a valid source image"); + } + + irect dr = {0}, *pdr = NULL; + if (argc > 2 && !JS_IsNull(argv[2])) { + dr = js2irect(js, argv[2]); + pdr = &dr; + } + + irect sr = {0}, *psr = NULL; + if (argc > 3 && !JS_IsNull(argv[3])) { + sr = js2irect(js, argv[3]); + psr = &sr; + } + + SDL_ScaleMode mode = SDL_SCALEMODE_LINEAR; + if (argc > 4) + mode = js2SDL_ScaleMode(js, argv[4]); + + SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE); + SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode); + + SDL_DestroySurface(src); + JSValue result = surface_to_image(js, dst); + SDL_DestroySurface(dst); + return result; +) + +// Duplicate function for image objects +JSC_CCALL(surface_dup_img, + if (argc < 1) + return JS_ThrowTypeError(js, "dup requires an image object"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "Argument must be a valid image object"); + + SDL_Surface *dup = SDL_DuplicateSurface(surf); + SDL_DestroySurface(surf); + + if (!dup) + return JS_ThrowInternalError(js, "Duplicate failed: %s", SDL_GetError()); + + JSValue result = surface_to_image(js, dup); + SDL_DestroySurface(dup); + return result; +) + +// Generic convert function for pixel format/colorspace conversion +JSC_CCALL(surface_convert_generic, + if (argc < 2) + return JS_ThrowTypeError(js, "convert requires source and conversion objects"); + + // Parse source object + int src_width, src_height; + JS_GETATOM(js, src_width, argv[0], width, number) + JS_GETATOM(js, src_height, argv[0], height, number) + + if (!src_width || !src_height) + return JS_ThrowTypeError(js, "source object requires width and height"); + + // Get source format + JSValue src_format_val = JS_GetPropertyStr(js, argv[0], "format"); + SDL_PixelFormat src_format = js2pixelformat(js, src_format_val); + JS_FreeValue(js, src_format_val); + + if (src_format == SDL_PIXELFORMAT_UNKNOWN) + return JS_ThrowTypeError(js, "source object requires valid format"); + + // Get source pixels + JSValue src_pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); + size_t src_len; + void *src_pixels = js_get_blob_data(js, &src_len, src_pixels_val); + + if (src_pixels == -1) { + JS_FreeValue(js, src_pixels_val); + return JS_EXCEPTION; + } + + // Get source pitch (optional, calculate if not provided) + int src_pitch; + JSValue src_pitch_val = JS_GetPropertyStr(js, argv[0], "pitch"); + if (!JS_IsNull(src_pitch_val)) { + src_pitch = js2number(js, src_pitch_val); + JS_FreeValue(js, src_pitch_val); + } else { + src_pitch = src_width * SDL_BYTESPERPIXEL(src_format); + } + + // Get source colorspace (optional) + JSValue src_colorspace_val = JS_GetPropertyStr(js, argv[0], "colorspace"); + SDL_Colorspace src_colorspace = SDL_COLORSPACE_SRGB; // default + if (!JS_IsNull(src_colorspace_val)) { + // For now, we'll use a simple numeric value for colorspace + int colorspace_num; + if (JS_ToInt32(js, &colorspace_num, src_colorspace_val) == 0) { + src_colorspace = (SDL_Colorspace)colorspace_num; + } + } + JS_FreeValue(js, src_colorspace_val); + + // Parse conversion object + JSValue dst_format_val = JS_GetPropertyStr(js, argv[1], "format"); + SDL_PixelFormat dst_format = js2pixelformat(js, dst_format_val); + JS_FreeValue(js, dst_format_val); + + if (dst_format == SDL_PIXELFORMAT_UNKNOWN) + return JS_ThrowTypeError(js, "conversion object requires valid format"); + + // Get destination pitch (optional) + int dst_pitch; + JSValue dst_pitch_val = JS_GetPropertyStr(js, argv[1], "pitch"); + if (!JS_IsNull(dst_pitch_val)) { + dst_pitch = js2number(js, dst_pitch_val); + JS_FreeValue(js, dst_pitch_val); + } else { + dst_pitch = src_width * SDL_BYTESPERPIXEL(dst_format); + } + + // Get destination colorspace (optional) + JSValue dst_colorspace_val = JS_GetPropertyStr(js, argv[1], "colorspace"); + SDL_Colorspace dst_colorspace = SDL_COLORSPACE_SRGB; // default + if (!JS_IsNull(dst_colorspace_val)) { + int colorspace_num; + if (JS_ToInt32(js, &colorspace_num, dst_colorspace_val) == 0) { + dst_colorspace = (SDL_Colorspace)colorspace_num; + } + } + JS_FreeValue(js, dst_colorspace_val); + + // Calculate destination buffer size + size_t dst_size = dst_pitch * src_height; + void *dst_pixels = malloc(dst_size); + if (!dst_pixels) { + JS_FreeValue(js, src_pixels_val); + return JS_ThrowOutOfMemory(js); + } + + // Check if we have colorspace info for both source and dest + bool has_src_colorspace = !JS_IsNull(JS_GetPropertyStr(js, argv[0], "colorspace")); + bool has_dst_colorspace = !JS_IsNull(JS_GetPropertyStr(js, argv[1], "colorspace")); + + bool success; + if (has_src_colorspace || has_dst_colorspace) { + // Use SDL_ConvertPixelsAndColorspace + success = SDL_ConvertPixelsAndColorspace( + src_width, src_height, + src_format, src_colorspace, 0, src_pixels, src_pitch, + dst_format, dst_colorspace, 0, dst_pixels, dst_pitch + ); + } else { + // Use SDL_ConvertPixels + success = SDL_ConvertPixels( + src_width, src_height, + src_format, src_pixels, src_pitch, + dst_format, dst_pixels, dst_pitch + ); + } + + JS_FreeValue(js, src_pixels_val); + + if (!success) { + free(dst_pixels); + return JS_ThrowInternalError(js, "Pixel conversion failed: %s", SDL_GetError()); + } + + // Create result image object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, src_width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, src_height)); + JS_SetPropertyStr(js, result, "format", pixelformat2js(js, dst_format)); + JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, dst_pitch)); + + JSValue pixels = js_new_blob_stoned_copy(js, dst_pixels, dst_size); + free(dst_pixels); + JS_SetPropertyStr(js, result, "pixels", pixels); + + // Add depth and hdr for consistency + JS_SetPropertyStr(js, result, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(dst_format))); + JS_SetPropertyStr(js, result, "hdr", JS_FALSE); + + return result; +) + +CELL_USE_INIT( + QJSCLASSPREP_FUNCS(SDL_Surface) + + // Add the surface constructor + JSValue ctor = JS_NewCFunction2(js, js_surface_constructor, "surface", 1, JS_CFUNC_constructor, 0); + + JSValue proto = JS_GetClassProto(js, js_SDL_Surface_id); + JS_SetConstructor(js, ctor, proto); + JS_FreeValue(js, proto); + + // Add the generic convert function as a property on the constructor + JS_SetPropertyStr(js, ctor, "convert", JS_NewCFunction(js, js_surface_convert_generic, "convert", 2)); + + // Add the compression functions as static methods on the constructor + JS_SetPropertyStr(js, ctor, "compress_bc1", JS_NewCFunction(js, js_surface_compress_bc1, "compress_bc1", 2)); + JS_SetPropertyStr(js, ctor, "compress_bc3", JS_NewCFunction(js, js_surface_compress_bc3, "compress_bc3", 2)); + JS_SetPropertyStr(js, ctor, "compress_bc4", JS_NewCFunction(js, js_surface_compress_bc4, "compress_bc4", 1)); + JS_SetPropertyStr(js, ctor, "compress_bc5", JS_NewCFunction(js, js_surface_compress_bc5, "compress_bc5", 1)); + + // Add standalone image manipulation functions + JS_SetPropertyStr(js, ctor, "scale", JS_NewCFunction(js, js_surface_scale_img, "scale", 2)); + JS_SetPropertyStr(js, ctor, "fill", JS_NewCFunction(js, js_surface_fill_img, "fill", 2)); + JS_SetPropertyStr(js, ctor, "rect", JS_NewCFunction(js, js_surface_rect_img, "rect", 3)); + JS_SetPropertyStr(js, ctor, "blit", JS_NewCFunction(js, js_surface_blit_img, "blit", 5)); + JS_SetPropertyStr(js, ctor, "dup", JS_NewCFunction(js, js_surface_dup_img, "dup", 1)); + + return ctor; +) \ No newline at end of file diff --git a/touch.c b/touch.c new file mode 100644 index 0000000..660c071 --- /dev/null +++ b/touch.c @@ -0,0 +1,73 @@ +#include "cell.h" +#include + +// SDL_GetTouchDevices() -> array of touch device IDs +JSC_CCALL(touch_get_devices, + int count = 0; + SDL_TouchID *devices = SDL_GetTouchDevices(&count); + if (!devices) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewInt64(js, devices[i])); + } + SDL_free(devices); + return arr; +) + +// SDL_GetTouchDeviceName(touchID) -> string +JSC_CCALL(touch_get_device_name, + int64_t touchID; + JS_ToInt64(js, &touchID, argv[0]); + const char *name = SDL_GetTouchDeviceName((SDL_TouchID)touchID); + return name ? JS_NewString(js, name) : JS_NULL; +) + +// SDL_GetTouchDeviceType(touchID) -> string +static const char *touch_device_type_to_string(SDL_TouchDeviceType type) { + switch (type) { + case SDL_TOUCH_DEVICE_DIRECT: return "direct"; + case SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE: return "indirect_absolute"; + case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: return "indirect_relative"; + default: return "invalid"; + } +} + +JSC_CCALL(touch_get_device_type, + int64_t touchID; + JS_ToInt64(js, &touchID, argv[0]); + SDL_TouchDeviceType type = SDL_GetTouchDeviceType((SDL_TouchID)touchID); + return JS_NewString(js, touch_device_type_to_string(type)); +) + +// SDL_GetTouchFingers(touchID) -> array of finger objects +JSC_CCALL(touch_get_fingers, + int64_t touchID; + JS_ToInt64(js, &touchID, argv[0]); + + int count = 0; + SDL_Finger **fingers = SDL_GetTouchFingers((SDL_TouchID)touchID, &count); + if (!fingers) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + SDL_Finger *f = fingers[i]; + JSValue finger = JS_NewObject(js); + JS_SetPropertyStr(js, finger, "id", JS_NewInt64(js, f->id)); + JS_SetPropertyStr(js, finger, "x", JS_NewFloat64(js, f->x)); + JS_SetPropertyStr(js, finger, "y", JS_NewFloat64(js, f->y)); + JS_SetPropertyStr(js, finger, "pressure", JS_NewFloat64(js, f->pressure)); + JS_SetPropertyUint32(js, arr, i, finger); + } + SDL_free(fingers); + return arr; +) + +static const JSCFunctionListEntry js_touch_funcs[] = { + MIST_FUNC_DEF(touch, get_devices, 0), + MIST_FUNC_DEF(touch, get_device_name, 1), + MIST_FUNC_DEF(touch, get_device_type, 1), + MIST_FUNC_DEF(touch, get_fingers, 1), +}; + +CELL_USE_FUNCS(js_touch_funcs) diff --git a/tray.c b/tray.c new file mode 100644 index 0000000..ca92f5e --- /dev/null +++ b/tray.c @@ -0,0 +1,255 @@ +#include "cell.h" +#include + +// Forward declaration for surface function +extern SDL_Surface *js2SDL_Surface(JSContext *js, JSValue val); + +// SDL_Tray class +void SDL_Tray_free(JSRuntime *rt, SDL_Tray *tray) { + if (tray) SDL_DestroyTray(tray); +} + +QJSCLASS(SDL_Tray,) + +// SDL_TrayMenu class (not freed separately - destroyed with tray) +void SDL_TrayMenu_free(JSRuntime *rt, SDL_TrayMenu *menu) { + // Menus are destroyed with their parent tray +} + +QJSCLASS(SDL_TrayMenu,) + +// SDL_TrayEntry class (not freed separately - destroyed with tray) +void SDL_TrayEntry_free(JSRuntime *rt, SDL_TrayEntry *entry) { + // Entries are destroyed with their parent tray +} + +QJSCLASS(SDL_TrayEntry,) + +// SDL_CreateTray(icon, tooltip) -> Tray object +JSC_CCALL(tray_create, + SDL_Surface *icon = NULL; + const char *tooltip = NULL; + + if (argc > 0 && !JS_IsNull(argv[0])) { + icon = js2SDL_Surface(js, argv[0]); + } + if (argc > 1 && !JS_IsNull(argv[1])) { + tooltip = JS_ToCString(js, argv[1]); + } + + SDL_Tray *tray = SDL_CreateTray(icon, tooltip); + + if (tooltip) JS_FreeCString(js, tooltip); + + if (!tray) return JS_NULL; + return SDL_Tray2js(js, tray); +) + +// Tray instance methods +JSC_CCALL(tray_set_icon, + SDL_Tray *tray = js2SDL_Tray(js, self); + SDL_Surface *icon = NULL; + if (argc > 0 && !JS_IsNull(argv[0])) { + icon = js2SDL_Surface(js, argv[0]); + } + SDL_SetTrayIcon(tray, icon); + return JS_NULL; +) + +JSC_CCALL(tray_set_tooltip, + SDL_Tray *tray = js2SDL_Tray(js, self); + const char *tooltip = NULL; + if (argc > 0 && !JS_IsNull(argv[0])) { + tooltip = JS_ToCString(js, argv[0]); + } + SDL_SetTrayTooltip(tray, tooltip); + if (tooltip) JS_FreeCString(js, tooltip); + return JS_NULL; +) + +JSC_CCALL(tray_create_menu, + SDL_Tray *tray = js2SDL_Tray(js, self); + SDL_TrayMenu *menu = SDL_CreateTrayMenu(tray); + if (!menu) return JS_NULL; + return SDL_TrayMenu2js(js, menu); +) + +JSC_CCALL(tray_get_menu, + SDL_Tray *tray = js2SDL_Tray(js, self); + SDL_TrayMenu *menu = SDL_GetTrayMenu(tray); + if (!menu) return JS_NULL; + return SDL_TrayMenu2js(js, menu); +) + +JSC_CCALL(tray_destroy, + SDL_Tray *tray = js2SDL_Tray(js, self); + SDL_DestroyTray(tray); + return JS_NULL; +) + +// TrayMenu instance methods +JSC_CCALL(traymenu_get_entries, + SDL_TrayMenu *menu = js2SDL_TrayMenu(js, self); + int count = 0; + const SDL_TrayEntry **entries = SDL_GetTrayEntries(menu, &count); + if (!entries) return JS_NewArray(js); + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, SDL_TrayEntry2js(js, (SDL_TrayEntry*)entries[i])); + } + return arr; +) + +JSC_CCALL(traymenu_insert_entry, + SDL_TrayMenu *menu = js2SDL_TrayMenu(js, self); + int pos = -1; + const char *label = NULL; + uint32_t flags = SDL_TRAYENTRY_BUTTON; + + if (argc > 0) JS_ToInt32(js, &pos, argv[0]); + if (argc > 1 && !JS_IsNull(argv[1])) { + label = JS_ToCString(js, argv[1]); + } + if (argc > 2) JS_ToUint32(js, &flags, argv[2]); + + SDL_TrayEntry *entry = SDL_InsertTrayEntryAt(menu, pos, label, flags); + + if (label) JS_FreeCString(js, label); + + if (!entry) return JS_NULL; + return SDL_TrayEntry2js(js, entry); +) + +JSC_CCALL(traymenu_get_parent_tray, + SDL_TrayMenu *menu = js2SDL_TrayMenu(js, self); + SDL_Tray *tray = SDL_GetTrayMenuParentTray(menu); + if (!tray) return JS_NULL; + return SDL_Tray2js(js, tray); +) + +JSC_CCALL(traymenu_get_parent_entry, + SDL_TrayMenu *menu = js2SDL_TrayMenu(js, self); + SDL_TrayEntry *entry = SDL_GetTrayMenuParentEntry(menu); + if (!entry) return JS_NULL; + return SDL_TrayEntry2js(js, entry); +) + +// TrayEntry instance methods +JSC_CCALL(trayentry_create_submenu, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + SDL_TrayMenu *menu = SDL_CreateTraySubmenu(entry); + if (!menu) return JS_NULL; + return SDL_TrayMenu2js(js, menu); +) + +JSC_CCALL(trayentry_get_submenu, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + SDL_TrayMenu *menu = SDL_GetTraySubmenu(entry); + if (!menu) return JS_NULL; + return SDL_TrayMenu2js(js, menu); +) + +JSC_CCALL(trayentry_set_label, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + const char *label = JS_ToCString(js, argv[0]); + if (!label) return JS_EXCEPTION; + SDL_SetTrayEntryLabel(entry, label); + JS_FreeCString(js, label); + return JS_NULL; +) + +JSC_CCALL(trayentry_get_label, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + const char *label = SDL_GetTrayEntryLabel(entry); + return label ? JS_NewString(js, label) : JS_NULL; +) + +JSC_CCALL(trayentry_set_checked, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + bool checked = JS_ToBool(js, argv[0]); + SDL_SetTrayEntryChecked(entry, checked); + return JS_NULL; +) + +JSC_CCALL(trayentry_get_checked, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + return JS_NewBool(js, SDL_GetTrayEntryChecked(entry)); +) + +JSC_CCALL(trayentry_set_enabled, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + bool enabled = JS_ToBool(js, argv[0]); + SDL_SetTrayEntryEnabled(entry, enabled); + return JS_NULL; +) + +JSC_CCALL(trayentry_get_enabled, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + return JS_NewBool(js, SDL_GetTrayEntryEnabled(entry)); +) + +JSC_CCALL(trayentry_get_parent, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + SDL_TrayMenu *menu = SDL_GetTrayEntryParent(entry); + if (!menu) return JS_NULL; + return SDL_TrayMenu2js(js, menu); +) + +JSC_CCALL(trayentry_remove, + SDL_TrayEntry *entry = js2SDL_TrayEntry(js, self); + SDL_RemoveTrayEntry(entry); + return JS_NULL; +) + +static const JSCFunctionListEntry js_SDL_Tray_funcs[] = { + MIST_FUNC_DEF(tray, set_icon, 1), + MIST_FUNC_DEF(tray, set_tooltip, 1), + MIST_FUNC_DEF(tray, create_menu, 0), + MIST_FUNC_DEF(tray, get_menu, 0), + MIST_FUNC_DEF(tray, destroy, 0), +}; + +static const JSCFunctionListEntry js_SDL_TrayMenu_funcs[] = { + MIST_FUNC_DEF(traymenu, get_entries, 0), + MIST_FUNC_DEF(traymenu, insert_entry, 3), + MIST_FUNC_DEF(traymenu, get_parent_tray, 0), + MIST_FUNC_DEF(traymenu, get_parent_entry, 0), +}; + +static const JSCFunctionListEntry js_SDL_TrayEntry_funcs[] = { + MIST_FUNC_DEF(trayentry, create_submenu, 0), + MIST_FUNC_DEF(trayentry, get_submenu, 0), + MIST_FUNC_DEF(trayentry, set_label, 1), + MIST_FUNC_DEF(trayentry, get_label, 0), + MIST_FUNC_DEF(trayentry, set_checked, 1), + MIST_FUNC_DEF(trayentry, get_checked, 0), + MIST_FUNC_DEF(trayentry, set_enabled, 1), + MIST_FUNC_DEF(trayentry, get_enabled, 0), + MIST_FUNC_DEF(trayentry, get_parent, 0), + MIST_FUNC_DEF(trayentry, remove, 0), +}; + +static const JSCFunctionListEntry js_tray_funcs[] = { + MIST_FUNC_DEF(tray, create, 2), +}; + +SDL_Surface *js2SDL_Surface(JSContext *js, JSValue val); + +CELL_USE_INIT( + QJSCLASSPREP_FUNCS(SDL_Tray); + QJSCLASSPREP_FUNCS(SDL_TrayMenu); + QJSCLASSPREP_FUNCS(SDL_TrayEntry); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ret, js_tray_funcs, countof(js_tray_funcs)); + + // Export entry flags + JS_SetPropertyStr(js, ret, "BUTTON", JS_NewUint32(js, SDL_TRAYENTRY_BUTTON)); + JS_SetPropertyStr(js, ret, "CHECKBOX", JS_NewUint32(js, SDL_TRAYENTRY_CHECKBOX)); + JS_SetPropertyStr(js, ret, "SUBMENU", JS_NewUint32(js, SDL_TRAYENTRY_SUBMENU)); + JS_SetPropertyStr(js, ret, "DISABLED", JS_NewUint32(js, SDL_TRAYENTRY_DISABLED)); + JS_SetPropertyStr(js, ret, "CHECKED", JS_NewUint32(js, SDL_TRAYENTRY_CHECKED)); + + return ret; +) diff --git a/video.c b/video.c new file mode 100644 index 0000000..0bf9673 --- /dev/null +++ b/video.c @@ -0,0 +1,759 @@ +#include "cell.h" + +#include +#include +#include +#include +#include +#include +#include +#include "sdl.h" + +// SDL Window free function +void SDL_Window_free(JSRuntime *rt, SDL_Window *w) +{ + SDL_DestroyWindow(w); +} + +QJSCLASS(SDL_Window,) + +void SDL_Cursor_free(JSRuntime *rt, SDL_Cursor *c) +{ + SDL_DestroyCursor(c); +} + +QJSCLASS(SDL_Cursor,) + +// Forward declarations for blend mode helpers +static JSValue blendmode2js(JSContext *js, SDL_BlendMode mode); +static SDL_BlendMode js2blendmode(JSContext *js, JSValue v); + +// Window constructor function +static JSValue js_SDL_Window_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) +{ + if (argc < 1 || !JS_IsObject(argv[0])) + return JS_ThrowTypeError(js, "Window constructor requires an object argument"); + + JSValue opts = argv[0]; + + // Get basic properties (defaults are handled in JavaScript) + const char *title = NULL; + JSValue title_val = JS_GetPropertyStr(js, opts, "title"); + if (!JS_IsNull(title_val)) { + title = JS_ToCString(js, title_val); + } + JS_FreeValue(js, title_val); + + if (!title) { + return JS_ThrowTypeError(js, "Window title is required"); + } + + int width = 640; + JSValue width_val = JS_GetPropertyStr(js, opts, "width"); + if (!JS_IsNull(width_val)) { + width = js2number(js, width_val); + } + JS_FreeValue(js, width_val); + + int height = 480; + JSValue height_val = JS_GetPropertyStr(js, opts, "height"); + if (!JS_IsNull(height_val)) { + height = js2number(js, height_val); + } + JS_FreeValue(js, height_val); + + // Create SDL properties object + SDL_PropertiesID props = SDL_CreateProperties(); + + // Always set basic properties + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title); + + // Handle window position + JSValue x_val = JS_GetPropertyStr(js, opts, "x"); + if (!JS_IsNull(x_val)) { + if (JS_IsString(x_val)) { + const char *pos = JS_ToCString(js, x_val); + if (strcmp(pos, "centered") == 0) + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + else + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED); + JS_FreeCString(js, pos); + } else { + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, js2number(js, x_val)); + } + } + JS_FreeValue(js, x_val); + + JSValue y_val = JS_GetPropertyStr(js, opts, "y"); + if (!JS_IsNull(y_val)) { + if (JS_IsString(y_val)) { + const char *pos = JS_ToCString(js, y_val); + if (strcmp(pos, "centered") == 0) + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + else + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED); + JS_FreeCString(js, pos); + } else { + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, js2number(js, y_val)); + } + } + JS_FreeValue(js, y_val); + + // Helper function to check and set boolean properties + #define SET_BOOL_PROP(js_name, sdl_prop) do { \ + JSValue val = JS_GetPropertyStr(js, opts, js_name); \ + if (!JS_IsNull(val)) { \ + SDL_SetBooleanProperty(props, sdl_prop, JS_ToBool(js, val)); \ + } \ + JS_FreeValue(js, val); \ + } while(0) + + // Set all boolean properties directly on the SDL properties object + SET_BOOL_PROP("resizable", SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN); + SET_BOOL_PROP("fullscreen", SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN); + SET_BOOL_PROP("hidden", SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN); + SET_BOOL_PROP("borderless", SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN); + SET_BOOL_PROP("alwaysOnTop", SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN); + SET_BOOL_PROP("minimized", SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN); + SET_BOOL_PROP("maximized", SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN); + SET_BOOL_PROP("mouseGrabbed", SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN); + SET_BOOL_PROP("highPixelDensity", SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN); + SET_BOOL_PROP("transparent", SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN); + SET_BOOL_PROP("utility", SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN); + SET_BOOL_PROP("tooltip", SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN); + SET_BOOL_PROP("popupMenu", SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN); + SET_BOOL_PROP("opengl", SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN); + SET_BOOL_PROP("vulkan", SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN); + SET_BOOL_PROP("metal", SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN); + SET_BOOL_PROP("modal", SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN); + SET_BOOL_PROP("externalGraphicsContext", SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN); + + // Handle focusable (inverse logic) + JSValue focusable_val = JS_GetPropertyStr(js, opts, "focusable"); + if (!JS_IsNull(focusable_val)) { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, JS_ToBool(js, focusable_val)); + } + JS_FreeValue(js, focusable_val); + + // Handle notFocusable (for backwards compatibility) + JSValue not_focusable_val = JS_GetPropertyStr(js, opts, "notFocusable"); + if (!JS_IsNull(not_focusable_val)) { + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !JS_ToBool(js, not_focusable_val)); + } + JS_FreeValue(js, not_focusable_val); + + #undef SET_BOOL_PROP + + // Handle parent window + JSValue parent_val = JS_GetPropertyStr(js, opts, "parent"); + if (!JS_IsNull(parent_val)) { + SDL_Window *parent = js2SDL_Window(js, parent_val); + if (parent) { + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, parent); + } + } + JS_FreeValue(js, parent_val); + + // Create window with properties + SDL_Window *window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + + // Always free the title string since we allocated it + if (title) { + JS_FreeCString(js, title); + } + + if (!window) { + return JS_ThrowReferenceError(js, "Failed to create window: %s", SDL_GetError()); + } + + // Create the window JS object + JSValue window_obj = SDL_Window2js(js, window); + + // Set additional properties that can't be set during creation + // These will be applied through the property setters + + JSValue opacity_val = JS_GetPropertyStr(js, opts, "opacity"); + if (!JS_IsNull(opacity_val)) { + JS_SetPropertyStr(js, window_obj, "opacity", opacity_val); + } + + JSValue min_size_val = JS_GetPropertyStr(js, opts, "minimumSize"); + if (!JS_IsNull(min_size_val)) { + JS_SetPropertyStr(js, window_obj, "minimumSize", min_size_val); + } + + JSValue max_size_val = JS_GetPropertyStr(js, opts, "maximumSize"); + if (!JS_IsNull(max_size_val)) { + JS_SetPropertyStr(js, window_obj, "maximumSize", max_size_val); + } + + JSValue pos_val = JS_GetPropertyStr(js, opts, "position"); + if (!JS_IsNull(pos_val)) { + JS_SetPropertyStr(js, window_obj, "position", pos_val); + } + + // Handle text input + JSValue text_input = JS_GetPropertyStr(js, opts, "textInput"); + if (JS_ToBool(js, text_input)) { +// SDL_StartTextInput(window); + } + JS_FreeValue(js, text_input); + + printf("created window %p\n", window); + + return window_obj; +} + +JSC_CCALL(SDL_Window_fullscreen, + SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN) +) + +JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_Window *window = js2SDL_Window(js,self); + return JS_NewBool(js,SDL_ScreenKeyboardShown(window)); +} + +JSValue js_window_theme(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_NULL; +} + +JSValue js_window_safe_area(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Rect r; + SDL_GetWindowSafeArea(w, &r); + rect newr; + SDL_RectToFRect(&r, &newr); + return rect2js(js,newr); +} + +JSValue js_window_bordered(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowBordered(w, JS_ToBool(js,argv[0])); + return JS_NULL; +} + +JSValue js_window_get_title(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + const char *title = SDL_GetWindowTitle(w); + return JS_NewString(js,title); +} + +JSValue js_window_set_title(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + const char *title = JS_ToCString(js,val); + SDL_SetWindowTitle(w,title); + JS_FreeCString(js,title); + return JS_NULL; +} + +JSValue js_window_get_size(JSContext *js, JSValue self) +{ + SDL_Window *win = js2SDL_Window(js,self); + int w, h; + SDL_GetWindowSize(win, &w, &h); + return vec22js(js, (vec2){w,h}); +} + +JSValue js_window_set_size(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + vec2 size = js2vec2(js,val); + SDL_SetWindowSize(w,size.x,size.y); + return JS_NULL; +} + +JSValue js_window_set_icon(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Surface *s = js2SDL_Surface(js,argv[0]); + if (!SDL_SetWindowIcon(w,s)) + return JS_ThrowReferenceError(js, "could not set window icon: %s", SDL_GetError()); + return JS_NULL; +} + +// Position getter/setter +JSValue js_window_get_position(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + int x, y; + SDL_GetWindowPosition(w, &x, &y); + return vec22js(js, (vec2){x,y}); +} + +JSValue js_window_set_position(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + vec2 pos = js2vec2(js,val); + SDL_SetWindowPosition(w,pos.x,pos.y); + return JS_NULL; +} + +// Mouse grab getter/setter +JSValue js_window_get_mouseGrab(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return JS_NewBool(js, SDL_GetWindowMouseGrab(w)); +} + +JSValue js_window_set_mouseGrab(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowMouseGrab(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Keyboard grab getter/setter +JSValue js_window_get_keyboardGrab(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return JS_NewBool(js, SDL_GetWindowKeyboardGrab(w)); +} + +JSValue js_window_set_keyboardGrab(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowKeyboardGrab(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Opacity getter/setter +JSValue js_window_get_opacity(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return number2js(js, SDL_GetWindowOpacity(w)); +} + +JSValue js_window_set_opacity(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + float opacity = js2number(js,val); + SDL_SetWindowOpacity(w, opacity); + return JS_NULL; +} + +// Minimum size getter/setter +JSValue js_window_get_minimumSize(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + int width, height; + SDL_GetWindowMinimumSize(w, &width, &height); + return vec22js(js, (vec2){width,height}); +} + +JSValue js_window_set_minimumSize(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + vec2 size = js2vec2(js,val); + SDL_SetWindowMinimumSize(w,size.x,size.y); + return JS_NULL; +} + +// Maximum size getter/setter +JSValue js_window_get_maximumSize(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + int width, height; + SDL_GetWindowMaximumSize(w, &width, &height); + return vec22js(js, (vec2){width,height}); +} + +JSValue js_window_set_maximumSize(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + vec2 size = js2vec2(js,val); + SDL_SetWindowMaximumSize(w,size.x,size.y); + return JS_NULL; +} + +// Resizable setter (read from flags) +JSValue js_window_get_resizable(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_RESIZABLE); +} + +JSValue js_window_set_resizable(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowResizable(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Bordered getter/setter +JSValue js_window_get_bordered(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, !(flags & SDL_WINDOW_BORDERLESS)); +} + +JSValue js_window_set_bordered(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowBordered(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Always on top getter/setter +JSValue js_window_get_alwaysOnTop(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_ALWAYS_ON_TOP); +} + +JSValue js_window_set_alwaysOnTop(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowAlwaysOnTop(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Fullscreen getter/setter +JSValue js_window_get_fullscreen(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_FULLSCREEN); +} + +JSValue js_window_set_fullscreen(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowFullscreen(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Focusable setter +JSValue js_window_get_focusable(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, !(flags & SDL_WINDOW_NOT_FOCUSABLE)); +} + +JSValue js_window_set_focusable(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowFocusable(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Modal setter +JSValue js_window_get_modal(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_MODAL); +} + +JSValue js_window_set_modal(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SetWindowModal(w, JS_ToBool(js,val)); + return JS_NULL; +} + +// Hidden/visible state +JSValue js_window_get_visible(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, !(flags & SDL_WINDOW_HIDDEN)); +} + +JSValue js_window_set_visible(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + if (JS_ToBool(js,val)) + SDL_ShowWindow(w); + else + SDL_HideWindow(w); + return JS_NULL; +} + +// Minimized state +JSValue js_window_get_minimized(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_MINIMIZED); +} + +JSValue js_window_set_minimized(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + if (JS_ToBool(js,val)) + SDL_MinimizeWindow(w); + else + SDL_RestoreWindow(w); + return JS_NULL; +} + +// Maximized state +JSValue js_window_get_maximized(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + return JS_NewBool(js, flags & SDL_WINDOW_MAXIMIZED); +} + +JSValue js_window_set_maximized(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + if (JS_ToBool(js,val)) + SDL_MaximizeWindow(w); + else + SDL_RestoreWindow(w); + return JS_NULL; +} + +// Other window methods +JSValue js_window_raise(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_RaiseWindow(w); + return JS_NULL; +} + +JSValue js_window_restore(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_RestoreWindow(w); + return JS_NULL; +} + +JSValue js_window_flash(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_FlashOperation op = SDL_FLASH_BRIEFLY; + if (argc > 0 && JS_IsString(argv[0])) { + const char *operation = JS_ToCString(js,argv[0]); + if (strcmp(operation, "cancel") == 0) op = SDL_FLASH_CANCEL; + else if (strcmp(operation, "briefly") == 0) op = SDL_FLASH_BRIEFLY; + else if (strcmp(operation, "until_focused") == 0) op = SDL_FLASH_UNTIL_FOCUSED; + JS_FreeCString(js,operation); + } + SDL_FlashWindow(w, op); + return JS_NULL; +} + +JSValue js_window_destroy(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_DestroyWindow(w); + return JS_NULL; +} + +JSValue js_window_get_id(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return number2js(js, SDL_GetWindowID(w)); +} + +JSValue js_window_get_parent(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Window *parent = SDL_GetWindowParent(w); + if (!parent) return JS_NULL; + return SDL_Window2js(js, parent); +} + +JSValue js_window_set_parent(JSContext *js, JSValue self, JSValue val) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Window *parent = NULL; + if (!JS_IsNull(val)) + parent = js2SDL_Window(js,val); + SDL_SetWindowParent(w, parent); + return JS_NULL; +} + +JSValue js_window_get_pixelDensity(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return number2js(js, SDL_GetWindowPixelDensity(w)); +} + +JSValue js_window_get_displayScale(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + return number2js(js, SDL_GetWindowDisplayScale(w)); +} + +JSValue js_window_get_sizeInPixels(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + int width, height; + SDL_GetWindowSizeInPixels(w, &width, &height); + return vec22js(js, (vec2){width,height}); +} + +// Surface related +JSValue js_window_get_surface(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Surface *surf = SDL_GetWindowSurface(w); + if (!surf) return JS_NULL; + return SDL_Surface2js(js, surf); +} + +JSValue js_window_updateSurface(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + if (!SDL_UpdateWindowSurface(w)) + return JS_ThrowReferenceError(js, "Failed to update window surface: %s", SDL_GetError()); + return JS_NULL; +} + +JSValue js_window_updateSurfaceRects(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + + if (!JS_IsArray(js, argv[0])) + return JS_ThrowTypeError(js, "Expected array of rectangles"); + + int len = JS_ArrayLength(js, argv[0]); + SDL_Rect rects[len]; + + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js, argv[0], i); + rect r = js2rect(js, val); + rects[i] = (SDL_Rect){r.x, r.y, r.w, r.h}; + JS_FreeValue(js, val); + } + + if (!SDL_UpdateWindowSurfaceRects(w, rects, len)) + return JS_ThrowReferenceError(js, "Failed to update window surface rects: %s", SDL_GetError()); + return JS_NULL; +} + +JSValue js_window_get_flags(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_WindowFlags flags = SDL_GetWindowFlags(w); + + JSValue ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "fullscreen", JS_NewBool(js, flags & SDL_WINDOW_FULLSCREEN)); + JS_SetPropertyStr(js, ret, "opengl", JS_NewBool(js, flags & SDL_WINDOW_OPENGL)); + JS_SetPropertyStr(js, ret, "occluded", JS_NewBool(js, flags & SDL_WINDOW_OCCLUDED)); + JS_SetPropertyStr(js, ret, "hidden", JS_NewBool(js, flags & SDL_WINDOW_HIDDEN)); + JS_SetPropertyStr(js, ret, "borderless", JS_NewBool(js, flags & SDL_WINDOW_BORDERLESS)); + JS_SetPropertyStr(js, ret, "resizable", JS_NewBool(js, flags & SDL_WINDOW_RESIZABLE)); + JS_SetPropertyStr(js, ret, "minimized", JS_NewBool(js, flags & SDL_WINDOW_MINIMIZED)); + JS_SetPropertyStr(js, ret, "maximized", JS_NewBool(js, flags & SDL_WINDOW_MAXIMIZED)); + JS_SetPropertyStr(js, ret, "mouseGrabbed", JS_NewBool(js, flags & SDL_WINDOW_MOUSE_GRABBED)); + JS_SetPropertyStr(js, ret, "inputFocus", JS_NewBool(js, flags & SDL_WINDOW_INPUT_FOCUS)); + JS_SetPropertyStr(js, ret, "mouseFocus", JS_NewBool(js, flags & SDL_WINDOW_MOUSE_FOCUS)); + JS_SetPropertyStr(js, ret, "external", JS_NewBool(js, flags & SDL_WINDOW_EXTERNAL)); + JS_SetPropertyStr(js, ret, "modal", JS_NewBool(js, flags & SDL_WINDOW_MODAL)); + JS_SetPropertyStr(js, ret, "highPixelDensity", JS_NewBool(js, flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)); + JS_SetPropertyStr(js, ret, "mouseCapture", JS_NewBool(js, flags & SDL_WINDOW_MOUSE_CAPTURE)); + JS_SetPropertyStr(js, ret, "mouseRelativeMode", JS_NewBool(js, flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)); + JS_SetPropertyStr(js, ret, "alwaysOnTop", JS_NewBool(js, flags & SDL_WINDOW_ALWAYS_ON_TOP)); + JS_SetPropertyStr(js, ret, "utility", JS_NewBool(js, flags & SDL_WINDOW_UTILITY)); + JS_SetPropertyStr(js, ret, "tooltip", JS_NewBool(js, flags & SDL_WINDOW_TOOLTIP)); + JS_SetPropertyStr(js, ret, "popupMenu", JS_NewBool(js, flags & SDL_WINDOW_POPUP_MENU)); + JS_SetPropertyStr(js, ret, "keyboardGrabbed", JS_NewBool(js, flags & SDL_WINDOW_KEYBOARD_GRABBED)); + JS_SetPropertyStr(js, ret, "vulkan", JS_NewBool(js, flags & SDL_WINDOW_VULKAN)); + JS_SetPropertyStr(js, ret, "metal", JS_NewBool(js, flags & SDL_WINDOW_METAL)); + JS_SetPropertyStr(js, ret, "transparent", JS_NewBool(js, flags & SDL_WINDOW_TRANSPARENT)); + JS_SetPropertyStr(js, ret, "notFocusable", JS_NewBool(js, flags & SDL_WINDOW_NOT_FOCUSABLE)); + + return ret; +} + +JSValue js_window_sync(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_SyncWindow(w); + return JS_NULL; +} + +static const JSCFunctionListEntry js_SDL_Window_funcs[] = { + MIST_FUNC_DEF(SDL_Window, fullscreen, 0), + MIST_FUNC_DEF(SDL_Window, keyboard_shown, 0), + MIST_FUNC_DEF(window, theme, 0), + MIST_FUNC_DEF(window, safe_area, 0), + MIST_FUNC_DEF(window, set_icon, 1), + MIST_FUNC_DEF(window, raise, 0), + MIST_FUNC_DEF(window, restore, 0), + MIST_FUNC_DEF(window, flash, 1), + MIST_FUNC_DEF(window, destroy, 0), + MIST_FUNC_DEF(window, sync, 0), + CGETSET_ADD(window, title), + CGETSET_ADD(window, size), + CGETSET_ADD(window, position), + CGETSET_ADD(window, mouseGrab), + CGETSET_ADD(window, keyboardGrab), + CGETSET_ADD(window, opacity), + CGETSET_ADD(window, minimumSize), + CGETSET_ADD(window, maximumSize), + CGETSET_ADD(window, resizable), + CGETSET_ADD(window, bordered), + CGETSET_ADD(window, alwaysOnTop), + CGETSET_ADD(window, fullscreen), + CGETSET_ADD(window, focusable), + CGETSET_ADD(window, modal), + CGETSET_ADD(window, visible), + CGETSET_ADD(window, minimized), + CGETSET_ADD(window, maximized), + CGETSET_ADD(window, parent), + JS_CGETSET_DEF("id", js_window_get_id, NULL), + JS_CGETSET_DEF("pixelDensity", js_window_get_pixelDensity, NULL), + JS_CGETSET_DEF("displayScale", js_window_get_displayScale, NULL), + JS_CGETSET_DEF("sizeInPixels", js_window_get_sizeInPixels, NULL), + JS_CGETSET_DEF("flags", js_window_get_flags, NULL), + JS_CGETSET_DEF("surface", js_window_get_surface, NULL), + MIST_FUNC_DEF(window, updateSurface, 0), + MIST_FUNC_DEF(window, updateSurfaceRects, 1), +}; + +// Cursor creation function +JSC_CCALL(sdl_create_cursor, + SDL_Surface *surf = js2SDL_Surface(js, argv[0]); + if (!surf) return JS_ThrowReferenceError(js, "Invalid surface"); + + vec2 hot = {0, 0}; + if (argc > 1) hot = js2vec2(js, argv[1]); + + SDL_Cursor *cursor = SDL_CreateColorCursor(surf, hot.x, hot.y); + if (!cursor) return JS_ThrowReferenceError(js, "Failed to create cursor: %s", SDL_GetError()); + + return SDL_Cursor2js(js, cursor); +) + +// Set cursor function +JSC_CCALL(sdl_set_cursor, + SDL_Cursor *cursor = js2SDL_Cursor(js, argv[0]); + + if (!cursor) return JS_ThrowReferenceError(js, "Invalid cursor"); + + SDL_SetCursor(cursor); +) + +CELL_USE_INIT( + if (!SDL_Init(SDL_INIT_VIDEO)) + return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError()); + + JSValue ret = JS_NewObject(js); + + JS_SetPropertyStr(js, ret, "window", QJSCLASSPREP_FUNCS_CTOR(SDL_Window, 1)); + + QJSCLASSPREP_NO_FUNCS(SDL_Cursor); + + // Add cursor functions + JS_SetPropertyStr(js, ret, "createCursor", JS_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2)); + JS_SetPropertyStr(js, ret, "setCursor", JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1)); + + return ret; +)