xbintsc 0.5.31 → 0.5.32
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 +16 -10
- package/dist/src/binder/references.js +14 -1
- package/dist/src/binder/references.js.map +1 -1
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -0
- package/dist/src/codegen/generator/module.js +54 -0
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +6 -4
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/css.d.ts +26 -0
- package/dist/src/extensions/gui/css.js +100 -0
- package/dist/src/extensions/gui/css.js.map +1 -0
- package/dist/src/extensions/gui/html.js +9 -2
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +16 -2
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/gui/three.d.ts +16 -0
- package/dist/src/extensions/gui/three.js +22 -0
- package/dist/src/extensions/gui/three.js.map +1 -0
- package/dist/src/extensions/media/index.d.ts +6 -5
- package/dist/src/extensions/media/index.js +12 -14
- package/dist/src/extensions/media/index.js.map +1 -1
- package/dist/tests/e2e/compile-builtins.test.js +24 -0
- package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/gui-browser.test.js +1 -1
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
- package/dist/tests/e2e/gui-effects.test.js +262 -0
- package/dist/tests/e2e/gui-effects.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
- package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
- package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
- package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
- package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
- package/dist/tests/e2e/gui-layout.test.js +48 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.js +5 -4
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
- package/dist/tests/e2e/gui-webgl.test.js +179 -0
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2.test.js +238 -0
- package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +41 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/dist/tests/extensions/media.test.js +7 -3
- package/dist/tests/extensions/media.test.js.map +1 -1
- package/doc/ai/extensions.md +29 -9
- package/doc/ai/troubleshooting.md +12 -8
- package/doc/ai/zh-CN/extensions.md +26 -8
- package/doc/ai/zh-CN/troubleshooting.md +10 -6
- package/doc/gui/api.md +3 -2
- package/doc/gui/build.md +72 -16
- package/doc/gui/canvas.md +11 -3
- package/doc/gui/dom.md +33 -18
- package/doc/gui/known-issues.md +57 -24
- package/doc/gui/layout.md +18 -0
- package/doc/gui/media.md +17 -13
- package/doc/gui/rendering.md +50 -3
- package/doc/gui/webgl.md +256 -0
- package/doc/gui.md +108 -17
- package/doc/npm-package.md +6 -4
- package/doc/requirements.md +18 -9
- package/doc/zh-CN/gui/api.md +3 -2
- package/doc/zh-CN/gui/build.md +64 -15
- package/doc/zh-CN/gui/canvas.md +9 -2
- package/doc/zh-CN/gui/dom.md +20 -7
- package/doc/zh-CN/gui/known-issues.md +45 -19
- package/doc/zh-CN/gui/layout.md +14 -0
- package/doc/zh-CN/gui/media.md +15 -11
- package/doc/zh-CN/gui/rendering.md +41 -4
- package/doc/zh-CN/gui/webgl.md +115 -0
- package/doc/zh-CN/gui.md +89 -14
- package/doc/zh-CN/npm-package.md +9 -6
- package/doc/zh-CN/requirements.md +12 -6
- package/llms.txt +2 -0
- package/package.json +1 -1
- package/runtime/ext_gui/browser.cpp +39 -13
- package/runtime/ext_gui/canvas.cpp +3 -18
- package/runtime/ext_gui/canvas_internal.h +10 -0
- package/runtime/ext_gui/canvas_raster.cpp +60 -0
- package/runtime/ext_gui/clip_values.cpp +246 -0
- package/runtime/ext_gui/css.cpp +94 -0
- package/runtime/ext_gui/css.h +22 -1
- package/runtime/ext_gui/css_match.cpp +3 -0
- package/runtime/ext_gui/css_selector.cpp +9 -0
- package/runtime/ext_gui/css_sheet.cpp +89 -45
- package/runtime/ext_gui/document.cpp +4 -1
- package/runtime/ext_gui/document.h +2 -0
- package/runtime/ext_gui/dom.h +16 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
- package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
- package/runtime/ext_gui/dom_api_internal.h +259 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +376 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +434 -0
- package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
- package/runtime/ext_gui/dom_api_webgl_buffer.cpp +212 -0
- package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
- package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
- package/runtime/ext_gui/dom_api_webgl_state.cpp +444 -0
- package/runtime/ext_gui/dom_api_webgl_texture.cpp +445 -0
- package/runtime/ext_gui/effect_values.cpp +312 -0
- package/runtime/ext_gui/gradient_values.cpp +476 -0
- package/runtime/ext_gui/grid_values.cpp +230 -0
- package/runtime/ext_gui/gui_canvas.cpp +50 -2
- package/runtime/ext_gui/gui_engine.h +27 -0
- package/runtime/ext_gui/gui_events.cpp +4 -1
- package/runtime/ext_gui/gui_media.cpp +1 -1
- package/runtime/ext_gui/gui_webgl.cpp +83 -0
- package/runtime/ext_gui/layout.cpp +21 -0
- package/runtime/ext_gui/layout_flow.cpp +81 -4
- package/runtime/ext_gui/layout_flow_internal.h +31 -0
- package/runtime/ext_gui/layout_grid.cpp +374 -0
- package/runtime/ext_gui/media.h +1 -1
- package/runtime/ext_gui/media_api.h +2 -2
- package/runtime/ext_gui/media_query.cpp +211 -0
- package/runtime/ext_gui/paint.cpp +111 -77
- package/runtime/ext_gui/paint.h +33 -3
- package/runtime/ext_gui/paint_background.cpp +229 -0
- package/runtime/ext_gui/paint_clip.cpp +268 -0
- package/runtime/ext_gui/paint_effects.cpp +324 -0
- package/runtime/ext_gui/paint_internal.h +85 -0
- package/runtime/ext_gui/renderer.cpp +8 -31
- package/runtime/ext_gui/renderer_clip.cpp +196 -0
- package/runtime/ext_gui/renderer_image.cpp +13 -8
- package/runtime/ext_gui/renderer_internal.h +15 -8
- package/runtime/ext_gui/renderer_text.cpp +6 -31
- package/runtime/ext_gui/style.cpp +343 -20
- package/runtime/ext_gui/style.h +144 -3
- package/runtime/ext_gui/style_print.cpp +134 -1
- package/runtime/ext_gui/style_values.cpp +152 -126
- package/runtime/ext_gui/style_values_internal.h +35 -0
- package/runtime/ext_gui/webgl.h +466 -0
- package/runtime/ext_gui/webgl2_constants.h +274 -0
- package/runtime/ext_gui/webgl_buffer.cpp +565 -0
- package/runtime/ext_gui/webgl_constants.h +317 -0
- package/runtime/ext_gui/webgl_context.cpp +466 -0
- package/runtime/ext_gui/webgl_draw.cpp +495 -0
- package/runtime/ext_gui/webgl_glsl.h +125 -0
- package/runtime/ext_gui/webgl_glsl_compile.cpp +431 -0
- package/runtime/ext_gui/webgl_glsl_rewrite.cpp +349 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +581 -0
- package/runtime/ext_gui/webgl_msl.cpp +62 -0
- package/runtime/ext_gui/webgl_msl.h +33 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +274 -0
- package/runtime/ext_gui/webgl_program_internal.h +18 -0
- package/runtime/ext_gui/webgl_shader.cpp +582 -0
- package/runtime/ext_gui/webgl_texture.cpp +293 -0
- package/runtime/ext_gui/webgl_uniform.cpp +167 -0
- package/runtime/ext_gui/webgl_webgl2.cpp +224 -0
- package/runtime/ext_media/media.cpp +1 -1
- package/runtime/ext_media/media_bridge.c +9 -3
- package/runtime/rt_internal.h +1 -0
- package/runtime/xt_containers/access.inc +8 -0
- package/runtime/xt_regex/api.inc +2 -0
- package/runtime/xt_regex/matcher.inc +16 -3
- package/runtime/xt_regex/parser.inc +8 -2
- package/runtime/xt_regex/types.inc +3 -0
- package/runtime/xt_stdlib2/regexp-match.inc +60 -21
- package/runtime/xt_stdlib2/regexp.inc +1 -0
- package/runtime/xt_typed_array.c +1 -1
- package/scripts/build-gui.ts +90 -3
- package/scripts/build-media.ts +323 -58
- package/scripts/setup-vs-toolchain.ps1 +152 -0
- package/src/binder/references.ts +13 -1
- package/src/codegen/generator/module.ts +50 -0
- package/src/codegen/generator/statements/variables.ts +6 -3
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/css.ts +113 -0
- package/src/extensions/gui/html.ts +9 -2
- package/src/extensions/gui/index.ts +16 -2
- package/src/extensions/gui/three.ts +25 -0
- package/src/extensions/media/index.ts +13 -16
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `clip-path` value parsing.
|
|
3
|
+
*
|
|
4
|
+
* The geometry is resolved in `paint_clip.cpp`, where the element's border box
|
|
5
|
+
* and transform are known. Values here keep CSS lengths unresolved so `%`, em
|
|
6
|
+
* and viewport units survive the cascade.
|
|
7
|
+
*/
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <utility>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
#include "css_internal.h"
|
|
14
|
+
#include "style_internal.h"
|
|
15
|
+
#include "style_values_internal.h"
|
|
16
|
+
|
|
17
|
+
namespace xtgui {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
std::vector<std::string> splitTokens(const std::string &text) {
|
|
22
|
+
std::vector<std::string> parts;
|
|
23
|
+
std::string current;
|
|
24
|
+
int depth = 0;
|
|
25
|
+
for (char c : text) {
|
|
26
|
+
if (c == '(') depth++;
|
|
27
|
+
if (c == ')') depth--;
|
|
28
|
+
if ((c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',') && depth == 0) {
|
|
29
|
+
if (!current.empty()) {
|
|
30
|
+
parts.push_back(current);
|
|
31
|
+
current.clear();
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
current.push_back(c);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!current.empty()) parts.push_back(current);
|
|
38
|
+
return parts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Split `text` on a top-level keyword (`at` / `round`), keeping both sides. */
|
|
42
|
+
bool splitKeyword(const std::string &text, const std::string &keyword, std::string *left,
|
|
43
|
+
std::string *right) {
|
|
44
|
+
std::string lower = xt_css_lower(text);
|
|
45
|
+
int depth = 0;
|
|
46
|
+
for (size_t i = 0; i + keyword.size() <= text.size(); i++) {
|
|
47
|
+
char c = text[i];
|
|
48
|
+
if (c == '(') depth++;
|
|
49
|
+
else if (c == ')') depth--;
|
|
50
|
+
if (depth != 0) continue;
|
|
51
|
+
if (lower.compare(i, keyword.size(), keyword) != 0) continue;
|
|
52
|
+
bool before = i == 0 || xt_is_css_space(text[i - 1]) || text[i - 1] == ',';
|
|
53
|
+
bool after = i + keyword.size() >= text.size() ||
|
|
54
|
+
xt_is_css_space(text[i + keyword.size()]) || text[i + keyword.size()] == ',';
|
|
55
|
+
if (!before || !after) continue;
|
|
56
|
+
*left = xt_css_trim(text.substr(0, i));
|
|
57
|
+
*right = xt_css_trim(text.substr(i + keyword.size()));
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bool parsePositionToken(const std::string &raw, Length *out, bool *vertical) {
|
|
64
|
+
std::string text = xt_css_lower(xt_css_trim(raw));
|
|
65
|
+
if (text == "left") { *out = {0.0f, Unit::Percent}; *vertical = false; return true; }
|
|
66
|
+
if (text == "right") { *out = {100.0f, Unit::Percent}; *vertical = false; return true; }
|
|
67
|
+
if (text == "top") { *out = {0.0f, Unit::Percent}; *vertical = true; return true; }
|
|
68
|
+
if (text == "bottom") { *out = {100.0f, Unit::Percent}; *vertical = true; return true; }
|
|
69
|
+
if (text == "center") { *out = {50.0f, Unit::Percent}; *vertical = false; return true; }
|
|
70
|
+
Length length = xt_css_parse_length(text);
|
|
71
|
+
if (length.unit == Unit::Invalid || length.unit == Unit::Auto || length.unit == Unit::None)
|
|
72
|
+
return false;
|
|
73
|
+
*out = length;
|
|
74
|
+
*vertical = false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
bool parseCenter(const std::string &text, Length *x, Length *y) {
|
|
79
|
+
std::vector<std::string> tokens = splitTokens(text);
|
|
80
|
+
if (tokens.empty() || tokens.size() > 2) return false;
|
|
81
|
+
Length values[2];
|
|
82
|
+
bool vertical[2] = {false, false};
|
|
83
|
+
for (size_t i = 0; i < tokens.size(); i++) {
|
|
84
|
+
if (!parsePositionToken(tokens[i], &values[i], &vertical[i])) return false;
|
|
85
|
+
}
|
|
86
|
+
if (tokens.size() == 1) {
|
|
87
|
+
if (vertical[0]) {
|
|
88
|
+
*x = {50.0f, Unit::Percent};
|
|
89
|
+
*y = values[0];
|
|
90
|
+
} else {
|
|
91
|
+
*x = values[0];
|
|
92
|
+
*y = {50.0f, Unit::Percent};
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (vertical[0] && !vertical[1]) {
|
|
97
|
+
*x = values[1];
|
|
98
|
+
*y = values[0];
|
|
99
|
+
} else {
|
|
100
|
+
*x = values[0];
|
|
101
|
+
*y = values[1];
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Parse the 1–4 side lengths of `inset(...)` and an optional round radius. */
|
|
107
|
+
bool parseInset(const std::string &body, ClipPathSpec *spec) {
|
|
108
|
+
std::string sides = body;
|
|
109
|
+
std::string round;
|
|
110
|
+
if (splitKeyword(body, "round", &sides, &round)) {
|
|
111
|
+
std::vector<std::string> radiusTokens = splitTokens(round);
|
|
112
|
+
if (!radiusTokens.empty()) {
|
|
113
|
+
Length radius = xt_css_parse_length(radiusTokens[0]);
|
|
114
|
+
if (radius.unit != Unit::Invalid && radius.unit != Unit::Auto &&
|
|
115
|
+
radius.unit != Unit::None)
|
|
116
|
+
spec->inset_radius = radius;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
std::vector<std::string> tokens = splitTokens(sides);
|
|
120
|
+
if (tokens.empty() || tokens.size() > 4) return false;
|
|
121
|
+
Length values[4];
|
|
122
|
+
for (size_t i = 0; i < tokens.size(); i++) {
|
|
123
|
+
values[i] = xt_css_parse_length(tokens[i]);
|
|
124
|
+
if (values[i].unit == Unit::Invalid) return false;
|
|
125
|
+
}
|
|
126
|
+
switch (tokens.size()) {
|
|
127
|
+
case 1:
|
|
128
|
+
spec->inset_top = spec->inset_right = spec->inset_bottom = spec->inset_left = values[0];
|
|
129
|
+
break;
|
|
130
|
+
case 2:
|
|
131
|
+
spec->inset_top = spec->inset_bottom = values[0];
|
|
132
|
+
spec->inset_right = spec->inset_left = values[1];
|
|
133
|
+
break;
|
|
134
|
+
case 3:
|
|
135
|
+
spec->inset_top = values[0];
|
|
136
|
+
spec->inset_right = spec->inset_left = values[1];
|
|
137
|
+
spec->inset_bottom = values[2];
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
spec->inset_top = values[0];
|
|
141
|
+
spec->inset_right = values[1];
|
|
142
|
+
spec->inset_bottom = values[2];
|
|
143
|
+
spec->inset_left = values[3];
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
spec->type = ClipPathSpec::Type::Inset;
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
bool parseCircle(const std::string &body, ClipPathSpec *spec) {
|
|
151
|
+
std::string radius;
|
|
152
|
+
std::string center;
|
|
153
|
+
if (splitKeyword(body, "at", &radius, ¢er)) {
|
|
154
|
+
if (!parseCenter(center, &spec->center_x, &spec->center_y)) return false;
|
|
155
|
+
} else {
|
|
156
|
+
radius = body;
|
|
157
|
+
}
|
|
158
|
+
std::vector<std::string> tokens = splitTokens(radius);
|
|
159
|
+
if (!tokens.empty()) {
|
|
160
|
+
Length length = xt_css_parse_length(tokens[0]);
|
|
161
|
+
if (length.unit == Unit::Invalid) return false;
|
|
162
|
+
spec->radius_x = length;
|
|
163
|
+
spec->radius_y = length;
|
|
164
|
+
spec->radius_explicit = true;
|
|
165
|
+
}
|
|
166
|
+
spec->type = ClipPathSpec::Type::Circle;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
bool parseEllipse(const std::string &body, ClipPathSpec *spec) {
|
|
171
|
+
std::string radii = body;
|
|
172
|
+
std::string center;
|
|
173
|
+
splitKeyword(body, "at", &radii, ¢er);
|
|
174
|
+
if (!center.empty() && !parseCenter(center, &spec->center_x, &spec->center_y)) return false;
|
|
175
|
+
std::vector<std::string> tokens = splitTokens(radii);
|
|
176
|
+
if (!tokens.empty()) {
|
|
177
|
+
Length rx = xt_css_parse_length(tokens[0]);
|
|
178
|
+
Length ry = tokens.size() > 1 ? xt_css_parse_length(tokens[1]) : rx;
|
|
179
|
+
if (rx.unit == Unit::Invalid || ry.unit == Unit::Invalid) return false;
|
|
180
|
+
spec->radius_x = rx;
|
|
181
|
+
spec->radius_y = ry;
|
|
182
|
+
spec->radius_explicit = true;
|
|
183
|
+
}
|
|
184
|
+
spec->type = ClipPathSpec::Type::Ellipse;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
bool parsePolygon(const std::string &body, ClipPathSpec *spec) {
|
|
189
|
+
std::string text = xt_css_trim(body);
|
|
190
|
+
std::string lower = xt_css_lower(text);
|
|
191
|
+
if (lower.rfind("nonzero", 0) == 0) text = xt_css_trim(text.substr(7));
|
|
192
|
+
else if (lower.rfind("evenodd", 0) == 0) text = xt_css_trim(text.substr(7));
|
|
193
|
+
/* Pairs are separated by commas in CSS, but whitespace is accepted too. */
|
|
194
|
+
std::vector<std::string> tokens = splitTokens(text);
|
|
195
|
+
if (tokens.size() < 3) return false;
|
|
196
|
+
std::vector<Length> points;
|
|
197
|
+
for (const std::string &token : tokens) {
|
|
198
|
+
Length length = xt_css_parse_length(token);
|
|
199
|
+
if (length.unit == Unit::Invalid) return false;
|
|
200
|
+
points.push_back(length);
|
|
201
|
+
}
|
|
202
|
+
if (points.size() < 3) return false;
|
|
203
|
+
if (points.size() % 2 != 0) points.pop_back();
|
|
204
|
+
spec->polygon = std::move(points);
|
|
205
|
+
spec->type = ClipPathSpec::Type::Polygon;
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} // namespace
|
|
210
|
+
|
|
211
|
+
bool xt_style_apply_clip_path(XtStyle *style, const std::string &property,
|
|
212
|
+
const std::string &value) {
|
|
213
|
+
if (property != "clip-path") return false;
|
|
214
|
+
std::string text = xt_css_trim(value);
|
|
215
|
+
std::string lower = xt_css_lower(text);
|
|
216
|
+
ClipPathSpec spec;
|
|
217
|
+
if (lower == "none" || text.empty()) {
|
|
218
|
+
style->clip_path = spec;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (text.size() < 8 || text.back() != ')') {
|
|
222
|
+
style->clip_path = spec;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (lower.rfind("inset(", 0) == 0) {
|
|
226
|
+
if (parseInset(text.substr(6, text.size() - 7), &spec)) style->clip_path = spec;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (lower.rfind("circle(", 0) == 0) {
|
|
230
|
+
if (parseCircle(text.substr(7, text.size() - 8), &spec)) style->clip_path = spec;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (lower.rfind("ellipse(", 0) == 0) {
|
|
234
|
+
if (parseEllipse(text.substr(8, text.size() - 9), &spec)) style->clip_path = spec;
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (lower.rfind("polygon(", 0) == 0) {
|
|
238
|
+
if (parsePolygon(text.substr(8, text.size() - 9), &spec)) style->clip_path = spec;
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
/* `url(#…)`, `path()` and other shapes are not part of the subset. */
|
|
242
|
+
style->clip_path = spec;
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
} // namespace xtgui
|
package/runtime/ext_gui/css.cpp
CHANGED
|
@@ -45,6 +45,39 @@ std::vector<std::string> xt_css_split_whitespace(const std::string &text) {
|
|
|
45
45
|
return parts;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
std::vector<std::string> xt_css_split_commas(const std::string &text) {
|
|
49
|
+
std::vector<std::string> parts;
|
|
50
|
+
std::string current;
|
|
51
|
+
int depth = 0;
|
|
52
|
+
char quote = 0;
|
|
53
|
+
for (char c : text) {
|
|
54
|
+
if (quote != 0) {
|
|
55
|
+
current.push_back(c);
|
|
56
|
+
if (c == quote) quote = 0;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (c == '"' || c == '\'') {
|
|
60
|
+
quote = c;
|
|
61
|
+
current.push_back(c);
|
|
62
|
+
} else if (c == '(' || c == '[') {
|
|
63
|
+
depth++;
|
|
64
|
+
current.push_back(c);
|
|
65
|
+
} else if (c == ')' || c == ']') {
|
|
66
|
+
if (depth > 0) depth--;
|
|
67
|
+
current.push_back(c);
|
|
68
|
+
} else if (c == ',' && depth == 0) {
|
|
69
|
+
std::string part = xt_css_trim(current);
|
|
70
|
+
if (!part.empty()) parts.push_back(part);
|
|
71
|
+
current.clear();
|
|
72
|
+
} else {
|
|
73
|
+
current.push_back(c);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
std::string part = xt_css_trim(current);
|
|
77
|
+
if (!part.empty()) parts.push_back(part);
|
|
78
|
+
return parts;
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
/* -- numbers / lengths / colors ------------------------------------------- */
|
|
49
82
|
|
|
50
83
|
bool xt_css_parse_number(const std::string &value, float *out) {
|
|
@@ -59,6 +92,60 @@ bool xt_css_parse_number(const std::string &value, float *out) {
|
|
|
59
92
|
return true;
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
|
|
96
|
+
/** Split a CSS function's argument list at top-level commas. */
|
|
97
|
+
static std::vector<std::string> splitFunctionArgs(const std::string &text) {
|
|
98
|
+
std::vector<std::string> args;
|
|
99
|
+
std::string current;
|
|
100
|
+
int depth = 0;
|
|
101
|
+
for (char c : text) {
|
|
102
|
+
if (c == '(') depth++;
|
|
103
|
+
if (c == ')') depth--;
|
|
104
|
+
if (c == ',' && depth == 0) {
|
|
105
|
+
args.push_back(xt_css_trim(current));
|
|
106
|
+
current.clear();
|
|
107
|
+
} else {
|
|
108
|
+
current.push_back(c);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!current.empty() || !args.empty()) args.push_back(xt_css_trim(current));
|
|
112
|
+
return args;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `clamp(min, value, max)`, `min(...)` and `max(...)` over lengths. The
|
|
116
|
+
* arguments stay unresolved; layout resolves them against the element's em and
|
|
117
|
+
* the viewport. `env(safe-area-inset-*)` has no notch information in a desktop
|
|
118
|
+
* window, so it resolves to zero (its fallback is ignored, matching the
|
|
119
|
+
* browser's value without insets). */
|
|
120
|
+
static Length parseFunctionLength(const std::string &name, const std::string &inner) {
|
|
121
|
+
Length result;
|
|
122
|
+
if (name == "env") {
|
|
123
|
+
result.unit = Unit::Px;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
if (name != "clamp" && name != "min" && name != "max") {
|
|
127
|
+
result.unit = Unit::Invalid;
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
std::vector<std::string> args = splitFunctionArgs(inner);
|
|
131
|
+
size_t expected = name == "clamp" ? 3 : 1;
|
|
132
|
+
if (args.size() < expected) {
|
|
133
|
+
result.unit = Unit::Invalid;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
result.unit = name == "clamp" ? Unit::Clamp : (name == "min" ? Unit::Min : Unit::Max);
|
|
137
|
+
for (const std::string &arg : args) {
|
|
138
|
+
Length value = xt_css_parse_length(arg);
|
|
139
|
+
if (value.unit == Unit::Invalid) {
|
|
140
|
+
result.unit = Unit::Invalid;
|
|
141
|
+
result.args.clear();
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
result.args.push_back(value);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
62
149
|
Length xt_css_parse_length(const std::string &value) {
|
|
63
150
|
Length length;
|
|
64
151
|
std::string text = xt_css_lower(xt_css_trim(value));
|
|
@@ -74,6 +161,13 @@ Length xt_css_parse_length(const std::string &value) {
|
|
|
74
161
|
length.unit = Unit::None;
|
|
75
162
|
return length;
|
|
76
163
|
}
|
|
164
|
+
size_t open = text.find('(');
|
|
165
|
+
if (open != std::string::npos && !text.empty() && text.back() == ')') {
|
|
166
|
+
std::string name = text.substr(0, open);
|
|
167
|
+
if (name == "clamp" || name == "min" || name == "max" || name == "env") {
|
|
168
|
+
return parseFunctionLength(name, text.substr(open + 1, text.size() - open - 2));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
77
171
|
char *end = nullptr;
|
|
78
172
|
float number = strtof(text.c_str(), &end);
|
|
79
173
|
if (end == text.c_str()) {
|
package/runtime/ext_gui/css.h
CHANGED
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
namespace xtgui {
|
|
19
19
|
|
|
20
|
-
enum class Unit { Auto, None, Px, Percent, Em, Rem, Vw, Vh, Number, Invalid };
|
|
20
|
+
enum class Unit { Auto, None, Px, Percent, Em, Rem, Vw, Vh, Number, Clamp, Min, Max, Invalid };
|
|
21
21
|
|
|
22
22
|
struct Length {
|
|
23
23
|
float value = 0.0f;
|
|
24
24
|
Unit unit = Unit::Auto;
|
|
25
|
+
/* Arguments of `clamp(...)`, `min(...)` and `max(...)`; empty for a scalar
|
|
26
|
+
* length. Kept unresolved so vw/vh/em are resolved against the element that
|
|
27
|
+
* uses the length, not the viewport at parse time. */
|
|
28
|
+
std::vector<Length> args = {};
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
struct Color {
|
|
@@ -52,6 +56,9 @@ struct CompoundSelector {
|
|
|
52
56
|
std::vector<std::string> classes;
|
|
53
57
|
std::vector<AttrSelector> attrs;
|
|
54
58
|
std::vector<std::string> pseudoClasses;
|
|
59
|
+
/** `before`/`after` for a `::before`/`::after` (or legacy `:before`)
|
|
60
|
+
* pseudo-element; empty for an ordinary selector. */
|
|
61
|
+
std::string pseudo_element;
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
enum class Combinator { Descendant, Child, Adjacent, Sibling };
|
|
@@ -66,6 +73,10 @@ struct Rule {
|
|
|
66
73
|
std::vector<ComplexSelector> selectors;
|
|
67
74
|
std::vector<Declaration> declarations;
|
|
68
75
|
int order = 0;
|
|
76
|
+
/** Raw `@media` prelude this rule came from (empty = unconditional). The
|
|
77
|
+
* query is evaluated against the current viewport during the cascade, so a
|
|
78
|
+
* resize restyles with the right rules. */
|
|
79
|
+
std::string media;
|
|
69
80
|
};
|
|
70
81
|
|
|
71
82
|
struct StyleSheet {
|
|
@@ -85,6 +96,16 @@ std::vector<Declaration> xt_css_parse_declarations(const std::string &text);
|
|
|
85
96
|
bool xt_css_match(const Node *node, const ComplexSelector &selector);
|
|
86
97
|
bool xt_css_match(const Node *node, const ComplexSelector &selector, const MatchState &state);
|
|
87
98
|
|
|
99
|
+
/** True when a raw `@media` prelude matches a viewport. Supports media types
|
|
100
|
+
* (`all`/`screen`), comma-separated query lists, `not`/`only`, `and`, and the
|
|
101
|
+
* width/height/orientation/hover/pointer/colour-scheme features (with
|
|
102
|
+
* `min-`/`max-` ranges). Unknown features never match, as in a browser. */
|
|
103
|
+
bool xt_css_media_matches(const std::string &query, float viewport_width, float viewport_height);
|
|
104
|
+
|
|
105
|
+
/** Split `text` at top-level commas (outside `(...)`, `[...]` and quotes),
|
|
106
|
+
* trimming each part and dropping empty ones. */
|
|
107
|
+
std::vector<std::string> xt_css_split_commas(const std::string &text);
|
|
108
|
+
|
|
88
109
|
Length xt_css_parse_length(const std::string &value);
|
|
89
110
|
Color xt_css_parse_color(const std::string &value);
|
|
90
111
|
bool xt_css_parse_number(const std::string &value, float *out);
|
|
@@ -132,6 +132,9 @@ bool matchPseudo(const Node *node, const std::string &pseudo, const MatchState &
|
|
|
132
132
|
|
|
133
133
|
bool matchCompound(const Node *node, const CompoundSelector &compound, const MatchState &state) {
|
|
134
134
|
if (!node->isElement()) return false;
|
|
135
|
+
/* A pseudo-element selector matches a generated node, never the origin
|
|
136
|
+
* element; the cascade matches it separately in `style.cpp`. */
|
|
137
|
+
if (!compound.pseudo_element.empty()) return false;
|
|
135
138
|
if (!compound.tag.empty() && node->tag != compound.tag) return false;
|
|
136
139
|
if (!compound.id.empty()) {
|
|
137
140
|
const std::string *id = node->attr("id");
|
|
@@ -72,6 +72,13 @@ class SelectorParser {
|
|
|
72
72
|
}
|
|
73
73
|
std::string name = xt_css_lower(readIdent(text, index));
|
|
74
74
|
if (name.empty()) return false;
|
|
75
|
+
/* `::before`/`::after` are generated-content elements, not states. The
|
|
76
|
+
* single-colon spellings (`:before`) are the legacy equivalent. */
|
|
77
|
+
if (pseudoElement || name == "before" || name == "after") {
|
|
78
|
+
compound->pseudo_element = name;
|
|
79
|
+
any = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
75
82
|
if (!pseudoElement && *index < text.size() && text[*index] == '(') {
|
|
76
83
|
int depth = 0;
|
|
77
84
|
size_t start = *index;
|
|
@@ -192,6 +199,8 @@ class SelectorParser {
|
|
|
192
199
|
if (!compound.id.empty()) ids++;
|
|
193
200
|
classes += computeCompoundSpecificity(compound);
|
|
194
201
|
if (!compound.tag.empty()) types++;
|
|
202
|
+
/* A pseudo-element counts like a type selector (CSS specificity). */
|
|
203
|
+
if (!compound.pseudo_element.empty()) types++;
|
|
195
204
|
}
|
|
196
205
|
return (ids << 20) | (classes << 10) | types;
|
|
197
206
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* xbintsc GUI engine — CSS declaration and stylesheet parsing.
|
|
3
|
+
*
|
|
4
|
+
* At-rules are no longer all skipped: `@media` blocks are parsed recursively
|
|
5
|
+
* and each rule keeps its (possibly nested) query, which the cascade evaluates
|
|
6
|
+
* against the viewport. Every other at-rule is ignored with its block.
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
9
|
#include "css_internal.h"
|
|
@@ -41,6 +45,42 @@ size_t findTopLevel(const std::string &text, char needle, size_t from) {
|
|
|
41
45
|
return std::string::npos;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/** The `}` matching the `{` at `open`, respecting strings and nested blocks. */
|
|
49
|
+
size_t findBlockEnd(const std::string &text, size_t open) {
|
|
50
|
+
int depth = 0;
|
|
51
|
+
char quote = 0;
|
|
52
|
+
for (size_t i = open; i < text.size(); i++) {
|
|
53
|
+
char c = text[i];
|
|
54
|
+
if (quote != 0) {
|
|
55
|
+
if (c == quote) quote = 0;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (c == '"' || c == '\'') quote = c;
|
|
59
|
+
else if (c == '{') depth++;
|
|
60
|
+
else if (c == '}') {
|
|
61
|
+
depth--;
|
|
62
|
+
if (depth == 0) return i;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return std::string::npos;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Join a nested `@media` prelude: distribute `inner` over an outer comma list. */
|
|
69
|
+
std::string combineMedia(const std::string &outer, const std::string &inner) {
|
|
70
|
+
if (outer.empty()) return inner;
|
|
71
|
+
if (inner.empty()) return outer;
|
|
72
|
+
std::vector<std::string> outerParts = xt_css_split_commas(outer);
|
|
73
|
+
std::vector<std::string> innerParts = xt_css_split_commas(inner);
|
|
74
|
+
std::string combined;
|
|
75
|
+
for (const std::string &o : outerParts) {
|
|
76
|
+
for (const std::string &i : innerParts) {
|
|
77
|
+
if (!combined.empty()) combined += ", ";
|
|
78
|
+
combined += o + " and " + i;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return combined;
|
|
82
|
+
}
|
|
83
|
+
|
|
44
84
|
std::string stripImportant(std::string value, bool *important) {
|
|
45
85
|
size_t pos = value.rfind('!');
|
|
46
86
|
if (pos == std::string::npos) return value;
|
|
@@ -52,6 +92,54 @@ std::string stripImportant(std::string value, bool *important) {
|
|
|
52
92
|
return value;
|
|
53
93
|
}
|
|
54
94
|
|
|
95
|
+
/** Parse one rule list, carrying the enclosing `@media` prelude. */
|
|
96
|
+
void parseRules(const std::string &text, const std::string &media, int *order,
|
|
97
|
+
StyleSheet *sheet) {
|
|
98
|
+
size_t index = 0;
|
|
99
|
+
while (index < text.size()) {
|
|
100
|
+
while (index < text.size() && xt_is_css_space(text[index])) index++;
|
|
101
|
+
if (index >= text.size()) break;
|
|
102
|
+
|
|
103
|
+
if (text[index] == '@') {
|
|
104
|
+
size_t nameStart = index + 1;
|
|
105
|
+
size_t nameEnd = nameStart;
|
|
106
|
+
while (nameEnd < text.size() &&
|
|
107
|
+
(std::isalnum((unsigned char)text[nameEnd]) || text[nameEnd] == '-'))
|
|
108
|
+
nameEnd++;
|
|
109
|
+
std::string name = xt_css_lower(text.substr(nameStart, nameEnd - nameStart));
|
|
110
|
+
size_t brace = text.find('{', nameEnd);
|
|
111
|
+
size_t semi = text.find(';', nameEnd);
|
|
112
|
+
if (brace != std::string::npos && (semi == std::string::npos || brace < semi)) {
|
|
113
|
+
size_t close = findBlockEnd(text, brace);
|
|
114
|
+
if (close == std::string::npos) break;
|
|
115
|
+
if (name == "media") {
|
|
116
|
+
std::string query = xt_css_trim(text.substr(nameEnd, brace - nameEnd));
|
|
117
|
+
std::string nested = combineMedia(media, query);
|
|
118
|
+
parseRules(text.substr(brace + 1, close - brace - 1), nested, order, sheet);
|
|
119
|
+
}
|
|
120
|
+
/* Other block at-rules (`@supports`, `@keyframes`, `@font-face`, …) are
|
|
121
|
+
* not part of the engine's subset: ignore the whole block. */
|
|
122
|
+
index = close + 1;
|
|
123
|
+
} else {
|
|
124
|
+
index = (semi == std::string::npos) ? text.size() : semi + 1;
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
size_t brace = text.find('{', index);
|
|
130
|
+
if (brace == std::string::npos) break;
|
|
131
|
+
size_t close = findBlockEnd(text, brace);
|
|
132
|
+
if (close == std::string::npos) break;
|
|
133
|
+
Rule rule;
|
|
134
|
+
rule.selectors = xt_css_parse_selector_list(text.substr(index, brace - index));
|
|
135
|
+
rule.declarations = xt_css_parse_declarations(text.substr(brace + 1, close - brace - 1));
|
|
136
|
+
rule.order = (*order)++;
|
|
137
|
+
rule.media = media;
|
|
138
|
+
if (!rule.selectors.empty() && !rule.declarations.empty()) sheet->rules.push_back(std::move(rule));
|
|
139
|
+
index = close + 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
55
143
|
} // namespace
|
|
56
144
|
|
|
57
145
|
std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
|
|
@@ -77,52 +165,8 @@ std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
|
|
|
77
165
|
StyleSheet xt_css_parse_stylesheet(const std::string &text) {
|
|
78
166
|
StyleSheet sheet;
|
|
79
167
|
std::string clean = stripComments(text);
|
|
80
|
-
size_t index = 0;
|
|
81
168
|
int order = 0;
|
|
82
|
-
|
|
83
|
-
while (index < clean.size() && xt_is_css_space(clean[index])) index++;
|
|
84
|
-
if (index >= clean.size()) break;
|
|
85
|
-
if (clean[index] == '@') {
|
|
86
|
-
size_t brace = clean.find('{', index);
|
|
87
|
-
size_t semi = clean.find(';', index);
|
|
88
|
-
if (brace != std::string::npos && (semi == std::string::npos || brace < semi)) {
|
|
89
|
-
int depth = 0;
|
|
90
|
-
size_t i = brace;
|
|
91
|
-
for (; i < clean.size(); i++) {
|
|
92
|
-
if (clean[i] == '{') depth++;
|
|
93
|
-
else if (clean[i] == '}') {
|
|
94
|
-
depth--;
|
|
95
|
-
if (depth == 0) break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
index = (i < clean.size()) ? i + 1 : clean.size();
|
|
99
|
-
} else {
|
|
100
|
-
index = (semi == std::string::npos) ? clean.size() : semi + 1;
|
|
101
|
-
}
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
size_t brace = clean.find('{', index);
|
|
105
|
-
if (brace == std::string::npos) break;
|
|
106
|
-
int depth = 0;
|
|
107
|
-
size_t close = std::string::npos;
|
|
108
|
-
for (size_t i = brace; i < clean.size(); i++) {
|
|
109
|
-
if (clean[i] == '{') depth++;
|
|
110
|
-
else if (clean[i] == '}') {
|
|
111
|
-
depth--;
|
|
112
|
-
if (depth == 0) {
|
|
113
|
-
close = i;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (close == std::string::npos) break;
|
|
119
|
-
Rule rule;
|
|
120
|
-
rule.selectors = xt_css_parse_selector_list(clean.substr(index, brace - index));
|
|
121
|
-
rule.declarations = xt_css_parse_declarations(clean.substr(brace + 1, close - brace - 1));
|
|
122
|
-
rule.order = order++;
|
|
123
|
-
if (!rule.selectors.empty() && !rule.declarations.empty()) sheet.rules.push_back(std::move(rule));
|
|
124
|
-
index = close + 1;
|
|
125
|
-
}
|
|
169
|
+
parseRules(clean, "", &order, &sheet);
|
|
126
170
|
return sheet;
|
|
127
171
|
}
|
|
128
172
|
|
|
@@ -47,7 +47,7 @@ void XtDocument::restyle(float viewportWidth, float viewportHeight) {
|
|
|
47
47
|
MatchState state;
|
|
48
48
|
state.hover = hover_;
|
|
49
49
|
state.focus = focus_;
|
|
50
|
-
xt_style_compute(root_.get(), sheet_, styles_, viewportWidth, viewportHeight, state);
|
|
50
|
+
xt_style_compute(root_.get(), sheet_, styles_, viewportWidth, viewportHeight, state, &pseudos_);
|
|
51
51
|
startTransitions();
|
|
52
52
|
updateDisplay();
|
|
53
53
|
}
|
|
@@ -124,6 +124,9 @@ void XtDocument::startTransitions() {
|
|
|
124
124
|
for (auto &entry : styles_) {
|
|
125
125
|
const Node *node = entry.first;
|
|
126
126
|
const XtStyle &target = entry.second;
|
|
127
|
+
/* Generated boxes are rebuilt on every restyle; a transition keyed by one
|
|
128
|
+
* would address a node that no longer exists. */
|
|
129
|
+
if (node->is_pseudo) continue;
|
|
127
130
|
if (target.transitions.empty()) continue;
|
|
128
131
|
auto prev_it = display_.find(node);
|
|
129
132
|
if (prev_it == display_.end()) continue;
|
|
@@ -118,6 +118,8 @@ class XtDocument {
|
|
|
118
118
|
std::unique_ptr<Node> root_;
|
|
119
119
|
/** Detached nodes (created or removed) stay alive here so handles remain valid. */
|
|
120
120
|
std::unordered_map<const Node *, std::unique_ptr<Node>> detached_;
|
|
121
|
+
/** Synthetic `::before`/`::after` nodes for the current style generation. */
|
|
122
|
+
PseudoNodeSet pseudos_;
|
|
121
123
|
bool dirty_ = false;
|
|
122
124
|
StyleSheet sheet_;
|
|
123
125
|
std::unordered_map<const Node *, XtStyle> styles_; // target (cascade)
|
package/runtime/ext_gui/dom.h
CHANGED
|
@@ -18,6 +18,9 @@ namespace xtgui {
|
|
|
18
18
|
|
|
19
19
|
enum class NodeType { Document, Element, Text };
|
|
20
20
|
|
|
21
|
+
/** Which generated-content box a synthetic pseudo-element node represents. */
|
|
22
|
+
enum class PseudoElement { None, Before, After };
|
|
23
|
+
|
|
21
24
|
struct Attr {
|
|
22
25
|
std::string name;
|
|
23
26
|
std::string value;
|
|
@@ -32,6 +35,19 @@ struct Node {
|
|
|
32
35
|
Node *parent = nullptr;
|
|
33
36
|
std::vector<std::unique_ptr<Node>> children;
|
|
34
37
|
|
|
38
|
+
/* -- generated content (`::before` / `::after`) -------------------------
|
|
39
|
+
* A pseudo-element is a synthetic node owned by the document's per-restyle
|
|
40
|
+
* arena, not by the DOM tree: it is never in `children`, so traversal,
|
|
41
|
+
* queries, serialization and `elementIndex()` do not see it. `pseudo_origin`
|
|
42
|
+
* is the element the selector matched and whose computed style it inherits.
|
|
43
|
+
* `pseudo_before`/`pseudo_after` are transient pointers set by the cascade
|
|
44
|
+
* for the layout pass (the arena is rebuilt on every restyle). */
|
|
45
|
+
bool is_pseudo = false;
|
|
46
|
+
PseudoElement pseudo = PseudoElement::None;
|
|
47
|
+
const Node *pseudo_origin = nullptr;
|
|
48
|
+
Node *pseudo_before = nullptr;
|
|
49
|
+
Node *pseudo_after = nullptr;
|
|
50
|
+
|
|
35
51
|
Node() = default;
|
|
36
52
|
explicit Node(NodeType nodeType) : type(nodeType) {}
|
|
37
53
|
|
|
@@ -65,6 +65,7 @@ static xtgui::Node *bind_pending(XtGuiWindow *win, xt_value self, xtgui::Node *n
|
|
|
65
65
|
xt_object_set(self, xt_string_from_cstr("__xt_gui_gen"),
|
|
66
66
|
xt_object_get_cstr(canonical, "__xt_gui_gen"));
|
|
67
67
|
xt_object_set(self, xt_string_from_cstr("__xt_gui_pending"), XT_UNDEFINED);
|
|
68
|
+
xt_gui_canvas_bind_pending(win, node, self);
|
|
68
69
|
return node;
|
|
69
70
|
}
|
|
70
71
|
|