diff --git a/CMakeLists.txt b/CMakeLists.txt index bf4a9da..f478249 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ add_library(dom src/ftxui/dom/paragraph.cpp src/ftxui/dom/reflect.cpp src/ftxui/dom/scroll_indicator.cpp + src/ftxui/dom/selectable.cpp src/ftxui/dom/separator.cpp src/ftxui/dom/size.cpp src/ftxui/dom/spinner.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 4339215..b55bdc6 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -38,6 +38,7 @@ example(radiobox) example(radiobox_in_frame) example(renderer) example(resizable_split) +example(selectable_input) example(scrollbar) example(slider) example(slider_direction) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp new file mode 100644 index 0000000..807ef46 --- /dev/null +++ b/examples/component/selectable_input.cpp @@ -0,0 +1,74 @@ +// 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 // for allocator, __shared_ptr_access +#include // 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 : ") | 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; + }); + + screen.Loop(renderer); +} diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 6c79913..faf1e83 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -26,6 +26,14 @@ struct Event; using Component = std::shared_ptr; class ScreenInteractivePrivate; +typedef struct { + + uint16_t startx = 0; + uint16_t endx = 0; + uint16_t starty = 0; + uint16_t endy = 0; +} Region; + class ScreenInteractive : public Screen { public: // Constructors: @@ -68,6 +76,8 @@ class ScreenInteractive : public Screen { void ForceHandleCtrlC(bool force); void ForceHandleCtrlZ(bool force); + std::string getSelection(void); + private: void ExitNow(); @@ -82,6 +92,8 @@ class ScreenInteractive : public Screen { void RunOnceBlocking(Component component); void HandleTask(Component component, Task& task); + bool selectableCatchEvent(Event event); + void refreshSelection(void); void Draw(Component component); void ResetCursorPosition(); @@ -126,6 +138,9 @@ class ScreenInteractive : public Screen { bool force_handle_ctrl_c_ = true; bool force_handle_ctrl_z_ = true; + Region selectedRegion; + std::string selectedText; + // The style of the cursor to restore on exit. int cursor_reset_shape_ = 1; diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index b17a711..325caa9 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -113,6 +113,8 @@ Decorator focusPositionRelative(float x, float y); Element automerge(Element child); Decorator hyperlink(std::string link); Element hyperlink(std::string link, Element child); +Element selectable(Element child); +Decorator selectable(void); // --- Layout is // Horizontal, Vertical or stacked set of elements. diff --git a/include/ftxui/screen/pixel.hpp b/include/ftxui/screen/pixel.hpp index cbc7cc2..817778b 100644 --- a/include/ftxui/screen/pixel.hpp +++ b/include/ftxui/screen/pixel.hpp @@ -21,6 +21,7 @@ struct Pixel { underlined(false), underlined_double(false), strikethrough(false), + selectable(false), automerge(false) {} // A bit field representing the style: @@ -30,6 +31,7 @@ struct Pixel { bool inverted : 1; bool underlined : 1; bool underlined_double : 1; + bool selectable : 1; bool strikethrough : 1; bool automerge : 1; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index b5643ed..e66012b 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -351,7 +351,8 @@ ScreenInteractive::ScreenInteractive(int dimx, bool use_alternative_screen) : Screen(dimx, dimy), dimension_(dimension), - use_alternative_screen_(use_alternative_screen) { + use_alternative_screen_(use_alternative_screen), + selectedText("") { task_receiver_ = MakeReceiver(); } @@ -781,7 +782,15 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { arg.screen_ = this; - const bool handled = component->OnEvent(arg); + bool handled = component->OnEvent(arg); + + if(handled == false) + { + if(selectableCatchEvent(arg)) + { + handled = true; + } + } if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) { RecordSignal(SIGABRT); @@ -824,6 +833,51 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { // clang-format on } +// private +bool ScreenInteractive::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; + } else if (mouse.motion == Mouse::Released) { + selectedRegion.endx = mouse.x; + selectedRegion.endy = mouse.y; + } else if (mouse.motion == Mouse::Moved) { + selectedRegion.endx = mouse.x; + selectedRegion.endy = mouse.y; + } + } + } + + return false; +} + +void ScreenInteractive::refreshSelection(void) { + + 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) { + if(PixelAt(x, y).selectable == true) + { + PixelAt(x, y).inverted ^= true; + selectedText += PixelAt(x, y).character; + } + } + } +} + +std::string ScreenInteractive::getSelection(void) { + + return selectedText; +} + // private // NOLINTNEXTLINE void ScreenInteractive::Draw(Component component) { @@ -894,6 +948,8 @@ void ScreenInteractive::Draw(Component component) { Render(*this, document); + refreshSelection(); + // Set cursor position for user using tools to insert CJK characters. { const int dx = dimx_ - 1 - cursor_.x + int(dimx_ != terminal.dimx); diff --git a/src/ftxui/dom/selectable.cpp b/src/ftxui/dom/selectable.cpp new file mode 100644 index 0000000..191c5f4 --- /dev/null +++ b/src/ftxui/dom/selectable.cpp @@ -0,0 +1,39 @@ +#include "ftxui/dom/elements.hpp" // for Element, Decorator +#include "ftxui/dom/node_decorator.hpp" // for NodeDecorator +#include "ftxui/component/event.hpp" // for Event + + +namespace ftxui { +namespace { + +class Selectable : public NodeDecorator { + public: + explicit Selectable(Element child) + : NodeDecorator(std::move(child)) {} + + private: + void Render(Screen& screen) override { + + for (int y = box_.y_min; y <= box_.y_max; ++y) { + for (int x = box_.x_min; x <= box_.x_max; ++x) { + screen.PixelAt(x, y).selectable = true; + } + } + + NodeDecorator::Render(screen); + } +}; + +} // namespace + + +Element selectable(Element child) { + return std::make_shared(std::move(child)); +} + +Decorator selectable(void) { + return + [](Element child) { return selectable(std::move(child)); }; +} + +} // namespace ftxui