xbintsc 0.3.69 → 0.3.81

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 (115) hide show
  1. package/README.md +57 -4
  2. package/README.zh-CN.md +51 -2
  3. package/dist/src/driver/compiler.js +6 -1
  4. package/dist/src/driver/compiler.js.map +1 -1
  5. package/dist/src/extensions/gui/index.js +10 -2
  6. package/dist/src/extensions/gui/index.js.map +1 -1
  7. package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
  8. package/dist/tests/e2e/gui-advanced.test.js +261 -0
  9. package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
  10. package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
  11. package/dist/tests/e2e/gui-canvas.test.js +233 -0
  12. package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
  13. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  14. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  15. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  16. package/dist/tests/e2e/gui-example.test.d.ts +1 -1
  17. package/dist/tests/e2e/gui-example.test.js +2 -2
  18. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  19. package/dist/tests/e2e/gui-helpers.js +5 -0
  20. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  21. package/dist/tests/e2e/gui-layout.test.js +73 -0
  22. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  23. package/dist/tests/e2e/gui-media.test.d.ts +8 -0
  24. package/dist/tests/e2e/gui-media.test.js +119 -0
  25. package/dist/tests/e2e/gui-media.test.js.map +1 -0
  26. package/dist/tests/e2e/gui-paint.test.js +40 -0
  27. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  28. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  29. package/dist/tests/e2e/gui-transform.test.js +198 -0
  30. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  31. package/doc/ai/cli.md +5 -0
  32. package/doc/ai/extensions.md +20 -3
  33. package/doc/ai/zh-CN/extensions.md +14 -3
  34. package/doc/gui/animation.md +97 -0
  35. package/doc/gui/api.md +92 -0
  36. package/doc/gui/build.md +91 -0
  37. package/doc/gui/canvas.md +98 -0
  38. package/doc/gui/dom.md +128 -0
  39. package/doc/gui/input.md +25 -0
  40. package/doc/gui/known-issues.md +78 -0
  41. package/doc/gui/layout.md +108 -0
  42. package/doc/gui/media.md +87 -0
  43. package/doc/gui/rendering.md +113 -0
  44. package/doc/gui/runtime.md +88 -0
  45. package/doc/gui-scripts.md +7 -3
  46. package/doc/gui.md +109 -612
  47. package/doc/implemented.md +1 -0
  48. package/doc/zh-CN/gui/animation.md +93 -0
  49. package/doc/zh-CN/gui/api.md +88 -0
  50. package/doc/zh-CN/gui/build.md +84 -0
  51. package/doc/zh-CN/gui/canvas.md +80 -0
  52. package/doc/zh-CN/gui/dom.md +119 -0
  53. package/doc/zh-CN/gui/input.md +23 -0
  54. package/doc/zh-CN/gui/known-issues.md +60 -0
  55. package/doc/zh-CN/gui/layout.md +91 -0
  56. package/doc/zh-CN/gui/media.md +71 -0
  57. package/doc/zh-CN/gui/rendering.md +92 -0
  58. package/doc/zh-CN/gui/runtime.md +76 -0
  59. package/doc/zh-CN/gui-scripts.md +6 -2
  60. package/doc/zh-CN/gui.md +89 -532
  61. package/doc/zh-CN/implemented.md +1 -0
  62. package/llms.txt +15 -2
  63. package/package.json +1 -1
  64. package/runtime/ext_gui/canvas.cpp +580 -0
  65. package/runtime/ext_gui/canvas.h +170 -0
  66. package/runtime/ext_gui/canvas_internal.h +59 -0
  67. package/runtime/ext_gui/canvas_raster.cpp +254 -0
  68. package/runtime/ext_gui/document.cpp +45 -4
  69. package/runtime/ext_gui/document.h +15 -1
  70. package/runtime/ext_gui/dom_api.cpp +3 -0
  71. package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
  72. package/runtime/ext_gui/dom_api_internal.h +73 -0
  73. package/runtime/ext_gui/dom_api_media.cpp +274 -0
  74. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  75. package/runtime/ext_gui/dom_api_proto.cpp +80 -0
  76. package/runtime/ext_gui/gui.cpp +6 -0
  77. package/runtime/ext_gui/gui_canvas.cpp +95 -0
  78. package/runtime/ext_gui/gui_engine.h +49 -0
  79. package/runtime/ext_gui/gui_events.cpp +21 -5
  80. package/runtime/ext_gui/gui_media.cpp +193 -0
  81. package/runtime/ext_gui/image.cpp +31 -12
  82. package/runtime/ext_gui/image.h +20 -7
  83. package/runtime/ext_gui/layout.cpp +130 -6
  84. package/runtime/ext_gui/layout.h +54 -1
  85. package/runtime/ext_gui/layout_flex.cpp +263 -99
  86. package/runtime/ext_gui/layout_flow.cpp +85 -9
  87. package/runtime/ext_gui/layout_flow_internal.h +84 -5
  88. package/runtime/ext_gui/layout_inline.cpp +54 -37
  89. package/runtime/ext_gui/media.cpp +514 -0
  90. package/runtime/ext_gui/media.h +104 -0
  91. package/runtime/ext_gui/nanosvg.h +3278 -0
  92. package/runtime/ext_gui/nanosvgrast.h +1472 -0
  93. package/runtime/ext_gui/paint.cpp +156 -38
  94. package/runtime/ext_gui/paint.h +42 -6
  95. package/runtime/ext_gui/renderer.cpp +117 -12
  96. package/runtime/ext_gui/renderer.h +49 -9
  97. package/runtime/ext_gui/renderer_image.cpp +205 -78
  98. package/runtime/ext_gui/renderer_internal.h +12 -9
  99. package/runtime/ext_gui/renderer_text.cpp +46 -39
  100. package/runtime/ext_gui/style.h +21 -0
  101. package/runtime/ext_gui/style_print.cpp +17 -0
  102. package/runtime/ext_gui/style_ua.cpp +7 -0
  103. package/runtime/ext_gui/style_values.cpp +83 -1
  104. package/runtime/ext_gui/svg.cpp +182 -0
  105. package/runtime/ext_gui/svg.h +46 -0
  106. package/runtime/ext_gui/text.cpp +156 -25
  107. package/runtime/ext_gui/text.h +11 -2
  108. package/runtime/ext_gui/transform.cpp +412 -0
  109. package/runtime/ext_gui/transform.h +112 -0
  110. package/runtime/ext_gui/window.cpp +10 -5
  111. package/scripts/build-gui.ts +148 -6
  112. package/scripts/check-file-length.ts +5 -1
  113. package/scripts/install-gui-deps.sh +28 -0
  114. package/src/driver/compiler.ts +6 -1
  115. package/src/extensions/gui/index.ts +10 -2
