From 4e75cf9e0e6de5a211ad29b97b9124aeb3e45582 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 15:33:25 +0700 Subject: [PATCH 01/14] We have a basic decorator --- CMakeLists.txt | 1 + examples/component/CMakeLists.txt | 1 + examples/component/selectable_input.cpp | 80 +++++++++++++++++++++++++ include/ftxui/dom/elements.hpp | 2 + src/ftxui/dom/selectable.cpp | 39 ++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 examples/component/selectable_input.cpp create mode 100644 src/ftxui/dom/selectable.cpp 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..e2fc15e --- /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 : ") | selectable(), 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..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/src/ftxui/dom/selectable.cpp b/src/ftxui/dom/selectable.cpp new file mode 100644 index 0000000..9f6f868 --- /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 + + + +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).inverted = 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 From dea2d6408b3b3ecd09b2d90eab833c4aedbe7bbd Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 17:35:21 +0700 Subject: [PATCH 02/14] We can catch mouse events --- examples/component/selectable_input.cpp | 2 +- .../ftxui/component/screen_interactive.hpp | 13 +++++ include/ftxui/dom/elements.hpp | 3 ++ include/ftxui/screen/pixel.hpp | 2 + src/ftxui/component/screen_interactive.cpp | 51 ++++++++++++++++++- src/ftxui/dom/selectable.cpp | 4 +- 6 files changed, 71 insertions(+), 4 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index e2fc15e..fd16b2d 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -59,7 +59,7 @@ int main() { 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()), + hbox(text(" Phone num : "), input_phone_number->Render()) | selectable(), separator(), text("Hello " + first_name + " " + last_name), text("Your password is " + password), diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 6c79913..a01fa95 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -26,6 +26,15 @@ 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; + bool changed = false; +} Region; + class ScreenInteractive : public Screen { public: // Constructors: @@ -82,6 +91,8 @@ class ScreenInteractive : public Screen { void RunOnceBlocking(Component component); void HandleTask(Component component, Task& task); + bool selectableCatchEvent(Event event); + std::string getSelection(void); void Draw(Component component); void ResetCursorPosition(); @@ -126,6 +137,8 @@ class ScreenInteractive : public Screen { bool force_handle_ctrl_c_ = true; bool force_handle_ctrl_z_ = true; + Region selectedRegion; + // 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 325caa9..cf79899 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -116,6 +116,9 @@ Element hyperlink(std::string link, Element child); Element selectable(Element child); Decorator selectable(void); +// -- Selection -- +std::string getSelection(void); + // --- Layout is // Horizontal, Vertical or stacked set of elements. Element hbox(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..7b71887 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -35,6 +35,12 @@ #include "ftxui/screen/pixel.hpp" // for Pixel #include "ftxui/screen/terminal.hpp" // for Dimensions, Size + + +#include +#include + + #if defined(_WIN32) #define DEFINE_CONSOLEV2_PROPERTIES #define WIN32_LEAN_AND_MEAN @@ -781,7 +787,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 +838,41 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { // clang-format on } +bool ScreenInteractive::selectableCatchEvent(Event event) { + + // std::ofstream MyFile("debug.log", std::ios_base::app); + // MyFile << "Top dog!" << std::endl; + // MyFile.close(); + + 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; +} + +std::string ScreenInteractive::getSelection(void) { + return "Selection"; +} + // private // NOLINTNEXTLINE void ScreenInteractive::Draw(Component component) { diff --git a/src/ftxui/dom/selectable.cpp b/src/ftxui/dom/selectable.cpp index 9f6f868..191c5f4 100644 --- a/src/ftxui/dom/selectable.cpp +++ b/src/ftxui/dom/selectable.cpp @@ -1,6 +1,6 @@ #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 { @@ -16,7 +16,7 @@ class Selectable : public NodeDecorator { 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).inverted = true; + screen.PixelAt(x, y).selectable = true; } } From e4a63318adaf6c793270132b45d214fbd75691bd Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 17:40:48 +0700 Subject: [PATCH 03/14] We can act on the screen --- examples/component/selectable_input.cpp | 3 +++ include/ftxui/component/screen_interactive.hpp | 5 +++-- include/ftxui/dom/elements.hpp | 3 --- src/ftxui/component/screen_interactive.cpp | 18 ++++++++++++++---- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index fd16b2d..c256482 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -55,6 +55,9 @@ int main() { // Tweak how the component tree is rendered: auto renderer = Renderer(component, [&] { + + textToCopy = screen.getSelection(); + return vbox({ hbox(text(" First name : "), input_first_name->Render()), hbox(text(" Last name : ") | selectable(), input_last_name->Render()), diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index a01fa95..e6d1560 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -32,7 +32,6 @@ typedef struct { uint16_t endx = 0; uint16_t starty = 0; uint16_t endy = 0; - bool changed = false; } Region; class ScreenInteractive : public Screen { @@ -77,6 +76,8 @@ class ScreenInteractive : public Screen { void ForceHandleCtrlC(bool force); void ForceHandleCtrlZ(bool force); + std::string getSelection(void); + private: void ExitNow(); @@ -92,7 +93,7 @@ class ScreenInteractive : public Screen { void HandleTask(Component component, Task& task); bool selectableCatchEvent(Event event); - std::string getSelection(void); + void refreshSelection(void); void Draw(Component component); void ResetCursorPosition(); diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index cf79899..325caa9 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -116,9 +116,6 @@ Element hyperlink(std::string link, Element child); Element selectable(Element child); Decorator selectable(void); -// -- Selection -- -std::string getSelection(void); - // --- Layout is // Horizontal, Vertical or stacked set of elements. Element hbox(Elements); diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 7b71887..75acc0a 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -853,15 +853,15 @@ bool ScreenInteractive::selectableCatchEvent(Event event) { selectedRegion.starty = mouse.y; selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - selectedRegion.changed = true; + refreshSelection(); } else if (mouse.motion == Mouse::Released) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - selectedRegion.changed = true; + refreshSelection(); } else if (mouse.motion == Mouse::Moved) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - selectedRegion.changed = true; + refreshSelection(); } } } @@ -869,8 +869,18 @@ bool ScreenInteractive::selectableCatchEvent(Event event) { return false; } +void ScreenInteractive::refreshSelection(void) { + + 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) { + PixelAt(x, y).inverted ^= true; + //selectedText += PixelAt(x, y).character; + } + } +} + std::string ScreenInteractive::getSelection(void) { - return "Selection"; + return "Kikoo"; } // private From 364993464abe4ea36b113c1aff826eac0b1573c1 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 21:13:00 +0700 Subject: [PATCH 04/14] It somewhat works --- examples/component/selectable_input.cpp | 2 +- .../ftxui/component/screen_interactive.hpp | 1 + src/ftxui/component/screen_interactive.cpp | 27 ++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index c256482..a3dcb23 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -69,7 +69,7 @@ int main() { 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) + text("textToCopy is " + textToCopy) }) | border; // | selectable([&textToCopy](std::string txtSelected){textToCopy = txtSelected;}) }); diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index e6d1560..faf1e83 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -139,6 +139,7 @@ class ScreenInteractive : public Screen { 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/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 75acc0a..d33b4f1 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -357,7 +357,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(); } @@ -853,15 +854,15 @@ bool ScreenInteractive::selectableCatchEvent(Event event) { selectedRegion.starty = mouse.y; selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - refreshSelection(); + // refreshSelection(); } else if (mouse.motion == Mouse::Released) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - refreshSelection(); + // refreshSelection(); } else if (mouse.motion == Mouse::Moved) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - refreshSelection(); + // refreshSelection(); } } } @@ -871,16 +872,26 @@ bool ScreenInteractive::selectableCatchEvent(Event event) { 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) { - PixelAt(x, y).inverted ^= true; - //selectedText += PixelAt(x, y).character; + if(PixelAt(x, y).selectable == true) + { + PixelAt(x, y).inverted ^= true; + selectedText += PixelAt(x, y).character; + } } } } std::string ScreenInteractive::getSelection(void) { - return "Kikoo"; + + // std::ofstream MyFile("debug.log", std::ios_base::app); + // MyFile << "Top dog!" << std::endl; + // MyFile.close(); + + return selectedText; } // private @@ -953,6 +964,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); From 118055d942f5bd66c85f22d42ee1219cc3410b70 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 21:16:04 +0700 Subject: [PATCH 05/14] Cleanup --- examples/component/selectable_input.cpp | 13 ++----------- src/ftxui/component/screen_interactive.cpp | 11 ----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index a3dcb23..807ef46 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -56,8 +56,6 @@ int main() { // Tweak how the component tree is rendered: auto renderer = Renderer(component, [&] { - textToCopy = screen.getSelection(); - return vbox({ hbox(text(" First name : "), input_first_name->Render()), hbox(text(" Last name : ") | selectable(), input_last_name->Render()), @@ -67,17 +65,10 @@ int main() { 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 is " + textToCopy) + text("Selected test is " + screen.getSelection()) }) | - border; // | selectable([&textToCopy](std::string txtSelected){textToCopy = txtSelected;}) + border; }); - // renderer |= CatchEvent([&](Event event) { - - // return selectableCatchEvent(event); - // }); - screen.Loop(renderer); } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index d33b4f1..1d45535 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -841,10 +841,6 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { bool ScreenInteractive::selectableCatchEvent(Event event) { - // std::ofstream MyFile("debug.log", std::ios_base::app); - // MyFile << "Top dog!" << std::endl; - // MyFile.close(); - if (event.is_mouse()) { auto& mouse = event.mouse(); if (mouse.button == Mouse::Left) { @@ -854,15 +850,12 @@ bool ScreenInteractive::selectableCatchEvent(Event event) { selectedRegion.starty = mouse.y; selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - // refreshSelection(); } else if (mouse.motion == Mouse::Released) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - // refreshSelection(); } else if (mouse.motion == Mouse::Moved) { selectedRegion.endx = mouse.x; selectedRegion.endy = mouse.y; - // refreshSelection(); } } } @@ -887,10 +880,6 @@ void ScreenInteractive::refreshSelection(void) { std::string ScreenInteractive::getSelection(void) { - // std::ofstream MyFile("debug.log", std::ios_base::app); - // MyFile << "Top dog!" << std::endl; - // MyFile.close(); - return selectedText; } From f8dd258d6581fa20ada5b4ab8f29677f1ab9c6e4 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 21:20:19 +0700 Subject: [PATCH 06/14] More cleanup --- src/ftxui/component/screen_interactive.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 1d45535..e66012b 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -35,12 +35,6 @@ #include "ftxui/screen/pixel.hpp" // for Pixel #include "ftxui/screen/terminal.hpp" // for Dimensions, Size - - -#include -#include - - #if defined(_WIN32) #define DEFINE_CONSOLEV2_PROPERTIES #define WIN32_LEAN_AND_MEAN @@ -839,6 +833,7 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { // clang-format on } +// private bool ScreenInteractive::selectableCatchEvent(Event event) { if (event.is_mouse()) { From 3754136dd6671dceda958341d07b182b9ac78f81 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sat, 31 Aug 2024 15:39:42 +0200 Subject: [PATCH 07/14] Reformat + fix pending selection. --- examples/component/selectable_input.cpp | 16 ++-- .../ftxui/component/screen_interactive.hpp | 13 +-- include/ftxui/dom/node.hpp | 6 ++ src/ftxui/component/screen_interactive.cpp | 95 ++++++++++++------- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index 807ef46..91dd577 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -1,10 +1,8 @@ // 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 @@ -12,7 +10,6 @@ #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; @@ -38,10 +35,10 @@ int main() { // 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) { + input_phone_number |= CatchEvent([&](const Event& event) { return event.is_character() && !std::isdigit(event.character()[0]); }); - input_phone_number |= CatchEvent([&](Event event) { + input_phone_number |= CatchEvent([&](const Event& event) { return event.is_character() && phoneNumber.size() > 10; }); @@ -55,17 +52,18 @@ int main() { // 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(" Last name : ") | selectable(), + input_last_name->Render()), hbox(text(" Password : "), input_password->Render()), - hbox(text(" Phone num : "), input_phone_number->Render()) | selectable(), + 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()) + text("Selected test is " + screen.GetSelection()), }) | border; }); diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index faf1e83..49f1837 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -27,7 +27,6 @@ using Component = std::shared_ptr; class ScreenInteractivePrivate; typedef struct { - uint16_t startx = 0; uint16_t endx = 0; uint16_t starty = 0; @@ -76,7 +75,7 @@ class ScreenInteractive : public Screen { void ForceHandleCtrlC(bool force); void ForceHandleCtrlZ(bool force); - std::string getSelection(void); + std::string GetSelection(); private: void ExitNow(); @@ -92,8 +91,8 @@ class ScreenInteractive : public Screen { void RunOnceBlocking(Component component); void HandleTask(Component component, Task& task); - bool selectableCatchEvent(Event event); - void refreshSelection(void); + bool HandleSelection(Event event); + void RefreshSelection(); void Draw(Component component); void ResetCursorPosition(); @@ -138,8 +137,10 @@ class ScreenInteractive : public Screen { bool force_handle_ctrl_c_ = true; bool force_handle_ctrl_z_ = true; - Region selectedRegion; - std::string selectedText; + bool selection_enabled = false; + CapturedMouse selection_pending; + Region selection_region; + std::string selection_text; // The style of the cursor to restore on exit. int cursor_reset_shape_ = 1; diff --git a/include/ftxui/dom/node.hpp b/include/ftxui/dom/node.hpp index f43157a..5a71512 100644 --- a/include/ftxui/dom/node.hpp +++ b/include/ftxui/dom/node.hpp @@ -52,6 +52,12 @@ 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_; Requirement requirement_; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index e66012b..0112f62 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -352,7 +352,7 @@ ScreenInteractive::ScreenInteractive(int dimx, : Screen(dimx, dimy), dimension_(dimension), use_alternative_screen_(use_alternative_screen), - selectedText("") { + selection_text("") { task_receiver_ = MakeReceiver(); } @@ -784,13 +784,7 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { bool handled = component->OnEvent(arg); - if(handled == false) - { - if(selectableCatchEvent(arg)) - { - handled = true; - } - } + handled = handled || HandleSelection(arg); if (arg == Event::CtrlC && (!handled || force_handle_ctrl_c_)) { RecordSignal(SIGABRT); @@ -834,48 +828,77 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { } // private -bool ScreenInteractive::selectableCatchEvent(Event event) { +bool ScreenInteractive::HandleSelection(Event event) { + if (!event.is_mouse()) { + return false; + } - if (event.is_mouse()) { - auto& mouse = event.mouse(); - if (mouse.button == Mouse::Left) { + auto& mouse = event.mouse(); + if (mouse.button != Mouse::Left) { + return false; + } - 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; - } + if (mouse.motion == Mouse::Pressed) { + selection_pending = CaptureMouse(); + if (!selection_pending) { + return false; } + selection_enabled = true; + selection_region.startx = mouse.x; + selection_region.starty = mouse.y; + selection_region.endx = mouse.x; + selection_region.endy = mouse.y; + return true; + } + + if (!selection_pending) { + return false; + } + + if (mouse.motion == Mouse::Moved) { + selection_region.endx = mouse.x; + selection_region.endy = mouse.y; + return true; + } + + if (mouse.motion == Mouse::Released) { + selection_region.endx = mouse.x; + selection_region.endy = mouse.y; + selection_pending = nullptr; + + if (selection_region.startx == selection_region.endx && + selection_region.starty == selection_region.endy) { + selection_enabled = false; + return true; + } + + return true; } return false; } -void ScreenInteractive::refreshSelection(void) { +void ScreenInteractive::RefreshSelection() { + if (!selection_enabled) { + return; + } + selection_text = ""; - 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) - { + for (int y = std::min(selection_region.starty, selection_region.endy); + y <= std::max(selection_region.starty, selection_region.endy); ++y) { + for (int x = std::min(selection_region.startx, selection_region.endx); + x <= std::max(selection_region.startx, selection_region.endx) - 1; + ++x) { + if (PixelAt(x, y).selectable == true) { PixelAt(x, y).inverted ^= true; - selectedText += PixelAt(x, y).character; + selection_text += PixelAt(x, y).character; } } } } -std::string ScreenInteractive::getSelection(void) { - - return selectedText; +std::string ScreenInteractive::GetSelection() { + return selection_text; } // private @@ -948,7 +971,7 @@ void ScreenInteractive::Draw(Component component) { Render(*this, document); - refreshSelection(); + RefreshSelection(); // Set cursor position for user using tools to insert CJK characters. { From 54b7a931787d7c2c1c4d9f85b1af03b8c5dec3ef Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 20:57:45 +0700 Subject: [PATCH 08/14] Make use of Box instead of my custom Region struct --- .../ftxui/component/screen_interactive.hpp | 12 ------- include/ftxui/screen/screen.hpp | 6 ++++ src/ftxui/component/screen_interactive.cpp | 31 +++++++++---------- src/ftxui/screen/screen.cpp | 4 ++- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 49f1837..6fd61c2 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -26,13 +26,6 @@ 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: @@ -137,11 +130,6 @@ class ScreenInteractive : public Screen { bool force_handle_ctrl_c_ = true; bool force_handle_ctrl_z_ = true; - bool selection_enabled = false; - CapturedMouse selection_pending; - Region selection_region; - std::string selection_text; - // The style of the cursor to restore on exit. int cursor_reset_shape_ = 1; diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 51b83a0..211ed59 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -10,6 +10,7 @@ #include "ftxui/screen/image.hpp" // for Pixel, Image #include "ftxui/screen/terminal.hpp" // for Dimensions +#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse namespace ftxui { @@ -62,6 +63,11 @@ class Screen : public Image { Cursor cursor() const { return cursor_; } void SetCursor(Cursor cursor) { cursor_ = cursor; } + bool selection_enabled = false; + CapturedMouse selection_pending; + 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); diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 0112f62..7b1bbf0 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -351,8 +351,7 @@ ScreenInteractive::ScreenInteractive(int dimx, bool use_alternative_screen) : Screen(dimx, dimy), dimension_(dimension), - use_alternative_screen_(use_alternative_screen), - selection_text("") { + use_alternative_screen_(use_alternative_screen) { task_receiver_ = MakeReceiver(); } @@ -844,10 +843,10 @@ bool ScreenInteractive::HandleSelection(Event event) { return false; } selection_enabled = true; - selection_region.startx = mouse.x; - selection_region.starty = mouse.y; - selection_region.endx = mouse.x; - selection_region.endy = mouse.y; + selection_region.x_min = mouse.x; + selection_region.y_min = mouse.y; + selection_region.x_max = mouse.x; + selection_region.y_max = mouse.y; return true; } @@ -856,18 +855,18 @@ bool ScreenInteractive::HandleSelection(Event event) { } if (mouse.motion == Mouse::Moved) { - selection_region.endx = mouse.x; - selection_region.endy = mouse.y; + selection_region.x_max = mouse.x; + selection_region.y_max = mouse.y; return true; } if (mouse.motion == Mouse::Released) { - selection_region.endx = mouse.x; - selection_region.endy = mouse.y; + selection_region.x_max = mouse.x; + selection_region.y_max = mouse.y; selection_pending = nullptr; - if (selection_region.startx == selection_region.endx && - selection_region.starty == selection_region.endy) { + if (selection_region.x_min == selection_region.x_max && + selection_region.y_min == selection_region.y_max) { selection_enabled = false; return true; } @@ -884,10 +883,10 @@ void ScreenInteractive::RefreshSelection() { } selection_text = ""; - for (int y = std::min(selection_region.starty, selection_region.endy); - y <= std::max(selection_region.starty, selection_region.endy); ++y) { - for (int x = std::min(selection_region.startx, selection_region.endx); - x <= std::max(selection_region.startx, selection_region.endx) - 1; + for (int y = std::min(selection_region.y_min, selection_region.y_max); + y <= std::max(selection_region.y_min, selection_region.y_max); ++y) { + for (int x = std::min(selection_region.x_min, selection_region.x_max); + x <= std::max(selection_region.x_min, selection_region.x_max) - 1; ++x) { if (PixelAt(x, y).selectable == true) { PixelAt(x, y).inverted ^= true; diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 7bd64e2..b1c0945 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -389,7 +389,9 @@ 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}, + selection_text("") { #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 From 68ba5cf7441a8828bfc1e5be7cf28f8a533a4e94 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 21:05:39 +0700 Subject: [PATCH 09/14] Implementation of the selection in the text node --- src/ftxui/component/screen_interactive.cpp | 24 +++------------------- src/ftxui/dom/text.cpp | 9 ++++++++ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 7b1bbf0..49cbee0 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -877,25 +877,6 @@ bool ScreenInteractive::HandleSelection(Event event) { return false; } -void ScreenInteractive::RefreshSelection() { - if (!selection_enabled) { - return; - } - selection_text = ""; - - for (int y = std::min(selection_region.y_min, selection_region.y_max); - y <= std::max(selection_region.y_min, selection_region.y_max); ++y) { - for (int x = std::min(selection_region.x_min, selection_region.x_max); - x <= std::max(selection_region.x_min, selection_region.x_max) - 1; - ++x) { - if (PixelAt(x, y).selectable == true) { - PixelAt(x, y).inverted ^= true; - selection_text += PixelAt(x, y).character; - } - } - } -} - std::string ScreenInteractive::GetSelection() { return selection_text; } @@ -968,9 +949,10 @@ void ScreenInteractive::Draw(Component component) { #endif previous_frame_resized_ = resized; - Render(*this, document); + // Clear selection text. + selection_text = ""; - RefreshSelection(); + Render(*this, document); // Set cursor position for user using tools to insert CJK characters. { diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 228e714..82b29a6 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -42,6 +42,15 @@ class Text : public Node { continue; } screen.PixelAt(x, y).character = cell; + + if(screen.PixelAt(x, y).selectable == true) + { + if(screen.selection_region.Contain(x, y)) { + screen.PixelAt(x, y).inverted ^= true; + screen.selection_text += screen.PixelAt(x, y).character; + } + } + ++x; } } From 2b5701214ac69222b22aac4a9588a46691c0a81e Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 21:09:59 +0700 Subject: [PATCH 10/14] Dirty wrap around implementation --- src/ftxui/dom/text.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 82b29a6..c89b360 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -42,13 +42,18 @@ class Text : public Node { continue; } screen.PixelAt(x, y).character = cell; - if(screen.PixelAt(x, y).selectable == true) { if(screen.selection_region.Contain(x, y)) { screen.PixelAt(x, y).inverted ^= true; screen.selection_text += screen.PixelAt(x, y).character; } + else if(screen.selection_region.x_min <= x && screen.selection_region.x_max <= x && + screen.selection_region.y_min <= y && screen.selection_region.y_max > y) + { + screen.PixelAt(x, y).inverted ^= true; + screen.selection_text += screen.PixelAt(x, y).character; + } } ++x; From 37baddbb386820be04219922d195bc0c0c1d9c00 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 21:15:59 +0700 Subject: [PATCH 11/14] Wrap around both directions --- src/ftxui/dom/text.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index c89b360..08de931 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -41,19 +41,28 @@ class Text : public Node { if (cell == "\n") { continue; } - screen.PixelAt(x, y).character = cell; - if(screen.PixelAt(x, y).selectable == true) + Pixel ¤tPixel = screen.PixelAt(x, y); + currentPixel.character = cell; + + if(currentPixel.selectable == true) { if(screen.selection_region.Contain(x, y)) { - screen.PixelAt(x, y).inverted ^= true; - screen.selection_text += screen.PixelAt(x, y).character; + currentPixel.inverted ^= true; + screen.selection_text += currentPixel.character; } else if(screen.selection_region.x_min <= x && screen.selection_region.x_max <= x && screen.selection_region.y_min <= y && screen.selection_region.y_max > y) { - screen.PixelAt(x, y).inverted ^= true; - screen.selection_text += screen.PixelAt(x, y).character; + currentPixel.inverted ^= true; + screen.selection_text += currentPixel.character; } + else if(screen.selection_region.x_min >= x && screen.selection_region.x_max >= x && + screen.selection_region.y_min < y && screen.selection_region.y_max >= y) + { + currentPixel.inverted ^= true; + screen.selection_text += currentPixel.character; + } + } ++x; From bb73b8feb0fd25c70b94325333e6e8b73501b6bb Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 21:34:53 +0700 Subject: [PATCH 12/14] More intuitive selection of text --- src/ftxui/dom/text.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 08de931..1415191 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -50,14 +50,14 @@ class Text : public Node { currentPixel.inverted ^= true; screen.selection_text += currentPixel.character; } - else if(screen.selection_region.x_min <= x && screen.selection_region.x_max <= x && - screen.selection_region.y_min <= y && screen.selection_region.y_max > y) + else if((x >= screen.selection_region.x_min) && (x >= screen.selection_region.x_max) && + (y >= screen.selection_region.y_min) && (y < screen.selection_region.y_max)) { currentPixel.inverted ^= true; screen.selection_text += currentPixel.character; } - else if(screen.selection_region.x_min >= x && screen.selection_region.x_max >= x && - screen.selection_region.y_min < y && screen.selection_region.y_max >= y) + else if((x <= screen.selection_region.x_min) && (x <= screen.selection_region.x_max) && + (y > screen.selection_region.y_min) && (y <= screen.selection_region.y_max)) { currentPixel.inverted ^= true; screen.selection_text += currentPixel.character; From 810ae40b1485399730e1e14e84e359d6b1bf220c Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Thu, 31 Oct 2024 21:49:31 +0700 Subject: [PATCH 13/14] Reverse selection are now possible --- include/ftxui/screen/box.hpp | 1 + include/ftxui/screen/screen.hpp | 1 + src/ftxui/component/screen_interactive.cpp | 26 +++++++++++++--------- src/ftxui/screen/box.cpp | 18 +++++++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 3770803..8ad7d1a 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -15,6 +15,7 @@ struct Box { 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; diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 211ed59..2f9bf19 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -65,6 +65,7 @@ class Screen : public Image { bool selection_enabled = false; CapturedMouse selection_pending; + Box mouse_selection_region; Box selection_region; std::string selection_text; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 49cbee0..ee2591d 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -843,10 +843,12 @@ bool ScreenInteractive::HandleSelection(Event event) { return false; } selection_enabled = true; - selection_region.x_min = mouse.x; - selection_region.y_min = mouse.y; - selection_region.x_max = mouse.x; - selection_region.y_max = mouse.y; + 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(); return true; } @@ -855,18 +857,22 @@ bool ScreenInteractive::HandleSelection(Event event) { } if (mouse.motion == Mouse::Moved) { - selection_region.x_max = mouse.x; - selection_region.y_max = mouse.y; + mouse_selection_region.x_max = mouse.x; + mouse_selection_region.y_max = mouse.y; + + selection_region = mouse_selection_region.Clean(); return true; } if (mouse.motion == Mouse::Released) { - selection_region.x_max = mouse.x; - selection_region.y_max = mouse.y; + mouse_selection_region.x_max = mouse.x; + mouse_selection_region.y_max = mouse.y; selection_pending = nullptr; - if (selection_region.x_min == selection_region.x_max && - selection_region.y_min == selection_region.y_max) { + 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; } diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index e190305..3e5bc92 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -39,6 +39,24 @@ bool Box::Contain(int x, int y) const { y_max >= y; } +/// @return a copy of box with the x_min <= x_max and y_min <= y_max. +/// @ingroup screen +Box Box::Clean() const { + Box newBox = *this; + + if(newBox.x_min > newBox.x_max) + { + std::swap(newBox.x_min, newBox.x_max); + } + + if(newBox.y_min > newBox.y_max) + { + std::swap(newBox.y_min, newBox.y_max); + } + + return newBox; +} + /// @return whether the box is empty. /// @ingroup screen bool Box::IsEmpty() const { From 143d152e3d7b495cb396bc848921bf1d71971c0f Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Wed, 6 Nov 2024 18:53:08 +0700 Subject: [PATCH 14/14] Select only if the selection starts in my text widget --- include/ftxui/screen/box.hpp | 2 ++ src/ftxui/dom/text.cpp | 15 ++++++++++++++- src/ftxui/screen/box.cpp | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 8ad7d1a..4992bb5 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -11,6 +11,8 @@ 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; diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 1415191..e1b2c51 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -34,6 +34,17 @@ class Text : public Node { if (y > box_.y_max) { return; } + + // Get the selection start point + int selection_start_x = !screen.selection_region.isXInverted ? screen.selection_region.x_min : screen.selection_region.x_max; + int selection_start_y = !screen.selection_region.isYInverted ? screen.selection_region.y_min : screen.selection_region.y_max; + bool selectedWidget = false; + + if(box_.Contain(selection_start_x, selection_start_y)) + { + selectedWidget = true; + } + for (const auto& cell : Utf8ToGlyphs(text_)) { if (x > box_.x_max) { return; @@ -44,7 +55,7 @@ class Text : public Node { Pixel ¤tPixel = screen.PixelAt(x, y); currentPixel.character = cell; - if(currentPixel.selectable == true) + if((selectedWidget == true) && (currentPixel.selectable == true)) { if(screen.selection_region.Contain(x, y)) { currentPixel.inverted ^= true; @@ -53,12 +64,14 @@ class Text : public Node { else if((x >= screen.selection_region.x_min) && (x >= screen.selection_region.x_max) && (y >= screen.selection_region.y_min) && (y < screen.selection_region.y_max)) { + // Wrap around selection on the right currentPixel.inverted ^= true; screen.selection_text += currentPixel.character; } else if((x <= screen.selection_region.x_min) && (x <= screen.selection_region.x_max) && (y > screen.selection_region.y_min) && (y <= screen.selection_region.y_max)) { + // Wrap around selection on the left currentPixel.inverted ^= true; screen.selection_text += currentPixel.character; } diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index 3e5bc92..e82884a 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -47,11 +47,13 @@ Box Box::Clean() const { if(newBox.x_min > newBox.x_max) { std::swap(newBox.x_min, newBox.x_max); + newBox.isXInverted = true; } if(newBox.y_min > newBox.y_max) { std::swap(newBox.y_min, newBox.y_max); + newBox.isYInverted = true; } return newBox;