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
@@ -9,13 +9,144 @@
9
9
  #include <cstdio>
10
10
  #include <cstring>
11
11
 
12
+ #include "canvas.h"
12
13
  #include "image.h"
14
+ #include "media.h"
13
15
 
14
16
  #include "renderer_shaders.h"
15
17
 
16
18
  namespace xtgui {
17
19
  namespace renderer_detail {
18
20
 
21
+ /** One live `<video>` texture. The texture is created once per pixel size and
22
+ * re-uploaded in place whenever the decoder advances (`generation`). */
23
+ struct MediaTexture {
24
+ SDL_GPUTexture *texture = nullptr;
25
+ int width = 0;
26
+ int height = 0;
27
+ uint64_t generation = ~0ull;
28
+ };
29
+
30
+ static std::unordered_map<uint64_t, MediaTexture> g_media_textures;
31
+
32
+ /** One live `<canvas>` texture, keyed by the canvas id from `canvas.h`. It is
33
+ * re-uploaded in place whenever the canvas bumps its `generation`. */
34
+ static std::unordered_map<uint64_t, MediaTexture> g_canvas_textures;
35
+
36
+ /** Upload RGBA8 `pixels` into `texture` through a staging buffer, submitting
37
+ * its own command buffer. Because command buffers run in submission order and
38
+ * this one is submitted before the frame that samples the texture, re-uploading
39
+ * a texture that the previous frame drew is safe. */
40
+ static bool uploadPixels(SDL_GPUDevice *device, SDL_GPUTexture *texture, int width, int height,
41
+ const void *pixels, uint32_t bytes) {
42
+ if (bytes == 0) return false;
43
+ SDL_GPUTransferBufferCreateInfo transfer_info;
44
+ memset(&transfer_info, 0, sizeof(transfer_info));
45
+ transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
46
+ transfer_info.size = bytes;
47
+ SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
48
+ if (transfer == nullptr) return false;
49
+ void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
50
+ std::memcpy(mapped, pixels, bytes);
51
+ SDL_UnmapGPUTransferBuffer(device, transfer);
52
+
53
+ SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
54
+ if (cmd == nullptr) {
55
+ SDL_ReleaseGPUTransferBuffer(device, transfer);
56
+ return false;
57
+ }
58
+ SDL_GPUTextureTransferInfo source;
59
+ memset(&source, 0, sizeof(source));
60
+ source.transfer_buffer = transfer;
61
+ source.offset = 0;
62
+ source.pixels_per_row = (Uint32)width;
63
+ source.rows_per_layer = (Uint32)height;
64
+ SDL_GPUTextureRegion destination;
65
+ memset(&destination, 0, sizeof(destination));
66
+ destination.texture = texture;
67
+ destination.mip_level = 0;
68
+ destination.layer = 0;
69
+ destination.w = (Uint32)width;
70
+ destination.h = (Uint32)height;
71
+ destination.d = 1;
72
+ SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
73
+ SDL_UploadToGPUTexture(copy, &source, &destination, false);
74
+ SDL_EndGPUCopyPass(copy);
75
+ SDL_SubmitGPUCommandBuffer(cmd);
76
+ SDL_ReleaseGPUTransferBuffer(device, transfer);
77
+ return true;
78
+ }
79
+
80
+ /** Ensure the texture for `media_id` exists and holds the decoder's newest
81
+ * frame. Returns `nullptr` before the first frame is decoded. */
82
+ static SDL_GPUTexture *ensureMediaTexture(SDL_GPUDevice *device, uint64_t media_id) {
83
+ xtgui::MediaPlayer *player = xtgui::xt_media_by_id(media_id);
84
+ if (player == nullptr) return nullptr;
85
+ const xtgui::Image *image = player->frame();
86
+ MediaTexture &entry = g_media_textures[media_id];
87
+ if (image == nullptr || image->width <= 0 || image->height <= 0) return entry.texture;
88
+ if (entry.texture == nullptr || entry.width != image->width || entry.height != image->height) {
89
+ if (entry.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.texture);
90
+ SDL_GPUTextureCreateInfo info;
91
+ memset(&info, 0, sizeof(info));
92
+ info.type = SDL_GPU_TEXTURETYPE_2D;
93
+ info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
94
+ info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
95
+ info.width = (Uint32)image->width;
96
+ info.height = (Uint32)image->height;
97
+ info.layer_count_or_depth = 1;
98
+ info.num_levels = 1;
99
+ info.sample_count = SDL_GPU_SAMPLECOUNT_1;
100
+ entry.texture = SDL_CreateGPUTexture(device, &info);
101
+ entry.width = image->width;
102
+ entry.height = image->height;
103
+ entry.generation = ~0ull;
104
+ if (entry.texture == nullptr) return nullptr;
105
+ }
106
+ if (entry.generation != player->frame_generation()) {
107
+ if (uploadPixels(device, entry.texture, entry.width, entry.height, image->pixels.data(),
108
+ (uint32_t)image->pixels.size())) {
109
+ entry.generation = player->frame_generation();
110
+ }
111
+ }
112
+ return entry.texture;
113
+ }
114
+
115
+ /** Ensure the texture for `canvas_id` exists and holds the canvas's newest
116
+ * pixels. Returns `nullptr` for an unknown or empty canvas. */
117
+ static SDL_GPUTexture *ensureCanvasTexture(SDL_GPUDevice *device, uint64_t canvas_id) {
118
+ xtgui::Canvas2D *canvas = xtgui::xt_canvas_by_id(canvas_id);
119
+ if (canvas == nullptr) return nullptr;
120
+ const xtgui::Image &image = canvas->image();
121
+ if (image.width <= 0 || image.height <= 0) return nullptr;
122
+ MediaTexture &entry = g_canvas_textures[canvas_id];
123
+ if (entry.texture == nullptr || entry.width != image.width || entry.height != image.height) {
124
+ if (entry.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.texture);
125
+ SDL_GPUTextureCreateInfo info;
126
+ memset(&info, 0, sizeof(info));
127
+ info.type = SDL_GPU_TEXTURETYPE_2D;
128
+ info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
129
+ info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
130
+ info.width = (Uint32)image.width;
131
+ info.height = (Uint32)image.height;
132
+ info.layer_count_or_depth = 1;
133
+ info.num_levels = 1;
134
+ info.sample_count = SDL_GPU_SAMPLECOUNT_1;
135
+ entry.texture = SDL_CreateGPUTexture(device, &info);
136
+ entry.width = image.width;
137
+ entry.height = image.height;
138
+ entry.generation = ~0ull;
139
+ if (entry.texture == nullptr) return nullptr;
140
+ }
141
+ if (entry.generation != canvas->generation()) {
142
+ if (uploadPixels(device, entry.texture, entry.width, entry.height, image.pixels.data(),
143
+ (uint32_t)image.pixels.size())) {
144
+ entry.generation = canvas->generation();
145
+ }
146
+ }
147
+ return entry.texture;
148
+ }
149
+
19
150
  SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference) {
20
151
  const ShaderSource image_vs = selectShader(device, ShaderProgram::Image, false);
21
152
  const ShaderSource image_fs = selectShader(device, ShaderProgram::Image, true);
@@ -96,15 +227,22 @@ SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *
96
227
  return pipeline;
97
228
  }
98
229
 
99
- /** Decode `src` (cached) and upload it as an RGBA texture (cached by path). */
100
- SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src) {
101
- auto found = g_image_textures.find(src);
230
+ /** Decode `src` (cached) and upload it as an RGBA texture. Vector sources are
231
+ * rasterised at the drawn pixel size and cached by that size; bitmaps decode at
232
+ * their intrinsic size and are cached by path. */
233
+ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
234
+ int pixel_height) {
235
+ bool vector_source = xtgui::xt_image_is_vector(src);
236
+ std::string key =
237
+ vector_source ? src + "@" + std::to_string(pixel_width) + "x" + std::to_string(pixel_height)
238
+ : src;
239
+ auto found = g_image_textures.find(key);
102
240
  if (found != g_image_textures.end()) return found->second;
103
241
 
104
- const xtgui::Image *image = xtgui::xt_image_load(src);
242
+ const xtgui::Image *image = xtgui::xt_image_load(src, pixel_width, pixel_height);
105
243
  if (image == nullptr || image->width <= 0 || image->height <= 0) {
106
244
  fprintf(stderr, "xt_gui: could not decode image '%s'\n", src.c_str());
107
- g_image_textures.emplace(src, nullptr);
245
+ g_image_textures.emplace(key, nullptr);
108
246
  return nullptr;
109
247
  }
110
248
 
@@ -121,56 +259,18 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
121
259
  SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &info);
