xbintsc 0.3.16 → 0.3.25
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/dist/src/driver/bundler/graph.d.ts +2 -1
- package/dist/src/driver/bundler/graph.js +18 -3
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.d.ts +2 -1
- package/dist/src/driver/bundler/merge.js +2 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.js +2 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/extensions/gui/html.d.ts +25 -0
- package/dist/src/extensions/gui/html.js +203 -0
- package/dist/src/extensions/gui/html.js.map +1 -0
- package/dist/src/extensions/gui/index.js +11 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/registry.d.ts +29 -0
- package/dist/src/extensions/registry.js +14 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lexer/scanner/identifiers.js +4 -12
- package/dist/src/lexer/scanner/identifiers.js.map +1 -1
- package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
- package/dist/tests/driver/bundler-resolve.test.js +230 -0
- package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
- package/dist/tests/driver/modules.test.js +68 -0
- package/dist/tests/driver/modules.test.js.map +1 -1
- package/dist/tests/e2e/gui.test.js +210 -1
- package/dist/tests/e2e/gui.test.js.map +1 -1
- package/dist/tests/extensions/gui-index.test.d.ts +1 -0
- package/dist/tests/extensions/gui-index.test.js +49 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.d.ts +1 -0
- package/dist/tests/extensions/gui.test.js +177 -0
- package/dist/tests/extensions/gui.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.js +36 -0
- package/dist/tests/extensions/registry.test.js.map +1 -1
- package/dist/tests/public-api.test.d.ts +1 -0
- package/dist/tests/public-api.test.js +31 -0
- package/dist/tests/public-api.test.js.map +1 -0
- package/package.json +1 -1
- package/runtime/ext_gui/document.cpp +90 -0
- package/runtime/ext_gui/document.h +23 -0
- package/runtime/ext_gui/dom.cpp +36 -0
- package/runtime/ext_gui/dom.h +7 -0
- package/runtime/ext_gui/dom_api.cpp +1247 -0
- package/runtime/ext_gui/dom_api.h +12 -0
- package/runtime/ext_gui/gui.cpp +49 -3
- package/runtime/ext_gui/gui_engine.h +54 -0
- package/runtime/ext_gui/script.cpp +63 -0
- package/runtime/ext_gui/window.cpp +60 -0
- package/src/driver/bundler/graph.ts +21 -2
- package/src/driver/bundler/merge.ts +3 -1
- package/src/driver/compiler.ts +2 -2
- package/src/extensions/gui/html.ts +236 -0
- package/src/extensions/gui/index.ts +11 -0
- package/src/extensions/registry.ts +39 -0
- package/src/index.ts +1 -1
- package/src/lexer/scanner/identifiers.ts +4 -11
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — DOM object model entry points.
|
|
3
|
+
*
|
|
4
|
+
* The implementation lives in `dom_api.cpp`; this header only pulls in the
|
|
5
|
+
* shared engine definitions so translation units can include either one.
|
|
6
|
+
*/
|
|
7
|
+
#ifndef XT_GUI_DOM_API_H
|
|
8
|
+
#define XT_GUI_DOM_API_H
|
|
9
|
+
|
|
10
|
+
#include "gui_engine.h"
|
|
11
|
+
|
|
12
|
+
#endif /* XT_GUI_DOM_API_H */
|
package/runtime/ext_gui/gui.cpp
CHANGED
|
@@ -89,6 +89,9 @@ void xt_gui_quit_window(XtGuiWindow *win) {
|
|
|
89
89
|
if (win == NULL || !win->open) return;
|
|
90
90
|
win->open = 0;
|
|
91
91
|
xt_gui_emit(win, "close");
|
|
92
|
+
win->animation_frames.clear();
|
|
93
|
+
xt_gui_handles_reset(win);
|
|
94
|
+
win->document_object = XT_UNDEFINED;
|
|
92
95
|
win->document.reset();
|
|
93
96
|
win->html.clear();
|
|
94
97
|
if (g_device != NULL) {
|
|
@@ -111,6 +114,19 @@ void xt_gui_window_viewport(XtGuiWindow *win, float *width, float *height) {
|
|
|
111
114
|
if (height != NULL) *height = (float)h;
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
void xt_gui_flush_dom(XtGuiWindow *win) {
|
|
118
|
+
if (win == NULL || win->document == nullptr) return;
|
|
119
|
+
bool mutated = win->struct_dirty != 0;
|
|
120
|
+
win->struct_dirty = 0;
|
|
121
|
+
if (win->document->takeDirty()) mutated = true;
|
|
122
|
+
if (!mutated) return;
|
|
123
|
+
float width = 0;
|
|
124
|
+
float height = 0;
|
|
125
|
+
xt_gui_window_viewport(win, &width, &height);
|
|
126
|
+
win->document->restyle(width, height);
|
|
127
|
+
win->geometry.dirty = 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
114
130
|
static XtGuiWindow *xt_gui_find_by_window_id(SDL_WindowID id) {
|
|
115
131
|
SDL_Window *window = SDL_GetWindowFromID(id);
|
|
116
132
|
if (window == NULL) return NULL;
|
|
@@ -164,10 +180,13 @@ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float
|
|
|
164
180
|
int clicks) {
|
|
165
181
|
if (win == NULL || !win->open) return;
|
|
166
182
|
xt_gui_update_hover(win, x, y);
|
|
183
|
+
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
167
184
|
if (strcmp(type, "mousedown") == 0 && win->document != nullptr) {
|
|
168
|
-
if (win->document->setFocus(
|
|
185
|
+
if (win->document->setFocus(node)) win->geometry.dirty = 1;
|
|
169
186
|
}
|
|
170
|
-
|
|
187
|
+
xt_value payload = xt_gui_pointer_payload(type, win, x, y, button, clicks);
|
|
188
|
+
if (node != nullptr) xt_gui_emit_dom_event(win, type, node, payload);
|
|
189
|
+
xt_gui_emit_payload(win, type, payload);
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, float delta_y) {
|
|
@@ -175,6 +194,8 @@ void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, fl
|
|
|
175
194
|
xt_value payload = xt_gui_pointer_payload("wheel", win, x, y, -1, 0);
|
|
176
195
|
xt_object_set(payload, xt_string_from_cstr("deltaX"), xt_number(delta_x));
|
|
177
196
|
xt_object_set(payload, xt_string_from_cstr("deltaY"), xt_number(delta_y));
|
|
197
|
+
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
198
|
+
if (node != nullptr) xt_gui_emit_dom_event(win, "wheel", node, payload);
|
|
178
199
|
xt_gui_emit_payload(win, "wheel", payload);
|
|
179
200
|
}
|
|
180
201
|
|
|
@@ -194,7 +215,10 @@ static xt_value xt_gui_key_payload(const char *type, const char *key, const char
|
|
|
194
215
|
|
|
195
216
|
void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code) {
|
|
196
217
|
if (win == NULL || !win->open) return;
|
|
197
|
-
|
|
218
|
+
xt_value payload = xt_gui_key_payload(type, key, code, false, false, false, false, false);
|
|
219
|
+
const xtgui::Node *node = win->document != nullptr ? win->document->focus() : nullptr;
|
|
220
|
+
if (node != nullptr) xt_gui_emit_dom_event(win, type, node, payload);
|
|
221
|
+
xt_gui_emit_payload(win, type, payload);
|
|
198
222
|
}
|
|
199
223
|
|
|
200
224
|
static void xt_gui_handle_event(const SDL_Event *event) {
|
|
@@ -265,6 +289,19 @@ static void xt_gui_handle_event(const SDL_Event *event) {
|
|
|
265
289
|
|
|
266
290
|
/* -- rendering ------------------------------------------------------------ */
|
|
267
291
|
|
|
292
|
+
void xt_gui_run_animation_frames(XtGuiWindow *win, double timestamp_ms) {
|
|
293
|
+
if (win == NULL || win->animation_frames.empty()) return;
|
|
294
|
+
/* Swap the queue out first: a callback that calls requestAnimationFrame
|
|
295
|
+
* again schedules for the *next* frame, not this one. */
|
|
296
|
+
std::vector<XtGuiAnimationFrame> frames;
|
|
297
|
+
frames.swap(win->animation_frames);
|
|
298
|
+
for (const XtGuiAnimationFrame &frame : frames) {
|
|
299
|
+
if (!win->open) break;
|
|
300
|
+
xt_value arg = xt_number(timestamp_ms);
|
|
301
|
+
xt_call_with_this(frame.fn, win->object, 1, &arg);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
268
305
|
void xt_gui_render_window(XtGuiWindow *win) {
|
|
269
306
|
if (win == NULL || !win->open || win->window == NULL || g_device == NULL) return;
|
|
270
307
|
float viewport_width = 0;
|
|
@@ -275,8 +312,15 @@ void xt_gui_render_window(XtGuiWindow *win) {
|
|
|
275
312
|
double now = (double)SDL_GetTicks();
|
|
276
313
|
double delta = win->last_frame_ms > 0.0 ? now - win->last_frame_ms : 0.0;
|
|
277
314
|
win->last_frame_ms = now;
|
|
315
|
+
/* Animation-frame callbacks run before layout, so any DOM mutation they make
|
|
316
|
+
* is picked up by this same frame. */
|
|
317
|
+
xt_gui_run_animation_frames(win, now);
|
|
278
318
|
if (win->document != nullptr && win->document->advance(delta)) win->geometry.dirty = 1;
|
|
279
319
|
|
|
320
|
+
/* A DOM mutation from a handler invalidates the tree: restyle/relayout
|
|
321
|
+
* before (re)building the geometry. */
|
|
322
|
+
xt_gui_flush_dom(win);
|
|
323
|
+
|
|
280
324
|
/* Rebuild the geometry only when the document or viewport changed. */
|
|
281
325
|
bool can_paint = xt_gui_renderer_ensure(g_device, win->window);
|
|
282
326
|
if (can_paint && win->geometry.dirty && win->document != nullptr) {
|
|
@@ -378,6 +422,8 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
|
|
|
378
422
|
record->text_geometry = XtGuiGeometry();
|
|
379
423
|
record->image_geometry = XtGuiGeometry();
|
|
380
424
|
record->last_frame_ms = 0.0;
|
|
425
|
+
record->animation_frames.clear();
|
|
426
|
+
record->next_animation_frame_id = 1;
|
|
381
427
|
record->background[0] = 0.08f;
|
|
382
428
|
record->background[1] = 0.09f;
|
|
383
429
|
record->background[2] = 0.11f;
|
|
@@ -22,6 +22,8 @@ extern "C" {
|
|
|
22
22
|
xt_value xt_gui_create_window(int32_t argc, xt_value *argv);
|
|
23
23
|
xt_value xt_gui_run(int32_t argc, xt_value *argv);
|
|
24
24
|
xt_value xt_gui_quit(int32_t argc, xt_value *argv);
|
|
25
|
+
/** `__registerScript(id, fn)`: record an AOT-compiled `<script>` body. */
|
|
26
|
+
xt_value xt_register_script(int32_t argc, xt_value *argv);
|
|
25
27
|
|
|
26
28
|
#ifdef __cplusplus
|
|
27
29
|
} /* extern "C" */
|
|
@@ -30,10 +32,26 @@ xt_value xt_gui_quit(int32_t argc, xt_value *argv);
|
|
|
30
32
|
|
|
31
33
|
#include <memory>
|
|
32
34
|
#include <string>
|
|
35
|
+
#include <unordered_map>
|
|
36
|
+
#include <vector>
|
|
33
37
|
|
|
34
38
|
#include "document.h"
|
|
35
39
|
#include "renderer.h"
|
|
36
40
|
|
|
41
|
+
/** One element-level event listener registered through `addEventListener`. */
|
|
42
|
+
struct XtGuiNodeListener {
|
|
43
|
+
std::string type;
|
|
44
|
+
xt_value fn;
|
|
45
|
+
bool once;
|
|
46
|
+
bool capture;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** One pending `requestAnimationFrame` callback. */
|
|
50
|
+
struct XtGuiAnimationFrame {
|
|
51
|
+
int id;
|
|
52
|
+
xt_value fn;
|
|
53
|
+
};
|
|
54
|
+
|
|
37
55
|
/* One live window. `object` is the JavaScript-visible handle; the record is
|
|
38
56
|
* addressed from it through the hidden `__xt_gui_index` property. */
|
|
39
57
|
struct XtGuiWindow {
|
|
@@ -50,10 +68,43 @@ struct XtGuiWindow {
|
|
|
50
68
|
float background[4];
|
|
51
69
|
int width;
|
|
52
70
|
int height;
|
|
71
|
+
|
|
72
|
+
/* -- DOM object model (element handles + events) ----------------------- */
|
|
73
|
+
/** `document` handle for this window (lazily created). */
|
|
74
|
+
xt_value document_object = XT_UNDEFINED;
|
|
75
|
+
/** Bumped on every `loadHTML`; stale handles no-op. */
|
|
76
|
+
double doc_generation = 1.0;
|
|
77
|
+
/** Node -> table index / cached handle, so identity is stable. */
|
|
78
|
+
std::vector<xtgui::Node *> node_order;
|
|
79
|
+
std::unordered_map<const xtgui::Node *, int> node_index;
|
|
80
|
+
std::unordered_map<const xtgui::Node *, xt_value> node_handles;
|
|
81
|
+
/** Element-level listeners, keyed by node. */
|
|
82
|
+
std::unordered_map<const xtgui::Node *, std::vector<XtGuiNodeListener>> node_listeners;
|
|
83
|
+
/** Set by a DOM mutation; consumed by the frame loop before repainting. */
|
|
84
|
+
int struct_dirty = 0;
|
|
85
|
+
/** Callbacks queued for the next frame, plus the id counter. */
|
|
86
|
+
std::vector<XtGuiAnimationFrame> animation_frames;
|
|
87
|
+
int next_animation_frame_id = 1;
|
|
53
88
|
};
|
|
54
89
|
|
|
55
90
|
/** Resolve a window handle (`this`) to its record, or NULL for a foreign value. */
|
|
56
91
|
XtGuiWindow *xt_gui_window_from_this(xt_value thisValue);
|
|
92
|
+
/** The `document` handle for `win` (created on first use). */
|
|
93
|
+
xt_value xt_gui_document_handle(XtGuiWindow *win);
|
|
94
|
+
/** Node handle for `node` (created and cached on first use). */
|
|
95
|
+
xt_value xt_gui_node_handle(XtGuiWindow *win, const xtgui::Node *node);
|
|
96
|
+
/** Drop every cached handle/listener and bump the document generation. */
|
|
97
|
+
void xt_gui_handles_reset(XtGuiWindow *win);
|
|
98
|
+
/** Deliver `type` to element listeners along the propagation path. */
|
|
99
|
+
void xt_gui_emit_dom_event(XtGuiWindow *win, const char *type, const xtgui::Node *target,
|
|
100
|
+
xt_value legacy_payload);
|
|
101
|
+
/** Synthesise a DOM event whose target is `target` (used by element.click()). */
|
|
102
|
+
void xt_gui_dispatch_to_node(XtGuiWindow *win, const xtgui::Node *target, const char *type);
|
|
103
|
+
/** Apply any pending DOM mutation (restyle/relayout) before a synchronous read. */
|
|
104
|
+
void xt_gui_flush_dom(XtGuiWindow *win);
|
|
105
|
+
/** Run every registered `<script data-xt-id>` body found in the document, in
|
|
106
|
+
* document order, passing `(windowHandle, documentHandle)`. */
|
|
107
|
+
void xt_gui_run_scripts(XtGuiWindow *win);
|
|
57
108
|
/** Shared prototype carrying the window methods. */
|
|
58
109
|
xt_value xt_gui_window_proto(void);
|
|
59
110
|
/** Release GPU claim + destroy the window and mark the record closed. */
|
|
@@ -64,6 +115,9 @@ void xt_gui_emit(XtGuiWindow *win, const char *event);
|
|
|
64
115
|
void xt_gui_emit_payload(XtGuiWindow *win, const char *event, xt_value payload);
|
|
65
116
|
/** Acquire a swapchain frame for `win` and clear it (placeholder paint). */
|
|
66
117
|
void xt_gui_render_window(XtGuiWindow *win);
|
|
118
|
+
/** Run every callback queued with `requestAnimationFrame` for the frame at
|
|
119
|
+
* `timestamp_ms`, then clear the queue. */
|
|
120
|
+
void xt_gui_run_animation_frames(XtGuiWindow *win, double timestamp_ms);
|
|
67
121
|
/** Current logical window size (CSS viewport), in pixels. */
|
|
68
122
|
void xt_gui_window_viewport(XtGuiWindow *win, float *width, float *height);
|
|
69
123
|
/** CSS-like descriptor of the deepest element at (x, y), or `""` (test/debug). */
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — AOT `<script>` registry.
|
|
3
|
+
*
|
|
4
|
+
* xbintsc has no runtime interpreter, so `<script>` bodies are compiled ahead
|
|
5
|
+
* of time. The `.html` asset loader (see `src/extensions/gui/html.ts`) extracts
|
|
6
|
+
* each inline body, wraps it in a function `(window, document) => { ... }` and
|
|
7
|
+
* calls the `__registerScript(id, fn)` builtin at module initialization. This
|
|
8
|
+
* file keeps that registry and runs the matching functions once the document
|
|
9
|
+
* has been parsed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include "gui_engine.h"
|
|
13
|
+
|
|
14
|
+
#include <string>
|
|
15
|
+
#include <unordered_map>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
/** Script id -> AOT-compiled function value. Lives for the whole process (the
|
|
19
|
+
* runtime arena never reclaims, so storing `xt_value`s is safe). */
|
|
20
|
+
static std::unordered_map<std::string, xt_value> &script_registry() {
|
|
21
|
+
static std::unordered_map<std::string, xt_value> registry;
|
|
22
|
+
return registry;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
|
|
26
|
+
if (argc >= 2) {
|
|
27
|
+
xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
|
|
28
|
+
const char *id = xt_string_data(id_value);
|
|
29
|
+
xt_value fn = xt_arg(argc, argv, 1);
|
|
30
|
+
if (id != nullptr && XT_IS_FUNCTION(fn)) {
|
|
31
|
+
script_registry()[std::string(id)] = fn;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return XT_UNDEFINED;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Collect `data-xt-id` markers from `<script>` elements, in document order. */
|
|
38
|
+
static void collect_script_ids(const xtgui::Node *node, std::vector<std::string> &ids) {
|
|
39
|
+
if (node == nullptr) return;
|
|
40
|
+
if (node->isElement() && node->isTag("script")) {
|
|
41
|
+
const std::string *id = node->attr("data-xt-id");
|
|
42
|
+
if (id != nullptr && !id->empty()) ids.push_back(*id);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const std::unique_ptr<xtgui::Node> &child : node->children) {
|
|
46
|
+
collect_script_ids(child.get(), ids);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void xt_gui_run_scripts(XtGuiWindow *win) {
|
|
51
|
+
if (win == nullptr || win->document == nullptr) return;
|
|
52
|
+
std::vector<std::string> ids;
|
|
53
|
+
collect_script_ids(win->document->root(), ids);
|
|
54
|
+
if (ids.empty()) return;
|
|
55
|
+
xt_value window_handle = win->object;
|
|
56
|
+
xt_value document_handle = xt_gui_document_handle(win);
|
|
57
|
+
for (const std::string &id : ids) {
|
|
58
|
+
auto it = script_registry().find(id);
|
|
59
|
+
if (it == script_registry().end()) continue;
|
|
60
|
+
xt_value args[2] = {window_handle, document_handle};
|
|
61
|
+
xt_call_with_this(it->second, XT_UNDEFINED, 2, args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
#include "gui_engine.h"
|
|
12
12
|
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
|
|
13
15
|
#include "paint.h"
|
|
14
16
|
#include "text.h"
|
|
15
17
|
|
|
@@ -48,16 +50,23 @@ static xt_value win_load_html(xt_value self, xt_value env, int32_t argc, xt_valu
|
|
|
48
50
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
49
51
|
if (win != nullptr) {
|
|
50
52
|
win->html = html;
|
|
53
|
+
/* Fresh document: invalidate every previously handed-out handle. */
|
|
54
|
+
xt_gui_handles_reset(win);
|
|
51
55
|
win->document = std::make_unique<xtgui::XtDocument>();
|
|
52
56
|
float width = 0;
|
|
53
57
|
float height = 0;
|
|
54
58
|
xt_gui_window_viewport(win, &width, &height);
|
|
55
59
|
win->document->load(html, width, height);
|
|
56
60
|
win->geometry.dirty = 1;
|
|
61
|
+
/* Run the AOT-compiled `<script>` bodies now that the tree is parsed, so
|
|
62
|
+
* `document.getElementById(...)` resolves, then settle the first layout. */
|
|
63
|
+
xt_gui_run_scripts(win);
|
|
64
|
+
xt_gui_flush_dom(win);
|
|
57
65
|
}
|
|
58
66
|
xt_object_set(self, xt_string_from_cstr("__xt_gui_html"), xt_string_from_cstr(html.c_str()));
|
|
59
67
|
/* Painting the document is milestone M3b+; the parsed tree and computed
|
|
60
68
|
* styles are available now (see `computedStyle`). */
|
|
69
|
+
xt_gui_emit(win, "DOMContentLoaded");
|
|
61
70
|
xt_gui_emit(win, "load");
|
|
62
71
|
return XT_UNDEFINED;
|
|
63
72
|
}
|
|
@@ -69,12 +78,21 @@ static xt_value win_get_html(xt_value self, xt_value env, int32_t argc, xt_value
|
|
|
69
78
|
return xt_object_get_cstr(self, "__xt_gui_html");
|
|
70
79
|
}
|
|
71
80
|
|
|
81
|
+
/** The `document` handle for this window (created on first access). */
|
|
82
|
+
static xt_value win_document_get(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
83
|
+
(void)env;
|
|
84
|
+
(void)argc;
|
|
85
|
+
(void)argv;
|
|
86
|
+
return xt_gui_document_handle(xt_gui_window_from_this(self));
|
|
87
|
+
}
|
|
88
|
+
|
|
72
89
|
/** Diagnostic/test hook: computed value of `property` for the first match of
|
|
73
90
|
* `selector`, or an empty string. */
|
|
74
91
|
static xt_value win_computed_style(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
75
92
|
(void)env;
|
|
76
93
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
77
94
|
if (win == nullptr || win->document == nullptr || argc < 2) return xt_string_from_cstr("");
|
|
95
|
+
xt_gui_flush_dom(win);
|
|
78
96
|
xt_value selectorValue = xt_to_string(xt_arg(argc, argv, 0));
|
|
79
97
|
xt_value propertyValue = xt_to_string(xt_arg(argc, argv, 1));
|
|
80
98
|
const char *selector = xt_string_data(selectorValue);
|
|
@@ -95,6 +113,7 @@ static xt_value win_document_tree(xt_value self, xt_value env, int32_t argc, xt_
|
|
|
95
113
|
(void)argv;
|
|
96
114
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
97
115
|
if (win == nullptr || win->document == nullptr) return xt_string_from_cstr("");
|
|
116
|
+
xt_gui_flush_dom(win);
|
|
98
117
|
std::string text = win->document->toDebugString();
|
|
99
118
|
return xt_string_from_cstr(text.c_str());
|
|
100
119
|
}
|
|
@@ -104,6 +123,7 @@ static xt_value win_query_count(xt_value self, xt_value env, int32_t argc, xt_va
|
|
|
104
123
|
(void)env;
|
|
105
124
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
106
125
|
if (win == nullptr || win->document == nullptr || argc < 1) return xt_number(0);
|
|
126
|
+
xt_gui_flush_dom(win);
|
|
107
127
|
xt_value selectorValue = xt_to_string(xt_arg(argc, argv, 0));
|
|
108
128
|
const char *selector = xt_string_data(selectorValue);
|
|
109
129
|
if (selector == nullptr) return xt_number(0);
|
|
@@ -116,6 +136,7 @@ static xt_value win_get_bounding_rect(xt_value self, xt_value env, int32_t argc,
|
|
|
116
136
|
(void)env;
|
|
117
137
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
118
138
|
if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
|
|
139
|
+
xt_gui_flush_dom(win);
|
|
119
140
|
xt_value selectorValue = xt_to_string(xt_arg(argc, argv, 0));
|
|
120
141
|
const char *selector = xt_string_data(selectorValue);
|
|
121
142
|
if (selector == nullptr) return XT_UNDEFINED;
|
|
@@ -138,6 +159,7 @@ static xt_value win_layout_tree(xt_value self, xt_value env, int32_t argc, xt_va
|
|
|
138
159
|
(void)argv;
|
|
139
160
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
140
161
|
if (win == nullptr || win->document == nullptr) return xt_string_from_cstr("");
|
|
162
|
+
xt_gui_flush_dom(win);
|
|
141
163
|
std::string text = win->document->layout().dump();
|
|
142
164
|
return xt_string_from_cstr(text.c_str());
|
|
143
165
|
}
|
|
@@ -169,6 +191,7 @@ static xt_value win_paint_list(xt_value self, xt_value env, int32_t argc, xt_val
|
|
|
169
191
|
(void)argv;
|
|
170
192
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
171
193
|
if (win == nullptr || win->document == nullptr) return xt_string_from_cstr("");
|
|
194
|
+
xt_gui_flush_dom(win);
|
|
172
195
|
xtgui::DisplayList list;
|
|
173
196
|
xtgui::xt_paint_build(win->document->layout().root(), list);
|
|
174
197
|
std::string text = list.dump();
|
|
@@ -182,6 +205,7 @@ static xt_value win_paint_count(xt_value self, xt_value env, int32_t argc, xt_va
|
|
|
182
205
|
(void)argv;
|
|
183
206
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
184
207
|
if (win == nullptr || win->document == nullptr) return xt_number(0);
|
|
208
|
+
xt_gui_flush_dom(win);
|
|
185
209
|
xtgui::DisplayList list;
|
|
186
210
|
xtgui::xt_paint_build(win->document->layout().root(), list);
|
|
187
211
|
return xt_number((double)list.rects.size());
|
|
@@ -259,6 +283,34 @@ static xt_value win_is_open(xt_value self, xt_value env, int32_t argc, xt_value
|
|
|
259
283
|
return (win != NULL && win->open) ? XT_TRUE : XT_FALSE;
|
|
260
284
|
}
|
|
261
285
|
|
|
286
|
+
/** `requestAnimationFrame(fn)` -> id. The callback runs once on the next frame
|
|
287
|
+
* with the frame timestamp (ms) as its only argument. */
|
|
288
|
+
static xt_value win_request_animation_frame(xt_value self, xt_value env, int32_t argc,
|
|
289
|
+
xt_value *argv) {
|
|
290
|
+
(void)env;
|
|
291
|
+
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
292
|
+
if (win == NULL || !win->open) return xt_number(0);
|
|
293
|
+
xt_value fn = xt_arg(argc, argv, 0);
|
|
294
|
+
if (!XT_IS_FUNCTION(fn)) return xt_number(0);
|
|
295
|
+
int id = win->next_animation_frame_id++;
|
|
296
|
+
win->animation_frames.push_back(XtGuiAnimationFrame{id, fn});
|
|
297
|
+
return xt_number((double)id);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** `cancelAnimationFrame(id)` drops a callback queued for the next frame. */
|
|
301
|
+
static xt_value win_cancel_animation_frame(xt_value self, xt_value env, int32_t argc,
|
|
302
|
+
xt_value *argv) {
|
|
303
|
+
(void)env;
|
|
304
|
+
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
305
|
+
if (win == NULL || argc < 1) return XT_UNDEFINED;
|
|
306
|
+
int id = (int)xt_to_number(xt_arg(argc, argv, 0));
|
|
307
|
+
std::vector<XtGuiAnimationFrame> &frames = win->animation_frames;
|
|
308
|
+
frames.erase(std::remove_if(frames.begin(), frames.end(),
|
|
309
|
+
[id](const XtGuiAnimationFrame &frame) { return frame.id == id; }),
|
|
310
|
+
frames.end());
|
|
311
|
+
return XT_UNDEFINED;
|
|
312
|
+
}
|
|
313
|
+
|
|
262
314
|
static xt_value win_on(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
263
315
|
(void)env;
|
|
264
316
|
if (argc >= 2) {
|
|
@@ -314,6 +366,7 @@ static xt_value win_hit_test(xt_value self, xt_value env, int32_t argc, xt_value
|
|
|
314
366
|
(void)env;
|
|
315
367
|
XtGuiWindow *win = xt_gui_window_from_this(self);
|
|
316
368
|
if (win == nullptr || win->document == nullptr || argc < 2) return xt_string_from_cstr("");
|
|
369
|
+
xt_gui_flush_dom(win);
|
|
317
370
|
float x = (float)xt_to_number(xt_arg(argc, argv, 0));
|
|
318
371
|
float y = (float)xt_to_number(xt_arg(argc, argv, 1));
|
|
319
372
|
std::string target = xt_gui_hit_test(win, x, y);
|
|
@@ -376,10 +429,15 @@ static void define_method(xt_value proto, const char *name, void *fn) {
|
|
|
376
429
|
xt_object_set(proto, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
|
|
377
430
|
}
|
|
378
431
|
|
|
432
|
+
static void define_getter(xt_value proto, const char *name, void *fn) {
|
|
433
|
+
xt_object_define_getter(proto, xt_string_from_cstr(name), xt_closure_new(fn, 0, NULL));
|
|
434
|
+
}
|
|
435
|
+
|
|
379
436
|
xt_value xt_gui_window_proto(void) {
|
|
380
437
|
if (XT_IS_OBJECT(g_window_proto)) return g_window_proto;
|
|
381
438
|
xt_value proto = xt_object_new();
|
|
382
439
|
define_method(proto, "setTitle", (void *)win_set_title);
|
|
440
|
+
define_getter(proto, "document", (void *)win_document_get);
|
|
383
441
|
define_method(proto, "setSize", (void *)win_set_size);
|
|
384
442
|
define_method(proto, "loadHTML", (void *)win_load_html);
|
|
385
443
|
define_method(proto, "getHTML", (void *)win_get_html);
|
|
@@ -396,6 +454,8 @@ xt_value xt_gui_window_proto(void) {
|
|
|
396
454
|
define_method(proto, "fontMetrics", (void *)win_font_metrics);
|
|
397
455
|
define_method(proto, "close", (void *)win_close);
|
|
398
456
|
define_method(proto, "isOpen", (void *)win_is_open);
|
|
457
|
+
define_method(proto, "requestAnimationFrame", (void *)win_request_animation_frame);
|
|
458
|
+
define_method(proto, "cancelAnimationFrame", (void *)win_cancel_animation_frame);
|
|
399
459
|
define_method(proto, "on", (void *)win_on);
|
|
400
460
|
define_method(proto, "off", (void *)win_off);
|
|
401
461
|
define_method(proto, "hitTest", (void *)win_hit_test);
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { dirname } from "node:path";
|
|
7
|
+
import { dirname, extname } from "node:path";
|
|
8
8
|
import { SyntaxKind, type ExportDeclaration, type ImportDeclaration, type Statement } from "../../ast/nodes.js";
|
|
9
9
|
import { bind } from "../../binder/binder.js";
|
|
10
10
|
import { DiagnosticBag, DiagnosticCode } from "../../diagnostics/diagnostic.js";
|
|
11
11
|
import { SourceFile } from "../../diagnostics/source.js";
|
|
12
12
|
import { Parser } from "../../parser/parser.js";
|
|
13
|
+
import type { AssetLoader } from "../../extensions/registry.js";
|
|
13
14
|
import { classifyDependency } from "./resolve.js";
|
|
14
15
|
import type { ModuleRecord } from "./types.js";
|
|
15
16
|
|
|
@@ -29,6 +30,7 @@ export function loadGraph(
|
|
|
29
30
|
entryPath: string,
|
|
30
31
|
diagnostics: DiagnosticBag,
|
|
31
32
|
externalSpecifiers: ReadonlySet<string>,
|
|
33
|
+
assetLoaders: Readonly<Record<string, AssetLoader>> = {},
|
|
32
34
|
): ModuleRecord[] | undefined {
|
|
33
35
|
const records = new Map<string, ModuleRecord>();
|
|
34
36
|
const order: ModuleRecord[] = [];
|
|
@@ -50,7 +52,24 @@ export function loadGraph(
|
|
|
50
52
|
return undefined;
|
|
51
53
|
}
|
|
52
54
|
visiting.add(path);
|
|
53
|
-
const
|
|
55
|
+
const raw = readFileSync(path, "utf8");
|
|
56
|
+
/* An extension may rewrite an imported asset (e.g. `.html`) into a TS
|
|
57
|
+
* module before it is parsed. The core only sees the transformed text. */
|
|
58
|
+
const loader = assetLoaders[extname(path).toLowerCase()];
|
|
59
|
+
let text = raw;
|
|
60
|
+
if (loader) {
|
|
61
|
+
try {
|
|
62
|
+
text = loader(path, raw).moduleSource;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
diagnostics.error(
|
|
65
|
+
DiagnosticCode.CodegenError,
|
|
66
|
+
error instanceof Error ? error.message : String(error),
|
|
67
|
+
);
|
|
68
|
+
failed = true;
|
|
69
|
+
visiting.delete(path);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
54
73
|
const file = new SourceFile(path, text);
|
|
55
74
|
const parser = new Parser(file, diagnostics);
|
|
56
75
|
const sourceFile = parser.parseSourceFile();
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import type { BindingName } from "../../ast/declarations.js";
|
|
28
28
|
import { SymbolKind } from "../../binder/binder.js";
|
|
29
29
|
import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
|
|
30
|
+
import type { AssetLoader } from "../../extensions/registry.js";
|
|
30
31
|
import { loadGraph } from "./graph.js";
|
|
31
32
|
import { classifyDependency } from "./resolve.js";
|
|
32
33
|
import {
|
|
@@ -43,8 +44,9 @@ export function bundleModules(
|
|
|
43
44
|
entryPath: string,
|
|
44
45
|
diagnostics: DiagnosticBag,
|
|
45
46
|
externalSpecifiers: ReadonlySet<string> = new Set(),
|
|
47
|
+
assetLoaders: Readonly<Record<string, AssetLoader>> = {},
|
|
46
48
|
): BundleResult | undefined {
|
|
47
|
-
const records = loadGraph(entryPath, diagnostics, externalSpecifiers);
|
|
49
|
+
const records = loadGraph(entryPath, diagnostics, externalSpecifiers, assetLoaders);
|
|
48
50
|
if (!records) return undefined;
|
|
49
51
|
|
|
50
52
|
const byPath = new Map(records.map((record) => [record.path, record]));
|
package/src/driver/compiler.ts
CHANGED
|
@@ -109,7 +109,7 @@ export function compileEntry(entryPath: string, extensions?: ExtensionRegistry):
|
|
|
109
109
|
statement.kind === SyntaxKind.ExportAssignment,
|
|
110
110
|
);
|
|
111
111
|
if (isModule) {
|
|
112
|
-
const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry));
|
|
112
|
+
const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry), registry.assetLoaders());
|
|
113
113
|
if (bundled) sourceFile = bundled.sourceFile;
|
|
114
114
|
}
|
|
115
115
|
const { ir } = generate(sourceFile, diagnostics, {
|
|
@@ -185,7 +185,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
185
185
|
);
|
|
186
186
|
let cacheText = sourceText;
|
|
187
187
|
if (isModule) {
|
|
188
|
-
const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry));
|
|
188
|
+
const bundled = bundleModules(absoluteEntry, diagnostics, externalModuleSpecifiers(registry), registry.assetLoaders());
|
|
189
189
|
if (bundled) {
|
|
190
190
|
sourceFile = bundled.sourceFile;
|
|
191
191
|
cacheText = bundled.text;
|