2023-08-19 19:56:36 +08:00
|
|
|
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
|
|
|
// Use of this source code is governed by the MIT license that can be found in
|
|
|
|
// the LICENSE file.
|
2021-12-12 00:58:25 +08:00
|
|
|
#include "ftxui/dom/flexbox_helper.hpp"
|
|
|
|
|
2022-06-12 23:08:22 +08:00
|
|
|
#include <algorithm> // for max, min
|
|
|
|
#include <cstddef> // for size_t
|
|
|
|
#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
|
|
|
|
#include <utility> // for swap, move
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
#include "ftxui/dom/box_helper.hpp" // for Element, Compute
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
namespace ftxui::flexbox_helper {
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
void SymmetryXY(FlexboxConfig& c) {
|
|
|
|
std::swap(c.gap_x, c.gap_y);
|
|
|
|
switch (c.direction) {
|
|
|
|
case FlexboxConfig::Direction::Row:
|
|
|
|
c.direction = FlexboxConfig::Direction::Column;
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Direction::RowInversed:
|
|
|
|
c.direction = FlexboxConfig::Direction::ColumnInversed;
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Direction::Column:
|
|
|
|
c.direction = FlexboxConfig::Direction::Row;
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Direction::ColumnInversed:
|
|
|
|
c.direction = FlexboxConfig::Direction::RowInversed;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SymmetryX(FlexboxConfig& c) {
|
|
|
|
switch (c.direction) {
|
|
|
|
case FlexboxConfig::Direction::Row:
|
|
|
|
c.direction = FlexboxConfig::Direction::RowInversed;
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Direction::RowInversed:
|
|
|
|
c.direction = FlexboxConfig::Direction::Row;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SymmetryY(FlexboxConfig& c) {
|
|
|
|
switch (c.wrap) {
|
|
|
|
case FlexboxConfig::Wrap::NoWrap:
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Wrap::Wrap:
|
|
|
|
c.wrap = FlexboxConfig::Wrap::WrapInversed;
|
|
|
|
break;
|
|
|
|
case FlexboxConfig::Wrap::WrapInversed:
|
|
|
|
c.wrap = FlexboxConfig::Wrap::Wrap;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SymmetryXY(Global& g) {
|
|
|
|
SymmetryXY(g.config);
|
|
|
|
std::swap(g.size_x, g.size_y);
|
|
|
|
for (auto& b : g.blocks) {
|
|
|
|
std::swap(b.min_size_x, b.min_size_y);
|
|
|
|
std::swap(b.flex_grow_x, b.flex_grow_y);
|
|
|
|
std::swap(b.flex_shrink_x, b.flex_shrink_y);
|
|
|
|
std::swap(b.x, b.y);
|
|
|
|
std::swap(b.dim_x, b.dim_y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SymmetryX(Global& g) {
|
|
|
|
SymmetryX(g.config);
|
|
|
|
for (auto& b : g.blocks) {
|
|
|
|
b.x = g.size_x - b.x - b.dim_x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SymmetryY(Global& g) {
|
|
|
|
SymmetryY(g.config);
|
|
|
|
for (auto& b : g.blocks) {
|
|
|
|
b.y = g.size_y - b.y - b.dim_y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Line {
|
|
|
|
std::vector<Block*> blocks;
|
|
|
|
};
|
|
|
|
|
|
|
|
void SetX(Global& global, std::vector<Line> lines) {
|
|
|
|
for (auto& line : lines) {
|
|
|
|
std::vector<box_helper::Element> elements;
|
2023-06-01 01:24:08 +08:00
|
|
|
elements.reserve(line.blocks.size());
|
2021-12-12 00:58:25 +08:00
|
|
|
for (auto* block : line.blocks) {
|
|
|
|
box_helper::Element element;
|
|
|
|
element.min_size = block->min_size_x;
|
|
|
|
element.flex_grow =
|
2022-03-31 08:17:43 +08:00
|
|
|
block->flex_grow_x != 0 || global.config.justify_content ==
|
|
|
|
FlexboxConfig::JustifyContent::Stretch
|
|
|
|
? 1
|
|
|
|
: 0;
|
2021-12-12 00:58:25 +08:00
|
|
|
element.flex_shrink = block->flex_shrink_x;
|
|
|
|
elements.push_back(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
box_helper::Compute(
|
|
|
|
&elements,
|
2022-03-31 08:17:43 +08:00
|
|
|
global.size_x - global.config.gap_x * (int(line.blocks.size()) - 1));
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
int x = 0;
|
|
|
|
for (size_t i = 0; i < line.blocks.size(); ++i) {
|
|
|
|
line.blocks[i]->dim_x = elements[i].size;
|
|
|
|
line.blocks[i]->x = x;
|
|
|
|
x += elements[i].size;
|
|
|
|
x += global.config.gap_x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
2021-12-12 00:58:25 +08:00
|
|
|
void SetY(Global& g, std::vector<Line> lines) {
|
|
|
|
std::vector<box_helper::Element> elements;
|
2023-06-01 01:24:08 +08:00
|
|
|
elements.reserve(lines.size());
|
2021-12-12 00:58:25 +08:00
|
|
|
for (auto& line : lines) {
|
|
|
|
box_helper::Element element;
|
|
|
|
element.flex_shrink = line.blocks.front()->flex_shrink_y;
|
|
|
|
element.flex_grow = line.blocks.front()->flex_grow_y;
|
|
|
|
for (auto* block : line.blocks) {
|
|
|
|
element.min_size = std::max(element.min_size, block->min_size_y);
|
|
|
|
element.flex_shrink = std::min(element.flex_shrink, block->flex_shrink_y);
|
|
|
|
element.flex_grow = std::min(element.flex_grow, block->flex_grow_y);
|
|
|
|
}
|
|
|
|
elements.push_back(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
// box_helper::Compute(&elements, g.size_y);
|
2022-03-31 08:17:43 +08:00
|
|
|
box_helper::Compute(&elements, 10000); // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
// [Align-content]
|
|
|
|
std::vector<int> ys(elements.size());
|
|
|
|
int y = 0;
|
|
|
|
for (size_t i = 0; i < elements.size(); ++i) {
|
|
|
|
ys[i] = y;
|
|
|
|
y += elements[i].size;
|
|
|
|
y += g.config.gap_y;
|
|
|
|
}
|
|
|
|
int remaining_space = std::max(0, g.size_y - y);
|
|
|
|
switch (g.config.align_content) {
|
|
|
|
case FlexboxConfig::AlignContent::FlexStart: {
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::FlexEnd: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (size_t i = 0; i < ys.size(); ++i) { // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += remaining_space;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::Center: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (size_t i = 0; i < ys.size(); ++i) { // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += remaining_space / 2;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::Stretch: {
|
2023-02-27 04:49:52 +08:00
|
|
|
for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
|
2022-12-20 01:51:25 +08:00
|
|
|
const int shifted = remaining_space * (i + 0) / (i + 1);
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += shifted;
|
2022-12-20 01:51:25 +08:00
|
|
|
const int consumed = remaining_space - shifted;
|
2021-12-12 00:58:25 +08:00
|
|
|
elements[i].size += consumed;
|
|
|
|
remaining_space -= consumed;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::SpaceBetween: {
|
2023-02-27 04:49:52 +08:00
|
|
|
for (int i = static_cast<int>(ys.size()) - 1; i >= 1; --i) { // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += remaining_space;
|
|
|
|
remaining_space = remaining_space * (i - 1) / i;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::SpaceAround: {
|
2023-02-27 04:49:52 +08:00
|
|
|
for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += remaining_space * (2 * i + 1) / (2 * i + 2);
|
|
|
|
remaining_space = remaining_space * (2 * i) / (2 * i + 2);
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignContent::SpaceEvenly: {
|
2023-02-27 04:49:52 +08:00
|
|
|
for (int i = static_cast<int>(ys.size()) - 1; i >= 0; --i) { // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
ys[i] += remaining_space * (i + 1) / (i + 2);
|
|
|
|
remaining_space = remaining_space * (i + 1) / (i + 2);
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// [Align items]
|
|
|
|
for (size_t i = 0; i < lines.size(); ++i) {
|
|
|
|
auto& element = elements[i];
|
|
|
|
for (auto* block : lines[i].blocks) {
|
2022-12-20 01:51:25 +08:00
|
|
|
const bool stretch =
|
2022-03-31 08:17:43 +08:00
|
|
|
block->flex_grow_y != 0 ||
|
2021-12-12 00:58:25 +08:00
|
|
|
g.config.align_content == FlexboxConfig::AlignContent::Stretch;
|
2022-12-20 01:51:25 +08:00
|
|
|
const int size =
|
2021-12-12 00:58:25 +08:00
|
|
|
stretch ? element.size : std::min(element.size, block->min_size_y);
|
|
|
|
switch (g.config.align_items) {
|
|
|
|
case FlexboxConfig::AlignItems::FlexStart: {
|
|
|
|
block->y = ys[i];
|
|
|
|
block->dim_y = size;
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignItems::Center: {
|
|
|
|
block->y = ys[i] + (element.size - size) / 2;
|
|
|
|
block->dim_y = size;
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignItems::FlexEnd: {
|
|
|
|
block->y = ys[i] + element.size - size;
|
|
|
|
block->dim_y = size;
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::AlignItems::Stretch: {
|
|
|
|
block->y = ys[i];
|
|
|
|
block->dim_y = element.size;
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JustifyContent(Global& g, std::vector<Line> lines) {
|
|
|
|
for (auto& line : lines) {
|
|
|
|
Block* last = line.blocks.back();
|
|
|
|
int remaining_space = g.size_x - last->x - last->dim_x;
|
|
|
|
switch (g.config.justify_content) {
|
|
|
|
case FlexboxConfig::JustifyContent::FlexStart:
|
|
|
|
case FlexboxConfig::JustifyContent::Stretch:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FlexboxConfig::JustifyContent::FlexEnd: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (auto* block : line.blocks) {
|
2021-12-12 00:58:25 +08:00
|
|
|
block->x += remaining_space;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::JustifyContent::Center: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (auto* block : line.blocks) {
|
2021-12-12 00:58:25 +08:00
|
|
|
block->x += remaining_space / 2;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::JustifyContent::SpaceBetween: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (int i = (int)line.blocks.size() - 1; i >= 1; --i) {
|
2021-12-12 00:58:25 +08:00
|
|
|
line.blocks[i]->x += remaining_space;
|
|
|
|
remaining_space = remaining_space * (i - 1) / i;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::JustifyContent::SpaceAround: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (int i = (int)line.blocks.size() - 1; i >= 0; --i) {
|
2021-12-12 00:58:25 +08:00
|
|
|
line.blocks[i]->x += remaining_space * (2 * i + 1) / (2 * i + 2);
|
|
|
|
remaining_space = remaining_space * (2 * i) / (2 * i + 2);
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
case FlexboxConfig::JustifyContent::SpaceEvenly: {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (int i = (int)line.blocks.size() - 1; i >= 0; --i) {
|
2021-12-12 00:58:25 +08:00
|
|
|
line.blocks[i]->x += remaining_space * (i + 1) / (i + 2);
|
|
|
|
remaining_space = remaining_space * (i + 1) / (i + 2);
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
break;
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
void Compute1(Global& global);
|
|
|
|
void Compute2(Global& global);
|
|
|
|
void Compute3(Global& global);
|
|
|
|
|
|
|
|
void Compute1(Global& global) {
|
2021-12-12 00:58:25 +08:00
|
|
|
if (global.config.direction == FlexboxConfig::Direction::RowInversed) {
|
|
|
|
SymmetryX(global);
|
2022-03-31 08:17:43 +08:00
|
|
|
Compute2(global);
|
2021-12-12 00:58:25 +08:00
|
|
|
SymmetryX(global);
|
|
|
|
return;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
Compute2(global);
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
void Compute2(Global& global) {
|
2021-12-12 00:58:25 +08:00
|
|
|
if (global.config.wrap == FlexboxConfig::Wrap::WrapInversed) {
|
|
|
|
SymmetryY(global);
|
2022-03-31 08:17:43 +08:00
|
|
|
Compute3(global);
|
2021-12-12 00:58:25 +08:00
|
|
|
SymmetryY(global);
|
|
|
|
return;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
Compute3(global);
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
void Compute3(Global& global) {
|
2021-12-12 00:58:25 +08:00
|
|
|
// Step 1: Lay out every elements into rows:
|
|
|
|
std::vector<Line> lines;
|
|
|
|
{
|
|
|
|
Line line;
|
|
|
|
int x = 0;
|
2023-06-01 01:24:08 +08:00
|
|
|
line.blocks.reserve(global.blocks.size());
|
2021-12-12 00:58:25 +08:00
|
|
|
for (auto& block : global.blocks) {
|
|
|
|
// Does it fit the end of the row?
|
|
|
|
// No? Then we need to start a new one:
|
|
|
|
if (x + block.min_size_x > global.size_x) {
|
|
|
|
x = 0;
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!line.blocks.empty()) {
|
2021-12-12 00:58:25 +08:00
|
|
|
lines.push_back(std::move(line));
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
line = Line();
|
|
|
|
}
|
|
|
|
|
2023-03-31 23:13:48 +08:00
|
|
|
block.line = lines.size();
|
|
|
|
block.line_position = line.blocks.size();
|
2021-12-12 00:58:25 +08:00
|
|
|
line.blocks.push_back(&block);
|
|
|
|
x += block.min_size_x + global.config.gap_x;
|
|
|
|
}
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!line.blocks.empty()) {
|
2021-12-12 00:58:25 +08:00
|
|
|
lines.push_back(std::move(line));
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Step 2: Set positions on the X axis.
|
|
|
|
SetX(global, lines);
|
|
|
|
JustifyContent(global, lines); // Distribute remaining space.
|
|
|
|
|
|
|
|
// Step 3: Set positions on the Y axis.
|
|
|
|
SetY(global, lines);
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void Compute(Global& global) {
|
|
|
|
if (global.config.direction == FlexboxConfig::Direction::Column ||
|
|
|
|
global.config.direction == FlexboxConfig::Direction::ColumnInversed) {
|
|
|
|
SymmetryXY(global);
|
|
|
|
Compute1(global);
|
|
|
|
SymmetryXY(global);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Compute1(global);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace ftxui::flexbox_helper
|