xbintsc 0.3.69 → 0.3.79
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 +59 -4
- package/README.zh-CN.md +52 -2
- package/dist/tests/e2e/gui-advanced.test.d.ts +6 -0
- package/dist/tests/e2e/gui-advanced.test.js +261 -0
- package/dist/tests/e2e/gui-advanced.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.d.ts +9 -0
- package/dist/tests/e2e/gui-canvas.test.js +233 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -0
- package/dist/tests/e2e/gui-example.test.d.ts +1 -1
- package/dist/tests/e2e/gui-example.test.js +2 -2
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +5 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +73 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +8 -0
- package/dist/tests/e2e/gui-media.test.js +119 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.js +40 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/ai/extensions.md +16 -3
- package/doc/ai/zh-CN/extensions.md +10 -3
- package/doc/gui/animation.md +46 -0
- package/doc/gui/api.md +92 -0
- package/doc/gui/build.md +91 -0
- package/doc/gui/canvas.md +98 -0
- package/doc/gui/dom.md +123 -0
- package/doc/gui/input.md +22 -0
- package/doc/gui/known-issues.md +66 -0
- package/doc/gui/layout.md +72 -0
- package/doc/gui/media.md +87 -0
- package/doc/gui/rendering.md +98 -0
- package/doc/gui/runtime.md +88 -0
- package/doc/gui-scripts.md +7 -3
- package/doc/gui.md +80 -612
- package/doc/implemented.md +1 -0
- package/doc/zh-CN/gui/animation.md +46 -0
- package/doc/zh-CN/gui/api.md +88 -0
- package/doc/zh-CN/gui/build.md +84 -0
- package/doc/zh-CN/gui/canvas.md +80 -0
- package/doc/zh-CN/gui/dom.md +114 -0
- package/doc/zh-CN/gui/input.md +21 -0
- package/doc/zh-CN/gui/known-issues.md +50 -0
- package/doc/zh-CN/gui/layout.md +60 -0
- package/doc/zh-CN/gui/media.md +71 -0
- package/doc/zh-CN/gui/rendering.md +81 -0
- package/doc/zh-CN/gui/runtime.md +76 -0
- package/doc/zh-CN/gui-scripts.md +6 -2
- package/doc/zh-CN/gui.md +64 -532
- package/doc/zh-CN/implemented.md +1 -0
- package/llms.txt +15 -2
- package/package.json +1 -1
- package/runtime/ext_gui/canvas.cpp +580 -0
- package/runtime/ext_gui/canvas.h +170 -0
- package/runtime/ext_gui/canvas_internal.h +59 -0
- package/runtime/ext_gui/canvas_raster.cpp +254 -0
- package/runtime/ext_gui/document.cpp +24 -1
- package/runtime/ext_gui/document.h +11 -0
- package/runtime/ext_gui/dom_api.cpp +3 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +548 -0
- package/runtime/ext_gui/dom_api_internal.h +73 -0
- package/runtime/ext_gui/dom_api_media.cpp +274 -0
- package/runtime/ext_gui/dom_api_proto.cpp +80 -0
- package/runtime/ext_gui/gui.cpp +6 -0
- package/runtime/ext_gui/gui_canvas.cpp +95 -0
- package/runtime/ext_gui/gui_engine.h +49 -0
- package/runtime/ext_gui/gui_events.cpp +21 -5
- package/runtime/ext_gui/gui_media.cpp +193 -0
- package/runtime/ext_gui/image.cpp +31 -12
- package/runtime/ext_gui/image.h +20 -7
- package/runtime/ext_gui/layout.cpp +52 -5
- package/runtime/ext_gui/layout.h +33 -1
- package/runtime/ext_gui/layout_flex.cpp +263 -99
- package/runtime/ext_gui/layout_flow.cpp +85 -9
- package/runtime/ext_gui/layout_flow_internal.h +84 -5
- package/runtime/ext_gui/layout_inline.cpp +54 -37
- package/runtime/ext_gui/media.cpp +514 -0
- package/runtime/ext_gui/media.h +104 -0
- package/runtime/ext_gui/nanosvg.h +3278 -0
- package/runtime/ext_gui/nanosvgrast.h +1472 -0
- package/runtime/ext_gui/paint.cpp +123 -38
- package/runtime/ext_gui/paint.h +36 -6
- package/runtime/ext_gui/renderer.cpp +113 -10
- package/runtime/ext_gui/renderer.h +49 -9
- package/runtime/ext_gui/renderer_image.cpp +203 -77
- package/runtime/ext_gui/renderer_internal.h +6 -8
- package/runtime/ext_gui/renderer_text.cpp +11 -26
- package/runtime/ext_gui/style.h +11 -0
- package/runtime/ext_gui/style_print.cpp +11 -0
- package/runtime/ext_gui/style_ua.cpp +7 -0
- package/runtime/ext_gui/style_values.cpp +40 -1
- package/runtime/ext_gui/svg.cpp +182 -0
- package/runtime/ext_gui/svg.h +46 -0
- package/runtime/ext_gui/text.cpp +156 -25
- package/runtime/ext_gui/text.h +11 -2
- package/runtime/ext_gui/window.cpp +4 -1
- package/scripts/build-gui.ts +148 -6
- package/scripts/check-file-length.ts +5 -1
- package/scripts/install-gui-deps.sh +28 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* xbintsc GUI engine — flex formatting context.
|
|
3
3
|
*
|
|
4
|
-
* Sizes flex items along the main axis (grow/shrink/basis),
|
|
5
|
-
*
|
|
4
|
+
* Sizes flex items along the main axis (grow/shrink/basis), wraps them into
|
|
5
|
+
* lines when `flex-wrap` allows it, resolves the cross axis (align-items /
|
|
6
|
+
* align-content) and places them (justify-content), reusing the block and
|
|
6
7
|
* inline helpers from `layout_flow.cpp` / `layout_inline.cpp`.
|
|
7
8
|
*/
|
|
8
9
|
|
|
@@ -11,151 +12,314 @@
|
|
|
11
12
|
namespace xtgui {
|
|
12
13
|
namespace layout_flow_detail {
|
|
13
14
|
|
|
15
|
+
/** Partition `basis` (outer main sizes) into lines, breaking before an item
|
|
16
|
+
* that no longer fits on the current line. A single line is produced when
|
|
17
|
+
* `wrap` is false. */
|
|
18
|
+
static std::vector<std::vector<size_t>> breakIntoLines(const std::vector<float> &basis,
|
|
19
|
+
float mainSize, float gap, bool wrap) {
|
|
20
|
+
std::vector<std::vector<size_t>> lines;
|
|
21
|
+
std::vector<size_t> current;
|
|
22
|
+
float used = 0;
|
|
23
|
+
for (size_t i = 0; i < basis.size(); i++) {
|
|
24
|
+
float add = basis[i] + (current.empty() ? 0.0f : gap);
|
|
25
|
+
if (wrap && !current.empty() && used + add > mainSize + 0.001f) {
|
|
26
|
+
lines.push_back(current);
|
|
27
|
+
current.clear();
|
|
28
|
+
used = 0;
|
|
29
|
+
add = basis[i];
|
|
30
|
+
}
|
|
31
|
+
current.push_back(i);
|
|
32
|
+
used += add;
|
|
33
|
+
}
|
|
34
|
+
if (!current.empty()) lines.push_back(current);
|
|
35
|
+
return lines;
|
|
36
|
+
}
|
|
37
|
+
|
|
14
38
|
float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float definiteHeight) const {
|
|
15
39
|
const XtStyle &containerStyle = *container->style;
|
|
16
40
|
bool isRow = containerStyle.flex_direction == FlexDirection::Row ||
|
|
17
41
|
containerStyle.flex_direction == FlexDirection::RowReverse;
|
|
18
|
-
bool reverse = containerStyle.flex_direction == FlexDirection::RowReverse ||
|
|
19
|
-
containerStyle.flex_direction == FlexDirection::ColumnReverse;
|
|
20
|
-
float gap = px(containerStyle.gap, contentWidth, containerStyle.font_size, 0.0f);
|
|
21
42
|
|
|
22
43
|
/* An absolutely positioned child is out of flow: it takes no part in the flex
|
|
23
44
|
* algorithm and is placed against the container's padding box instead. */
|
|
24
45
|
std::vector<LayoutBox *> items;
|
|
25
46
|
for (const std::unique_ptr<LayoutBox> &child : container->children) {
|
|
26
47
|
if (child->display == Display::None || isAbsoluteChild(child.get())) continue;
|
|
48
|
+
/* Whitespace-only text (source indentation) does not become a flex item. */
|
|
49
|
+
if (isCollapsibleText(child.get())) continue;
|
|
27
50
|
items.push_back(child.get());
|
|
28
51
|
}
|
|
29
52
|
if (items.empty()) return 0;
|
|
53
|
+
|
|
54
|
+
float mainGap =
|
|
55
|
+
px(isRow ? containerStyle.column_gap : containerStyle.row_gap, contentWidth,
|
|
56
|
+
containerStyle.font_size, 0.0f);
|
|
57
|
+
float crossGap =
|
|
58
|
+
px(isRow ? containerStyle.row_gap : containerStyle.column_gap, contentWidth,
|
|
59
|
+
containerStyle.font_size, 0.0f);
|
|
60
|
+
if (isRow)
|
|
61
|
+
return layoutFlexRow(container, items, contentWidth, definiteHeight, mainGap, crossGap);
|
|
62
|
+
return layoutFlexColumn(container, items, contentWidth, definiteHeight, mainGap, crossGap);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
float Layouter::layoutFlexRow(LayoutBox *container, const std::vector<LayoutBox *> &items,
|
|
66
|
+
float contentWidth, float definiteHeight, float mainGap,
|
|
67
|
+
float crossGap) const {
|
|
68
|
+
const XtStyle &containerStyle = *container->style;
|
|
69
|
+
bool reverse = containerStyle.flex_direction == FlexDirection::RowReverse;
|
|
70
|
+
bool wrap = containerStyle.flex_wrap != FlexWrap::NoWrap;
|
|
71
|
+
bool wrapReverse = containerStyle.flex_wrap == FlexWrap::WrapReverse;
|
|
30
72
|
size_t count = items.size();
|
|
73
|
+
float mainSize = contentWidth;
|
|
31
74
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
float
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
else basis = preferredContentWidth(items[i], mainSize);
|
|
43
|
-
} else {
|
|
44
|
-
basis = px(style.flex_basis, mainSize, style.font_size, 0.0f);
|
|
45
|
-
}
|
|
46
|
-
resolveEdges(items[i], mainSize);
|
|
47
|
-
float edges = horizontalEdges(items[i]);
|
|
48
|
-
outer[i] = basis + edges;
|
|
49
|
-
mainSizes[i] = outer[i];
|
|
50
|
-
sumBase += outer[i];
|
|
75
|
+
/* Main-axis basis: flex-basis, then width, then preferred content width. */
|
|
76
|
+
std::vector<float> basis(count, 0.0f);
|
|
77
|
+
for (size_t i = 0; i < count; i++) {
|
|
78
|
+
const XtStyle &style = *items[i]->style;
|
|
79
|
+
float base;
|
|
80
|
+
if (isAuto(style.flex_basis)) {
|
|
81
|
+
if (!isAuto(style.width)) base = px(style.width, mainSize, style.font_size, 0.0f);
|
|
82
|
+
else base = preferredContentWidth(items[i], mainSize);
|
|
83
|
+
} else {
|
|
84
|
+
base = px(style.flex_basis, mainSize, style.font_size, 0.0f);
|
|
51
85
|
}
|
|
52
|
-
|
|
86
|
+
resolveEdges(items[i], mainSize);
|
|
87
|
+
basis[i] = base + horizontalEdges(items[i]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
std::vector<std::vector<size_t>> lines = breakIntoLines(basis, mainSize, mainGap, wrap);
|
|
91
|
+
std::vector<float> mainSizes(count, 0.0f);
|
|
92
|
+
std::vector<float> cross(count, 0.0f);
|
|
93
|
+
std::vector<float> lineCross(lines.size(), 0.0f);
|
|
94
|
+
|
|
95
|
+
for (size_t li = 0; li < lines.size(); li++) {
|
|
96
|
+
const std::vector<size_t> &line = lines[li];
|
|
97
|
+
float sumBase = 0;
|
|
98
|
+
for (size_t i : line) sumBase += basis[i];
|
|
99
|
+
for (size_t i : line) mainSizes[i] = basis[i];
|
|
100
|
+
float free = mainSize - sumBase - mainGap * (float)(line.size() - 1);
|
|
53
101
|
if (free > 0) {
|
|
54
102
|
float totalGrow = 0;
|
|
55
|
-
for (
|
|
56
|
-
if (totalGrow > 0)
|
|
57
|
-
for (size_t i
|
|
58
|
-
mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
|
|
59
|
-
}
|
|
103
|
+
for (size_t i : line) totalGrow += items[i]->style->flex_grow;
|
|
104
|
+
if (totalGrow > 0)
|
|
105
|
+
for (size_t i : line) mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
|
|
60
106
|
} else if (free < 0) {
|
|
61
107
|
float totalShrink = 0;
|
|
62
|
-
for (size_t i
|
|
63
|
-
if (totalShrink > 0)
|
|
64
|
-
for (size_t i
|
|
65
|
-
mainSizes[i] += free * (items[i]->style->flex_shrink *
|
|
66
|
-
mainSizes[i]
|
|
108
|
+
for (size_t i : line) totalShrink += items[i]->style->flex_shrink * basis[i];
|
|
109
|
+
if (totalShrink > 0)
|
|
110
|
+
for (size_t i : line) {
|
|
111
|
+
mainSizes[i] += free * (items[i]->style->flex_shrink * basis[i]) / totalShrink;
|
|
112
|
+
if (mainSizes[i] < 0) mainSizes[i] = 0;
|
|
67
113
|
}
|
|
68
|
-
}
|
|
69
114
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
float crossSize = definiteHeight >= 0 ? definiteHeight : 0;
|
|
73
|
-
std::vector<float> cross(count, 0.0f);
|
|
74
|
-
for (size_t i = 0; i < count; i++) {
|
|
115
|
+
/* Measure each item's cross size by laying it out at its used main size. */
|
|
116
|
+
for (size_t i : line) {
|
|
75
117
|
float contentW = mainSizes[i] - horizontalEdges(items[i]);
|
|
76
118
|
if (contentW < 0) contentW = 0;
|
|
77
119
|
setContainingBlock(items[i], containingBlockAncestor(items[i]));
|
|
78
|
-
layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
|
|
79
|
-
|
|
120
|
+
layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight,
|
|
121
|
+
contentW);
|
|
80
122
|
cross[i] = verticalEdges(items[i]) + items[i]->height;
|
|
81
|
-
if (
|
|
123
|
+
if (cross[i] > lineCross[li]) lineCross[li] = cross[i];
|
|
82
124
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Cross-axis sizing and align-content. A single line takes the container's
|
|
128
|
+
* definite cross size (or its content size); multiple lines share any extra
|
|
129
|
+
* space according to align-content. */
|
|
130
|
+
float crossContainer;
|
|
131
|
+
float lineOffset = 0.0f;
|
|
132
|
+
float lineStep = crossGap;
|
|
133
|
+
if (lines.size() == 1) {
|
|
134
|
+
crossContainer = definiteHeight >= 0 ? definiteHeight : lineCross[0];
|
|
135
|
+
lineCross[0] = crossContainer;
|
|
136
|
+
} else {
|
|
137
|
+
float total = crossGap * (float)(lines.size() - 1);
|
|
138
|
+
for (float value : lineCross) total += value;
|
|
139
|
+
crossContainer = definiteHeight >= 0 ? definiteHeight : total;
|
|
140
|
+
float remaining = crossContainer - total;
|
|
141
|
+
if (remaining < 0) remaining = 0;
|
|
142
|
+
switch (containerStyle.align_content) {
|
|
143
|
+
case AlignContent::Stretch:
|
|
144
|
+
for (float &value : lineCross) value += remaining / (float)lines.size();
|
|
145
|
+
break;
|
|
146
|
+
case AlignContent::FlexEnd:
|
|
147
|
+
lineOffset = remaining;
|
|
148
|
+
break;
|
|
149
|
+
case AlignContent::Center:
|
|
150
|
+
lineOffset = remaining / 2.0f;
|
|
151
|
+
break;
|
|
152
|
+
case AlignContent::SpaceBetween:
|
|
153
|
+
if (lines.size() > 1) lineStep += remaining / (float)(lines.size() - 1);
|
|
154
|
+
break;
|
|
155
|
+
case AlignContent::SpaceAround:
|
|
156
|
+
lineStep += remaining / (float)lines.size();
|
|
157
|
+
lineOffset = remaining / (2.0f * (float)lines.size());
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
break;
|
|
93
161
|
}
|
|
162
|
+
}
|
|
94
163
|
|
|
95
|
-
|
|
96
|
-
|
|
164
|
+
for (size_t li = 0; li < lines.size(); li++) {
|
|
165
|
+
const std::vector<size_t> &line = lines[li];
|
|
166
|
+
float lineStart = wrapReverse ? crossContainer - lineOffset - lineCross[li] : lineOffset;
|
|
167
|
+
float sumMain = 0;
|
|
168
|
+
for (size_t i : line) sumMain += mainSizes[i];
|
|
169
|
+
float freeSpace = mainSize - (sumMain + mainGap * (float)(line.size() - 1));
|
|
97
170
|
float offset = 0;
|
|
98
|
-
float between =
|
|
99
|
-
applyJustify(containerStyle.justify_content, freeSpace,
|
|
100
|
-
|
|
171
|
+
float between = mainGap;
|
|
172
|
+
applyJustify(containerStyle.justify_content, freeSpace, line.size(), &offset, &between);
|
|
101
173
|
float cursor = container->content_x + offset;
|
|
102
|
-
for (size_t k = 0; k <
|
|
103
|
-
size_t i = reverse ? (
|
|
104
|
-
|
|
174
|
+
for (size_t k = 0; k < line.size(); k++) {
|
|
175
|
+
size_t i = reverse ? line[line.size() - 1 - k] : line[k];
|
|
176
|
+
bool stretch = containerStyle.align_items == AlignItems::Stretch &&
|
|
177
|
+
isAuto(items[i]->style->height) &&
|
|
178
|
+
(definiteHeight >= 0 || lines.size() > 1);
|
|
179
|
+
if (stretch) {
|
|
180
|
+
float contentH = lineCross[li] - verticalEdges(items[i]);
|
|
181
|
+
if (contentH < 0) contentH = 0;
|
|
182
|
+
float contentW = mainSizes[i] - horizontalEdges(items[i]);
|
|
183
|
+
layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
|
|
184
|
+
definiteHeight, contentW, contentH);
|
|
185
|
+
cross[i] = lineCross[li];
|
|
186
|
+
}
|
|
187
|
+
float crossOffset = crossOffsetFor(containerStyle.align_items, lineCross[li], cross[i]);
|
|
105
188
|
float dx = cursor + items[i]->margin_left - items[i]->x;
|
|
106
|
-
float dy =
|
|
189
|
+
float dy =
|
|
190
|
+
container->content_y + lineStart + crossOffset + items[i]->margin_top - items[i]->y;
|
|
107
191
|
translate(items[i], dx, dy);
|
|
108
192
|
cursor += mainSizes[i] + between;
|
|
109
193
|
}
|
|
110
|
-
|
|
194
|
+
lineOffset += lineCross[li] + lineStep;
|
|
111
195
|
}
|
|
196
|
+
return crossContainer;
|
|
197
|
+
}
|
|
112
198
|
|
|
113
|
-
|
|
114
|
-
|
|
199
|
+
float Layouter::layoutFlexColumn(LayoutBox *container, const std::vector<LayoutBox *> &items,
|
|
200
|
+
float contentWidth, float definiteHeight, float mainGap,
|
|
201
|
+
float crossGap) const {
|
|
202
|
+
const XtStyle &containerStyle = *container->style;
|
|
203
|
+
bool reverse = containerStyle.flex_direction == FlexDirection::ColumnReverse;
|
|
204
|
+
bool wrap = containerStyle.flex_wrap != FlexWrap::NoWrap && definiteHeight >= 0;
|
|
205
|
+
bool wrapReverse = containerStyle.flex_wrap == FlexWrap::WrapReverse;
|
|
206
|
+
size_t count = items.size();
|
|
207
|
+
|
|
208
|
+
/* Column items have to be laid out to learn their main (height) size. */
|
|
115
209
|
std::vector<float> outer(count, 0.0f);
|
|
116
|
-
float sumBase = 0;
|
|
117
210
|
for (size_t i = 0; i < count; i++) {
|
|
118
211
|
setContainingBlock(items[i], containingBlockAncestor(items[i]));
|
|
119
212
|
layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight);
|
|
120
213
|
outer[i] = verticalEdges(items[i]) + items[i]->height;
|
|
121
|
-
sumBase += outer[i];
|
|
122
214
|
}
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
float
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
215
|
+
|
|
216
|
+
if (!wrap) {
|
|
217
|
+
float mainSize = definiteHeight >= 0 ? definiteHeight : -1;
|
|
218
|
+
std::vector<float> mainSizes = outer;
|
|
219
|
+
if (mainSize >= 0) {
|
|
220
|
+
float free = mainSize - sumOf(outer) - mainGap * (float)(count - 1);
|
|
221
|
+
if (free > 0) {
|
|
222
|
+
float totalGrow = 0;
|
|
223
|
+
for (LayoutBox *item : items) totalGrow += item->style->flex_grow;
|
|
224
|
+
if (totalGrow > 0) {
|
|
225
|
+
for (size_t i = 0; i < count; i++) {
|
|
226
|
+
mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
|
|
227
|
+
float contentH = mainSizes[i] - verticalEdges(items[i]);
|
|
228
|
+
if (contentH < 0) contentH = 0;
|
|
229
|
+
layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
|
|
230
|
+
definiteHeight, -1, contentH);
|
|
231
|
+
}
|
|
136
232
|
}
|
|
137
233
|
}
|
|
234
|
+
} else {
|
|
235
|
+
mainSize = sumOf(outer) + mainGap * (float)(count - 1);
|
|
138
236
|
}
|
|
139
|
-
|
|
140
|
-
|
|
237
|
+
float freeSpace = mainSize - (sumOf(mainSizes) + mainGap * (float)(count - 1));
|
|
238
|
+
float offset = 0;
|
|
239
|
+
float between = mainGap;
|
|
240
|
+
applyJustify(containerStyle.justify_content, freeSpace, count, &offset, &between);
|
|
241
|
+
float cursor = container->content_y + offset;
|
|
242
|
+
for (size_t k = 0; k < count; k++) {
|
|
243
|
+
size_t i = reverse ? (count - 1 - k) : k;
|
|
244
|
+
float dx = container->content_x + items[i]->margin_left - items[i]->x;
|
|
245
|
+
float dy = cursor + items[i]->margin_top - items[i]->y;
|
|
246
|
+
translate(items[i], dx, dy);
|
|
247
|
+
cursor += mainSizes[i] + between;
|
|
248
|
+
}
|
|
249
|
+
return mainSize;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Wrapped columns: lines run top-to-bottom and are placed side by side. */
|
|
253
|
+
std::vector<std::vector<size_t>> lines = breakIntoLines(outer, definiteHeight, mainGap, true);
|
|
254
|
+
std::vector<float> mainSizes(count, 0.0f);
|
|
255
|
+
std::vector<float> lineCross(lines.size(), 0.0f);
|
|
256
|
+
for (size_t li = 0; li < lines.size(); li++) {
|
|
257
|
+
const std::vector<size_t> &line = lines[li];
|
|
258
|
+
float sumBase = 0;
|
|
259
|
+
for (size_t i : line) sumBase += outer[i];
|
|
260
|
+
for (size_t i : line) mainSizes[i] = outer[i];
|
|
261
|
+
float free = definiteHeight - sumBase - mainGap * (float)(line.size() - 1);
|
|
262
|
+
if (free > 0) {
|
|
263
|
+
float totalGrow = 0;
|
|
264
|
+
for (size_t i : line) totalGrow += items[i]->style->flex_grow;
|
|
265
|
+
if (totalGrow > 0)
|
|
266
|
+
for (size_t i : line) mainSizes[i] += free * items[i]->style->flex_grow / totalGrow;
|
|
267
|
+
}
|
|
268
|
+
for (size_t i : line) {
|
|
269
|
+
float contentH = mainSizes[i] - verticalEdges(items[i]);
|
|
270
|
+
if (contentH < 0) contentH = 0;
|
|
271
|
+
layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
|
|
272
|
+
definiteHeight, -1, contentH);
|
|
273
|
+
float crossSize = horizontalEdges(items[i]) + items[i]->width;
|
|
274
|
+
if (crossSize > lineCross[li]) lineCross[li] = crossSize;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
float total = crossGap * (float)(lines.size() - 1);
|
|
278
|
+
for (float value : lineCross) total += value;
|
|
279
|
+
float lineOffset = 0.0f;
|
|
280
|
+
float lineStep = crossGap;
|
|
281
|
+
float remaining = contentWidth - total;
|
|
282
|
+
if (remaining < 0) remaining = 0;
|
|
283
|
+
switch (containerStyle.align_content) {
|
|
284
|
+
case AlignContent::Stretch:
|
|
285
|
+
for (float &value : lineCross) value += remaining / (float)lines.size();
|
|
286
|
+
break;
|
|
287
|
+
case AlignContent::FlexEnd:
|
|
288
|
+
lineOffset = remaining;
|
|
289
|
+
break;
|
|
290
|
+
case AlignContent::Center:
|
|
291
|
+
lineOffset = remaining / 2.0f;
|
|
292
|
+
break;
|
|
293
|
+
case AlignContent::SpaceBetween:
|
|
294
|
+
if (lines.size() > 1) lineStep += remaining / (float)(lines.size() - 1);
|
|
295
|
+
break;
|
|
296
|
+
case AlignContent::SpaceAround:
|
|
297
|
+
lineStep += remaining / (float)lines.size();
|
|
298
|
+
lineOffset = remaining / (2.0f * (float)lines.size());
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
break;
|
|
141
302
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
303
|
+
for (size_t li = 0; li < lines.size(); li++) {
|
|
304
|
+
const std::vector<size_t> &line = lines[li];
|
|
305
|
+
float lineStart = wrapReverse ? contentWidth - lineOffset - lineCross[li] : lineOffset;
|
|
306
|
+
float sumMain = 0;
|
|
307
|
+
for (size_t i : line) sumMain += mainSizes[i];
|
|
308
|
+
float freeSpace = definiteHeight - (sumMain + mainGap * (float)(line.size() - 1));
|
|
309
|
+
float offset = 0;
|
|
310
|
+
float between = mainGap;
|
|
311
|
+
applyJustify(containerStyle.justify_content, freeSpace, line.size(), &offset, &between);
|
|
312
|
+
float cursor = container->content_y + offset;
|
|
313
|
+
for (size_t k = 0; k < line.size(); k++) {
|
|
314
|
+
size_t i = reverse ? line[line.size() - 1 - k] : line[k];
|
|
315
|
+
float dx = container->content_x + lineStart + items[i]->margin_left - items[i]->x;
|
|
316
|
+
float dy = cursor + items[i]->margin_top - items[i]->y;
|
|
317
|
+
translate(items[i], dx, dy);
|
|
318
|
+
cursor += mainSizes[i] + between;
|
|
319
|
+
}
|
|
320
|
+
lineOffset += lineCross[li] + lineStep;
|
|
157
321
|
}
|
|
158
|
-
return
|
|
322
|
+
return contentWidth;
|
|
159
323
|
}
|
|
160
324
|
|
|
161
325
|
float Layouter::sumOf(const std::vector<float> &values) {
|
|
@@ -44,7 +44,7 @@ float Layouter::verticalEdges(const LayoutBox *box) const {
|
|
|
44
44
|
float Layouter::preferredContentWidth(const LayoutBox *box, float available) const {
|
|
45
45
|
const XtStyle &style = *box->style;
|
|
46
46
|
if (!isAuto(style.width)) return px(style.width, available, style.font_size, 0.0f);
|
|
47
|
-
if (box->is_image && box->intrinsic_width > 0) return box->intrinsic_width;
|
|
47
|
+
if ((box->is_image || box->is_canvas) && box->intrinsic_width > 0) return box->intrinsic_width;
|
|
48
48
|
float best = 0;
|
|
49
49
|
for (const std::unique_ptr<LayoutBox> &child : box->children) {
|
|
50
50
|
if (child->display == Display::None) continue;
|
|
@@ -201,7 +201,7 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
|
|
|
201
201
|
if (contentWidth < 0) contentWidth = 0;
|
|
202
202
|
|
|
203
203
|
box->x = x + box->margin_left;
|
|
204
|
-
box->y = y
|
|
204
|
+
box->y = y;
|
|
205
205
|
box->content_x = box->x + box->border_left + box->padding_left;
|
|
206
206
|
box->content_y = box->y + box->border_top + box->padding_top;
|
|
207
207
|
box->content_width = contentWidth;
|
|
@@ -225,8 +225,8 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
|
|
|
225
225
|
if (contentHeight < 0) contentHeight = 0;
|
|
226
226
|
}
|
|
227
227
|
/* Replaced elements keep their aspect ratio when only one axis is set. */
|
|
228
|
-
if (box->is_image && forcedContentHeight < 0 && isAuto(style.height) &&
|
|
229
|
-
box->intrinsic_height > 0) {
|
|
228
|
+
if ((box->is_image || box->is_canvas) && forcedContentHeight < 0 && isAuto(style.height) &&
|
|
229
|
+
box->intrinsic_width > 0 && box->intrinsic_height > 0) {
|
|
230
230
|
contentHeight = contentWidth * box->intrinsic_height / box->intrinsic_width;
|
|
231
231
|
}
|
|
232
232
|
box->content_height = contentHeight;
|
|
@@ -239,9 +239,10 @@ float Layouter::layoutBlock(LayoutBox *box, float x, float y, float availableWid
|
|
|
239
239
|
publishContainingBlock(box);
|
|
240
240
|
layoutAbsoluteChildren(box, contentWidth, availableHeight);
|
|
241
241
|
applyPosition(box);
|
|
242
|
-
/*
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
/* The caller owns the vertical margins: it collapses them and advances the
|
|
243
|
+
* flow itself, so the border-box height is what comes back. */
|
|
244
|
+
(void)inFlow;
|
|
245
|
+
return box->height;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
bool Layouter::isAbsoluteChild(const LayoutBox *box) {
|
|
@@ -266,11 +267,17 @@ float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float avail
|
|
|
266
267
|
|
|
267
268
|
float y = block->content_y;
|
|
268
269
|
float startY = y;
|
|
270
|
+
/* The bottom margin of the last in-flow block, waiting to collapse with the
|
|
271
|
+
* next block's top margin. */
|
|
272
|
+
float pending = 0.0f;
|
|
273
|
+
bool hasBlock = false;
|
|
269
274
|
std::vector<LayoutBox *> inlineRun;
|
|
275
|
+
FloatContext floats;
|
|
270
276
|
auto flush = [&]() {
|
|
271
277
|
if (inlineRun.empty()) return;
|
|
272
278
|
/* Source whitespace between block boxes collapses away (HTML's model), so a
|
|
273
|
-
* run that carries no visible content produces no line at all
|
|
279
|
+
* run that carries no visible content produces no line at all — and,
|
|
280
|
+
* crucially, does not break margin adjacency with the next block. */
|
|
274
281
|
bool visible = false;
|
|
275
282
|
for (LayoutBox *box : inlineRun) {
|
|
276
283
|
if (!isCollapsibleText(box)) {
|
|
@@ -278,8 +285,20 @@ float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float avail
|
|
|
278
285
|
break;
|
|
279
286
|
}
|
|
280
287
|
}
|
|
281
|
-
if (visible)
|
|
288
|
+
if (!visible) {
|
|
289
|
+
/* Source whitespace only: no line, and it does not break margin
|
|
290
|
+
* adjacency with the next block. */
|
|
291
|
+
inlineRun.clear();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
/* Inline content breaks adjacency: apply the pending margin here instead of
|
|
295
|
+
* letting it collapse into the next block. */
|
|
296
|
+
y += pending;
|
|
297
|
+
pending = 0.0f;
|
|
298
|
+
floats.prune(y);
|
|
299
|
+
y += layoutInlineRun(block, inlineRun, contentWidth, y, &floats);
|
|
282
300
|
inlineRun.clear();
|
|
301
|
+
hasBlock = true;
|
|
283
302
|
};
|
|
284
303
|
for (const std::unique_ptr<LayoutBox> &child : block->children) {
|
|
285
304
|
Display display = child->display;
|
|
@@ -289,16 +308,73 @@ float Layouter::layoutChildren(LayoutBox *block, float contentWidth, float avail
|
|
|
289
308
|
* its own size. */
|
|
290
309
|
continue;
|
|
291
310
|
}
|
|
311
|
+
const XtStyle *childStyle = child->style;
|
|
312
|
+
if (childStyle != nullptr && childStyle->float_side != FloatSide::None) {
|
|
313
|
+
/* A float interrupts the inline run (content before it is placed first,
|
|
314
|
+
* then the float affects the lines that follow), and takes no flow space
|
|
315
|
+
* of its own. */
|
|
316
|
+
flush();
|
|
317
|
+
resolveEdges(child.get(), contentWidth);
|
|
318
|
+
float edges = horizontalEdges(child.get());
|
|
319
|
+
float available = contentWidth - edges;
|
|
320
|
+
if (available < 0) available = 0;
|
|
321
|
+
float outerWidth;
|
|
322
|
+
if (!isAuto(childStyle->width)) {
|
|
323
|
+
outerWidth = px(childStyle->width, contentWidth, childStyle->font_size, 0.0f) + edges;
|
|
324
|
+
} else {
|
|
325
|
+
outerWidth = preferredContentWidth(child.get(), contentWidth);
|
|
326
|
+
if (outerWidth > available) outerWidth = available;
|
|
327
|
+
outerWidth += edges;
|
|
328
|
+
}
|
|
329
|
+
float contentW = outerWidth - edges;
|
|
330
|
+
if (contentW < 0) contentW = 0;
|
|
331
|
+
float floatX = childStyle->float_side == FloatSide::Left
|
|
332
|
+
? block->content_x
|
|
333
|
+
: block->content_x + contentWidth - outerWidth;
|
|
334
|
+
setContainingBlock(child.get(), containingBlockAncestor(block));
|
|
335
|
+
float height =
|
|
336
|
+
layoutBlock(child.get(), floatX, y, contentWidth, availableHeight, contentW);
|
|
337
|
+
ActiveFloat entry;
|
|
338
|
+
entry.left = childStyle->float_side == FloatSide::Left;
|
|
339
|
+
entry.x = child->x;
|
|
340
|
+
entry.width = child->width + child->margin_left + child->margin_right;
|
|
341
|
+
entry.top = y;
|
|
342
|
+
entry.height = height + child->margin_top + child->margin_bottom;
|
|
343
|
+
floats.floats.push_back(entry);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
292
346
|
if (display == Display::Block || display == Display::ListItem || display == Display::Flex ||
|
|
293
347
|
child->style == nullptr) {
|
|
294
348
|
flush();
|
|
349
|
+
resolveEdges(child.get(), contentWidth);
|
|
350
|
+
/* Adjacent vertical margins collapse: the gap is the collapsed value of
|
|
351
|
+
* the waiting bottom margin and this child's top margin. */
|
|
352
|
+
float top = child->margin_top;
|
|
353
|
+
float gap = hasBlock ? collapseMargins(pending, top) : top;
|
|
354
|
+
float desired = y + gap;
|
|
355
|
+
if (childStyle != nullptr && childStyle->clear != Clear::None) {
|
|
356
|
+
bool clearLeft = childStyle->clear == Clear::Left || childStyle->clear == Clear::Both;
|
|
357
|
+
bool clearRight = childStyle->clear == Clear::Right || childStyle->clear == Clear::Both;
|
|
358
|
+
float clearY = floats.clearY(clearLeft, clearRight, y) + top;
|
|
359
|
+
if (desired < clearY) desired = clearY;
|
|
360
|
+
}
|
|
361
|
+
y = desired;
|
|
295
362
|
setContainingBlock(child.get(), containingBlockAncestor(block));
|
|
296
363
|
y += layoutBlock(child.get(), block->content_x, y, contentWidth, availableHeight);
|
|
364
|
+
pending = child->margin_bottom;
|
|
365
|
+
hasBlock = true;
|
|
297
366
|
} else {
|
|
298
367
|
inlineRun.push_back(child.get());
|
|
299
368
|
}
|
|
300
369
|
}
|
|
301
370
|
flush();
|
|
371
|
+
/* The last bottom margin is part of the content box: it escapes only if the
|
|
372
|
+
* parent has no bottom padding/border (handled by the parent's caller). */
|
|
373
|
+
y += pending;
|
|
374
|
+
/* Grow to contain the floats: a block with only floated children would
|
|
375
|
+
* otherwise collapse to zero height. */
|
|
376
|
+
float floatBottom = floats.maxBottom();
|
|
377
|
+
if (floatBottom > y) y = floatBottom;
|
|
302
378
|
return y - startY;
|
|
303
379
|
}
|
|
304
380
|
|