@@ -0,0 +1,274 @@
1
+ /*
2
+ * xbintsc GUI engine — `<video>` / `<audio>` object API.
3
+ *
4
+ * These methods live on the shared node prototype (`g_node_proto`), so they are
5
+ * callable on any element; a non-media element simply resolves no decoder and
6
+ * the methods no-op / return the media defaults. The decoders themselves live
7
+ * on the owning window (`gui_media.cpp`); this unit is the thin bridge from a
8
+ * node handle to its `MediaPlayer`.
9
+ */
10
+
11
+ #include "dom_api_internal.h"
12
+
13
+ #include <string>
14
+
15
+ namespace domapi {
16
+
17
+ /** The decoder already open for `node`, without opening one (getters must not
18
+ * have the side effect of starting playback). */
19
+ static xtgui::MediaPlayer *openPlayer(XtGuiWindow *win, xtgui::Node *node) {
20
+ if (win == nullptr || node == nullptr) return nullptr;
21
+ auto found = win->media_players.find(node);
22
+ return found == win->media_players.end() ? nullptr : found->second.player.get();
23
+ }
24
+
25
+ /** Set/clear a boolean attribute (`loop`, `muted`, `autoplay`) so the value
26
+ * survives a later (re)open in `gui_media.cpp`. */
27
+ static void setBooleanAttribute(XtGuiWindow *win, xtgui::Node *node, const char *name, bool value) {
28
+ if (value) {
29
+ set_attribute_raw(node, name, "");
30
+ } else {
31
+ remove_attribute_raw(node, name);
32
+ }
33
+ mark_dirty(win);
34
+ }
35
+
36
+ #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
37
+
38
+ METHOD(node_play) {
39
+ (void)env;
40
+ (void)argc;
41
+ (void)argv;
42
+ XtGuiWindow *win = nullptr;
43
+ xtgui::Node *node = resolve_node(self, &win);
44
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
45
+ xtgui::MediaPlayer *player = xt_gui_media_player(win, node);
46
+ if (player != nullptr) player->play();
47
+ return XT_UNDEFINED;
48
+ }
49
+
50
+ METHOD(node_pause) {
51
+ (void)env;
52
+ (void)argc;
53
+ (void)argv;
54
+ XtGuiWindow *win = nullptr;
55
+ xtgui::Node *node = resolve_node(self, &win);
56
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
57
+ xtgui::MediaPlayer *player = openPlayer(win, node);
58
+ if (player != nullptr) player->pause();
59
+ return XT_UNDEFINED;
60
+ }
61
+
62
+ METHOD(node_load) {
63
+ (void)env;
64
+ (void)argc;
65
+ (void)argv;
66
+ XtGuiWindow *win = nullptr;
67
+ xtgui::Node *node = resolve_node(self, &win);
68
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
69
+ /* Re-open from the current source, discarding any existing decoder. */
70
+ xt_gui_media_forget(win, node);
71
+ xt_gui_media_player(win, node);
72
+ return XT_UNDEFINED;
73
+ }
74
+
75
+ METHOD(node_paused_get) {
76
+ (void)env;
77
+ (void)argc;
78
+ (void)argv;
79
+ XtGuiWindow *win = nullptr;
80
+ xtgui::Node *node = resolve_node(self, &win);
81
+ xtgui::MediaPlayer *player = openPlayer(win, node);
82
+ return (player == nullptr || player->paused()) ? XT_TRUE : XT_FALSE;
83
+ }
84
+
85
+ METHOD(node_ended_get) {
86
+ (void)env;
87
+ (void)argc;
88
+ (void)argv;
89
+ XtGuiWindow *win = nullptr;
90
+ xtgui::Node *node = resolve_node(self, &win);
91
+ xtgui::MediaPlayer *player = openPlayer(win, node);
92
+ return (player != nullptr && player->ended()) ? XT_TRUE : XT_FALSE;
93
+ }
94
+
95
+ METHOD(node_current_time_get) {
96
+ (void)env;
97
+ (void)argc;
98
+ (void)argv;
99
+ XtGuiWindow *win = nullptr;
100
+ xtgui::Node *node = resolve_node(self, &win);
101
+ xtgui::MediaPlayer *player = openPlayer(win, node);
102
+ return xt_number(player != nullptr ? player->current_time() : 0.0);
103
+ }
104
+
105
+ METHOD(node_current_time_set) {
106
+ (void)env;
107
+ XtGuiWindow *win = nullptr;
108
+ xtgui::Node *node = resolve_node(self, &win);
109
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
110
+ xtgui::MediaPlayer *player = openPlayer(win, node);
111
+ if (player != nullptr) player->seek(argc >= 1 ? xt_to_number(xt_arg(argc, argv, 0)) : 0.0);
112
+ return XT_UNDEFINED;
113
+ }
114
+
115
+ METHOD(node_duration_get) {
116
+ (void)env;
117
+ (void)argc;
118
+ (void)argv;
119
+ XtGuiWindow *win = nullptr;
120
+ xtgui::Node *node = resolve_node(self, &win);
121
+ xtgui::MediaPlayer *player = openPlayer(win, node);
122
+ return xt_number(player != nullptr ? player->duration() : 0.0);
123
+ }
124
+
125
+ METHOD(node_volume_get) {
126
+ (void)env;
127
+ (void)argc;
128
+ (void)argv;
129
+ XtGuiWindow *win = nullptr;
130
+ xtgui::Node *node = resolve_node(self, &win);
131
+ xtgui::MediaPlayer *player = openPlayer(win, node);
132
+ return xt_number(player != nullptr ? player->volume() : 1.0);
133
+ }
134
+
135
+ METHOD(node_volume_set) {
136
+ (void)env;
137
+ XtGuiWindow *win = nullptr;
138
+ xtgui::Node *node = resolve_node(self, &win);
139
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
140
+ double value = argc >= 1 ? xt_to_number(xt_arg(argc, argv, 0)) : 1.0;
141
+ if (value < 0.0) value = 0.0;
142
+ if (value > 1.0) value = 1.0;
143
+ xtgui::MediaPlayer *player = openPlayer(win, node);
144
+ if (player != nullptr) player->set_volume(value);
145
+ return XT_UNDEFINED;
146
+ }
147
+
148
+ METHOD(node_muted_get) {
149
+ (void)env;
150
+ (void)argc;
151
+ (void)argv;
152
+ XtGuiWindow *win = nullptr;
153
+ xtgui::Node *node = resolve_node(self, &win);
154
+ xtgui::MediaPlayer *player = openPlayer(win, node);
155
+ return (player != nullptr && player->muted()) ? XT_TRUE : XT_FALSE;
156
+ }
157
+
158
+ METHOD(node_muted_set) {
159
+ (void)env;
160
+ XtGuiWindow *win = nullptr;
161
+ xtgui::Node *node = resolve_node(self, &win);
162
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
163
+ bool value = argc >= 1 && value_truthy(xt_arg(argc, argv, 0));
164
+ setBooleanAttribute(win, node, "muted", value);
165
+ xtgui::MediaPlayer *player = openPlayer(win, node);
166
+ if (player != nullptr) player->set_muted(value);
167
+ return XT_UNDEFINED;
168
+ }
169
+
170
+ METHOD(node_loop_get) {
171
+ (void)env;
172
+ (void)argc;
173
+ (void)argv;
174
+ XtGuiWindow *win = nullptr;
175
+ xtgui::Node *node = resolve_node(self, &win);
176
+ xtgui::MediaPlayer *player = openPlayer(win, node);
177
+ return (player != nullptr && player->loop()) ? XT_TRUE : XT_FALSE;
178
+ }
179
+
180
+ METHOD(node_loop_set) {
181
+ (void)env;
182
+ XtGuiWindow *win = nullptr;
183
+ xtgui::Node *node = resolve_node(self, &win);
184
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
185
+ bool value = argc >= 1 && value_truthy(xt_arg(argc, argv, 0));
186
+ setBooleanAttribute(win, node, "loop", value);
187
+ xtgui::MediaPlayer *player = openPlayer(win, node);
188
+ if (player != nullptr) player->set_loop(value);
189
+ return XT_UNDEFINED;
190
+ }
191
+
192
+ METHOD(node_autoplay_get) {
193
+ (void)env;
194
+ (void)argc;
195
+ (void)argv;
196
+ XtGuiWindow *win = nullptr;
197
+ xtgui::Node *node = resolve_node(self, &win);
198
+ xtgui::MediaPlayer *player = openPlayer(win, node);
199
+ return (player != nullptr && player->autoplay()) ? XT_TRUE : XT_FALSE;
200
+ }
201
+
202
+ METHOD(node_autoplay_set) {
203
+ (void)env;
204
+ XtGuiWindow *win = nullptr;
205
+ xtgui::Node *node = resolve_node(self, &win);
206
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
207
+ bool value = argc >= 1 && value_truthy(xt_arg(argc, argv, 0));
208
+ setBooleanAttribute(win, node, "autoplay", value);
209
+ xtgui::MediaPlayer *player = openPlayer(win, node);
210
+ if (player != nullptr) player->set_autoplay(value);
211
+ return XT_UNDEFINED;
212
+ }
213
+
214
+ METHOD(node_video_width_get) {
215
+ (void)env;
216
+ (void)argc;
217
+ (void)argv;
218
+ XtGuiWindow *win = nullptr;
219
+ xtgui::Node *node = resolve_node(self, &win);
220
+ xtgui::MediaPlayer *player = openPlayer(win, node);
221
+ return xt_number(player != nullptr ? player->frame_width() : 0);
222
+ }
223
+
224
+ METHOD(node_video_height_get) {
225
+ (void)env;
226
+ (void)argc;
227
+ (void)argv;
228
+ XtGuiWindow *win = nullptr;
229
+ xtgui::Node *node = resolve_node(self, &win);
230
+ xtgui::MediaPlayer *player = openPlayer(win, node);
231
+ return xt_number(player != nullptr ? player->frame_height() : 0);
232
+ }
233
+
234
+ METHOD(node_ready_state_get) {
235
+ (void)env;
236
+ (void)argc;
237
+ (void)argv;
238
+ XtGuiWindow *win = nullptr;
239
+ xtgui::Node *node = resolve_node(self, &win);
240
+ xtgui::MediaPlayer *player = openPlayer(win, node);
241
+ /* HAVE_ENOUGH_DATA (4) once a player exists, HAVE_NOTHING (0) otherwise. */
242
+ return xt_number(player != nullptr ? 4 : 0);
243
+ }
244
+
245
+ METHOD(node_media_src_get) {
246
+ (void)env;
247
+ (void)argc;
248
+ (void)argv;
249
+ xtgui::Node *node = resolve_node(self, nullptr);
250
+ if (node == nullptr || !node->isElement()) return xt_string_from_cstr("");
251
+ const std::string *src = node->attr("src");
252
+ return xt_string_from_cstr(src != nullptr ? src->c_str() : "");
253
+ }
254
+
255
+ METHOD(node_media_src_set) {
256
+ (void)env;
257
+ XtGuiWindow *win = nullptr;
258
+ xtgui::Node *node = resolve_node(self, &win);
259
+ if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
260
+ std::string value = argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "";
261
+ /* Changing the source discards the decoder; the frame loop reopens it. */
262
+ xt_gui_media_forget(win, node);
263
+ if (value.empty()) {
264
+ remove_attribute_raw(node, "src");
265
+ mark_dirty(win);
266
+ } else {
267
+ set_attribute(win, node, "src", value);
268
+ }
269
+ return XT_UNDEFINED;
270
+ }
271
+
272
+ #undef METHOD
273
+
274
+ } // namespace domapi
@@ -217,11 +217,13 @@ METHOD(node_get_bounding_rect) {
217
217
  if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
218
218
  const xtgui::LayoutBox *box = win->document->boxOf(node);
219
219
  if (box == nullptr) return XT_UNDEFINED;
220
+ /* Like the DOM: the rectangle of the *transformed* border box. */
221
+ xtgui::LayoutRect rect = xtgui::xt_layout_transformed_bounds(box);
220
222
  xt_value object = xt_object_new();
221
- xt_object_set(object, xt_string_from_cstr("x"), xt_number(box->x));
222
- xt_object_set(object, xt_string_from_cstr("y"), xt_number(box->y));
223
- xt_object_set(object, xt_string_from_cstr("width"), xt_number(box->width));
224
- xt_object_set(object, xt_string_from_cstr("height"), xt_number(box->height));
223
+ xt_object_set(object, xt_string_from_cstr("x"), xt_number(rect.x));
224
+ xt_object_set(object, xt_string_from_cstr("y"), xt_number(rect.y));
225
+ xt_object_set(object, xt_string_from_cstr("width"), xt_number(rect.width));
226
+ xt_object_set(object, xt_string_from_cstr("height"), xt_number(rect.height));
225
227
  return object;
226
228
  }
