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.
- package/README.md +57 -4
- package/README.zh-CN.md +51 -2
- package/dist/src/driver/compiler.js +6 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/extensions/gui/index.js +10 -2
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
- package/dist/tests/e2e/gui-advanced.test.js +261 -0
- package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas.test.js +233 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
- package/dist/tests/e2e/gui-example-svg.test.js +105 -0
- package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +1 -1
- package/dist/tests/e2e/gui-example.test.js +2 -2
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +5 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +73 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +8 -0
- package/dist/tests/e2e/gui-media.test.js +119 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.js +40 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
- package/dist/tests/e2e/gui-transform.test.js +198 -0
- package/dist/tests/e2e/gui-transform.test.js.map +1 -0
- package/doc/ai/cli.md +5 -0
- package/doc/ai/extensions.md +20 -3
- package/doc/ai/zh-CN/extensions.md +14 -3
- package/doc/gui/animation.md +97 -0
- package/doc/gui/api.md +92 -0
- package/doc/gui/build.md +91 -0
- package/doc/gui/canvas.md +98 -0
- package/doc/gui/dom.md +128 -0
- package/doc/gui/input.md +25 -0
- package/doc/gui/known-issues.md +78 -0
- package/doc/gui/layout.md +108 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +113 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +109 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +93 -0
- package/doc/zh-CN/gui/api.md +88 -0
- package/doc/zh-CN/gui/build.md +84 -0
- package/doc/zh-CN/gui/canvas.md +80 -0
- package/doc/zh-CN/gui/dom.md +119 -0
- package/doc/zh-CN/gui/input.md +23 -0
- package/doc/zh-CN/gui/known-issues.md +60 -0
- package/doc/zh-CN/gui/layout.md +91 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +92 -0
- package/doc/zh-CN/gui/runtime.md +76 -0
- package/doc/zh-CN/gui-scripts.md +6 -2
- package/doc/zh-CN/gui.md +89 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/llms.txt +15 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +580 -0
- package/runtime/ext_gui/canvas.h +170 -0
- package/runtime/ext_gui/canvas_internal.h +59 -0
- package/runtime/ext_gui/canvas_raster.cpp +254 -0
- package/runtime/ext_gui/document.cpp +45 -4
- package/runtime/ext_gui/document.h +15 -1
- package/runtime/ext_gui/dom_api.cpp +3 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
- package/runtime/ext_gui/dom_api_internal.h +73 -0
- package/runtime/ext_gui/dom_api_media.cpp +274 -0
- package/runtime/ext_gui/dom_api_node.cpp +6 -4
- package/runtime/ext_gui/dom_api_proto.cpp +80 -0
- package/runtime/ext_gui/gui.cpp +6 -0
- package/runtime/ext_gui/gui_canvas.cpp +95 -0
- package/runtime/ext_gui/gui_engine.h +49 -0
- package/runtime/ext_gui/gui_events.cpp +21 -5
- package/runtime/ext_gui/gui_media.cpp +193 -0
- package/runtime/ext_gui/image.cpp +31 -12
- package/runtime/ext_gui/image.h +20 -7
- package/runtime/ext_gui/layout.cpp +130 -6
- package/runtime/ext_gui/layout.h +54 -1
- package/runtime/ext_gui/layout_flex.cpp +263 -99
- package/runtime/ext_gui/layout_flow.cpp +85 -9
- package/runtime/ext_gui/layout_flow_internal.h +84 -5
- package/runtime/ext_gui/layout_inline.cpp +54 -37
- package/runtime/ext_gui/media.cpp +514 -0
- package/runtime/ext_gui/media.h +104 -0
- package/runtime/ext_gui/nanosvg.h +3278 -0
- package/runtime/ext_gui/nanosvgrast.h +1472 -0
- package/runtime/ext_gui/paint.cpp +156 -38
- package/runtime/ext_gui/paint.h +42 -6
- package/runtime/ext_gui/renderer.cpp +117 -12
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +205 -78
- package/runtime/ext_gui/renderer_internal.h +12 -9
- package/runtime/ext_gui/renderer_text.cpp +46 -39
- package/runtime/ext_gui/style.h +21 -0
- package/runtime/ext_gui/style_print.cpp +17 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +83 -1
- package/runtime/ext_gui/svg.cpp +182 -0
- package/runtime/ext_gui/svg.h +46 -0
- package/runtime/ext_gui/text.cpp +156 -25
- package/runtime/ext_gui/text.h +11 -2
- package/runtime/ext_gui/transform.cpp +412 -0
- package/runtime/ext_gui/transform.h +112 -0
- package/runtime/ext_gui/window.cpp +10 -5
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- package/scripts/install-gui-deps.sh +28 -0
- package/src/driver/compiler.ts +6 -1
- 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(
|
|
222
|
-
xt_object_set(object, xt_string_from_cstr("y"), xt_number(
|
|
223
|
-
xt_object_set(object, xt_string_from_cstr("width"), xt_number(
|
|
224
|
-
xt_object_set(object, xt_string_from_cstr("height"), xt_number(
|
|
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
|
package/runtime/ext_gui/gui.cpp
CHANGED
|
@@ -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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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;
|