xbintsc 0.3.15 → 0.3.16

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.
Files changed (156) hide show
  1. package/dist/src/cli/main.js +4 -4
  2. package/dist/src/cli/main.js.map +1 -1
  3. package/dist/src/codegen/generator/state.js +8 -0
  4. package/dist/src/codegen/generator/state.js.map +1 -1
  5. package/dist/src/codegen/generator/tables.js +8 -0
  6. package/dist/src/codegen/generator/tables.js.map +1 -1
  7. package/dist/src/driver/bundler/graph.d.ts +8 -0
  8. package/dist/src/driver/bundler/graph.js +85 -0
  9. package/dist/src/driver/bundler/graph.js.map +1 -0
  10. package/dist/src/driver/bundler/merge.d.ts +11 -0
  11. package/dist/src/driver/bundler/merge.js +288 -0
  12. package/dist/src/driver/bundler/merge.js.map +1 -0
  13. package/dist/src/driver/bundler/resolve.d.ts +12 -0
  14. package/dist/src/driver/bundler/resolve.js +246 -0
  15. package/dist/src/driver/bundler/resolve.js.map +1 -0
  16. package/dist/src/driver/bundler/symbols.d.ts +15 -0
  17. package/dist/src/driver/bundler/symbols.js +51 -0
  18. package/dist/src/driver/bundler/symbols.js.map +1 -0
  19. package/dist/src/driver/bundler/types.d.ts +36 -0
  20. package/dist/src/driver/bundler/types.js +5 -0
  21. package/dist/src/driver/bundler/types.js.map +1 -0
  22. package/dist/src/driver/modules.d.ts +9 -16
  23. package/dist/src/driver/modules.js +8 -644
  24. package/dist/src/driver/modules.js.map +1 -1
  25. package/dist/src/extensions/catalog.js +2 -1
  26. package/dist/src/extensions/catalog.js.map +1 -1
  27. package/dist/src/extensions/gui/index.d.ts +24 -0
  28. package/dist/src/extensions/gui/index.js +101 -0
  29. package/dist/src/extensions/gui/index.js.map +1 -0
  30. package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
  31. package/dist/tests/e2e/compile-basics.test.js +134 -0
  32. package/dist/tests/e2e/compile-basics.test.js.map +1 -0
  33. package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
  34. package/dist/tests/e2e/compile-builtins.test.js +191 -0
  35. package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
  36. package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
  37. package/dist/tests/e2e/compile-control-flow.test.js +190 -0
  38. package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
  39. package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
  40. package/dist/tests/e2e/compile-modules.test.js +48 -0
  41. package/dist/tests/e2e/compile-modules.test.js.map +1 -0
  42. package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
  43. package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
  44. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
  45. package/dist/tests/e2e/differential-helpers.d.ts +12 -0
  46. package/dist/tests/e2e/differential-helpers.js +16 -0
  47. package/dist/tests/e2e/differential-helpers.js.map +1 -0
  48. package/dist/tests/e2e/differential-language.test.d.ts +4 -0
  49. package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
  50. package/dist/tests/e2e/differential-language.test.js.map +1 -0
  51. package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
  52. package/dist/tests/e2e/differential-symbols.test.js +92 -0
  53. package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
  54. package/dist/tests/e2e/differential-values.test.d.ts +4 -0
  55. package/dist/tests/e2e/differential-values.test.js +197 -0
  56. package/dist/tests/e2e/differential-values.test.js.map +1 -0
  57. package/dist/tests/e2e/gui.test.d.ts +12 -0
  58. package/dist/tests/e2e/gui.test.js +493 -0
  59. package/dist/tests/e2e/gui.test.js.map +1 -0
  60. package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
  61. package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
  62. package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
  63. package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
  64. package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
  65. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
  66. package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
  67. package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
  68. package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
  69. package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
  70. package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
  71. package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
  72. package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
  73. package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
  74. package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
  75. package/dist/tests/e2e/timers.test.d.ts +7 -0
  76. package/dist/tests/e2e/timers.test.js +63 -0
  77. package/dist/tests/e2e/timers.test.js.map +1 -0
  78. package/package.json +2 -1
  79. package/runtime/ext_gui/css.cpp +743 -0
  80. package/runtime/ext_gui/css.h +98 -0
  81. package/runtime/ext_gui/document.cpp +264 -0
  82. package/runtime/ext_gui/document.h +89 -0
  83. package/runtime/ext_gui/dom.cpp +472 -0
  84. package/runtime/ext_gui/dom.h +71 -0
  85. package/runtime/ext_gui/gui.cpp +457 -0
  86. package/runtime/ext_gui/gui_engine.h +81 -0
  87. package/runtime/ext_gui/image.cpp +125 -0
  88. package/runtime/ext_gui/image.h +42 -0
  89. package/runtime/ext_gui/layout.cpp +748 -0
  90. package/runtime/ext_gui/layout.h +93 -0
  91. package/runtime/ext_gui/paint.cpp +137 -0
  92. package/runtime/ext_gui/paint.h +75 -0
  93. package/runtime/ext_gui/renderer.cpp +959 -0
  94. package/runtime/ext_gui/renderer.h +68 -0
  95. package/runtime/ext_gui/stb_image.h +7988 -0
  96. package/runtime/ext_gui/style.cpp +771 -0
  97. package/runtime/ext_gui/style.h +117 -0
  98. package/runtime/ext_gui/text.cpp +378 -0
  99. package/runtime/ext_gui/text.h +78 -0
  100. package/runtime/ext_gui/window.cpp +405 -0
  101. package/runtime/ext_node/fs/fs_common.h +13 -527
  102. package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
  103. package/runtime/ext_node/fs/parts/builtins.inc +69 -0
  104. package/runtime/ext_node/fs/parts/dir.inc +47 -0
  105. package/runtime/ext_node/fs/parts/encoding.inc +186 -0
  106. package/runtime/ext_node/fs/parts/errors.inc +66 -0
  107. package/runtime/ext_node/fs/parts/flags.inc +39 -0
  108. package/runtime/ext_node/fs/parts/platform.inc +86 -0
  109. package/runtime/ext_node/fs/parts/promisify.inc +15 -0
  110. package/runtime/ext_node/fs/parts/time.inc +9 -0
  111. package/runtime/rt.h +33 -0
  112. package/runtime/xt_builtins.c +4 -0
  113. package/runtime/xt_containers/access.inc +168 -0
  114. package/runtime/xt_containers/arrays.inc +108 -0
  115. package/runtime/xt_containers/boxes.inc +11 -0
  116. package/runtime/xt_containers/objects.inc +326 -0
  117. package/runtime/xt_containers.c +8 -618
  118. package/runtime/xt_io/base64.inc +87 -0
  119. package/runtime/xt_io/console.inc +119 -0
  120. package/runtime/xt_io/exception.inc +142 -0
  121. package/runtime/xt_io/inspect.inc +212 -0
  122. package/runtime/xt_io.c +8 -553
  123. package/runtime/xt_loop.c +262 -24
  124. package/runtime/xt_regex/api.inc +58 -0
  125. package/runtime/xt_regex/helpers.inc +68 -0
  126. package/runtime/xt_regex/matcher.inc +248 -0
  127. package/runtime/xt_regex/parser.inc +236 -0
  128. package/runtime/xt_regex/types.inc +58 -0
  129. package/runtime/xt_regex.h +9 -672
  130. package/runtime/xt_values/arithmetic.inc +150 -0
  131. package/runtime/xt_values/comparison.inc +112 -0
  132. package/runtime/xt_values/constructors.inc +41 -0
  133. package/runtime/xt_values/conversions.inc +115 -0
  134. package/runtime/xt_values/number-format.inc +180 -0
  135. package/runtime/xt_values/strings.inc +22 -0
  136. package/runtime/xt_values.c +10 -626
  137. package/scripts/build-gui.ts +317 -0
  138. package/src/cli/main.ts +4 -3
  139. package/src/codegen/generator/state.ts +8 -0
  140. package/src/codegen/generator/tables.ts +8 -0
  141. package/src/driver/bundler/graph.ts +95 -0
  142. package/src/driver/bundler/merge.ts +312 -0
  143. package/src/driver/bundler/resolve.ts +240 -0
  144. package/src/driver/bundler/symbols.ts +55 -0
  145. package/src/driver/bundler/types.ts +37 -0
  146. package/src/driver/modules.ts +9 -693
  147. package/src/extensions/catalog.ts +2 -1
  148. package/src/extensions/gui/index.ts +107 -0
  149. package/dist/tests/e2e/compile.test.d.ts +0 -8
  150. package/dist/tests/e2e/compile.test.js +0 -893
  151. package/dist/tests/e2e/compile.test.js.map +0 -1
  152. package/dist/tests/e2e/differential.test.d.ts +0 -7
  153. package/dist/tests/e2e/differential.test.js.map +0 -1
  154. package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
  155. package/dist/tests/e2e/runtime-coverage.test.js +0 -646
  156. package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