227
229
 
@@ -46,12 +46,37 @@ xt_value node_proto() {
46
46
  define_getter(proto, "style", (void *)node_style_get);
47
47
  define_getter(proto, "offsetWidth", (void *)node_offset_width_get);
48
48
  define_getter(proto, "offsetHeight", (void *)node_offset_height_get);
49
+ /* Media element properties (`<video>`/`<audio>`; harmless on any element). */
50
+ define_getter(proto, "paused", (void *)node_paused_get);
51
+ define_getter(proto, "ended", (void *)node_ended_get);
52
+ define_getter(proto, "duration", (void *)node_duration_get);
53
+ define_getter(proto, "currentTime", (void *)node_current_time_get);
54
+ define_getter(proto, "volume", (void *)node_volume_get);
55
+ define_getter(proto, "muted", (void *)node_muted_get);
56
+ define_getter(proto, "loop", (void *)node_loop_get);
57
+ define_getter(proto, "autoplay", (void *)node_autoplay_get);
58
+ define_getter(proto, "videoWidth", (void *)node_video_width_get);
59
+ define_getter(proto, "videoHeight", (void *)node_video_height_get);
60
+ define_getter(proto, "readyState", (void *)node_ready_state_get);
61
+ define_getter(proto, "src", (void *)node_media_src_get);
62
+ /* `<canvas>` element properties (`width`/`height` also address the attribute
63
+ * on other elements). */
64
+ define_getter(proto, "width", (void *)node_width_get);
65
+ define_getter(proto, "height", (void *)node_height_get);
49
66
  /* Setters. */
50
67
  define_setter(proto, "id", (void *)node_id_set);
51
68
  define_setter(proto, "className", (void *)node_class_name_set);
52
69
  define_setter(proto, "textContent", (void *)node_text_content_set);
53
70
  define_setter(proto, "innerText", (void *)node_text_content_set);
54
71
  define_setter(proto, "innerHTML", (void *)node_inner_html_set);
72
+ define_setter(proto, "currentTime", (void *)node_current_time_set);
73
+ define_setter(proto, "volume", (void *)node_volume_set);
74
+ define_setter(proto, "muted", (void *)node_muted_set);
75
+ define_setter(proto, "loop", (void *)node_loop_set);
76
+ define_setter(proto, "autoplay", (void *)node_autoplay_set);
77
+ define_setter(proto, "src", (void *)node_media_src_set);
78
+ define_setter(proto, "width", (void *)node_width_set);
79
+ define_setter(proto, "height", (void *)node_height_set);
55
80
  /* Methods. */
56
81
  define_method(proto, "getAttribute", (void *)node_get_attribute);
57
82
  define_method(proto, "setAttribute", (void *)node_set_attribute);
@@ -74,6 +99,10 @@ xt_value node_proto() {
74
99
  define_method(proto, "addEventListener", (void *)node_add_event_listener);
75
100
  define_method(proto, "removeEventListener", (void *)node_remove_event_listener);
76
101
  define_method(proto, "dispatchEvent", (void *)node_dispatch_event);
102
+ define_method(proto, "play", (void *)node_play);
103
+ define_method(proto, "pause", (void *)node_pause);
104
+ define_method(proto, "load", (void *)node_load);
105
+ define_method(proto, "getContext", (void *)node_get_context);
77
106
  g_node_proto = proto;
78
107
  return proto;
79
108
  }
@@ -132,4 +161,55 @@ xt_value style_proto() {
132
161
  return proto;
133
162
  }
134
163
 
164
+ xt_value canvas_proto() {
165
+ xt_gc_add_root(&g_canvas_proto);
166
+ if (XT_IS_OBJECT(g_canvas_proto)) return g_canvas_proto;
167
+ xt_value proto = xt_object_new();
168
+ /* Properties. */
169
+ define_getter(proto, "canvas", (void *)canvas_get_canvas);
170
+ define_getter(proto, "fillStyle", (void *)canvas_fill_style_get);
171
+ define_setter(proto, "fillStyle", (void *)canvas_fill_style_set);
172
+ define_getter(proto, "strokeStyle", (void *)canvas_stroke_style_get);
173
+ define_setter(proto, "strokeStyle", (void *)canvas_stroke_style_set);
174
+ define_getter(proto, "lineWidth", (void *)canvas_line_width_get);
175
+ define_setter(proto, "lineWidth", (void *)canvas_line_width_set);
176
+ define_getter(proto, "globalAlpha", (void *)canvas_global_alpha_get);
177
+ define_setter(proto, "globalAlpha", (void *)canvas_global_alpha_set);
178
+ define_getter(proto, "font", (void *)canvas_font_get);
179
+ define_setter(proto, "font", (void *)canvas_font_set);
180
+ define_getter(proto, "textAlign", (void *)canvas_text_align_get);
181
+ define_setter(proto, "textAlign", (void *)canvas_text_align_set);
182
+ define_getter(proto, "textBaseline", (void *)canvas_text_baseline_get);
183
+ define_setter(proto, "textBaseline", (void *)canvas_text_baseline_set);
184
+ /* Methods. */
185
+ define_method(proto, "save", (void *)canvas_save);
186
+ define_method(proto, "restore", (void *)canvas_restore);
187
+ define_method(proto, "translate", (void *)canvas_translate);
188
+ define_method(proto, "rotate", (void *)canvas_rotate);
189
+ define_method(proto, "scale", (void *)canvas_scale);
190
+ define_method(proto, "transform", (void *)canvas_transform);
191
+ define_method(proto, "setTransform", (void *)canvas_set_transform);
192
+ define_method(proto, "resetTransform", (void *)canvas_reset_transform);
193
+ define_method(proto, "beginPath", (void *)canvas_begin_path);
194
+ define_method(proto, "closePath", (void *)canvas_close_path);
195
+ define_method(proto, "moveTo", (void *)canvas_move_to);
196
+ define_method(proto, "lineTo", (void *)canvas_line_to);
197
+ define_method(proto, "rect", (void *)canvas_rect);
198
+ define_method(proto, "arc", (void *)canvas_arc);
199
+ define_method(proto, "quadraticCurveTo", (void *)canvas_quadratic_curve_to);
200
+ define_method(proto, "bezierCurveTo", (void *)canvas_bezier_curve_to);
201
+ define_method(proto, "fill", (void *)canvas_fill);
202
+ define_method(proto, "stroke", (void *)canvas_stroke);
203
+ define_method(proto, "clearRect", (void *)canvas_clear_rect);
204
+ define_method(proto, "fillRect", (void *)canvas_fill_rect);
205
+ define_method(proto, "strokeRect", (void *)canvas_stroke_rect);
206
+ define_method(proto, "measureText", (void *)canvas_measure_text);
207
+ define_method(proto, "fillText", (void *)canvas_fill_text);
208
+ define_method(proto, "strokeText", (void *)canvas_stroke_text);
209
+ define_method(proto, "drawImage", (void *)canvas_draw_image);
210
+ define_method(proto, "getImageData", (void *)canvas_get_image_data);
211
+ g_canvas_proto = proto;
212
+ return proto;
213
+ }
214
+
135
215
  } // namespace domapi
