mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-12-05 01:11:46 +08:00
Support for inverted selections.
This commit is contained in:
parent
52e92a99be
commit
f9b47f427d
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
33
include/ftxui/dom/selection.hpp
Normal file
33
include/ftxui/dom/selection.hpp
Normal 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 */
|
@ -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.
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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
106
src/ftxui/dom/selection.cpp
Normal 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
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
Selection selection_saturated = selection.SaturateVertical(box_);
|
||||
|
||||
for (auto& child : children_) {
|
||||
child->Selection(selection, selected);
|
||||
child->Select(selection_saturated);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user