122
260
  if (texture == nullptr) {
123
261
  fprintf(stderr, "xt_gui: image texture creation failed: %s\n", SDL_GetError());
124
- g_image_textures.emplace(src, nullptr);
262
+ g_image_textures.emplace(key, nullptr);
125
263
  return nullptr;
126
264
  }
127
265
 
128
266
  uint32_t bytes = (uint32_t)(image->pixels.size());
129
- SDL_GPUTransferBufferCreateInfo transfer_info;
130
- memset(&transfer_info, 0, sizeof(transfer_info));
131
- transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
132
- transfer_info.size = bytes;
133
- SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &transfer_info);
134
- if (transfer == nullptr) {
267
+ if (!uploadPixels(device, texture, image->width, image->height, image->pixels.data(), bytes)) {
135
268
  SDL_ReleaseGPUTexture(device, texture);
136
- g_image_textures.emplace(src, nullptr);
269
+ g_image_textures.emplace(key, nullptr);
137
270
  return nullptr;
138
271
  }
139
- void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
140
- std::memcpy(mapped, image->pixels.data(), bytes);
141
- SDL_UnmapGPUTransferBuffer(device, transfer);
142
272
 
143
- SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device);
144
- if (cmd == nullptr) {
145
- SDL_ReleaseGPUTransferBuffer(device, transfer);
146
- SDL_ReleaseGPUTexture(device, texture);
147
- g_image_textures.emplace(src, nullptr);
148
- return nullptr;
149
- }
150
- SDL_GPUTextureTransferInfo source;
151
- memset(&source, 0, sizeof(source));
152
- source.transfer_buffer = transfer;
153
- source.offset = 0;
154
- source.pixels_per_row = (Uint32)image->width;
155
- source.rows_per_layer = (Uint32)image->height;
156
- SDL_GPUTextureRegion destination;
157
- memset(&destination, 0, sizeof(destination));
158
- destination.texture = texture;
159
- destination.mip_level = 0;
160
- destination.layer = 0;
161
- destination.x = 0;
162
- destination.y = 0;
163
- destination.z = 0;
164
- destination.w = (Uint32)image->width;
165
- destination.h = (Uint32)image->height;
166
- destination.d = 1;
167
- SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd);
168
- SDL_UploadToGPUTexture(copy, &source, &destination, false);
169
- SDL_EndGPUCopyPass(copy);
170
- SDL_SubmitGPUCommandBuffer(cmd);
171
- SDL_ReleaseGPUTransferBuffer(device, transfer);
172
-
173
- g_image_textures.emplace(src, texture);
273
+ g_image_textures.emplace(key, texture);
174
274
  return texture;
