Support for inverted selections.

This commit is contained in:
ArthurSonzogni 2024-12-01 17:40:19 +01:00
parent 52e92a99be
commit f9b47f427d
No known key found for this signature in database
GPG Key ID: 41D98248C074CD6C
11 changed files with 196 additions and 93 deletions

View File

@ -56,11 +56,11 @@ add_library(dom
include/ftxui/dom/flexbox_config.hpp
include/ftxui/dom/node.hpp
include/ftxui/dom/requirement.hpp
include/ftxui/dom/selection.hpp
include/ftxui/dom/take_any_args.hpp
src/ftxui/dom/automerge.cpp
src/ftxui/dom/blink.cpp
src/ftxui/dom/bold.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/border.cpp
src/ftxui/dom/box_helper.cpp
src/ftxui/dom/box_helper.hpp
@ -81,6 +81,7 @@ add_library(dom
src/ftxui/dom/graph.cpp
src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/hyperlink.cpp
src/ftxui/dom/inverted.cpp
src/ftxui/dom/linear_gradient.cpp
src/ftxui/dom/node.cpp
@ -89,6 +90,7 @@ add_library(dom
src/ftxui/dom/reflect.cpp
src/ftxui/dom/scroll_indicator.cpp
src/ftxui/dom/selectable.cpp
src/ftxui/dom/selection.cpp
src/ftxui/dom/separator.cpp
src/ftxui/dom/size.cpp
src/ftxui/dom/spinner.cpp

View File

@ -138,7 +138,10 @@ class ScreenInteractive : public Screen {
// Selection API:
bool selection_enabled_ = false;
CapturedMouse selection_pending_;
Box selection_box_;
int selection_start_x_ = 0;
int selection_start_y_ = 0;
int selection_end_x_ = 0;
int selection_end_y_ = 0;
friend class Loop;

View File

@ -9,6 +9,7 @@
#include <vector> // for vector
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp"
@ -43,7 +44,7 @@ class Node {
// Step 3: (optional) Selection
// Propagated from Parents to Children.
virtual void Selection(Box selection, std::vector<Box>* selected);
virtual void Select(Selection& selection);
// Step 4: Draw this element.
virtual void Render(Screen& screen);
@ -66,7 +67,7 @@ class Node {
void Render(Screen& screen, const Element& element);
void Render(Screen& screen, Node* node);
void Render(Screen& screen, Node* node, Box selection);
void Render(Screen& screen, Node* node, Selection& selection);
} // namespace ftxui

View File

@ -0,0 +1,33 @@
// Copyright 2024 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#ifndef FTXUI_DOM_SELECTION_HPP
#define FTXUI_DOM_SELECTION_HPP
#include "ftxui/screen/box.hpp" // for Box
namespace ftxui {
/// @brief Represent a selection in the terminal.
class Selection {
public:
Selection(int start_x, int start_y, int end_x, int end_y);
const Box& GetBox() const;
Selection SaturateHorizontal(Box box);
Selection SaturateVertical(Box box);
private:
Selection* const parent_ = nullptr;
const int start_x_;
const int start_y_;
const int end_x_;
const int end_y_;
const Box box_;
};
} // namespace ftxui
#endif /* end of include guard: FTXUI_DOM_SELECTION_HPP */

View File

@ -837,28 +837,29 @@ bool ScreenInteractive::HandleSelection(Event event) {
return false;
}
if(mouse.motion == Mouse::Pressed) {
selection_pending_ = CaptureMouse();
if (!selection_pending_) {
return false;
}
selection_enabled_ = true;
selection_box_.x_min = mouse.x;
selection_box_.y_min = mouse.y;
selection_box_.x_max = mouse.x;
selection_box_.y_max = mouse.y;
return true;
if (mouse.motion == Mouse::Pressed) {
selection_pending_ = CaptureMouse();
selection_start_x_ = mouse.x;
selection_start_y_ = mouse.y;
selection_end_x_ = mouse.x;
selection_end_y_ = mouse.y;
}
else if((mouse.motion == Mouse::Moved) && (selection_pending_)) {
selection_box_.x_max = mouse.x;
selection_box_.y_max = mouse.y;
return true;
if (!selection_pending_) {
return false;
}
else if((mouse.motion == Mouse::Released) && (selection_pending_)) {
selection_box_.x_max = mouse.x;
selection_box_.y_max = mouse.y;
selection_pending_ = nullptr;
if (mouse.motion == Mouse::Moved) {
selection_end_x_ = mouse.x;
selection_end_y_ = mouse.y;
return true;
}
if (mouse.motion == Mouse::Released) {
selection_pending_ = nullptr;
selection_end_x_ = mouse.x;
selection_end_y_ = mouse.y;
return true;
}
return false;
@ -932,7 +933,9 @@ void ScreenInteractive::Draw(Component component) {
#endif
previous_frame_resized_ = resized;
Render(*this, document.get(), selection_box_);
Selection selection(selection_start_x_, selection_start_y_, //
selection_end_x_, selection_end_y_);
Render(*this, document.get(), selection);
// Set cursor position for user using tools to insert CJK characters.
{

View File

@ -65,27 +65,16 @@ class HBox : public Node {
}
}
void Selection(Box selection, std::vector<Box>* selected) override {
void Select(Selection& selection) override {
// If this Node box_ doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection, box_).IsEmpty()) {
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
const bool xmin_saturated =
selection.y_min < box_.y_min || selection.x_min < box_.x_min;
const bool xmax_saturated =
selection.y_max > box_.y_max || selection.x_max > box_.x_max;
if (xmin_saturated) {
selection.x_min = std::min(box_.x_min, selection.x_min);
}
if (xmax_saturated) {
selection.x_max = std::max(box_.x_max, selection.x_max);
}
Selection selection_saturated = selection.SaturateHorizontal(box_);
for (auto& child : children_) {
child->Selection(selection, selected);
child->Select(selection_saturated);
}
}
};

View File

@ -30,15 +30,15 @@ void Node::SetBox(Box box) {
/// @brief Compute the selection of an element.
/// @ingroup dom
void Node::Selection(Box selection, std::vector<Box>* selected) {
void Node::Select(Selection& selection) {
// If this Node box_ doesn't intersect with the selection, then no selection.
if (Box::Intersection(selection, box_).IsEmpty()) {
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
// By default we defer the selection to the children.
for (auto& child : children_) {
child->Selection(selection, selected);
child->Select(selection);
}
}
@ -60,16 +60,18 @@ void Node::Check(Status* status) {
/// @brief Display an element on a ftxui::Screen.
/// @ingroup dom
void Render(Screen& screen, const Element& element) {
Render(screen, element.get(), Box{0, 0, -1, -1});
Selection selection(0, 0, -1, -1);
Render(screen, element.get(), selection);
}
/// @brief Display an element on a ftxui::Screen.
/// @ingroup dom
void Render(Screen& screen, Node* node) {
Render(screen, node, Box{0, 0, -1, -1});
Selection selection(0, 0, -1, -1);
Render(screen, node, selection);
}
void Render(Screen& screen, Node* node, Box selection) {
void Render(Screen& screen, Node* node, Selection& selection) {
Box box;
box.x_min = 0;
box.y_min = 0;
@ -93,22 +95,7 @@ void Render(Screen& screen, Node* node, Box selection) {
}
// Step 3: Selection
std::vector<Box> selected;
Box selectionCleaned = selection;
if(selection.x_min > selection.x_max)
{
selectionCleaned.x_min = selection.x_max;
selectionCleaned.x_max = selection.x_min;
}
if(selection.y_min > selection.y_max)
{
selectionCleaned.y_min = selection.y_max;
selectionCleaned.y_max = selection.y_min;
}
node->Selection(selectionCleaned, &selected);
node->Select(selection);
// Step 4: Draw the element.
screen.stencil = box;

106
src/ftxui/dom/selection.cpp Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2024 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 "ftxui/dom/selection.hpp" // for Selection
#include <algorithm> // for max, min
namespace ftxui {
/// @brief Create a selection.
/// @param start_x The x coordinate of the start of the selection.
/// @param start_y The y coordinate of the start of the selection.
/// @param end_x The x coordinate of the end of the selection.
/// @param end_y The y coordinate of the end of the selection.
Selection::Selection(int start_x, int start_y, int end_x, int end_y)
: start_x_(start_x),
start_y_(start_y),
end_x_(end_x),
end_y_(end_y),
box_{
std::min(start_x, end_x),
std::max(start_x, end_x),
std::min(start_y, end_y),
std::max(start_y, end_y),
} {}
/// @brief Get the box of the selection.
/// @return The box of the selection.
const Box& Selection::GetBox() const {
return box_;
}
/// @brief Saturate the selection to be inside the box.
/// This is called by `hbox` to propagate the selection to its children.
/// @param box The box to saturate the selection in.
/// @return The saturated selection.
Selection Selection::SaturateHorizontal(Box box) {
int start_x = start_x_;
int start_y = start_y_;
int end_x = end_x_;
int end_y = end_y_;
const bool start_outside = !box.Contain(start_x, start_y);
const bool end_outside = !box.Contain(end_x, end_y);
const bool properly_ordered =
start_y < end_y || (start_y == end_y && start_x <= end_x);
if (properly_ordered) {
if (start_outside) {
start_x = box.x_min;
start_y = box.y_min;
}
if (end_outside) {
end_x = box.x_max;
end_y = box.y_max;
}
} else {
if (start_outside) {
start_x = box.x_max;
start_y = box.y_max;
}
if (end_outside) {
end_x = box.x_min;
end_y = box.y_min;
}
}
return Selection(start_x, start_y, end_x, end_y);
}
/// @brief Saturate the selection to be inside the box.
/// This is called by `vbox` to propagate the selection to its children.
/// @param box The box to saturate the selection in.
/// @return The saturated selection.
Selection Selection::SaturateVertical(Box box) {
int start_x = start_x_;
int start_y = start_y_;
int end_x = end_x_;
int end_y = end_y_;
const bool start_outside = !box.Contain(start_x, start_y);
const bool end_outside = !box.Contain(end_x, end_y);
const bool properly_ordered =
start_y < end_y || (start_y == end_y && start_x <= end_x);
if (properly_ordered) {
if (start_outside) {
start_x = box.x_min;
start_y = box.y_min;
}
if (end_outside) {
end_x = box.x_max;
end_y = box.y_max;
}
} else {
if (start_outside) {
start_x = box.x_max;
start_y = box.y_max;
}
if (end_outside) {
end_x = box.x_min;
end_y = box.y_min;
}
}
return Selection(start_x, start_y, end_x, end_y);
}
} // namespace ftxui

View File

@ -26,29 +26,19 @@ class Text : public Node {
void ComputeRequirement() override {
requirement_.min_x = string_width(text_);
requirement_.min_y = 1;
has_selection = false;
}
void Selection(Box selection, std::vector<Box>* selected) override {
if (Box::Intersection(selection, box_).IsEmpty()) {
void Select(Selection& selection) override {
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
const bool xmin_saturated =
selection.y_min < box_.y_min || selection.x_min < box_.x_min;
const bool xmax_saturated =
selection.y_max > box_.y_max || selection.x_max > box_.x_max;
selection_start_ = xmin_saturated ? box_.x_min : selection.x_min;
selection_end_ = xmax_saturated ? box_.x_max : selection.x_max;
Selection selection_saturated = selection.SaturateHorizontal(box_);
has_selection = true;
Box out;
out.x_min = selection_start_;
out.x_max = selection_end_;
out.y_min = box_.y_min;
out.y_max = box_.y_max;
selected->push_back(out);
selection_start_ = selection_saturated.GetBox().x_min;
selection_end_ = selection_saturated.GetBox().x_max;
}
void Render(Screen& screen) override {

View File

@ -65,27 +65,17 @@ class VBox : public Node {
}
}
void Selection(Box selection, std::vector<Box>* selected) override {
void Select(Selection& selection) override {
// If this Node box_ doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection, box_).IsEmpty()) {
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return;
}
const bool ymin_saturated =
selection.x_min < box_.x_min || selection.y_min < box_.y_min;
const bool ymax_saturated =
selection.x_max > box_.x_max || selection.y_max > box_.y_max;
Selection selection_saturated = selection.SaturateVertical(box_);
if (ymin_saturated) {
selection.y_min = std::min(box_.y_min, selection.y_min);
}
if (ymax_saturated) {
selection.y_max = std::max(box_.y_max, selection.y_max);
}
for (auto& child : children_) {
child->Selection(selection, selected);
child->Select(selection_saturated);
}
}
};

View File

@ -389,8 +389,7 @@ Screen Screen::Create(Dimensions dimension) {
return {dimension.dimx, dimension.dimy};
}
Screen::Screen(int dimx, int dimy) :
Image{dimx, dimy} {
Screen::Screen(int dimx, int dimy) : Image{dimx, dimy} {
#if defined(_WIN32)
// The placement of this call is a bit weird, however we can assume that
// anybody who instantiates a Screen object eventually wants to output