FTXUI  5.0.0
C++ functional terminal UI.
flexbox_helper.cpp
Go to the documentation of this file.
1// Copyright 2021 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
5
6#include <algorithm> // for max, min
7#include <cstddef> // for size_t
8#include <ftxui/dom/flexbox_config.hpp> // for FlexboxConfig, FlexboxConfig::Direction, FlexboxConfig::AlignContent, FlexboxConfig::JustifyContent, FlexboxConfig::Wrap, FlexboxConfig::Direction::RowInversed, FlexboxConfig::AlignItems, FlexboxConfig::Direction::Row, FlexboxConfig::Direction::Column, FlexboxConfig::Direction::ColumnInversed, FlexboxConfig::Wrap::WrapInversed, FlexboxConfig::AlignContent::Stretch, FlexboxConfig::JustifyContent::Stretch, FlexboxConfig::Wrap::Wrap, FlexboxConfig::AlignContent::Center, FlexboxConfig::AlignContent::FlexEnd, FlexboxConfig::AlignContent::FlexStart, FlexboxConfig::AlignContent::SpaceAround, FlexboxConfig::AlignContent::SpaceBetween, FlexboxConfig::AlignContent::SpaceEvenly, FlexboxConfig::AlignItems::Center, FlexboxConfig::AlignItems::FlexEnd, FlexboxConfig::AlignItems::FlexStart, FlexboxConfig::AlignItems::Stretch, FlexboxConfig::JustifyContent::Center, FlexboxConfig::JustifyContent::FlexEnd, FlexboxConfig::JustifyContent::FlexStart, FlexboxConfig::JustifyContent::SpaceAround, FlexboxConfig::JustifyContent::SpaceBetween, FlexboxConfig::JustifyContent::SpaceEvenly, FlexboxConfig::Wrap::NoWrap
9#include <memory> // for allocator_traits<>::value_type
10#include <utility> // for swap, move
11
12#include "ftxui/dom/box_helper.hpp" // for Element, Compute
13
15
16namespace {
17void SymmetryXY(FlexboxConfig& c) {
18 std::swap(c.gap_x, c.gap_y);
19 switch (c.direction) {
22 break;
25 break;
28 break;
31 break;
32 }
33}
34
35void SymmetryX(FlexboxConfig& c) {
36 switch (c.direction) {
39 break;
42 break;
43 default:
44 break;
45 }
46}
47
48void SymmetryY(FlexboxConfig& c) {
49 switch (c.wrap) {
51 break;
54 break;
57 break;
58 }
59}
60
61void SymmetryXY(Global& g) {
62 SymmetryXY(g.config);
63 std::swap(g.size_x, g.size_y);
64 for (auto& b : g.blocks) {
65 std::swap(b.min_size_x, b.min_size_y);
66 std::swap(b.flex_grow_x, b.flex_grow_y);
67 std::swap(b.flex_shrink_x, b.flex_shrink_y);
68 std::swap(b.x, b.y);
69 std::swap(b.dim_x, b.dim_y);
70 }
71}
72
73void SymmetryX(Global& g) {
74 SymmetryX(g.config);
75 for (auto& b : g.blocks) {
76 b.x = g.size_x - b.x - b.dim_x;
77 }
78}
79
80void SymmetryY(Global& g) {
81 SymmetryY(g.config);
82 for (auto& b : g.blocks) {
83 b.y = g.size_y - b.y - b.dim_y;
84 }
85}
86
87struct Line {
88 std::vector<Block*> blocks;
89};
90
91void SetX(Global& global, std::vector<Line> lines) {
92 for (auto& line : lines) {
93 std::vector<box_helper::Element> elements;
94 elements.reserve(line.blocks.size());
95 for (auto* block : line.blocks) {
96 box_helper::Element element;
97 element.min_size = block->min_size_x;
98 element.flex_grow =
99 block->flex_grow_x != 0 || global.config.justify_content ==
101 ? 1
102 : 0;
103 element.flex_shrink = block->flex_shrink_x;
104 elements.push_back(element);
105 }
106
108 &elements,
109 global.size_x - global.config.gap_x * (int(line.blocks.size()) - 1));
110
111 int x = 0;
112 for (size_t i = 0; i < line.blocks.size(); ++i) {
113 line.blocks[i]->dim_x = elements[i].size;
114 line.blocks[i]->x = x;
115 x += elements[i].size;
116 x += global.config.gap_x;
117 }
118 }
119}
120
121// NOLINTNEXTLINE(readability-function-cognitive-complexity)
122void SetY(Global& g, std::vector<Line> lines) {
123 std::vector<box_helper::Element> elements;
124 elements.reserve(lines.size());
125 for (auto& line : lines) {
126 box_helper::Element element;
127 element.flex_shrink = line.blocks.front()->flex_shrink_y;
128 element.flex_grow = line.blocks.front()->flex_grow_y;
129 for (auto* block : line.blocks) {
130 element.min_size = std::max(element.min_size, block->min_size_y);
131 element.flex_shrink = std::min(element.flex_shrink, block->flex_shrink_y);
132 element.flex_grow = std::min(element.flex_grow, block->flex_grow_y);
133 }
134 elements.push_back(element);
135 }
136
137 // box_helper::Compute(&elements, g.size_y);
138 box_helper::Compute(&elements, 10000); // NOLINT
139
140 // [Align-content]
141 std::vector<int> ys(elements.size());
142 int y = 0;
143 for (size_t i = 0; i < elements.size(); ++i) {
144 ys[i] = y;
145 y += elements[i].size;
146 y += g.config.gap_y;
147 }
148 int remaining_space = std::max(0, g.size_y - y);
149 switch (g.config.align_content) {
151 break;
152 }
153
155 for (size_t i = 0; i < ys.size(); ++i) { // NOLINT
156 ys[i] += remaining_space;
157 }
158 break;
159 }
160
162 for (size_t i = 0; i < ys.size(); ++i) { // NOLINT
163 ys[i] += remaining_space / 2;
164 }
165 break;
166 }
167
169 for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
170 const int shifted = remaining_space * (i + 0) / (i + 1);
171 ys[i] += shifted;
172 const int consumed = remaining_space - shifted;
173 elements[i].size += consumed;
174 remaining_space -= consumed;
175 }
176 break;
177 }
178
180 for (int i = static_cast<int>(ys.size()) - 1; i >= 1; --i) { // NOLINT
181 ys[i] += remaining_space;
182 remaining_space = remaining_space * (i - 1) / i;
183 }
184 break;
185 }
186
188 for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
189 ys[i] += remaining_space * (2 * i + 1) / (2 * i + 2);
190 remaining_space = remaining_space * (2 * i) / (2 * i + 2);
191 }
192 break;
193 }
194
196 for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
197 ys[i] += remaining_space * (i + 1) / (i + 2);
198 remaining_space = remaining_space * (i + 1) / (i + 2);
199 }
200 break;
201 }
202 }
203
204 // [Align items]
205 for (size_t i = 0; i < lines.size(); ++i) {
206 auto& element = elements[i];
207 for (auto* block : lines[i].blocks) {
208 const bool stretch =
209 block->flex_grow_y != 0 ||
211 const int size =
212 stretch ? element.size : std::min(element.size, block->min_size_y);
213 switch (g.config.align_items) {
215 block->y = ys[i];
216 block->dim_y = size;
217 break;
218 }
219
221 block->y = ys[i] + (element.size - size) / 2;
222 block->dim_y = size;
223 break;
224 }
225
227 block->y = ys[i] + element.size - size;
228 block->dim_y = size;
229 break;
230 }
231
233 block->y = ys[i];
234 block->dim_y = element.size;
235 break;
236 }
237 }
238 }
239 }
240}
241
242void JustifyContent(Global& g, std::vector<Line> lines) {
243 for (auto& line : lines) {
244 Block* last = line.blocks.back();
245 int remaining_space = g.size_x - last->x - last->dim_x;
246 switch (g.config.justify_content) {
249 break;
250
252 for (auto* block : line.blocks) {
253 block->x += remaining_space;
254 }
255 break;
256 }
257
259 for (auto* block : line.blocks) {
260 block->x += remaining_space / 2;
261 }
262 break;
263 }
264
266 for (int i = (int)line.blocks.size() - 1; i >= 1; --i) {
267 line.blocks[i]->x += remaining_space;
268 remaining_space = remaining_space * (i - 1) / i;
269 }
270 break;
271 }
272
274 for (int i = (int)line.blocks.size() - 1; i >= 0; --i) {
275 line.blocks[i]->x += remaining_space * (2 * i + 1) / (2 * i + 2);
276 remaining_space = remaining_space * (2 * i) / (2 * i + 2);
277 }
278 break;
279 }
280
282 for (int i = (int)line.blocks.size() - 1; i >= 0; --i) {
283 line.blocks[i]->x += remaining_space * (i + 1) / (i + 2);
284 remaining_space = remaining_space * (i + 1) / (i + 2);
285 }
286 break;
287 }
288 }
289 }
290}
291
292void Compute1(Global& global);
293void Compute2(Global& global);
294void Compute3(Global& global);
295
296void Compute1(Global& global) {
298 SymmetryX(global);
299 Compute2(global);
300 SymmetryX(global);
301 return;
302 }
303 Compute2(global);
304}
305
306void Compute2(Global& global) {
308 SymmetryY(global);
309 Compute3(global);
310 SymmetryY(global);
311 return;
312 }
313 Compute3(global);
314}
315
316void Compute3(Global& global) {
317 // Step 1: Lay out every elements into rows:
318 std::vector<Line> lines;
319 {
320 Line line;
321 int x = 0;
322 line.blocks.reserve(global.blocks.size());
323 for (auto& block : global.blocks) {
324 // Does it fit the end of the row?
325 // No? Then we need to start a new one:
326 if (x + block.min_size_x > global.size_x) {
327 x = 0;
328 if (!line.blocks.empty()) {
329 lines.push_back(std::move(line));
330 }
331 line = Line();
332 }
333
334 block.line = lines.size();
335 block.line_position = line.blocks.size();
336 line.blocks.push_back(&block);
337 x += block.min_size_x + global.config.gap_x;
338 }
339 if (!line.blocks.empty()) {
340 lines.push_back(std::move(line));
341 }
342 }
343
344 // Step 2: Set positions on the X axis.
345 SetX(global, lines);
346 JustifyContent(global, lines); // Distribute remaining space.
347
348 // Step 3: Set positions on the Y axis.
349 SetY(global, lines);
350}
351
352} // namespace
353
354void Compute(Global& global) {
357 SymmetryXY(global);
358 Compute1(global);
359 SymmetryXY(global);
360 return;
361 }
362 Compute1(global);
363}
364
365} // namespace ftxui::flexbox_helper
void Compute(std::vector< Element > *elements, int target_size)
Definition: box_helper.cpp:64
void Compute(Global &global)
std::vector< Block > blocks
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Definition: size.cpp:90
AlignContent align_content
@ Center
items are centered along the cross axis.
@ FlexStart
items are placed at the start of the cross axis.
@ FlexEnd
items are placed at the end of the cross axis.
@ SpaceBetween
items are evenly distributed in the cross axis.
@ Stretch
items are stretched to fill the cross axis.
@ Column
Flex items are laid out in a column.
@ Row
Flex items are laid out in a row.
@ RowInversed
Flex items are laid out in a row, but in reverse order.
@ NoWrap
Flex items will all try to fit onto one line.
@ Wrap
Flex items will wrap onto multiple lines.
@ Center
items are centered along the cross axis.
@ FlexStart
items are placed at the start of the cross axis.
@ FlexEnd
items are placed at the end of the cross axis.
@ Stretch
items are stretched to fill the cross axis.
JustifyContent justify_content
@ Center
Items are centered along the line.
@ FlexStart
Items are aligned to the start of flexbox's direction.
@ FlexEnd
Items are aligned to the end of flexbox's direction.
@ SpaceBetween
Items are evenly distributed in the line; first item is on the start.
@ Stretch
Items are stretched to fill the line.