xbintsc 0.5.36 → 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.
Files changed (66) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/driver/version.d.ts +1 -1
  3. package/dist/src/driver/version.js +1 -1
  4. package/dist/src/extensions/gui/index.js +12 -0
  5. package/dist/src/extensions/gui/index.js.map +1 -1
  6. package/dist/tests/e2e/gui-browser.test.js +65 -0
  7. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  8. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  9. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  10. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  11. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  12. package/dist/tests/e2e/gui-dom.test.js +215 -0
  13. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  14. package/dist/tests/e2e/gui-events.test.js +112 -0
  15. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  16. package/doc/gui/api.md +38 -9
  17. package/doc/gui/canvas.md +49 -18
  18. package/doc/gui/dom.md +51 -4
  19. package/doc/zh-CN/gui/api.md +33 -4
  20. package/doc/zh-CN/gui/canvas.md +44 -19
  21. package/doc/zh-CN/gui/dom.md +45 -3
  22. package/package.json +7 -7
  23. package/runtime/ext_gui/browser.cpp +34 -49
  24. package/runtime/ext_gui/browser_abort.cpp +249 -0
  25. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  26. package/runtime/ext_gui/browser_media.cpp +176 -0
  27. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  28. package/runtime/ext_gui/canvas.cpp +88 -30
  29. package/runtime/ext_gui/canvas.h +74 -9
  30. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  31. package/runtime/ext_gui/canvas_internal.h +8 -5
  32. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  33. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  34. package/runtime/ext_gui/canvas_style.cpp +4 -2
  35. package/runtime/ext_gui/dom.h +4 -0
  36. package/runtime/ext_gui/dom_api.cpp +1 -0
  37. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  38. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  39. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  40. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  41. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  42. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  43. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  44. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  45. package/runtime/ext_gui/dom_api_internal.h +55 -2
  46. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  47. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  48. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  49. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  50. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  51. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  52. package/runtime/ext_gui/gui.cpp +59 -5
  53. package/runtime/ext_gui/gui_engine.h +47 -0
  54. package/runtime/ext_gui/gui_events.cpp +80 -9
  55. package/runtime/ext_gui/listener_options.h +43 -0
  56. package/runtime/ext_gui/paint.cpp +28 -0
  57. package/runtime/ext_gui/paint.h +4 -0
  58. package/runtime/ext_gui/window.cpp +14 -65
  59. package/runtime/ext_gui/window_events.cpp +125 -0
  60. package/runtime/rt.h +26 -0
  61. package/runtime/rt_internal.h +4 -0
  62. package/runtime/xt_builtins.c +0 -21
  63. package/runtime/xt_containers/access.inc +94 -2
  64. package/runtime/xt_containers/objects.inc +29 -31
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/index.ts +12 -0
@@ -0,0 +1,249 @@
1
+ /*
2
+ * xbintsc GUI engine — `AbortController` / `AbortSignal`.
3
+ *
4
+ * The pair exists so `addEventListener(..., { signal })` can actually remove a
5
+ * listener, as the DOM specifies. The engine has no Web-IDL machinery, so both
6
+ * objects are plain runtime objects with a shared hidden state:
7
+ *
8
+ * controller.__xt_gui_abort_state the state object
9
+ * controller.__xt_gui_abort_signal the signal handed out by `.signal`
10
+ * signal.__xt_gui_abort_state the same state object
11
+ *
12
+ * The state holds `aborted`, `reason` and the 'abort' listener list. Every value
13
+ * lives in a runtime object reachable from the handle the page kept, so the
14
+ * mark-sweep collector sees the whole graph.
15
+ */
16
+
17
+ #include "dom_api_internal.h"
18
+ #include "listener_options.h"
19
+
20
+ namespace domapi {
21
+
22
+ xt_value abort_signal_proto();
23
+ xt_value abort_controller_proto();
24
+
25
+ namespace {
26
+
27
+ xt_value new_state() {
28
+ xt_value state = xt_object_new();
29
+ xt_object_set(state, xt_string_from_cstr("aborted"), XT_FALSE);
30
+ xt_object_set(state, xt_string_from_cstr("reason"), XT_UNDEFINED);
31
+ xt_object_set(state, xt_string_from_cstr("listeners"), xt_array_new(0, nullptr));
32
+ return state;
33
+ }
34
+
35
+ xt_value state_of(xt_value self) { return xt_object_get_cstr(self, "__xt_gui_abort_state"); }
36
+
37
+ xt_value signal_of(xt_value self) { return xt_object_get_cstr(self, "__xt_gui_abort_signal"); }
38
+
39
+ bool state_aborted(xt_value state) {
40
+ return XT_IS_OBJECT(state) && value_truthy(xt_object_get_cstr(state, "aborted"));
41
+ }
42
+
43
+ xt_value abort_signal_from_state(xt_value state, xt_value controller) {
44
+ xt_value signal = xt_object_new_with_proto(abort_signal_proto());
45
+ xt_object_set(signal, xt_string_from_cstr("__xt_gui_abort_state"), state);
46
+ if (!XT_IS_UNDEFINED(controller)) {
47
+ xt_object_set(signal, xt_string_from_cstr("__xt_gui_abort_controller"), controller);
48
+ }
49
+ return signal;
50
+ }
51
+
52
+ /** Call `fn` (or `{ handleEvent }`) with an `{ type, target }` event. */
53
+ void call_abort_listener(xt_value fn, xt_value signal) {
54
+ xt_value event = xt_object_new_with_proto(event_proto());
55
+ xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr("abort"));
56
+ xt_object_set(event, xt_string_from_cstr("target"), signal);
57
+ xt_object_set(event, xt_string_from_cstr("currentTarget"), signal);
58
+ xt_object_set(event, xt_string_from_cstr("defaultPrevented"), XT_FALSE);
59
+ if (XT_IS_FUNCTION(fn)) {
60
+ xt_value args[1] = {event};
61
+ xt_call_with_this(fn, signal, 1, args);
62
+ return;
63
+ }
64
+ if (XT_IS_OBJECT(fn)) {
65
+ xt_value handler = xt_object_get_cstr(fn, "handleEvent");
66
+ if (XT_IS_FUNCTION(handler)) {
67
+ xt_value args[1] = {event};
68
+ xt_call_with_this(handler, fn, 1, args);
69
+ }
70
+ }
71
+ }
72
+
73
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
74
+
75
+ METHOD(signal_aborted_get) {
76
+ (void)env;
77
+ (void)argc;
78
+ (void)argv;
79
+ return state_aborted(state_of(self)) ? XT_TRUE : XT_FALSE;
80
+ }
81
+
82
+ METHOD(signal_reason_get) {
83
+ (void)env;
84
+ (void)argc;
85
+ (void)argv;
86
+ xt_value state = state_of(self);
87
+ if (!XT_IS_OBJECT(state)) return XT_UNDEFINED;
88
+ return xt_object_get_cstr(state, "reason");
89
+ }
90
+
91
+ METHOD(signal_onabort_get) {
92
+ (void)env;
93
+ (void)argc;
94
+ (void)argv;
95
+ return xt_object_get_cstr(self, "__xt_gui_onabort");
96
+ }
97
+
98
+ METHOD(signal_onabort_set) {
99
+ (void)env;
100
+ if (argc >= 1) xt_object_set(self, xt_string_from_cstr("__xt_gui_onabort"), xt_arg(argc, argv, 0));
101
+ return XT_UNDEFINED;
102
+ }
103
+
104
+ /** `signal.addEventListener("abort", fn, options?)` — only 'abort' exists. */
105
+ METHOD(signal_add_listener) {
106
+ (void)env;
107
+ xt_value state = state_of(self);
108
+ if (!XT_IS_OBJECT(state) || argc < 2) return XT_UNDEFINED;
109
+ std::string type = to_string_value(xt_arg(argc, argv, 0));
110
+ if (type != "abort") return XT_UNDEFINED;
111
+ xt_value fn = xt_arg(argc, argv, 1);
112
+ if (!XT_IS_FUNCTION(fn) && !XT_IS_OBJECT(fn)) return XT_UNDEFINED;
113
+ XtGuiListenerOptions options = xt_gui_listener_options(xt_arg(argc, argv, 2));
114
+ xt_value entry = xt_object_new();
115
+ xt_object_set(entry, xt_string_from_cstr("fn"), fn);
116
+ xt_object_set(entry, xt_string_from_cstr("once"), options.once ? XT_TRUE : XT_FALSE);
117
+ xt_array_push(xt_object_get_cstr(state, "listeners"), entry);
118
+ return XT_UNDEFINED;
119
+ }
120
+
121
+ METHOD(signal_remove_listener) {
122
+ (void)env;
123
+ xt_value state = state_of(self);
124
+ if (!XT_IS_OBJECT(state) || argc < 2) return XT_UNDEFINED;
125
+ xt_value listeners = xt_object_get_cstr(state, "listeners");
126
+ if (!XT_IS_ARRAY(listeners)) return XT_UNDEFINED;
127
+ xt_value fn = xt_arg(argc, argv, 1);
128
+ xt_value kept = xt_array_new(0, nullptr);
129
+ int32_t count = (int32_t)xt_to_number(xt_array_length(listeners));
130
+ for (int32_t i = 0; i < count; i++) {
131
+ xt_value entry = xt_array_get(listeners, xt_number((double)i));
132
+ if (!xt_truthy(xt_seq(xt_object_get_cstr(entry, "fn"), fn))) xt_array_push(kept, entry);
133
+ }
134
+ xt_object_set(state, xt_string_from_cstr("listeners"), kept);
135
+ return XT_UNDEFINED;
136
+ }
137
+
138
+ /** `signal.throwIfAborted()`: raise the abort reason (an `Error` by default). */
139
+ METHOD(signal_throw_if_aborted) {
140
+ (void)env;
141
+ (void)argc;
142
+ (void)argv;
143
+ xt_value state = state_of(self);
144
+ if (!state_aborted(state)) return XT_UNDEFINED;
145
+ xt_value reason = xt_object_get_cstr(state, "reason");
146
+ if (XT_IS_UNDEFINED(reason) || reason == XT_NULL) {
147
+ reason = xt_error_ctor_by_name(xt_string_from_cstr("Error"), 0, nullptr);
148
+ }
149
+ xt_throw(reason);
150
+ return XT_UNDEFINED;
151
+ }
152
+
153
+ METHOD(controller_signal_get) {
154
+ (void)env;
155
+ (void)argc;
156
+ (void)argv;
157
+ xt_value signal = signal_of(self);
158
+ if (XT_IS_OBJECT(signal)) return signal;
159
+ xt_value state = state_of(self);
160
+ if (!XT_IS_OBJECT(state)) {
161
+ state = new_state();
162
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_abort_state"), state);
163
+ }
164
+ signal = abort_signal_from_state(state, self);
165
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_abort_signal"), signal);
166
+ return signal;
167
+ }
168
+
169
+ /** `controller.abort(reason)`: flip the state and dispatch 'abort' once. */
170
+ METHOD(controller_abort) {
171
+ (void)env;
172
+ xt_value state = state_of(self);
173
+ if (!XT_IS_OBJECT(state) || state_aborted(state)) return XT_UNDEFINED;
174
+ xt_value reason = argc >= 1 ? xt_arg(argc, argv, 0) : XT_UNDEFINED;
175
+ if (XT_IS_UNDEFINED(reason)) {
176
+ xt_value message = xt_string_from_cstr("This operation was aborted");
177
+ reason = xt_error_ctor_by_name(xt_string_from_cstr("Error"), 1, &message);
178
+ }
179
+ xt_object_set(state, xt_string_from_cstr("aborted"), XT_TRUE);
180
+ xt_object_set(state, xt_string_from_cstr("reason"), reason);
181
+ xt_value signal = signal_of(self);
182
+ if (!XT_IS_OBJECT(signal)) signal = controller_signal_get(self, XT_UNDEFINED, 0, nullptr);
183
+ xt_value onabort = xt_object_get_cstr(signal, "__xt_gui_onabort");
184
+ if (XT_IS_FUNCTION(onabort)) call_abort_listener(onabort, signal);
185
+ xt_value listeners = xt_object_get_cstr(state, "listeners");
186
+ int32_t count = XT_IS_ARRAY(listeners) ? (int32_t)xt_to_number(xt_array_length(listeners)) : 0;
187
+ xt_value snapshot = xt_array_new(0, nullptr);
188
+ for (int32_t i = 0; i < count; i++) {
189
+ xt_array_push(snapshot, xt_array_get(listeners, xt_number((double)i)));
190
+ }
191
+ xt_object_set(state, xt_string_from_cstr("listeners"), xt_array_new(0, nullptr));
192
+ for (int32_t i = 0; i < count; i++) {
193
+ xt_value entry = xt_array_get(snapshot, xt_number((double)i));
194
+ call_abort_listener(xt_object_get_cstr(entry, "fn"), signal);
195
+ }
196
+ return XT_UNDEFINED;
197
+ }
198
+
199
+ #undef METHOD
200
+
201
+ } // namespace
202
+
203
+ xt_value abort_signal_proto() {
204
+ static xt_value proto = XT_UNDEFINED;
205
+ if (XT_IS_OBJECT(proto)) return proto;
206
+ proto = xt_object_new();
207
+ xt_gc_add_root(&proto);
208
+ xt_object_define_getter(proto, xt_string_from_cstr("aborted"),
209
+ xt_closure_new((void *)signal_aborted_get, 0, nullptr));
210
+ xt_object_define_getter(proto, xt_string_from_cstr("reason"),
211
+ xt_closure_new((void *)signal_reason_get, 0, nullptr));
212
+ xt_object_define_getter(proto, xt_string_from_cstr("onabort"),
213
+ xt_closure_new((void *)signal_onabort_get, 0, nullptr));
214
+ xt_object_define_setter(proto, xt_string_from_cstr("onabort"),
215
+ xt_closure_new((void *)signal_onabort_set, 0, nullptr));
216
+ xt_object_set(proto, xt_string_from_cstr("addEventListener"),
217
+ xt_closure_new((void *)signal_add_listener, 0, nullptr));
218
+ xt_object_set(proto, xt_string_from_cstr("removeEventListener"),
219
+ xt_closure_new((void *)signal_remove_listener, 0, nullptr));
220
+ xt_object_set(proto, xt_string_from_cstr("throwIfAborted"),
221
+ xt_closure_new((void *)signal_throw_if_aborted, 0, nullptr));
222
+ return proto;
223
+ }
224
+
225
+ xt_value abort_controller_proto() {
226
+ static xt_value proto = XT_UNDEFINED;
227
+ if (XT_IS_OBJECT(proto)) return proto;
228
+ proto = xt_object_new();
229
+ xt_gc_add_root(&proto);
230
+ xt_object_define_getter(proto, xt_string_from_cstr("signal"),
231
+ xt_closure_new((void *)controller_signal_get, 0, nullptr));
232
+ xt_object_set(proto, xt_string_from_cstr("abort"),
233
+ xt_closure_new((void *)controller_abort, 0, nullptr));
234
+ return proto;
235
+ }
236
+
237
+ xt_value abort_controller_new() {
238
+ xt_value controller = xt_object_new_with_proto(abort_controller_proto());
239
+ xt_object_set(controller, xt_string_from_cstr("__xt_gui_abort_state"), new_state());
240
+ return controller;
241
+ }
242
+
243
+ xt_value abort_signal_new(bool aborted) {
244
+ xt_value state = new_state();
245
+ if (aborted) xt_object_set(state, xt_string_from_cstr("aborted"), XT_TRUE);
246
+ return abort_signal_from_state(state, XT_UNDEFINED);
247
+ }
248
+
249
+ } // namespace domapi
@@ -0,0 +1,76 @@
1
+ /*
2
+ * xbintsc GUI engine — `window.confirm` / `window.prompt`.
3
+ *
4
+ * A compiled page has no browser chrome, so a blocking dialog is opt-in: with
5
+ * `XT_GUI_DIALOGS=1` and a live window the engine shows a native SDL message
6
+ * box. Otherwise (and always for `prompt`, which would need a text input) the
7
+ * message is written to stderr and the documented default is returned — the
8
+ * arguments are never silently dropped. `confirm` answers false and `prompt`
9
+ * answers `defaultValue` (or "") when no dialog is shown.
10
+ */
11
+
12
+ #include "gui_engine.h"
13
+
14
+ #include <stdio.h>
15
+ #include <string>
16
+
17
+ namespace {
18
+
19
+ std::string message_of(int32_t argc, xt_value *argv) {
20
+ if (argc < 1) return std::string();
21
+ const char *data = xt_string_data(xt_to_string(xt_arg(argc, argv, 0)));
22
+ return data != nullptr ? data : "";
23
+ }
24
+
25
+ bool dialogs_enabled() {
26
+ const char *value = SDL_getenv("XT_GUI_DIALOGS");
27
+ return value != nullptr && value[0] == '1';
28
+ }
29
+
30
+ bool show_message(XtGuiWindow *win, const std::string &message, bool with_cancel) {
31
+ if (win == nullptr || win->window == nullptr) return false;
32
+ SDL_MessageBoxButtonData buttons[2];
33
+ SDL_zero(buttons);
34
+ int count = 1;
35
+ buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
36
+ buttons[0].buttonID = 1;
37
+ buttons[0].text = "OK";
38
+ if (with_cancel) {
39
+ buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
40
+ buttons[1].buttonID = 0;
41
+ buttons[1].text = "Cancel";
42
+ count = 2;
43
+ }
44
+ SDL_MessageBoxData data;
45
+ SDL_zero(data);
46
+ data.flags = SDL_MESSAGEBOX_INFORMATION;
47
+ data.window = win->window;
48
+ data.title = "xbintsc";
49
+ data.message = message.c_str();
50
+ data.numbuttons = count;
51
+ data.buttons = buttons;
52
+ int pressed = -1;
53
+ if (!SDL_ShowMessageBox(&data, &pressed)) return false;
54
+ return pressed == 1;
55
+ }
56
+
57
+ } // namespace
58
+
59
+ xt_value xt_browser_confirm(XtGuiWindow *win, int32_t argc, xt_value *argv) {
60
+ std::string message = message_of(argc, argv);
61
+ if (dialogs_enabled() && show_message(win, message, true)) return XT_TRUE;
62
+ fprintf(stderr, "confirm: %s\n", message.c_str());
63
+ return XT_FALSE;
64
+ }
65
+
66
+ xt_value xt_browser_prompt(XtGuiWindow *win, int32_t argc, xt_value *argv) {
67
+ std::string message = message_of(argc, argv);
68
+ std::string fallback;
69
+ if (argc >= 2) {
70
+ const char *data = xt_string_data(xt_to_string(xt_arg(argc, argv, 1)));
71
+ if (data != nullptr) fallback = data;
72
+ }
73
+ if (dialogs_enabled()) show_message(win, message, false);
74
+ fprintf(stderr, "prompt: %s\n", message.c_str());
75
+ return xt_string_from_cstr(fallback.c_str());
76
+ }
@@ -0,0 +1,176 @@
1
+ /*
2
+ * xbintsc GUI engine — live `matchMedia` lists.
3
+ *
4
+ * `window.matchMedia(query)` returns one cached `MediaQueryList` per query. The
5
+ * engine re-evaluates every live list on a window resize (the same evaluator
6
+ * `@media` rules use) and dispatches a `change` event when the match flips, so
7
+ * a page can react to a viewport change instead of polling `matches`.
8
+ */
9
+
10
+ #include "gui_engine.h"
11
+
12
+
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ #include "css.h"
17
+
18
+ namespace {
19
+
20
+ std::string string_value(xt_value value) {
21
+ const char *data = xt_string_data(xt_to_string(value));
22
+ return data != nullptr ? data : "";
23
+ }
24
+
25
+ xt_value listeners_of(xt_value self, bool create) {
26
+ xt_value listeners = xt_object_get_cstr(self, "__xt_gui_media_listeners");
27
+ if (XT_IS_ARRAY(listeners) || !create) return listeners;
28
+ listeners = xt_array_new(0, nullptr);
29
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_listeners"), listeners);
30
+ return listeners;
31
+ }
32
+
33
+ void add_listener(xt_value self, xt_value fn, bool once, xt_value signal) {
34
+ if (!XT_IS_FUNCTION(fn)) return;
35
+ xt_value entry = xt_object_new();
36
+ xt_object_set(entry, xt_string_from_cstr("fn"), fn);
37
+ xt_object_set(entry, xt_string_from_cstr("once"), once ? XT_TRUE : XT_FALSE);
38
+ xt_object_set(entry, xt_string_from_cstr("signal"), signal);
39
+ xt_array_push(listeners_of(self, true), entry);
40
+ }
41
+
42
+ void remove_listener(xt_value self, xt_value fn) {
43
+ xt_value listeners = listeners_of(self, false);
44
+ if (!XT_IS_ARRAY(listeners)) return;
45
+ xt_value kept = xt_array_new(0, nullptr);
46
+ int32_t count = (int32_t)xt_to_number(xt_array_length(listeners));
47
+ for (int32_t i = 0; i < count; i++) {
48
+ xt_value entry = xt_array_get(listeners, xt_number((double)i));
49
+ if (!xt_truthy(xt_seq(xt_object_get_cstr(entry, "fn"), fn))) xt_array_push(kept, entry);
50
+ }
51
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_listeners"), kept);
52
+ }
53
+
54
+ void call_change_listener(xt_value fn, xt_value list, xt_value event) {
55
+ if (!XT_IS_FUNCTION(fn)) return;
56
+ xt_value args[1] = {event};
57
+ xt_call_with_this(fn, list, 1, args);
58
+ }
59
+
60
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
61
+
62
+ METHOD(media_add_event_listener) {
63
+ (void)env;
64
+ if (argc < 2 || string_value(xt_arg(argc, argv, 0)) != "change") return XT_UNDEFINED;
65
+ bool once = false;
66
+ xt_value options = xt_arg(argc, argv, 2);
67
+ if (XT_IS_OBJECT(options)) once = xt_truthy(xt_object_get_cstr(options, "once")) != 0;
68
+ add_listener(self, xt_arg(argc, argv, 1), once, XT_UNDEFINED);
69
+ return XT_UNDEFINED;
70
+ }
71
+
72
+ METHOD(media_remove_event_listener) {
73
+ (void)env;
74
+ if (argc < 2 || string_value(xt_arg(argc, argv, 0)) != "change") return XT_UNDEFINED;
75
+ remove_listener(self, xt_arg(argc, argv, 1));
76
+ return XT_UNDEFINED;
77
+ }
78
+
79
+ /** Legacy `addListener(fn)` / `removeListener(fn)`. */
80
+ METHOD(media_add_listener) {
81
+ (void)env;
82
+ if (argc >= 1) add_listener(self, xt_arg(argc, argv, 0), false, XT_UNDEFINED);
83
+ return XT_UNDEFINED;
84
+ }
85
+
86
+ METHOD(media_remove_listener) {
87
+ (void)env;
88
+ if (argc >= 1) remove_listener(self, xt_arg(argc, argv, 0));
89
+ return XT_UNDEFINED;
90
+ }
91
+
92
+ METHOD(media_onchange_get) {
93
+ (void)env;
94
+ (void)argc;
95
+ (void)argv;
96
+ return xt_object_get_cstr(self, "__xt_gui_media_onchange");
97
+ }
98
+
99
+ METHOD(media_onchange_set) {
100
+ (void)env;
101
+ if (argc >= 1) {
102
+ xt_object_set(self, xt_string_from_cstr("__xt_gui_media_onchange"), xt_arg(argc, argv, 0));
103
+ }
104
+ return XT_UNDEFINED;
105
+ }
106
+
107
+ #undef METHOD
108
+
109
+ xt_value build_list(const std::string &query, bool matches) {
110
+ xt_value list = xt_object_new();
111
+ xt_object_set(list, xt_string_from_cstr("media"), xt_string_from_cstr(query.c_str()));
112
+ xt_object_set(list, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
113
+ xt_object_set(list, xt_string_from_cstr("__xt_gui_media_query"),
114
+ xt_string_from_cstr(query.c_str()));
115
+ xt_object_set(list, xt_string_from_cstr("addEventListener"),
116
+ xt_closure_new((void *)media_add_event_listener, 0, nullptr));
117
+ xt_object_set(list, xt_string_from_cstr("removeEventListener"),
118
+ xt_closure_new((void *)media_remove_event_listener, 0, nullptr));
119
+ xt_object_set(list, xt_string_from_cstr("addListener"),
120
+ xt_closure_new((void *)media_add_listener, 0, nullptr));
121
+ xt_object_set(list, xt_string_from_cstr("removeListener"),
122
+ xt_closure_new((void *)media_remove_listener, 0, nullptr));
123
+ xt_object_define_getter(list, xt_string_from_cstr("onchange"),
124
+ xt_closure_new((void *)media_onchange_get, 0, nullptr));
125
+ xt_object_define_setter(list, xt_string_from_cstr("onchange"),
126
+ xt_closure_new((void *)media_onchange_set, 0, nullptr));
127
+ return list;
128
+ }
129
+
130
+ } // namespace
131
+
132
+ xt_value xt_browser_media_list(xt_value window, const std::string &query) {
133
+ XtGuiWindow *win = xt_gui_window_from_this(window);
134
+ if (win == nullptr) return build_list(query, false);
135
+ auto cached = win->media_lists_by_query.find(query);
136
+ if (cached != win->media_lists_by_query.end()) return cached->second;
137
+ float width = 0.0f;
138
+ float height = 0.0f;
139
+ xt_gui_window_viewport(win, &width, &height);
140
+ xt_value list = build_list(query, xtgui::xt_css_media_matches(query, width, height));
141
+ win->media_lists.push_back(list);
142
+ win->media_lists_by_query[query] = list;
143
+ return list;
144
+ }
145
+
146
+ void xt_gui_media_lists_notify(XtGuiWindow *win) {
147
+ if (win == nullptr) return;
148
+ float width = 0.0f;
149
+ float height = 0.0f;
150
+ xt_gui_window_viewport(win, &width, &height);
151
+ for (xt_value list : win->media_lists) {
152
+ if (!XT_IS_OBJECT(list)) continue;
153
+ std::string query = string_value(xt_object_get_cstr(list, "__xt_gui_media_query"));
154
+ bool matches = xtgui::xt_css_media_matches(query, width, height);
155
+ bool was = xt_truthy(xt_object_get_cstr(list, "matches"));
156
+ xt_object_set(list, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
157
+ if (was == matches) continue;
158
+ xt_value event = xt_object_new();
159
+ xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr("change"));
160
+ xt_object_set(event, xt_string_from_cstr("media"), xt_string_from_cstr(query.c_str()));
161
+ xt_object_set(event, xt_string_from_cstr("matches"), matches ? XT_TRUE : XT_FALSE);
162
+ xt_value onchange = xt_object_get_cstr(list, "__xt_gui_media_onchange");
163
+ call_change_listener(onchange, list, event);
164
+ xt_value listeners = listeners_of(list, false);
165
+ if (!XT_IS_ARRAY(listeners)) continue;
166
+ xt_value snapshot = xt_array_new(0, nullptr);
167
+ int32_t count = (int32_t)xt_to_number(xt_array_length(listeners));
168
+ for (int32_t i = 0; i < count; i++) {
169
+ xt_array_push(snapshot, xt_array_get(listeners, xt_number((double)i)));
170
+ }
171
+ for (int32_t i = 0; i < count; i++) {
172
+ xt_value entry = xt_array_get(snapshot, xt_number((double)i));
173
+ call_change_listener(xt_object_get_cstr(entry, "fn"), list, event);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,130 @@
1
+ /*
2
+ * xbintsc GUI engine — window navigation and focus.
3
+ *
4
+ * `scrollTo` / `scrollBy` move the page viewport (painting and hit testing
5
+ * subtract the offset), `scrollX`/`scrollY` (and the `page*Offset` aliases)
6
+ * report it, and `focus` / `blur` raise the OS window and toggle
7
+ * `document.hasFocus()` plus the corresponding window events.
8
+ */
9
+
10
+ #include "gui_engine.h"
11
+
12
+ #include <cmath>
13
+
14
+ namespace {
15
+
16
+ xt_value scroll_axis(xt_value options, const char *name) {
17
+ if (!XT_IS_OBJECT(options)) return XT_UNDEFINED;
18
+ return xt_object_get_cstr(options, name);
19
+ }
20
+
21
+ double coordinate(xt_value value, double fallback) {
22
+ return XT_IS_NUMBER(value) ? xt_to_number(value) : fallback;
23
+ }
24
+
25
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
26
+
27
+ /** `scrollTo(x, y)` / `scrollTo({ left, top })`. */
28
+ METHOD(win_scroll_to) {
29
+ (void)env;
30
+ XtGuiWindow *win = xt_gui_window_from_this(self);
31
+ if (win == nullptr || argc < 1) return XT_UNDEFINED;
32
+ xt_value options = xt_arg(argc, argv, 0);
33
+ double x = 0;
34
+ double y = 0;
35
+ if (XT_IS_OBJECT(options) && !XT_IS_ARRAY(options)) {
36
+ x = coordinate(scroll_axis(options, "left"), coordinate(scroll_axis(options, "x"), 0));
37
+ y = coordinate(scroll_axis(options, "top"), coordinate(scroll_axis(options, "y"), 0));
38
+ } else {
39
+ x = coordinate(options, 0);
40
+ if (argc >= 2) y = coordinate(xt_arg(argc, argv, 1), 0);
41
+ }
42
+ xt_gui_scroll_to(win, (float)x, (float)y);
43
+ return XT_UNDEFINED;
44
+ }
45
+
46
+ METHOD(win_scroll_by) {
47
+ (void)env;
48
+ XtGuiWindow *win = xt_gui_window_from_this(self);
49
+ if (win == nullptr || argc < 1) return XT_UNDEFINED;
50
+ xt_value options = xt_arg(argc, argv, 0);
51
+ double dx = 0;
52
+ double dy = 0;
53
+ if (XT_IS_OBJECT(options) && !XT_IS_ARRAY(options)) {
54
+ dx = coordinate(scroll_axis(options, "left"), coordinate(scroll_axis(options, "x"), 0));
55
+ dy = coordinate(scroll_axis(options, "top"), coordinate(scroll_axis(options, "y"), 0));
56
+ } else {
57
+ dx = coordinate(options, 0);
58
+ if (argc >= 2) dy = coordinate(xt_arg(argc, argv, 1), 0);
59
+ }
60
+ xt_gui_scroll_to(win, win->scroll_x + (float)dx, win->scroll_y + (float)dy);
61
+ return XT_UNDEFINED;
62
+ }
63
+
64
+ METHOD(win_scroll_x_get) {
65
+ (void)env;
66
+ (void)argc;
67
+ (void)argv;
68
+ XtGuiWindow *win = xt_gui_window_from_this(self);
69
+ return xt_number(win != nullptr ? (double)win->scroll_x : 0.0);
70
+ }
71
+
72
+ METHOD(win_scroll_y_get) {
73
+ (void)env;
74
+ (void)argc;
75
+ (void)argv;
76
+ XtGuiWindow *win = xt_gui_window_from_this(self);
77
+ return xt_number(win != nullptr ? (double)win->scroll_y : 0.0);
78
+ }
79
+
80
+ METHOD(win_focus) {
81
+ (void)env;
82
+ (void)argc;
83
+ (void)argv;
84
+ XtGuiWindow *win = xt_gui_window_from_this(self);
85
+ if (win == nullptr || !win->open) return XT_UNDEFINED;
86
+ if (win->window != nullptr) SDL_RaiseWindow(win->window);
87
+ if (!win->focused) {
88
+ win->focused = 1;
89
+ xt_gui_emit(win, "focus");
90
+ }
91
+ return XT_UNDEFINED;
92
+ }
93
+
94
+ METHOD(win_blur) {
95
+ (void)env;
96
+ (void)argc;
97
+ (void)argv;
98
+ XtGuiWindow *win = xt_gui_window_from_this(self);
99
+ if (win == nullptr || !win->open) return XT_UNDEFINED;
100
+ if (win->focused) {
101
+ win->focused = 0;
102
+ xt_gui_emit(win, "blur");
103
+ }
104
+ return XT_UNDEFINED;
105
+ }
106
+
107
+ #undef METHOD
108
+
109
+ } // namespace
110
+
111
+ void xt_browser_install_window_navigation(xt_value proto) {
112
+ xt_object_set(proto, xt_string_from_cstr("scrollTo"),
113
+ xt_closure_new((void *)win_scroll_to, 0, nullptr));
114
+ xt_object_set(proto, xt_string_from_cstr("scroll"),
115
+ xt_closure_new((void *)win_scroll_to, 0, nullptr));
116
+ xt_object_set(proto, xt_string_from_cstr("scrollBy"),
117
+ xt_closure_new((void *)win_scroll_by, 0, nullptr));
118
+ xt_object_set(proto, xt_string_from_cstr("focus"),
119
+ xt_closure_new((void *)win_focus, 0, nullptr));
120
+ xt_object_set(proto, xt_string_from_cstr("blur"),
121
+ xt_closure_new((void *)win_blur, 0, nullptr));
122
+ xt_object_define_getter(proto, xt_string_from_cstr("scrollX"),
123
+ xt_closure_new((void *)win_scroll_x_get, 0, nullptr));
124
+ xt_object_define_getter(proto, xt_string_from_cstr("scrollY"),
125
+ xt_closure_new((void *)win_scroll_y_get, 0, nullptr));
126
+ xt_object_define_getter(proto, xt_string_from_cstr("pageXOffset"),
127
+ xt_closure_new((void *)win_scroll_x_get, 0, nullptr));
128
+ xt_object_define_getter(proto, xt_string_from_cstr("pageYOffset"),
129
+ xt_closure_new((void *)win_scroll_y_get, 0, nullptr));
130
+ }