xbintsc 0.3.46 → 0.3.61
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/AGENTS.md +95 -0
- package/README.md +25 -0
- package/README.zh-CN.md +23 -0
- package/dist/src/cli/hints.d.ts +54 -0
- package/dist/src/cli/hints.js +165 -0
- package/dist/src/cli/hints.js.map +1 -0
- package/dist/src/cli/main.js +73 -9
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +26 -0
- package/dist/src/codegen/generator/tables.js +64 -12
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/diagnostics/source-text.d.ts +22 -0
- package/dist/src/diagnostics/source-text.js +76 -0
- package/dist/src/diagnostics/source-text.js.map +1 -0
- package/dist/src/driver/bundler/graph.js +2 -1
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/compiler.js +28 -3
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +7 -0
- package/dist/src/driver/config.js.map +1 -1
- package/dist/src/driver/win-entry.d.ts +21 -0
- package/dist/src/driver/win-entry.js +52 -0
- package/dist/src/driver/win-entry.js.map +1 -0
- package/dist/src/lexer/scanner/strings.js +16 -3
- package/dist/src/lexer/scanner/strings.js.map +1 -1
- package/dist/tests/cli/hints.test.d.ts +9 -0
- package/dist/tests/cli/hints.test.js +143 -0
- package/dist/tests/cli/hints.test.js.map +1 -0
- package/dist/tests/cli/main.test.js +6 -4
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +17 -2
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-browser.test.d.ts +12 -0
- package/dist/tests/e2e/gui-example-browser.test.js +164 -0
- package/dist/tests/e2e/gui-example-browser.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +14 -0
- package/dist/tests/e2e/gui-example.test.js +76 -0
- package/dist/tests/e2e/gui-example.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +39 -4
- package/dist/tests/e2e/gui-helpers.js +38 -9
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +155 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-lifecycle.test.js +10 -1
- package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +44 -4
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-script.test.js +8 -4
- package/dist/tests/e2e/gui-script.test.js.map +1 -1
- package/dist/tests/helpers.js +3 -2
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/lexer/strings.test.js +14 -2
- package/dist/tests/lexer/strings.test.js.map +1 -1
- package/doc/DESIGN.md +117 -0
- package/doc/ai/README.md +63 -0
- package/doc/ai/build-recipe.md +137 -0
- package/doc/ai/cli.md +142 -0
- package/doc/ai/contributing.md +196 -0
- package/doc/ai/extensions.md +148 -0
- package/doc/ai/language-support.md +152 -0
- package/doc/ai/troubleshooting.md +163 -0
- package/doc/ai/zh-CN/README.md +56 -0
- package/doc/ai/zh-CN/build-recipe.md +132 -0
- package/doc/ai/zh-CN/cli.md +127 -0
- package/doc/ai/zh-CN/contributing.md +173 -0
- package/doc/ai/zh-CN/extensions.md +139 -0
- package/doc/ai/zh-CN/language-support.md +147 -0
- package/doc/ai/zh-CN/troubleshooting.md +150 -0
- package/doc/gui-scripts.md +350 -0
- package/doc/gui.md +752 -0
- package/doc/icon.md +265 -0
- package/doc/implemented.md +373 -0
- package/doc/node-implemented.md +588 -0
- package/doc/node-unimplemented.md +167 -0
- package/doc/post/announce.md +43 -0
- package/doc/requirements.md +145 -0
- package/doc/unimplemented.md +286 -0
- package/doc/xbintsc.config.schema.json +71 -0
- package/doc/zh-CN/DESIGN.md +104 -0
- package/doc/zh-CN/gui-scripts.md +329 -0
- package/doc/zh-CN/gui.md +656 -0
- package/doc/zh-CN/icon.md +241 -0
- package/doc/zh-CN/implemented.md +365 -0
- package/doc/zh-CN/node-implemented.md +533 -0
- package/doc/zh-CN/node-unimplemented.md +141 -0
- package/doc/zh-CN/plan-require-node-modules.md +284 -0
- package/doc/zh-CN/post/announce.md +47 -0
- package/doc/zh-CN/requirements.md +134 -0
- package/doc/zh-CN/unimplemented.md +247 -0
- package/llms.txt +45 -0
- package/package.json +4 -1
- package/runtime/ext_gui/dom_api.cpp +19 -0
- package/runtime/ext_gui/dom_api_node.cpp +4 -1
- package/runtime/ext_gui/dom_api_node_read.cpp +12 -0
- package/runtime/ext_gui/gui.cpp +45 -3
- package/runtime/ext_gui/gui_engine.h +15 -0
- package/runtime/ext_gui/gui_events.cpp +10 -3
- package/runtime/ext_gui/layout.cpp +6 -0
- package/runtime/ext_gui/layout.h +4 -0
- package/runtime/ext_gui/layout_flex.cpp +13 -5
- package/runtime/ext_gui/layout_flow.cpp +134 -4
- package/runtime/ext_gui/layout_flow_internal.h +41 -2
- package/runtime/ext_gui/layout_inline.cpp +11 -3
- package/runtime/ext_gui/paint.cpp +6 -0
- package/runtime/ext_gui/renderer.cpp +13 -11
- package/runtime/ext_gui/renderer_image.cpp +12 -8
- package/runtime/ext_gui/renderer_shaders.h +131 -4
- package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
- package/runtime/ext_gui/renderer_text.cpp +12 -8
- package/runtime/ext_gui/script.cpp +12 -2
- package/runtime/ext_gui/shaders.hlsl +98 -0
- package/runtime/ext_gui/spirv/fill.frag +19 -0
- package/runtime/ext_gui/spirv/fill.vert +42 -0
- package/runtime/ext_gui/spirv/image.frag +16 -0
- package/runtime/ext_gui/spirv/quad.vert +30 -0
- package/runtime/ext_gui/spirv/text.frag +16 -0
- package/runtime/ext_gui/text.cpp +14 -0
- package/runtime/ext_gui/window.cpp +12 -0
- package/runtime/rt.h +10 -0
- package/scripts/build-gui-shaders.mjs +204 -0
- package/scripts/build-gui.ts +35 -0
- package/scripts/check-file-length.ts +5 -1
- package/src/cli/hints.ts +194 -0
- package/src/cli/main.ts +82 -9
- package/src/codegen/generator/tables.ts +60 -14
- package/src/diagnostics/source-text.ts +78 -0
- package/src/driver/bundler/graph.ts +2 -1
- package/src/driver/compiler.ts +29 -3
- package/src/driver/config.ts +7 -0
- package/src/driver/win-entry.ts +59 -0
- package/src/lexer/scanner/strings.ts +16 -3
|
@@ -48,6 +48,8 @@ float Layouter::preferredContentWidth(const LayoutBox *box, float available) con
|
|
|
48
48
|
float best = 0;
|
|
49
49
|
for (const std::unique_ptr<LayoutBox> &child : box->children) {
|
|
50
50
|
if (child->display == Display::None) continue;
|
|
51
|
+
/* Out-of-flow children do not contribute to the parent's intrinsic width. */
|
|
52
|
+
if (isAbsoluteChild(child.get())) continue;
|
|
51
53
|
if (child->is_text) {
|
|
52
54
|
best = std::max(best, textWidth(child->text, *child->style));
|
|
53
55
|
} else {
|
|
@@ -91,18 +93,107 @@ void Layouter::resolveEdges(LayoutBox *box, float containingWidth) const {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
bool Layouter::establishesContainingBlock(const LayoutBox *box) {
|
|
97
|
+
return box->style != nullptr && box->style->position != Position::Static;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const LayoutBox *Layouter::containingBlockAncestor(const LayoutBox *box) const {
|
|
101
|
+
/* Absolute boxes resolve against the padding box of the nearest ancestor with
|
|
102
|
+
* `position != static`; when there is none, the caller falls back to the
|
|
103
|
+
* initial containing block (the viewport). */
|
|
104
|
+
for (const LayoutBox *ancestor = box; ancestor != nullptr; ancestor = ancestor->parent) {
|
|
105
|
+
if (establishesContainingBlock(ancestor)) return ancestor;
|
|
106
|
+
}
|
|
107
|
+
return nullptr;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void Layouter::setContainingBlock(LayoutBox *box, const LayoutBox *ancestor) const {
|
|
111
|
+
if (ancestor != nullptr) {
|
|
112
|
+
/* The padding box of the ancestor. Its height is only known once the
|
|
113
|
+
* ancestor's own layout has finished, which is why `publishContainingBlock`
|
|
114
|
+
* is called again at the end of `layoutBlock` for the box that owns it. */
|
|
115
|
+
box->cb_x = ancestor->content_x - ancestor->padding_left;
|
|
116
|
+
box->cb_y = ancestor->content_y - ancestor->padding_top;
|
|
117
|
+
box->cb_width = ancestor->padding_left + ancestor->content_width + ancestor->padding_right;
|
|
118
|
+
box->cb_height = ancestor->padding_top + ancestor->content_height + ancestor->padding_bottom;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
/* No positioned ancestor: the initial containing block is the viewport. */
|
|
122
|
+
box->cb_x = 0;
|
|
123
|
+
box->cb_y = 0;
|
|
124
|
+
box->cb_width = vw;
|
|
125
|
+
box->cb_height = vh;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void Layouter::publishContainingBlock(LayoutBox *box) const {
|
|
129
|
+
if (!establishesContainingBlock(box)) return;
|
|
130
|
+
for (const std::unique_ptr<LayoutBox> &child : box->children) setContainingBlock(child.get(), box);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
void Layouter::applyPosition(LayoutBox *box) const {
|
|
134
|
+
if (box->style == nullptr) return;
|
|
135
|
+
const XtStyle &style = *box->style;
|
|
136
|
+
if (style.position == Position::Static) return;
|
|
137
|
+
float em = style.font_size;
|
|
138
|
+
|
|
139
|
+
if (style.position == Position::Relative) {
|
|
140
|
+
/* Relative positioning offsets the box (and its subtree) without changing
|
|
141
|
+
* the space it occupies in the flow. */
|
|
142
|
+
float dx = 0;
|
|
143
|
+
float dy = 0;
|
|
144
|
+
if (!isAuto(style.left)) dx = px(style.left, box->cb_width, em);
|
|
145
|
+
else if (!isAuto(style.right)) dx = -px(style.right, box->cb_width, em);
|
|
146
|
+
if (!isAuto(style.top)) dy = px(style.top, box->cb_height, em);
|
|
147
|
+
else if (!isAuto(style.bottom)) dy = -px(style.bottom, box->cb_height, em);
|
|
148
|
+
if (dx != 0 || dy != 0) translate(box, dx, dy);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Absolute (and fixed, which resolves against the viewport): the static
|
|
153
|
+
* position stands in for every `auto` inset. */
|
|
154
|
+
float x = box->x;
|
|
155
|
+
float y = box->y;
|
|
156
|
+
if (!isAuto(style.left)) x = box->cb_x + px(style.left, box->cb_width, em) + box->margin_left;
|
|
157
|
+
else if (!isAuto(style.right)) {
|
|
158
|
+
x = box->cb_x + box->cb_width - px(style.right, box->cb_width, em) - box->margin_right - box->width;
|
|
159
|
+
}
|
|
160
|
+
if (!isAuto(style.top)) y = box->cb_y + px(style.top, box->cb_height, em) + box->margin_top;
|
|
161
|
+
else if (!isAuto(style.bottom)) {
|
|
162
|
+
y = box->cb_y + box->cb_height - px(style.bottom, box->cb_height, em) - box->margin_bottom - box->height;
|
|
163
|
+
}
|
|
164
|
+
translate(box, x - box->x, y - box->y);
|
|
165
|
+
}
|
|
166
|
+
|
|
94
167
|
float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWidth,
|
|
95
168
|
float availableHeight, float forcedContentWidth,
|
|
96
|
-
float forcedContentHeight) const {
|
|
169
|
+
float forcedContentHeight, bool inFlow) const {
|
|
97
170
|
const XtStyle &style = *box->style;
|
|
98
171
|
float em = style.font_size;
|
|
99
172
|
resolveEdges(box, availableWidth);
|
|
100
173
|
|
|
101
174
|
float contentWidth;
|
|
175
|
+
bool outOfFlow = style.position == Position::Absolute || style.position == Position::Fixed;
|
|
102
176
|
if (forcedContentWidth >= 0) {
|
|
103
177
|
contentWidth = forcedContentWidth;
|
|
104
178
|
} else if (isAuto(style.width)) {
|
|
105
|
-
|
|
179
|
+
if (outOfFlow && !isAuto(style.left) && !isAuto(style.right)) {
|
|
180
|
+
/* Both horizontal insets are set, so `auto` fills the space between them
|
|
181
|
+
* (the padding box of the containing block). */
|
|
182
|
+
contentWidth = box->cb_width - horizontalEdges(box) - px(style.left, box->cb_width, em) -
|
|
183
|
+
px(style.right, box->cb_width, em);
|
|
184
|
+
if (contentWidth < 0) contentWidth = 0;
|
|
185
|
+
} else if (outOfFlow) {
|
|
186
|
+
/* An out-of-flow box does not fill its containing block: an `auto` width
|
|
187
|
+
* is shrink-to-fit — its preferred width, capped by the space available
|
|
188
|
+
* (the containing block's width, not the parent's content box, because an
|
|
189
|
+
* absolute box is laid out against the former). */
|
|
190
|
+
float available = box->cb_width - horizontalEdges(box);
|
|
191
|
+
float preferred = preferredContentWidth(box, availableWidth);
|
|
192
|
+
contentWidth = preferred < available ? preferred : available;
|
|
193
|
+
if (contentWidth < 0) contentWidth = 0;
|
|
194
|
+
} else {
|
|
195
|
+
contentWidth = availableWidth - horizontalEdges(box);
|
|
196
|
+
}
|
|
106
197
|
} else {
|
|
107
198
|
contentWidth = px(style.width, availableWidth, em);
|
|
108
199
|
}
|
|
@@ -142,9 +233,32 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
|
|
|
142
233
|
box->height =
|
|
143
234
|
box->border_top + box->padding_top + contentHeight + box->padding_bottom + box->border_bottom;
|
|
144
235
|
box->baseline = box->height;
|
|
236
|
+
/* Now that this box has its final size, its padding box is the containing
|
|
237
|
+
* block its own children resolve against — and only now can an out-of-flow
|
|
238
|
+
* child be placed with `bottom`/`right`. */
|
|
239
|
+
publishContainingBlock(box);
|
|
240
|
+
layoutAbsoluteChildren(box, contentWidth, availableHeight);
|
|
241
|
+
applyPosition(box);
|
|
242
|
+
/* An absolutely positioned box consumes no space in its parent's flow. */
|
|
243
|
+
if (!inFlow) return 0;
|
|
145
244
|
return box->margin_top + box->height + box->margin_bottom;
|
|
146
245
|
}
|
|
147
246
|
|
|
247
|
+
bool Layouter::isAbsoluteChild(const LayoutBox *box) {
|
|
248
|
+
return box->style != nullptr && (box->style->position == Position::Absolute ||
|
|
249
|
+
box->style->position == Position::Fixed);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void Layouter::layoutAbsoluteChildren(LayoutBox *block, float contentWidth,
|
|
253
|
+
float availableHeight) const {
|
|
254
|
+
for (const std::unique_ptr<LayoutBox> &child : block->children) {
|
|
255
|
+
if (child->display == Display::None || !isAbsoluteChild(child.get())) continue;
|
|
256
|
+
setContainingBlock(child.get(), block);
|
|
257
|
+
layoutBlock(child.get(), block->content_x, block->content_y, contentWidth, availableHeight, -1,
|
|
258
|
+
-1, false);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
148
262
|
float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float availableHeight,
|
|
149
263
|
float definiteHeight) const {
|
|
150
264
|
if (block->style->display == Display::Flex)
|
|
@@ -155,14 +269,30 @@ float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float avail
|
|
|
155
269
|
std::vector<LayoutBox *> inlineRun;
|
|
156
270
|
auto flush = [&]() {
|
|
157
271
|
if (inlineRun.empty()) return;
|
|
158
|
-
|
|
272
|
+
/* Source whitespace between block boxes collapses away (HTML's model), so a
|
|
273
|
+
* run that carries no visible content produces no line at all. */
|
|
274
|
+
bool visible = false;
|
|
275
|
+
for (LayoutBox *box : inlineRun) {
|
|
276
|
+
if (!isCollapsibleText(box)) {
|
|
277
|
+
visible = true;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (visible) y += layoutInlineRun(block, inlineRun, contentWidth, y);
|
|
159
282
|
inlineRun.clear();
|
|
160
283
|
};
|
|
161
284
|
for (const std::unique_ptr<LayoutBox> &child : block->children) {
|
|
162
285
|
Display display = child->display;
|
|
163
286
|
if (display == Display::None) continue;
|
|
164
|
-
if (
|
|
287
|
+
if (isAbsoluteChild(child.get())) {
|
|
288
|
+
/* Out of flow: `layoutAbsoluteChildren` places it once this block knows
|
|
289
|
+
* its own size. */
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (display == Display::Block || display == Display::ListItem || display == Display::Flex ||
|
|
293
|
+
child->style == nullptr) {
|
|
165
294
|
flush();
|
|
295
|
+
setContainingBlock(child.get(), containingBlockAncestor(block));
|
|
166
296
|
y += layoutBlock(child.get(), block->content_x, y, contentWidth, availableHeight);
|
|
167
297
|
} else {
|
|
168
298
|
inlineRun.push_back(child.get());
|
|
@@ -62,6 +62,17 @@ inline bool isAuto(const Length &length) {
|
|
|
62
62
|
return length.unit == Unit::Auto || length.unit == Unit::Invalid || length.unit == Unit::None;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** True for a text node that holds nothing but HTML source whitespace. HTML
|
|
66
|
+
* collapses those away between block-level boxes, which is what keeps a
|
|
67
|
+
* document's indentation from becoming part of the page (the browser model). */
|
|
68
|
+
inline bool isCollapsibleText(const LayoutBox *box) {
|
|
69
|
+
if (!box->is_text) return false;
|
|
70
|
+
for (char c : box->text) {
|
|
71
|
+
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/* Per-character advance as a fraction of the font size is no longer needed
|
|
66
77
|
* here: text metrics come from the shaping stack (see `text.h`). */
|
|
67
78
|
|
|
@@ -117,14 +128,42 @@ struct Layouter {
|
|
|
117
128
|
|
|
118
129
|
void resolveEdges(LayoutBox *box, float containingWidth) const;
|
|
119
130
|
|
|
120
|
-
/** Lay out `box` at (x, y) and return its outer height (margins included).
|
|
131
|
+
/** Lay out `box` at (x, y) and return its outer height (margins included).
|
|
132
|
+
* `inFlow` is false for an absolutely positioned child: it is still laid out,
|
|
133
|
+
* but the caller ignores the returned height (it is out of flow). */
|
|
121
134
|
float layoutBlock(LayoutBox *box, float x, float y, float availableWidth, float availableHeight,
|
|
122
|
-
float forcedContentWidth = -1, float forcedContentHeight = -1
|
|
135
|
+
float forcedContentWidth = -1, float forcedContentHeight = -1,
|
|
136
|
+
bool inFlow = true) const;
|
|
123
137
|
|
|
124
138
|
/** Lay out the children of `block`; returns the used content height. */
|
|
125
139
|
float layoutChildren(LayoutBox *block, float contentWidth, float availableHeight,
|
|
126
140
|
float definiteHeight) const;
|
|
127
141
|
|
|
142
|
+
/** Lay out the absolutely positioned children of `block`. Called once the
|
|
143
|
+
* block has its final size, because those children resolve `bottom`/`right`
|
|
144
|
+
* against its padding box. */
|
|
145
|
+
void layoutAbsoluteChildren(LayoutBox *block, float contentWidth, float availableHeight) const;
|
|
146
|
+
|
|
147
|
+
/** True when `box` establishes a containing block for absolute descendants. */
|
|
148
|
+
static bool establishesContainingBlock(const LayoutBox *box);
|
|
149
|
+
|
|
150
|
+
/** True for an out-of-flow child (`position: absolute` or `fixed`). */
|
|
151
|
+
static bool isAbsoluteChild(const LayoutBox *box);
|
|
152
|
+
|
|
153
|
+
/** Nearest ancestor (or `box` itself) that is a containing block, else null. */
|
|
154
|
+
const LayoutBox *containingBlockAncestor(const LayoutBox *box) const;
|
|
155
|
+
|
|
156
|
+
/** Re-publish the containing block of `box`'s children once `box` has its own
|
|
157
|
+
* final size (an absolute child resolves `bottom`/`right` against it). */
|
|
158
|
+
void publishContainingBlock(LayoutBox *box) const;
|
|
159
|
+
|
|
160
|
+
/** Publish `box`'s padding box as the containing block for absolute
|
|
161
|
+
* descendants of the nearest positioned ancestor (null means the viewport). */
|
|
162
|
+
void setContainingBlock(LayoutBox *box, const LayoutBox *ancestor) const;
|
|
163
|
+
|
|
164
|
+
/** Resolve the static/relative/absolute offsets of an already laid-out box. */
|
|
165
|
+
void applyPosition(LayoutBox *box) const;
|
|
166
|
+
|
|
128
167
|
void flattenInline(LayoutBox *box, float contentWidth, std::vector<InlineItem> &out) const;
|
|
129
168
|
|
|
130
169
|
void computeInlineGeometry(LayoutBox *box) const;
|
|
@@ -48,6 +48,7 @@ void Layouter::flattenInline(LayoutBox *box, float contentWidth,
|
|
|
48
48
|
if (box->display == Display::InlineBlock) {
|
|
49
49
|
float available = contentWidth;
|
|
50
50
|
float intrinsic = preferredContentWidth(box, available);
|
|
51
|
+
setContainingBlock(box, containingBlockAncestor(box));
|
|
51
52
|
layoutBlock(box, 0, 0, available, -1, intrinsic);
|
|
52
53
|
InlineItem item;
|
|
53
54
|
item.box = box;
|
|
@@ -163,9 +164,16 @@ float Layouter::layoutInlineRun(LayoutBox *container, const std::vector<LayoutBo
|
|
|
163
164
|
}
|
|
164
165
|
for (InlineItem *item : current) {
|
|
165
166
|
if (item->atomic) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
/* An out-of-flow item was already placed against its containing block by
|
|
168
|
+
* `layoutBlock`, so it must not be moved back onto the line. */
|
|
169
|
+
if (!isAbsoluteChild(item->box)) {
|
|
170
|
+
float dx = x + item->box->margin_left - item->box->x;
|
|
171
|
+
float dy = cursorY + item->box->margin_top - item->box->y;
|
|
172
|
+
translate(item->box, dx, dy);
|
|
173
|
+
/* `position: relative` on an inline-block shifts it after line
|
|
174
|
+
* placement, so the line box keeps the space it reserved. */
|
|
175
|
+
applyPosition(item->box);
|
|
176
|
+
}
|
|
169
177
|
} else {
|
|
170
178
|
item->box->fragments.push_back({x, cursorY, item->width, height, item->text});
|
|
171
179
|
}
|
|
@@ -67,6 +67,12 @@ void paintBox(const LayoutBox *box, DisplayList &out) {
|
|
|
67
67
|
if (box == nullptr) return;
|
|
68
68
|
const XtStyle *style = box->style;
|
|
69
69
|
|
|
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. */
|
|
74
|
+
if (style != nullptr && style->opacity <= 0.0f) return;
|
|
75
|
+
|
|
70
76
|
if (style != nullptr && box->display != Display::None) {
|
|
71
77
|
if (box->is_text) {
|
|
72
78
|
addTextRuns(box, *style, out);
|
|
@@ -130,30 +130,32 @@ bool xt_gui_renderer_ensure(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
|
130
130
|
using namespace xtgui::renderer_detail;
|
|
131
131
|
if (g_pipeline != nullptr) return true;
|
|
132
132
|
if (g_renderer_failed) return false;
|
|
133
|
-
|
|
133
|
+
const ShaderSource fill_vs = selectShader(device, ShaderProgram::Fill, false);
|
|
134
|
+
const ShaderSource fill_fs = selectShader(device, ShaderProgram::Fill, true);
|
|
135
|
+
if (!fill_vs.valid() || !fill_fs.valid()) {
|
|
134
136
|
fprintf(stderr,
|
|
135
|
-
"xt_gui: backend
|
|
136
|
-
"(Vulkan
|
|
137
|
+
"xt_gui: backend supports neither Metal nor D3D12 shaders; shape/text painting "
|
|
138
|
+
"disabled (Vulkan SPIR-V blobs are a TODO)\n");
|
|
137
139
|
g_renderer_failed = 1;
|
|
138
140
|
return false;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
SDL_GPUShaderCreateInfo vs_info;
|
|
142
144
|
memset(&vs_info, 0, sizeof(vs_info));
|
|
143
|
-
vs_info.code_size =
|
|
144
|
-
vs_info.code =
|
|
145
|
-
vs_info.entrypoint =
|
|
146
|
-
vs_info.format =
|
|
145
|
+
vs_info.code_size = fill_vs.size;
|
|
146
|
+
vs_info.code = fill_vs.code;
|
|
147
|
+
vs_info.entrypoint = fill_vs.entry;
|
|
148
|
+
vs_info.format = fill_vs.format;
|
|
147
149
|
vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
|
148
150
|
vs_info.num_uniform_buffers = 1;
|
|
149
151
|
SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
|
|
150
152
|
|
|
151
153
|
SDL_GPUShaderCreateInfo fs_info;
|
|
152
154
|
memset(&fs_info, 0, sizeof(fs_info));
|
|
153
|
-
fs_info.code_size =
|
|
154
|
-
fs_info.code =
|
|
155
|
-
fs_info.entrypoint =
|
|
156
|
-
fs_info.format =
|
|
155
|
+
fs_info.code_size = fill_fs.size;
|
|
156
|
+
fs_info.code = fill_fs.code;
|
|
157
|
+
fs_info.entrypoint = fill_fs.entry;
|
|
158
|
+
fs_info.format = fill_fs.format;
|
|
157
159
|
fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
158
160
|
SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
|
|
159
161
|
|
|
@@ -17,22 +17,26 @@ namespace xtgui {
|
|
|
17
17
|
namespace renderer_detail {
|
|
18
18
|
|
|
19
19
|
SDL_GPUGraphicsPipeline *createImagePipeline(SDL_GPUDevice *device, SDL_Window *reference) {
|
|
20
|
+
const ShaderSource image_vs = selectShader(device, ShaderProgram::Image, false);
|
|
21
|
+
const ShaderSource image_fs = selectShader(device, ShaderProgram::Image, true);
|
|
22
|
+
if (!image_vs.valid() || !image_fs.valid()) return nullptr;
|
|
23
|
+
|
|
20
24
|
SDL_GPUShaderCreateInfo vs_info;
|
|
21
25
|
memset(&vs_info, 0, sizeof(vs_info));
|
|
22
|
-
vs_info.code_size =
|
|
23
|
-
vs_info.code =
|
|
24
|
-
vs_info.entrypoint =
|
|
25
|
-
vs_info.format =
|
|
26
|
+
vs_info.code_size = image_vs.size;
|
|
27
|
+
vs_info.code = image_vs.code;
|
|
28
|
+
vs_info.entrypoint = image_vs.entry;
|
|
29
|
+
vs_info.format = image_vs.format;
|
|
26
30
|
vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
|
27
31
|
vs_info.num_uniform_buffers = 1;
|
|
28
32
|
SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
|
|
29
33
|
|
|
30
34
|
SDL_GPUShaderCreateInfo fs_info;
|
|
31
35
|
memset(&fs_info, 0, sizeof(fs_info));
|
|
32
|
-
fs_info.code_size =
|
|
33
|
-
fs_info.code =
|
|
34
|
-
fs_info.entrypoint =
|
|
35
|
-
fs_info.format =
|
|
36
|
+
fs_info.code_size = image_fs.size;
|
|
37
|
+
fs_info.code = image_fs.code;
|
|
38
|
+
fs_info.entrypoint = image_fs.entry;
|
|
39
|
+
fs_info.format = image_fs.format;
|
|
36
40
|
fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
|
37
41
|
fs_info.num_samplers = 1;
|
|
38
42
|
SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
|
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* xbintsc GUI engine —
|
|
2
|
+
* xbintsc GUI engine — shader sources.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `renderer.cpp` (fills), `renderer_text.cpp` (glyphs) and `renderer_image.cpp`
|
|
5
|
+
* (images) each build one SDL_GPU graphics pipeline. Three shader families back
|
|
6
|
+
* them, one per SDL_GPU backend:
|
|
7
|
+
*
|
|
8
|
+
* - Metal: the MSL string literals below, which SDL_GPU compiles at runtime.
|
|
9
|
+
* - Vulkan: SPIR-V blobs in `renderer_shaders_data.h`, compiled from
|
|
10
|
+
* `spirv/*.{vert,frag}` by `scripts/build-gui-shaders.mjs`.
|
|
11
|
+
* - Direct3D 12: DXIL blobs from the same header, compiled from `shaders.hlsl`.
|
|
12
|
+
*
|
|
13
|
+
* `selectShader` resolves a logical program and stage to whichever of those the
|
|
14
|
+
* device accepts. The backend rejects any other format, so a missing blob means
|
|
15
|
+
* "no pipeline", never a mismatched one.
|
|
16
|
+
*
|
|
17
|
+
* Text and image quads share the vertex program; only the fragment entry point
|
|
18
|
+
* differs (`fs_text` vs `fs_image`).
|
|
19
|
+
*
|
|
20
|
+
* Note the entry-point names differ per format: MSL/DXIL keep the descriptive
|
|
21
|
+
* names from their sources, while glslc emits the GLSL `main`.
|
|
7
22
|
*/
|
|
8
23
|
#ifndef XT_GUI_RENDERER_SHADERS_H
|
|
9
24
|
#define XT_GUI_RENDERER_SHADERS_H
|
|
10
25
|
|
|
26
|
+
#include <SDL3/SDL.h>
|
|
27
|
+
#include <cstring>
|
|
28
|
+
|
|
29
|
+
#include "renderer_shaders_data.h"
|
|
30
|
+
|
|
31
|
+
namespace xtgui {
|
|
32
|
+
namespace renderer_detail {
|
|
33
|
+
|
|
34
|
+
/** The three pipeline programs the engine builds. */
|
|
35
|
+
enum class ShaderProgram { Fill, Text, Image };
|
|
36
|
+
|
|
11
37
|
/* Rounded-rectangle fill. Vertex attributes (matching SDL_GPUVertexAttribute):
|
|
12
38
|
* 0 -> position (pixels, viewport-relative)
|
|
13
39
|
* 1 -> local (pixels, relative to the rectangle centre)
|
|
@@ -105,4 +131,105 @@ fragment float4 fs_image(TextOut in [[stage_in]],
|
|
|
105
131
|
}
|
|
106
132
|
)MSL";
|
|
107
133
|
|
|
134
|
+
/** One stage of one pipeline program, in whichever format the device accepts. */
|
|
135
|
+
struct ShaderSource {
|
|
136
|
+
SDL_GPUShaderFormat format = 0;
|
|
137
|
+
const unsigned char *code = nullptr;
|
|
138
|
+
size_t size = 0;
|
|
139
|
+
const char *entry = nullptr;
|
|
140
|
+
|
|
141
|
+
bool valid() const { return code != nullptr; }
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** The MSL text backing `program` (one literal covers both stages). */
|
|
145
|
+
inline const char *mslSource(ShaderProgram program) {
|
|
146
|
+
return program == ShaderProgram::Fill ? kFillMSL : kTextMSL;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** MSL entry point names for `program`, matching the literals below. */
|
|
150
|
+
inline const char *vertexEntry(ShaderProgram program) {
|
|
151
|
+
return program == ShaderProgram::Fill ? "vs_main" : "vs_text";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
inline const char *fragmentEntry(ShaderProgram program) {
|
|
155
|
+
if (program == ShaderProgram::Fill) return "fs_main";
|
|
156
|
+
return program == ShaderProgram::Text ? "fs_text" : "fs_image";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** A blob pair for one shader format: bytes plus the entry-point names it uses.
|
|
160
|
+
* The names are stored in `ShaderSource` rather than resolved at the call site
|
|
161
|
+
* because cross-compilers rename entry points (glslc emits `main`, dxc keeps the
|
|
162
|
+
* HLSL name, MSL keeps the Metal name). */
|
|
163
|
+
struct ShaderBlobs {
|
|
164
|
+
const unsigned char *vertex;
|
|
165
|
+
size_t vertex_size;
|
|
166
|
+
const char *vertex_entry;
|
|
167
|
+
const unsigned char *fragment;
|
|
168
|
+
size_t fragment_size;
|
|
169
|
+
const char *fragment_entry;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** SPIR-V blobs for `program` (Vulkan). glslc names every entry point `main`. */
|
|
173
|
+
inline ShaderBlobs spirvBlobs(ShaderProgram program) {
|
|
174
|
+
switch (program) {
|
|
175
|
+
case ShaderProgram::Fill:
|
|
176
|
+
return {kFillSpirvVs, kFillSpirvVsSize, "main", kFillSpirvFs, kFillSpirvFsSize, "main"};
|
|
177
|
+
case ShaderProgram::Text:
|
|
178
|
+
return {kTextSpirvVs, kTextSpirvVsSize, "main", kTextSpirvFs, kTextSpirvFsSize, "main"};
|
|
179
|
+
case ShaderProgram::Image:
|
|
180
|
+
return {kTextSpirvVs, kTextSpirvVsSize, "main", kImageSpirvFs, kImageSpirvFsSize, "main"};
|
|
181
|
+
}
|
|
182
|
+
return {nullptr, 0, nullptr, nullptr, 0, nullptr};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** DXIL blobs for `program` (Direct3D 12); one blob per entry point. */
|
|
186
|
+
inline ShaderBlobs dxilBlobs(ShaderProgram program) {
|
|
187
|
+
switch (program) {
|
|
188
|
+
case ShaderProgram::Fill:
|
|
189
|
+
return {kFillDxilVs, kFillDxilVsSize, "vs_main", kFillDxilFs, kFillDxilFsSize, "fs_main"};
|
|
190
|
+
case ShaderProgram::Text:
|
|
191
|
+
return {kTextDxilVs, kTextDxilVsSize, "vs_text", kTextDxilFs, kTextDxilFsSize, "fs_text"};
|
|
192
|
+
case ShaderProgram::Image:
|
|
193
|
+
return {kTextDxilVs, kTextDxilVsSize, "vs_text", kImageDxilFs, kImageDxilFsSize, "fs_image"};
|
|
194
|
+
}
|
|
195
|
+
return {nullptr, 0, nullptr, nullptr, 0, nullptr};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** MSL for `program`: a single source holds both stages, so it is expressed as a
|
|
199
|
+
* blob pair sharing one buffer. */
|
|
200
|
+
inline ShaderBlobs mslBlobs(ShaderProgram program) {
|
|
201
|
+
const char *msl = mslSource(program);
|
|
202
|
+
const size_t size = strlen(msl);
|
|
203
|
+
const unsigned char *code = (const unsigned char *)msl;
|
|
204
|
+
return {code, size, vertexEntry(program), code, size, fragmentEntry(program)};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Resolve `program`'s vertex and fragment stages to shaders the device accepts.
|
|
208
|
+
* Returns an invalid source when the backend supports none of the formats the
|
|
209
|
+
* engine ships. */
|
|
210
|
+
inline ShaderSource selectShader(SDL_GPUDevice *device, ShaderProgram program, bool fragment) {
|
|
211
|
+
ShaderSource source;
|
|
212
|
+
const SDL_GPUShaderFormat formats = SDL_GetGPUShaderFormats(device);
|
|
213
|
+
ShaderBlobs blobs;
|
|
214
|
+
if ((formats & SDL_GPU_SHADERFORMAT_MSL) != 0) {
|
|
215
|
+
source.format = SDL_GPU_SHADERFORMAT_MSL;
|
|
216
|
+
blobs = mslBlobs(program);
|
|
217
|
+
} else if ((formats & SDL_GPU_SHADERFORMAT_SPIRV) != 0) {
|
|
218
|
+
source.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
|
219
|
+
blobs = spirvBlobs(program);
|
|
220
|
+
} else if ((formats & SDL_GPU_SHADERFORMAT_DXIL) != 0) {
|
|
221
|
+
source.format = SDL_GPU_SHADERFORMAT_DXIL;
|
|
222
|
+
blobs = dxilBlobs(program);
|
|
223
|
+
} else {
|
|
224
|
+
return source;
|
|
225
|
+
}
|
|
226
|
+
source.code = fragment ? blobs.fragment : blobs.vertex;
|
|
227
|
+
source.size = fragment ? blobs.fragment_size : blobs.vertex_size;
|
|
228
|
+
source.entry = fragment ? blobs.fragment_entry : blobs.vertex_entry;
|
|
229
|
+
return source;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
} // namespace renderer_detail
|
|
233
|
+
} // namespace xtgui
|
|
234
|
+
|
|
108
235
|
#endif /* XT_GUI_RENDERER_SHADERS_H */
|