Skip to content

Commit

Permalink
Start the tree-aware selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurSonzogni committed Nov 13, 2024
1 parent 143d152 commit d38f3d2
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 164 deletions.
101 changes: 51 additions & 50 deletions examples/component/selectable_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,63 @@
#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border
#include "ftxui/util/ref.hpp" // for Ref

int main() {
using namespace ftxui;
using namespace ftxui;

// The data:
std::string first_name;
std::string last_name;
std::string password;
std::string phoneNumber;
// Region selection;
std::string textToCopy;
Element LoremIpsum() {
return vbox({
text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua."),
text("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris "
"nisi ut aliquip ex ea commodo consequat."),
text("Duis aute irure dolor in reprehenderit in voluptate velit esse "
"cillum dolore eu fugiat nulla pariatur."),
text("Excepteur sint occaecat cupidatat non proident, sunt in culpa qui "
"officia deserunt mollit anim id est laborum."),
});
}

int main() {
auto screen = ScreenInteractive::TerminalOutput();

// The basic input components:
Component input_first_name = Input(&first_name, "first name");
Component input_last_name = Input(&last_name, "last name");

// The password input component:
InputOption password_option;
password_option.password = true;
Component input_password = Input(&password, "password", password_option);

// The phone number input component:
// We are using `CatchEvent` to filter out non-digit characters.
Component input_phone_number = Input(&phoneNumber, "phone number");
input_phone_number |= CatchEvent([&](const Event& event) {
return event.is_character() && !std::isdigit(event.character()[0]);
});
input_phone_number |= CatchEvent([&](const Event& event) {
return event.is_character() && phoneNumber.size() > 10;
});

// The component tree:
auto component = Container::Vertical({
input_first_name,
input_last_name,
input_password,
input_phone_number,
});
auto quit = Button("Quit", screen.ExitLoopClosure());

// Tweak how the component tree is rendered:
auto renderer = Renderer(component, [&] {
// The components:
auto renderer = Renderer(quit, [&] {
return vbox({
hbox(text(" First name : "), input_first_name->Render()),
hbox(text(" Last name : ") | selectable(),
input_last_name->Render()),
hbox(text(" Password : "), input_password->Render()),
hbox(text(" Phone num : "), input_phone_number->Render()) |
selectable(),
separator(),
text("Hello " + first_name + " " + last_name),
text("Your password is " + password),
text("Your phone number is " + phoneNumber),
text("Selected test is " + screen.GetSelection()),
}) |
border;
window(text("Horizontal split"), hbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Vertical split"), vbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
window(text("Vertical split"),
vbox({
window(text("horizontal split"), hbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
separator(),
window(text("horizontal split"), hbox({
LoremIpsum(),
separator(),
LoremIpsum(),
separator(),
LoremIpsum(),
})),
})),
quit->Render(),
});
});

screen.Loop(renderer);
Expand Down
9 changes: 8 additions & 1 deletion include/ftxui/component/screen_interactive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ class ScreenInteractive : public Screen {
void ForceHandleCtrlC(bool force);
void ForceHandleCtrlZ(bool force);

std::string GetSelection();
// Selection API.
//void OnSelectionChange(std::function<void(std::
//void ClearSelection();

private:
void ExitNow();
Expand Down Expand Up @@ -133,6 +135,11 @@ class ScreenInteractive : public Screen {
// The style of the cursor to restore on exit.
int cursor_reset_shape_ = 1;

// Selection API:
bool selection_enabled_ = false;
CapturedMouse selection_pending_;
Box selection_box_;

friend class Loop;

public:
Expand Down
13 changes: 7 additions & 6 deletions include/ftxui/dom/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef FTXUI_DOM_NODE_HPP
#define FTXUI_DOM_NODE_HPP

#include <list> // for list
#include <memory> // for shared_ptr
#include <vector> // for vector

Expand Down Expand Up @@ -40,7 +41,11 @@ class Node {
// Propagated from Parents to Children.
virtual void SetBox(Box box);

// Step 3: Draw this element.
// Step 3: (optional) Selection
// Propagated from Parents to Children.
virtual void Selection(Box selection, std::vector<Box>* selected);

// Step 4: Draw this element.
virtual void Render(Screen& screen);

// Layout may not resolve within a single iteration for some elements. This
Expand All @@ -52,11 +57,6 @@ class Node {
};
virtual void Check(Status* status);

// Selection.
// Propagated from Parents to Children.
virtual void Select(Box selected_area) {
// TODO: Implement this.
}

protected:
Elements children_;
Expand All @@ -66,6 +66,7 @@ class Node {

void Render(Screen& screen, const Element& element);
void Render(Screen& screen, Node* node);
void Render(Screen& screen, Node* node, Box selection);

} // namespace ftxui

Expand Down
3 changes: 0 additions & 3 deletions include/ftxui/screen/box.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ struct Box {
int x_max = 0;
int y_min = 0;
int y_max = 0;
bool isXInverted = false; // false means the box box from x_min to x_max (in the case of a selection for example)
bool isYInverted = false; // false means the box box from y_min to y_max (in the case of a selection for example)

static auto Intersection(Box a, Box b) -> Box;
static auto Union(Box a, Box b) -> Box;
bool Contain(int x, int y) const;
Box Clean() const;
bool IsEmpty() const;
bool operator==(const Box& other) const;
bool operator!=(const Box& other) const;
Expand Down
6 changes: 0 additions & 6 deletions include/ftxui/screen/screen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ class Screen : public Image {
Cursor cursor() const { return cursor_; }
void SetCursor(Cursor cursor) { cursor_ = cursor; }

bool selection_enabled = false;
CapturedMouse selection_pending;
Box mouse_selection_region;
Box selection_region;
std::string selection_text;

// Store an hyperlink in the screen. Return the id of the hyperlink. The id is
// used to identify the hyperlink when the user click on it.
uint8_t RegisterHyperlink(const std::string& link);
Expand Down
54 changes: 17 additions & 37 deletions src/ftxui/component/screen_interactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,53 +838,36 @@ bool ScreenInteractive::HandleSelection(Event event) {
}

if (mouse.motion == Mouse::Pressed) {
selection_pending = CaptureMouse();
if (!selection_pending) {
selection_pending_ = CaptureMouse();
if (!selection_pending_) {
return false;
}
selection_enabled = true;
mouse_selection_region.x_min = mouse.x;
mouse_selection_region.y_min = mouse.y;
mouse_selection_region.x_max = mouse.x;
mouse_selection_region.y_max = mouse.y;

selection_region = mouse_selection_region.Clean();
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 (!selection_pending) {
if (!selection_pending_) {
return false;
}

if (mouse.motion == Mouse::Moved) {
mouse_selection_region.x_max = mouse.x;
mouse_selection_region.y_max = mouse.y;

selection_region = mouse_selection_region.Clean();
selection_box_.x_max = mouse.x;
selection_box_.y_max = mouse.y;
return true;
}

if (mouse.motion == Mouse::Released) {
mouse_selection_region.x_max = mouse.x;
mouse_selection_region.y_max = mouse.y;
selection_pending = nullptr;

selection_region = mouse_selection_region.Clean();

if (mouse_selection_region.x_min == mouse_selection_region.x_max &&
mouse_selection_region.y_min == mouse_selection_region.y_max) {
selection_enabled = false;
return true;
}

return true;
if (mouse.motion != Mouse::Released) {
return false;
}

return false;
}

std::string ScreenInteractive::GetSelection() {
return selection_text;
selection_box_.x_max = mouse.x;
selection_box_.y_max = mouse.y;
selection_pending_ = nullptr;
return true;
}

// private
Expand Down Expand Up @@ -955,10 +938,7 @@ void ScreenInteractive::Draw(Component component) {
#endif
previous_frame_resized_ = resized;

// Clear selection text.
selection_text = "";

Render(*this, document);
Render(*this, document.get(), selection_box_);

// Set cursor position for user using tools to insert CJK characters.
{
Expand Down
24 changes: 24 additions & 0 deletions src/ftxui/dom/hbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ class HBox : public Node {
x = box.x_max + 1;
}
}

void Selection(Box selection, std::vector<Box>* selected) override {
// If this Node box_ doesn't intersect with the selection, then no
// selection.
if (Box::Intersection(selection, box_).IsEmpty()) {
return;
}

const bool xmin_satured =
selection.y_min < box_.y_min || selection.x_min < box_.x_min;
const bool xmax_satured =
selection.y_max > box_.y_max || selection.x_max > box_.x_max;

if (xmin_satured) {
selection.x_min = box_.x_min;
}
if (xmax_satured) {
selection.x_max = box_.x_max;
}

for (auto& child : children_) {
child->Selection(selection, selected);
}
}
};

} // namespace
Expand Down
29 changes: 26 additions & 3 deletions src/ftxui/dom/node.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <iostream>
// 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.
Expand Down Expand Up @@ -27,6 +28,20 @@ void Node::SetBox(Box box) {
box_ = box;
}

/// @brief Compute the selection of an element.
/// @ingroup dom
void Node::Selection(Box selection, std::vector<Box>* selected) {
// If this Node box_ doesn't intersect with the selection, then no selection.
if (Box::Intersection(selection, box_).IsEmpty()) {
return;
}

// By default we defer the selection to the children.
for (auto& child : children_) {
child->Selection(selection, selected);
}
}

/// @brief Display an element on a ftxui::Screen.
/// @ingroup dom
void Node::Render(Screen& screen) {
Expand All @@ -45,12 +60,16 @@ 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());
Render(screen, element.get(), Box{0, 0, -1, -1});
}

/// @brief Display an element on a ftxui::Screen.
/// @ingroup dom
void Render(Screen& screen, Node* node) {
Render(screen, node, Box{0, 0, -1, -1});
}

void Render(Screen& screen, Node* node, Box selection) {
Box box;
box.x_min = 0;
box.y_min = 0;
Expand All @@ -73,11 +92,15 @@ void Render(Screen& screen, Node* node) {
node->Check(&status);
}

// Step 3: Draw the element.
// Step 3: Selection
std::vector<Box> selected;
node->Selection(selection, &selected);

// Step 4: Draw the element.
screen.stencil = box;
node->Render(screen);

// Step 4: Apply shaders
// Step 5: Apply shaders
screen.ApplyShader();
}

Expand Down
Loading

0 comments on commit d38f3d2

Please sign in to comment.