diff --git a/CMakeLists.txt b/CMakeLists.txt index bf4a9da..2444657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 5d80fd8..fadf647 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(slider) example(slider_direction) example(slider_rgb) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp new file mode 100644 index 0000000..5be4512 --- /dev/null +++ b/examples/component/selectable_input.cpp @@ -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 // 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 : "), 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); +} diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index b17a711..f2a65b4 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -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; @@ -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 @@ -96,6 +106,9 @@ Element canvas(std::function); Element bold(Element); Element dim(Element); Element inverted(Element); +Element selectable(std::function onSelectionChange, Element); +Decorator selectable(std::function onSelectionChange); +bool selectableCatchEvent(Event event); Element underlined(Element); Element underlinedDouble(Element); Element blink(Element); diff --git a/src/ftxui/dom/selectable.cpp b/src/ftxui/dom/selectable.cpp new file mode 100644 index 0000000..50a18e7 --- /dev/null +++ b/src/ftxui/dom/selectable.cpp @@ -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 // for make_shared +#include // 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 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 onSelectionChange_; +}; +} // namespace + +/// @brief Add a filter that will invert the foreground and the background +/// colors. +/// @ingroup dom + +Element selectable(std::function onSelectionChange, Element child) { + return std::make_shared(std::move(child), onSelectionChange); +} + +Decorator selectable(std::function 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