Implement Node::Select for flexbox. (#977)

This commit is contained in:
Arthur Sonzogni 2025-03-21 16:15:25 +01:00 committed by GitHub
parent f2fb434e31
commit 96e8b8d92e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 102 additions and 37 deletions

View File

@ -850,7 +850,7 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
bool ScreenInteractive::HandleSelection(bool handled, Event event) { bool ScreenInteractive::HandleSelection(bool handled, Event event) {
if (handled) { if (handled) {
selection_pending_ = nullptr; selection_pending_ = nullptr;
selection_data_.empty = false; selection_data_.empty = true;
selection_ = nullptr; selection_ = nullptr;
return true; return true;
} }

View File

@ -93,28 +93,28 @@ class Flexbox : public Node {
for (auto& child : children_) { for (auto& child : children_) {
child->ComputeRequirement(); child->ComputeRequirement();
} }
flexbox_helper::Global global; global_ = flexbox_helper::Global();
global.config = config_normalized_; global_.config = config_normalized_;
if (IsColumnOriented()) { if (IsColumnOriented()) {
global.size_x = 100000; // NOLINT global_.size_x = 100000; // NOLINT
global.size_y = asked_; global_.size_y = asked_;
} else { } else {
global.size_x = asked_; global_.size_x = asked_;
global.size_y = 100000; // NOLINT global_.size_y = 100000; // NOLINT
} }
Layout(global, true); Layout(global_, true);
if (global.blocks.empty()) { if (global_.blocks.empty()) {
return; return;
} }
// Compute the union of all the blocks: // Compute the union of all the blocks:
Box box; Box box;
box.x_min = global.blocks[0].x; box.x_min = global_.blocks[0].x;
box.y_min = global.blocks[0].y; box.y_min = global_.blocks[0].y;
box.x_max = global.blocks[0].x + global.blocks[0].dim_x; box.x_max = global_.blocks[0].x + global_.blocks[0].dim_x;
box.y_max = global.blocks[0].y + global.blocks[0].dim_y; box.y_max = global_.blocks[0].y + global_.blocks[0].dim_y;
for (auto& b : global.blocks) { for (auto& b : global_.blocks) {
box.x_min = std::min(box.x_min, b.x); box.x_min = std::min(box.x_min, b.x);
box.y_min = std::min(box.y_min, b.y); box.y_min = std::min(box.y_min, b.y);
box.x_max = std::max(box.x_max, b.x + b.dim_x); box.x_max = std::max(box.x_max, b.x + b.dim_x);
@ -128,7 +128,7 @@ class Flexbox : public Node {
if (requirement_.focused.Prefer(children_[i]->requirement().focused)) { if (requirement_.focused.Prefer(children_[i]->requirement().focused)) {
requirement_.focused = children_[i]->requirement().focused; requirement_.focused = children_[i]->requirement().focused;
// Shift |focused.box| according to its position inside this component: // Shift |focused.box| according to its position inside this component:
auto& b = global.blocks[i]; auto& b = global_.blocks[i];
requirement_.focused.box.Shift(b.x, b.y); requirement_.focused.box.Shift(b.x, b.y);
requirement_.focused.box = requirement_.focused.box =
Box::Intersection(requirement_.focused.box, box); Box::Intersection(requirement_.focused.box, box);
@ -167,6 +167,43 @@ class Flexbox : public Node {
} }
} }
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;
}
Selection selection_line =
IsColumnOriented() ? selection_lines.SaturateHorizontal(box)
: selection_lines.SaturateVertical(box);
for (auto& block : line.blocks) {
std::ignore = block;
children_[i]->Select(selection_line);
i++;
}
}
}
void Check(Status* status) override { void Check(Status* status) override {
for (auto& child : children_) { for (auto& child : children_) {
child->Check(status); child->Check(status);
@ -184,6 +221,7 @@ class Flexbox : public Node {
bool need_iteration_ = true; bool need_iteration_ = true;
const FlexboxConfig config_; const FlexboxConfig config_;
const FlexboxConfig config_normalized_; const FlexboxConfig config_normalized_;
flexbox_helper::Global global_;
}; };
} // namespace } // namespace

View File

@ -68,6 +68,10 @@ void SymmetryXY(Global& g) {
std::swap(b.x, b.y); std::swap(b.x, b.y);
std::swap(b.dim_x, b.dim_y); std::swap(b.dim_x, b.dim_y);
} }
for (auto& l : g.lines) {
std::swap(l.x, l.y);
std::swap(l.dim_x, l.dim_y);
}
} }
void SymmetryX(Global& g) { void SymmetryX(Global& g) {
@ -75,6 +79,9 @@ void SymmetryX(Global& g) {
for (auto& b : g.blocks) { for (auto& b : g.blocks) {
b.x = g.size_x - b.x - b.dim_x; b.x = g.size_x - b.x - b.dim_x;
} }
for (auto& l : g.lines) {
l.x = g.size_x - l.x - l.dim_x;
}
} }
void SymmetryY(Global& g) { void SymmetryY(Global& g) {
@ -82,14 +89,13 @@ void SymmetryY(Global& g) {
for (auto& b : g.blocks) { for (auto& b : g.blocks) {
b.y = g.size_y - b.y - b.dim_y; b.y = g.size_y - b.y - b.dim_y;
} }
for (auto& l : g.lines) {
l.y = g.size_y - l.y - l.dim_y;
}
} }
struct Line { void SetX(Global& global) {
std::vector<Block*> blocks; for (auto& line : global.lines) {
};
void SetX(Global& global, std::vector<Line> lines) {
for (auto& line : lines) {
std::vector<box_helper::Element> elements; std::vector<box_helper::Element> elements;
elements.reserve(line.blocks.size()); elements.reserve(line.blocks.size());
for (auto* block : line.blocks) { for (auto* block : line.blocks) {
@ -110,19 +116,24 @@ void SetX(Global& global, std::vector<Line> lines) {
int x = 0; int x = 0;
for (size_t i = 0; i < line.blocks.size(); ++i) { for (size_t i = 0; i < line.blocks.size(); ++i) {
line.blocks[i]->dim_x = elements[i].size;
line.blocks[i]->x = x; line.blocks[i]->x = x;
line.blocks[i]->dim_x = elements[i].size;
x += elements[i].size; x += elements[i].size;
x += global.config.gap_x; x += global.config.gap_x;
} }
} }
for (auto& line : global.lines) {
line.x = 0;
line.dim_x = global.size_x;
}
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void SetY(Global& g, std::vector<Line> lines) { void SetY(Global& g) {
std::vector<box_helper::Element> elements; std::vector<box_helper::Element> elements;
elements.reserve(lines.size()); elements.reserve(g.lines.size());
for (auto& line : lines) { for (auto& line : g.lines) {
box_helper::Element element; box_helper::Element element;
element.flex_shrink = line.blocks.front()->flex_shrink_y; element.flex_shrink = line.blocks.front()->flex_shrink_y;
element.flex_grow = line.blocks.front()->flex_grow_y; element.flex_grow = line.blocks.front()->flex_grow_y;
@ -202,9 +213,9 @@ void SetY(Global& g, std::vector<Line> lines) {
} }
// [Align items] // [Align items]
for (size_t i = 0; i < lines.size(); ++i) { for (size_t i = 0; i < g.lines.size(); ++i) {
auto& element = elements[i]; auto& element = elements[i];
for (auto* block : lines[i].blocks) { for (auto* block : g.lines[i].blocks) {
const bool stretch = const bool stretch =
block->flex_grow_y != 0 || block->flex_grow_y != 0 ||
g.config.align_content == FlexboxConfig::AlignContent::Stretch; g.config.align_content == FlexboxConfig::AlignContent::Stretch;
@ -237,10 +248,16 @@ void SetY(Global& g, std::vector<Line> lines) {
} }
} }
} }
ys.push_back(g.size_y);
for (size_t i = 0; i < g.lines.size(); ++i) {
g.lines[i].y = ys[i];
g.lines[i].dim_y = ys[i + 1] - ys[i];
}
} }
void JustifyContent(Global& g, std::vector<Line> lines) { void JustifyContent(Global& g) {
for (auto& line : lines) { for (auto& line : g.lines) {
Block* last = line.blocks.back(); Block* last = line.blocks.back();
int remaining_space = g.size_x - last->x - last->dim_x; int remaining_space = g.size_x - last->x - last->dim_x;
switch (g.config.justify_content) { switch (g.config.justify_content) {
@ -315,38 +332,36 @@ void Compute2(Global& global) {
void Compute3(Global& global) { void Compute3(Global& global) {
// Step 1: Lay out every elements into rows: // Step 1: Lay out every elements into rows:
std::vector<Line> lines;
{ {
Line line; Line line;
int x = 0; int x = 0;
line.blocks.reserve(global.blocks.size());
for (auto& block : global.blocks) { for (auto& block : global.blocks) {
// Does it fit the end of the row? // Does it fit the end of the row?
// No? Then we need to start a new one: // No? Then we need to start a new one:
if (x + block.min_size_x > global.size_x) { if (x + block.min_size_x > global.size_x) {
x = 0; x = 0;
if (!line.blocks.empty()) { if (!line.blocks.empty()) {
lines.push_back(std::move(line)); global.lines.push_back(std::move(line));
} }
line = Line(); line = Line();
} }
block.line = static_cast<int>(lines.size()); block.line = static_cast<int>(global.lines.size());
block.line_position = static_cast<int>(line.blocks.size()); block.line_position = static_cast<int>(line.blocks.size());
line.blocks.push_back(&block); line.blocks.push_back(&block);
x += block.min_size_x + global.config.gap_x; x += block.min_size_x + global.config.gap_x;
} }
if (!line.blocks.empty()) { if (!line.blocks.empty()) {
lines.push_back(std::move(line)); global.lines.push_back(std::move(line));
} }
} }
// Step 2: Set positions on the X axis. // Step 2: Set positions on the X axis.
SetX(global, lines); SetX(global);
JustifyContent(global, lines); // Distribute remaining space. JustifyContent(global); // Distribute remaining space.
// Step 3: Set positions on the Y axis. // Step 3: Set positions on the Y axis.
SetY(global, lines); SetY(global);
} }
} // namespace } // namespace

View File

@ -9,6 +9,7 @@
namespace ftxui::flexbox_helper { namespace ftxui::flexbox_helper {
// A block is a rectangle in the flexbox.
struct Block { struct Block {
// Input: // Input:
int min_size_x = 0; int min_size_x = 0;
@ -28,13 +29,24 @@ struct Block {
bool overflow = false; bool overflow = false;
}; };
// A line is a row of blocks.
struct Line {
std::vector<Block*> blocks;
int x = 0;
int y = 0;
int dim_x = 0;
int dim_y = 0;
};
struct Global { struct Global {
std::vector<Block> blocks; std::vector<Block> blocks;
std::vector<Line> lines;
FlexboxConfig config; FlexboxConfig config;
int size_x; int size_x;
int size_y; int size_y;
}; };
void Compute(Global& global); void Compute(Global& global);
} // namespace ftxui::flexbox_helper } // namespace ftxui::flexbox_helper