175
275
  }
176
276
 
@@ -178,13 +278,15 @@ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src
178
278
  } // namespace xtgui
179
279
 
180
280
  void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
181
- const xtgui::DisplayList &list) {
281
+ const xtgui::DisplayList &list, float scale) {
182
282
  using namespace xtgui::renderer_detail;
183
- g_image_batches.clear();
283
+ geometry->op_counts.assign(list.images.size(), 0);
284
+ geometry->op_textures.assign(list.images.size(), nullptr);
184
285
  if (g_image_pipeline == nullptr) {
185
286
  geometry->vertex_count = 0;
186
287
  return;
187
288
  }
289
+ if (scale <= 0.0f) scale = 1.0f;
188
290
  std::vector<TextVertex> verts;
189
291
  verts.reserve(list.images.size() * 6);
190
292
  xtgui::Color white{1.0f, 1.0f, 1.0f, 1.0f};
@@ -193,45 +295,70 @@ void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
193
295
  full.v0 = 0.0f;
194
296
  full.u1 = 1.0f;
195
297
  full.v1 = 1.0f;
196
- for (const xtgui::PaintImage &image : list.images) {
298
+ for (size_t index = 0; index < list.images.size(); index++) {
299
+ const xtgui::PaintImage &image = list.images[index];
197
300
  if (image.width <= 0.0f || image.height <= 0.0f) continue;
198
- SDL_GPUTexture *texture = ensureImageTexture(device, image.src);
301
+ SDL_GPUTexture *texture = nullptr;
302
+ if (image.canvas_id != 0) {
303
+ texture = ensureCanvasTexture(device, image.canvas_id);
304
+ } else if (image.media_id != 0) {
305
+ texture = ensureMediaTexture(device, image.media_id);
306
+ } else {
307
+ /* Draw-time device pixels: an SVG is rasterised at this size so it stays
308
+ * crisp on HiDPI and when scaled. */
309
+ int pixel_width = (int)(image.width * scale + 0.5f);
310
+ int pixel_height = (int)(image.height * scale + 0.5f);
311
+ if (pixel_width < 1) pixel_width = 1;
312
+ if (pixel_height < 1) pixel_height = 1;
313
+ texture = ensureImageTexture(device, image.src, pixel_width, pixel_height);
314
+ }
199
315
  if (texture == nullptr) continue;
200
- uint32_t first = (uint32_t)verts.size();
201
- pushGlyph(verts, image.x, image.y, image.width, image.height, full, white);
202
- g_image_batches.push_back(ImageBatch{texture, first, 6});
316
+ pushGlyph(verts, image.x, image.y, image.width, image.height, full, white,
317
+ image.transform);
318
+ geometry->op_counts[index] = 6;
319
+ geometry->op_textures[index] = texture;
203
320
  }
204
321
  uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
205
322
  }
206
323
 
207
- void xt_gui_image_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
208
- float viewport_width, float viewport_height) {
209
- using namespace xtgui::renderer_detail;
210
- if (g_image_pipeline == nullptr || g_sampler == nullptr) return;
211
- if (geometry->buffer == nullptr || geometry->vertex_count == 0 || g_image_batches.empty()) return;
212
- if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
213
- SDL_BindGPUGraphicsPipeline(pass, g_image_pipeline);
214
- SDL_GPUBufferBinding binding;
215
- binding.buffer = geometry->buffer;
216
- binding.offset = 0;
217
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
218
- float viewport[2] = {viewport_width, viewport_height};
219
- SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
220
- for (const ImageBatch &batch : g_image_batches) {
221
- if (batch.texture == nullptr) continue;
222
- SDL_GPUTextureSamplerBinding texture_binding;
223
- texture_binding.texture = batch.texture;
224
- texture_binding.sampler = g_sampler;
225
- SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
226
- SDL_DrawGPUPrimitives(pass, batch.count, 1, batch.first, 0);
227
- }
228
- }
229
-
230
324
  void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
