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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
#include "paint.h"
|
|
6
6
|
|
|
7
|
+
#include <algorithm>
|
|
7
8
|
#include <cstdio>
|
|
8
9
|
#include <utility>
|
|
9
10
|
|
|
@@ -21,6 +22,18 @@ std::string formatColor(const Color &color) {
|
|
|
21
22
|
return buffer;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** ` transform=matrix(...)` for a non-identity matrix, so the dump (and the
|
|
26
|
+
* tests that read it) can see where a box was transformed to. */
|
|
27
|
+
std::string transformSuffix(const Matrix2D &matrix) {
|
|
28
|
+
if (matrix.identity()) return "";
|
|
29
|
+
char buffer[192];
|
|
30
|
+
std::snprintf(buffer, sizeof(buffer),
|
|
31
|
+
" transform=matrix(%.4g,%.4g,%.4g,%.4g,%.4g,%.4g)", (double)matrix.a,
|
|
32
|
+
(double)matrix.b, (double)matrix.c, (double)matrix.d, (double)matrix.e,
|
|
33
|
+
(double)matrix.f);
|
|
34
|
+
return buffer;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
FontSpec specOf(const XtStyle &style) {
|
|
25
38
|
FontSpec spec;
|
|
26
39
|
spec.family = style.font_family;
|
|
@@ -31,7 +44,7 @@ FontSpec specOf(const XtStyle &style) {
|
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
void addRect(DisplayList &out, float x, float y, float width, float height, float radius,
|
|
34
|
-
const Color &color) {
|
|
47
|
+
const Color &color, const PaintClip &clip, const Matrix2D &transform) {
|
|
35
48
|
if (width <= 0.0f || height <= 0.0f || color.a <= 0.0f) return;
|
|
36
49
|
PaintRect rect;
|
|
37
50
|
rect.x = x;
|
|
@@ -40,10 +53,13 @@ void addRect(DisplayList &out, float x, float y, float width, float height, floa
|
|
|
40
53
|
rect.height = height;
|
|
41
54
|
rect.radius = radius;
|
|
42
55
|
rect.color = color;
|
|
56
|
+
rect.transform = transform;
|
|
43
57
|
out.rects.push_back(rect);
|
|
58
|
+
out.ops.push_back(PaintOp{PaintKind::Rect, (uint32_t)(out.rects.size() - 1), clip});
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out
|
|
61
|
+
void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out,
|
|
62
|
+
const PaintClip &clip) {
|
|
47
63
|
if (box->fragments.empty() || style.color.a <= 0.0f) return;
|
|
48
64
|
FontSpec spec = specOf(style);
|
|
49
65
|
float ascent = 0;
|
|
@@ -59,29 +75,83 @@ void addTextRuns(const LayoutBox *box, const XtStyle &style, DisplayList &out) {
|
|
|
59
75
|
run.color = style.color;
|
|
60
76
|
run.text = fragment.text;
|
|
61
77
|
run.font = spec;
|
|
78
|
+
run.transform = box->world;
|
|
62
79
|
out.texts.push_back(std::move(run));
|
|
80
|
+
out.ops.push_back(PaintOp{PaintKind::Text, (uint32_t)(out.texts.size() - 1), clip});
|
|
63
81
|
}
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
void
|
|
84
|
+
void addImage(const PaintImage &image, DisplayList &out, const PaintClip &clip) {
|
|
85
|
+
out.images.push_back(image);
|
|
86
|
+
out.ops.push_back(PaintOp{PaintKind::Image, (uint32_t)(out.images.size() - 1), clip});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip);
|
|
90
|
+
|
|
91
|
+
/** The clip a box imposes on its content: its padding box, intersected with the
|
|
92
|
+
* clip already in effect. Only `overflow: hidden/scroll/auto` clips. A
|
|
93
|
+
* transformed box clips to the bounding box of its transformed padding box. */
|
|
94
|
+
PaintClip clipForChildren(const LayoutBox *box, const PaintClip &parent) {
|
|
95
|
+
if (box->style == nullptr || box->style->overflow == Overflow::Visible) return parent;
|
|
96
|
+
float x = box->x + box->border_left;
|
|
97
|
+
float y = box->y + box->border_top;
|
|
98
|
+
float width = box->width - box->border_left - box->border_right;
|
|
99
|
+
float height = box->height - box->border_top - box->border_bottom;
|
|
100
|
+
if (width < 0) width = 0;
|
|
101
|
+
if (height < 0) height = 0;
|
|
102
|
+
if (box->has_transform) {
|
|
103
|
+
LayoutRect bounds = xt_layout_transform_rect(box->world, x, y, width, height);
|
|
104
|
+
x = bounds.x;
|
|
105
|
+
y = bounds.y;
|
|
106
|
+
width = bounds.width;
|
|
107
|
+
height = bounds.height;
|
|
108
|
+
}
|
|
109
|
+
if (!parent.active) return PaintClip{true, x, y, width, height};
|
|
110
|
+
float x0 = std::max(x, parent.x);
|
|
111
|
+
float y0 = std::max(y, parent.y);
|
|
112
|
+
float x1 = std::min(x + width, parent.x + parent.width);
|
|
113
|
+
float y1 = std::min(y + height, parent.y + parent.height);
|
|
114
|
+
return PaintClip{true, x0, y0, std::max(0.0f, x1 - x0), std::max(0.0f, y1 - y0)};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
int zIndexOf(const LayoutBox *box) {
|
|
118
|
+
if (box->style == nullptr || !box->style->has_z_index) return 0;
|
|
119
|
+
return box->style->z_index;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Paint `box`'s children following z-index: negative z first (ascending), then
|
|
123
|
+
* auto/zero in document order, then positive z (ascending), so higher stacks
|
|
124
|
+
* paint last. */
|
|
125
|
+
void paintChildren(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
|
|
126
|
+
std::vector<const LayoutBox *> children;
|
|
127
|
+
for (const std::unique_ptr<LayoutBox> &child : box->children) {
|
|
128
|
+
if (child->display == Display::None || child->style == nullptr) continue;
|
|
129
|
+
children.push_back(child.get());
|
|
130
|
+
}
|
|
131
|
+
std::stable_sort(children.begin(), children.end(), [](const LayoutBox *a, const LayoutBox *b) {
|
|
132
|
+
return zIndexOf(a) < zIndexOf(b);
|
|
133
|
+
});
|
|
134
|
+
for (const LayoutBox *child : children) paintBox(child, out, clip);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
void paintBox(const LayoutBox *box, DisplayList &out, const PaintClip &clip) {
|
|
67
138
|
if (box == nullptr) return;
|
|
68
139
|
const XtStyle *style = box->style;
|
|
69
140
|
|
|
70
|
-
/* A fully transparent box paints nothing — not itself, and not its subtree.
|
|
71
|
-
* `opacity` is otherwise unsupported: a shape carries its colour's alpha only,
|
|
72
|
-
* so a partly transparent box (an `opacity` transition mid-flight, say) is
|
|
73
|
-
* still painted opaque. */
|
|
141
|
+
/* A fully transparent box paints nothing — not itself, and not its subtree. */
|
|
74
142
|
if (style != nullptr && style->opacity <= 0.0f) return;
|
|
75
143
|
|
|
76
144
|
if (style != nullptr && box->display != Display::None) {
|
|
77
145
|
if (box->is_text) {
|
|
78
|
-
addTextRuns(box, *style, out);
|
|
146
|
+
addTextRuns(box, *style, out, clip);
|
|
79
147
|
} else {
|
|
80
|
-
/* Background first, then the
|
|
81
|
-
*
|
|
148
|
+
/* Background first, then the border ring, then replaced content. Children
|
|
149
|
+
* are clipped by this box's padding box when it establishes one. Every
|
|
150
|
+
* shape carries the box's `transform` matrix for the renderer. */
|
|
151
|
+
const Matrix2D &matrix = box->world;
|
|
82
152
|
if (style->has_background) {
|
|
83
153
|
addRect(out, box->x, box->y, box->width, box->height, style->border_radius,
|
|
84
|
-
style->background);
|
|
154
|
+
style->background, clip, matrix);
|
|
85
155
|
}
|
|
86
156
|
if (style->border_style != BorderStyle::None && style->has_border_color) {
|
|
87
157
|
const Color bc = style->border_color;
|
|
@@ -90,52 +160,100 @@ void paintBox(const LayoutBox *box, DisplayList &out) {
|
|
|
90
160
|
float bottom = box->border_bottom;
|
|
91
161
|
float left = box->border_left;
|
|
92
162
|
float inner_height = box->height - top - bottom;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
addRect(out, box->x, box->y +
|
|
97
|
-
addRect(out, box->x, box->y + top,
|
|
98
|
-
|
|
163
|
+
addRect(out, box->x, box->y, box->width, top, 0.0f, bc, clip, matrix);
|
|
164
|
+
addRect(out, box->x, box->y + box->height - bottom, box->width, bottom, 0.0f, bc, clip,
|
|
165
|
+
matrix);
|
|
166
|
+
addRect(out, box->x, box->y + top, left, inner_height, 0.0f, bc, clip, matrix);
|
|
167
|
+
addRect(out, box->x + box->width - right, box->y + top, right, inner_height, 0.0f, bc,
|
|
168
|
+
clip, matrix);
|
|
99
169
|
}
|
|
100
|
-
if (box->
|
|
170
|
+
if (box->is_canvas && box->canvas_id != 0 && box->content_width > 0.0f &&
|
|
101
171
|
box->content_height > 0.0f) {
|
|
102
172
|
PaintImage image;
|
|
103
173
|
image.x = box->content_x;
|
|
104
174
|
image.y = box->content_y;
|
|
105
175
|
image.width = box->content_width;
|
|
106
176
|
image.height = box->content_height;
|
|
177
|
+
image.canvas_id = box->canvas_id;
|
|
178
|
+
image.transform = matrix;
|
|
179
|
+
addImage(image, out, clip);
|
|
180
|
+
} else if (box->is_media && box->media_id != 0 && box->content_width > 0.0f &&
|
|
181
|
+
box->content_height > 0.0f) {
|
|
182
|
+
PaintImage image;
|
|
183
|
+
image.x = box->content_x;
|
|
184
|
+
image.y = box->content_y;
|
|
185
|
+
image.width = box->content_width;
|
|
186
|
+
image.height = box->content_height;
|
|
187
|
+
image.media_id = box->media_id;
|
|
188
|
+
image.transform = matrix;
|
|
189
|
+
addImage(image, out, clip);
|
|
190
|
+
} else if (box->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
|
|
191
|
+
box->content_height > 0.0f) {
|
|
192
|
+
PaintImage image;
|
|
193
|
+
image.x = box->content_x;
|
|
194
|
+
image.y = box->content_y;
|
|
195
|
+
image.width = box->content_width;
|
|
196
|
+
image.height = box->content_height;
|
|
107
197
|
image.src = box->image_src;
|
|
108
|
-
|
|
198
|
+
image.transform = matrix;
|
|
199
|
+
addImage(image, out, clip);
|
|
109
200
|
}
|
|
110
201
|
}
|
|
111
202
|
}
|
|
112
203
|
|
|
113
|
-
|
|
204
|
+
paintChildren(box, out, clipForChildren(box, clip));
|
|
114
205
|
}
|
|
115
206
|
|
|
116
207
|
} // namespace
|
|
117
208
|
|
|
118
|
-
void xt_paint_build(const LayoutBox *root, DisplayList &out) {
|
|
209
|
+
void xt_paint_build(const LayoutBox *root, DisplayList &out) {
|
|
210
|
+
PaintClip none;
|
|
211
|
+
paintBox(root, out, none);
|
|
212
|
+
}
|
|
119
213
|
|
|
120
214
|
std::string DisplayList::dump() const {
|
|
121
215
|
std::string text;
|
|
122
|
-
char buffer[
|
|
123
|
-
for (const
|
|
124
|
-
std::
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
std::
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
216
|
+
char buffer[288];
|
|
217
|
+
for (const PaintOp &op : ops) {
|
|
218
|
+
std::string prefix;
|
|
219
|
+
if (op.clip.active) {
|
|
220
|
+
std::snprintf(buffer, sizeof(buffer), "clip=%.1f,%.1f,%.1f,%.1f ", op.clip.x, op.clip.y,
|
|
221
|
+
op.clip.width, op.clip.height);
|
|
222
|
+
prefix = buffer;
|
|
223
|
+
}
|
|
224
|
+
std::string suffix;
|
|
225
|
+
if (op.kind == PaintKind::Rect) {
|
|
226
|
+
suffix = transformSuffix(rects[op.index].transform);
|
|
227
|
+
const PaintRect &rect = rects[op.index];
|
|
228
|
+
std::snprintf(buffer, sizeof(buffer),
|
|
229
|
+
"rect x=%.1f y=%.1f w=%.1f h=%.1f r=%.1f color=%s", rect.x, rect.y,
|
|
230
|
+
rect.width, rect.height, rect.radius, formatColor(rect.color).c_str());
|
|
231
|
+
text += prefix + buffer;
|
|
232
|
+
} else if (op.kind == PaintKind::Text) {
|
|
233
|
+
suffix = transformSuffix(texts[op.index].transform);
|
|
234
|
+
const PaintText &run = texts[op.index];
|
|
235
|
+
std::snprintf(buffer, sizeof(buffer),
|
|
236
|
+
"text x=%.1f baseline=%.1f size=%.1f color=%s \"%s\"", run.x, run.baseline,
|
|
237
|
+
run.font.pixel_size, formatColor(run.color).c_str(), run.text.c_str());
|
|
238
|
+
text += prefix + buffer;
|
|
239
|
+
} else {
|
|
240
|
+
const PaintImage &image = images[op.index];
|
|
241
|
+
suffix = transformSuffix(image.transform);
|
|
242
|
+
if (image.media_id != 0) {
|
|
243
|
+
std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu",
|
|
244
|
+
image.x, image.y, image.width, image.height,
|
|
245
|
+
(unsigned long long)image.media_id);
|
|
246
|
+
} else if (image.canvas_id != 0) {
|
|
247
|
+
std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f canvas=%llu",
|
|
248
|
+
image.x, image.y, image.width, image.height,
|
|
249
|
+
(unsigned long long)image.canvas_id);
|
|
250
|
+
} else {
|
|
251
|
+
std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f src=%s", image.x,
|
|
252
|
+
image.y, image.width, image.height, image.src.c_str());
|
|
253
|
+
}
|
|
254
|
+
text += prefix + buffer;
|
|
255
|
+
}
|
|
256
|
+
text += suffix + "\n";
|
|
139
257
|
}
|
|
140
258
|
return text;
|
|
141
259
|
}
|
package/runtime/ext_gui/paint.h
CHANGED
|
@@ -6,19 +6,21 @@
|
|
|
6
6
|
* the same list into triangles (rectangles) and glyph quads (text), and
|
|
7
7
|
* diagnostics/tests can inspect it without a GPU.
|
|
8
8
|
*
|
|
9
|
-
* Rectangles and
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*/
|
|
9
|
+
* Rectangles, text and images share one painter-order `ops` list, so a
|
|
10
|
+
* `z-index`ed child can paint over a sibling's background and an `overflow`
|
|
11
|
+
* clip applies to every shape in the subtree. Coordinates are viewport-
|
|
12
|
+
* relative. */
|
|
13
13
|
#ifndef XT_GUI_PAINT_H
|
|
14
14
|
#define XT_GUI_PAINT_H
|
|
15
15
|
|
|
16
|
+
#include <cstdint>
|
|
16
17
|
#include <string>
|
|
17
18
|
#include <vector>
|
|
18
19
|
|
|
19
20
|
#include "css.h"
|
|
20
21
|
#include "layout.h"
|
|
21
22
|
#include "text.h"
|
|
23
|
+
#include "transform.h"
|
|
22
24
|
|
|
23
25
|
namespace xtgui {
|
|
24
26
|
|
|
@@ -30,6 +32,8 @@ struct PaintRect {
|
|
|
30
32
|
float height = 0;
|
|
31
33
|
float radius = 0;
|
|
32
34
|
Color color;
|
|
35
|
+
/** `transform` of the box this shape belongs to (layout -> device space). */
|
|
36
|
+
Matrix2D transform;
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
/** A run of text to draw at `x`/`baseline` (viewport-relative). */
|
|
@@ -41,29 +45,61 @@ struct PaintText {
|
|
|
41
45
|
Color color;
|
|
42
46
|
std::string text;
|
|
43
47
|
FontSpec font;
|
|
48
|
+
Matrix2D transform;
|
|
44
49
|
};
|
|
45
50
|
|
|
46
|
-
/** An image to draw, scaled into the given rectangle (viewport-relative).
|
|
51
|
+
/** An image to draw, scaled into the given rectangle (viewport-relative).
|
|
52
|
+
* A non-zero `media_id` names a live `<video>` frame owned by the media
|
|
53
|
+
* subsystem; a non-zero `canvas_id` names a live `<canvas>` bitmap owned by the
|
|
54
|
+
* DOM; `src` is then empty and the renderer binds that subsystem's texture. */
|
|
47
55
|
struct PaintImage {
|
|
48
56
|
float x = 0;
|
|
49
57
|
float y = 0;
|
|
50
58
|
float width = 0;
|
|
51
59
|
float height = 0;
|
|
52
60
|
std::string src;
|
|
61
|
+
uint64_t media_id = 0;
|
|
62
|
+
uint64_t canvas_id = 0;
|
|
63
|
+
Matrix2D transform;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** The clip rectangle in effect for a painted shape (device-space logical
|
|
67
|
+
* pixels — a transformed subtree contributes the bounding box of its clip).
|
|
68
|
+
* `width <= 0` means "no clip". */
|
|
69
|
+
struct PaintClip {
|
|
70
|
+
bool active = false;
|
|
71
|
+
float x = 0;
|
|
72
|
+
float y = 0;
|
|
73
|
+
float width = 0;
|
|
74
|
+
float height = 0;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
enum class PaintKind { Rect, Text, Image };
|
|
78
|
+
|
|
79
|
+
/** One entry in painter order: which kind of shape, its index in the matching
|
|
80
|
+
* payload vector, and the clip in effect. */
|
|
81
|
+
struct PaintOp {
|
|
82
|
+
PaintKind kind = PaintKind::Rect;
|
|
83
|
+
uint32_t index = 0;
|
|
84
|
+
PaintClip clip;
|
|
53
85
|
};
|
|
54
86
|
|
|
55
87
|
struct DisplayList {
|
|
56
88
|
std::vector<PaintRect> rects;
|
|
57
89
|
std::vector<PaintText> texts;
|
|
58
90
|
std::vector<PaintImage> images;
|
|
91
|
+
/** Shapes in painter order (backgrounds/borders first, then children, then
|
|
92
|
+
* stacked siblings), each with the clip rectangle that bounds it. */
|
|
93
|
+
std::vector<PaintOp> ops;
|
|
59
94
|
|
|
60
95
|
void clear() {
|
|
61
96
|
rects.clear();
|
|
62
97
|
texts.clear();
|
|
63
98
|
images.clear();
|
|
99
|
+
ops.clear();
|
|
64
100
|
}
|
|
65
101
|
bool empty() const { return rects.empty() && texts.empty() && images.empty(); }
|
|
66
|
-
/** Human-readable dump, one shape per line (
|
|
102
|
+
/** Human-readable dump, one shape per line, in painter order (diagnostics/tests). */
|
|
67
103
|
std::string dump() const;
|
|
68
104
|
};
|
|
69
105
|
|
|
@@ -28,7 +28,6 @@ SDL_GPUGraphicsPipeline *g_image_pipeline = nullptr;
|
|
|
28
28
|
SDL_GPUSampler *g_sampler = nullptr;
|
|
29
29
|
int g_renderer_failed = 0;
|
|
30
30
|
bool g_text_ok = false;
|
|
31
|
-
std::vector<ImageBatch> g_image_batches;
|
|
32
31
|
std::unordered_map<std::string, SDL_GPUTexture *> g_image_textures;
|
|
33
32
|
SDL_GPUTexture *g_atlas_texture = nullptr;
|
|
34
33
|
std::vector<unsigned char> g_atlas_mirror;
|
|
@@ -51,8 +50,10 @@ void pushRect(std::vector<Vertex> &verts, const xtgui::PaintRect &rect) {
|
|
|
51
50
|
const float local_y[4] = {-hh, -hh, hh, hh};
|
|
52
51
|
Vertex corners[4];
|
|
53
52
|
for (int i = 0; i < 4; i++) {
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
/* `local` keeps the distance-field coordinates in the box's own frame, so
|
|
54
|
+
* a rotated/scaled shape still gets a rounded rectangle (the radius scales
|
|
55
|
+
* with the matrix instead of staying circular). */
|
|
56
|
+
rect.transform.apply(cx + local_x[i], cy + local_y[i], &corners[i].x, &corners[i].y);
|
|
56
57
|
corners[i].lx = local_x[i];
|
|
57
58
|
corners[i].ly = local_y[i];
|
|
58
59
|
corners[i].hx = hw;
|
|
@@ -246,7 +247,6 @@ void xt_gui_renderer_destroy(SDL_GPUDevice *device) {
|
|
|
246
247
|
if (entry.second != nullptr) SDL_ReleaseGPUTexture(device, entry.second);
|
|
247
248
|
}
|
|
248
249
|
g_image_textures.clear();
|
|
249
|
-
g_image_batches.clear();
|
|
250
250
|
if (g_atlas_texture != nullptr) {
|
|
251
251
|
SDL_ReleaseGPUTexture(device, g_atlas_texture);
|
|
252
252
|
g_atlas_texture = nullptr;
|
|
@@ -277,21 +277,126 @@ void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
277
277
|
using namespace xtgui::renderer_detail;
|
|
278
278
|
std::vector<Vertex> verts;
|
|
279
279
|
verts.reserve(list.rects.size() * 6);
|
|
280
|
+
geometry->op_counts.assign(list.rects.size(), 6);
|
|
280
281
|
for (const xtgui::PaintRect &rect : list.rects) pushRect(verts, rect);
|
|
281
282
|
uploadVertices(device, geometry, verts.data(), (uint32_t)verts.size(), sizeof(Vertex));
|
|
282
283
|
}
|
|
283
284
|
|
|
285
|
+
void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
286
|
+
const xtgui::DisplayList &list) {
|
|
287
|
+
shapes->runs.clear();
|
|
288
|
+
uint32_t cursor[3] = {0, 0, 0};
|
|
289
|
+
for (const xtgui::PaintOp &op : list.ops) {
|
|
290
|
+
int pipeline = (int)op.kind;
|
|
291
|
+
XtGuiGeometry *geometry = pipeline == 0 ? shapes : (pipeline == 1 ? texts : images);
|
|
292
|
+
if (op.index >= geometry->op_counts.size()) continue;
|
|
293
|
+
uint32_t count = geometry->op_counts[op.index];
|
|
294
|
+
uint32_t first = cursor[pipeline];
|
|
295
|
+
cursor[pipeline] += count;
|
|
296
|
+
if (count == 0) continue;
|
|
297
|
+
SDL_GPUTexture *texture = nullptr;
|
|
298
|
+
if (pipeline == 2 && op.index < images->op_textures.size()) texture = images->op_textures[op.index];
|
|
299
|
+
float cx = op.clip.active ? op.clip.x : 0.0f;
|
|
300
|
+
float cy = op.clip.active ? op.clip.y : 0.0f;
|
|
301
|
+
float cw = op.clip.active ? op.clip.width : 0.0f;
|
|
302
|
+
float ch = op.clip.active ? op.clip.height : 0.0f;
|
|
303
|
+
if (!shapes->runs.empty()) {
|
|
304
|
+
XtGuiDrawRun &last = shapes->runs.back();
|
|
305
|
+
if (last.pipeline == pipeline && last.first + last.count == first && last.clip_x == cx &&
|
|
306
|
+
last.clip_y == cy && last.clip_width == cw && last.clip_height == ch &&
|
|
307
|
+
last.texture == texture) {
|
|
308
|
+
last.count += count;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
XtGuiDrawRun run;
|
|
313
|
+
run.pipeline = pipeline;
|
|
314
|
+
run.first = first;
|
|
315
|
+
run.count = count;
|
|
316
|
+
run.clip_x = cx;
|
|
317
|
+
run.clip_y = cy;
|
|
318
|
+
run.clip_width = cw;
|
|
319
|
+
run.clip_height = ch;
|
|
320
|
+
run.texture = texture;
|
|
321
|
+
shapes->runs.push_back(run);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
284
325
|
void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
285
|
-
XtGuiGeometry *
|
|
326
|
+
XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
327
|
+
float viewport_width, float viewport_height, float pixel_density) {
|
|
286
328
|
using namespace xtgui::renderer_detail;
|
|
287
|
-
if (g_pipeline == nullptr || geometry->buffer == nullptr || geometry->vertex_count == 0) return;
|
|
288
329
|
if (viewport_width <= 0.0f || viewport_height <= 0.0f) return;
|
|
289
|
-
|
|
290
|
-
SDL_GPUBufferBinding binding;
|
|
291
|
-
binding.buffer = geometry->buffer;
|
|
292
|
-
binding.offset = 0;
|
|
293
|
-
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
330
|
+
if (pixel_density <= 0.0f) pixel_density = 1.0f;
|
|
294
331
|
float viewport[2] = {viewport_width, viewport_height};
|
|
295
332
|
SDL_PushGPUVertexUniformData(cmd, 0, viewport, sizeof(viewport));
|
|
296
|
-
|
|
333
|
+
|
|
334
|
+
int target_width = (int)(viewport_width * pixel_density + 0.5f);
|
|
335
|
+
int target_height = (int)(viewport_height * pixel_density + 0.5f);
|
|
336
|
+
for (const XtGuiDrawRun &run : shapes->runs) {
|
|
337
|
+
SDL_Rect scissor;
|
|
338
|
+
scissor.x = 0;
|
|
339
|
+
scissor.y = 0;
|
|
340
|
+
scissor.w = target_width;
|
|
341
|
+
scissor.h = target_height;
|
|
342
|
+
if (run.clip_width > 0.0f && run.clip_height > 0.0f) {
|
|
343
|
+
int x0 = (int)(run.clip_x * pixel_density);
|
|
344
|
+
int y0 = (int)(run.clip_y * pixel_density);
|
|
345
|
+
int x1 = (int)((run.clip_x + run.clip_width) * pixel_density + 0.5f);
|
|
346
|
+
int y1 = (int)((run.clip_y + run.clip_height) * pixel_density + 0.5f);
|
|
347
|
+
if (x0 < 0) x0 = 0;
|
|
348
|
+
if (y0 < 0) y0 = 0;
|
|
349
|
+
if (x1 > target_width) x1 = target_width;
|
|
350
|
+
if (y1 > target_height) y1 = target_height;
|
|
351
|
+
scissor.x = x0;
|
|
352
|
+
scissor.y = y0;
|
|
353
|
+
scissor.w = x1 > x0 ? x1 - x0 : 0;
|
|
354
|
+
scissor.h = y1 > y0 ? y1 - y0 : 0;
|
|
355
|
+
}
|
|
356
|
+
SDL_SetGPUScissor(pass, &scissor);
|
|
357
|
+
|
|
358
|
+
if (run.pipeline == XtGuiDrawRun::Shape) {
|
|
359
|
+
if (g_pipeline == nullptr || shapes->buffer == nullptr) continue;
|
|
360
|
+
SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
|
|
361
|
+
SDL_GPUBufferBinding binding;
|
|
362
|
+
binding.buffer = shapes->buffer;
|
|
363
|
+
binding.offset = 0;
|
|
364
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
365
|
+
SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
|
|
366
|
+
} else if (run.pipeline == XtGuiDrawRun::Text) {
|
|
367
|
+
if (!g_text_ok || g_atlas_texture == nullptr || texts->buffer == nullptr) continue;
|
|
368
|
+
SDL_BindGPUGraphicsPipeline(pass, g_text_pipeline);
|
|
369
|
+
SDL_GPUBufferBinding binding;
|
|
370
|
+
binding.buffer = texts->buffer;
|
|
371
|
+
binding.offset = 0;
|
|
372
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
373
|
+
SDL_GPUTextureSamplerBinding atlas_binding;
|
|
374
|
+
atlas_binding.texture = g_atlas_texture;
|
|
375
|
+
atlas_binding.sampler = g_sampler;
|
|
376
|
+
SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1);
|
|
377
|
+
SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
|
|
378
|
+
} else {
|
|
379
|
+
if (g_image_pipeline == nullptr || g_sampler == nullptr || images->buffer == nullptr ||
|
|
380
|
+
run.texture == nullptr)
|
|
381
|
+
continue;
|
|
382
|
+
SDL_BindGPUGraphicsPipeline(pass, g_image_pipeline);
|
|
383
|
+
SDL_GPUBufferBinding binding;
|
|
384
|
+
binding.buffer = images->buffer;
|
|
385
|
+
binding.offset = 0;
|
|
386
|
+
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
387
|
+
SDL_GPUTextureSamplerBinding texture_binding;
|
|
388
|
+
texture_binding.texture = run.texture;
|
|
389
|
+
texture_binding.sampler = g_sampler;
|
|
390
|
+
SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
|
|
391
|
+
SDL_DrawGPUPrimitives(pass, run.count, 1, run.first, 0);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Restore the full-target scissor so the next frame starts clean. */
|
|
396
|
+
SDL_Rect full;
|
|
397
|
+
full.x = 0;
|
|
398
|
+
full.y = 0;
|
|
399
|
+
full.w = target_width;
|
|
400
|
+
full.h = target_height;
|
|
401
|
+
SDL_SetGPUScissor(pass, &full);
|
|
297
402
|
}
|
|
@@ -22,17 +22,43 @@
|
|
|
22
22
|
|
|
23
23
|
#include <SDL3/SDL.h>
|
|
24
24
|
#include <cstdint>
|
|
25
|
+
#include <vector>
|
|
25
26
|
|
|
26
27
|
namespace xtgui {
|
|
27
28
|
struct DisplayList;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/** One draw call in painter order: a contiguous range of vertices in one of the
|
|
32
|
+
* three pipelines, with the clip rectangle (logical pixels) that bounds it. A
|
|
33
|
+
* `clip_width <= 0` means the whole viewport. For image runs, `texture` is the
|
|
34
|
+
* bound texture. */
|
|
35
|
+
struct XtGuiDrawRun {
|
|
36
|
+
enum Pipeline { Shape = 0, Text = 1, Image = 2 };
|
|
37
|
+
int pipeline = Shape;
|
|
38
|
+
uint32_t first = 0;
|
|
39
|
+
uint32_t count = 0;
|
|
40
|
+
float clip_x = 0;
|
|
41
|
+
float clip_y = 0;
|
|
42
|
+
float clip_width = 0;
|
|
43
|
+
float clip_height = 0;
|
|
44
|
+
SDL_GPUTexture *texture = nullptr;
|
|
45
|
+
};
|
|
46
|
+
|
|
30
47
|
/** Per-window GPU geometry (vertex buffer + draw range). */
|
|
31
48
|
struct XtGuiGeometry {
|
|
32
49
|
SDL_GPUBuffer *buffer = nullptr;
|
|
33
50
|
uint32_t capacity_bytes = 0;
|
|
34
51
|
uint32_t vertex_count = 0;
|
|
35
52
|
bool dirty = true;
|
|
53
|
+
/** Vertex count contributed by each shape of this pipeline's kind, aligned
|
|
54
|
+
* with the display list's payload vector (used to build the run list). */
|
|
55
|
+
std::vector<uint32_t> op_counts;
|
|
56
|
+
/** Texture for each image op, aligned with `DisplayList::images` (image
|
|
57
|
+
* geometry only). */
|
|
58
|
+
std::vector<SDL_GPUTexture *> op_textures;
|
|
59
|
+
/** Painter-order draw runs, built from the display list by
|
|
60
|
+
* `xt_gui_build_runs` and consumed by `xt_gui_renderer_draw`. */
|
|
61
|
+
std::vector<XtGuiDrawRun> runs;
|
|
36
62
|
};
|
|
37
63
|
|
|
38
64
|
/** Create the shared pipelines if needed. Returns false when the backend has no
|
|
@@ -47,22 +73,36 @@ void xt_gui_geometry_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
|
47
73
|
const xtgui::DisplayList &list);
|
|
48
74
|
/** Draw `geometry` inside an active render pass. */
|
|
49
75
|
void xt_gui_renderer_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass,
|
|
50
|
-
XtGuiGeometry *
|
|
76
|
+
XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
77
|
+
float viewport_width, float viewport_height, float pixel_density);
|
|
51
78
|
/** Shape `list.texts`, ensure their glyphs are in the atlas, and upload the
|
|
52
79
|
* text geometry. `scale` is the device pixel density (1.0 = logical pixels). */
|
|
53
80
|
void xt_gui_text_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
54
81
|
const xtgui::DisplayList &list, float scale);
|
|
55
|
-
/** Draw the text geometry (glyph atlas bound) inside an active render pass. */
|
|
56
|
-
void xt_gui_text_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
|
|
57
|
-
float viewport_width, float viewport_height);
|
|
58
82
|
/** Decode/upload `list.images` and build the image quad geometry (one batch per
|
|
59
|
-
* texture). Textures are cached by source path.
|
|
83
|
+
* texture). Textures are cached by source path and pixel size. `scale` is the
|
|
84
|
+
* device pixel density (1.0 = logical pixels) used to pick the rasterisation
|
|
85
|
+
* size of a vector source (SVG); raster sources decode at their intrinsic size. */
|
|
60
86
|
void xt_gui_image_upload(SDL_GPUDevice *device, XtGuiGeometry *geometry,
|
|
61
|
-
const xtgui::DisplayList &list);
|
|
62
|
-
/** Draw the image geometry (binds each batch's texture) inside a render pass. */
|
|
63
|
-
void xt_gui_image_draw(SDL_GPUCommandBuffer *cmd, SDL_GPURenderPass *pass, XtGuiGeometry *geometry,
|
|
64
|
-
float viewport_width, float viewport_height);
|
|
87
|
+
const xtgui::DisplayList &list, float scale);
|
|
65
88
|
/** Release the image texture cache (after SDL_WaitForGPUIdle). */
|
|
66
89
|
void xt_gui_image_textures_destroy(SDL_GPUDevice *device);
|
|
90
|
+
/** Build the painter-order run list from the display list, using the per-op
|
|
91
|
+
* vertex counts and textures recorded by the three upload functions. Stores the
|
|
92
|
+
* result in `shapes->runs`. */
|
|
93
|
+
void xt_gui_build_runs(XtGuiGeometry *shapes, XtGuiGeometry *texts, XtGuiGeometry *images,
|
|
94
|
+
const xtgui::DisplayList &list);
|
|
95
|
+
/** Re-upload the current decoded frame of every live `<video>` texture. Called
|
|
96
|
+
* once per frame, before the render command buffer, so the texture sampled by
|
|
97
|
+
* the draw already holds that frame. */
|
|
98
|
+
void xt_gui_media_upload_frames(SDL_GPUDevice *device);
|
|
99
|
+
/** Release one media texture (when a `<video>` is removed or navigated away). */
|
|
100
|
+
void xt_gui_media_texture_release(SDL_GPUDevice *device, uint64_t media_id);
|
|
101
|
+
/** Re-upload the current bitmap of every live `<canvas>` texture. Called once
|
|
102
|
+
* per frame, before the render command buffer, so a canvas animated from
|
|
103
|
+
* `requestAnimationFrame` reaches the screen without a relayout. */
|
|
104
|
+
void xt_gui_canvas_upload_frames(SDL_GPUDevice *device);
|
|
105
|
+
/** Release one canvas texture (when the canvas is dropped). */
|
|
106
|
+
void xt_gui_canvas_texture_release(SDL_GPUDevice *device, uint64_t canvas_id);
|
|
67
107
|
|
|
68
108
|
#endif /* XT_GUI_RENDERER_H */
|