677 lines
27 KiB
C
677 lines
27 KiB
C
#include <stdint.h>
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3_image/SDL_image.h>
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
|
#include <lean/lean.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
static lean_external_class * sdl_texture_external_class = NULL;
|
|
|
|
// finalizer is basically the destructor for the external object
|
|
static void sdl_texture_finalizer(void * h) {
|
|
// destroy the texture on finalization
|
|
SDL_DestroyTexture((SDL_Texture*)h);
|
|
}
|
|
|
|
// this is not needed for our use case, but must be defined
|
|
static void sdl_texture_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_window_external_class = NULL;
|
|
|
|
// finalizer is basically the destructor for the external object
|
|
static void sdl_window_finalizer(void * h) {
|
|
// destroy the window on finalization
|
|
SDL_DestroyWindow((SDL_Window*)h);
|
|
}
|
|
|
|
|
|
static void sdl_window_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_renderer_external_class = NULL;
|
|
|
|
// finalizer is basically the destructor for the external object
|
|
static void sdl_renderer_finalizer(void * h) {
|
|
// destroy the renderer on finalization
|
|
SDL_DestroyRenderer((SDL_Renderer*)h);
|
|
}
|
|
|
|
|
|
static void sdl_renderer_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_surface_external_class = NULL;
|
|
|
|
// finalizer is basically the destructor for the external object
|
|
static void sdl_surface_finalizer(void * h) {
|
|
// destroy the surface on finalization
|
|
SDL_DestroySurface((SDL_Surface*)h);
|
|
}
|
|
|
|
|
|
static void sdl_surface_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_ttf_font_external_class = NULL;
|
|
|
|
static void sdl_ttf_font_finalizer(void * h) {
|
|
TTF_CloseFont((TTF_Font*)h);
|
|
}
|
|
|
|
static void sdl_ttf_font_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_mixer_external_class = NULL;
|
|
|
|
static void sdl_mixer_finalizer(void * h) {
|
|
MIX_DestroyMixer((MIX_Mixer*)h);
|
|
}
|
|
|
|
static void sdl_mixer_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_mixer_track_external_class = NULL;
|
|
|
|
static void sdl_mixer_track_finalizer(void * h) {
|
|
MIX_DestroyTrack((MIX_Track*)h);
|
|
}
|
|
|
|
static void sdl_mixer_track_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_mixer_audio_external_class = NULL;
|
|
|
|
static void sdl_mixer_audio_finalizer(void * h) {
|
|
MIX_DestroyAudio((MIX_Audio*)h);
|
|
}
|
|
|
|
static void sdl_mixer_audio_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
|
|
static lean_external_class * sdl_camera_external_class = NULL;
|
|
static void sdl_camera_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
static void sdl_camera_finalizer(void * h) {
|
|
SDL_CloseCamera((SDL_Camera*)h);
|
|
}
|
|
static lean_external_class * sdl_camera_spec_external_class = NULL;
|
|
static void sdl_camera_spec_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
static void sdl_camera_spec_finalizer(void * h) {
|
|
free(h);
|
|
}
|
|
|
|
static lean_external_class * sdl_camera_frame_external_class = NULL;
|
|
static void sdl_camera_frame_foreach(void * val, lean_obj_arg fn) {
|
|
|
|
}
|
|
// Camera frames must be explicitly released via SDL_ReleaseCameraFrame, not destroyed
|
|
static void sdl_camera_frame_finalizer(void * h) {
|
|
// No-op - if we get here without release, frame is leaked to the camera system
|
|
}
|
|
|
|
lean_obj_res sdl_init(uint32_t flags, lean_obj_arg w) {
|
|
int32_t result = SDL_Init(flags);
|
|
|
|
// create reference to external class for SDL_Texture
|
|
sdl_texture_external_class = lean_register_external_class(sdl_texture_finalizer, sdl_texture_foreach);
|
|
sdl_window_external_class = lean_register_external_class(sdl_window_finalizer, sdl_window_foreach);
|
|
sdl_renderer_external_class = lean_register_external_class(sdl_renderer_finalizer, sdl_renderer_foreach);
|
|
sdl_surface_external_class = lean_register_external_class(sdl_surface_finalizer, sdl_surface_foreach);
|
|
sdl_camera_external_class = lean_register_external_class(sdl_camera_finalizer, sdl_camera_foreach);
|
|
sdl_camera_spec_external_class = lean_register_external_class(sdl_camera_spec_finalizer, sdl_camera_spec_foreach);
|
|
sdl_camera_frame_external_class = lean_register_external_class(sdl_camera_frame_finalizer, sdl_camera_frame_foreach);
|
|
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_ttf_init(lean_obj_arg w) {
|
|
bool result = TTF_Init();
|
|
|
|
sdl_ttf_font_external_class = lean_register_external_class(sdl_ttf_font_finalizer, sdl_ttf_font_foreach);
|
|
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_mixer_init(lean_obj_arg w) {
|
|
bool result = MIX_Init();
|
|
|
|
sdl_mixer_external_class = lean_register_external_class(sdl_mixer_finalizer, sdl_mixer_foreach);
|
|
sdl_mixer_track_external_class = lean_register_external_class(sdl_mixer_track_finalizer, sdl_mixer_track_foreach);
|
|
sdl_mixer_audio_external_class = lean_register_external_class(sdl_mixer_audio_finalizer, sdl_mixer_audio_foreach);
|
|
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_create_mixer(lean_obj_arg w) {
|
|
MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
|
|
if (mixer == NULL) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* external_mixer = lean_alloc_external(sdl_mixer_external_class, mixer);
|
|
return lean_io_result_mk_ok(external_mixer);
|
|
}
|
|
|
|
|
|
lean_obj_res sdl_quit(lean_obj_arg w) {
|
|
SDL_Quit();
|
|
return lean_io_result_mk_ok(lean_box(0));
|
|
}
|
|
|
|
lean_obj_res sdl_create_window(b_lean_obj_arg title, uint32_t w, uint32_t h, uint32_t flags) {
|
|
const char* title_str = lean_string_cstr(title);
|
|
SDL_Window* g_window = SDL_CreateWindow(title_str, (int)w, (int)h, flags);
|
|
if (g_window == NULL) {
|
|
return lean_io_result_mk_error(lean_mk_string("C: SDL_CreateWindow failed"));
|
|
}
|
|
lean_object* external_window = lean_alloc_external(sdl_window_external_class, g_window);
|
|
return lean_io_result_mk_ok(external_window);
|
|
}
|
|
|
|
lean_obj_res sdl_create_renderer(b_lean_obj_arg g_window) {
|
|
SDL_Window* window = (SDL_Window*)lean_get_external_data(g_window);
|
|
SDL_Renderer* g_renderer = SDL_CreateRenderer(window, NULL);
|
|
if (g_renderer == NULL) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* external_renderer = lean_alloc_external(sdl_renderer_external_class, g_renderer);
|
|
return lean_io_result_mk_ok(external_renderer);
|
|
}
|
|
|
|
lean_obj_res sdl_create_window_and_renderer(b_lean_obj_arg title, uint32_t w, uint32_t h, uint32_t flags) {
|
|
const char* title_str = lean_string_cstr(title);
|
|
SDL_Window* g_window = NULL;
|
|
SDL_Renderer* g_renderer = NULL;
|
|
|
|
//TODO: use SDL_GetError()
|
|
if (!SDL_CreateWindowAndRenderer(title_str, (int)w, (int)h, flags, &g_window, &g_renderer)) {
|
|
return lean_io_result_mk_error(lean_mk_string("C: SDL_CreateWindowAndRenderer failed"));
|
|
}
|
|
|
|
// Wrap the SDL objects in Lean external objects
|
|
lean_object* external_window = lean_alloc_external(sdl_window_external_class, g_window);
|
|
lean_object* external_renderer = lean_alloc_external(sdl_renderer_external_class, g_renderer);
|
|
|
|
// Create a Prod (pair) - Prod.mk has tag 0, 2 object fields, 0 scalar fields
|
|
lean_object* pair = lean_alloc_ctor(0, 2, 0);
|
|
lean_ctor_set(pair, 0, external_window); // first field (fst)
|
|
lean_ctor_set(pair, 1, external_renderer); // second field (snd)
|
|
|
|
return lean_io_result_mk_ok(pair);
|
|
}
|
|
|
|
lean_obj_res sdl_set_render_draw_color(b_lean_obj_arg g_renderer, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
if (renderer == NULL) return lean_io_result_mk_error(lean_mk_string("C: Renderer is NULL"));
|
|
int32_t result = SDL_SetRenderDrawColor(renderer, r, g, b, a);
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_set_render_draw_color_float(b_lean_obj_arg renderer_obj, double r, double g, double b, double a) {
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(renderer_obj);
|
|
if (renderer == NULL) return lean_io_result_mk_error(lean_mk_string("C: Renderer is NULL"));
|
|
bool result = SDL_SetRenderDrawColorFloat(renderer, (float)r, (float)g, (float)b, (float)a);
|
|
return lean_io_result_mk_ok(lean_box(result));
|
|
}
|
|
|
|
lean_obj_res sdl_render_clear(b_lean_obj_arg g_renderer, lean_obj_arg w) {
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
if (renderer == NULL) return lean_io_result_mk_error(lean_mk_string("C: Renderer is NULL"));
|
|
int32_t result = SDL_RenderClear(renderer);
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_render_present(b_lean_obj_arg g_renderer, lean_obj_arg w) {
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
if (renderer == NULL) return lean_io_result_mk_error(lean_mk_io_user_error(lean_mk_string("C: Renderer is NULL")));
|
|
SDL_RenderPresent(renderer);
|
|
return lean_io_result_mk_ok(lean_box(0));
|
|
}
|
|
|
|
static SDL_Rect lean_sdl_rect_to_c_SDL_Rect(b_lean_obj_arg rect_obj) {
|
|
return (SDL_Rect) {
|
|
.x = (int32_t)lean_ctor_get_uint32(rect_obj, 0),
|
|
.y = (int32_t)lean_ctor_get_uint32(rect_obj, 4),
|
|
.w = (int32_t)lean_ctor_get_uint32(rect_obj, 8),
|
|
.h = (int32_t)lean_ctor_get_uint32(rect_obj, 12),
|
|
};
|
|
}
|
|
|
|
|
|
lean_obj_res sdl_render_fill_rect(b_lean_obj_arg g_renderer, b_lean_obj_arg rect_obj) {
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
if (renderer == NULL) return lean_io_result_mk_error(lean_mk_string("C: Renderer is NULL"));
|
|
|
|
SDL_Rect r = lean_sdl_rect_to_c_SDL_Rect(rect_obj);
|
|
SDL_FRect rect = {r.x, r.y, r.w, r.h};
|
|
int32_t result = SDL_RenderFillRect(renderer, &rect);
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_delay(uint32_t ms) {
|
|
SDL_Delay(ms);
|
|
return lean_io_result_mk_ok(lean_box(0));
|
|
}
|
|
|
|
lean_obj_res sdl_poll_event(lean_obj_arg w) {
|
|
SDL_Event event;
|
|
int has_event = SDL_PollEvent(&event);
|
|
return lean_io_result_mk_ok(lean_box_uint32(has_event ? event.type : 0));
|
|
}
|
|
|
|
lean_obj_res sdl_get_ticks(lean_obj_arg w) {
|
|
uint64_t ticks = SDL_GetTicks();
|
|
return lean_io_result_mk_ok(lean_box_uint64(ticks));
|
|
}
|
|
|
|
lean_obj_res sdl_get_key_state(uint32_t scancode, lean_obj_arg w) {
|
|
const uint8_t* state = (const uint8_t*)SDL_GetKeyboardState(NULL);
|
|
uint8_t pressed = state[scancode];
|
|
return lean_io_result_mk_ok(lean_box(pressed));
|
|
}
|
|
|
|
// TEXTURE SUPPORT
|
|
// Assuming 64x64 texture
|
|
lean_obj_res sdl_image_load(b_lean_obj_arg filename, lean_obj_arg w) {
|
|
const char* filename_str = lean_string_cstr(filename);
|
|
SDL_Surface* surface = IMG_Load(filename_str);
|
|
if (!surface) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* external_surface = lean_alloc_external(sdl_surface_external_class, surface);
|
|
return lean_io_result_mk_ok(external_surface);
|
|
}
|
|
|
|
uint32_t sdl_Surface_get_format(b_lean_obj_arg surface_obj) {
|
|
SDL_Surface* surface = (SDL_Surface*)lean_get_external_data(surface_obj);
|
|
return (uint32_t)surface->format;
|
|
}
|
|
|
|
int32_t sdl_Surface_get_w(b_lean_obj_arg surface_obj) {
|
|
SDL_Surface* surface = (SDL_Surface*)lean_get_external_data(surface_obj);
|
|
return (int32_t)surface->w;
|
|
}
|
|
|
|
int32_t sdl_Surface_get_h(b_lean_obj_arg surface_obj) {
|
|
SDL_Surface* surface = (SDL_Surface*)lean_get_external_data(surface_obj);
|
|
return (int32_t)surface->h;
|
|
}
|
|
|
|
int32_t sdl_Surface_get_pitch(b_lean_obj_arg surface_obj) {
|
|
SDL_Surface* surface = (SDL_Surface*)lean_get_external_data(surface_obj);
|
|
return (int32_t)surface->pitch;
|
|
}
|
|
|
|
lean_obj_res sdl_create_texture(b_lean_obj_arg renderer_obj, uint32_t pixel_format, uint32_t texture_access, uint32_t width, uint32_t height) {
|
|
SDL_Renderer * renderer = (SDL_Renderer *)lean_get_external_data(renderer_obj);
|
|
SDL_Texture* texture = SDL_CreateTexture(renderer, pixel_format, (SDL_TextureAccess)texture_access, width, height);
|
|
|
|
if (!texture) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* external_texture = lean_alloc_external(sdl_texture_external_class, texture);
|
|
return lean_io_result_mk_ok(external_texture);
|
|
}
|
|
|
|
lean_obj_res sdl_create_texture_from_surface(b_lean_obj_arg g_renderer, b_lean_obj_arg g_surface, lean_obj_arg w) {
|
|
SDL_Renderer * renderer = (SDL_Renderer *)lean_get_external_data(g_renderer);
|
|
SDL_Surface * surface = (SDL_Surface *)lean_get_external_data(g_surface);
|
|
if (!renderer || !surface)
|
|
{
|
|
return lean_io_result_mk_error(lean_mk_string("C: Invalid renderer or surface"));
|
|
}
|
|
SDL_Texture * g_texture = SDL_CreateTextureFromSurface(renderer, surface);
|
|
|
|
if (!g_texture) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
lean_object* external_texture = lean_alloc_external(sdl_texture_external_class, g_texture);
|
|
|
|
return lean_io_result_mk_ok(external_texture);
|
|
}
|
|
|
|
lean_obj_res sdl_update_texture(b_lean_obj_arg texture_obj, b_lean_obj_arg byte_array, int32_t pitch) {
|
|
SDL_Texture * texture = (SDL_Texture *)lean_get_external_data(texture_obj);
|
|
void* pixels = (void*)lean_sarray_cptr(byte_array);
|
|
|
|
bool result = SDL_UpdateTexture(texture, NULL, pixels, pitch);
|
|
return lean_io_result_mk_ok(lean_box(result));
|
|
}
|
|
|
|
// Zero-copy optimization: updates a texture directly from an SDL_Surface's
|
|
// pixel buffer, avoiding the intermediate ByteArray copy. This is not
|
|
// strictly faithful to the SDL_UpdateTexture C API (which takes a void*),
|
|
// but is safe because the surface is borrowed for the duration of the call,
|
|
// preventing the GC from freeing the pixel data mid-use.
|
|
lean_obj_res sdl_update_texture_from_surface(b_lean_obj_arg texture_obj, b_lean_obj_arg surface_obj) {
|
|
SDL_Texture * texture = (SDL_Texture *)lean_get_external_data(texture_obj);
|
|
SDL_Surface * surface = (SDL_Surface *)lean_get_external_data(surface_obj);
|
|
|
|
bool result = SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch);
|
|
return lean_io_result_mk_ok(lean_box(result));
|
|
}
|
|
|
|
lean_obj_res sdl_load_font(b_lean_obj_arg fontname, uint32_t font_size, lean_obj_arg w) {
|
|
const char* fontname_str = lean_string_cstr(fontname);
|
|
TTF_Font* font = TTF_OpenFont(fontname_str, font_size);
|
|
if (!font) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
lean_object* external_font = lean_alloc_external(sdl_ttf_font_external_class, font);
|
|
|
|
return lean_io_result_mk_ok(external_font);
|
|
}
|
|
|
|
lean_obj_res sdl_create_track(b_lean_obj_arg g_mixer, lean_obj_arg w) {
|
|
MIX_Mixer* mixer = (MIX_Mixer*)lean_get_external_data(g_mixer);
|
|
MIX_Track* mixerTrack = MIX_CreateTrack(mixer);
|
|
if (!mixerTrack) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
lean_object* external_track = lean_alloc_external(sdl_mixer_track_external_class, mixerTrack);
|
|
|
|
return lean_io_result_mk_ok(external_track);
|
|
}
|
|
|
|
|
|
lean_obj_res sdl_load_audio(b_lean_obj_arg g_mixer, b_lean_obj_arg filename, lean_obj_arg w) {
|
|
MIX_Mixer* mixer = (MIX_Mixer*)lean_get_external_data(g_mixer);
|
|
const char* filename_str = lean_string_cstr(filename);
|
|
MIX_Audio* audio = MIX_LoadAudio(mixer, filename_str, false);
|
|
if (!audio) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
lean_object* external_audio = lean_alloc_external(sdl_mixer_audio_external_class, audio);
|
|
|
|
return lean_io_result_mk_ok(external_audio);
|
|
}
|
|
|
|
lean_obj_res sdl_set_track_audio(b_lean_obj_arg g_track, b_lean_obj_arg g_audio, lean_obj_arg w) {
|
|
MIX_Track* track = (MIX_Track*)lean_get_external_data(g_track);
|
|
MIX_Audio* audio = (MIX_Audio*)lean_get_external_data(g_audio);
|
|
if (!track || !audio) {
|
|
return lean_io_result_mk_error(lean_mk_string("C: Invalid track or audio"));
|
|
}
|
|
bool result = MIX_SetTrackAudio(track, audio);
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
lean_obj_res sdl_play_track(b_lean_obj_arg g_track, lean_obj_arg w) {
|
|
MIX_Track* track = (MIX_Track*)lean_get_external_data(g_track);
|
|
if (!track) {
|
|
return lean_io_result_mk_error(lean_mk_string("C: Invalid track or audio"));
|
|
}
|
|
bool result = MIX_PlayTrack(track, 0);
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|
|
|
|
// TODO: VERY inefficient text rendering, re-renders entire text each time
|
|
lean_obj_res sdl_text_to_surface(b_lean_obj_arg g_renderer, b_lean_obj_arg g_font, b_lean_obj_arg text, uint32_t dst_x, uint32_t dst_y, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha, lean_obj_arg w) {
|
|
if (!g_renderer || !g_font) return lean_io_result_mk_error(lean_mk_string("C: Renderer or Font is NULL"));
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
TTF_Font* font = (TTF_Font*)lean_get_external_data(g_font);
|
|
const char* text_str = lean_string_cstr(text);
|
|
size_t text_len = lean_string_len(text);
|
|
SDL_Color color = { red, green, blue, alpha };
|
|
SDL_Surface* text_surface = TTF_RenderText_Blended(font, text_str, text_len, color);
|
|
if (!text_surface) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
lean_object* external_text_surface = lean_alloc_external(sdl_surface_external_class, text_surface);
|
|
|
|
return lean_io_result_mk_ok(external_text_surface);
|
|
}
|
|
|
|
|
|
lean_obj_res sdl_get_texture_width(b_lean_obj_arg g_texture, lean_obj_arg w) {
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(g_texture);
|
|
|
|
SDL_PropertiesID messageTexProps = SDL_GetTextureProperties(texture);
|
|
|
|
// get number property always returns a signed 64-bit integer
|
|
int64_t width = SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
|
|
|
|
// however, there seems to be no way to return int64_t directly to Lean
|
|
// so we box it as uint64_t instead
|
|
// TODO: figure out a way to return int64_t directly
|
|
return lean_io_result_mk_ok(lean_box_uint64(width));
|
|
}
|
|
|
|
lean_obj_res sdl_get_texture_height(b_lean_obj_arg g_texture, lean_obj_arg w) {
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(g_texture);
|
|
|
|
SDL_PropertiesID messageTexProps = SDL_GetTextureProperties(texture);
|
|
|
|
int64_t height = SDL_GetNumberProperty(messageTexProps, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
|
|
return lean_io_result_mk_ok(lean_box_uint64(height));
|
|
}
|
|
|
|
lean_obj_res sdl_render_texture(b_lean_obj_arg g_renderer, b_lean_obj_arg g_texture, int64_t src_x, int64_t src_y, int64_t src_width, int64_t src_height, int64_t dst_x, int64_t dst_y, int64_t dst_width, int64_t dst_height, lean_obj_arg w) {
|
|
if (!g_renderer || !g_texture) return lean_io_result_mk_ok(lean_box_uint32(-1));
|
|
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(g_texture);
|
|
|
|
SDL_FRect src_rect = { (float)src_x, (float)src_y, (float)src_width, (float)src_height };
|
|
|
|
SDL_FRect dst_rect = { (float)dst_x, (float)dst_y, (float)dst_width, (float)dst_height };
|
|
|
|
return lean_io_result_mk_ok(lean_box_uint32(SDL_RenderTexture(renderer, texture, &src_rect, &dst_rect)));
|
|
}
|
|
|
|
lean_obj_res sdl_render_texture_rect(b_lean_obj_arg renderer_obj, b_lean_obj_arg texture_obj, b_lean_obj_arg src, b_lean_obj_arg dst) {
|
|
if (!renderer_obj || !texture_obj) {
|
|
return lean_io_result_mk_ok(lean_box(false));
|
|
}
|
|
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(renderer_obj);
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(texture_obj);
|
|
|
|
double sx = lean_ctor_get_float(src, 0);
|
|
double sy = lean_ctor_get_float(src, sizeof(double));
|
|
double sw = lean_ctor_get_float(src, 2 * sizeof(double));
|
|
double sh = lean_ctor_get_float(src, 3 * sizeof(double));
|
|
SDL_FRect src_rect = { .x = (float)sx, .y = (float)sy, .w = (float)sw, .h = (float)sh };
|
|
|
|
double dx = lean_ctor_get_float(dst, 0);
|
|
double dy = lean_ctor_get_float(dst, sizeof(double));
|
|
double dw = lean_ctor_get_float(dst, 2 * sizeof(double));
|
|
double dh = lean_ctor_get_float(dst, 3 * sizeof(double));
|
|
SDL_FRect dst_rect = { .x = (float)dx, .y = (float)dy, .w = (float)dw, .h = (float)dh };
|
|
|
|
return lean_io_result_mk_ok(lean_box(SDL_RenderTexture(renderer, texture, &src_rect, &dst_rect)));
|
|
}
|
|
|
|
lean_obj_res sdl_render_entire_texture(b_lean_obj_arg g_renderer, b_lean_obj_arg g_texture, int64_t dst_x, int64_t dst_y, int64_t dst_width, int64_t dst_height, lean_obj_arg w) {
|
|
if (!g_renderer || !g_texture) return lean_io_result_mk_ok(lean_box_uint32(-1));
|
|
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(g_renderer);
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(g_texture);
|
|
|
|
SDL_FRect dst_rect = { (float)dst_x, (float)dst_y, (float)dst_width, (float)dst_height };
|
|
|
|
return lean_io_result_mk_ok(lean_box_uint32(SDL_RenderTexture(renderer, texture, NULL, &dst_rect)));
|
|
}
|
|
|
|
lean_obj_res sdl_render_texture_fullscreen(b_lean_obj_arg renderer_obj, b_lean_obj_arg texture_obj) {
|
|
if (!renderer_obj || !texture_obj) return lean_io_result_mk_ok(lean_box(false));
|
|
|
|
SDL_Renderer* renderer = (SDL_Renderer*)lean_get_external_data(renderer_obj);
|
|
SDL_Texture* texture = (SDL_Texture*)lean_get_external_data(texture_obj);
|
|
|
|
// NULL, NULL stretches texture to fill the entire render target
|
|
bool result = SDL_RenderTexture(renderer, texture, NULL, NULL);
|
|
return lean_io_result_mk_ok(lean_box(result));
|
|
}
|
|
|
|
|
|
lean_obj_res sdl_get_cameras() {
|
|
int count = 0;
|
|
SDL_CameraID* devices = SDL_GetCameras(&count);
|
|
if (devices == NULL) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
|
|
SDL_Log("SDL_CameraID devices count %d\n", count);
|
|
|
|
// Build Lean list by iterating backwards through the array
|
|
// List.nil has constructor tag 0, 0 object fields, 0 scalar fields
|
|
lean_object* list = lean_alloc_ctor(0, 0, 0); // Start with empty list (nil)
|
|
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
// Box the camera ID (SDL_CameraID is a uint32_t)
|
|
lean_object* camera_id = lean_box_uint32(devices[i]);
|
|
|
|
// List.cons has constructor tag 1, 2 object fields (head, tail)
|
|
lean_object* cons = lean_alloc_ctor(1, 2, 0);
|
|
lean_ctor_set(cons, 0, camera_id); // head
|
|
lean_ctor_set(cons, 1, list); // tail
|
|
|
|
list = cons;
|
|
}
|
|
|
|
SDL_free(devices); // Free the SDL array
|
|
|
|
return lean_io_result_mk_ok(list);
|
|
}
|
|
|
|
|
|
//TODO: implement 2nd SDL_CameraSpec arg
|
|
lean_obj_res sdl_open_camera(uint32_t instance_id) {
|
|
|
|
SDL_Camera* camera = SDL_OpenCamera(instance_id, NULL);
|
|
if (camera == NULL) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* external_camera = lean_alloc_external(sdl_camera_external_class, camera);
|
|
return lean_io_result_mk_ok(external_camera);
|
|
}
|
|
|
|
void print_lean_object(lean_object* obj) {
|
|
printf("obj->m_rc: %d\n", obj->m_rc);
|
|
printf("obj->m_cs_sz: %u\n", obj->m_cs_sz);
|
|
printf("obj->m_other: %u\n", obj->m_other);
|
|
printf("obj->m_tag: %u\n", obj->m_tag);
|
|
}
|
|
|
|
lean_obj_res sdl_get_camera_format(b_lean_obj_arg camera_obj) {
|
|
SDL_Camera* camera = (SDL_Camera*)lean_get_external_data(camera_obj);
|
|
SDL_CameraSpec* spec = (SDL_CameraSpec*) malloc(sizeof(SDL_CameraSpec));
|
|
bool result = SDL_GetCameraFormat(camera, spec);
|
|
if (!result) {
|
|
return lean_io_result_mk_error(lean_mk_string(SDL_GetError()));
|
|
}
|
|
lean_object* camera_spec = lean_alloc_external(sdl_camera_spec_external_class, spec);
|
|
return lean_io_result_mk_ok(camera_spec);
|
|
}
|
|
|
|
uint32_t sdl_CameraSpec_get_width(b_lean_obj_arg camera_spec_obj) {
|
|
SDL_CameraSpec* camera_spec = (SDL_CameraSpec*)lean_get_external_data(camera_spec_obj);
|
|
uint32_t width = (uint32_t)camera_spec->width;
|
|
return width;
|
|
}
|
|
|
|
uint32_t sdl_CameraSpec_get_height(b_lean_obj_arg camera_spec_obj) {
|
|
SDL_CameraSpec* camera_spec = (SDL_CameraSpec*)lean_get_external_data(camera_spec_obj);
|
|
uint32_t height = (uint32_t)camera_spec->height;
|
|
return height;
|
|
}
|
|
|
|
uint32_t sdl_CameraSpec_get_framerate_numerator(b_lean_obj_arg camera_spec_obj) {
|
|
SDL_CameraSpec* camera_spec = (SDL_CameraSpec*)lean_get_external_data(camera_spec_obj);
|
|
uint32_t framerate_numerator = (uint32_t)camera_spec->framerate_numerator;
|
|
return framerate_numerator;
|
|
}
|
|
|
|
uint32_t sdl_CameraSpec_get_framerate_denominator(b_lean_obj_arg camera_spec_obj) {
|
|
SDL_CameraSpec* camera_spec = (SDL_CameraSpec*)lean_get_external_data(camera_spec_obj);
|
|
uint32_t framerate_denominator = (uint32_t)camera_spec->framerate_denominator;
|
|
return framerate_denominator;
|
|
}
|
|
|
|
lean_obj_res sdl_acquire_camera_frame(b_lean_obj_arg camera_obj) {
|
|
SDL_Camera* camera = (SDL_Camera*)lean_get_external_data(camera_obj);
|
|
uint64_t timestamp = 0;
|
|
SDL_Surface* frame = SDL_AcquireCameraFrame(camera, ×tamp);
|
|
if (!frame) {
|
|
// Return Option.none (tag 0, 0 object fields, 0 scalar fields)
|
|
lean_object* none = lean_alloc_ctor(0, 0, 0);
|
|
return lean_io_result_mk_ok(none);
|
|
}
|
|
lean_object* external_surface = lean_alloc_external(sdl_camera_frame_external_class, frame);
|
|
// Return Option.some frame (tag 1, 1 object field)
|
|
lean_object* some = lean_alloc_ctor(1, 1, 0);
|
|
lean_ctor_set(some, 0, external_surface);
|
|
return lean_io_result_mk_ok(some);
|
|
}
|
|
|
|
lean_obj_res sdl_release_camera_frame(b_lean_obj_arg camera_obj, lean_obj_arg frame_obj) {
|
|
SDL_Camera* camera = (SDL_Camera*)lean_get_external_data(camera_obj);
|
|
SDL_Surface* frame = (SDL_Surface*)lean_get_external_data(frame_obj);
|
|
|
|
SDL_ReleaseCameraFrame(camera, frame);
|
|
|
|
// frame_obj is lean_obj_arg (ownership transferred), so we must release it
|
|
// The finalizer is a no-op, so this is safe after SDL_ReleaseCameraFrame
|
|
lean_dec(frame_obj);
|
|
|
|
return lean_io_result_mk_ok(lean_box(0));
|
|
}
|
|
|
|
|
|
// Mouse support (caching avoids redundant SDL calls within the same frame)
|
|
static struct {
|
|
float x, y;
|
|
uint32_t buttons;
|
|
bool relative_mode;
|
|
uint32_t last_frame_tick;
|
|
} mouse_cache = {0, 0, 0, false, 0};
|
|
|
|
lean_obj_res sdl_get_mouse_state(lean_obj_arg w) {
|
|
uint32_t current_tick = SDL_GetTicks();
|
|
if (mouse_cache.last_frame_tick != current_tick) {
|
|
if (mouse_cache.relative_mode) {
|
|
mouse_cache.buttons = SDL_GetRelativeMouseState(&mouse_cache.x, &mouse_cache.y);
|
|
} else {
|
|
mouse_cache.buttons = SDL_GetMouseState(&mouse_cache.x, &mouse_cache.y);
|
|
}
|
|
mouse_cache.last_frame_tick = current_tick;
|
|
}
|
|
|
|
// Pack into 64 bits: X[63:32] | Y[31:16] | buttons[15:0]
|
|
// X gets full 32 bits; Y and buttons are truncated to 16 bits each.
|
|
int32_t ix = (int32_t)mouse_cache.x;
|
|
int32_t iy = (int32_t)mouse_cache.y;
|
|
uint64_t packed = ((uint64_t)(uint32_t)ix << 32) |
|
|
((uint64_t)((uint32_t)iy & 0xFFFF) << 16) |
|
|
((uint64_t)(mouse_cache.buttons & 0xFFFF));
|
|
return lean_io_result_mk_ok(lean_box_uint64(packed));
|
|
}
|
|
|
|
lean_obj_res sdl_set_relative_mouse_mode(b_lean_obj_arg g_window, bool enabled, lean_obj_arg w) {
|
|
SDL_Window* window = (SDL_Window*)lean_get_external_data(g_window);
|
|
if (window == NULL) return lean_io_result_mk_error(lean_mk_string("C: Window is NULL"));
|
|
int32_t result = SDL_SetWindowRelativeMouseMode(window, enabled);
|
|
mouse_cache.relative_mode = enabled;
|
|
mouse_cache.last_frame_tick = 0; // Force refresh
|
|
return lean_io_result_mk_ok(lean_box_uint32(result));
|
|
}
|