xbintsc 0.3.18 → 0.3.35
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 +33 -0
- package/dist/src/cli/main.js +124 -30
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +8 -1
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/state.js +20 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +6 -0
- package/dist/src/codegen/generator/tables.js +38 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/commonjs.d.ts +52 -0
- package/dist/src/driver/bundler/commonjs.js +493 -0
- package/dist/src/driver/bundler/commonjs.js.map +1 -0
- package/dist/src/driver/bundler/graph.d.ts +2 -1
- package/dist/src/driver/bundler/graph.js +32 -3
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.d.ts +2 -1
- package/dist/src/driver/bundler/merge.js +16 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/bundler/types.d.ts +8 -0
- package/dist/src/driver/compiler.d.ts +8 -1
- package/dist/src/driver/compiler.js +75 -7
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +53 -0
- package/dist/src/driver/config.js +141 -0
- package/dist/src/driver/config.js.map +1 -0
- package/dist/src/driver/icon.d.ts +62 -0
- package/dist/src/driver/icon.js +151 -0
- package/dist/src/driver/icon.js.map +1 -0
- package/dist/src/driver/index.d.ts +4 -0
- package/dist/src/driver/index.js +4 -0
- package/dist/src/driver/index.js.map +1 -1
- package/dist/src/driver/mac-bundle.d.ts +41 -0
- package/dist/src/driver/mac-bundle.js +123 -0
- package/dist/src/driver/mac-bundle.js.map +1 -0
- package/dist/src/driver/win-icon.d.ts +27 -0
- package/dist/src/driver/win-icon.js +80 -0
- package/dist/src/driver/win-icon.js.map +1 -0
- package/dist/src/extensions/gui/html.d.ts +25 -0
- package/dist/src/extensions/gui/html.js +203 -0
- package/dist/src/extensions/gui/html.js.map +1 -0
- package/dist/src/extensions/gui/index.js +11 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/registry.d.ts +29 -0
- package/dist/src/extensions/registry.js +14 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lexer/scanner/identifiers.js +4 -12
- package/dist/src/lexer/scanner/identifiers.js.map +1 -1
- package/dist/src/parser/statements.js +17 -4
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/tests/cli/main.test.js +46 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
- package/dist/tests/driver/bundler-resolve.test.js +230 -0
- package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
- package/dist/tests/driver/config.test.d.ts +1 -0
- package/dist/tests/driver/config.test.js +124 -0
- package/dist/tests/driver/config.test.js.map +1 -0
- package/dist/tests/driver/icon.test.d.ts +1 -0
- package/dist/tests/driver/icon.test.js +421 -0
- package/dist/tests/driver/icon.test.js.map +1 -0
- package/dist/tests/driver/modules.test.js +177 -0
- package/dist/tests/driver/modules.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.d.ts +12 -0
- package/dist/tests/e2e/fetch.test.js +272 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -0
- package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
- package/dist/tests/e2e/gui-dom.test.js +102 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -0
- package/dist/tests/e2e/gui-events.test.d.ts +5 -0
- package/dist/tests/e2e/gui-events.test.js +60 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +31 -0
- package/dist/tests/e2e/gui-helpers.js +74 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -0
- package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
- package/dist/tests/e2e/gui-layout.test.js +88 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -0
- package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
- package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
- package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
- package/dist/tests/e2e/gui-paint.test.js +111 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.d.ts +5 -0
- package/dist/tests/e2e/gui-script.test.js +78 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -0
- package/dist/tests/e2e/gui-style.test.d.ts +5 -0
- package/dist/tests/e2e/gui-style.test.js +175 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -0
- package/dist/tests/e2e/icon.test.d.ts +7 -0
- package/dist/tests/e2e/icon.test.js +80 -0
- package/dist/tests/e2e/icon.test.js.map +1 -0
- package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
- package/dist/tests/e2e/npm-modules.test.js +47 -1
- package/dist/tests/e2e/npm-modules.test.js.map +1 -1
- package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
- package/dist/tests/extensions/gui-index.test.d.ts +1 -0
- package/dist/tests/extensions/gui-index.test.js +49 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.d.ts +1 -0
- package/dist/tests/extensions/gui.test.js +177 -0
- package/dist/tests/extensions/gui.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.js +36 -0
- package/dist/tests/extensions/registry.test.js.map +1 -1
- package/dist/tests/parser/statements.test.js +11 -0
- package/dist/tests/parser/statements.test.js.map +1 -1
- package/dist/tests/public-api.test.d.ts +1 -0
- package/dist/tests/public-api.test.js +31 -0
- package/dist/tests/public-api.test.js.map +1 -0
- package/package.json +1 -1
- package/runtime/ext_gui/css.cpp +4 -517
- package/runtime/ext_gui/css_internal.h +27 -0
- package/runtime/ext_gui/css_match.cpp +195 -0
- package/runtime/ext_gui/css_selector.cpp +207 -0
- package/runtime/ext_gui/css_sheet.cpp +129 -0
- package/runtime/ext_gui/document.cpp +90 -0
- package/runtime/ext_gui/document.h +23 -0
- package/runtime/ext_gui/dom.cpp +43 -403
- package/runtime/ext_gui/dom.h +7 -0
- package/runtime/ext_gui/dom_api.cpp +164 -0
- package/runtime/ext_gui/dom_api.h +12 -0
- package/runtime/ext_gui/dom_api_document.cpp +96 -0
- package/runtime/ext_gui/dom_api_events.cpp +123 -0
- package/runtime/ext_gui/dom_api_internal.h +135 -0
- package/runtime/ext_gui/dom_api_node.cpp +329 -0
- package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
- package/runtime/ext_gui/dom_api_proto.cpp +130 -0
- package/runtime/ext_gui/dom_api_style.cpp +192 -0
- package/runtime/ext_gui/dom_internal.h +39 -0
- package/runtime/ext_gui/dom_parse.cpp +348 -0
- package/runtime/ext_gui/dom_serialize.cpp +54 -0
- package/runtime/ext_gui/gui.cpp +61 -245
- package/runtime/ext_gui/gui_engine.h +54 -0
- package/runtime/ext_gui/gui_events.cpp +294 -0
- package/runtime/ext_gui/gui_internal.h +29 -0
- package/runtime/ext_gui/layout.cpp +6 -597
- package/runtime/ext_gui/layout_flex.cpp +200 -0
- package/runtime/ext_gui/layout_flow.cpp +191 -0
- package/runtime/ext_gui/layout_flow_internal.h +149 -0
- package/runtime/ext_gui/layout_inline.cpp +181 -0
- package/runtime/ext_gui/layout_internal.h +20 -0
- package/runtime/ext_gui/renderer.cpp +15 -679
- package/runtime/ext_gui/renderer_image.cpp +233 -0
- package/runtime/ext_gui/renderer_internal.h +112 -0
- package/runtime/ext_gui/renderer_shaders.h +108 -0
- package/runtime/ext_gui/renderer_text.cpp +342 -0
- package/runtime/ext_gui/script.cpp +63 -0
- package/runtime/ext_gui/style.cpp +12 -660
- package/runtime/ext_gui/style_internal.h +33 -0
- package/runtime/ext_gui/style_print.cpp +113 -0
- package/runtime/ext_gui/style_ua.cpp +98 -0
- package/runtime/ext_gui/style_values.cpp +468 -0
- package/runtime/ext_gui/window.cpp +60 -0
- package/runtime/rt.h +6 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +1 -0
- package/runtime/xt_containers/access.inc +32 -1
- package/runtime/xt_fetch/buffer.inc +64 -0
- package/runtime/xt_fetch/headers.inc +312 -0
- package/runtime/xt_fetch/request.inc +386 -0
- package/runtime/xt_fetch/response.inc +107 -0
- package/runtime/xt_fetch/socket.inc +72 -0
- package/runtime/xt_fetch/url.inc +108 -0
- package/runtime/xt_fetch.c +39 -0
- package/runtime/xt_stdlib2/set.inc +1 -1
- package/runtime/xt_typed_array.c +614 -0
- package/src/cli/main.ts +137 -29
- package/src/codegen/generator/calls/access.ts +9 -1
- package/src/codegen/generator/state.ts +20 -0
- package/src/codegen/generator/tables.ts +40 -0
- package/src/driver/bundler/commonjs.ts +553 -0
- package/src/driver/bundler/graph.ts +35 -2
- package/src/driver/bundler/merge.ts +18 -1
- package/src/driver/bundler/types.ts +8 -0
- package/src/driver/compiler.ts +85 -7
- package/src/driver/config.ts +173 -0
- package/src/driver/icon.ts +184 -0
- package/src/driver/index.ts +30 -0
- package/src/driver/mac-bundle.ts +156 -0
- package/src/driver/win-icon.ts +96 -0
- package/src/extensions/gui/html.ts +236 -0
- package/src/extensions/gui/index.ts +11 -0
- package/src/extensions/registry.ts +39 -0
- package/src/index.ts +1 -1
- package/src/lexer/scanner/identifiers.ts +4 -11
- package/src/parser/statements.ts +17 -4
- package/dist/tests/e2e/gui.test.d.ts +0 -12
- package/dist/tests/e2e/gui.test.js +0 -493
- package/dist/tests/e2e/gui.test.js.map +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS selector matching against the DOM.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "css_internal.h"
|
|
6
|
+
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <memory>
|
|
9
|
+
|
|
10
|
+
namespace xtgui {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
bool hasClass(const Node *node, const std::string &name) {
|
|
15
|
+
const std::string *value = node->attr("class");
|
|
16
|
+
if (value == nullptr) return false;
|
|
17
|
+
for (const std::string &token : xt_css_split_whitespace(*value)) {
|
|
18
|
+
if (token == name) return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
bool matchAttribute(const Node *node, const AttrSelector &attr) {
|
|
24
|
+
const std::string *value = node->attr(attr.name);
|
|
25
|
+
if (value == nullptr) return false;
|
|
26
|
+
if (attr.op.empty()) return true;
|
|
27
|
+
if (attr.op == "=") return *value == attr.value;
|
|
28
|
+
if (attr.op == "~=") {
|
|
29
|
+
for (const std::string &token : xt_css_split_whitespace(*value)) {
|
|
30
|
+
if (token == attr.value) return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (attr.op == "|=") {
|
|
35
|
+
return *value == attr.value || value->rfind(attr.value + "-", 0) == 0;
|
|
36
|
+
}
|
|
37
|
+
if (attr.op == "^=") return !attr.value.empty() && value->rfind(attr.value, 0) == 0;
|
|
38
|
+
if (attr.op == "$=") {
|
|
39
|
+
return !attr.value.empty() && value->size() >= attr.value.size() &&
|
|
40
|
+
value->compare(value->size() - attr.value.size(), attr.value.size(), attr.value) == 0;
|
|
41
|
+
}
|
|
42
|
+
if (attr.op == "*=") {
|
|
43
|
+
return !attr.value.empty() && value->find(attr.value) != std::string::npos;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Parse the `an+b` micro-syntax of `:nth-child`. */
|
|
49
|
+
bool parseNth(const std::string &expression, int *a, int *b) {
|
|
50
|
+
std::string text;
|
|
51
|
+
for (char c : xt_css_lower(xt_css_trim(expression))) {
|
|
52
|
+
if (!xt_is_css_space(c)) text.push_back(c);
|
|
53
|
+
}
|
|
54
|
+
if (text == "odd") { *a = 2; *b = 1; return true; }
|
|
55
|
+
if (text == "even") { *a = 2; *b = 0; return true; }
|
|
56
|
+
size_t nPos = text.find('n');
|
|
57
|
+
if (nPos == std::string::npos) {
|
|
58
|
+
*a = 0;
|
|
59
|
+
*b = atoi(text.c_str());
|
|
60
|
+
return !text.empty();
|
|
61
|
+
}
|
|
62
|
+
std::string aPart = text.substr(0, nPos);
|
|
63
|
+
std::string bPart = text.substr(nPos + 1);
|
|
64
|
+
if (aPart.empty() || aPart == "+") *a = 1;
|
|
65
|
+
else if (aPart == "-") *a = -1;
|
|
66
|
+
else *a = atoi(aPart.c_str());
|
|
67
|
+
if (bPart.empty()) *b = 0;
|
|
68
|
+
else *b = atoi(bPart.c_str());
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
bool matchesNth(int index, const std::string &expression) {
|
|
73
|
+
int a = 0;
|
|
74
|
+
int b = 0;
|
|
75
|
+
if (!parseNth(expression, &a, &b)) return false;
|
|
76
|
+
if (a == 0) return index == b;
|
|
77
|
+
int diff = index - b;
|
|
78
|
+
if (a > 0) return diff >= 0 && diff % a == 0;
|
|
79
|
+
return diff <= 0 && (-diff) % (-a) == 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
bool isHoverTarget(const Node *node, const Node *hover) {
|
|
83
|
+
for (const Node *current = hover; current != nullptr; current = current->parent) {
|
|
84
|
+
if (current == node) return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
bool matchPseudo(const Node *node, const std::string &pseudo, const MatchState &state) {
|
|
90
|
+
if (pseudo == "first-child") return node->elementIndex() == 0 && node->elementSiblingCount() > 0;
|
|
91
|
+
if (pseudo == "last-child") return node->elementIndex() == node->elementSiblingCount() - 1;
|
|
92
|
+
if (pseudo == "only-child") return node->elementSiblingCount() == 1;
|
|
93
|
+
if (pseudo == "root") return node->parent != nullptr && node->parent->isDocument();
|
|
94
|
+
if (pseudo == "hover") return isHoverTarget(node, state.hover);
|
|
95
|
+
if (pseudo == "focus") return node == state.focus;
|
|
96
|
+
if (pseudo == "empty") {
|
|
97
|
+
for (const std::unique_ptr<Node> &child : node->children) {
|
|
98
|
+
if (child->isElement()) return false;
|
|
99
|
+
if (child->isText() && !xt_css_trim(child->text).empty()) return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (pseudo == "disabled") return node->attr("disabled") != nullptr;
|
|
104
|
+
if (pseudo == "checked") return node->attr("checked") != nullptr;
|
|
105
|
+
if (pseudo.rfind("nth-child(", 0) == 0) {
|
|
106
|
+
return matchesNth(node->elementIndex() + 1, pseudo.substr(10, pseudo.size() - 11));
|
|
107
|
+
}
|
|
108
|
+
if (pseudo.rfind("not(", 0) == 0) {
|
|
109
|
+
std::string inner = xt_css_trim(pseudo.substr(4, pseudo.size() - 5));
|
|
110
|
+
std::vector<ComplexSelector> parsed = xt_css_parse_selector_list(inner);
|
|
111
|
+
if (parsed.empty() || parsed[0].compounds.size() != 1) return false;
|
|
112
|
+
const CompoundSelector &compound = parsed[0].compounds[0];
|
|
113
|
+
for (const AttrSelector &attr : compound.attrs) {
|
|
114
|
+
if (!matchAttribute(node, attr)) return true;
|
|
115
|
+
}
|
|
116
|
+
if (!compound.id.empty()) {
|
|
117
|
+
const std::string *id = node->attr("id");
|
|
118
|
+
if (id == nullptr || *id != compound.id) return true;
|
|
119
|
+
}
|
|
120
|
+
if (!compound.tag.empty() && node->tag != compound.tag) return true;
|
|
121
|
+
for (const std::string &name : compound.classes) {
|
|
122
|
+
if (!hasClass(node, name)) return true;
|
|
123
|
+
}
|
|
124
|
+
for (const std::string &nested : compound.pseudoClasses) {
|
|
125
|
+
if (!matchPseudo(node, nested, state)) return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
/* Truly unknown / unsupported pseudo-classes never match. */
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
bool matchCompound(const Node *node, const CompoundSelector &compound, const MatchState &state) {
|
|
134
|
+
if (!node->isElement()) return false;
|
|
135
|
+
if (!compound.tag.empty() && node->tag != compound.tag) return false;
|
|
136
|
+
if (!compound.id.empty()) {
|
|
137
|
+
const std::string *id = node->attr("id");
|
|
138
|
+
if (id == nullptr || *id != compound.id) return false;
|
|
139
|
+
}
|
|
140
|
+
for (const std::string &name : compound.classes) {
|
|
141
|
+
if (!hasClass(node, name)) return false;
|
|
142
|
+
}
|
|
143
|
+
for (const AttrSelector &attr : compound.attrs) {
|
|
144
|
+
if (!matchAttribute(node, attr)) return false;
|
|
145
|
+
}
|
|
146
|
+
for (const std::string &pseudo : compound.pseudoClasses) {
|
|
147
|
+
if (!matchPseudo(node, pseudo, state)) return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bool matchAt(const Node *node, const ComplexSelector &selector, size_t index,
|
|
153
|
+
const MatchState &state) {
|
|
154
|
+
if (!matchCompound(node, selector.compounds[index], state)) return false;
|
|
155
|
+
if (index == 0) return true;
|
|
156
|
+
Combinator combinator = selector.combinators[index - 1];
|
|
157
|
+
if (combinator == Combinator::Child) {
|
|
158
|
+
return node->parent != nullptr && node->parent->isElement() &&
|
|
159
|
+
matchAt(node->parent, selector, index - 1, state);
|
|
160
|
+
}
|
|
161
|
+
if (combinator == Combinator::Descendant) {
|
|
162
|
+
for (const Node *parent = node->parent; parent != nullptr; parent = parent->parent) {
|
|
163
|
+
if (parent->isElement() && matchAt(parent, selector, index - 1, state)) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (node->parent == nullptr) return false;
|
|
168
|
+
if (combinator == Combinator::Adjacent) {
|
|
169
|
+
const Node *previous = nullptr;
|
|
170
|
+
for (const std::unique_ptr<Node> &sibling : node->parent->children) {
|
|
171
|
+
if (sibling.get() == node) break;
|
|
172
|
+
if (sibling->isElement()) previous = sibling.get();
|
|
173
|
+
}
|
|
174
|
+
return previous != nullptr && matchAt(previous, selector, index - 1, state);
|
|
175
|
+
}
|
|
176
|
+
/* General sibling combinator (`~`): any previous element sibling. */
|
|
177
|
+
for (const std::unique_ptr<Node> &sibling : node->parent->children) {
|
|
178
|
+
if (sibling.get() == node) break;
|
|
179
|
+
if (sibling->isElement() && matchAt(sibling.get(), selector, index - 1, state)) return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
} // namespace
|
|
185
|
+
|
|
186
|
+
bool xt_css_match(const Node *node, const ComplexSelector &selector) {
|
|
187
|
+
return xt_css_match(node, selector, MatchState());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
bool xt_css_match(const Node *node, const ComplexSelector &selector, const MatchState &state) {
|
|
191
|
+
if (selector.compounds.empty()) return false;
|
|
192
|
+
return matchAt(node, selector, selector.compounds.size() - 1, state);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} // namespace xtgui
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS selector list parser.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "css_internal.h"
|
|
6
|
+
|
|
7
|
+
namespace xtgui {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
class SelectorParser {
|
|
12
|
+
public:
|
|
13
|
+
explicit SelectorParser(const std::string &text) : text_(text) {}
|
|
14
|
+
|
|
15
|
+
std::vector<ComplexSelector> parseList() {
|
|
16
|
+
std::vector<ComplexSelector> selectors;
|
|
17
|
+
size_t start = 0;
|
|
18
|
+
int depth = 0;
|
|
19
|
+
for (size_t i = 0; i <= text_.size(); i++) {
|
|
20
|
+
char c = (i < text_.size()) ? text_[i] : ',';
|
|
21
|
+
if (c == '(' || c == '[') depth++;
|
|
22
|
+
else if (c == ')' || c == ']') depth--;
|
|
23
|
+
else if (c == ',' && depth == 0) {
|
|
24
|
+
std::string part = text_.substr(start, i - start);
|
|
25
|
+
ComplexSelector selector;
|
|
26
|
+
if (parseComplex(part, &selector) && !selector.compounds.empty()) {
|
|
27
|
+
selectors.push_back(std::move(selector));
|
|
28
|
+
}
|
|
29
|
+
start = i + 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return selectors;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
const std::string &text_;
|
|
37
|
+
|
|
38
|
+
std::string readIdent(const std::string &text, size_t *index) {
|
|
39
|
+
size_t start = *index;
|
|
40
|
+
while (*index < text.size() && xt_css_is_ident_char(text[*index])) (*index)++;
|
|
41
|
+
return text.substr(start, *index - start);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Parse `div.foo#bar[a="b"]:pseudo` into one compound selector. */
|
|
45
|
+
bool parseCompound(const std::string &text, size_t *index, CompoundSelector *compound) {
|
|
46
|
+
bool any = false;
|
|
47
|
+
while (*index < text.size()) {
|
|
48
|
+
char c = text[*index];
|
|
49
|
+
if (xt_is_css_space(c) || c == '>' || c == '+' || c == '~' || c == ',') break;
|
|
50
|
+
if (c == '*') {
|
|
51
|
+
(*index)++;
|
|
52
|
+
any = true;
|
|
53
|
+
} else if (c == '.') {
|
|
54
|
+
(*index)++;
|
|
55
|
+
std::string name = readIdent(text, index);
|
|
56
|
+
if (name.empty()) return false;
|
|
57
|
+
compound->classes.push_back(name);
|
|
58
|
+
any = true;
|
|
59
|
+
} else if (c == '#') {
|
|
60
|
+
(*index)++;
|
|
61
|
+
compound->id = readIdent(text, index);
|
|
62
|
+
any = true;
|
|
63
|
+
} else if (c == '[') {
|
|
64
|
+
if (!parseAttribute(text, index, compound)) return false;
|
|
65
|
+
any = true;
|
|
66
|
+
} else if (c == ':') {
|
|
67
|
+
(*index)++;
|
|
68
|
+
bool pseudoElement = false;
|
|
69
|
+
if (*index < text.size() && text[*index] == ':') {
|
|
70
|
+
pseudoElement = true;
|
|
71
|
+
(*index)++;
|
|
72
|
+
}
|
|
73
|
+
std::string name = xt_css_lower(readIdent(text, index));
|
|
74
|
+
if (name.empty()) return false;
|
|
75
|
+
if (!pseudoElement && *index < text.size() && text[*index] == '(') {
|
|
76
|
+
int depth = 0;
|
|
77
|
+
size_t start = *index;
|
|
78
|
+
while (*index < text.size()) {
|
|
79
|
+
if (text[*index] == '(') depth++;
|
|
80
|
+
else if (text[*index] == ')') {
|
|
81
|
+
depth--;
|
|
82
|
+
if (depth == 0) {
|
|
83
|
+
(*index)++;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
(*index)++;
|
|
88
|
+
}
|
|
89
|
+
name += text.substr(start, *index - start);
|
|
90
|
+
}
|
|
91
|
+
compound->pseudoClasses.push_back(name);
|
|
92
|
+
any = true;
|
|
93
|
+
} else if (xt_css_is_ident_start(c)) {
|
|
94
|
+
compound->tag = xt_css_lower(readIdent(text, index));
|
|
95
|
+
any = true;
|
|
96
|
+
} else {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return any;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
bool parseAttribute(const std::string &text, size_t *index, CompoundSelector *compound) {
|
|
104
|
+
(*index)++; // '['
|
|
105
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
106
|
+
AttrSelector attr;
|
|
107
|
+
attr.name = xt_css_lower(readIdent(text, index));
|
|
108
|
+
if (attr.name.empty()) return false;
|
|
109
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
110
|
+
if (*index < text.size() && text[*index] == ']') {
|
|
111
|
+
(*index)++;
|
|
112
|
+
compound->attrs.push_back(attr);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (*index < text.size() && (text[*index] == '=' || text[*index] == '~' || text[*index] == '|' ||
|
|
116
|
+
text[*index] == '^' || text[*index] == '$' || text[*index] == '*')) {
|
|
117
|
+
std::string op(1, text[*index]);
|
|
118
|
+
(*index)++;
|
|
119
|
+
if (*index < text.size() && text[*index] == '=') {
|
|
120
|
+
op += '=';
|
|
121
|
+
(*index)++;
|
|
122
|
+
} else if (op != "=") {
|
|
123
|
+
/* Only `~= |= ^= $= *=` need a trailing `=`; a lone `=` is complete. */
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
127
|
+
std::string value;
|
|
128
|
+
if (*index < text.size() && (text[*index] == '"' || text[*index] == '\'')) {
|
|
129
|
+
char quote = text[(*index)++];
|
|
130
|
+
size_t start = *index;
|
|
131
|
+
while (*index < text.size() && text[*index] != quote) (*index)++;
|
|
132
|
+
value = text.substr(start, *index - start);
|
|
133
|
+
if (*index < text.size()) (*index)++;
|
|
134
|
+
} else {
|
|
135
|
+
value = readIdent(text, index);
|
|
136
|
+
}
|
|
137
|
+
attr.op = op;
|
|
138
|
+
attr.value = value;
|
|
139
|
+
}
|
|
140
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
141
|
+
if (*index >= text.size() || text[*index] != ']') return false;
|
|
142
|
+
(*index)++;
|
|
143
|
+
compound->attrs.push_back(attr);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
bool parseComplex(const std::string &text, ComplexSelector *selector) {
|
|
148
|
+
size_t index = 0;
|
|
149
|
+
while (index < text.size() && xt_is_css_space(text[index])) index++;
|
|
150
|
+
while (index < text.size()) {
|
|
151
|
+
CompoundSelector compound;
|
|
152
|
+
if (!parseCompound(text, &index, &compound)) return false;
|
|
153
|
+
selector->compounds.push_back(std::move(compound));
|
|
154
|
+
bool hadSpace = false;
|
|
155
|
+
while (index < text.size() && xt_is_css_space(text[index])) {
|
|
156
|
+
hadSpace = true;
|
|
157
|
+
index++;
|
|
158
|
+
}
|
|
159
|
+
if (index >= text.size() || text[index] == ',') break;
|
|
160
|
+
char c = text[index];
|
|
161
|
+
if (c == '>' || c == '+' || c == '~') {
|
|
162
|
+
index++;
|
|
163
|
+
while (index < text.size() && xt_is_css_space(text[index])) index++;
|
|
164
|
+
selector->combinators.push_back(c == '>' ? Combinator::Child
|
|
165
|
+
: c == '+' ? Combinator::Adjacent
|
|
166
|
+
: Combinator::Sibling);
|
|
167
|
+
} else if (hadSpace) {
|
|
168
|
+
selector->combinators.push_back(Combinator::Descendant);
|
|
169
|
+
} else {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (selector->combinators.size() + 1 != selector->compounds.size()) {
|
|
174
|
+
/* Ensure the invariant holds even if parsing stopped early. */
|
|
175
|
+
selector->combinators.resize(selector->compounds.size() > 0 ? selector->compounds.size() - 1 : 0);
|
|
176
|
+
}
|
|
177
|
+
selector->specificity = computeSpecificity(*selector);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static int computeCompoundSpecificity(const CompoundSelector &compound) {
|
|
182
|
+
/* `:not(x)` is approximated as a class; pseudo-elements would count as a
|
|
183
|
+
* type but are not modelled yet. Good enough for a UI stylesheet. */
|
|
184
|
+
return (int)(compound.classes.size() + compound.attrs.size() + compound.pseudoClasses.size());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static int computeSpecificity(const ComplexSelector &selector) {
|
|
188
|
+
int ids = 0;
|
|
189
|
+
int classes = 0;
|
|
190
|
+
int types = 0;
|
|
191
|
+
for (const CompoundSelector &compound : selector.compounds) {
|
|
192
|
+
if (!compound.id.empty()) ids++;
|
|
193
|
+
classes += computeCompoundSpecificity(compound);
|
|
194
|
+
if (!compound.tag.empty()) types++;
|
|
195
|
+
}
|
|
196
|
+
return (ids << 20) | (classes << 10) | types;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
} // namespace
|
|
201
|
+
|
|
202
|
+
std::vector<ComplexSelector> xt_css_parse_selector_list(const std::string &text) {
|
|
203
|
+
SelectorParser parser(text);
|
|
204
|
+
return parser.parseList();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
} // namespace xtgui
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS declaration and stylesheet parsing.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "css_internal.h"
|
|
6
|
+
|
|
7
|
+
namespace xtgui {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
// Strip C-style block comments before the sheet is tokenised.
|
|
12
|
+
std::string stripComments(const std::string &text) {
|
|
13
|
+
std::string out;
|
|
14
|
+
out.reserve(text.size());
|
|
15
|
+
for (size_t i = 0; i < text.size(); i++) {
|
|
16
|
+
if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '*') {
|
|
17
|
+
size_t end = text.find("*/", i + 2);
|
|
18
|
+
i = (end == std::string::npos) ? text.size() : end + 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
out.push_back(text[i]);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Find the first `needle` at nesting depth zero (parens/brackets/quotes). */
|
|
27
|
+
size_t findTopLevel(const std::string &text, char needle, size_t from) {
|
|
28
|
+
int depth = 0;
|
|
29
|
+
char quote = 0;
|
|
30
|
+
for (size_t i = from; i < text.size(); i++) {
|
|
31
|
+
char c = text[i];
|
|
32
|
+
if (quote != 0) {
|
|
33
|
+
if (c == quote) quote = 0;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (c == '"' || c == '\'') quote = c;
|
|
37
|
+
else if (c == '(' || c == '[') depth++;
|
|
38
|
+
else if (c == ')' || c == ']') depth--;
|
|
39
|
+
else if (c == needle && depth == 0) return i;
|
|
40
|
+
}
|
|
41
|
+
return std::string::npos;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
std::string stripImportant(std::string value, bool *important) {
|
|
45
|
+
size_t pos = value.rfind('!');
|
|
46
|
+
if (pos == std::string::npos) return value;
|
|
47
|
+
std::string flag = xt_css_lower(xt_css_trim(value.substr(pos + 1)));
|
|
48
|
+
if (flag == "important") {
|
|
49
|
+
*important = true;
|
|
50
|
+
return xt_css_trim(value.substr(0, pos));
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} // namespace
|
|
56
|
+
|
|
57
|
+
std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
|
|
58
|
+
std::vector<Declaration> declarations;
|
|
59
|
+
size_t start = 0;
|
|
60
|
+
while (start < text.size()) {
|
|
61
|
+
size_t end = findTopLevel(text, ';', start);
|
|
62
|
+
if (end == std::string::npos) end = text.size();
|
|
63
|
+
std::string part = text.substr(start, end - start);
|
|
64
|
+
start = end + 1;
|
|
65
|
+
size_t colon = findTopLevel(part, ':', 0);
|
|
66
|
+
if (colon == std::string::npos) continue;
|
|
67
|
+
Declaration declaration;
|
|
68
|
+
declaration.property = xt_css_lower(xt_css_trim(part.substr(0, colon)));
|
|
69
|
+
declaration.value = stripImportant(xt_css_trim(part.substr(colon + 1)), &declaration.important);
|
|
70
|
+
if (!declaration.property.empty() && !declaration.value.empty()) {
|
|
71
|
+
declarations.push_back(std::move(declaration));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return declarations;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
StyleSheet xt_css_parse_stylesheet(const std::string &text) {
|
|
78
|
+
StyleSheet sheet;
|
|
79
|
+
std::string clean = stripComments(text);
|
|
80
|
+
size_t index = 0;
|
|
81
|
+
int order = 0;
|
|
82
|
+
while (index < clean.size()) {
|
|
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
|
+
}
|
|
126
|
+
return sheet;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
} // namespace xtgui
|
|
@@ -24,6 +24,8 @@ void XtDocument::collectStyleText(const Node *node, std::string *out) const {
|
|
|
24
24
|
|
|
25
25
|
void XtDocument::load(const std::string &html, float viewportWidth, float viewportHeight) {
|
|
26
26
|
root_ = xt_html_parse(html);
|
|
27
|
+
detached_.clear();
|
|
28
|
+
dirty_ = false;
|
|
27
29
|
hover_ = nullptr;
|
|
28
30
|
focus_ = nullptr;
|
|
29
31
|
styles_.clear();
|
|
@@ -261,4 +263,92 @@ std::string XtDocument::toDebugString() const {
|
|
|
261
263
|
return root_ == nullptr ? std::string() : xt_dom_to_string(root_.get());
|
|
262
264
|
}
|
|
263
265
|
|
|
266
|
+
/* -- DOM mutation --------------------------------------------------------- */
|
|
267
|
+
|
|
268
|
+
Node *XtDocument::createElement(const std::string &tag) {
|
|
269
|
+
auto node = std::make_unique<Node>(NodeType::Element);
|
|
270
|
+
node->tag = tag;
|
|
271
|
+
Node *raw = node.get();
|
|
272
|
+
detached_[raw] = std::move(node);
|
|
273
|
+
return raw;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Node *XtDocument::createTextNode(const std::string &text) {
|
|
277
|
+
auto node = std::make_unique<Node>(NodeType::Text);
|
|
278
|
+
node->text = text;
|
|
279
|
+
Node *raw = node.get();
|
|
280
|
+
detached_[raw] = std::move(node);
|
|
281
|
+
return raw;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
namespace {
|
|
285
|
+
|
|
286
|
+
/** Take ownership of a node that is either attached or sitting in the pool. */
|
|
287
|
+
std::unique_ptr<Node> takeNode(
|
|
288
|
+
Node *node, std::unordered_map<const Node *, std::unique_ptr<Node>> &pool) {
|
|
289
|
+
if (node->parent != nullptr) return node->parent->detachChild(node);
|
|
290
|
+
auto it = pool.find(node);
|
|
291
|
+
if (it == pool.end()) return nullptr;
|
|
292
|
+
std::unique_ptr<Node> owned = std::move(it->second);
|
|
293
|
+
pool.erase(it);
|
|
294
|
+
return owned;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
} // namespace
|
|
298
|
+
|
|
299
|
+
Node *XtDocument::appendChild(Node *parent, Node *child) {
|
|
300
|
+
if (parent == nullptr || child == nullptr) return nullptr;
|
|
301
|
+
if (!parent->isElement() || child->isDocument() || child == root_.get()) return nullptr;
|
|
302
|
+
if (child->contains(parent)) return nullptr; /* reject cycles */
|
|
303
|
+
std::unique_ptr<Node> owned = takeNode(child, detached_);
|
|
304
|
+
if (owned == nullptr) return nullptr;
|
|
305
|
+
invalidate();
|
|
306
|
+
return parent->append(std::move(owned));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Node *XtDocument::insertBefore(Node *parent, Node *child, Node *reference) {
|
|
310
|
+
if (reference == nullptr) return appendChild(parent, child);
|
|
311
|
+
if (parent == nullptr || child == nullptr || child == reference) return child;
|
|
312
|
+
if (!parent->isElement() || child->isDocument() || child == root_.get()) return nullptr;
|
|
313
|
+
if (child->contains(parent)) return nullptr; /* reject cycles */
|
|
314
|
+
bool reference_is_child = false;
|
|
315
|
+
for (const std::unique_ptr<Node> &entry : parent->children) {
|
|
316
|
+
if (entry.get() == reference) {
|
|
317
|
+
reference_is_child = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (!reference_is_child) return nullptr;
|
|
322
|
+
std::unique_ptr<Node> owned = takeNode(child, detached_);
|
|
323
|
+
if (owned == nullptr) return nullptr;
|
|
324
|
+
invalidate();
|
|
325
|
+
return parent->insertChild(std::move(owned), reference);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
bool XtDocument::removeChild(Node *parent, Node *child) {
|
|
329
|
+
if (parent == nullptr || child == nullptr || child->parent != parent) return false;
|
|
330
|
+
std::unique_ptr<Node> owned = parent->detachChild(child);
|
|
331
|
+
if (owned == nullptr) return false;
|
|
332
|
+
detached_[child] = std::move(owned);
|
|
333
|
+
invalidate();
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Node *XtDocument::replaceChild(Node *parent, Node *newChild, Node *oldChild) {
|
|
338
|
+
if (parent == nullptr || oldChild == nullptr || oldChild->parent != parent) return nullptr;
|
|
339
|
+
if (newChild == oldChild) return newChild;
|
|
340
|
+
if (newChild != nullptr && !insertBefore(parent, newChild, oldChild)) return nullptr;
|
|
341
|
+
removeChild(parent, oldChild);
|
|
342
|
+
return newChild;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
void XtDocument::detach(Node *node) {
|
|
346
|
+
if (node == nullptr || node == root_.get()) return;
|
|
347
|
+
if (node->parent != nullptr) {
|
|
348
|
+
std::unique_ptr<Node> owned = node->parent->detachChild(node);
|
|
349
|
+
if (owned != nullptr) detached_[node] = std::move(owned);
|
|
350
|
+
}
|
|
351
|
+
invalidate();
|
|
352
|
+
}
|
|
353
|
+
|
|
264
354
|
} // namespace xtgui
|
|
@@ -63,6 +63,26 @@ class XtDocument {
|
|
|
63
63
|
const Node *querySelector(const std::string &selectorText) const;
|
|
64
64
|
/** Elements matching a CSS selector, in document order. */
|
|
65
65
|
std::vector<const Node *> querySelectorAll(const std::string &selectorText) const;
|
|
66
|
+
|
|
67
|
+
/* -- DOM mutation (element handles) ------------------------------------ */
|
|
68
|
+
/** Create a detached element/text node owned by the document. */
|
|
69
|
+
Node *createElement(const std::string &tag);
|
|
70
|
+
Node *createTextNode(const std::string &text);
|
|
71
|
+
/** Move `child` under `parent` (from its current parent or the detached pool). */
|
|
72
|
+
Node *appendChild(Node *parent, Node *child);
|
|
73
|
+
Node *insertBefore(Node *parent, Node *child, Node *reference);
|
|
74
|
+
bool removeChild(Node *parent, Node *child);
|
|
75
|
+
Node *replaceChild(Node *parent, Node *newChild, Node *oldChild);
|
|
76
|
+
/** Release a node from the document entirely (still owned, detached). */
|
|
77
|
+
void detach(Node *node);
|
|
78
|
+
|
|
79
|
+
/** Mark the document as needing a restyle/relayout before the next paint. */
|
|
80
|
+
void invalidate() { dirty_ = true; }
|
|
81
|
+
bool takeDirty() {
|
|
82
|
+
bool was = dirty_;
|
|
83
|
+
dirty_ = false;
|
|
84
|
+
return was;
|
|
85
|
+
}
|
|
66
86
|
/** Serialized DOM, for diagnostics. */
|
|
67
87
|
std::string toDebugString() const;
|
|
68
88
|
|
|
@@ -72,6 +92,9 @@ class XtDocument {
|
|
|
72
92
|
void updateDisplay();
|
|
73
93
|
|
|
74
94
|
std::unique_ptr<Node> root_;
|
|
95
|
+
/** Detached nodes (created or removed) stay alive here so handles remain valid. */
|
|
96
|
+
std::unordered_map<const Node *, std::unique_ptr<Node>> detached_;
|
|
97
|
+
bool dirty_ = false;
|
|
75
98
|
StyleSheet sheet_;
|
|
76
99
|
std::unordered_map<const Node *, XtStyle> styles_; // target (cascade)
|
|
77
100
|
std::unordered_map<const Node *, XtStyle> display_; // after transitions
|