xbintsc 0.5.32 → 0.5.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/README.zh-CN.md +10 -8
- package/dist/scripts/check-version.d.ts +27 -0
- package/dist/scripts/check-version.js +71 -0
- package/dist/scripts/check-version.js.map +1 -0
- package/dist/scripts/package-platform.d.ts +58 -0
- package/dist/scripts/package-platform.js +142 -0
- package/dist/scripts/package-platform.js.map +1 -0
- package/dist/scripts/verify-platform-package.d.ts +34 -0
- package/dist/scripts/verify-platform-package.js +161 -0
- package/dist/scripts/verify-platform-package.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/main.js +3 -1
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/runtime-lib.d.ts +29 -4
- package/dist/src/driver/runtime-lib.js +68 -10
- package/dist/src/driver/runtime-lib.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/index.js +18 -5
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/media/index.js +4 -3
- package/dist/src/extensions/media/index.js.map +1 -1
- package/dist/src/parser/context.d.ts +41 -0
- package/dist/src/parser/context.js +89 -4
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/literals.js +6 -12
- package/dist/src/parser/literals.js.map +1 -1
- package/dist/tests/driver/platform-package.test.d.ts +1 -0
- package/dist/tests/driver/platform-package.test.js +49 -0
- package/dist/tests/driver/platform-package.test.js.map +1 -0
- package/dist/tests/driver/runtime-lib.test.js +51 -4
- package/dist/tests/driver/runtime-lib.test.js.map +1 -1
- package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
- package/dist/tests/e2e/gui-audio.test.js +287 -0
- package/dist/tests/e2e/gui-audio.test.js.map +1 -0
- package/dist/tests/e2e/gui-browser.test.js +65 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
- package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
- package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-dom.test.js +215 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -1
- package/dist/tests/e2e/gui-events.test.js +112 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -1
- package/dist/tests/e2e/gui-helpers.d.ts +7 -0
- package/dist/tests/e2e/gui-helpers.js +1 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.js +78 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-style.test.js +55 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +258 -0
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2.test.js +45 -1
- package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
- package/dist/tests/extensions/gui-index.test.js +1 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -1
- package/dist/tests/extensions/media.test.js +1 -0
- package/dist/tests/extensions/media.test.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +30 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/platform-tarball.test.d.ts +1 -0
- package/dist/tests/platform-tarball.test.js +111 -0
- package/dist/tests/platform-tarball.test.js.map +1 -0
- package/dist/tests/version.test.js +6 -0
- package/dist/tests/version.test.js.map +1 -1
- package/doc/ai/contributing.md +11 -0
- package/doc/ai/extensions.md +7 -4
- package/doc/ai/troubleshooting.md +7 -5
- package/doc/ai/zh-CN/contributing.md +9 -0
- package/doc/ai/zh-CN/extensions.md +4 -2
- package/doc/ai/zh-CN/troubleshooting.md +5 -3
- package/doc/gui/api.md +99 -22
- package/doc/gui/build.md +11 -4
- package/doc/gui/canvas.md +49 -18
- package/doc/gui/dom.md +57 -7
- package/doc/gui/media.md +4 -0
- package/doc/gui/webgl.md +105 -46
- package/doc/gui.md +7 -3
- package/doc/npm-package.md +55 -27
- package/doc/requirements.md +6 -2
- package/doc/zh-CN/gui/api.md +84 -14
- package/doc/zh-CN/gui/build.md +7 -2
- package/doc/zh-CN/gui/canvas.md +44 -19
- package/doc/zh-CN/gui/dom.md +48 -4
- package/doc/zh-CN/gui/media.md +3 -0
- package/doc/zh-CN/gui/webgl.md +22 -16
- package/doc/zh-CN/gui.md +3 -2
- package/doc/zh-CN/npm-package.md +44 -23
- package/doc/zh-CN/requirements.md +4 -1
- package/package.json +17 -1
- package/runtime/ext_gui/audio.cpp +365 -153
- package/runtime/ext_gui/audio_buffer.cpp +166 -0
- package/runtime/ext_gui/audio_buffer.h +37 -0
- package/runtime/ext_gui/audio_decode.cpp +219 -0
- package/runtime/ext_gui/audio_decode.h +39 -0
- package/runtime/ext_gui/audio_device.cpp +87 -0
- package/runtime/ext_gui/audio_effects.cpp +163 -0
- package/runtime/ext_gui/audio_effects.h +25 -0
- package/runtime/ext_gui/audio_internal.h +30 -0
- package/runtime/ext_gui/audio_mixer.cpp +494 -0
- package/runtime/ext_gui/audio_mixer.h +238 -0
- package/runtime/ext_gui/audio_param.cpp +104 -0
- package/runtime/ext_gui/audio_param_object.cpp +155 -0
- package/runtime/ext_gui/browser.cpp +45 -50
- package/runtime/ext_gui/browser_abort.cpp +249 -0
- package/runtime/ext_gui/browser_dialogs.cpp +76 -0
- package/runtime/ext_gui/browser_media.cpp +176 -0
- package/runtime/ext_gui/browser_navigation.cpp +130 -0
- package/runtime/ext_gui/canvas.cpp +88 -30
- package/runtime/ext_gui/canvas.h +74 -9
- package/runtime/ext_gui/canvas_image_data.cpp +143 -0
- package/runtime/ext_gui/canvas_internal.h +8 -5
- package/runtime/ext_gui/canvas_pattern.cpp +90 -0
- package/runtime/ext_gui/canvas_raster.cpp +31 -9
- package/runtime/ext_gui/canvas_style.cpp +4 -2
- package/runtime/ext_gui/css_selector.cpp +21 -3
- package/runtime/ext_gui/dom.h +4 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
- package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
- package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
- package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
- package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
- package/runtime/ext_gui/dom_api_document.cpp +58 -5
- package/runtime/ext_gui/dom_api_events.cpp +93 -17
- package/runtime/ext_gui/dom_api_internal.h +71 -2
- package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
- package/runtime/ext_gui/dom_api_node.cpp +40 -19
- package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
- package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
- package/runtime/ext_gui/dom_api_proto.cpp +35 -0
- package/runtime/ext_gui/dom_api_style.cpp +223 -13
- package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
- package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
- package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
- package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
- package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
- package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
- package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
- package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
- package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
- package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
- package/runtime/ext_gui/gui.cpp +103 -14
- package/runtime/ext_gui/gui_engine.h +56 -4
- package/runtime/ext_gui/gui_events.cpp +80 -9
- package/runtime/ext_gui/image_bitmap.cpp +28 -0
- package/runtime/ext_gui/image_bitmap.h +26 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
- package/runtime/ext_gui/listener_options.h +43 -0
- package/runtime/ext_gui/media.cpp +32 -0
- package/runtime/ext_gui/media.h +13 -0
- package/runtime/ext_gui/media_api.h +23 -0
- package/runtime/ext_gui/paint.cpp +28 -0
- package/runtime/ext_gui/paint.h +4 -0
- package/runtime/ext_gui/webgl.h +152 -18
- package/runtime/ext_gui/webgl_blit.cpp +95 -0
- package/runtime/ext_gui/webgl_buffer.cpp +178 -263
- package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
- package/runtime/ext_gui/webgl_clear.cpp +142 -0
- package/runtime/ext_gui/webgl_compressed.cpp +180 -0
- package/runtime/ext_gui/webgl_context.cpp +241 -156
- package/runtime/ext_gui/webgl_draw.cpp +68 -3
- package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
- package/runtime/ext_gui/webgl_glsl.h +40 -0
- package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
- package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
- package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
- package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
- package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
- package/runtime/ext_gui/webgl_program_internal.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +13 -1
- package/runtime/ext_gui/webgl_state.cpp +228 -0
- package/runtime/ext_gui/webgl_texture.cpp +144 -17
- package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
- package/runtime/ext_gui/webgl_uniform_block.h +31 -0
- package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
- package/runtime/ext_gui/window.cpp +14 -65
- package/runtime/ext_gui/window_events.cpp +125 -0
- package/runtime/ext_media/decode_audio.cpp +308 -0
- package/runtime/ext_media/media_bridge.c +2 -0
- package/runtime/rt.h +43 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_array_buffer.c +30 -0
- package/runtime/xt_builtins.c +0 -21
- package/runtime/xt_containers/access.inc +94 -2
- package/runtime/xt_containers/objects.inc +29 -31
- package/runtime/xt_typed_array/elements.inc +13 -0
- package/scripts/build-media.ts +14 -6
- package/scripts/build-runtime.ts +21 -16
- package/scripts/check-version.ts +39 -9
- package/scripts/package-platform.ts +183 -0
- package/scripts/set-platform-version.ts +62 -0
- package/scripts/verify-platform-package.ts +182 -0
- package/src/cli/main.ts +5 -1
- package/src/driver/runtime-lib.ts +73 -9
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/index.ts +18 -5
- package/src/extensions/media/index.ts +4 -3
- package/src/parser/context.ts +99 -4
- package/src/parser/literals.ts +6 -12
package/runtime/ext_gui/gui.cpp
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
#include "paint.h"
|
|
20
20
|
|
|
21
21
|
#include <stdio.h>
|
|
22
|
+
#include <stdlib.h>
|
|
22
23
|
#include <string.h>
|
|
23
24
|
|
|
24
25
|
namespace guidev {
|
|
@@ -89,20 +90,49 @@ static void xt_gui_gc_scan_roots(void) {
|
|
|
89
90
|
xt_gc_mark_value(win.document_object);
|
|
90
91
|
for (const auto &entry : win.node_handles) xt_gc_mark_value(entry.second);
|
|
91
92
|
for (const auto &entry : win.node_listeners) {
|
|
92
|
-
for (const XtGuiNodeListener &listener : entry.second)
|
|
93
|
+
for (const XtGuiNodeListener &listener : entry.second) {
|
|
94
|
+
xt_gc_mark_value(listener.fn);
|
|
95
|
+
xt_gc_mark_value(listener.signal);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const XtGuiWindowListener &listener : win.window_listeners) {
|
|
99
|
+
xt_gc_mark_value(listener.fn);
|
|
100
|
+
xt_gc_mark_value(listener.signal);
|
|
93
101
|
}
|
|
94
102
|
for (const XtGuiAnimationFrame &frame : win.animation_frames) xt_gc_mark_value(frame.fn);
|
|
95
103
|
for (const XtGuiAnimationFrame &frame : win.running_frames) xt_gc_mark_value(frame.fn);
|
|
104
|
+
for (const xt_value list : win.media_lists) xt_gc_mark_value(list);
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
/* -- initialisation ------------------------------------------------------- */
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Exit status for "this host cannot run a GUI program": no video driver, or no
|
|
112
|
+
* GPU device the SDL_GPU backends accept. It is deliberately distinct from a
|
|
113
|
+
* failed program so a CI script (or a user) can skip the run instead of
|
|
114
|
+
* reporting a crash. 77 is the autotools "skip" convention; `ci.yml` treats it
|
|
115
|
+
* that way for the provisional macOS job.
|
|
116
|
+
*/
|
|
117
|
+
const int kGuiUnavailableExit = 77;
|
|
118
|
+
|
|
119
|
+
/** Report a window that could not be created as a JavaScript `Error`. Returning
|
|
120
|
+
* `undefined` used to surface one line later as `Cannot read properties of
|
|
121
|
+
* undefined (reading 'on')`, which hid the real diagnostic. */
|
|
122
|
+
static void throw_gui_error(const char *message) {
|
|
123
|
+
xt_value text = xt_string_from_cstr(message);
|
|
124
|
+
xt_throw(xt_error_ctor_by_name(xt_string_from_cstr("Error"), 1, &text));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static void ensure_init(void) {
|
|
128
|
+
if (g_sdl_initialized) return;
|
|
103
129
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
|
104
130
|
fprintf(stderr, "xt_gui: SDL_Init failed: %s\n", SDL_GetError());
|
|
105
|
-
|
|
131
|
+
fprintf(stderr,
|
|
132
|
+
"xt_gui: the GUI extension needs a display (or a virtual one such as Xvfb); "
|
|
133
|
+
"exiting with status %d.\n",
|
|
134
|
+
kGuiUnavailableExit);
|
|
135
|
+
exit(kGuiUnavailableExit);
|
|
106
136
|
}
|
|
107
137
|
/* Accept every shader format so the same code runs on Metal, Vulkan and
|
|
108
138
|
* D3D12; the backend only needs the one it supports. The engine ships MSL,
|
|
@@ -113,11 +143,17 @@ static int ensure_init(void) { if (g_sdl_initialized) return 1;
|
|
|
113
143
|
g_device = SDL_CreateGPUDevice(formats, false, NULL);
|
|
114
144
|
if (g_device == NULL) {
|
|
115
145
|
fprintf(stderr, "xt_gui: SDL_CreateGPUDevice failed: %s\n", SDL_GetError());
|
|
146
|
+
fprintf(stderr,
|
|
147
|
+
"xt_gui: the GUI extension needs a Metal (macOS), Vulkan or D3D12 device; "
|
|
148
|
+
"this host does not provide a usable one. Exiting with status %d.\n",
|
|
149
|
+
kGuiUnavailableExit);
|
|
116
150
|
SDL_Quit();
|
|
117
|
-
|
|
151
|
+
exit(kGuiUnavailableExit);
|
|
118
152
|
}
|
|
153
|
+
/* Registered only once a window can exist: a failed init has nothing to
|
|
154
|
+
* mark, and a caller that cannot retry never needs a second provider. */
|
|
155
|
+
xt_gc_register_root_provider(xt_gui_gc_scan_roots);
|
|
119
156
|
g_sdl_initialized = 1;
|
|
120
|
-
return 1;
|
|
121
157
|
}
|
|
122
158
|
|
|
123
159
|
/* -- window lookup -------------------------------------------------------- */
|
|
@@ -149,6 +185,9 @@ void xt_gui_quit_window(XtGuiWindow *win) {
|
|
|
149
185
|
win->open = 0;
|
|
150
186
|
xt_gui_emit(win, "close");
|
|
151
187
|
win->animation_frames.clear();
|
|
188
|
+
win->media_lists.clear();
|
|
189
|
+
win->media_lists_by_query.clear();
|
|
190
|
+
win->window_listeners.clear();
|
|
152
191
|
xt_gui_media_shutdown(win);
|
|
153
192
|
xt_gui_canvas_shutdown(win);
|
|
154
193
|
xt_gui_handles_reset(win);
|
|
@@ -190,12 +229,12 @@ static int xt_gui_int_or(xt_value value, int fallback) {
|
|
|
190
229
|
/* Build (and record) one window. Shared by `createWindow` and the implicit
|
|
191
230
|
* window a page written against `window`/`document` gets on first access. */
|
|
192
231
|
static XtGuiWindow *create_window_record(const char *title, int width, int height,
|
|
193
|
-
bool high_density) {
|
|
232
|
+
bool high_density, SDL_WindowFlags extra_flags) {
|
|
194
233
|
if (g_window_count >= XT_GUI_MAX_WINDOWS) {
|
|
195
234
|
fprintf(stderr, "xt_gui: too many windows (max %d)\n", XT_GUI_MAX_WINDOWS);
|
|
196
235
|
return NULL;
|
|
197
236
|
}
|
|
198
|
-
SDL_WindowFlags flags =
|
|
237
|
+
SDL_WindowFlags flags = extra_flags;
|
|
199
238
|
if (high_density) flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
|
200
239
|
SDL_Window *window = SDL_CreateWindow(title, width, height, flags);
|
|
201
240
|
if (window == NULL) {
|
|
@@ -225,6 +264,11 @@ static XtGuiWindow *create_window_record(const char *title, int width, int heigh
|
|
|
225
264
|
record->animation_frames.clear();
|
|
226
265
|
record->media_players.clear();
|
|
227
266
|
record->next_animation_frame_id = 1;
|
|
267
|
+
record->scroll_x = 0.0f;
|
|
268
|
+
record->scroll_y = 0.0f;
|
|
269
|
+
record->focused = 0;
|
|
270
|
+
record->media_lists.clear();
|
|
271
|
+
record->media_lists_by_query.clear();
|
|
228
272
|
record->background[0] = 0.08f;
|
|
229
273
|
record->background[1] = 0.09f;
|
|
230
274
|
record->background[2] = 0.11f;
|
|
@@ -254,20 +298,43 @@ xt_value xt_gui_ensure_window(void) {
|
|
|
254
298
|
for (int i = g_window_count - 1; i >= 0; i--) {
|
|
255
299
|
if (g_windows[i].open) return g_windows[i].object;
|
|
256
300
|
}
|
|
257
|
-
|
|
258
|
-
XtGuiWindow *record =
|
|
259
|
-
|
|
301
|
+
ensure_init();
|
|
302
|
+
XtGuiWindow *record =
|
|
303
|
+
create_window_record("xbintsc gui", 800, 600, true, SDL_WINDOW_RESIZABLE);
|
|
304
|
+
if (record == NULL) {
|
|
305
|
+
throw_gui_error("xbintsc gui: could not open the implicit window (see the diagnostic above)");
|
|
306
|
+
return XT_UNDEFINED;
|
|
307
|
+
}
|
|
260
308
|
g_implicit_window = 1;
|
|
261
309
|
return record->object;
|
|
262
310
|
}
|
|
263
311
|
|
|
264
312
|
extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
|
|
265
|
-
|
|
313
|
+
ensure_init();
|
|
266
314
|
|
|
267
315
|
const char *title = "xbintsc gui";
|
|
268
316
|
int width = 800;
|
|
269
317
|
int height = 600;
|
|
270
318
|
bool high_density = true;
|
|
319
|
+
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
|
|
320
|
+
bool has_x = false;
|
|
321
|
+
bool has_y = false;
|
|
322
|
+
bool has_opacity = false;
|
|
323
|
+
int x = 0;
|
|
324
|
+
int y = 0;
|
|
325
|
+
float opacity = 1.0f;
|
|
326
|
+
bool maximized = false;
|
|
327
|
+
bool minimized = false;
|
|
328
|
+
auto option_bool = [](xt_value options, const char *name, bool fallback) {
|
|
329
|
+
xt_value value = xt_object_get_cstr(options, name);
|
|
330
|
+
return XT_IS_BOOL(value) ? (xt_truthy(value) != 0) : fallback;
|
|
331
|
+
};
|
|
332
|
+
auto option_number = [](xt_value options, const char *name, bool *present, double fallback) {
|
|
333
|
+
xt_value value = xt_object_get_cstr(options, name);
|
|
334
|
+
if (!XT_IS_NUMBER(value)) return fallback;
|
|
335
|
+
*present = true;
|
|
336
|
+
return xt_to_number(value);
|
|
337
|
+
};
|
|
271
338
|
xt_value options = xt_arg(argc, argv, 0);
|
|
272
339
|
if (XT_IS_OBJECT(options)) {
|
|
273
340
|
title = xt_gui_string_or(xt_object_get_cstr(options, "title"), title);
|
|
@@ -275,6 +342,19 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
|
|
|
275
342
|
height = xt_gui_int_or(xt_object_get_cstr(options, "height"), height);
|
|
276
343
|
xt_value density = xt_object_get_cstr(options, "highDensity");
|
|
277
344
|
if (!XT_IS_UNDEFINED(density)) high_density = xt_truthy(density);
|
|
345
|
+
/* Window flags and placement: the rest of the options object used to be
|
|
346
|
+
* ignored, which made `{ resizable: false }` or `{ borderless: true }`
|
|
347
|
+
* silently open a plain window. */
|
|
348
|
+
if (!option_bool(options, "resizable", true)) flags &= ~SDL_WINDOW_RESIZABLE;
|
|
349
|
+
if (option_bool(options, "borderless", false)) flags |= SDL_WINDOW_BORDERLESS;
|
|
350
|
+
if (option_bool(options, "alwaysOnTop", false)) flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
|
351
|
+
if (option_bool(options, "hidden", false)) flags |= SDL_WINDOW_HIDDEN;
|
|
352
|
+
if (option_bool(options, "fullscreen", false)) flags |= SDL_WINDOW_FULLSCREEN;
|
|
353
|
+
x = (int)option_number(options, "x", &has_x, 0);
|
|
354
|
+
y = (int)option_number(options, "y", &has_y, 0);
|
|
355
|
+
opacity = (float)option_number(options, "opacity", &has_opacity, 1.0);
|
|
356
|
+
maximized = option_bool(options, "maximized", false);
|
|
357
|
+
minimized = option_bool(options, "minimized", false);
|
|
278
358
|
} else if (XT_IS_STRING(options)) {
|
|
279
359
|
title = xt_string_data(options);
|
|
280
360
|
width = xt_gui_int_or(xt_arg(argc, argv, 1), width);
|
|
@@ -303,8 +383,17 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
|
|
|
303
383
|
return record->object;
|
|
304
384
|
}
|
|
305
385
|
|
|
306
|
-
XtGuiWindow *record = create_window_record(title, width, height, high_density);
|
|
307
|
-
|
|
386
|
+
XtGuiWindow *record = create_window_record(title, width, height, high_density, flags);
|
|
387
|
+
if (record == NULL) {
|
|
388
|
+
throw_gui_error("xbintsc gui: could not open a window (see the diagnostic above)");
|
|
389
|
+
return XT_UNDEFINED;
|
|
390
|
+
}
|
|
391
|
+
if (has_x || has_y) SDL_SetWindowPosition(record->window, has_x ? x : SDL_WINDOWPOS_CENTERED,
|
|
392
|
+
has_y ? y : SDL_WINDOWPOS_CENTERED);
|
|
393
|
+
if (has_opacity) SDL_SetWindowOpacity(record->window, opacity);
|
|
394
|
+
if (maximized) SDL_MaximizeWindow(record->window);
|
|
395
|
+
if (minimized) SDL_MinimizeWindow(record->window);
|
|
396
|
+
return record->object;
|
|
308
397
|
}
|
|
309
398
|
|
|
310
399
|
/* -- run() / quit() ------------------------------------------------------- */
|
|
@@ -45,6 +45,9 @@ xt_value xt_browser_local_storage(int32_t argc, xt_value *argv);
|
|
|
45
45
|
xt_value xt_browser_missing(int32_t argc, xt_value *argv);
|
|
46
46
|
/** A DOM type name (`Image`, `HTMLCanvasElement`, …) as a constructor value. */
|
|
47
47
|
xt_value xt_browser_type(int32_t argc, xt_value *argv);
|
|
48
|
+
/** `createImageBitmap(source)`, exposed as a global *value*: returns the
|
|
49
|
+
* closure the compiler stores for the bare identifier. */
|
|
50
|
+
xt_value xt_browser_create_image_bitmap(int32_t argc, xt_value *argv);
|
|
48
51
|
xt_value xt_browser_request_animation_frame(int32_t argc, xt_value *argv);
|
|
49
52
|
xt_value xt_browser_cancel_animation_frame(int32_t argc, xt_value *argv);
|
|
50
53
|
xt_value xt_browser_match_media(int32_t argc, xt_value *argv);
|
|
@@ -52,6 +55,8 @@ xt_value xt_browser_computed_style(int32_t argc, xt_value *argv);
|
|
|
52
55
|
xt_value xt_browser_escape(int32_t argc, xt_value *argv);
|
|
53
56
|
xt_value xt_browser_alert(int32_t argc, xt_value *argv);
|
|
54
57
|
xt_value xt_browser_prompt(int32_t argc, xt_value *argv);
|
|
58
|
+
/** The bare `confirm(message)` global (the window method shares the engine). */
|
|
59
|
+
xt_value xt_browser_confirm_global(int32_t argc, xt_value *argv);
|
|
55
60
|
/** Install `innerWidth`, `setTimeout`, `navigator`, … on the window prototype. */
|
|
56
61
|
void xt_browser_install_window(xt_value proto);
|
|
57
62
|
/** Poll and dispatch pending SDL input events once (the browser frame pump in
|
|
@@ -63,11 +68,13 @@ void xt_browser_schedule_frame(void);
|
|
|
63
68
|
/** Whether `run()` currently owns the frame loop (the pump then stays idle). */
|
|
64
69
|
int xt_gui_run_active(void);
|
|
65
70
|
|
|
66
|
-
/* Web Audio objects (`audio.cpp`).
|
|
67
|
-
|
|
71
|
+
/* Web Audio objects (`audio.cpp`).
|
|
72
|
+
* `AudioContext.prototype` — also the `instanceof AudioContext` target. */
|
|
68
73
|
xt_value xt_gui_audio_proto(void);
|
|
69
|
-
/** `new AudioContext(...)`:
|
|
70
|
-
*
|
|
74
|
+
/** `new AudioContext(...)`: builds a real graph (mixer in `audio_mixer.cpp`)
|
|
75
|
+
* and decodes through `audio_decode.cpp` + the optional media backend. The
|
|
76
|
+
* device opens on the first `start()`/`resume()`; a headless host stays silent
|
|
77
|
+
* while the graph still renders. */
|
|
71
78
|
xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
|
|
72
79
|
|
|
73
80
|
#ifdef __cplusplus
|
|
@@ -92,6 +99,22 @@ struct XtGuiNodeListener {
|
|
|
92
99
|
xt_value fn;
|
|
93
100
|
bool once;
|
|
94
101
|
bool capture;
|
|
102
|
+
/** `{ passive: true }`: `preventDefault()` is ignored while it runs. */
|
|
103
|
+
bool passive;
|
|
104
|
+
/** `{ signal }`: the listener is skipped once the signal is aborted. */
|
|
105
|
+
xt_value signal;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** One window-level listener (`window.addEventListener`). Window delivery has
|
|
109
|
+
* no capture phase, but `capture` is still part of listener identity, as in the
|
|
110
|
+
* DOM; `once`, `passive` and `signal` behave like the element ones. */
|
|
111
|
+
struct XtGuiWindowListener {
|
|
112
|
+
std::string type;
|
|
113
|
+
xt_value fn;
|
|
114
|
+
bool once;
|
|
115
|
+
bool capture;
|
|
116
|
+
bool passive;
|
|
117
|
+
xt_value signal;
|
|
95
118
|
};
|
|
96
119
|
|
|
97
120
|
/** One pending `requestAnimationFrame` callback. */
|
|
@@ -137,6 +160,14 @@ struct XtGuiWindow {
|
|
|
137
160
|
float background[4];
|
|
138
161
|
int width;
|
|
139
162
|
int height;
|
|
163
|
+
/** Page scroll offset (CSS pixels); painting and hit testing subtract it. */
|
|
164
|
+
float scroll_x = 0;
|
|
165
|
+
float scroll_y = 0;
|
|
166
|
+
/** `document.hasFocus()` state, toggled by `window.focus()` / `blur()`. */
|
|
167
|
+
int focused = 0;
|
|
168
|
+
/** Live `matchMedia` lists for this window (one per query string). */
|
|
169
|
+
std::vector<xt_value> media_lists;
|
|
170
|
+
std::unordered_map<std::string, xt_value> media_lists_by_query;
|
|
140
171
|
|
|
141
172
|
/* -- DOM object model (element handles + events) ----------------------- */
|
|
142
173
|
/** `document` handle for this window (lazily created). */
|
|
@@ -151,6 +182,8 @@ struct XtGuiWindow {
|
|
|
151
182
|
std::unordered_map<const xtgui::Node *, xt_value> node_handles;
|
|
152
183
|
/** Element-level listeners, keyed by node. */
|
|
153
184
|
std::unordered_map<const xtgui::Node *, std::vector<XtGuiNodeListener>> node_listeners;
|
|
185
|
+
/** Window-level listeners registered through `window.addEventListener`. */
|
|
186
|
+
std::vector<XtGuiWindowListener> window_listeners;
|
|
154
187
|
/** Set by a DOM mutation; consumed by the frame loop before repainting. */
|
|
155
188
|
int struct_dirty = 0;
|
|
156
189
|
/** Callbacks queued for the next frame, plus the id counter. */
|
|
@@ -208,6 +241,9 @@ void xt_gui_run_scripts(XtGuiWindow *win);
|
|
|
208
241
|
void xt_gui_script_gc_scan(void);
|
|
209
242
|
/** Shared prototype carrying the window methods. */
|
|
210
243
|
xt_value xt_gui_window_proto(void);
|
|
244
|
+
/** Install `on` / `off` / `addEventListener` / `removeEventListener` (the
|
|
245
|
+
* listener options bag is parsed in `window_events.cpp`). */
|
|
246
|
+
void xt_gui_install_window_events(xt_value proto);
|
|
211
247
|
/** Release GPU claim + destroy the window and mark the record closed. */
|
|
212
248
|
void xt_gui_quit_window(XtGuiWindow *win);
|
|
213
249
|
/** Deliver `event` (no arguments) to handlers registered with `on`. */
|
|
@@ -221,6 +257,22 @@ void xt_gui_render_window(XtGuiWindow *win);
|
|
|
221
257
|
void xt_gui_run_animation_frames(XtGuiWindow *win, double timestamp_ms);
|
|
222
258
|
/** Current logical window size (CSS viewport), in pixels. */
|
|
223
259
|
void xt_gui_window_viewport(XtGuiWindow *win, float *width, float *height);
|
|
260
|
+
/** Scroll the page to `(x, y)` (clamped to the content bounds), repaint and
|
|
261
|
+
* notify live `matchMedia` lists. */
|
|
262
|
+
void xt_gui_scroll_to(XtGuiWindow *win, float x, float y);
|
|
263
|
+
/** The largest valid scroll offset for the current layout. */
|
|
264
|
+
void xt_gui_scroll_bounds(XtGuiWindow *win, float *max_x, float *max_y);
|
|
265
|
+
/** Re-evaluate every live `matchMedia` list (window resize / scroll). */
|
|
266
|
+
void xt_gui_media_lists_notify(XtGuiWindow *win);
|
|
267
|
+
/** `window.matchMedia(query)`: the cached live list for this window. */
|
|
268
|
+
xt_value xt_browser_media_list(xt_value window, const std::string &query);
|
|
269
|
+
/** `window.confirm` / `window.prompt` (native dialog only when
|
|
270
|
+
* `XT_GUI_DIALOGS=1`; otherwise the message goes to stderr and the documented
|
|
271
|
+
* default is returned). */
|
|
272
|
+
xt_value xt_browser_confirm(XtGuiWindow *win, int32_t argc, xt_value *argv);
|
|
273
|
+
xt_value xt_browser_prompt(XtGuiWindow *win, int32_t argc, xt_value *argv);
|
|
274
|
+
/** Install `scrollTo`/`scrollBy`/`scrollX`/`scrollY`/`focus`/`blur`. */
|
|
275
|
+
void xt_browser_install_window_navigation(xt_value proto);
|
|
224
276
|
/** CSS-like descriptor of the deepest element at (x, y), or `""` (test/debug). */
|
|
225
277
|
std::string xt_gui_hit_test(XtGuiWindow *win, float x, float y);
|
|
226
278
|
/** Synthesise a pointer event (`mousemove`/`mousedown`/`mouseup`/`click`). */
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "gui_internal.h"
|
|
7
|
+
#include "listener_options.h"
|
|
7
8
|
|
|
8
9
|
#include "paint.h"
|
|
9
10
|
|
|
11
|
+
#include <algorithm>
|
|
10
12
|
#include <stdio.h>
|
|
11
13
|
#include <string.h>
|
|
12
14
|
|
|
@@ -20,22 +22,39 @@ void xt_gui_emit(XtGuiWindow *win, const char *event) {
|
|
|
20
22
|
xt_gui_emit_payload(win, event, XT_UNDEFINED);
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
/** Drop one window listener (by identity: type + fn + capture). */
|
|
26
|
+
static void remove_window_listener(XtGuiWindow *win, const XtGuiWindowListener &listener) {
|
|
27
|
+
std::vector<XtGuiWindowListener> &listeners = win->window_listeners;
|
|
28
|
+
for (auto it = listeners.begin(); it != listeners.end(); ++it) {
|
|
29
|
+
if (it->type == listener.type && it->capture == listener.capture &&
|
|
30
|
+
xt_truthy(xt_seq(it->fn, listener.fn))) {
|
|
31
|
+
listeners.erase(it);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
void xt_gui_emit_payload(XtGuiWindow *win, const char *event, xt_value payload) {
|
|
24
38
|
if (win == NULL || !XT_IS_OBJECT(win->object)) return;
|
|
25
|
-
xt_value listeners = xt_object_get_cstr(win->object, "__xt_gui_events");
|
|
26
|
-
if (!XT_IS_OBJECT(listeners)) return;
|
|
27
|
-
xt_value array = xt_object_get(listeners, xt_string_from_cstr(event));
|
|
28
|
-
if (!XT_IS_ARRAY(array)) return;
|
|
29
39
|
xt_value args[1];
|
|
30
40
|
int32_t count = 0;
|
|
31
41
|
if (!XT_IS_UNDEFINED(payload)) {
|
|
32
42
|
args[0] = payload;
|
|
33
43
|
count = 1;
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
/* Snapshot the list: a handler may register or remove listeners while it
|
|
46
|
+
* runs, and the DOM runs the batch as it stood when dispatch started. */
|
|
47
|
+
std::vector<XtGuiWindowListener> snapshot = win->window_listeners;
|
|
48
|
+
for (const XtGuiWindowListener &listener : snapshot) {
|
|
49
|
+
if (listener.type != event) continue;
|
|
50
|
+
/* `{ signal }` removes the listener at abort time (checked here, so no
|
|
51
|
+
* callback plumbing is needed inside the signal). */
|
|
52
|
+
if (xt_gui_signal_aborted(listener.signal)) {
|
|
53
|
+
remove_window_listener(win, listener);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (listener.once) remove_window_listener(win, listener);
|
|
57
|
+
if (XT_IS_FUNCTION(listener.fn)) xt_call_with_this(listener.fn, win->object, count, args);
|
|
39
58
|
}
|
|
40
59
|
}
|
|
41
60
|
|
|
@@ -52,6 +71,53 @@ void xt_gui_flush_dom(XtGuiWindow *win) {
|
|
|
52
71
|
win->geometry.dirty = 1;
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
/* -- page scroll ---------------------------------------------------------- */
|
|
75
|
+
|
|
76
|
+
namespace {
|
|
77
|
+
|
|
78
|
+
void content_bounds(const xtgui::LayoutBox *box, float *max_x, float *max_y) {
|
|
79
|
+
if (box == nullptr) return;
|
|
80
|
+
float right = box->x + box->width;
|
|
81
|
+
float bottom = box->y + box->height;
|
|
82
|
+
if (right > *max_x) *max_x = right;
|
|
83
|
+
if (bottom > *max_y) *max_y = bottom;
|
|
84
|
+
for (const std::unique_ptr<xtgui::LayoutBox> &child : box->children) {
|
|
85
|
+
content_bounds(child.get(), max_x, max_y);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
} // namespace
|
|
90
|
+
|
|
91
|
+
void xt_gui_scroll_bounds(XtGuiWindow *win, float *max_x, float *max_y) {
|
|
92
|
+
float content_x = 0;
|
|
93
|
+
float content_y = 0;
|
|
94
|
+
if (win != nullptr && win->document != nullptr) {
|
|
95
|
+
xt_gui_flush_dom(win);
|
|
96
|
+
content_bounds(win->document->layout().root(), &content_x, &content_y);
|
|
97
|
+
}
|
|
98
|
+
float viewport_width = 0;
|
|
99
|
+
float viewport_height = 0;
|
|
100
|
+
if (win != nullptr) xt_gui_window_viewport(win, &viewport_width, &viewport_height);
|
|
101
|
+
*max_x = content_x > viewport_width ? content_x - viewport_width : 0.0f;
|
|
102
|
+
*max_y = content_y > viewport_height ? content_y - viewport_height : 0.0f;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
void xt_gui_scroll_to(XtGuiWindow *win, float x, float y) {
|
|
106
|
+
if (win == nullptr) return;
|
|
107
|
+
float max_x = 0;
|
|
108
|
+
float max_y = 0;
|
|
109
|
+
xt_gui_scroll_bounds(win, &max_x, &max_y);
|
|
110
|
+
if (x < 0.0f) x = 0.0f;
|
|
111
|
+
if (y < 0.0f) y = 0.0f;
|
|
112
|
+
if (x > max_x) x = max_x;
|
|
113
|
+
if (y > max_y) y = max_y;
|
|
114
|
+
if (x == win->scroll_x && y == win->scroll_y) return;
|
|
115
|
+
win->scroll_x = x;
|
|
116
|
+
win->scroll_y = y;
|
|
117
|
+
win->geometry.dirty = 1;
|
|
118
|
+
xt_gui_emit(win, "scroll");
|
|
119
|
+
}
|
|
120
|
+
|
|
55
121
|
static XtGuiWindow *xt_gui_find_by_window_id(SDL_WindowID id) {
|
|
56
122
|
SDL_Window *window = SDL_GetWindowFromID(id);
|
|
57
123
|
if (window == NULL) return NULL;
|
|
@@ -66,7 +132,8 @@ static XtGuiWindow *xt_gui_find_by_window_id(SDL_WindowID id) {
|
|
|
66
132
|
/** Deepest element under (x, y), or NULL. */
|
|
67
133
|
static const xtgui::Node *xt_gui_hit_node(XtGuiWindow *win, float x, float y) {
|
|
68
134
|
if (win->document == nullptr) return nullptr;
|
|
69
|
-
const xtgui::LayoutBox *box =
|
|
135
|
+
const xtgui::LayoutBox *box =
|
|
136
|
+
win->document->layout().hitTest(x + win->scroll_x, y + win->scroll_y);
|
|
70
137
|
const xtgui::Node *node = box != nullptr ? box->node : nullptr;
|
|
71
138
|
while (node != nullptr && !node->isElement()) node = node->parent;
|
|
72
139
|
return node;
|
|
@@ -122,6 +189,8 @@ void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, fl
|
|
|
122
189
|
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
123
190
|
if (node != nullptr) xt_gui_emit_dom_event(win, "wheel", node, payload);
|
|
124
191
|
xt_gui_emit_payload(win, "wheel", payload);
|
|
192
|
+
/* Default action: scroll the page (content that fits does not move). */
|
|
193
|
+
xt_gui_scroll_to(win, win->scroll_x + delta_x, win->scroll_y + delta_y);
|
|
125
194
|
}
|
|
126
195
|
|
|
127
196
|
static xt_value xt_gui_key_payload(const char *type, const char *key, const char *code, bool repeat,
|
|
@@ -168,6 +237,7 @@ void handle_event(const SDL_Event *event) {
|
|
|
168
237
|
/* Web 'resize', so a document can re-measure on a window change in every
|
|
169
238
|
* host (a browser already fires one). */
|
|
170
239
|
xt_gui_emit(win, "resize");
|
|
240
|
+
xt_gui_media_lists_notify(win);
|
|
171
241
|
return;
|
|
172
242
|
}
|
|
173
243
|
|
|
@@ -273,6 +343,7 @@ void xt_gui_render_window(XtGuiWindow *win) {
|
|
|
273
343
|
if (can_paint && win->geometry.dirty && win->document != nullptr) {
|
|
274
344
|
xtgui::DisplayList list;
|
|
275
345
|
xtgui::xt_paint_build(win->document->layout().root(), list, viewport_width, viewport_height);
|
|
346
|
+
xt_paint_translate(list, -win->scroll_x, -win->scroll_y);
|
|
276
347
|
xt_gui_geometry_upload(g_device, &win->geometry, list);
|
|
277
348
|
float density = SDL_GetWindowPixelDensity(win->window);
|
|
278
349
|
float text_scale = density > 0.0f ? density : 1.0f;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `ImageBitmap` pixel storage.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "image_bitmap.h"
|
|
6
|
+
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
|
|
9
|
+
namespace xtgui {
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
std::unordered_map<uint64_t, Image> g_bitmaps;
|
|
13
|
+
uint64_t g_next_bitmap = 1;
|
|
14
|
+
|
|
15
|
+
} // namespace
|
|
16
|
+
|
|
17
|
+
uint64_t xt_image_bitmap_add(const Image &image) {
|
|
18
|
+
const uint64_t id = g_next_bitmap++;
|
|
19
|
+
g_bitmaps[id] = image;
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const Image *xt_image_bitmap_get(uint64_t id) {
|
|
24
|
+
auto found = g_bitmaps.find(id);
|
|
25
|
+
return found == g_bitmaps.end() ? nullptr : &found->second;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} // namespace xtgui
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `ImageBitmap` storage.
|
|
3
|
+
*
|
|
4
|
+
* `createImageBitmap(source)` returns a settled promise of an immutable
|
|
5
|
+
* bitmap. The pixels stay in C++ so `texImage2D` can consume them without
|
|
6
|
+
* materialising a JavaScript array; the handle object carries the numeric id
|
|
7
|
+
* under `__xt_bitmap`.
|
|
8
|
+
*/
|
|
9
|
+
#ifndef XT_GUI_IMAGE_BITMAP_H
|
|
10
|
+
#define XT_GUI_IMAGE_BITMAP_H
|
|
11
|
+
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
|
|
14
|
+
#include "image.h"
|
|
15
|
+
|
|
16
|
+
namespace xtgui {
|
|
17
|
+
|
|
18
|
+
/** Store a decoded bitmap and return its process-wide id. */
|
|
19
|
+
uint64_t xt_image_bitmap_add(const Image &image);
|
|
20
|
+
|
|
21
|
+
/** Look up a bitmap; null when the id is unknown. */
|
|
22
|
+
const Image *xt_image_bitmap_get(uint64_t id);
|
|
23
|
+
|
|
24
|
+
} // namespace xtgui
|
|
25
|
+
|
|
26
|
+
#endif /* XT_GUI_IMAGE_BITMAP_H */
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `createImageBitmap`.
|
|
3
|
+
*
|
|
4
|
+
* The browser entry point returns a promise (as it does on the web), so code
|
|
5
|
+
* that writes `const bitmap = await createImageBitmap(canvas)` works unchanged.
|
|
6
|
+
* The pixels are kept in `image_bitmap.cpp`; `texImage2D` reads the same
|
|
7
|
+
* registry through the bitmap's `__xt_bitmap` id.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "gui_engine.h"
|
|
11
|
+
#include "dom_api_internal.h"
|
|
12
|
+
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <string>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
#include "image_bitmap.h"
|
|
18
|
+
#include "webgl.h"
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
void flip_rows(std::vector<unsigned char> *pixels, int width, int height) {
|
|
23
|
+
const size_t stride = (size_t)width * 4;
|
|
24
|
+
std::vector<unsigned char> row(stride);
|
|
25
|
+
for (int y = 0; y < height / 2; y++) {
|
|
26
|
+
unsigned char *top = pixels->data() + (size_t)y * stride;
|
|
27
|
+
unsigned char *bottom = pixels->data() + (size_t)(height - 1 - y) * stride;
|
|
28
|
+
memcpy(row.data(), top, stride);
|
|
29
|
+
memcpy(top, bottom, stride);
|
|
30
|
+
memcpy(bottom, row.data(), stride);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
bool source_pixels(xt_value source, xtgui::Image *out) {
|
|
35
|
+
/* ImageData / another bitmap-like object: width, height, data. */
|
|
36
|
+
xt_value width_value = xt_object_get_cstr(source, "width");
|
|
37
|
+
xt_value height_value = xt_object_get_cstr(source, "height");
|
|
38
|
+
xt_value data = xt_object_get_cstr(source, "data");
|
|
39
|
+
if (XT_IS_OBJECT(data) && XT_IS_NUMBER(width_value) && XT_IS_NUMBER(height_value)) {
|
|
40
|
+
const int width = (int)xt_to_number(width_value);
|
|
41
|
+
const int height = (int)xt_to_number(height_value);
|
|
42
|
+
const std::vector<uint8_t> bytes = domapi::webgl_bytes(data);
|
|
43
|
+
if (width <= 0 || height <= 0 || bytes.size() < (size_t)width * height * 4) return false;
|
|
44
|
+
out->width = width;
|
|
45
|
+
out->height = height;
|
|
46
|
+
out->pixels.assign(bytes.begin(), bytes.begin() + (size_t)width * height * 4);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
XtGuiWindow *win = nullptr;
|
|
51
|
+
xtgui::Node *node = domapi::resolve_node(source, &win);
|
|
52
|
+
if (node == nullptr || !node->isElement()) return false;
|
|
53
|
+
if (node->isTag("img")) {
|
|
54
|
+
const std::string *src = node->attr("src");
|
|
55
|
+
const xtgui::Image *image = src != nullptr ? xtgui::xt_image_load(*src) : nullptr;
|
|
56
|
+
if (image == nullptr) return false;
|
|
57
|
+
*out = *image;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (node->isTag("canvas")) {
|
|
61
|
+
if (xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node)) {
|
|
62
|
+
*out = canvas->image();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (xtgui::WebGLContext *context = xt_gui_webgl_find(win, node)) {
|
|
66
|
+
out->width = context->width();
|
|
67
|
+
out->height = context->height();
|
|
68
|
+
out->pixels.resize((size_t)out->width * out->height * 4);
|
|
69
|
+
if (!context->read_pixels(0, 0, out->width, out->height, out->pixels.data())) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
|
|
73
|
+
flip_rows(&out->pixels, out->width, out->height);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (node->isTag("video")) {
|
|
79
|
+
xtgui::MediaPlayer *player = xt_gui_media_player(win, node);
|
|
80
|
+
const xtgui::Image *frame = player != nullptr ? player->frame() : nullptr;
|
|
81
|
+
if (frame == nullptr || frame->width <= 0 || frame->height <= 0) return false;
|
|
82
|
+
*out = *frame;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
xt_value bitmap_object(const xtgui::Image &image) {
|
|
89
|
+
const uint64_t id = xtgui::xt_image_bitmap_add(image);
|
|
90
|
+
xt_value name = xt_string_from_cstr("ImageBitmap");
|
|
91
|
+
xt_value constructor = xt_browser_type(1, &name);
|
|
92
|
+
xt_value prototype = xt_object_get_cstr(constructor, "prototype");
|
|
93
|
+
xt_value bitmap = XT_IS_OBJECT(prototype) ? xt_object_new_with_proto(prototype)
|
|
94
|
+
: xt_object_new();
|
|
95
|
+
xt_object_set(bitmap, xt_string_from_cstr("width"), xt_number(image.width));
|
|
96
|
+
xt_object_set(bitmap, xt_string_from_cstr("height"), xt_number(image.height));
|
|
97
|
+
xt_object_set(bitmap, xt_string_from_cstr("__xt_bitmap"), xt_number((double)id));
|
|
98
|
+
return bitmap;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
} // namespace
|
|
102
|
+
|
|
103
|
+
namespace {
|
|
104
|
+
|
|
105
|
+
xt_value create_bitmap(int32_t argc, xt_value *argv) {
|
|
106
|
+
if (argc < 1) return xt_promise_reject(xt_string_from_cstr("TypeError"));
|
|
107
|
+
xtgui::Image image;
|
|
108
|
+
if (!source_pixels(xt_arg(argc, argv, 0), &image)) {
|
|
109
|
+
return xt_promise_reject(xt_string_from_cstr("InvalidStateError"));
|
|
110
|
+
}
|
|
111
|
+
return xt_promise_resolve(bitmap_object(image));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The closure ABI the runtime calls for a first-class function value. */
|
|
115
|
+
xt_value create_bitmap_call(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
116
|
+
(void)self;
|
|
117
|
+
(void)env;
|
|
118
|
+
return create_bitmap(argc, argv);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
xt_value g_create_bitmap = XT_UNDEFINED;
|
|
122
|
+
|
|
123
|
+
} // namespace
|
|
124
|
+
|
|
125
|
+
/* `createImageBitmap` is a *global value*: the compiler calls this getter once
|
|
126
|
+
* and stores the returned closure, which is then called like any function. */
|
|
127
|
+
xt_value xt_browser_create_image_bitmap(int32_t argc, xt_value *argv) {
|
|
128
|
+
(void)argc;
|
|
129
|
+
(void)argv;
|
|
130
|
+
if (!XT_IS_OBJECT(g_create_bitmap)) {
|
|
131
|
+
g_create_bitmap = xt_closure_new((void *)create_bitmap_call, 0, nullptr);
|
|
132
|
+
xt_gc_add_root(&g_create_bitmap);
|
|
133
|
+
}
|
|
134
|
+
return g_create_bitmap;
|
|
135
|
+
}
|
|
136
|
+
|