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

View File

@ -138,7 +138,10 @@ class ScreenInteractive : public Screen {
// Selection API: // Selection API:
bool selection_enabled_ = false; bool selection_enabled_ = false;
CapturedMouse selection_pending_; 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; friend class Loop;

View File

@ -9,6 +9,7 @@
#include <vector> // for vector #include <vector> // for vector
#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/dom/selection.hpp" // for Selection
#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp" #include "ftxui/screen/screen.hpp"
@ -43,7 +44,7 @@ class Node {
// Step 3: (optional) Selection // Step 3: (optional) Selection
// Propagated from Parents to Children. // Propagated from Parents to Children.
virtual void Selection(Box selection, std::vector<Box>* selected); virtual void Select(Selection& selection);
// Step 4: Draw this element. // Step 4: Draw this element.
virtual void Render(Screen& screen); virtual void Render(Screen& screen);
@ -66,7 +67,7 @@ class Node {
void Render(Screen& screen, const Element& element); void Render(Screen& screen, const Element& element);
void Render(Screen& screen, Node* node); void Render(Screen& screen, Node* node);
void Render(Screen& screen, Node* node, Box selection); void Render(Screen& screen, Node* node, Selection& selection);
} // namespace ftxui } // 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; return false;
} }
if(mouse.motion == Mouse::Pressed) { if (mouse.motion == Mouse::Pressed) {
selection_pending_ = CaptureMouse(); selection_pending_ = CaptureMouse();
if (!selection_pending_) { selection_start_x_ = mouse.x;
return false; selection_start_y_ = mouse.y;
} selection_end_x_ = mouse.x;
selection_enabled_ = true; selection_end_y_ = mouse.y;
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;
} }
else if((mouse.motion == Mouse::Moved) && (selection_pending_)) {
selection_box_.x_max = mouse.x; if (!selection_pending_) {
selection_box_.y_max = mouse.y; return false;
return true;
} }
else if((mouse.motion == Mouse::Released) && (selection_pending_)) {
selection_box_.x_max = mouse.x; if (mouse.motion == Mouse::Moved) {
selection_box_.y_max = mouse.y; selection_end_x_ = mouse.x;
selection_pending_ = nullptr; selection_end_y_ = mouse.y;
return true; return true;
}
if (mouse.motion == Mouse::Released) {
selection_pending_ = nullptr;
selection_end_x_ = mouse.x;
selection_end_y_ = mouse.y;
return true;
} }
return false; return false;
@ -932,7 +933,9 @@ void ScreenInteractive::Draw(Component component) {
#endif #endif
previous_frame_resized_ = resized; 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. // 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 // If this Node box_ doesn't intersect with the selection, then no
// selection. // selection.
if (Box::Intersection(selection, box_).IsEmpty()) { if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return; return;
} }
const bool xmin_saturated = Selection selection_saturated = selection.SaturateHorizontal(box_);
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);
}
for (auto& child : children_) { 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. /// @brief Compute the selection of an element.
/// @ingroup dom /// @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 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; return;
} }
// By default we defer the selection to the children. // By default we defer the selection to the children.
for (auto& child : 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. /// @brief Display an element on a ftxui::Screen.
/// @ingroup dom /// @ingroup dom
void Render(Screen& screen, const Element& element) { 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. /// @brief Display an element on a ftxui::Screen.
/// @ingroup dom /// @ingroup dom
void Render(Screen& screen, Node* node) { 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 box;
box.x_min = 0; box.x_min = 0;
box.y_min = 0; box.y_min = 0;
@ -93,22 +95,7 @@ void Render(Screen& screen, Node* node, Box selection) {
} }
// Step 3: Selection // Step 3: Selection
std::vector<Box> selected; node->Select(selection);
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);
// Step 4: Draw the element. // Step 4: Draw the element.
screen.stencil = box; 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 { void ComputeRequirement() override {
requirement_.min_x = string_width(text_); requirement_.min_x = string_width(text_);
requirement_.min_y = 1; requirement_.min_y = 1;
has_selection = false;
} }
void Selection(Box selection, std::vector<Box>* selected) override { void Select(Selection& selection) override {
if (Box::Intersection(selection, box_).IsEmpty()) { if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return; return;
} }
const bool xmin_saturated = Selection selection_saturated = selection.SaturateHorizontal(box_);
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;
has_selection = true; has_selection = true;
selection_start_ = selection_saturated.GetBox().x_min;
Box out; selection_end_ = selection_saturated.GetBox().x_max;
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);
} }
void Render(Screen& screen) override { 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 // If this Node box_ doesn't intersect with the selection, then no
// selection. // selection.
if (Box::Intersection(selection, box_).IsEmpty()) { if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
return; return;
} }
const bool ymin_saturated = Selection selection_saturated = selection.SaturateVertical(box_);
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;
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_) { 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}; return {dimension.dimx, dimension.dimy};
} }
Screen::Screen(int dimx, int dimy) : Screen::Screen(int dimx, int dimy) : Image{dimx, dimy} {
Image{dimx, dimy} {
#if defined(_WIN32) #if defined(_WIN32)
// The placement of this call is a bit weird, however we can assume that // The placement of this call is a bit weird, however we can assume that
// anybody who instantiates a Screen object eventually wants to output // anybody who instantiates a Screen object eventually wants to output