@@ -143,6 +143,8 @@ void xt_gui_quit_window(XtGuiWindow *win) {
143
143
  win->open = 0;
144
144
  xt_gui_emit(win, "close");
145
145
  win->animation_frames.clear();
146
+ xt_gui_media_shutdown(win);
147
+ xt_gui_canvas_shutdown(win);
146
148
  xt_gui_handles_reset(win);
147
149
  win->document_object = XT_UNDEFINED;
148
150
  win->document.reset();
@@ -227,6 +229,7 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
227
229
  record->image_geometry = XtGuiGeometry();
228
230
  record->last_frame_ms = 0.0;
229
231
  record->animation_frames.clear();
232
+ record->media_players.clear();
230
233
  record->next_animation_frame_id = 1;
231
234
  record->background[0] = 0.08f;
232
235
  record->background[1] = 0.09f;
@@ -300,6 +303,9 @@ extern "C" xt_value xt_gui_run(int32_t argc, xt_value *argv) {
300
303
  SDL_Quit();
301
304
  g_sdl_initialized = 0;
302
305
  }
306
+ /* Drop the decoded-image caches (including parsed SVG documents) now that
307
+ * the GPU textures that referenced them are gone. */
308
+ xtgui::xt_image_shutdown();
303
309
  g_window_count = 0;
304
310
  return XT_UNDEFINED;
305
311
  }
