window actor now created when use('sdl_video') is called

This commit is contained in:
2025-05-25 19:06:24 -05:00
parent 49786842f0
commit aac0c3813b
8 changed files with 349 additions and 75 deletions

View File

@@ -0,0 +1,61 @@
// SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations
// Default window configuration - documents all available window options
var default_window = {
// Basic properties
title: "Prosperon Window",
width: 640,
height: 480,
// Position - can be numbers or "centered"
x: undefined, // SDL_WINDOWPOS_UNDEFINED by default
y: undefined, // SDL_WINDOWPOS_UNDEFINED by default
// Window behavior flags
resizable: true,
fullscreen: false,
hidden: false,
borderless: false,
alwaysOnTop: false,
minimized: false,
maximized: false,
// Input grabbing
mouseGrabbed: false,
keyboardGrabbed: false,
// Display properties
highPixelDensity: false,
transparent: false,
opacity: 1.0, // 0.0 to 1.0
// Focus behavior
notFocusable: false,
// Special window types (mutually exclusive)
utility: false, // Utility window (not in taskbar)
tooltip: false, // Tooltip window (requires parent)
popupMenu: false, // Popup menu window (requires parent)
// Graphics API flags (let SDL choose if not specified)
opengl: false, // Force OpenGL context
vulkan: false, // Force Vulkan context
metal: false, // Force Metal context (macOS)
// Advanced properties
parent: undefined, // Parent window for tooltips/popups/modal
modal: false, // Modal to parent window (requires parent)
externalGraphicsContext: false, // Use external graphics context
// Input handling
textInput: true, // Enable text input on creation
};
var winwin
// Export the video functions for other actors to call
$_.receiver(function(msg) {
console.log("Video actor received message:", msg);
winwin = new prosperon.endowments.window(default_window)
});

View File

@@ -1,2 +0,0 @@
var video = prosperon.endowments.video

View File

@@ -1,5 +0,0 @@
var sdl_video = this
return sdl_video

View File

@@ -52,8 +52,6 @@
#include "qjs_steam.h"
#endif
SDL_Window *global_window;
#include <signal.h>
void gui_input(SDL_Event *e);

View File

@@ -9,8 +9,6 @@
#define JS_SetProperty(js, tar, str, val) JS_SetPropertyStr(js, tar, #str, val)
#define JS_GetProperty(js, tar, atom) JS_GetPropertyStr(js, tar, #atom)
extern SDL_Window *global_window;
// Core FFI functions
void ffi_load(JSContext *js);
int js_print_exception(JSContext *js, JSValue v);

View File

@@ -11,6 +11,7 @@
// Global GPU device and window
static SDL_GPUDevice *global_gpu;
static SDL_Window *global_window;
// GPU Free functions
void SDL_GPUDevice_free(JSRuntime *rt, SDL_GPUDevice *d)

View File

