1789 lines
56 KiB
C
1789 lines
56 KiB
C
#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 <SDL3/SDL.h>
|
|
#include <SDL3/SDL_gpu.h>
|
|
#include <SDL3/SDL_error.h>
|
|
#include <SDL3/SDL_properties.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(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_IsUndefined(opacity_val)) {
|
|
JS_SetPropertyStr(js, window_obj, "opacity", opacity_val);
|
|
}
|
|
|
|
JSValue min_size_val = JS_GetPropertyStr(js, opts, "minimumSize");
|
|
if (!JS_IsUndefined(min_size_val)) {
|
|
JS_SetPropertyStr(js, window_obj, "minimumSize", min_size_val);
|
|
}
|
|
|
|
JSValue max_size_val = JS_GetPropertyStr(js, opts, "maximumSize");
|
|
if (!JS_IsUndefined(max_size_val)) {
|
|
JS_SetPropertyStr(js, window_obj, "maximumSize", max_size_val);
|
|
}
|
|
|
|
JSValue pos_val = JS_GetPropertyStr(js, opts, "position");
|
|
if (!JS_IsUndefined(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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
JSValue js_window_restore(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|
{
|
|
SDL_Window *w = js2SDL_Window(js,self);
|
|
SDL_RestoreWindow(w);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
JSValue js_window_destroy(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|
{
|
|
SDL_Window *w = js2SDL_Window(js,self);
|
|
SDL_DestroyWindow(w);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
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_IsUndefined(val))
|
|
parent = js2SDL_Window(js,val);
|
|
SDL_SetWindowParent(w, parent);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
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_IsUndefined(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_UNDEFINED;
|
|
}
|
|
|
|
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_UNDEFINED;
|
|
|
|
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_UNDEFINED;
|
|
}
|
|
|
|
/* 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_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_IsUndefined(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_IsUndefined(val))
|
|
SDL_SetRenderTarget(r, NULL);
|
|
else {
|
|
SDL_Texture *tex = js2SDL_Texture(js,val);
|
|
SDL_SetRenderTarget(r,tex);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_IsUndefined(val))
|
|
SDL_SetRenderViewport(r,NULL);
|
|
else {
|
|
rect view = js2rect(js,val);
|
|
SDL_SetRenderViewport(r,&view);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
// 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_IsUndefined(val))
|
|
SDL_SetRenderClipRect(r,NULL);
|
|
else {
|
|
rect clip = js2rect(js,val);
|
|
SDL_SetRenderClipRect(r,&clip);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_IsUndefined(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_IsUndefined(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_IsUndefined(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, 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_IsUndefined(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_IsUndefined(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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_UNDEFINED;
|
|
}
|
|
|
|
// 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_IsUndefined(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_IsUndefined(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;
|
|
}
|