@@ -0,0 +1,95 @@
1
+ /*
2
+ * xbintsc GUI engine — `<canvas>` lifetime.
3
+ *
4
+ * A canvas bitmap is owned by the window, keyed by its DOM element (the same
5
+ * shape as the `<video>` decoder table). This unit reconciles that table with
6
+ * the live document, publishes each canvas's pixel size to the document so
7
+ * layout can give the element its intrinsic box, and releases the matching GPU
8
+ * texture when a canvas goes away.
9
+ */
10
+
11
+ #include <memory>
12
+ #include <vector>
13
+
14
+ #include "canvas.h"
15
+ #include "document.h"
16
+ #include "gui_engine.h"
17
+ #include "gui_internal.h"
18
+
19
+ namespace {
20
+
21
+ bool isCanvasElement(const xtgui::Node *node) {
22
+ return node != nullptr && node->isElement() && node->isTag("canvas");
23
+ }
24
+
25
+ /** Canvas children are fallback content, so a canvas element ends the walk. */
26
+ void collectCanvases(const xtgui::Node *node, std::vector<const xtgui::Node *> *out) {
27
+ if (node == nullptr) return;
28
+ if (isCanvasElement(node)) {
29
+ out->push_back(node);
30
+ return;
31
+ }
32
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) {
33
+ collectCanvases(child.get(), out);
34
+ }
35
+ }
36
+
37
+ int canvasAttributeSize(const xtgui::Node *node, const char *name, int fallback) {
38
+ const std::string *attr = node->attr(name);
39
+ if (attr == nullptr) return fallback;
40
+ float value = 0.0f;
41
+ if (!xtgui::xt_css_parse_number(*attr, &value) || value <= 0.0f) return fallback;
42
+ return (int)(value + 0.5f);
43
+ }
44
+
45
+ void releaseCanvasTexture(xtgui::Canvas2D *canvas) {
46
+ if (canvas != nullptr && guidev::g_device != nullptr) {
47
+ xt_gui_canvas_texture_release(guidev::g_device, canvas->id());
48
+ }
49
+ }
50
+
51
+ } // namespace
52
+
53
+ xtgui::Canvas2D *xt_gui_canvas_find(XtGuiWindow *win, const xtgui::Node *node) {
54
+ if (win == nullptr || node == nullptr) return nullptr;
55
+ auto found = win->canvases.find(node);
56
+ return found == win->canvases.end() ? nullptr : found->second.get();
57
+ }
58
+
59
+ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node) {
60
+ if (win == nullptr || !isCanvasElement(node)) return nullptr;
61
+ auto found = win->canvases.find(node);
62
+ if (found != win->canvases.end()) return found->second.get();
63
+ int width = canvasAttributeSize(node, "width", 300);
64
+ int height = canvasAttributeSize(node, "height", 150);
65
+ auto canvas = std::make_unique<xtgui::Canvas2D>(width, height);
66
+ xtgui::Canvas2D *raw = canvas.get();
67
+ win->canvases[node] = std::move(canvas);
68
+ return raw;
69
+ }
70
+
71
+ void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node) {
72
+ if (win == nullptr || node == nullptr) return;
73
+ auto found = win->canvases.find(node);
74
+ if (found == win->canvases.end()) return;
75
+ releaseCanvasTexture(found->second.get());
76
+ win->canvases.erase(found);
77
+ }
78
+
79
+ void xt_gui_canvas_sync(XtGuiWindow *win) {
80
+ if (win == nullptr || win->document == nullptr) return;
81
+ std::vector<const xtgui::Node *> nodes;
82
+ collectCanvases(win->document->root(), &nodes);
83
+ for (const xtgui::Node *node : nodes) {
84
+ xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
85
+ if (canvas == nullptr) continue;
86
+ win->document->setCanvasInfo(node, (float)canvas->width(), (float)canvas->height(),
87
+ canvas->id());
88
+ }
89
+ }
90
+
91
+ void xt_gui_canvas_shutdown(XtGuiWindow *win) {
92
+ if (win == nullptr) return;
93
+ for (auto &entry : win->canvases) releaseCanvasTexture(entry.second.get());
94
+ win->canvases.clear();
95
+ }
@@ -38,7 +38,9 @@ const char *xt_gui_driver(void);
38
38
  #include <unordered_map>
