FTXUI/src/ftxui/dom/flexbox.cpp
2023-08-19 13:57:01 +02:00

265 lines
8.2 KiB
C++

// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <algorithm> // for min, max
#include <cstddef> // for size_t
#include <memory> // for __shared_ptr_access, shared_ptr, allocator_traits<>::value_type, make_shared
#include <utility> // for move, swap
#include <vector> // for vector
#include "ftxui/dom/elements.hpp" // for Element, Elements, flexbox, hflow, vflow
#include "ftxui/dom/flexbox_config.hpp" // for FlexboxConfig, FlexboxConfig::Direction, FlexboxConfig::Direction::Column, FlexboxConfig::AlignContent, FlexboxConfig::Direction::ColumnInversed, FlexboxConfig::Direction::Row, FlexboxConfig::JustifyContent, FlexboxConfig::Wrap, FlexboxConfig::AlignContent::FlexStart, FlexboxConfig::Direction::RowInversed, FlexboxConfig::JustifyContent::FlexStart, FlexboxConfig::Wrap::Wrap
#include "ftxui/dom/flexbox_helper.hpp" // for Block, Global, Compute
#include "ftxui/dom/node.hpp" // for Node, Elements, Node::Status
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/box.hpp" // for Box
namespace ftxui {
namespace {
void Normalize(FlexboxConfig::Direction& direction) {
switch (direction) {
case FlexboxConfig::Direction::Row:
case FlexboxConfig::Direction::RowInversed: {
direction = FlexboxConfig::Direction::Row;
} break;
case FlexboxConfig::Direction::Column:
case FlexboxConfig::Direction::ColumnInversed: {
direction = FlexboxConfig::Direction::Column;
} break;
}
}
void Normalize(FlexboxConfig::AlignContent& align_content) {
align_content = FlexboxConfig::AlignContent::FlexStart;
}
void Normalize(FlexboxConfig::JustifyContent& justify_content) {
justify_content = FlexboxConfig::JustifyContent::FlexStart;
}
void Normalize(FlexboxConfig::Wrap& wrap) {
wrap = FlexboxConfig::Wrap::Wrap;
}
FlexboxConfig Normalize(FlexboxConfig config) {
Normalize(config.direction);
Normalize(config.wrap);
Normalize(config.justify_content);
Normalize(config.align_content);
return config;
}
class Flexbox : public Node {
public:
Flexbox(Elements children, FlexboxConfig config)
: Node(std::move(children)),
config_(config),
config_normalized_(Normalize(config)) {
requirement_.flex_grow_x = 1;
requirement_.flex_grow_y = 0;
if (IsColumnOriented()) {
std::swap(requirement_.flex_grow_x, requirement_.flex_grow_y);
}
}
bool IsColumnOriented() const {
return config_.direction == FlexboxConfig::Direction::Column ||
config_.direction == FlexboxConfig::Direction::ColumnInversed;
}
void Layout(flexbox_helper::Global& global,
bool compute_requirement = false) {
global.blocks.reserve(children_.size());
for (auto& child : children_) {
flexbox_helper::Block block;
block.min_size_x = child->requirement().min_x;
block.min_size_y = child->requirement().min_y;
if (!compute_requirement) {
block.flex_grow_x = child->requirement().flex_grow_x;
block.flex_grow_y = child->requirement().flex_grow_y;
block.flex_shrink_x = child->requirement().flex_shrink_x;
block.flex_shrink_y = child->requirement().flex_shrink_y;
}
global.blocks.push_back(block);
}
flexbox_helper::Compute(global);
}
void ComputeRequirement() override {
for (auto& child : children_) {
child->ComputeRequirement();
}
flexbox_helper::Global global;
global.config = config_normalized_;
if (IsColumnOriented()) {
global.size_x = 100000; // NOLINT
global.size_y = asked_;
} else {
global.size_x = asked_;
global.size_y = 100000; // NOLINT
}
Layout(global, true);
// Reset:
requirement_.selection = Requirement::Selection::NORMAL;
requirement_.selected_box = Box();
requirement_.min_x = 0;
requirement_.min_y = 0;
if (global.blocks.empty()) {
return;
}
// Compute the union of all the blocks:
Box box;
box.x_min = global.blocks[0].x;
box.y_min = global.blocks[0].y;
box.x_max = global.blocks[0].x + global.blocks[0].dim_x;
box.y_max = global.blocks[0].y + global.blocks[0].dim_y;
for (auto& b : global.blocks) {
box.x_min = std::min(box.x_min, b.x);
box.y_min = std::min(box.y_min, b.y);
box.x_max = std::max(box.x_max, b.x + b.dim_x);
box.y_max = std::max(box.y_max, b.y + b.dim_y);
}
requirement_.min_x = box.x_max - box.x_min;
requirement_.min_y = box.y_max - box.y_min;
// Find the selection:
for (size_t i = 0; i < children_.size(); ++i) {
if (requirement_.selection >= children_[i]->requirement().selection) {
continue;
}
requirement_.selection = children_[i]->requirement().selection;
Box selected_box = children_[i]->requirement().selected_box;
// Shift |selected_box| according to its position inside this component:
auto& b = global.blocks[i];
selected_box.x_min += b.x;
selected_box.y_min += b.y;
selected_box.x_max += b.x;
selected_box.y_max += b.y;
requirement_.selected_box = Box::Intersection(selected_box, box);
}
}
void SetBox(Box box) override {
Node::SetBox(box);
const int asked_previous = asked_;
asked_ = std::min(asked_, IsColumnOriented() ? box.y_max - box.y_min + 1
: box.x_max - box.x_min + 1);
need_iteration_ = (asked_ != asked_previous);
flexbox_helper::Global global;
global.config = config_;
global.size_x = box.x_max - box.x_min + 1;
global.size_y = box.y_max - box.y_min + 1;
Layout(global);
for (size_t i = 0; i < children_.size(); ++i) {
auto& child = children_[i];
auto& b = global.blocks[i];
Box children_box;
children_box.x_min = box.x_min + b.x;
children_box.y_min = box.y_min + b.y;
children_box.x_max = box.x_min + b.x + b.dim_x - 1;
children_box.y_max = box.y_min + b.y + b.dim_y - 1;
const Box intersection = Box::Intersection(children_box, box);
child->SetBox(intersection);
need_iteration_ |= (intersection != children_box);
}
}
void Check(Status* status) override {
for (auto& child : children_) {
child->Check(status);
}
if (status->iteration == 0) {
asked_ = 6000; // NOLINT
need_iteration_ = true;
}
status->need_iteration |= need_iteration_;
}
int asked_ = 6000; // NOLINT
bool need_iteration_ = true;
const FlexboxConfig config_;
const FlexboxConfig config_normalized_;
};
} // namespace
/// @brief A container displaying elements on row/columns and capable of
/// wrapping on the next column/row when full.
/// @param children The elements in the container
/// @param config The option
/// @return The container.
///
/// #### Example
///
/// ```cpp
/// flexbox({
/// text("element 1"),
/// text("element 2"),
/// text("element 3"),
/// }, FlexboxConfig()
// .Set(FlexboxConfig::Direction::Column)
// .Set(FlexboxConfig::Wrap::WrapInversed)
// .SetGapMainAxis(1)
// .SetGapCrossAxis(1)
// )
/// ```
Element flexbox(Elements children, FlexboxConfig config) {
return std::make_shared<Flexbox>(std::move(children), config);
}
/// @brief A container displaying elements in rows from left to right. When
/// filled, it starts on a new row below.
/// @param children The elements in the container
/// @return The container.
///
/// #### Example
///
/// ```cpp
/// hflow({
/// text("element 1"),
/// text("element 2"),
/// text("element 3"),
/// });
/// ```
Element hflow(Elements children) {
return flexbox(std::move(children), FlexboxConfig());
}
/// @brief A container displaying elements in rows from top to bottom. When
/// filled, it starts on a new columns on the right.
/// filled, it starts on a new row.
/// is full, it starts a new row.
/// @param children The elements in the container
/// @return The container.
///
/// #### Example
///
/// ```cpp
/// vflow({
/// text("element 1"),
/// text("element 2"),
/// text("element 3"),
/// });
/// ```
Element vflow(Elements children) {
return flexbox(std::move(children),
FlexboxConfig().Set(FlexboxConfig::Direction::Column));
}
} // namespace ftxui