@@ -0,0 +1,457 @@
1
+ /*
2
+ * xbintsc GUI engine — process-level state and the main loop.
3
+ *
4
+ * One SDL_GPU device is shared by every window. `xt_gui_run` owns the UI
5
+ * thread: each frame it pumps SDL events, renders every open window, then
6
+ * services the xbintsc reactor (`xt_loop_poll(0)`) and promise microtasks so
7
+ * sockets, timers and `await` keep working inside a GUI program.
8
+ *
9
+ * The engine is deliberately free of any compiler knowledge: it only uses the
10
+ * public runtime ABI (`rt.h`).
11
+ */
12
+
13
+ #include "gui_engine.h"
14
+ #include "paint.h"
15
+
16
+ #include <stdio.h>
17
+ #include <string.h>
18
+
19
+ #define XT_GUI_MAX_WINDOWS 64
20
+
21
+ static SDL_GPUDevice *g_device = NULL;
22
+ static int g_sdl_initialized = 0;
23
+ static int g_quit = 0;
24
+ static XtGuiWindow g_windows[XT_GUI_MAX_WINDOWS];
25
+ static int g_window_count = 0;
26
+
27
+ /* -- initialisation ------------------------------------------------------- */
28
+
29
+ static int xt_gui_ensure_init(void) {
30
+ if (g_sdl_initialized) return 1;
31
+ if (!SDL_Init(SDL_INIT_VIDEO)) {
32
+ fprintf(stderr, "xt_gui: SDL_Init failed: %s\n", SDL_GetError());
33
+ return 0;
34
+ }
35
+ /* Accept every shader format so the same code runs on Metal, Vulkan and
36
+ * D3D12; the backend only needs the one it supports. */
37
+ SDL_GPUShaderFormat formats =
38
+ SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL;
39
+ g_device = SDL_CreateGPUDevice(formats, false, NULL);
40
+ if (g_device == NULL) {
41
+ fprintf(stderr, "xt_gui: SDL_CreateGPUDevice failed: %s\n", SDL_GetError());
42
+ SDL_Quit();
43
+ return 0;
44
+ }
45
+ g_sdl_initialized = 1;
46
+ return 1;
47
+ }
48
+
49
+ /* -- window lookup -------------------------------------------------------- */
50
+
51
+ static XtGuiWindow *xt_gui_window_by_object(xt_value object) {
52
+ xt_value index_value = xt_object_get_cstr(object, "__xt_gui_index");
53
+ if (!XT_IS_NUMBER(index_value)) return NULL;
54
+ int index = (int)xt_to_number(index_value);
55
+ if (index < 0 || index >= g_window_count) return NULL;
56
+ return &g_windows[index];
57
+ }
58
+
59
+ XtGuiWindow *xt_gui_window_from_this(xt_value thisValue) {
60
+ return xt_gui_window_by_object(thisValue);
61
+ }
62
+
63
+ /* -- events --------------------------------------------------------------- */
64
+
65
+ void xt_gui_emit(XtGuiWindow *win, const char *event) {
66
+ xt_gui_emit_payload(win, event, XT_UNDEFINED);
67
+ }
68
+
69
+ void xt_gui_emit_payload(XtGuiWindow *win, const char *event, xt_value payload) {
70
+ if (win == NULL || !XT_IS_OBJECT(win->object)) return;
71
+ xt_value listeners = xt_object_get_cstr(win->object, "__xt_gui_events");
72
+ if (!XT_IS_OBJECT(listeners)) return;
73
+ xt_value array = xt_object_get(listeners, xt_string_from_cstr(event));
74
+ if (!XT_IS_ARRAY(array)) return;
75
+ xt_value args[1];
76
+ int32_t count = 0;
77
+ if (!XT_IS_UNDEFINED(payload)) {
78
+ args[0] = payload;
79
+ count = 1;
80
+ }
81
+ int32_t listener_count = (int32_t)xt_to_number(xt_array_length(array));
82
+ for (int32_t i = 0; i < listener_count; i++) {
83
+ xt_value fn = xt_array_get(array, xt_number((double)i));
84
+ if (XT_IS_FUNCTION(fn)) xt_call_with_this(fn, win->object, count, args);
85
+ }
86
+ }
87
+
88
+ void xt_gui_quit_window(XtGuiWindow *win) {
89
+ if (win == NULL || !win->open) return;
90
+ win->open = 0;
91
+ xt_gui_emit(win, "close");
92
+ win->document.reset();
93
+ win->html.clear();
94
+ if (g_device != NULL) {
95
+ xt_gui_geometry_destroy(g_device, &win->geometry);
96
+ xt_gui_geometry_destroy(g_device, &win->text_geometry);
97
+ xt_gui_geometry_destroy(g_device, &win->image_geometry);
98
+ }
99
+ if (win->window != NULL && g_device != NULL) {
100
+ SDL_ReleaseWindowFromGPUDevice(g_device, win->window);
101
+ }
102
+ if (win->window != NULL) SDL_DestroyWindow(win->window);
103
+ win->window = NULL;
104
+ }
105
+
106
+ void xt_gui_window_viewport(XtGuiWindow *win, float *width, float *height) {
107
+ int w = win->width;
108
+ int h = win->height;
109
+ if (win->window != NULL) SDL_GetWindowSize(win->window, &w, &h);
110
+ if (width != NULL) *width = (float)w;
111
+ if (height != NULL) *height = (float)h;
112
+ }
113
+
114
+ static XtGuiWindow *xt_gui_find_by_window_id(SDL_WindowID id) {
115
+ SDL_Window *window = SDL_GetWindowFromID(id);
116
+ if (window == NULL) return NULL;
117
+ for (int i = 0; i < g_window_count; i++) {
118
+ if (g_windows[i].window == window) return &g_windows[i];
119
+ }
120
+ return NULL;
121
+ }
122
+
123
+ /* -- input events --------------------------------------------------------- */
124
+
125
+ /** Deepest element under (x, y), or NULL. */
126
+ static const xtgui::Node *xt_gui_hit_node(XtGuiWindow *win, float x, float y) {
127
+ if (win->document == nullptr) return nullptr;
128
+ const xtgui::LayoutBox *box = win->document->layout().hitTest(x, y);
129
+ const xtgui::Node *node = box != nullptr ? box->node : nullptr;
130
+ while (node != nullptr && !node->isElement()) node = node->parent;
131
+ return node;
132
+ }
133
+
134
+ /** Deepest element under (x, y) as a CSS-like descriptor, or an empty string. */
135
+ std::string xt_gui_hit_test(XtGuiWindow *win, float x, float y) {
136
+ const xtgui::Node *node = xt_gui_hit_node(win, x, y);
137
+ return node == nullptr ? std::string() : xtgui::xt_dom_describe(node);
138
+ }
139
+
140
+ /** Update the hovered element from the pointer position; marks paint dirty. */
141
+ static void xt_gui_update_hover(XtGuiWindow *win, float x, float y) {
142
+ if (win->document == nullptr) return;
143
+ if (win->document->setHover(xt_gui_hit_node(win, x, y))) win->geometry.dirty = 1;
144
+ }
145
+
146
+ static xt_value xt_gui_pointer_payload(const char *type, XtGuiWindow *win, float x, float y,
147
+ int button, int clicks) {
148
+ xt_value payload = xt_object_new();
149
+ xt_object_set(payload, xt_string_from_cstr("type"), xt_string_from_cstr(type));
150
+ xt_object_set(payload, xt_string_from_cstr("x"), xt_number(x));
151
+ xt_object_set(payload, xt_string_from_cstr("y"), xt_number(y));
152
+ xt_object_set(payload, xt_string_from_cstr("clientX"), xt_number(x));
153
+ xt_object_set(payload, xt_string_from_cstr("clientY"), xt_number(y));
154
+ xt_object_set(payload, xt_string_from_cstr("button"), xt_number(button));
155
+ xt_object_set(payload, xt_string_from_cstr("clicks"), xt_number(clicks));
156
+ std::string target = xt_gui_hit_test(win, x, y);
157
+ if (!target.empty()) {
158
+ xt_object_set(payload, xt_string_from_cstr("target"), xt_string_from_cstr(target.c_str()));
159
+ }
160
+ return payload;
161
+ }
162
+
163
+ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float y, int button,
164
+ int clicks) {
165
+ if (win == NULL || !win->open) return;
166
+ xt_gui_update_hover(win, x, y);
167
+ if (strcmp(type, "mousedown") == 0 && win->document != nullptr) {
168
+ if (win->document->setFocus(xt_gui_hit_node(win, x, y))) win->geometry.dirty = 1;
169
+ }
170
+ xt_gui_emit_payload(win, type, xt_gui_pointer_payload(type, win, x, y, button, clicks));
171
+ }
172
+
173
+ void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, float delta_y) {
174
+ if (win == NULL || !win->open) return;
175
+ xt_value payload = xt_gui_pointer_payload("wheel", win, x, y, -1, 0);
176
+ xt_object_set(payload, xt_string_from_cstr("deltaX"), xt_number(delta_x));
177
+ xt_object_set(payload, xt_string_from_cstr("deltaY"), xt_number(delta_y));
178
+ xt_gui_emit_payload(win, "wheel", payload);
179
+ }
180
+
181
+ static xt_value xt_gui_key_payload(const char *type, const char *key, const char *code, bool repeat,
182
+ bool ctrl, bool shift, bool alt, bool meta) {
183
+ xt_value payload = xt_object_new();
184
+ xt_object_set(payload, xt_string_from_cstr("type"), xt_string_from_cstr(type));
185
+ xt_object_set(payload, xt_string_from_cstr("key"), xt_string_from_cstr(key));
186
+ xt_object_set(payload, xt_string_from_cstr("code"), xt_string_from_cstr(code));
187
+ xt_object_set(payload, xt_string_from_cstr("repeat"), repeat ? XT_TRUE : XT_FALSE);
188
+ xt_object_set(payload, xt_string_from_cstr("ctrl"), ctrl ? XT_TRUE : XT_FALSE);
189
+ xt_object_set(payload, xt_string_from_cstr("shift"), shift ? XT_TRUE : XT_FALSE);
190
+ xt_object_set(payload, xt_string_from_cstr("alt"), alt ? XT_TRUE : XT_FALSE);
191
+ xt_object_set(payload, xt_string_from_cstr("meta"), meta ? XT_TRUE : XT_FALSE);
192
+ return payload;
193
+ }
194
+
195
+ void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code) {
196
+ if (win == NULL || !win->open) return;
197
+ xt_gui_emit_payload(win, type, xt_gui_key_payload(type, key, code, false, false, false, false, false));
198
+ }
199
+
200
+ static void xt_gui_handle_event(const SDL_Event *event) {
201
+ if (event->type == SDL_EVENT_QUIT) {
202
+ g_quit = 1;
203
+ return;
204
+ }
205
+ if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
206
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->window.windowID);
207
+ if (win != NULL && win->open) xt_gui_quit_window(win);
208
+ return;
209
+ }
210
+ if (event->type == SDL_EVENT_WINDOW_RESIZED) {
211
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->window.windowID);
212
+ if (win == NULL || !win->open) return;
213
+ win->width = event->window.data1;
214
+ win->height = event->window.data2;
215
+ if (win->document != NULL) win->document->restyle((float)win->width, (float)win->height);
216
+ win->geometry.dirty = 1;
217
+ return;
218
+ }
219
+
220
+ if (event->type == SDL_EVENT_MOUSE_MOTION) {
221
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->motion.windowID);
222
+ xt_gui_dispatch_pointer(win, "mousemove", event->motion.x, event->motion.y, -1, 0);
223
+ return;
224
+ }
225
+ if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
226
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->button.windowID);
227
+ /* SDL numbers buttons 1/2/3 (left/middle/right); DOM uses 0/1/2. */
228
+ xt_gui_dispatch_pointer(win, "mousedown", event->button.x, event->button.y,
229
+ (int)event->button.button - 1, event->button.clicks);
230
+ return;
231
+ }
232
+ if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
233
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->button.windowID);
234
+ if (win == NULL || !win->open) return;
235
+ int button = (int)event->button.button - 1;
236
+ xt_gui_dispatch_pointer(win, "mouseup", event->button.x, event->button.y, button,
237
+ event->button.clicks);
238
+ if (button == 0) {
239
+ xt_gui_dispatch_pointer(win, "click", event->button.x, event->button.y, button,
240
+ event->button.clicks);
241
+ }
242
+ return;
243
+ }
244
+ if (event->type == SDL_EVENT_MOUSE_WHEEL) {
245
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->wheel.windowID);
246
+ /* DOM `deltaY` grows downwards; SDL's grows away from the user. */
247
+ xt_gui_dispatch_wheel(win, event->wheel.mouse_x, event->wheel.mouse_y, event->wheel.x,
248
+ -event->wheel.y);
249
+ return;
250
+ }
251
+ if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) {
252
+ XtGuiWindow *win = xt_gui_find_by_window_id(event->key.windowID);
253
+ if (win == NULL || !win->open) return;
254
+ const char *type = event->type == SDL_EVENT_KEY_DOWN ? "keydown" : "keyup";
255
+ xt_gui_emit_payload(win, type,
256
+ xt_gui_key_payload(type, SDL_GetKeyName(event->key.key),
257
+ SDL_GetScancodeName(event->key.scancode),
258
+ event->key.repeat, (event->key.mod & SDL_KMOD_CTRL) != 0,
259
+ (event->key.mod & SDL_KMOD_SHIFT) != 0,
260
+ (event->key.mod & SDL_KMOD_ALT) != 0,
261
+ (event->key.mod & SDL_KMOD_GUI) != 0));
262
+ return;
263
+ }
264
+ }
265
+
266
+ /* -- rendering ------------------------------------------------------------ */
267
+
268
+ void xt_gui_render_window(XtGuiWindow *win) {
269
+ if (win == NULL || !win->open || win->window == NULL || g_device == NULL) return;
270
+ float viewport_width = 0;
271
+ float viewport_height = 0;
272
+ xt_gui_window_viewport(win, &viewport_width, &viewport_height);
273
+
274
+ /* Drive CSS transitions. */
275
+ double now = (double)SDL_GetTicks();
276
+ double delta = win->last_frame_ms > 0.0 ? now - win->last_frame_ms : 0.0;
277
+ win->last_frame_ms = now;
278
+ if (win->document != nullptr && win->document->advance(delta)) win->geometry.dirty = 1;
279
+
280
+ /* Rebuild the geometry only when the document or viewport changed. */
281
+ bool can_paint = xt_gui_renderer_ensure(g_device, win->window);
282
+ if (can_paint && win->geometry.dirty && win->document != nullptr) {
283
+ xtgui::DisplayList list;
284
+ xtgui::xt_paint_build(win->document->layout().root(), list);
285
+ xt_gui_geometry_upload(g_device, &win->geometry, list);
286
+ float density = SDL_GetWindowPixelDensity(win->window);
287
+ xt_gui_text_upload(g_device, &win->text_geometry, list, density > 0.0f ? density : 1.0f);
288
+ xt_gui_image_upload(g_device, &win->image_geometry, list);
289
+ win->geometry.dirty = 0;
290
+ }
291
+
292
+ SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(g_device);
293
+ if (cmd == NULL) return;
294
+ SDL_GPUTexture *swapchain = NULL;
295
+ SDL_AcquireGPUSwapchainTexture(cmd, win->window, &swapchain, NULL, NULL);
296
+ bool firstFrame = false;
297
+ if (swapchain != NULL) {
298
+ SDL_GPUColorTargetInfo target;
299
+ memset(&target, 0, sizeof(target));
300
+ target.texture = swapchain;
301
+ target.clear_color.r = win->background[0];
302
+ target.clear_color.g = win->background[1];
303
+ target.clear_color.b = win->background[2];
304
+ target.clear_color.a = win->background[3];
305
+ target.load_op = SDL_GPU_LOADOP_CLEAR;
306
+ target.store_op = SDL_GPU_STOREOP_STORE;
307
+ SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, NULL);
308
+ if (can_paint) {
309
+ xt_gui_renderer_draw(cmd, pass, &win->geometry, viewport_width, viewport_height);
310
+ xt_gui_text_draw(cmd, pass, &win->text_geometry, viewport_width, viewport_height);
311
+ xt_gui_image_draw(cmd, pass, &win->image_geometry, viewport_width, viewport_height);
312
+ }
313
+ SDL_EndGPURenderPass(pass);
314
+ firstFrame = !win->ready;
315
+ win->ready = 1;
316
+ }
317
+ SDL_SubmitGPUCommandBuffer(cmd);
318
+ /* The first presented frame marks the window ready. Emitting after the
319
+ * submit lets a `ready` handler safely close/destroy the window. */
320
+ if (firstFrame) xt_gui_emit(win, "ready");
321
+ }
322
+
323
+ /* -- createWindow(options) ------------------------------------------------ */
324
+
325
+ static const char *xt_gui_string_or(xt_value value, const char *fallback) {
326
+ if (!XT_IS_STRING(value)) return fallback;
327
+ return xt_string_data(value);
328
+ }
329
+
330
+ static int xt_gui_int_or(xt_value value, int fallback) {
331
+ if (!XT_IS_NUMBER(value)) return fallback;
332
+ return (int)xt_to_number(value);
333
+ }
334
+
335
+ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
336
+ if (!xt_gui_ensure_init()) return XT_UNDEFINED;
337
+ if (g_window_count >= XT_GUI_MAX_WINDOWS) {
338
+ fprintf(stderr, "xt_gui: too many windows (max %d)\n", XT_GUI_MAX_WINDOWS);
339
+ return XT_UNDEFINED;
340
+ }
341
+
342
+ const char *title = "xbintsc gui";
343
+ int width = 800;
344
+ int height = 600;
345
+ xt_value options = xt_arg(argc, argv, 0);
346
+ if (XT_IS_OBJECT(options)) {
347
+ title = xt_gui_string_or(xt_object_get_cstr(options, "title"), title);
348
+ width = xt_gui_int_or(xt_object_get_cstr(options, "width"), width);
349
+ height = xt_gui_int_or(xt_object_get_cstr(options, "height"), height);
350
+ } else if (XT_IS_STRING(options)) {
351
+ title = xt_string_data(options);
352
+ width = xt_gui_int_or(xt_arg(argc, argv, 1), width);
353
+ height = xt_gui_int_or(xt_arg(argc, argv, 2), height);
354
+ }
355
+
356
+ SDL_Window *window = SDL_CreateWindow(
357
+ title, width, height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
358
+ if (window == NULL) {
359
+ fprintf(stderr, "xt_gui: SDL_CreateWindow failed: %s\n", SDL_GetError());
360
+ return XT_UNDEFINED;
361
+ }
362
+ if (!SDL_ClaimWindowForGPUDevice(g_device, window)) {
363
+ fprintf(stderr, "xt_gui: SDL_ClaimWindowForGPUDevice failed: %s\n", SDL_GetError());
364
+ SDL_DestroyWindow(window);
365
+ return XT_UNDEFINED;
366
+ }
367
+
368
+ int index = g_window_count++;
369
+ XtGuiWindow *record = &g_windows[index];
370
+ record->window = window;
371
+ record->open = 1;
372
+ record->ready = 0;
373
+ record->width = width;
374
+ record->height = height;
375
+ record->html.clear();
376
+ record->document.reset();
377
+ record->geometry = XtGuiGeometry();
378
+ record->text_geometry = XtGuiGeometry();
379
+ record->image_geometry = XtGuiGeometry();
380
+ record->last_frame_ms = 0.0;
381
+ record->background[0] = 0.08f;
382
+ record->background[1] = 0.09f;
383
+ record->background[2] = 0.11f;
384
+ record->background[3] = 1.0f;
385
+
386
+ xt_value object = xt_object_new_with_proto(xt_gui_window_proto());
387
+ record->object = object;
388
+ xt_object_set(object, xt_string_from_cstr("__xt_gui_index"), xt_number((double)index));
389
+ xt_object_set(object, xt_string_from_cstr("__xt_gui_html"), xt_string_from_cstr(""));
390
+ xt_object_set(object, xt_string_from_cstr("__xt_gui_events"), xt_object_new());
391
+
392
+ return object;
393
+ }
394
+
395
+ /* -- run() / quit() ------------------------------------------------------- */
396
+
397
+ extern "C" xt_value xt_gui_run(int32_t argc, xt_value *argv) {
398
+ (void)argc;
399
+ (void)argv;
400
+ /* Test/CI hook: exit after N ms even if windows are open. */
401
+ Uint64 deadline = 0;
402
+ const char *autoclose = SDL_getenv("XT_GUI_AUTOCLOSE_MS");
403
+ if (autoclose != NULL && autoclose[0] != '\0') {
404
+ deadline = SDL_GetTicks() + (Uint64)SDL_atoi(autoclose);
405
+ }
406
+ while (!g_quit) {
407
+ SDL_Event event;
408
+ int had_event = 0;
409
+ while (SDL_PollEvent(&event)) {
410
+ had_event = 1;
411
+ xt_gui_handle_event(&event);
412
+ }
413
+
414
+ int open = 0;
415
+ for (int i = 0; i < g_window_count; i++) {
416
+ if (!g_windows[i].open) continue;
417
+ open++;
418
+ xt_gui_render_window(&g_windows[i]);
419
+ }
420
+ if (open == 0) break;
421
+
422
+ /* Let sockets, timers and promise continuations make progress between
423
+ * frames. `xt_loop_poll(0)` is non-blocking. */
424
+ xt_loop_poll(0);
425
+ xt_drain_microtasks();
426
+
427
+ if (deadline != 0 && SDL_GetTicks() >= deadline) break;
428
+ if (!had_event) SDL_Delay(1);
429
+ }
430
+
431
+ SDL_GPUDevice *device = g_device;
432
+ if (device != NULL) {
433
+ SDL_WaitForGPUIdle(device);
434
+ for (int i = 0; i < g_window_count; i++) {
435
+ if (g_windows[i].open) xt_gui_quit_window(&g_windows[i]);
436
+ }
437
+ xt_gui_renderer_destroy(device);
438
+ SDL_DestroyGPUDevice(device);
439
+ g_device = NULL;
440
+ }
441
+ if (g_sdl_initialized) {
442
+ SDL_Quit();
443
+ g_sdl_initialized = 0;
444
+ }
445
+ g_window_count = 0;
446
+ return XT_UNDEFINED;
447
+ }
448
+
449
+ extern "C" xt_value xt_gui_quit(int32_t argc, xt_value *argv) {
450
+ (void)argc;
451
+ (void)argv;
452
+ g_quit = 1;
453
+ for (int i = 0; i < g_window_count; i++) {
454
+ if (g_windows[i].open) xt_gui_quit_window(&g_windows[i]);
455
+ }
456
+ return XT_UNDEFINED;
457
+ }
@@ -0,0 +1,81 @@
1
+ /*
2
+ * xbintsc GUI engine — internal header.
3
+ *
4
+ * The engine owns one SDL3 window + SDL_GPU device per `createWindow(...)`
5
+ * call. Windows are exposed to TypeScript as runtime objects whose methods live
6
+ * on a shared prototype (see `window.cpp`). The three `xt_gui_*` entry points
7
+ * below use the uniform `(argc, argv)` ABI the extension bindings expect.
8
+ *
9
+ * SDL3 and its dependencies are linked into `gui.a`; only the SDL3 headers
10
+ * (`SDL3/SDL.h`) are needed to build these translation units.
11
+ */
12
+ #ifndef XT_GUI_ENGINE_H
13
+ #define XT_GUI_ENGINE_H
14
+
15
+ #include "rt.h"
16
+
17
+ #ifdef __cplusplus
18
+ extern "C" {
19
+ #endif
20
+
21
+ /* Uniform `(argc, argv)` entry points bound by `src/extensions/gui`. */
22
+ xt_value xt_gui_create_window(int32_t argc, xt_value *argv);
23
+ xt_value xt_gui_run(int32_t argc, xt_value *argv);
24
+ xt_value xt_gui_quit(int32_t argc, xt_value *argv);
25
+
26
+ #ifdef __cplusplus
27
+ } /* extern "C" */
28
+
29
+ #include <SDL3/SDL.h>
30
+
31
+ #include <memory>
32
+ #include <string>
33
+
34
+ #include "document.h"
35
+ #include "renderer.h"
36
+
37
+ /* One live window. `object` is the JavaScript-visible handle; the record is
38
+ * addressed from it through the hidden `__xt_gui_index` property. */
39
+ struct XtGuiWindow {
40
+ SDL_Window *window;
41
+ xt_value object;
42
+ int open;
43
+ double last_frame_ms;
44
+ int ready;
45
+ std::string html;
46
+ std::unique_ptr<xtgui::XtDocument> document;
47
+ XtGuiGeometry geometry;
48
+ XtGuiGeometry text_geometry;
49
+ XtGuiGeometry image_geometry;
50
+ float background[4];
51
+ int width;
52
+ int height;
53
+ };
54
+
55
+ /** Resolve a window handle (`this`) to its record, or NULL for a foreign value. */
56
+ XtGuiWindow *xt_gui_window_from_this(xt_value thisValue);
57
+ /** Shared prototype carrying the window methods. */
58
+ xt_value xt_gui_window_proto(void);
59
+ /** Release GPU claim + destroy the window and mark the record closed. */
60
+ void xt_gui_quit_window(XtGuiWindow *win);
61
+ /** Deliver `event` (no arguments) to handlers registered with `on`. */
62
+ void xt_gui_emit(XtGuiWindow *win, const char *event);
63
+ /** Deliver `event` with a single `payload` argument (input events). */
64
+ void xt_gui_emit_payload(XtGuiWindow *win, const char *event, xt_value payload);
65
+ /** Acquire a swapchain frame for `win` and clear it (placeholder paint). */
66
+ void xt_gui_render_window(XtGuiWindow *win);
67
+ /** Current logical window size (CSS viewport), in pixels. */
68
+ void xt_gui_window_viewport(XtGuiWindow *win, float *width, float *height);
69
+ /** CSS-like descriptor of the deepest element at (x, y), or `""` (test/debug). */
70
+ std::string xt_gui_hit_test(XtGuiWindow *win, float x, float y);
71
+ /** Synthesise a pointer event (`mousemove`/`mousedown`/`mouseup`/`click`). */
72
+ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float y, int button,
73
+ int clicks);
74
+ /** Synthesise a wheel event. */
75
+ void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, float delta_y);
76
+ /** Synthesise a key event (`keydown`/`keyup`). */
77
+ void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code);
78
+
79
+ #endif /* __cplusplus */
80
+
81
+ #endif /* XT_GUI_ENGINE_H */
@@ -0,0 +1,125 @@
1
+ /*
2
+ * xbintsc GUI engine — image decoding implementation.
3
+ */
4
+
5
+ #include "image.h"
6
+
7
+ #define STB_IMAGE_IMPLEMENTATION
8
+ #define STBI_ONLY_PNG
9
+ #define STBI_ONLY_JPEG
10
+ #define STBI_ONLY_BMP
11
+ #define STBI_ONLY_GIF
12
+ #define STBI_ONLY_TGA
13
+ #include "stb_image.h"
14
+
15
+ #include <cctype>
16
+ #include <unordered_map>
17
+
18
+ namespace xtgui {
19
+ namespace {
20
+
21
+ struct SizeEntry {
22
+ bool valid = false;
23
+ int width = 0;
24
+ int height = 0;
25
+ };
26
+
27
+ std::unordered_map<std::string, Image> &imageCache() {
28
+ static std::unordered_map<std::string, Image> cache;
29
+ return cache;
30
+ }
31
+
32
+ std::unordered_map<std::string, SizeEntry> &sizeCache() {
33
+ static std::unordered_map<std::string, SizeEntry> cache;
34
+ return cache;
35
+ }
36
+
37
+ /** Strip a `file://` prefix and percent-decode a local path. */
38
+ std::string normalizePath(const std::string &input) {
39
+ std::string path = input;
40
+ if (path.rfind("file://", 0) == 0) {
41
+ path = path.substr(7);
42
+ /* `file:///abs` -> `/abs`; `file://host/abs` is not supported. */
43
+ size_t slash = path.find('/');
44
+ if (slash != std::string::npos && slash != 0) path = path.substr(slash);
45
+ }
46
+ std::string out;
47
+ out.reserve(path.size());
48
+ for (size_t i = 0; i < path.size(); i++) {
49
+ if (path[i] == '%' && i + 2 < path.size() && std::isxdigit((unsigned char)path[i + 1]) &&
50
+ std::isxdigit((unsigned char)path[i + 2])) {
51
+ auto hex = [](char c) -> int {
52
+ if (c >= '0' && c <= '9') return c - '0';
53
+ return std::tolower((unsigned char)c) - 'a' + 10;
54
+ };
55
+ out.push_back((char)(hex(path[i + 1]) * 16 + hex(path[i + 2])));
56
+ i += 2;
57
+ } else {
58
+ out.push_back(path[i]);
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ } // namespace
65
+
66
+ const Image *xt_image_load(const std::string &raw_path) {
67
+ std::string path = normalizePath(raw_path);
68
+ auto &cache = imageCache();
69
+ auto found = cache.find(path);
70
+ if (found != cache.end()) {
71
+ return found->second.width > 0 ? &found->second : nullptr;
72
+ }
73
+
74
+ Image image;
75
+ int width = 0;
76
+ int height = 0;
77
+ int channels = 0;
78
+ unsigned char *data = stbi_load(path.c_str(), &width, &height, &channels, 4);
79
+ if (data != nullptr) {
80
+ image.width = width;
81
+ image.height = height;
82
+ image.pixels.assign(data, data + (size_t)width * (size_t)height * 4);
83
+ stbi_image_free(data);
84
+ }
85
+ auto inserted = cache.emplace(path, std::move(image));
86
+ return inserted.first->second.width > 0 ? &inserted.first->second : nullptr;
87
+ }
88
+
89
+ bool xt_image_size(const std::string &raw_path, int *width, int *height) {
90
+ std::string path = normalizePath(raw_path);
91
+ auto &cache = sizeCache();
92
+ auto found = cache.find(path);
93
+ if (found == cache.end()) {
94
+ SizeEntry entry;
95
+ int channels = 0;
96
+ entry.valid = stbi_info(path.c_str(), &entry.width, &entry.height, &channels) != 0;
97
+ found = cache.emplace(path, entry).first;
98
+ }
99
+ if (!found->second.valid) return false;
100
+ if (width != nullptr) *width = found->second.width;
101
+ if (height != nullptr) *height = found->second.height;
102
+ return true;
103
+ }
104
+
105
+ bool xt_image_decode(const unsigned char *data, size_t size, Image *out) {
106
+ if (data == nullptr || out == nullptr) return false;
107
+ int width = 0;
108
+ int height = 0;
109
+ int channels = 0;
110
+ unsigned char *decoded =
111
+ stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4);
112
+ if (decoded == nullptr) return false;
113
+ out->width = width;
114
+ out->height = height;
115
+ out->pixels.assign(decoded, decoded + (size_t)width * (size_t)height * 4);
116
+ stbi_image_free(decoded);
117
+ return true;
118
+ }
119
+
120
+ void xt_image_shutdown() {
121
+ imageCache().clear();
122
+ sizeCache().clear();
123
+ }
124
+
125
+ } // namespace xtgui