39
39
  #include <vector>
40
40
 
41
+ #include "canvas.h"
41
42
  #include "document.h"
43
+ #include "media.h"
42
44
  #include "renderer.h"
43
45
 
44
46
  /** One element-level event listener registered through `addEventListener`. */
@@ -64,6 +66,18 @@ struct XtGuiNodeSlot {
64
66
  int generation = 0;
65
67
  };
66
68
 
69
+ /** One media element's decoder slot. A failed open (bad `src`, or a build
70
+ * without a decoder) is remembered so the engine does not retry it every
71
+ * frame and fire `error` repeatedly. */
72
+ struct XtGuiMediaEntry {
73
+ std::unique_ptr<xtgui::MediaPlayer> player;
74
+ std::string src;
75
+ bool failed = false;
76
+ /** Decoder generation already handed to the image upload, so a new video
77
+ * frame can mark the geometry dirty exactly once. */
78
+ uint64_t rendered_generation = 0;
79
+ };
80
+
67
81
  /* One live window. `object` is the JavaScript-visible handle; the record is
68
82
  * addressed from it through the hidden `__xt_gui_index` property. */
69
83
  struct XtGuiWindow {
@@ -98,6 +112,14 @@ struct XtGuiWindow {
98
112
  int struct_dirty = 0;
99
113
  /** Callbacks queued for the next frame, plus the id counter. */
100
114
  std::vector<XtGuiAnimationFrame> animation_frames;
115
+ /** One decoder per `<video>`/`<audio>` element, keyed by the element node. The
116
+ * map doubles as the set of elements whose media is loaded; `gui_media.cpp`
117
+ * reconciles it with the live DOM every frame. */
118
+ std::unordered_map<const xtgui::Node *, XtGuiMediaEntry> media_players;
119
+ /** One `Canvas2D` per `<canvas>` element, keyed by the element node. Created
120
+ * lazily by `getContext("2d")` (or by `xt_gui_canvas_sync` for a live canvas),
121
+ * released when the node is forgotten or the document reloads. */
122
+ std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::Canvas2D>> canvases;
101
123
  /** The batch currently running: its callbacks have been swapped out of
102
124
  * `animation_frames`, and a collection triggered by one of them must still
103
125
  * see the rest. Kept on the record (not a local) so the GC root provider can
@@ -156,6 +178,33 @@ void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, fl
156
178
  /** Synthesise a key event (`keydown`/`keyup`). */
157
179
  void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code);
158
180
 
181
+ /** The decoder for one media element, opening it (and its `src`) on first use.
182
+ * Returns `nullptr` for a non-media element or an unreadable source. */
183
+ xtgui::MediaPlayer *xt_gui_media_player(XtGuiWindow *win, const xtgui::Node *node);
184
+ /** Close and drop the decoder for one element (e.g. its `src` changed). */
185
+ void xt_gui_media_forget(XtGuiWindow *win, const xtgui::Node *node);
186
+ /** Reconcile the player table with the live DOM: open new `<video>`/`<audio>`
187
+ * sources, drop players whose element is gone, and record decoded sizes so the
188
+ * next layout gives a `<video>` its intrinsic box. */
189
+ void xt_gui_media_sync(XtGuiWindow *win);
190
+ /** Advance every decoder and dispatch the resulting DOM events. */
191
+ void xt_gui_media_tick(XtGuiWindow *win, double now_ms);
192
+ /** Release all decoders and their GPU textures (window close / `loadHTML`). */
193
+ void xt_gui_media_shutdown(XtGuiWindow *win);
194
+
195
+ /** The 2D context of a `<canvas>` element, creating it (at the element's
196
+ * `width`/`height` attributes, 300x150 by default) on first use. */
197
+ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node);
198
+ /** The existing 2D context of `node`, or null (no side effects). */
199
+ xtgui::Canvas2D *xt_gui_canvas_find(XtGuiWindow *win, const xtgui::Node *node);
200
+ /** Drop a node's canvas (called when its handle is forgotten). */
201
+ void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node);
202
+ /** Publish live canvases to the document (sizes + render ids) so layout and
203
+ * paint see them; creates a context for a canvas that has none yet. */
204
+ void xt_gui_canvas_sync(XtGuiWindow *win);
205
+ /** Release every canvas of `win` (window close / `loadHTML`). */
206
+ void xt_gui_canvas_shutdown(XtGuiWindow *win);
207
+
159
208
  #endif /* __cplusplus */
