From 4e75cf9e0e6de5a211ad29b97b9124aeb3e45582 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 27 Aug 2024 15:33:25 +0700 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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; From d38f3d229ae1c44298e1a3054d6ac3b25ba21726 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Wed, 13 Nov 2024 22:05:04 +0100 Subject: [PATCH 15/20] Start the tree-aware selection. --- examples/component/selectable_input.cpp | 103 +++++++++--------- .../ftxui/component/screen_interactive.hpp | 9 +- include/ftxui/dom/node.hpp | 13 ++- include/ftxui/screen/box.hpp | 3 - include/ftxui/screen/screen.hpp | 6 - src/ftxui/component/screen_interactive.cpp | 54 +++------ src/ftxui/dom/hbox.cpp | 24 ++++ src/ftxui/dom/node.cpp | 29 ++++- src/ftxui/dom/text.cpp | 73 +++++++------ src/ftxui/dom/vbox.cpp | 23 ++++ src/ftxui/screen/box.cpp | 20 ---- src/ftxui/screen/screen.cpp | 3 +- 12 files changed, 195 insertions(+), 165 deletions(-) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp index 91dd577..0546cd9 100644 --- a/examples/component/selectable_input.cpp +++ b/examples/component/selectable_input.cpp @@ -10,62 +10,63 @@ #include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border #include "ftxui/util/ref.hpp" // for Ref +using namespace ftxui; + +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() { - 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"); + auto quit = Button("Quit", screen.ExitLoopClosure()); - // 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, - }); - - // 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); diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 6fd61c2..29d01e3 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -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 // for list #include // for shared_ptr #include // for vector @@ -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* selected); + + // Step 4: Draw this element. virtual void Render(Screen& screen); // Layout may not resolve within a single iteration for some elements. This @@ -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_; @@ -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 diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 4992bb5..3770803 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -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; diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 2f9bf19..39cc4ea 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -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); diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index ee2591d..1f856f1 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -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 @@ -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. { diff --git a/src/ftxui/dom/hbox.cpp b/src/ftxui/dom/hbox.cpp index 2053e84..b323414 100644 --- a/src/ftxui/dom/hbox.cpp +++ b/src/ftxui/dom/hbox.cpp @@ -64,6 +64,30 @@ class HBox : public Node { x = box.x_max + 1; } } + + void Selection(Box selection, std::vector* 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 diff --git a/src/ftxui/dom/node.cpp b/src/ftxui/dom/node.cpp index 5220335..23191ff 100644 --- a/src/ftxui/dom/node.cpp +++ b/src/ftxui/dom/node.cpp @@ -1,3 +1,4 @@ +#include // 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. @@ -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* 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) { @@ -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; @@ -73,11 +92,15 @@ void Render(Screen& screen, Node* node) { node->Check(&status); } - // Step 3: Draw the element. + // Step 3: Selection + std::vector 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(); } diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index e1b2c51..791c658 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -28,6 +28,29 @@ class Text : public Node { requirement_.min_y = 1; } + void Selection(Box selection, std::vector* selected) override { + 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; + + selection_start_ = xmin_satured ? box_.x_min : selection.x_min; + selection_end_ = xmax_satured ? box_.x_max : selection.x_max; + + has_selection = true; + + Box out; + out.x_min = selection_start_; + out.x_max = selection_end_; + out.y_min = box_.y_min; + out.y_max = box_.y_max; + selected->push_back(out); + } + void Render(Screen& screen) override { int x = box_.x_min; const int y = box_.y_min; @@ -35,55 +58,33 @@ class Text : public Node { 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; + break; } if (cell == "\n") { continue; } - Pixel ¤tPixel = screen.PixelAt(x, y); - currentPixel.character = cell; - - if((selectedWidget == true) && (currentPixel.selectable == true)) - { - if(screen.selection_region.Contain(x, y)) { - 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 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; - } - - } + screen.PixelAt(x, y).character = cell; ++x; } + + if (!has_selection) { + return; + } + + // Invert the selection + for(int x = selection_start_; x <= selection_end_; x++) { + screen.PixelAt(x, y).inverted = true; + } } private: std::string text_; + bool has_selection = false; + int selection_start_ = 0; + int selection_end_ = 10; }; class VText : public Node { diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index 28d885d..9a48f7c 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -64,6 +64,29 @@ class VBox : public Node { y = box.y_max + 1; } } + + void Selection(Box selection, std::vector* selected) override { + // If this Node box_ doesn't intersect with the selection, then no + // selection. + if (Box::Intersection(selection, box_).IsEmpty()) { + return; + } + + const bool ymin_satured = + selection.x_min < box_.x_min || selection.y_min < box_.y_min; + const bool ymax_satured = + selection.x_max > box_.x_max || selection.y_max > box_.y_max; + if (ymin_satured) { + selection.y_min = box_.y_min; + } + if (ymax_satured) { + selection.y_max = box_.y_max; + } + + for (auto& child : children_) { + child->Selection(selection, selected); + } + } }; } // namespace diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index e82884a..e190305 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -39,26 +39,6 @@ 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); - newBox.isXInverted = true; - } - - if(newBox.y_min > newBox.y_max) - { - std::swap(newBox.y_min, newBox.y_max); - newBox.isYInverted = true; - } - - return newBox; -} - /// @return whether the box is empty. /// @ingroup screen bool Box::IsEmpty() const { diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index b1c0945..be6f1d9 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -390,8 +390,7 @@ Screen Screen::Create(Dimensions dimension) { } Screen::Screen(int dimx, int dimy) : - Image{dimx, dimy}, - selection_text("") { + Image{dimx, dimy} { #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 46f7fbb8dc506884ebf5e4803acec270bb912204 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 26 Nov 2024 17:13:40 +0700 Subject: [PATCH 16/20] Easier to read HandleSelection --- src/ftxui/component/screen_interactive.cpp | 52 ++++++++++------------ 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 1f856f1..8cfa7e0 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -837,37 +837,31 @@ bool ScreenInteractive::HandleSelection(Event event) { return false; } - if (mouse.motion == Mouse::Pressed) { - selection_pending_ = CaptureMouse(); - if (!selection_pending_) { - return false; - } - 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(mouse.motion == Mouse::Pressed) { + selection_pending_ = CaptureMouse(); + if (!selection_pending_) { + return false; + } + 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; + } + else if((mouse.motion == Mouse::Moved) && (selection_pending_)) { + selection_box_.x_max = mouse.x; + selection_box_.y_max = mouse.y; + return true; + } + else if((mouse.motion == Mouse::Released) && (selection_pending_)) { + selection_box_.x_max = mouse.x; + selection_box_.y_max = mouse.y; + selection_pending_ = nullptr; + return true; } - if (!selection_pending_) { - return false; - } - - if (mouse.motion == Mouse::Moved) { - selection_box_.x_max = mouse.x; - selection_box_.y_max = mouse.y; - return true; - } - - if (mouse.motion != Mouse::Released) { - return false; - } - - selection_box_.x_max = mouse.x; - selection_box_.y_max = mouse.y; - selection_pending_ = nullptr; - return true; + return false; } // private From 26507ad5b97eb85bdb70df35a431d0971cb2b95a Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 26 Nov 2024 17:25:16 +0700 Subject: [PATCH 17/20] Corrected typo --- src/ftxui/dom/hbox.cpp | 8 ++++---- src/ftxui/dom/text.cpp | 8 ++++---- src/ftxui/dom/vbox.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ftxui/dom/hbox.cpp b/src/ftxui/dom/hbox.cpp index b323414..6676e37 100644 --- a/src/ftxui/dom/hbox.cpp +++ b/src/ftxui/dom/hbox.cpp @@ -72,15 +72,15 @@ class HBox : public Node { return; } - const bool xmin_satured = + const bool xmin_saturated = selection.y_min < box_.y_min || selection.x_min < box_.x_min; - const bool xmax_satured = + const bool xmax_saturated = selection.y_max > box_.y_max || selection.x_max > box_.x_max; - if (xmin_satured) { + if (xmin_saturated) { selection.x_min = box_.x_min; } - if (xmax_satured) { + if (xmax_saturated) { selection.x_max = box_.x_max; } diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 791c658..678c064 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -33,13 +33,13 @@ class Text : public Node { return; } - const bool xmin_satured = + const bool xmin_saturated = selection.y_min < box_.y_min || selection.x_min < box_.x_min; - const bool xmax_satured = + const bool xmax_saturated = selection.y_max > box_.y_max || selection.x_max > box_.x_max; - selection_start_ = xmin_satured ? box_.x_min : selection.x_min; - selection_end_ = xmax_satured ? box_.x_max : selection.x_max; + selection_start_ = xmin_saturated ? box_.x_min : selection.x_min; + selection_end_ = xmax_saturated ? box_.x_max : selection.x_max; has_selection = true; diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index 9a48f7c..361f5e2 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -72,14 +72,14 @@ class VBox : public Node { return; } - const bool ymin_satured = + const bool ymin_saturated = selection.x_min < box_.x_min || selection.y_min < box_.y_min; - const bool ymax_satured = + const bool ymax_saturated = selection.x_max > box_.x_max || selection.y_max > box_.y_max; - if (ymin_satured) { + if (ymin_saturated) { selection.y_min = box_.y_min; } - if (ymax_satured) { + if (ymax_saturated) { selection.y_max = box_.y_max; } From 9a94abc31decb3115cd37b68df79a3c32037c1ad Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 26 Nov 2024 21:39:29 +0700 Subject: [PATCH 18/20] We can reverse select --- src/ftxui/dom/node.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ftxui/dom/node.cpp b/src/ftxui/dom/node.cpp index 23191ff..6e0a12f 100644 --- a/src/ftxui/dom/node.cpp +++ b/src/ftxui/dom/node.cpp @@ -94,7 +94,21 @@ void Render(Screen& screen, Node* node, Box selection) { // Step 3: Selection std::vector selected; - node->Selection(selection, &selected); + + Box selectionCleaned = selection; + if(selection.x_min > selection.x_max) + { + selectionCleaned.x_min = selection.x_max; + selectionCleaned.x_max = selection.x_min; + } + + if(selection.y_min > selection.y_max) + { + selectionCleaned.y_min = selection.y_max; + selectionCleaned.y_max = selection.y_min; + } + + node->Selection(selectionCleaned, &selected); // Step 4: Draw the element. screen.stencil = box; From 907e1463859e1c6126cb372e90314b0d302ae3d3 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Tue, 26 Nov 2024 21:47:01 +0700 Subject: [PATCH 19/20] Correct the title highlighting of hbox grabing to top border --- src/ftxui/dom/text.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index 678c064..f5188d5 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -67,17 +67,14 @@ class Text : public Node { } screen.PixelAt(x, y).character = cell; + if (has_selection) { + if((x >= selection_start_) && (x <= selection_end_)) { + screen.PixelAt(x, y).inverted = true; + } + } + ++x; } - - if (!has_selection) { - return; - } - - // Invert the selection - for(int x = selection_start_; x <= selection_end_; x++) { - screen.PixelAt(x, y).inverted = true; - } } private: From 52e92a99be3ea61b6d5cc501133a12c92062fd61 Mon Sep 17 00:00:00 2001 From: Clement Roblot Date: Wed, 27 Nov 2024 00:29:08 +0700 Subject: [PATCH 20/20] More robust saturation mechanism --- src/ftxui/dom/hbox.cpp | 4 ++-- src/ftxui/dom/vbox.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ftxui/dom/hbox.cpp b/src/ftxui/dom/hbox.cpp index 6676e37..00bc508 100644 --- a/src/ftxui/dom/hbox.cpp +++ b/src/ftxui/dom/hbox.cpp @@ -78,10 +78,10 @@ class HBox : public Node { selection.y_max > box_.y_max || selection.x_max > box_.x_max; if (xmin_saturated) { - selection.x_min = box_.x_min; + selection.x_min = std::min(box_.x_min, selection.x_min); } if (xmax_saturated) { - selection.x_max = box_.x_max; + selection.x_max = std::max(box_.x_max, selection.x_max); } for (auto& child : children_) { diff --git a/src/ftxui/dom/vbox.cpp b/src/ftxui/dom/vbox.cpp index 361f5e2..761a8b3 100644 --- a/src/ftxui/dom/vbox.cpp +++ b/src/ftxui/dom/vbox.cpp @@ -76,11 +76,12 @@ class VBox : public Node { selection.x_min < box_.x_min || selection.y_min < box_.y_min; const bool ymax_saturated = selection.x_max > box_.x_max || selection.y_max > box_.y_max; + if (ymin_saturated) { - selection.y_min = box_.y_min; + selection.y_min = std::min(box_.y_min, selection.y_min); } if (ymax_saturated) { - selection.y_max = box_.y_max; + selection.y_max = std::max(box_.y_max, selection.y_max); } for (auto& child : children_) {