Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selection of content to then use the clipboard #911

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ add_library(dom
src/ftxui/dom/gridbox.cpp
src/ftxui/dom/hbox.cpp
src/ftxui/dom/inverted.cpp
src/ftxui/dom/selectable.cpp
src/ftxui/dom/linear_gradient.cpp
src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp
Expand Down
1 change: 1 addition & 0 deletions examples/component/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ example(radiobox)
example(radiobox_in_frame)
example(renderer)
example(resizable_split)
example(selectable_input)
example(slider)
example(slider_direction)
example(slider_rgb)
Expand Down
80 changes: 80 additions & 0 deletions examples/component/selectable_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.
#include <memory> // for allocator, __shared_ptr_access
#include <string> // for char_traits, operator+, string, basic_string

#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#include "ftxui/component/screen_interactive.hpp" // for Component, ScreenInteractive
#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;

// The data:
std::string first_name;
std::string last_name;
std::string password;
std::string phoneNumber;
Region selection;
std::string textToCopy;

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([&](Event event) {
return event.is_character() && !std::isdigit(event.character()[0]);
});
input_phone_number |= CatchEvent([&](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,
});

// Tweak how the component tree is rendered:
auto renderer = Renderer(component, [&] {
return vbox({
hbox(text(" First name : "), input_first_name->Render()),
hbox(text(" Last name : "), input_last_name->Render()),
hbox(text(" Password : "), input_password->Render()),
hbox(text(" Phone num : "), input_phone_number->Render()),
separator(),
text("Hello " + first_name + " " + last_name),
text("Your password is " + password),
text("Your phone number is " + phoneNumber),
text("select_start " + std::to_string(selection.startx) + ";" + std::to_string(selection.starty)),
text("select_end " + std::to_string(selection.endx) + ";" + std::to_string(selection.endy)),
text("textToCopy " + textToCopy)
}) |
border | selectable([&textToCopy](std::string txtSelected){textToCopy = txtSelected;});
});

renderer |= CatchEvent([&](Event event) {

return selectableCatchEvent(event);
});

screen.Loop(renderer);
}
13 changes: 13 additions & 0 deletions include/ftxui/dom/elements.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ftxui/screen/color.hpp"
#include "ftxui/screen/terminal.hpp"
#include "ftxui/util/ref.hpp"
#include "ftxui/component/event.hpp"

namespace ftxui {
class Node;
Expand All @@ -33,6 +34,15 @@ enum BorderStyle {
EMPTY,
};

typedef struct {

uint16_t startx = 0;
uint16_t endx = 0;
uint16_t starty = 0;
uint16_t endy = 0;
bool changed = false;
} Region;

// Pipe elements into decorator togethers.
// For instance the next lines are equivalents:
// -> text("ftxui") | bold | underlined
Expand Down Expand Up @@ -96,6 +106,9 @@ Element canvas(std::function<void(Canvas&)>);
Element bold(Element);
Element dim(Element);
Element inverted(Element);
Element selectable(std::function<void(const std::string)> onSelectionChange, Element);
Decorator selectable(std::function<void(const std::string)> onSelectionChange);
bool selectableCatchEvent(Event event);
Element underlined(Element);
Element underlinedDouble(Element);
Element blink(Element);
Expand Down
84 changes: 84 additions & 0 deletions src/ftxui/dom/selectable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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.
#include <memory> // for make_shared
#include <utility> // for move

#include "ftxui/dom/elements.hpp" // for Element, inverted
#include "ftxui/dom/node.hpp" // for Node
#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/screen.hpp" // for Pixel, Screen

namespace ftxui {

Region selectedRegion;

namespace {
class Selectable : public NodeDecorator {
public:
using NodeDecorator::NodeDecorator;
Selectable(Element child, std::function<void(const std::string)> onSelectionChange)
: NodeDecorator(std::move(child)), onSelectionChange_(onSelectionChange) {}

void Render(Screen& screen) override {
Node::Render(screen);
std::string selectedText = "";
for (int y = std::min(selectedRegion.starty, selectedRegion.endy); y <= std::max(selectedRegion.starty, selectedRegion.endy); ++y) {
for (int x = std::min(selectedRegion.startx, selectedRegion.endx); x <= std::max(selectedRegion.startx, selectedRegion.endx)-1; ++x) {
screen.PixelAt(x, y).inverted ^= true;
selectedText += screen.PixelAt(x, y).character;
}
}

if(selectedRegion.changed == true) {
selectedRegion.changed = false;;
onSelectionChange_(selectedText);
}
}

private:
std::function<void(const std::string)> onSelectionChange_;
};
} // namespace

/// @brief Add a filter that will invert the foreground and the background
/// colors.
/// @ingroup dom

Element selectable(std::function<void(const std::string)> onSelectionChange, Element child) {
return std::make_shared<Selectable>(std::move(child), onSelectionChange);
}

Decorator selectable(std::function<void(const std::string)> onSelectionChange) {
return [onSelectionChange](Element child) { return selectable(onSelectionChange, std::move(child)); };
}

bool selectableCatchEvent(Event event) {

if (event.is_mouse()) {
auto& mouse = event.mouse();
if (mouse.button == Mouse::Left) {

if (mouse.motion == Mouse::Pressed) {
selectedRegion.startx = mouse.x;
selectedRegion.starty = mouse.y;
selectedRegion.endx = mouse.x;
selectedRegion.endy = mouse.y;
selectedRegion.changed = true;
} else if (mouse.motion == Mouse::Released) {
selectedRegion.endx = mouse.x;
selectedRegion.endy = mouse.y;
selectedRegion.changed = true;
} else if (mouse.motion == Mouse::Moved) {
selectedRegion.endx = mouse.x;
selectedRegion.endy = mouse.y;
selectedRegion.changed = true;
}
}
}

return false;
}

} // namespace ftxui