xbintsc 0.3.68 → 0.3.79
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 +59 -4
- package/README.zh-CN.md +52 -2
- 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.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/doc/ai/extensions.md +16 -3
- package/doc/ai/zh-CN/extensions.md +10 -3
- package/doc/gui/animation.md +46 -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 +123 -0
- package/doc/gui/input.md +22 -0
- package/doc/gui/known-issues.md +66 -0
- package/doc/gui/layout.md +72 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +98 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +80 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +46 -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 +114 -0
- package/doc/zh-CN/gui/input.md +21 -0
- package/doc/zh-CN/gui/known-issues.md +50 -0
- package/doc/zh-CN/gui/layout.md +60 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +81 -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 +64 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/doc/zh-CN/post/xbintsc-build-pelican.md +2 -2
- 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 +24 -1
- package/runtime/ext_gui/document.h +11 -0
- 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_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 +52 -5
- package/runtime/ext_gui/layout.h +33 -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 +123 -38
- package/runtime/ext_gui/paint.h +36 -6
- package/runtime/ext_gui/renderer.cpp +113 -10
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +203 -77
- package/runtime/ext_gui/renderer_internal.h +6 -8
- package/runtime/ext_gui/renderer_text.cpp +11 -26
- package/runtime/ext_gui/style.h +11 -0
- package/runtime/ext_gui/style_print.cpp +11 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +40 -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/window.cpp +4 -1
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- package/scripts/install-gui-deps.sh +28 -0
|
@@ -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
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,69 @@ 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 (
|
|
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 =
|
|
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
316
|
pushGlyph(verts, image.x, image.y, image.width, image.height, full, white);
|
|
202
|
-
|
|
317
|
+
geometry->op_counts[index] = 6;
|
|
318
|
+
geometry->op_textures[index] = texture;
|
|
203
319
|
}
|
|
204
320
|
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
205
321
|
}
|
|
206
322
|
|
|
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
323
|
void xt_gui_image_textures_destroy(SDL_GPUDevice *device) {
|
|
231
324
|
using namespace xtgui::renderer_detail;
|
|
232
325
|
for (auto &entry : g_image_textures) {
|
|
233
326
|
if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
|
|
234
327
|
}
|
|
235
328
|
g_image_textures.clear();
|
|
236
|
-
|
|
329
|
+
for (auto &entry : g_media_textures) {
|
|
330
|
+
if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
|
|
331
|
+
}
|
|
332
|
+
g_media_textures.clear();
|
|
333
|
+
for (auto &entry : g_canvas_textures) {
|
|
334
|
+
if (entry.second.texture != nullptr) SDL_ReleaseGPUTexture(device, entry.second.texture);
|
|
335
|
+
}
|
|
336
|
+
g_canvas_textures.clear();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
void xt_gui_media_upload_frames(SDL_GPUDevice *device) {
|
|
340
|
+
using namespace xtgui::renderer_detail;
|
|
341
|
+
for (auto &entry : g_media_textures) ensureMediaTexture(device, entry.first);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id) {
|
|
345
|
+
using namespace xtgui::renderer_detail;
|
|
346
|
+
auto it = g_media_textures.find(media_id);
|
|
347
|
+
if (it == g_media_textures.end()) return;
|
|
348
|
+
if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
|
|
349
|
+
g_media_textures.erase(it);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
void xt_gui_canvas_upload_frames(SDL_GPUDevice *device) {
|
|
353
|
+
using namespace xtgui::renderer_detail;
|
|
354
|
+
for (auto &entry : g_canvas_textures) ensureCanvasTexture(device, entry.first);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id) {
|
|
358
|
+
using namespace xtgui::renderer_detail;
|
|
359
|
+
auto it = g_canvas_textures.find(canvas_id);
|
|
360
|
+
if (it == g_canvas_textures.end()) return;
|
|
361
|
+
if (it->second.texture != nullptr) SDL_ReleaseGPUTexture(device, it->second.texture);
|
|
362
|
+
g_canvas_textures.erase(it);
|
|
237
363
|
}
|
|
@@ -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 ---------------------------------------------------------- */
|
|
@@ -104,7 +97,12 @@ SDL_GPUSampler *createSampler(SDL_GPUDevice *device);
|
|
|
104
97
|
/* -- images --------------------------------------------------------------- */
|
|
105
98
|
|
|
106
99
|
SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference);
|
|
107
|
-
|
|
100
|
+
/** Decode `src` (cached) and upload it as an RGBA texture. `pixel_width` /
|
|
101
|
+
* `pixel_height` are the drawn size in device pixels; vector sources (SVG) are
|
|
102
|
+
* rasterised at that size, raster sources ignore it. The texture cache is keyed
|
|
103
|
+
* by the resulting pixel size, so an SVG drawn at two sizes gets two textures. */
|
|
104
|
+
SDL_GPUTexture *ensureImageTexture(SDL_GPUDevice *device, const std::string &src, int pixel_width,
|
|
105
|
+
int pixel_height);
|
|
108
106
|
|
|
109
107
|
} // namespace renderer_detail
|
|
110
108
|
} // namespace xtgui
|
|
@@ -295,22 +295,26 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
295
295
|
if (scale <= 0.0f) scale = 1.0f;
|
|
296
296
|
|
|
297
297
|
std::vector<TextVertex> verts;
|
|
298
|
-
|
|
298
|
+
geometry->op_counts.assign(list.texts.size(), 0);
|
|
299
|
+
for (size_t index = 0; index < list.texts.size(); index++) {
|
|
300
|
+
const xtgui::PaintText &run = list.texts[index];
|
|
299
301
|
if (run.text.empty() || run.color.a <= 0.0f) continue;
|
|
300
302
|
xtgui::Font *logical = xtgui::xt_text_resolve(run.font);
|
|
301
303
|
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;
|
|
306
304
|
|
|
307
305
|
std::vector<xtgui::ShapedGlyph> glyphs;
|
|
308
306
|
xtgui::xt_text_shape_run(run.text, logical, glyphs);
|
|
309
307
|
float pen_x = run.x;
|
|
308
|
+
uint32_t before = (uint32_t)verts.size();
|
|
310
309
|
for (const xtgui::ShapedGlyph &glyph : glyphs) {
|
|
311
310
|
if (glyph.glyph != 0) {
|
|
311
|
+
xtgui::Font *face = glyph.font != nullptr ? glyph.font : logical;
|
|
312
|
+
if (scale != 1.0f) {
|
|
313
|
+
xtgui::Font *scaled = xtgui::xt_text_scale(face, run.font.pixel_size * scale);
|
|
314
|
+
if (scaled != nullptr) face = scaled;
|
|
315
|
+
}
|
|
312
316
|
GlyphEntry entry;
|
|
313
|
-
if (ensureGlyph(device,
|
|
317
|
+
if (ensureGlyph(device, face, glyph.glyph, entry)) {
|
|
314
318
|
float quad_x = pen_x + glyph.x_offset + (float)entry.left / scale;
|
|
315
319
|
float quad_y = run.baseline + glyph.y_offset - (float)entry.top / scale;
|
|
316
320
|
float quad_w = (float)entry.width / scale;
|
|
@@ -320,27 +324,8 @@ void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
320
324
|
}
|
|
321
325
|
pen_x += glyph.x_advance;
|
|
322
326
|
}
|
|
327
|
+
geometry->op_counts[index] = (uint32_t)verts.size() - before;
|
|
323
328
|
}
|
|
324
329
|
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(TextVertex));
|
|
325
330
|
flushAtlas(device);
|
|
326
331
|
}
|
|
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
|
-
}
|
package/runtime/ext_gui/style.h
CHANGED
|
@@ -22,8 +22,12 @@ namespace xtgui {
|
|
|
22
22
|
enum class Display { None, Block, Inline, InlineBlock, Flex, ListItem };
|
|
23
23
|
enum class Position { Static, Relative, Absolute, Fixed };
|
|
24
24
|
enum class FlexDirection { Row, RowReverse, Column, ColumnReverse };
|
|
25
|
+
enum class FlexWrap { NoWrap, Wrap, WrapReverse };
|
|
25
26
|
enum class JustifyContent { FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly };
|
|
26
27
|
enum class AlignItems { Stretch, FlexStart, FlexEnd, Center, Baseline };
|
|
28
|
+
enum class AlignContent { Stretch, FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround };
|
|
29
|
+
enum class FloatSide { None, Left, Right };
|
|
30
|
+
enum class Clear { None, Left, Right, Both };
|
|
27
31
|
enum class TextAlign { Start, Center, End, Justify };
|
|
28
32
|
enum class WhiteSpace { Normal, NoWrap, Pre, PreWrap, PreLine };
|
|
29
33
|
enum class Overflow { Visible, Hidden, Scroll, Auto };
|
|
@@ -84,9 +88,13 @@ struct XtStyle {
|
|
|
84
88
|
WhiteSpace white_space = WhiteSpace::Normal; // inherited
|
|
85
89
|
|
|
86
90
|
FlexDirection flex_direction = FlexDirection::Row;
|
|
91
|
+
FlexWrap flex_wrap = FlexWrap::NoWrap;
|
|
87
92
|
JustifyContent justify_content = JustifyContent::FlexStart;
|
|
88
93
|
AlignItems align_items = AlignItems::Stretch;
|
|
94
|
+
AlignContent align_content = AlignContent::Stretch;
|
|
89
95
|
Length gap;
|
|
96
|
+
Length row_gap;
|
|
97
|
+
Length column_gap;
|
|
90
98
|
float flex_grow = 0.0f;
|
|
91
99
|
float flex_shrink = 1.0f;
|
|
92
100
|
Length flex_basis;
|
|
@@ -96,6 +104,9 @@ struct XtStyle {
|
|
|
96
104
|
int z_index = 0;
|
|
97
105
|
bool has_z_index = false;
|
|
98
106
|
|
|
107
|
+
FloatSide float_side = FloatSide::None;
|
|
108
|
+
Clear clear = Clear::None;
|
|
109
|
+
|
|
99
110
|
/** `transition` specs; not inherited. Applied by `XtDocument`. */
|
|
100
111
|
std::vector<TransitionSpec> transitions;
|
|
101
112
|
};
|
|
@@ -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,9 +103,15 @@ 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);
|
|
@@ -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") {
|
|
@@ -347,6 +347,23 @@ void xt_style_apply_declaration(XtStyle *style, const std::string &property,
|
|
|
347
347
|
if (parseEnum(value, names, 4, &index)) style->flex_direction = (FlexDirection)index;
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
|
+
if (property == "flex-wrap") {
|
|
351
|
+
static const char *const names[] = {"nowrap", "wrap", "wrap-reverse"};
|
|
352
|
+
int index = 0;
|
|
353
|
+
if (parseEnum(value, names, 3, &index)) style->flex_wrap = (FlexWrap)index;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (property == "flex-flow") {
|
|
357
|
+
/* `flex-flow: <direction> || <wrap>` in either order. */
|
|
358
|
+
for (const std::string &token : xt_css_split_whitespace(value)) {
|
|
359
|
+
static const char *const dirs[] = {"row", "row-reverse", "column", "column-reverse"};
|
|
360
|
+
static const char *const wraps[] = {"nowrap", "wrap", "wrap-reverse"};
|
|
361
|
+
int index = 0;
|
|
362
|
+
if (parseEnum(token, dirs, 4, &index)) style->flex_direction = (FlexDirection)index;
|
|
363
|
+
else if (parseEnum(token, wraps, 3, &index)) style->flex_wrap = (FlexWrap)index;
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
350
367
|
if (property == "justify-content") {
|
|
351
368
|
static const char *const names[] = {"flex-start", "flex-end", "center", "space-between",
|
|
352
369
|
"space-around", "space-evenly"};
|
|
@@ -360,10 +377,20 @@ void xt_style_apply_declaration(XtStyle *style, const std::string &property,
|
|
|
360
377
|
if (parseEnum(value, names, 5, &index)) style->align_items = (AlignItems)index;
|
|
361
378
|
return;
|
|
362
379
|
}
|
|
363
|
-
if (property == "
|
|
380
|
+
if (property == "align-content") {
|
|
381
|
+
static const char *const names[] = {"stretch", "flex-start", "flex-end", "center",
|
|
382
|
+
"space-between", "space-around"};
|
|
383
|
+
int index = 0;
|
|
384
|
+
if (parseEnum(value, names, 6, &index)) style->align_content = (AlignContent)index;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (property == "gap") {
|
|
364
388
|
assignLength(&style->gap, value);
|
|
389
|
+
style->row_gap = style->column_gap = style->gap;
|
|
365
390
|
return;
|
|
366
391
|
}
|
|
392
|
+
if (property == "row-gap") { assignLength(&style->row_gap, value); return; }
|
|
393
|
+
if (property == "column-gap") { assignLength(&style->column_gap, value); return; }
|
|
367
394
|
if (property == "flex-grow") {
|
|
368
395
|
float number = 0;
|
|
369
396
|
if (xt_css_parse_number(value, &number)) style->flex_grow = number;
|
|
@@ -433,6 +460,18 @@ void xt_style_apply_declaration(XtStyle *style, const std::string &property,
|
|
|
433
460
|
}
|
|
434
461
|
return;
|
|
435
462
|
}
|
|
463
|
+
if (property == "float") {
|
|
464
|
+
static const char *const names[] = {"none", "left", "right"};
|
|
465
|
+
int index = 0;
|
|
466
|
+
if (parseEnum(value, names, 3, &index)) style->float_side = (FloatSide)index;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (property == "clear") {
|
|
470
|
+
static const char *const names[] = {"none", "left", "right", "both"};
|
|
471
|
+
int index = 0;
|
|
472
|
+
if (parseEnum(value, names, 4, &index)) style->clear = (Clear)index;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
436
475
|
if (property == "transition") {
|
|
437
476
|
parseTransitionShorthand(value, &style->transitions);
|
|
438
477
|
return;
|