FTXUI/src/ftxui/dom/flexbox_helper.cpp

326 lines
9.2 KiB
C++
Raw Normal View History

#include "ftxui/dom/flexbox_helper.hpp"
#include <stddef.h> // for size_t
#include <algorithm> // for min, max
#include <memory> // for allocator_traits<>::value_type
#include <utility> // for swap, move
#include "ftxui/dom/box_helper.hpp" // for Element, Compute
namespace ftxui {
namespace flexbox_helper {
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;
for (auto* block : line.blocks) {
box_helper::Element element;
element.min_size = block->min_size_x;
element.flex_grow =
block->flex_grow_x || global.config.justify_content ==
FlexboxConfig::JustifyContent::Stretch;
element.flex_shrink = block->flex_shrink_x;
elements.push_back(element);
}
box_helper::Compute(
&elements,
global.size_x - global.config.gap_x * (line.blocks.size() - 1));
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;
}
}
}
void SetY(Global& g, std::vector<Line> lines) {
std::vector<box_helper::Element> elements;
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);
box_helper::Compute(&elements, 10000);
// [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: {
} break;
case FlexboxConfig::AlignContent::FlexEnd: {
for (size_t i = 0; i < ys.size(); ++i)
ys[i] += remaining_space;
} break;
case FlexboxConfig::AlignContent::Center: {
for (size_t i = 0; i < ys.size(); ++i)
ys[i] += remaining_space / 2;
} break;
case FlexboxConfig::AlignContent::Stretch: {
for (int i = ys.size() - 1; i >= 0; --i) {
int shifted = remaining_space * (i + 0) / (i + 1);
ys[i] += shifted;
int consumed = remaining_space - shifted;
elements[i].size += consumed;
remaining_space -= consumed;
}
} break;
case FlexboxConfig::AlignContent::SpaceBetween: {
for (int i = ys.size() - 1; i >= 1; --i) {
ys[i] += remaining_space;
remaining_space = remaining_space * (i - 1) / i;
}
} break;
case FlexboxConfig::AlignContent::SpaceAround: {
for (int i = ys.size() - 1; i >= 0; --i) {
ys[i] += remaining_space * (2 * i + 1) / (2 * i + 2);
remaining_space = remaining_space * (2 * i) / (2 * i + 2);
}
} break;
case FlexboxConfig::AlignContent::SpaceEvenly: {
for (int i = ys.size() - 1; i >= 0; --i) {
ys[i] += remaining_space * (i + 1) / (i + 2);
remaining_space = remaining_space * (i + 1) / (i + 2);
}
} break;
}
// [Align items]
for (size_t i = 0; i < lines.size(); ++i) {
auto& element = elements[i];
for (auto* block : lines[i].blocks) {
bool stretch =
block->flex_grow_y ||
g.config.align_content == FlexboxConfig::AlignContent::Stretch;
int size =
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;
} break;
case FlexboxConfig::AlignItems::Center: {
block->y = ys[i] + (element.size - size) / 2;
block->dim_y = size;
} break;
case FlexboxConfig::AlignItems::FlexEnd: {
block->y = ys[i] + element.size - size;
block->dim_y = size;
} break;
case FlexboxConfig::AlignItems::Stretch: {
block->y = ys[i];
block->dim_y = element.size;
} break;
}
}
}
}
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: {
for (auto* block : line.blocks)
block->x += remaining_space;
} break;
case FlexboxConfig::JustifyContent::Center: {
for (auto* block : line.blocks)
block->x += remaining_space / 2;
} break;
case FlexboxConfig::JustifyContent::SpaceBetween: {
for (int i = line.blocks.size() - 1; i >= 1; --i) {
line.blocks[i]->x += remaining_space;
remaining_space = remaining_space * (i - 1) / i;
}
} break;
case FlexboxConfig::JustifyContent::SpaceAround: {
for (int i = line.blocks.size() - 1; i >= 0; --i) {
line.blocks[i]->x += remaining_space * (2 * i + 1) / (2 * i + 2);
remaining_space = remaining_space * (2 * i) / (2 * i + 2);
}
} break;
case FlexboxConfig::JustifyContent::SpaceEvenly: {
for (int i = line.blocks.size() - 1; i >= 0; --i) {
line.blocks[i]->x += remaining_space * (i + 1) / (i + 2);
remaining_space = remaining_space * (i + 1) / (i + 2);
}
} break;
}
}
}
} // namespace
void Compute(Global& global) {
if (global.config.direction == FlexboxConfig::Direction::Column ||
global.config.direction == FlexboxConfig::Direction::ColumnInversed) {
SymmetryXY(global);
Compute(global);
SymmetryXY(global);
return;
}
if (global.config.direction == FlexboxConfig::Direction::RowInversed) {
SymmetryX(global);
Compute(global);
SymmetryX(global);
return;
}
if (global.config.wrap == FlexboxConfig::Wrap::WrapInversed) {
SymmetryY(global);
Compute(global);
SymmetryY(global);
return;
}
// Step 1: Lay out every elements into rows:
std::vector<Line> lines;
{
Line line;
int x = 0;
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;
if (!line.blocks.empty())
lines.push_back(std::move(line));
line = Line();
}
block.line = lines.size();
block.line_position = line.blocks.size();
line.blocks.push_back(&block);
x += block.min_size_x + global.config.gap_x;
}
if (!line.blocks.empty())
lines.push_back(std::move(line));
}
// 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);
}
} // namespace flexbox_helper
} // namespace ftxui
// 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.