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
@@ -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 paintBox(const LayoutBox *box, DisplayList &out) {
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 (sharp-cornered) border ring on top, then the
81
- * children, so their content paints over the padding. */
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
- /* Top/bottom span the whole width and overlap the side edges, which is
94
- * harmless (same colour) and avoids seams from float rounding. */
95
- addRect(out, box->x, box->y, box->width, top, 0.0f, bc);
96
- addRect(out, box->x, box->y + box->height - bottom, box->width, bottom, 0.0f, bc);
97
- addRect(out, box->x, box->y + top, left, inner_height, 0.0f, bc);
98
- addRect(out, box->x + box->width - right, box->y + top, right, inner_height, 0.0f, bc);
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->is_image && !box->image_src.empty() && box->content_width > 0.0f &&
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
- out.images.push_back(std::move(image));
198
+ image.transform = matrix;
199
+ addImage(image, out, clip);
109
200
  }
110
201
  }
111
202
  }
112
203
 
113
- for (const std::unique_ptr<LayoutBox> &child : box->children) paintBox(child.get(), out);
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) { paintBox(root, 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[192];
123
- for (const PaintRect &rect : rects) {
124
- std::snprintf(buffer, sizeof(buffer), "rect x=%.1f y=%.1f w=%.1f h=%.1f r=%.1f color=%s\n",
125
- rect.x, rect.y, rect.width, rect.height, rect.radius,
126
- formatColor(rect.color).c_str());
127
- text += buffer;
128
- }
129
- for (const PaintText &run : texts) {
130
- std::snprintf(buffer, sizeof(buffer),
131
- "text x=%.1f baseline=%.1f size=%.1f color=%s \"%s\"\n", run.x, run.baseline,
132
- run.font.pixel_size, formatColor(run.color).c_str(), run.text.c_str());
133
- text += buffer;
134
- }
135
- for (const PaintImage &image : images) {
136
- std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f src=%s\n", image.x,
137
- image.y, image.width, image.height, image.src.c_str());
138
- text += buffer;
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
  }
@@ -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 text are kept in separate lists: backgrounds/borders are drawn
10
- * first, then all text on top. That matches almost every static UI and lets the
11
- * renderer use one draw call per list instead of switching pipelines per shape.
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 (for diagnostics/tests). */
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
- corners[i].x = cx + local_x[i];
55
- corners[i].y = cy + local_y[i];
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 *geometry, float viewport_width, float viewport_height) {
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
- SDL_BindGPUGraphicsPipeline(pass, g_pipeline);
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
- SDL_DrawGPUPrimitives(pass, geometry->vertex_count, 1, 0, 0);
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 *geometry, float viewport_width, float viewport_height);
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 */