2023-08-19 19:56:36 +08:00
|
|
|
// 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.
|
2021-12-12 00:58:25 +08:00
|
|
|
#include <algorithm> // for min, max
|
2022-03-31 08:17:43 +08:00
|
|
|
#include <cstddef> // for size_t
|
2021-12-12 00:58:25 +08:00
|
|
|
#include <memory> // for __shared_ptr_access, shared_ptr, allocator_traits<>::value_type, make_shared
|
2025-03-23 00:30:34 +08:00
|
|
|
#include <tuple> // for ignore
|
2021-12-12 00:58:25 +08:00
|
|
|
#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
|
2025-03-23 00:30:34 +08:00
|
|
|
#include "ftxui/dom/selection.hpp" // for Selection
|
2021-12-12 00:58:25 +08:00
|
|
|
#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;
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
if (IsColumnOriented()) {
|
2021-12-12 00:58:25 +08:00
|
|
|
std::swap(requirement_.flex_grow_x, requirement_.flex_grow_y);
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
bool IsColumnOriented() const {
|
2021-12-12 00:58:25 +08:00
|
|
|
return config_.direction == FlexboxConfig::Direction::Column ||
|
|
|
|
config_.direction == FlexboxConfig::Direction::ColumnInversed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layout(flexbox_helper::Global& global,
|
|
|
|
bool compute_requirement = false) {
|
2023-06-01 01:24:08 +08:00
|
|
|
global.blocks.reserve(children_.size());
|
2021-12-12 00:58:25 +08:00
|
|
|
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 {
|
2025-03-19 22:33:05 +08:00
|
|
|
requirement_ = Requirement{};
|
2022-03-31 08:17:43 +08:00
|
|
|
for (auto& child : children_) {
|
2021-12-12 00:58:25 +08:00
|
|
|
child->ComputeRequirement();
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2025-03-21 23:15:25 +08:00
|
|
|
global_ = flexbox_helper::Global();
|
|
|
|
global_.config = config_normalized_;
|
2021-12-12 00:58:25 +08:00
|
|
|
if (IsColumnOriented()) {
|
2025-03-21 23:15:25 +08:00
|
|
|
global_.size_x = 100000; // NOLINT
|
|
|
|
global_.size_y = asked_;
|
2021-12-12 00:58:25 +08:00
|
|
|
} else {
|
2025-03-21 23:15:25 +08:00
|
|
|
global_.size_x = asked_;
|
|
|
|
global_.size_y = 100000; // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
2025-03-21 23:15:25 +08:00
|
|
|
Layout(global_, true);
|
2021-12-12 00:58:25 +08:00
|
|
|
|
2025-03-21 23:15:25 +08:00
|
|
|
if (global_.blocks.empty()) {
|
2021-12-12 00:58:25 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-23 03:41:29 +08:00
|
|
|
// Compute the union of all the blocks:
|
2021-12-12 00:58:25 +08:00
|
|
|
Box box;
|
2025-03-21 23:15:25 +08:00
|
|
|
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) {
|
2021-12-12 00:58:25 +08:00
|
|
|
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;
|
2022-05-23 03:41:29 +08:00
|
|
|
|
|
|
|
// Find the selection:
|
|
|
|
for (size_t i = 0; i < children_.size(); ++i) {
|
2025-03-19 22:33:05 +08:00
|
|
|
if (requirement_.focused.Prefer(children_[i]->requirement().focused)) {
|
|
|
|
requirement_.focused = children_[i]->requirement().focused;
|
|
|
|
// Shift |focused.box| according to its position inside this component:
|
2025-03-21 23:15:25 +08:00
|
|
|
auto& b = global_.blocks[i];
|
2025-03-19 22:33:05 +08:00
|
|
|
requirement_.focused.box.Shift(b.x, b.y);
|
|
|
|
requirement_.focused.box =
|
|
|
|
Box::Intersection(requirement_.focused.box, box);
|
2022-05-23 03:41:29 +08:00
|
|
|
}
|
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SetBox(Box box) override {
|
|
|
|
Node::SetBox(box);
|
|
|
|
|
2022-12-20 01:51:25 +08:00
|
|
|
const int asked_previous = asked_;
|
2021-12-12 00:58:25 +08:00
|
|
|
asked_ = std::min(asked_, IsColumnOriented() ? box.y_max - box.y_min + 1
|
|
|
|
: box.x_max - box.x_min + 1);
|
2022-05-23 03:41:29 +08:00
|
|
|
need_iteration_ = (asked_ != asked_previous);
|
|
|
|
|
2021-12-12 00:58:25 +08:00
|
|
|
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;
|
|
|
|
|
2022-12-20 01:51:25 +08:00
|
|
|
const Box intersection = Box::Intersection(children_box, box);
|
2021-12-12 00:58:25 +08:00
|
|
|
child->SetBox(intersection);
|
|
|
|
|
|
|
|
need_iteration_ |= (intersection != children_box);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-21 23:15:25 +08:00
|
|
|
void Select(Selection& selection) override {
|
|
|
|
// If this Node box_ doesn't intersect with the selection, then no
|
|
|
|
// selection.
|
|
|
|
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Selection selection_lines = IsColumnOriented()
|
|
|
|
? selection.SaturateVertical(box_)
|
|
|
|
: selection.SaturateHorizontal(box_);
|
|
|
|
|
|
|
|
size_t i = 0;
|
|
|
|
for (auto& line : global_.lines) {
|
|
|
|
Box box;
|
|
|
|
box.x_min = box_.x_min + line.x;
|
|
|
|
box.x_max = box_.x_min + line.x + line.dim_x - 1;
|
|
|
|
box.y_min = box_.y_min + line.y;
|
|
|
|
box.y_max = box_.y_min + line.y + line.dim_y - 1;
|
|
|
|
|
|
|
|
// If the line box doesn't intersect with the selection, then no
|
|
|
|
// selection.
|
|
|
|
if (Box::Intersection(selection.GetBox(), box).IsEmpty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-03-23 00:30:34 +08:00
|
|
|
Selection selection_line = IsColumnOriented()
|
|
|
|
? selection_lines.SaturateHorizontal(box)
|
|
|
|
: selection_lines.SaturateVertical(box);
|
2025-03-21 23:15:25 +08:00
|
|
|
|
|
|
|
for (auto& block : line.blocks) {
|
|
|
|
std::ignore = block;
|
|
|
|
children_[i]->Select(selection_line);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 00:58:25 +08:00
|
|
|
void Check(Status* status) override {
|
2022-03-31 08:17:43 +08:00
|
|
|
for (auto& child : children_) {
|
2021-12-12 00:58:25 +08:00
|
|
|
child->Check(status);
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-12-12 00:58:25 +08:00
|
|
|
|
|
|
|
if (status->iteration == 0) {
|
2022-03-31 08:17:43 +08:00
|
|
|
asked_ = 6000; // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
need_iteration_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
status->need_iteration |= need_iteration_;
|
|
|
|
}
|
|
|
|
|
2022-03-31 08:17:43 +08:00
|
|
|
int asked_ = 6000; // NOLINT
|
2021-12-12 00:58:25 +08:00
|
|
|
bool need_iteration_ = true;
|
|
|
|
const FlexboxConfig config_;
|
|
|
|
const FlexboxConfig config_normalized_;
|
2025-03-21 23:15:25 +08:00
|
|
|
flexbox_helper::Global global_;
|
2021-12-12 00:58:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
} // 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) {
|
2022-03-31 08:17:43 +08:00
|
|
|
return std::make_shared<Flexbox>(std::move(children), config);
|
2021-12-12 00:58:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @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
|