231
325
  using namespace xtgui::renderer_detail;
232
326
  for (auto &entry : g_image_textures) {
233
327
  if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
234
328
  }
235
329
  g_image_textures.clear();
236
- g_image_batches.clear();
330
+ for (auto &entry : g_media_textures) {
331
+ if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
332
+ }
333
+ g_media_textures.clear();
334
+ for (auto &entry : g_canvas_textures) {
335
+ if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
336
+ }
337
+ g_canvas_textures.clear();
338
+ }
339
+
340
+ void xt_gui_media_upload_frames(SDL_GPUDevice *device) {
341
+ using namespace xtgui::renderer_detail;
342
+ for (auto &entry : g_media_textures) ensureMediaTexture(device, entry.first);
343
+ }
344
+
345
+ void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
346
+ using namespace xtgui::renderer_detail;
347
+ auto it = g_media_textures.find(media_id);
348
+ if (it == g_media_textures.end()) return;
349
+ if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
350
+ g_media_textures.erase(it);
351
+ }
352
+
353
+ void xt_gui_canvas_upload_frames(SDL_GPUDevice *device) {
354
+ using namespace xtgui::renderer_detail;
355
+ for (auto &entry : g_canvas_textures) ensureCanvasTexture(device, entry.first);
356
+ }
357
+
358
+ void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
359
+ using namespace xtgui::renderer_detail;
360
+ auto it = g_canvas_textures.find(canvas_id);
361
+ if (it == g_canvas_textures.end()) return;
362
+ if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
363
+ g_canvas_textures.erase(it);
237
364
  }
@@ -35,19 +35,12 @@ struct TextVertex {
35
35
  };
36
36
 
37
37
  /* Image quads are batched by texture; textures are cached by source path. */
38
- struct ImageBatch {
39
- SDL_GPUTexture *texture = nullptr;
40
- uint32_t first = 0;
41
- uint32_t count = 0;
42
- };
43
-
44
38
  extern SDL_GPUGraphicsPipeline *g_pipeline;
