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 <utility> // for swap, move
10 #include <vector>
11 
12 #include "ftxui/dom/box_helper.hpp" // for Element, Compute
13 
15 
16 namespace {
17 void 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 
35 void SymmetryX(FlexboxConfig& c) {
36  switch (c.direction) {
39  break;
42  break;
43  default:
44  break;
45  }
46 }
47 
48 void SymmetryY(FlexboxConfig& c) {
49  switch (c.wrap) {
51  break;
54  break;
57  break;
58  }
59 }
60 
61 void 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 
73 void 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 
80 void 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 
87 struct Line {
88  std::vector<Block*> blocks;
89 };
90 
91 void 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)
122 void 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 
242 void 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 
292 void Compute1(Global& global);
293 void Compute2(Global& global);
294 void Compute3(Global& global);
295 
296 void Compute1(Global& global) {
298  SymmetryX(global);
299  Compute2(global);
300  SymmetryX(global);
301  return;
302  }
303  Compute2(global);
304 }
305 
306 void Compute2(Global& global) {
308  SymmetryY(global);
309  Compute3(global);
310  SymmetryY(global);
311  return;
312  }
313  Compute3(global);
314 }
315 
316 void 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 = static_cast<int>(lines.size());
335  block.line_position = static_cast<int>(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 
354 void 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:65
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:89
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.