160
209
 
161
210
  #endif /* XT_GUI_ENGINE_H */
@@ -254,6 +254,14 @@ void xt_gui_render_window(XtGuiWindow *win) {
254
254
  * auto-close guard): `xt_gui_quit_window` released the document and the GPU
255
255
  * geometry, so there is nothing left to lay out or paint. */
256
256
  if (!win->open) return;
257
+ /* Reconcile decoders with the live DOM first, so this frame's layout sees the
258
+ * current sources and intrinsic sizes, then advance playback and fire the
259
+ * DOM media events. */
260
+ xt_gui_media_sync(win);
261
+ xt_gui_media_tick(win, now);
262
+ /* Publish canvas bitmaps to the document (and create contexts for a canvas
263
+ * that has none) before this frame's layout. */
264
+ xt_gui_canvas_sync(win);
257
265
  if (win->document != nullptr && win->document->advance(delta)) win->geometry.dirty = 1;
258
266
 
259
267
  /* A DOM mutation from a handler invalidates the tree: restyle/relayout
@@ -267,11 +275,18 @@ void xt_gui_render_window(XtGuiWindow *win) {
267
275
  xtgui::xt_paint_build(win->document->layout().root(), list);
268
276
  xt_gui_geometry_upload(g_device, &win->geometry, list);
269
277
  float density = SDL_GetWindowPixelDensity(win->window);
270
- xt_gui_text_upload(g_device, &win->text_geometry, list, density > 0.0f ? density : 1.0f);
271
- xt_gui_image_upload(g_device, &win->image_geometry, list);
278
+ float text_scale = density > 0.0f ? density : 1.0f;
279
+ xt_gui_text_upload(g_device, &win->text_geometry, list, text_scale);
280
+ xt_gui_image_upload(g_device, &win->image_geometry, list, text_scale);
281
+ xt_gui_build_runs(&win->geometry, &win->text_geometry, &win->image_geometry, list);
272
282
  win->geometry.dirty = 0;
273
283
  }
274
284
 
285
+ /* Upload any new video frame before the render command buffer is recorded,
286
+ * so the draw samples the frame that just arrived. */
287
+ xt_gui_media_upload_frames(g_device);
288
+ /* Canvases are CPU-drawn: push any new bitmap without a relayout. */
289
+ xt_gui_canvas_upload_frames(g_device);
275
290
  SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(g_device);
276
291
  if (cmd == NULL) return;
277
292
  SDL_GPUTexture *swapchain = NULL;
@@ -289,9 +304,10 @@ void xt_gui_render_window(XtGuiWindow *win) {
289
304
  target.store_op = SDL_GPU_STOREOP_STORE;
290
305
  SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, NULL);
291
306
  if (can_paint) {
292
- xt_gui_renderer_draw(cmd, pass, &win->geometry, viewport_width, viewport_height);
293
- xt_gui_text_draw(cmd, pass, &win->text_geometry, viewport_width, viewport_height);
294
- xt_gui_image_draw(cmd, pass, &win->image_geometry, viewport_width, viewport_height);
307
+ float density = SDL_GetWindowPixelDensity(win->window);
308
+ if (density <= 0.0f) density = 1.0f;
309
+ xt_gui_renderer_draw(cmd, pass, &win->geometry, &win->text_geometry, &win->image_geometry,
310
+ viewport_width, viewport_height, density);
295
311
  }
296
312
  SDL_EndGPURenderPass(pass);
297
313
  firstFrame = !win->ready;