45
39
  extern SDL_GPUGraphicsPipeline *g_text_pipeline;
46
40
  extern SDL_GPUGraphicsPipeline *g_image_pipeline;
47
41
  extern SDL_GPUSampler *g_sampler;
48
42
  extern int g_renderer_failed;
49
43
  extern bool g_text_ok;
50
- extern std::vector<ImageBatch> g_image_batches;
51
44
  extern std::unordered_map<std::string, SDL_GPUTexture *> g_image_textures;
52
45
 
53
46
  /* -- glyph atlas ---------------------------------------------------------- */
@@ -88,7 +81,12 @@ extern bool g_atlas_full_logged;
88
81
 
89
82
  void pushRect(std::vector<Vertex> &verts, const xtgui::PaintRect &rect);
90
83
  void pushGlyph(std::vector<TextVertex> &verts, float x, float y, float width, float height,
91
- const GlyphEntry &entry, const xtgui::Color &color);
84
+ const GlyphEntry &entry, const xtgui::Color &color,
85
+ const xtgui::Matrix2D &transform);
86
+ /** Push a textured quad, applying a `transform` matrix to its four corners. */
87
+ void pushQuad(std::vector<TextVertex> &verts, const float corners_x[4], const float corners_y[4],
88
+ const float u[4], const float v[4], const xtgui::Color &color,
89
+ const xtgui::Matrix2D &transform);
92
90
  void uploadVertices(SDL_GPUDevice *device, XtGuiGeometry *geometry, const void *data,
93
91
  uint32_t count, uint32_t stride);
94
92
 
@@ -104,7 +102,12 @@ SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
104
102
  /* -- images --------------------------------------------------------------- */
105
103
 
106
104
  SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference);
107
- SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src);
105
+ /** Decode `src` (cached) and upload it as an RGBA texture. `pixel_width` /
106
+ * `pixel_height` are the drawn size in device pixels; vector sources (SVG) are
107
+ * rasterised at that size, raster sources ignore it. The texture cache is keyed
108
+ * by the resulting pixel size, so an SVG drawn at two sizes gets two textures. */
109
+ SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
110
+ int pixel_height);
108
111
 
109
112
  } // namespace renderer_detail
110
113
  } // namespace xtgui