@@ -11,10 +11,12 @@
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_properties.h>
#include <stdio.h>
#include <string.h>
// External globals
extern SDL_Window *global_window;
extern SDL_ThreadID main_thread;
// External function declarations
extern prosperon_rt *create_actor(int argc, char **argv, void (*hook)(JSContext *));
extern const char *register_actor(const char *id, prosperon_rt *rt, int main_thread);
// SDL Window free function
void SDL_Window_free(JSRuntime *rt, SDL_Window *w)
@@ -109,57 +111,235 @@ static inline rect renderer_worldrect_to_screen(renderer_ctx *ctx, rect r_world)
return out;
}
// Window creation function
#define JS_SDL_PROP(JS, VAL, SDLPROP, PROP) \
{ \
JSValue v = JS_GetPropertyStr(JS,VAL,#PROP); \
const char *str = JS_ToCString(JS, v); \
SDL_SetAppMetadataProperty(SDLPROP, str); \
JS_FreeCString(JS,str); \
JS_FreeValue(js,v); \
} \
JSC_CCALL(os_engine_start,
if (!SDL_Init(SDL_INIT_VIDEO))
return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
if (SDL_GetCurrentThreadID() != main_thread)
return JS_ThrowInternalError(js, "This can only be called from the root actor.");
if (global_window)
return JS_ThrowReferenceError(js, "The engine was already started.");
JSValue p = argv[0];
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_NAME_STRING, name)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_VERSION_STRING, version)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_IDENTIFIER_STRING, identifier)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_CREATOR_STRING, creator)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_COPYRIGHT_STRING, copyright)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_URL_STRING, url)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_TYPE_STRING, type)
// 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");
const char *title;
JSValue title_val = JS_GetPropertyStr(js, argv[0], "title");
title = JS_ToCString(js, title_val);
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);
JSValue width_val = JS_GetPropertyStr(js, argv[0], "width");
JSValue height_val = JS_GetPropertyStr(js, argv[0], "height");
int width = js2number(js, width_val);
int height = js2number(js, height_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);
SDL_Window *new = SDL_CreateWindow(title, width, height, SDL_WINDOW_RESIZABLE);
JS_FreeCString(js,title);
if (!new) return JS_ThrowReferenceError(js, "Couldn't open window: %s\n", SDL_GetError());
SDL_StartTextInput(new);
global_window = new;
return SDL_Window2js(js,new);
)
// Handle window position
int x = SDL_WINDOWPOS_UNDEFINED;
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) x = SDL_WINDOWPOS_CENTERED;
JS_FreeCString(js, pos);
} else {
x = js2number(js, x_val);
}
}
JS_FreeValue(js, x_val);
int y = SDL_WINDOWPOS_UNDEFINED;
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) y = SDL_WINDOWPOS_CENTERED;
JS_FreeCString(js, pos);
} else {
y = js2number(js, y_val);
}
}
JS_FreeValue(js, y_val);
// Build window flags
Uint64 flags = 0;
// Check boolean properties
JSValue resizable = JS_GetPropertyStr(js, opts, "resizable");
if (JS_ToBool(js, resizable)) flags |= SDL_WINDOW_RESIZABLE;
JS_FreeValue(js, resizable);
JSValue fullscreen = JS_GetPropertyStr(js, opts, "fullscreen");
if (JS_ToBool(js, fullscreen)) flags |= SDL_WINDOW_FULLSCREEN;
JS_FreeValue(js, fullscreen);
JSValue hidden = JS_GetPropertyStr(js, opts, "hidden");
if (JS_ToBool(js, hidden)) flags |= SDL_WINDOW_HIDDEN;
JS_FreeValue(js, hidden);
JSValue borderless = JS_GetPropertyStr(js, opts, "borderless");
if (JS_ToBool(js, borderless)) flags |= SDL_WINDOW_BORDERLESS;
JS_FreeValue(js, borderless);
JSValue always_on_top = JS_GetPropertyStr(js, opts, "alwaysOnTop");
if (JS_ToBool(js, always_on_top)) flags |= SDL_WINDOW_ALWAYS_ON_TOP;
JS_FreeValue(js, always_on_top);
JSValue minimized = JS_GetPropertyStr(js, opts, "minimized");
if (JS_ToBool(js, minimized)) flags |= SDL_WINDOW_MINIMIZED;
JS_FreeValue(js, minimized);
JSValue maximized = JS_GetPropertyStr(js, opts, "maximized");
if (JS_ToBool(js, maximized)) flags |= SDL_WINDOW_MAXIMIZED;
JS_FreeValue(js, maximized);
JSValue mouse_grabbed = JS_GetPropertyStr(js, opts, "mouseGrabbed");
if (JS_ToBool(js, mouse_grabbed)) flags |= SDL_WINDOW_MOUSE_GRABBED;
JS_FreeValue(js, mouse_grabbed);
JSValue keyboard_grabbed = JS_GetPropertyStr(js, opts, "keyboardGrabbed");
if (JS_ToBool(js, keyboard_grabbed)) flags |= SDL_WINDOW_KEYBOARD_GRABBED;
JS_FreeValue(js, keyboard_grabbed);
JSValue high_pixel_density = JS_GetPropertyStr(js, opts, "highPixelDensity");
if (JS_ToBool(js, high_pixel_density)) flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
JS_FreeValue(js, high_pixel_density);
JSValue transparent = JS_GetPropertyStr(js, opts, "transparent");
if (JS_ToBool(js, transparent)) flags |= SDL_WINDOW_TRANSPARENT;
JS_FreeValue(js, transparent);
JSValue not_focusable = JS_GetPropertyStr(js, opts, "notFocusable");
if (JS_ToBool(js, not_focusable)) flags |= SDL_WINDOW_NOT_FOCUSABLE;
JS_FreeValue(js, not_focusable);
// Special window types
JSValue utility = JS_GetPropertyStr(js, opts, "utility");
if (JS_ToBool(js, utility)) flags |= SDL_WINDOW_UTILITY;
JS_FreeValue(js, utility);
JSValue tooltip = JS_GetPropertyStr(js, opts, "tooltip");
if (JS_ToBool(js, tooltip)) flags |= SDL_WINDOW_TOOLTIP;
JS_FreeValue(js, tooltip);
JSValue popup_menu = JS_GetPropertyStr(js, opts, "popupMenu");
if (JS_ToBool(js, popup_menu)) flags |= SDL_WINDOW_POPUP_MENU;
JS_FreeValue(js, popup_menu);
// Graphics API flags
JSValue opengl = JS_GetPropertyStr(js, opts, "opengl");
if (JS_ToBool(js, opengl)) flags |= SDL_WINDOW_OPENGL;
JS_FreeValue(js, opengl);
JSValue vulkan = JS_GetPropertyStr(js, opts, "vulkan");
if (JS_ToBool(js, vulkan)) flags |= SDL_WINDOW_VULKAN;
JS_FreeValue(js, vulkan);
JSValue metal = JS_GetPropertyStr(js, opts, "metal");
if (JS_ToBool(js, metal)) flags |= SDL_WINDOW_METAL;
JS_FreeValue(js, metal);
// Handle parent window for tooltips, popups, etc
SDL_Window *parent = NULL;
JSValue parent_val = JS_GetPropertyStr(js, opts, "parent");
if (!JS_IsUndefined(parent_val)) {
parent = js2SDL_Window(js, parent_val);
}
JS_FreeValue(js, parent_val);
// Create window with properties if we need special handling
SDL_Window *window = NULL;
if (parent || (flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))) {
// Use SDL_CreateWindowWithProperties for advanced features
SDL_PropertiesID props = SDL_CreateProperties();
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);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
// Set boolean properties
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, flags & SDL_WINDOW_RESIZABLE);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, flags & SDL_WINDOW_FULLSCREEN);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, flags & SDL_WINDOW_HIDDEN);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, flags & SDL_WINDOW_BORDERLESS);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN, flags & SDL_WINDOW_ALWAYS_ON_TOP);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN, flags & SDL_WINDOW_MINIMIZED);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN, flags & SDL_WINDOW_MAXIMIZED);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN, flags & SDL_WINDOW_MOUSE_GRABBED);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN, flags & SDL_WINDOW_TRANSPARENT);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !(flags & SDL_WINDOW_NOT_FOCUSABLE));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN, flags & SDL_WINDOW_UTILITY);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, flags & SDL_WINDOW_TOOLTIP);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, flags & SDL_WINDOW_POPUP_MENU);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, flags & SDL_WINDOW_OPENGL);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, flags & SDL_WINDOW_VULKAN);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, flags & SDL_WINDOW_METAL);
if (parent) {
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, parent);
}
// Handle modal property
JSValue modal = JS_GetPropertyStr(js, opts, "modal");
if (JS_ToBool(js, modal) && parent) {
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN, 1);
}
JS_FreeValue(js, modal);
// Handle external graphics context
JSValue external_graphics = JS_GetPropertyStr(js, opts, "externalGraphicsContext");
if (JS_ToBool(js, external_graphics)) {
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, 1);
}
JS_FreeValue(js, external_graphics);
window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
} else {
// Use simpler API for basic windows
window = SDL_CreateWindow(title, width, height, flags);
}
// 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());
}
// Set additional properties that can't be set during creation
JSValue opacity_val = JS_GetPropertyStr(js, opts, "opacity");
if (!JS_IsUndefined(opacity_val)) {
float opacity = js2number(js, opacity_val);
SDL_SetWindowOpacity(window, opacity);
}
JS_FreeValue(js, opacity_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 SDL_Window2js(js, window);
}
// Window functions
JSC_SCALL(SDL_Window_make_renderer,
@@ -662,7 +842,7 @@ static const JSCFunctionListEntry js_renderer_ctx_funcs[] = {
extern SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v);
static const JSCFunctionListEntry js_sdl_video_funcs[] = {
MIST_FUNC_DEF(os, engine_start, 1),
// No regular functions, window is now a constructor
};
JSC_CCALL(texture_mode,
@@ -675,28 +855,64 @@ static const JSCFunctionListEntry js_SDL_Texture_funcs[] = {
MIST_FUNC_DEF(texture, mode, 1),
};
JSValue js_sdl_video_use(JSContext *js) {
// In reality, this should return the ID of the ioactor. If it's not created yet,
// on any thread, it should be created. Then, the sdl_video_funcs need to be
// attached to it so it has access via prosperon.endowments.video
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_sdl_video_funcs,countof(js_sdl_video_funcs));
// Hook function to set up endowments for the video actor
static void video_actor_hook(JSContext *js) {
// Get prosperon object
JSValue prosperon = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "prosperon");
JSValue c_types = JS_GetPropertyStr(js, prosperon, "c_types");
// Initialize classes
JSValue c_types = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "c_types");
JSValue prosperon = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "prosperon");
c_types = JS_GetPropertyStr(js, prosperon, "c_types");
JS_FreeValue(js, prosperon);
// add the singular ioactor address to the exported module
QJSCLASSPREP_FUNCS(SDL_Window)
QJSCLASSPREP_FUNCS(renderer_ctx)
QJSCLASSPREP_FUNCS(SDL_Texture)
JS_FreeValue(js, c_types);
return mod;
// 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);
// Get or create endowments object
JSValue endowments = JS_GetPropertyStr(js, prosperon, "endowments");
if (JS_IsUndefined(endowments)) {
endowments = JS_NewObject(js);
JS_SetPropertyStr(js, prosperon, "endowments", JS_DupValue(js, endowments));
}
// Set window constructor in endowments
JS_SetPropertyStr(js, endowments, "window", window_ctor);
JS_FreeValue(js, endowments);
JS_FreeValue(js, prosperon);
}
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());
// Generate a unique ID for the video actor
char id[64];
snprintf(id, sizeof(id), "video_%d", SDL_GetTicks());
printf("id is %s\n", id);
// Prepare argv for create_actor
// We need to create the actor on the main thread
const char *argv[] = {
"./prosperon",
"spawn",
"--id", id,
"--program", "scripts/core/_sdl_video.js",
"--main", "1"
};
int argc = 8;
// Create the actor with the hook to set up endowments
prosperon_rt *rt = create_actor(argc, (char**)argv, video_actor_hook);
return JS_NewString(js,id);
JSValue ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "id", JS_NewString(js,id));
return ret;
}

7
tests/sdl_video.js Normal file
View File

@@ -0,0 +1,7 @@
var video = use('sdl_video')
console.log(video)
send({__ACTORDATA__:{id:video}}, {kind:"HELLO!"})
$_.delay($_.stop, 2)