#include "qjs_sdl_video.h" #include "qjs_blob.h" #include "jsffi.h" #include "qjs_sdl_surface.h" #include "qjs_actor.h" #include "sprite.h" #include "transform.h" #include #include #include #include #include #include #include "qjs_sdl.h" // SDL Window free function void SDL_Window_free(JSRuntime *rt, SDL_Window *w) { SDL_DestroyWindow(w); } void SDL_Renderer_free(JSRuntime *rt, SDL_Renderer *r) { SDL_DestroyRenderer(r); } void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){ SDL_DestroyTexture(t); } QJSCLASS(SDL_Texture, float w, h; SDL_GetTextureSize(n, &w, &h); JS_SetPropertyStr(js, j, "width", number2js(js,w)); JS_SetPropertyStr(js,j,"height",number2js(js,h)); ) // Class definitions QJSCLASS(SDL_Renderer,) QJSCLASS(SDL_Window,) void SDL_Cursor_free(JSRuntime *rt, SDL_Cursor *c) { SDL_DestroyCursor(c); } QJSCLASS(SDL_Cursor,) // External function declarations extern JSValue rect2js(JSContext *js, rect r); extern rect js2rect(JSContext *js, JSValue v); extern HMM_Vec2 js2vec2(JSContext *js, JSValue v); extern JSValue vec22js(JSContext *js, HMM_Vec2 v); extern colorf js2color(JSContext *js, JSValue v); extern double js2number(JSContext *js, JSValue v); extern JSValue number2js(JSContext *js, double n); extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v); extern JSValue SDL_Texture2js(JSContext *js, SDL_Texture *t); extern SDL_Window *js2SDL_Window(JSContext *js, JSValue v); extern JSValue SDL_Window2js(JSContext *js, SDL_Window *w); extern SDL_Renderer *js2SDL_Renderer(JSContext *js, JSValue v); extern JSValue SDL_Renderer2js(JSContext *js, SDL_Renderer *r); extern void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size); extern double js_getnum_str(JSContext *js, JSValue v, const char *str); extern sprite *js2sprite(JSContext *js, JSValue v); extern HMM_Vec3 js2vec3(JSContext *js, JSValue v); extern JSValue vec32js(JSContext *js, HMM_Vec3 v); extern HMM_Vec4 js2vec4(JSContext *js, JSValue v); extern JSValue vec42js(JSContext *js, HMM_Vec4 v); extern JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index); extern JSValue make_quad_indices_buffer(JSContext *js, int quads); extern JSClassID js_SDL_Surface_id; // 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_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) && !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) && !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) && !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) && !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); return window_obj; } // Window functions JSC_SCALL(SDL_Window_make_renderer, SDL_Window *win = js2SDL_Window(js,self); SDL_Renderer *renderer = SDL_CreateRenderer(win, NULL); if (!renderer) { return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError()); } SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); return SDL_Renderer2js(js,renderer); ) JSC_CCALL(SDL_Window_fullscreen, SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN) ) JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue self) { SDL_Window *window = js2SDL_Window(js,self); return JS_NewBool(js,SDL_ScreenKeyboardShown(window)); } JSValue js_window_theme(JSContext *js, JSValue self) { return JS_NULL; } JSValue js_window_safe_area(JSContext *js, JSValue self) { SDL_Window *w = js2SDL_Window(js,self); rect r; SDL_GetWindowSafeArea(w, &r); return rect2js(js,r); } 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, (HMM_Vec2){w,h}); } JSValue js_window_set_size(JSContext *js, JSValue self, JSValue val) { SDL_Window *w = js2SDL_Window(js,self); HMM_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, (HMM_Vec2){x,y}); } JSValue js_window_set_position(JSContext *js, JSValue self, JSValue val) { SDL_Window *w = js2SDL_Window(js,self); HMM_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, (HMM_Vec2){width,height}); } JSValue js_window_set_minimumSize(JSContext *js, JSValue self, JSValue val) { SDL_Window *w = js2SDL_Window(js,self); HMM_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, (HMM_Vec2){width,height}); } JSValue js_window_set_maximumSize(JSContext *js, JSValue self, JSValue val) { SDL_Window *w = js2SDL_Window(js,self); HMM_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) && !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, (HMM_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); return number2js(js, SDL_GetWindowFlags(w)); } 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, make_renderer, 1), 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), }; // Renderer functions 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(renderer_load_texture, SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Surface *surf = js2SDL_Surface(js,argv[0]); if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface."); SDL_Texture *tex = SDL_CreateTextureFromSurface(r,surf); if (!tex) return JS_ThrowReferenceError(js, "Could not create texture from surface: %s", SDL_GetError()); ret = SDL_Texture2js(js,tex); ) JSC_CCALL(renderer_get_image, SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Surface *surf = NULL; if (!JS_IsNull(argv[0])) { rect rect = js2rect(js,argv[0]); surf = SDL_RenderReadPixels(r,&rect); } else surf = SDL_RenderReadPixels(r,NULL); if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer"); return SDL_Surface2js(js,surf); ) JSC_CCALL(renderer_line, if (!JS_IsArray(js, argv[0])) return JS_ThrowReferenceError(js, "First arugment must be an array of points."); SDL_Renderer *r = js2SDL_Renderer(js,self); int len = JS_ArrayLength(js,argv[0]); if (len < 2) return JS_ThrowReferenceError(js, "Must provide at least 2 points to render."); SDL_FPoint points[len]; assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint)); for (int i = 0; i < len; i++) { JSValue val = JS_GetPropertyUint32(js,argv[0],i); HMM_Vec2 pt = js2vec2(js,val); JS_FreeValue(js,val); points[i] = (SDL_FPoint){pt.x, pt.y}; } SDL_RenderLines(r,points,len); ) JSC_CCALL(renderer_point, SDL_Renderer *r = js2SDL_Renderer(js, self); if (JS_IsArray(js, argv[0])) { int len = JS_ArrayLength(js, argv[0]); SDL_FPoint pts[len]; for (int i = 0; i < len; ++i) { JSValue val = JS_GetPropertyUint32(js, argv[0], i); HMM_Vec2 pt = js2vec2(js, val); JS_FreeValue(js, val); pts[i] = (SDL_FPoint){pt.x, pt.y}; } SDL_RenderPoints(r, pts, len); return JS_NULL; } HMM_Vec2 pt = js2vec2(js, argv[0]); SDL_RenderPoint(r, pt.x, pt.y); ) JSC_CCALL(renderer_texture, SDL_Renderer *r = js2SDL_Renderer(js, self); SDL_Texture *tex = js2SDL_Texture(js,argv[0]); rect src = js2rect(js,argv[1]); rect dst = js2rect(js,argv[2]); double angle; JS_ToFloat64(js, &angle, argv[3]); angle *= -360; HMM_Vec2 anchor = js2vec2(js, argv[4]); anchor.y = dst.h - anchor.y*dst.h; anchor.x *= dst.w; SDL_RenderTextureRotated(r, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE); ) JSC_CCALL(renderer_rects, SDL_Renderer *r = js2SDL_Renderer(js, self); /* array-of-rectangles case */ if (JS_IsArray(js, argv[0])) { int len = JS_ArrayLength(js, argv[0]); if (len <= 0) return JS_NULL; SDL_FRect rects[len]; for (int i = 0; i < len; ++i) { JSValue val = JS_GetPropertyUint32(js, argv[0], i); rect rect = js2rect(js, val); JS_FreeValue(js, val); rects[i] = rect; } if (!SDL_RenderFillRects(r, rects, len)) return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError()); return JS_NULL; } /* single-rect path */ rect rect = js2rect(js, argv[0]); if (!SDL_RenderFillRects(r, &rect, 1)) return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError()); ) // Should take a single struct with pos, color, uv, and indices arrays JSC_CCALL(renderer_geometry, SDL_Renderer *r = js2SDL_Renderer(js,self); JSValue pos = JS_GetPropertyStr(js,argv[1], "pos"); JSValue color = JS_GetPropertyStr(js,argv[1], "color"); JSValue uv = JS_GetPropertyStr(js,argv[1], "uv"); JSValue indices = JS_GetPropertyStr(js,argv[1], "indices"); int vertices = js_getnum_str(js, argv[1], "vertices"); int count = js_getnum_str(js, argv[1], "num_indices"); size_t pos_stride, indices_stride, uv_stride, color_stride; void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL); void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL); void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL); void *colordata = get_gpu_buffer(js,color,&color_stride, NULL); SDL_Texture *tex = js2SDL_Texture(js,argv[0]); if (!SDL_RenderGeometryRaw(r, tex, posdata, pos_stride,colordata,color_stride,uvdata, uv_stride, vertices, idxdata, count, indices_stride)) ret = JS_ThrowReferenceError(js, "Error rendering geometry: %s",SDL_GetError()); JS_FreeValue(js,pos); JS_FreeValue(js,color); JS_FreeValue(js,uv); JS_FreeValue(js,indices); ) JSC_CCALL(renderer_geometry_raw, SDL_Renderer *r = js2SDL_Renderer(js,self); // argv[0] is texture SDL_Texture *tex = NULL; if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsNull(argv[0])) tex = js2SDL_Texture(js,argv[0]); // Get blob data size_t xy_size, color_size, uv_size, indices_size; void *xy_data = js_get_blob_data(js, &xy_size, argv[1]); int xy_stride = js2number(js, argv[2]); void *color_data = js_get_blob_data(js, &color_size, argv[3]); int color_stride = js2number(js, argv[4]); void *uv_data = js_get_blob_data(js, &uv_size, argv[5]); int uv_stride = js2number(js, argv[6]); int num_vertices = js2number(js, argv[7]); void *indices_data = js_get_blob_data(js, &indices_size, argv[8]); int num_indices = js2number(js, argv[9]); int size_indices = js2number(js, argv[10]); if (!xy_data || !color_data || !uv_data) return JS_ThrowTypeError(js, "Invalid geometry data"); if (!SDL_RenderGeometryRaw(r, tex, (const float *)xy_data, xy_stride, (const SDL_FColor *)color_data, color_stride, (const float *)uv_data, uv_stride, num_vertices, indices_data, num_indices, size_indices)) return JS_ThrowReferenceError(js, "Error rendering geometry: %s", SDL_GetError()); ) JSC_CCALL(renderer_geometry2, SDL_Renderer *r = js2SDL_Renderer(js, self); SDL_Texture *tex = js2SDL_Texture(js, argv[0]); JSValue geo = argv[1]; int vertices = js_getnum_str(js, geo, "vertices"); int count = js_getnum_str(js, geo, "num_indices"); JSValue pos_v = JS_GetPropertyStr(js, geo, "pos"); JSValue color_v = JS_GetPropertyStr(js, geo, "color"); JSValue uv_v = JS_GetPropertyStr(js, geo, "uv"); JSValue idx_v = JS_GetPropertyStr(js, geo, "indices"); size_t pos_stride, color_stride, uv_stride, idx_stride; void *pos_data = get_gpu_buffer(js, pos_v, &pos_stride, NULL); void *color_data = get_gpu_buffer(js, color_v, &color_stride, NULL); void *uv_data = get_gpu_buffer(js, uv_v, &uv_stride, NULL); void *idx_data = get_gpu_buffer(js, idx_v, &idx_stride, NULL); SDL_Vertex *verts = malloc(vertices * sizeof *verts); for(int i = 0; i < vertices; ++i) { const float *p = (const float *)((uint8_t *)pos_data + i * pos_stride); const float *u = (const float *)((uint8_t *)uv_data + i * uv_stride); const float *c = (const float *)((uint8_t *)color_data + i * color_stride); verts[i].position = (SDL_FPoint){ p[0], p[1] }; verts[i].tex_coord = (SDL_FPoint){ u[0], u[1] }; verts[i].color = (SDL_FColor){ c[0], c[1], c[2], c[3] }; } const int *indices32 = NULL; int *tmp_idx = NULL; if(idx_data && count) { if(idx_stride == 4) indices32 = (const int *)idx_data; else { tmp_idx = malloc(count * sizeof *tmp_idx); if(idx_stride == 2) { const uint16_t *src = idx_data; for(int i = 0; i < count; ++i) tmp_idx[i] = src[i]; } else { /* 8-bit */ const uint8_t *src = idx_data; for(int i = 0; i < count; ++i) tmp_idx[i] = src[i]; } indices32 = tmp_idx; } } printf("num verts, num indices: %d, %d\n", vertices, count); if(!SDL_RenderGeometry(r, tex, verts, vertices, indices32, count)) printf("Error rendering geometry: %s\n", SDL_GetError()); free(tmp_idx); free(verts); JS_FreeValue(js, pos_v); JS_FreeValue(js, color_v); JS_FreeValue(js, uv_v); JS_FreeValue(js, idx_v); ) static sprite js_getsprite(JSContext *js, JSValue sp) { sprite *s = js2sprite(js, sp); if (s) return *s; sprite pp = {0}; return pp; } JSC_CCALL(renderer_sprite, SDL_Renderer *r = js2SDL_Renderer(js, self); sprite sp = js_getsprite(js, argv[0]); SDL_Texture *tex; JSValue texture_val = JS_GetPropertyStr(js, sp.image, "texture"); tex = js2SDL_Texture(js, texture_val); JS_FreeValue(js, texture_val); rect uv; JSValue rect_val = JS_GetPropertyStr(js, sp.image, "rect"); uv = js2rect(js, rect_val); JS_FreeValue(js, rect_val); float w = uv.w, h = uv.h; HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y }; HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y }; HMM_Vec2 bl_local = { -sp.center.X, -sp.center.Y + h }; HMM_Vec2 world_tl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tl_local)); HMM_Vec2 world_tr = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tr_local)); HMM_Vec2 world_bl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, bl_local)); SDL_FPoint origin = (SDL_FPoint){world_tl.x, world_tl.y}; SDL_FPoint right = (SDL_FPoint){world_tr.x, world_tr.y}; SDL_FPoint down = (SDL_FPoint){world_bl.x, world_bl.y}; if (!SDL_RenderTextureAffine(r, tex, &uv, &origin, &right, &down)) return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError()); ) /* logical presentation helpers */ typedef struct { const char *name; SDL_RendererLogicalPresentation pres; } pres_entry; static const pres_entry k_pres_table[] = { { "stretch", SDL_LOGICAL_PRESENTATION_STRETCH }, { "letterbox", SDL_LOGICAL_PRESENTATION_LETTERBOX }, { "overscan", SDL_LOGICAL_PRESENTATION_OVERSCAN }, { "integer", SDL_LOGICAL_PRESENTATION_INTEGER_SCALE }, { NULL, SDL_LOGICAL_PRESENTATION_DISABLED } /* fallback */ }; static JSValue logicalpresentation2js(JSContext *js, SDL_RendererLogicalPresentation pres){ const pres_entry *it; for(it = k_pres_table; it->name; ++it) if(it->pres == pres) break; return JS_NewString(js, it->name ? it->name : "disabled"); } static SDL_RendererLogicalPresentation js2SDL_LogicalPresentation(JSContext *js, JSValue v){ if(JS_IsNull(v)) return SDL_LOGICAL_PRESENTATION_DISABLED; const char *s = JS_ToCString(js, v); if(!s) return SDL_LOGICAL_PRESENTATION_DISABLED; const pres_entry *it; for(it = k_pres_table; it->name; ++it) if(!strcmp(it->name, s)) break; JS_FreeCString(js, s); return it->pres; } // Renderer getter/setter functions // Target getter/setter JSValue js_renderer_get_target(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Texture *tex = SDL_GetRenderTarget(r); if (!tex) return JS_NULL; return SDL_Texture2js(js, tex); } JSValue js_renderer_set_target(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); if (JS_IsNull(val) || JS_IsNull(val)) SDL_SetRenderTarget(r, NULL); else { SDL_Texture *tex = js2SDL_Texture(js,val); SDL_SetRenderTarget(r,tex); } return JS_NULL; } // Logical presentation getter/setter JSValue js_renderer_get_logicalPresentation(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); int w, h; SDL_RendererLogicalPresentation mode; SDL_GetRenderLogicalPresentation(r, &w, &h, &mode); JSValue ret = JS_NewObject(js); JS_SetPropertyStr(js, ret, "width", number2js(js, w)); JS_SetPropertyStr(js, ret, "height", number2js(js, h)); JS_SetPropertyStr(js, ret, "mode", logicalpresentation2js(js, mode)); return ret; } JSValue js_renderer_set_logicalPresentation(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); double w = js_getnum_str(js, val, "width"); double h = js_getnum_str(js, val, "height"); JSValue mode_val = JS_GetPropertyStr(js, val, "mode"); SDL_RendererLogicalPresentation mode = js2SDL_LogicalPresentation(js, mode_val); JS_FreeValue(js, mode_val); SDL_SetRenderLogicalPresentation(r, w, h, mode); return JS_NULL; } // Viewport getter/setter JSValue js_renderer_get_viewport(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); rect viewport; SDL_GetRenderViewport(r, &viewport); return rect2js(js, viewport); } JSValue js_renderer_set_viewport(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); if (JS_IsNull(val) || JS_IsNull(val)) SDL_SetRenderViewport(r,NULL); else { rect view = js2rect(js,val); SDL_SetRenderViewport(r,&view); } return JS_NULL; } // Clip rect getter/setter JSValue js_renderer_get_clipRect(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); rect clip; SDL_GetRenderClipRect(r, &clip); return rect2js(js, clip); } JSValue js_renderer_set_clipRect(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); if (JS_IsNull(val) || JS_IsNull(val)) SDL_SetRenderClipRect(r,NULL); else { rect clip = js2rect(js,val); SDL_SetRenderClipRect(r,&clip); } return JS_NULL; } // Scale getter/setter JSValue js_renderer_get_scale(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); float x, y; SDL_GetRenderScale(r, &x, &y); return vec22js(js, (HMM_Vec2){x, y}); } JSValue js_renderer_set_scale(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); HMM_Vec2 scale = js2vec2(js,val); SDL_SetRenderScale(r, scale.x, scale.y); return JS_NULL; } // Draw color getter/setter (float version) JSValue js_renderer_get_drawColor(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); float red, green, blue, alpha; SDL_GetRenderDrawColorFloat(r, &red, &green, &blue, &alpha); JSValue ret = JS_NewObject(js); JS_SetPropertyStr(js, ret, "r", number2js(js, red)); JS_SetPropertyStr(js, ret, "g", number2js(js, green)); JS_SetPropertyStr(js, ret, "b", number2js(js, blue)); JS_SetPropertyStr(js, ret, "a", number2js(js, alpha)); return ret; } JSValue js_renderer_set_drawColor(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); colorf color = js2color(js,val); SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a); return JS_NULL; } // Color scale getter/setter JSValue js_renderer_get_colorScale(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); float scale; SDL_GetRenderColorScale(r, &scale); return number2js(js, scale); } JSValue js_renderer_set_colorScale(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); float scale = js2number(js,val); SDL_SetRenderColorScale(r, scale); return JS_NULL; } // Draw blend mode getter/setter JSValue js_renderer_get_drawBlendMode(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_BlendMode mode; SDL_GetRenderDrawBlendMode(r, &mode); return blendmode2js(js, mode); } JSValue js_renderer_set_drawBlendMode(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_BlendMode mode = js2blendmode(js,val); SDL_SetRenderDrawBlendMode(r, mode); return JS_NULL; } // VSync getter/setter JSValue js_renderer_get_vsync(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); int vsync; SDL_GetRenderVSync(r, &vsync); return number2js(js, vsync); } JSValue js_renderer_set_vsync(JSContext *js, JSValue self, JSValue val) { SDL_Renderer *r = js2SDL_Renderer(js,self); int vsync = js2number(js,val); SDL_SetRenderVSync(r, vsync); return JS_NULL; } // Read-only properties JSValue js_renderer_get_window(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Window *win = SDL_GetRenderWindow(r); if (!win) return JS_NULL; return SDL_Window2js(js, win); } JSValue js_renderer_get_name(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); const char *name = SDL_GetRendererName(r); return JS_NewString(js, name ? name : ""); } JSValue js_renderer_get_outputSize(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); int w, h; SDL_GetRenderOutputSize(r, &w, &h); return vec22js(js, (HMM_Vec2){w, h}); } JSValue js_renderer_get_currentOutputSize(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); int w, h; SDL_GetCurrentRenderOutputSize(r, &w, &h); return vec22js(js, (HMM_Vec2){w, h}); } JSValue js_renderer_get_logicalPresentationRect(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); rect presentRect; SDL_GetRenderLogicalPresentationRect(r, &presentRect); return rect2js(js, presentRect); } JSValue js_renderer_get_safeArea(JSContext *js, JSValue self) { SDL_Renderer *r = js2SDL_Renderer(js,self); rect safe; SDL_GetRenderSafeArea(r, &safe); return rect2js(js, safe); } // Converts window coordinates to renderer coordinates JSC_CCALL(renderer_coordsFromWindow, SDL_Renderer *r = js2SDL_Renderer(js,self); HMM_Vec2 pos, coord; pos = js2vec2(js,argv[0]); SDL_RenderCoordinatesFromWindow(r,pos.x,pos.y, &coord.x, &coord.y); return vec22js(js,coord); ) // Converts renderer coordinates to window coordinates JSC_CCALL(renderer_coordsToWindow, SDL_Renderer *r = js2SDL_Renderer(js,self); HMM_Vec2 pos, coord; pos = js2vec2(js,argv[0]); SDL_RenderCoordinatesToWindow(r,pos.x,pos.y, &coord.x, &coord.y); return vec22js(js,coord); ) // Flush any pending rendering commands JSC_CCALL(renderer_flush, SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_FlushRenderer(r); ) // Additional rendering functions JSC_CCALL(renderer_rect, SDL_Renderer *r = js2SDL_Renderer(js,self); rect rect = js2rect(js, argv[0]); if (!SDL_RenderRect(r, &rect)) return JS_ThrowReferenceError(js, "SDL_RenderRect: %s", SDL_GetError()); ) JSC_CCALL(renderer_fillRect, SDL_Renderer *r = js2SDL_Renderer(js,self); rect rect = js2rect(js, argv[0]); if (!SDL_RenderFillRect(r, &rect)) return JS_ThrowReferenceError(js, "SDL_RenderFillRect: %s", SDL_GetError()); ) // Render a single line from point A to point B JSC_CCALL(renderer_lineTo, SDL_Renderer *r = js2SDL_Renderer(js,self); HMM_Vec2 a = js2vec2(js, argv[0]); HMM_Vec2 b = js2vec2(js, argv[1]); if (!SDL_RenderLine(r, a.x, a.y, b.x, b.y)) return JS_ThrowReferenceError(js, "SDL_RenderLine: %s", SDL_GetError()); ) // Check if clipping is enabled JSC_CCALL(renderer_clipEnabled, SDL_Renderer *r = js2SDL_Renderer(js,self); return JS_NewBool(js, SDL_RenderClipEnabled(r)); ) // Render texture with 9-slice grid JSC_CCALL(renderer_texture9Grid, SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Texture *tex = js2SDL_Texture(js, argv[0]); rect src_rect = js2rect(js, argv[1]); double left_w = js2number(js,argv[2]); double right_w = js2number(js,argv[3]); double top_h = js2number(js,argv[4]); double bottom_h = js2number(js, argv[5]); double tex_scale = js2number(js, argv[6]); rect dst_rect = js2rect(js, argv[7]); if (!SDL_RenderTexture9Grid(r, tex, &src_rect, left_w, right_w, top_h, bottom_h, tex_scale, &dst_rect)) return JS_ThrowReferenceError(js, "Failed to render 9-grid texture: %s", SDL_GetError()); ) // Render tiled texture JSC_CCALL(renderer_textureTiled, SDL_Renderer *r = js2SDL_Renderer(js,self); SDL_Texture *tex = js2SDL_Texture(js, argv[0]); rect src_rect = {0}; if (argc > 1 && !JS_IsNull(argv[1]) && !JS_IsNull(argv[1])) src_rect = js2rect(js, argv[1]); float scale = 1.0f; if (argc > 2) scale = js2number(js, argv[2]); rect dst_rect = {0}; if (argc > 3 && !JS_IsNull(argv[3]) && !JS_IsNull(argv[3])) dst_rect = js2rect(js, argv[3]); if (!SDL_RenderTextureTiled(r, tex, (src_rect.w > 0 && src_rect.h > 0) ? &src_rect : NULL, scale, (dst_rect.w > 0 && dst_rect.h > 0) ? &dst_rect : NULL)) return JS_ThrowReferenceError(js, "Failed to render tiled texture: %s", SDL_GetError()); ) // Render debug text JSC_CCALL(renderer_debugText, SDL_Renderer *r = js2SDL_Renderer(js,self); HMM_Vec2 pos = js2vec2(js, argv[0]); const char *text = JS_ToCString(js, argv[1]); int ren = SDL_RenderDebugText(r, pos.x, pos.y, text); JS_FreeCString(js,text); if (!ren) return JS_ThrowReferenceError(js, "Failed to render debug text: %s", SDL_GetError()); ) // Read pixels from renderer JSC_CCALL(renderer_readPixels, SDL_Renderer *r = js2SDL_Renderer(js,self); rect read_rect = {0}; if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsNull(argv[0])) read_rect = js2rect(js, argv[0]); SDL_Surface *surf = SDL_RenderReadPixels(r, (read_rect.w > 0 && read_rect.h > 0) ? &read_rect : NULL); if (!surf) return JS_ThrowReferenceError(js, "Failed to read pixels: %s", SDL_GetError()); return SDL_Surface2js(js, surf); ) static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { MIST_FUNC_DEF(SDL_Renderer, present, 0), MIST_FUNC_DEF(SDL_Renderer, clear, 0), MIST_FUNC_DEF(renderer, line, 1), MIST_FUNC_DEF(renderer, point, 1), MIST_FUNC_DEF(renderer, texture, 5), MIST_FUNC_DEF(renderer, rects, 1), MIST_FUNC_DEF(renderer, geometry, 2), MIST_FUNC_DEF(renderer, geometry_raw, 11), MIST_FUNC_DEF(renderer, geometry2, 2), MIST_FUNC_DEF(renderer, sprite, 1), MIST_FUNC_DEF(renderer, load_texture, 1), MIST_FUNC_DEF(renderer, get_image, 1), MIST_FUNC_DEF(renderer, coordsFromWindow, 1), MIST_FUNC_DEF(renderer, coordsToWindow, 1), MIST_FUNC_DEF(renderer, flush, 0), // Additional shape drawing functions MIST_FUNC_DEF(renderer, rect, 1), MIST_FUNC_DEF(renderer, fillRect, 1), MIST_FUNC_DEF(renderer, lineTo, 2), MIST_FUNC_DEF(renderer, debugText, 2), MIST_FUNC_DEF(renderer, readPixels, 1), MIST_FUNC_DEF(renderer, clipEnabled, 0), MIST_FUNC_DEF(renderer, texture9Grid, 6), MIST_FUNC_DEF(renderer, textureTiled, 4), // Getter/setter properties CGETSET_ADD(renderer, target), CGETSET_ADD(renderer, logicalPresentation), CGETSET_ADD(renderer, viewport), CGETSET_ADD(renderer, clipRect), CGETSET_ADD(renderer, scale), CGETSET_ADD(renderer, drawColor), CGETSET_ADD(renderer, colorScale), CGETSET_ADD(renderer, drawBlendMode), CGETSET_ADD(renderer, vsync), // Read-only properties JS_CGETSET_DEF("window", js_renderer_get_window, NULL), JS_CGETSET_DEF("name", js_renderer_get_name, NULL), JS_CGETSET_DEF("outputSize", js_renderer_get_outputSize, NULL), JS_CGETSET_DEF("currentOutputSize", js_renderer_get_currentOutputSize, NULL), JS_CGETSET_DEF("logicalPresentationRect", js_renderer_get_logicalPresentationRect, NULL), JS_CGETSET_DEF("safeArea", js_renderer_get_safeArea, NULL), }; extern SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v); // Blend mode helper static JSValue blendmode2js(JSContext *js, SDL_BlendMode mode) { switch(mode) { case SDL_BLENDMODE_NONE: return JS_NewString(js, "none"); case SDL_BLENDMODE_BLEND: return JS_NewString(js, "blend"); case SDL_BLENDMODE_BLEND_PREMULTIPLIED: return JS_NewString(js, "blend_premultiplied"); case SDL_BLENDMODE_ADD: return JS_NewString(js, "add"); case SDL_BLENDMODE_ADD_PREMULTIPLIED: return JS_NewString(js, "add_premultiplied"); case SDL_BLENDMODE_MOD: return JS_NewString(js, "mod"); case SDL_BLENDMODE_MUL: return JS_NewString(js, "mul"); default: return JS_NewString(js, "invalid"); } } static SDL_BlendMode js2blendmode(JSContext *js, JSValue v) { if (JS_IsNumber(v)) { return js2number(js, v); } const char *str = JS_ToCString(js, v); SDL_BlendMode mode = SDL_BLENDMODE_BLEND; if (str) { if (strcmp(str, "none") == 0) mode = SDL_BLENDMODE_NONE; else if (strcmp(str, "blend") == 0) mode = SDL_BLENDMODE_BLEND; else if (strcmp(str, "blend_premultiplied") == 0) mode = SDL_BLENDMODE_BLEND_PREMULTIPLIED; else if (strcmp(str, "add") == 0) mode = SDL_BLENDMODE_ADD; else if (strcmp(str, "add_premultiplied") == 0) mode = SDL_BLENDMODE_ADD_PREMULTIPLIED; else if (strcmp(str, "mod") == 0) mode = SDL_BLENDMODE_MOD; else if (strcmp(str, "mul") == 0) mode = SDL_BLENDMODE_MUL; JS_FreeCString(js, str); } return mode; } // Texture constructor static JSValue js_texture_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) { if (argc < 2) return JS_ThrowTypeError(js, "Texture constructor requires renderer and either surface or object with width/height/format"); SDL_Renderer *renderer = js2SDL_Renderer(js, argv[0]); if (!renderer) return JS_ThrowReferenceError(js, "Invalid renderer"); SDL_Texture *tex = NULL; // Check if second arg is a surface if (JS_GetClassID(argv[1]) == js_SDL_Surface_id) { SDL_Surface *surf = js2SDL_Surface(js, argv[1]); tex = SDL_CreateTextureFromSurface(renderer, surf); if (!tex) return JS_ThrowReferenceError(js, "Failed to create texture from surface: %s", SDL_GetError()); } else if (JS_IsObject(argv[1])) { // Create from properties object JSValue obj = argv[1]; int width = js_getnum_str(js, obj, "width"); int height = js_getnum_str(js, obj, "height"); JSValue format_val = JS_GetPropertyStr(js, obj, "format"); SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888; if (!JS_IsNull(format_val)) format = js2pixelformat(js, format_val); JS_FreeValue(js, format_val); // Check for pixels data JSValue pixels_val = JS_GetPropertyStr(js, obj, "pixels"); if (!JS_IsNull(pixels_val)) { // Create surface first, then texture size_t size; uint8_t *pixels = js_get_blob_data(js, &size, pixels_val); if (!pixels) { JS_FreeValue(js, pixels_val); return JS_ThrowTypeError(js, "pixels must be an ArrayBuffer"); } int pitch = js_getnum_str(js, obj, "pitch"); if (pitch == 0) { // Calculate pitch from format int bpp = SDL_BYTESPERPIXEL(format); pitch = width * bpp; } SDL_Surface *surf = SDL_CreateSurfaceFrom(width, height, format, pixels, pitch); if (!surf) { JS_FreeValue(js, pixels_val); return JS_ThrowReferenceError(js, "Failed to create surface: %s", SDL_GetError()); } tex = SDL_CreateTextureFromSurface(renderer, surf); SDL_DestroySurface(surf); JS_FreeValue(js, pixels_val); if (!tex) return JS_ThrowReferenceError(js, "Failed to create texture: %s", SDL_GetError()); } else { // Create empty texture tex = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, width, height); if (!tex) return JS_ThrowReferenceError(js, "Failed to create texture: %s", SDL_GetError()); } } else { return JS_ThrowTypeError(js, "Second argument must be a surface or object with width/height/format"); } JSValue tex_obj = SDL_Texture2js(js, tex); return tex_obj; } static const JSCFunctionListEntry js_sdl_video_funcs[] = { JS_PROP_INT32_DEF("BLENDMODE_NONE", SDL_BLENDMODE_NONE, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("BLENDMODE_BLEND", SDL_BLENDMODE_BLEND, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("BLENDMODE_ADD", SDL_BLENDMODE_ADD, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("BLENDMODE_MOD", SDL_BLENDMODE_MOD, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("BLENDMODE_MUL", SDL_BLENDMODE_MUL, JS_PROP_CONFIGURABLE), }; // Cursor creation function JSC_CCALL(sdl_create_cursor, SDL_Surface *surf = js2SDL_Surface(js, argv[0]); if (!surf) return JS_ThrowReferenceError(js, "Invalid surface"); HMM_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); ) // Texture getter/setter functions // Alpha mod getter/setter JSValue js_texture_get_alphaMod(JSContext *js, JSValue self) { SDL_Texture *tex = js2SDL_Texture(js,self); float alpha; SDL_GetTextureAlphaModFloat(tex, &alpha); return number2js(js, alpha); } JSValue js_texture_set_alphaMod(JSContext *js, JSValue self, JSValue val) { SDL_Texture *tex = js2SDL_Texture(js,self); float alpha = js2number(js,val); SDL_SetTextureAlphaModFloat(tex, alpha); return JS_NULL; } // Color mod getter/setter JSValue js_texture_get_colorMod(JSContext *js, JSValue self) { SDL_Texture *tex = js2SDL_Texture(js,self); float r, g, b; SDL_GetTextureColorModFloat(tex, &r, &g, &b); JSValue ret = JS_NewObject(js); JS_SetPropertyStr(js, ret, "r", number2js(js, r)); JS_SetPropertyStr(js, ret, "g", number2js(js, g)); JS_SetPropertyStr(js, ret, "b", number2js(js, b)); return ret; } JSValue js_texture_set_colorMod(JSContext *js, JSValue self, JSValue val) { SDL_Texture *tex = js2SDL_Texture(js,self); colorf color = js2color(js,val); SDL_SetTextureColorModFloat(tex, color.r, color.g, color.b); return JS_NULL; } // Blend mode getter/setter JSValue js_texture_get_blendMode(JSContext *js, JSValue self) { SDL_Texture *tex = js2SDL_Texture(js,self); SDL_BlendMode mode; SDL_GetTextureBlendMode(tex, &mode); return blendmode2js(js, mode); } JSValue js_texture_set_blendMode(JSContext *js, JSValue self, JSValue val) { SDL_Texture *tex = js2SDL_Texture(js,self); SDL_BlendMode mode = js2blendmode(js,val); SDL_SetTextureBlendMode(tex, mode); return JS_NULL; } // Scale mode getter/setter JSValue js_texture_get_scaleMode(JSContext *js, JSValue self) { SDL_Texture *tex = js2SDL_Texture(js,self); SDL_ScaleMode mode; SDL_GetTextureScaleMode(tex, &mode); const char *str = "nearest"; if (mode == SDL_SCALEMODE_LINEAR) str = "linear"; return JS_NewString(js, str); } JSValue js_texture_set_scaleMode(JSContext *js, JSValue self, JSValue val) { SDL_Texture *tex = js2SDL_Texture(js,self); SDL_ScaleMode mode = js2SDL_ScaleMode(js,val); SDL_SetTextureScaleMode(tex, mode); return JS_NULL; } // Size getter (read-only) JSValue js_texture_get_size(JSContext *js, JSValue self) { SDL_Texture *tex = js2SDL_Texture(js,self); float w, h; SDL_GetTextureSize(tex, &w, &h); return vec22js(js, (HMM_Vec2){w, h}); } // Update texture data JSC_CCALL(texture_update, SDL_Texture *tex = js2SDL_Texture(js,self); rect update_rect = {0}; void *pixels = NULL; int pitch = 0; if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsNull(argv[0])) update_rect = js2rect(js, argv[0]); if (argc >= 2) { size_t size; pixels = js_get_blob_data(js, &size, argv[1]); if (!pixels) return JS_ThrowTypeError(js, "Second argument must be an ArrayBuffer"); } if (argc >= 3) pitch = js2number(js, argv[2]); if (!SDL_UpdateTexture(tex, (update_rect.w > 0 && update_rect.h > 0) ? &update_rect : NULL, pixels, pitch)) return JS_ThrowReferenceError(js, "Failed to update texture: %s", SDL_GetError()); ) // Lock texture for direct pixel access JSC_CCALL(texture_lock, SDL_Texture *tex = js2SDL_Texture(js,self); rect lock_rect = {0}; void *pixels; int pitch; if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsNull(argv[0])) lock_rect = js2rect(js, argv[0]); if (!SDL_LockTexture(tex, (lock_rect.w > 0 && lock_rect.h > 0) ? &lock_rect : NULL, &pixels, &pitch)) return JS_ThrowReferenceError(js, "Failed to lock texture: %s", SDL_GetError()); ) // Unlock texture JSC_CCALL(texture_unlock, SDL_Texture *tex = js2SDL_Texture(js,self); SDL_UnlockTexture(tex); ) // Query texture info JSC_CCALL(texture_query, SDL_Texture *tex = js2SDL_Texture(js,self); SDL_PixelFormat format; int access, w, h; if (!SDL_GetTextureProperties(tex)) return JS_ThrowReferenceError(js, "Failed to query texture"); ret = JS_NewObject(js); // TODO: Get these from properties once SDL3 API is clear return ret; ) static const JSCFunctionListEntry js_SDL_Texture_funcs[] = { CGETSET_ADD(texture, alphaMod), CGETSET_ADD(texture, colorMod), CGETSET_ADD(texture, blendMode), CGETSET_ADD(texture, scaleMode), JS_CGETSET_DEF("size", js_texture_get_size, NULL), MIST_FUNC_DEF(texture, update, 3), MIST_FUNC_DEF(texture, lock, 1), MIST_FUNC_DEF(texture, unlock, 0), MIST_FUNC_DEF(texture, query, 0), }; // Additional renderer functions // Create and destroy window/renderer pair JSC_CCALL(sdl_createWindowAndRenderer, const char *title = JS_ToCString(js, argv[0]); int width = js2number(js, argv[1]); int height = js2number(js, argv[2]); SDL_WindowFlags flags = argc > 3 ? js2number(js, argv[3]) : 0; SDL_Window *window; SDL_Renderer *renderer; if (!SDL_CreateWindowAndRenderer(title, width, height, flags, &window, &renderer)) { JS_FreeCString(js,title); return JS_ThrowReferenceError(js, "Failed to create window and renderer: %s", SDL_GetError()); } JS_FreeCString(js,title); ret = JS_NewObject(js); JS_SetPropertyStr(js, ret, "window", SDL_Window2js(js, window)); JS_SetPropertyStr(js, ret, "renderer", SDL_Renderer2js(js, renderer)); return ret; ) #include "qjs_wota.h" JSValue js_sdl_video_use(JSContext *js) { if (!SDL_Init(SDL_INIT_VIDEO)) return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError()); JSValue ret = JS_NewObject(js); // Initialize classes QJSCLASSPREP_FUNCS(SDL_Window) QJSCLASSPREP_FUNCS(SDL_Renderer) QJSCLASSPREP_FUNCS(SDL_Texture) QJSCLASSPREP_NO_FUNCS(SDL_Cursor) // Create window constructor JSValue window_ctor = JS_NewCFunction2(js, js_window_constructor, "window", 1, JS_CFUNC_constructor, 0); // Set prototype on constructor JS_SetConstructor(js, window_ctor, SDL_Window_proto); // Create texture constructor JSValue texture_ctor = JS_NewCFunction2(js, js_texture_constructor, "texture", 2, JS_CFUNC_constructor, 0); // Set prototype on constructor JS_SetConstructor(js, texture_ctor, SDL_Texture_proto); // Set constructors in endowments JS_SetPropertyStr(js, ret, "window", window_ctor); JS_SetPropertyStr(js, ret, "texture", texture_ctor); // Add utility function JS_SetPropertyStr(js, ret, "createWindowAndRenderer", JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4)); // 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; }