@@ -17,16 +17,17 @@
17
17
  namespace xtgui {
18
18
  namespace renderer_detail {
19
19
 
20
- void pushGlyph(std::vector<TextVertex> &verts, float x, float y, float width, float height,
21
- const GlyphEntry &entry, const xtgui::Color &color) {
22
- const float corners_x[4] = {x, x + width, x + width, x};
23
- const float corners_y[4] = {y, y, y + height, y + height};
24
- const float u[4] = {entry.u0, entry.u1, entry.u1, entry.u0};
25
- const float v[4] = {entry.v0, entry.v0, entry.v1, entry.v1};
20
+ /** Bounds for the atlas raster of a transformed glyph: below 1 the text is
21
+ * already shrinking, above 4 the atlas cost outweighs the sharpness. */
22
+ constexpr float kMinGlyphScale = 0.5f;
23
+ constexpr float kMaxGlyphScale = 4.0f;
24
+
25
+ void pushQuad(std::vector<TextVertex> &verts, const float corners_x[4], const float corners_y[4],
26
+ const float u[4], const float v[4], const xtgui::Color &color,
27
+ const xtgui::Matrix2D &transform) {
26
28
  TextVertex corners[4];
27
29
  for (int i = 0; i < 4; i++) {
28
- corners[i].x = corners_x[i];
29
- corners[i].y = corners_y[i];
30
+ transform.apply(corners_x[i], corners_y[i], &corners[i].x, &corners[i].y);
30
31
  corners[i].u = u[i];
31
32
  corners[i].v = v[i];
32
33
  corners[i].r = color.r;
@@ -38,6 +39,16 @@ void pushGlyph(std::vector<TextVertex> &verts, float x, float y, float width, fl
38
39
  for (int i = 0; i < 6; i++) verts.push_back(corners[order[i]]);
39
40
  }
40
41
 
42
+ void pushGlyph(std::vector<TextVertex> &verts, float x, float y, float width, float height,
43
+ const GlyphEntry &entry, const xtgui::Color &color,
44
+ const xtgui::Matrix2D &transform) {
45
+ const float corners_x[4] = {x, x + width, x + width, x};
46
+ const float corners_y[4] = {y, y, y + height, y + height};
47
+ const float u[4] = {entry.u0, entry.u1, entry.u1, entry.u0};
48
+ const float v[4] = {entry.v0, entry.v0, entry.v1, entry.v1};
49
+ pushQuad(verts, corners_x, corners_y, u, v, color, transform);
50
+ }
51
+
41
52
  bool ensureAtlasTexture(SDL_GPUDevice *device) {
42
53
  if (g_atlas_texture != nullptr) return true;
43
54
  SDL_GPUTextureCreateInfo info;
@@ -295,52 +306,48 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
295
306
  if (scale <= 0.0f) scale = 1.0f;
296
307
 
297
308
  std::vector<TextVertex> verts;
298
- for (const xtgui::PaintText &run : list.texts) {
309
+ geometry->op_counts.assign(list.texts.size(), 0);
310
+ for (size_t index = 0; index < list.texts.size(); index++) {
311
+ const xtgui::PaintText &run = list.texts[index];
299
312
  if (run.text.empty() || run.color.a <= 0.0f) continue;
300
313
  xtgui::Font *logical = xtgui::xt_text_resolve(run.font);
301
314
  if (logical == nullptr) continue;
302
- xtgui::FontSpec raster_spec = run.font;
303
- raster_spec.pixel_size = run.font.pixel_size * scale;
304
- xtgui::Font *raster = xtgui::xt_text_resolve(raster_spec);
305
- if (raster == nullptr) raster = logical;
315
+
316
+ /* A scaling `transform` magnifies the glyph quads on the GPU, so the atlas
317
+ has to hold a raster of that size or the text looks like a scaled bitmap:
318
+ rasterise at the device scale times the transform's magnification, and
319
+ clamp it — the atlas is finite, and a 40x transform would evict every
320
+ other glyph (a magnified bitmap is the right trade at that point). */
321
+ float glyph_scale = scale * run.transform.magnification();
322
+ if (glyph_scale < kMinGlyphScale) glyph_scale = kMinGlyphScale;
323
+ if (glyph_scale > kMaxGlyphScale) glyph_scale = kMaxGlyphScale;
306
324
 
307
325
  std::vector<xtgui::ShapedGlyph> glyphs;
308
326
  xtgui::xt_text_shape_run(run.text, logical, glyphs);
309
327
  float pen_x = run.x;
328
+ uint32_t before = (uint32_t)verts.size();
310
329
  for (const xtgui::ShapedGlyph &glyph : glyphs) {
311
330
  if (glyph.glyph != 0) {
331
+ xtgui::Font *face = glyph.font != nullptr ? glyph.font : logical;
332
+ if (glyph_scale != 1.0f) {
333
+ xtgui::Font *scaled = xtgui::xt_text_scale(face, run.font.pixel_size * glyph_scale);
334
+ if (scaled != nullptr) face = scaled;
335
+ }
312
336
  GlyphEntry entry;
313
- if (ensureGlyph(device, raster, glyph.glyph, entry)) {
314
- float quad_x = pen_x + glyph.x_offset + (float)entry.left / scale;
315
- float quad_y = run.baseline + glyph.y_offset - (float)entry.top / scale;
316
- float quad_w = (float)entry.width / scale;
317
- float quad_h = (float)entry.height / scale;
318
- pushGlyph(verts, quad_x, quad_y, quad_w, quad_h, entry, run.color);
337
+ if (ensureGlyph(device, face, glyph.glyph, entry)) {
338
+ /* Entry metrics are in raster pixels; divide them back into logical
339
+ units — the transform (not the atlas) does the final scaling. */
340
+ float quad_x = pen_x + glyph.x_offset + (float)entry.left / glyph_scale;
341
+ float quad_y = run.baseline + glyph.y_offset - (float)entry.top / glyph_scale;
342
+ float quad_w = (float)entry.width / glyph_scale;
343
+ float quad_h = (float)entry.height / glyph_scale;
344
+ pushGlyph(verts, quad_x, quad_y, quad_w, quad_h, entry, run.color, run.transform);
319
345
  }
320
346
  }
321
347
  pen_x += glyph.x_advance;
322
348
  }
349
+ geometry->op_counts[index] = (uint32_t)verts.size() - before;
323
350
  }
324
351
  uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
325
352
  flushAtlas(device);
326
353
  }
327
-
328
- void xt_gui_text_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
329
- float viewport_width, float viewport_height) {
330
- using namespace xtgui::renderer_detail;
331
- if (!g_text_ok || geometry->buffer == nullptr || geometry->vertex_count == 0) return;
332
- if (g_atlas_texture == nullptr) return;
333
- if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
334
- SDL_BindGPUGraphicsPipeline(pass, g_text_pipeline);
335
- SDL_GPUBufferBinding binding;
336
- binding.buffer = geometry->buffer;
337
- binding.offset = 0;
338
- SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
339
- SDL_GPUTextureSamplerBinding atlas_binding;
340
- atlas_binding.texture = g_atlas_texture;
341
- atlas_binding.sampler = g_sampler;
342
- SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1);
343
- float viewport[2] = {viewport_width, viewport_height};
344
- SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
345
- SDL_DrawGPUPrimitives(pass, geometry->vertex_count, 1, 0, 0);
346
- }
@@ -16,14 +16,19 @@
16
16
 
17
17
  #include "css.h"
18
18
  #include "dom.h"
19
+ #include "transform.h"
19
20
 
20
21
  namespace xtgui {
21
22
 
22
23
  enum class Display { None, Block, Inline, InlineBlock, Flex, ListItem };
23
24
  enum class Position { Static, Relative, Absolute, Fixed };
24
25
  enum class FlexDirection { Row, RowReverse, Column, ColumnReverse };
26
+ enum class FlexWrap { NoWrap, Wrap, WrapReverse };
25
27
  enum class JustifyContent { FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly };
26
28
  enum class AlignItems { Stretch, FlexStart, FlexEnd, Center, Baseline };
29
+ enum class AlignContent { Stretch, FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround };
30
+ enum class FloatSide { None, Left, Right };
31
+ enum class Clear { None, Left, Right, Both };
27
32
  enum class TextAlign { Start, Center, End, Justify };
28
33
  enum class WhiteSpace { Normal, NoWrap, Pre, PreWrap, PreLine };
29
34
  enum class Overflow { Visible, Hidden, Scroll, Auto };
@@ -84,9 +89,13 @@ struct XtStyle {
84
89
  WhiteSpace white_space = WhiteSpace::Normal; // inherited
85
90
 
86
91
  FlexDirection flex_direction = FlexDirection::Row;
92
+ FlexWrap flex_wrap = FlexWrap::NoWrap;
87
93
  JustifyContent justify_content = JustifyContent::FlexStart;
88
94
  AlignItems align_items = AlignItems::Stretch;
95
+ AlignContent align_content = AlignContent::Stretch;
89
96
  Length gap;
97
+ Length row_gap;
98
+ Length column_gap;
90
99
  float flex_grow = 0.0f;
91
100
  float flex_shrink = 1.0f;
92
101
  Length flex_basis;
@@ -96,6 +105,15 @@ struct XtStyle {
96
105
  int z_index = 0;
97
106
  bool has_z_index = false;
98
107
 
108
+ FloatSide float_side = FloatSide::None;
109
+ Clear clear = Clear::None;
110
+
111
+ /** `transform` (not inherited) and `transform-origin` (defaults to the centre
112
+ * of the border box). Layout resolves the list into one matrix per box. */
113
+ std::vector<TransformOp> transform;
114
+ Length transform_origin_x{50.0f, Unit::Percent};
115
+ Length transform_origin_y{50.0f, Unit::Percent};
116
+
99
117
  /** `transition` specs; not inherited. Applied by `XtDocument`. */
100
118
  std::vector<TransitionSpec> transitions;
101
119
  };
@@ -112,6 +130,9 @@ void xt_style_compute(const Node *root, const StyleSheet &sheet,
112
130
  /** Format a computed property for diagnostics/tests (`computedStyle(sel, prop)`). */
113
131
  std::string xt_style_property_to_string(const XtStyle &style, const std::string &property);
114
132
 
133
+ /** Format a length the way `computedStyle` does (`4px`, `50%`, `1.5`). */
134
+ std::string xt_style_length_to_string(const Length &length);
135
+
115
136
  } // namespace xtgui
116
137
 
117
138
  #endif /* XT_GUI_STYLE_H */
@@ -61,9 +61,14 @@ std::string xt_style_property_to_string(const XtStyle &style, const std::string
61
61
  static const char *const displayNames[] = {"none", "block", "inline", "inline-block", "flex", "list-item"};
62
62
  static const char *const positionNames[] = {"static", "relative", "absolute", "fixed"};
63
63
  static const char *const flexNames[] = {"row", "row-reverse", "column", "column-reverse"};
64
+ static const char *const wrapNames[] = {"nowrap", "wrap", "wrap-reverse"};
64
65
  static const char *const justifyNames[] = {"flex-start", "flex-end", "center", "space-between",
65
66
  "space-around", "space-evenly"};
66
67
  static const char *const alignNames[] = {"stretch", "flex-start", "flex-end", "center", "baseline"};
68
+ static const char *const alignContentNames[] = {"stretch", "flex-start", "flex-end", "center",
69
+ "space-between", "space-around"};
70
+ static const char *const floatNames[] = {"none", "left", "right"};
71
+ static const char *const clearNames[] = {"none", "left", "right", "both"};
67
72
  static const char *const alignTextNames[] = {"start", "center", "end", "justify"};
68
73
 
69
74
  if (property == "display") return displayNames[(int)style.display];
@@ -98,16 +103,28 @@ std::string xt_style_property_to_string(const XtStyle &style, const std::string
98
103
  }
99
104
  if (property == "text-align") return alignTextNames[(int)style.text_align];
100
105
  if (property == "flex-direction") return flexNames[(int)style.flex_direction];
106
+ if (property == "flex-wrap") return wrapNames[(int)style.flex_wrap];
101
107
  if (property == "justify-content") return justifyNames[(int)style.justify_content];
102
108
  if (property == "align-items") return alignNames[(int)style.align_items];
109
+ if (property == "align-content") return alignContentNames[(int)style.align_content];
103
110
  if (property == "gap") return formatLength(style.gap);
111
+ if (property == "row-gap") return formatLength(style.row_gap);
112
+ if (property == "column-gap") return formatLength(style.column_gap);
113
+ if (property == "float") return floatNames[(int)style.float_side];
114
+ if (property == "clear") return clearNames[(int)style.clear];
104
115
  if (property == "flex-grow") return formatNumber(style.flex_grow);
105
116
  if (property == "flex-shrink") return formatNumber(style.flex_shrink);
106
117
  if (property == "flex-basis") return formatLength(style.flex_basis);
107
118
  if (property == "border-radius") return formatNumber(style.border_radius) + "px";
108
119
  if (property == "opacity") return formatNumber(style.opacity);
109
120
  if (property == "z-index") return style.has_z_index ? formatNumber((float)style.z_index) : "auto";
121
+ if (property == "transform") return xt_transform_to_string(style.transform);
122
+ if (property == "transform-origin") {
123
+ return formatLength(style.transform_origin_x) + " " + formatLength(style.transform_origin_y);
124
+ }
110
125
  return "";
111
126
  }
112
127
 
128
+ std::string xt_style_length_to_string(const Length &length) { return formatLength(length); }
129
+
113
130
  } // namespace xtgui
@@ -40,6 +40,13 @@ void xt_style_apply_user_agent(const Node *node, XtStyle *style) {
40
40
  style->display = Display::InlineBlock;
41
41
  }
42
42
  if (tag == "img") style->display = Display::InlineBlock;
43
+ /* `<video>` mirrors `<img>` (a replaced inline-block; 300x150 by default from
44
+ * layout), while `<audio>` and its `<source>`/`<track>` children have no
45
+ * rendered box of their own. */
46
+ if (tag == "video") style->display = Display::InlineBlock;
47
+ /* `<canvas>` is also a replaced inline-block (300x150 default in layout). */
48
+ if (tag == "canvas") style->display = Display::InlineBlock;
49
+ if (tag == "audio" || tag == "source" || tag == "track") style->display = Display::None;
43
50
  if (tag == "body") style->margin.set({8.0f, Unit::Px});
44
51
  if (tag == "p") style->margin.top = style->margin.bottom = {16.0f, Unit::Px};
45
52
  if (tag == "blockquote") {