diff --git a/CMakeLists.txt b/CMakeLists.txt index c2e800ee..2c121b1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ add_library(dom include/ftxui/dom/selection.hpp include/ftxui/dom/take_any_args.hpp src/ftxui/dom/automerge.cpp + src/ftxui/dom/selection_style.cpp src/ftxui/dom/blink.cpp src/ftxui/dom/bold.cpp src/ftxui/dom/border.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index b55bdc62..62318b03 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -38,7 +38,7 @@ example(radiobox) example(radiobox_in_frame) example(renderer) example(resizable_split) -example(selectable_input) +example(selection) example(scrollbar) example(slider) example(slider_direction) diff --git a/examples/component/selectable_input.cpp b/examples/component/selectable_input.cpp deleted file mode 100644 index ac2d41fb..00000000 --- a/examples/component/selectable_input.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// 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 char_traits, operator+, string, basic_string - -#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 - -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() { - auto screen = ScreenInteractive::TerminalOutput(); - int selectionChangeCounter = 0; - std::string selection = ""; - - auto quit = Button("Quit", screen.ExitLoopClosure()); - - // The components: - auto renderer = Renderer(quit, [&] { - return vbox({ - text("Select changed: " + std::to_string(selectionChangeCounter) + " times"), - text("Currently selected: " + selection), - window(text("Horizontal split"), hbox({ - LoremIpsum(), - separator(), - LoremIpsum(), - separator(), - LoremIpsum(), - })), - window(text("Vertical split"), vbox({ - LoremIpsum(), - separator(), - LoremIpsum() | unselectable, - 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.setSelectionOptions({ - .transform = [](Pixel& pixel) { - pixel.underlined_double = true; - }, - .on_change = [&] { - selectionChangeCounter++; - selection = screen.GetSelectedContent(renderer); - } - }); - - screen.Loop(renderer); -} diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 14daf1f2..2e5bb3f1 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -71,7 +71,6 @@ class ScreenInteractive : public Screen { // Selection API. std::string GetSelectedContent(Component component); - void setSelectionOptions(SelectionOption option); private: void ExitNow(); @@ -137,14 +136,12 @@ class ScreenInteractive : public Screen { int cursor_reset_shape_ = 1; // Selection API: - bool selection_enabled_ = false; CapturedMouse selection_pending_; int selection_start_x_ = 0; int selection_start_y_ = 0; int selection_end_x_ = 0; int selection_end_y_ = 0; bool selection_changed = false; - SelectionOption selection_options_ = SelectionOption::Simple(); friend class Loop; diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index bd086def..fa16080e 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -113,7 +113,11 @@ Decorator focusPositionRelative(float x, float y); Element automerge(Element child); Decorator hyperlink(std::string link); Element hyperlink(std::string link, Element child); -Element unselectable(Element child); +Element selectionStyleReset(Element); +Decorator selectionColor(Color foreground); +Decorator selectionBackgroundColor(Color foreground); +Decorator selectionForegroundColor(Color foreground); +Decorator selectionStyle(std::function style); // --- Layout is // Horizontal, Vertical or stacked set of elements. diff --git a/include/ftxui/dom/selection.hpp b/include/ftxui/dom/selection.hpp index 7931b87e..76cc7e4b 100644 --- a/include/ftxui/dom/selection.hpp +++ b/include/ftxui/dom/selection.hpp @@ -7,34 +7,16 @@ #include -#include "ftxui/screen/box.hpp" // for Box +#include "ftxui/screen/box.hpp" // for Box #include "ftxui/screen/pixel.hpp" // for Pixel namespace ftxui { -/// @brief Option for the selection of content. -/// @ingroup component -struct SelectionOption { - /// @brief Selection is simply inverted: - static SelectionOption Simple(); - - // Style: - std::function transform = [](Pixel& pixel) { - - pixel.inverted = true; - }; - - // Observers: - /// Called when the selection changed. - std::function on_change = [] {}; -}; - /// @brief Represent a selection in the terminal. class Selection { public: - Selection(int start_x, int start_y, int end_x, int end_y, SelectionOption option = SelectionOption::Simple()); + Selection(int start_x, int start_y, int end_x, int end_y); const Box& GetBox() const; - const SelectionOption& GetOption() const; Selection SaturateHorizontal(Box box); Selection SaturateVertical(Box box); @@ -46,7 +28,6 @@ class Selection { const int end_x_; const int end_y_; const Box box_; - const SelectionOption option; }; } // namespace ftxui diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index 51b83a03..aa2f4f8c 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -4,12 +4,14 @@ #ifndef FTXUI_SCREEN_SCREEN_HPP #define FTXUI_SCREEN_SCREEN_HPP -#include // for uint8_t -#include // for string, basic_string, allocator -#include // for vector +#include // for uint8_t +#include // for function +#include // for string, basic_string, allocator +#include // for vector #include "ftxui/screen/image.hpp" // for Pixel, Image #include "ftxui/screen/terminal.hpp" // for Dimensions +#include "ftxui/util/autoreset.hpp" // for AutoReset namespace ftxui { @@ -67,9 +69,18 @@ class Screen : public Image { uint8_t RegisterHyperlink(const std::string& link); const std::string& Hyperlink(uint8_t id) const; + using SelectionStyle = std::function; + const SelectionStyle& GetSelectionStyle() const; + void SetSelectionStyle(SelectionStyle decorator); + protected: Cursor cursor_; std::vector hyperlinks_ = {""}; + + // The current selection style. This is overridden by various dom elements. + SelectionStyle selection_style_ = [](Pixel& pixel) { + pixel.inverted ^= true; + }; }; } // namespace ftxui diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index ebc7d117..49b158e1 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -577,19 +577,13 @@ void ScreenInteractive::ForceHandleCtrlZ(bool force) { } /// @brief Returns the content of the current selection -std::string ScreenInteractive::GetSelectedContent(Component component) -{ +std::string ScreenInteractive::GetSelectedContent(Component component) { Selection selection(selection_start_x_, selection_start_y_, // - selection_end_x_, selection_end_y_, selection_options_); + selection_end_x_, selection_end_y_); return GetNodeSelectedContent(*this, component->Render().get(), selection); } -void ScreenInteractive::setSelectionOptions(SelectionOption option) -{ - selection_options_ = std::move(option); -} - /// @brief Return the currently active screen, or null if none. // static ScreenInteractive* ScreenInteractive::Active() { @@ -966,14 +960,9 @@ void ScreenInteractive::Draw(Component component) { previous_frame_resized_ = resized; Selection selection(selection_start_x_, selection_start_y_, // - selection_end_x_, selection_end_y_, selection_options_); + selection_end_x_, selection_end_y_); Render(*this, document.get(), selection); - if(selection_changed == true) - { - selection_options_.on_change(); - } - // Set cursor position for user using tools to insert CJK characters. { const int dx = dimx_ - 1 - cursor_.x + int(dimx_ != terminal.dimx); diff --git a/src/ftxui/dom/selection.cpp b/src/ftxui/dom/selection.cpp index 0bcccb25..6c51e287 100644 --- a/src/ftxui/dom/selection.cpp +++ b/src/ftxui/dom/selection.cpp @@ -26,12 +26,11 @@ class Unselectable : public NodeDecorator { /// @param start_y The y coordinate of the start of the selection. /// @param end_x The x coordinate of the end of the selection. /// @param end_y The y coordinate of the end of the selection. -Selection::Selection(int start_x, int start_y, int end_x, int end_y, SelectionOption option) +Selection::Selection(int start_x, int start_y, int end_x, int end_y) : start_x_(start_x), start_y_(start_y), end_x_(end_x), end_y_(end_y), - option(option), box_{ std::min(start_x, end_x), std::max(start_x, end_x), @@ -45,12 +44,6 @@ const Box& Selection::GetBox() const { return box_; } -/// @brief Get the options of the selection. -/// @return The options of the selection. -const SelectionOption& Selection::GetOption() const { - return option; -} - /// @brief Saturate the selection to be inside the box. /// This is called by `hbox` to propagate the selection to its children. /// @param box The box to saturate the selection in. @@ -84,7 +77,7 @@ Selection Selection::SaturateHorizontal(Box box) { end_y = box.y_min; } } - return Selection(start_x, start_y, end_x, end_y, option); + return Selection(start_x, start_y, end_x, end_y); } /// @brief Saturate the selection to be inside the box. @@ -121,26 +114,7 @@ Selection Selection::SaturateVertical(Box box) { end_y = box.y_min; } } - return Selection(start_x, start_y, end_x, end_y, option); -} - -/// @brief Add a filter that will invert the foreground and the background -/// colors. -/// @ingroup dom -Element unselectable(Element child) { - return std::make_shared(std::move(child)); -} - -/// @brief Create a SelectionOption, inverting the selection. -/// @ingroup component -// static -SelectionOption SelectionOption::Simple() { - SelectionOption option; - option.transform = [](Pixel& pixel) { - - pixel.inverted = true; - }; - return option; + return Selection(start_x, start_y, end_x, end_y); } } // namespace ftxui diff --git a/src/ftxui/dom/selection_test.cpp b/src/ftxui/dom/selection_test.cpp index 3a2d9959..1ec4948b 100644 --- a/src/ftxui/dom/selection_test.cpp +++ b/src/ftxui/dom/selection_test.cpp @@ -4,15 +4,14 @@ #include #include // for raise, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM -#include "ftxui/dom/elements.hpp" // for text -#include "ftxui/dom/node.hpp" // for Render -#include "ftxui/screen/screen.hpp" // for Screen -#include "ftxui/component/event.hpp" // for Event -#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released -#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical -#include "ftxui/component/screen_interactive.hpp" +#include "ftxui/component/component.hpp" // for Input, Renderer, Vertical +#include "ftxui/component/event.hpp" // for Event #include "ftxui/component/loop.hpp" // for Loop - +#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released +#include "ftxui/component/screen_interactive.hpp" +#include "ftxui/dom/elements.hpp" // for text +#include "ftxui/dom/node.hpp" // for Render +#include "ftxui/screen/screen.hpp" // for Screen // NOLINTBEGIN namespace ftxui { @@ -43,12 +42,8 @@ Event MouseReleased(int x, int y) { } } // namespace - TEST(SelectionTest, DefaultSelection) { - - auto component = Renderer([&] { - return text("Lorem ipsum dolor"); - }); + auto component = Renderer([&] { return text("Lorem ipsum dolor"); }); auto screen = ScreenInteractive::FixedSize(20, 1); @@ -64,24 +59,12 @@ TEST(SelectionTest, DefaultSelection) { } TEST(SelectionTest, CallbackSelection) { - int selectionChangeCounter = 0; - auto component = Renderer([&] { - return text("Lorem ipsum dolor"); - }); + auto component = Renderer([&] { return text("Lorem ipsum dolor"); }); auto screen = ScreenInteractive::FixedSize(20, 1); - screen.setSelectionOptions({ - .transform = [](Pixel& pixel) { - pixel.underlined_double = true; - }, - .on_change = [&] { - selectionChangeCounter++; - } - }); - Loop loop(&screen, component); loop.RunOnce(); @@ -96,34 +79,22 @@ TEST(SelectionTest, CallbackSelection) { } TEST(SelectionTest, StyleSelection) { - int selectionChangeCounter = 0; - auto component = Renderer([&] { - return text("Lorem ipsum dolor"); - }); + auto component = Renderer([&] { return text("Lorem ipsum dolor"); }); auto screen = ScreenInteractive::FixedSize(20, 1); - Selection selection(2, 0, 9, 0, { - .transform = [](Pixel& pixel) { - pixel.underlined_double = true; - }, - .on_change = [&] { - } - }); + Selection selection(2, 0, 9, 0); Render(screen, component->Render().get(), selection); EXPECT_EQ(screen.ToString(), "Lo\x1B[21mrem ipsu\x1B[24mm dolor "); } - TEST(SelectionTest, VBoxSelection) { - auto component = Renderer([&] { - return vbox({ text("Lorem ipsum dolor"), - text("Ut enim ad minim")}); + return vbox({text("Lorem ipsum dolor"), text("Ut enim ad minim")}); }); auto screen = ScreenInteractive::FixedSize(20, 2); @@ -132,15 +103,15 @@ TEST(SelectionTest, VBoxSelection) { Render(screen, component->Render().get(), selection); - EXPECT_EQ(screen.ToString(), "Lo\x1B[7mrem ipsum dolor\x1B[27m \r\n\x1B[7mUt \x1B[27menim ad minim "); + EXPECT_EQ(screen.ToString(), + "Lo\x1B[7mrem ipsum dolor\x1B[27m \r\n\x1B[7mUt \x1B[27menim ad " + "minim "); } TEST(SelectionTest, VBoxSaturatedSelection) { - auto component = Renderer([&] { - return vbox({ text("Lorem ipsum dolor"), - text("Ut enim ad minim"), - text("Duis aute irure")}); + return vbox({text("Lorem ipsum dolor"), text("Ut enim ad minim"), + text("Duis aute irure")}); }); auto screen = ScreenInteractive::FixedSize(20, 3); @@ -149,15 +120,14 @@ TEST(SelectionTest, VBoxSaturatedSelection) { Render(screen, component->Render().get(), selection); - EXPECT_EQ(screen.ToString(), "Lo\x1B[7mrem ipsum dolor\x1B[27m \r\n\x1B[7mUt enim ad minim\x1B[27m \r\n\x1B[7mDui\x1B[27ms aute irure "); + EXPECT_EQ(screen.ToString(), + "Lo\x1B[7mrem ipsum dolor\x1B[27m \r\n\x1B[7mUt enim ad " + "minim\x1B[27m \r\n\x1B[7mDui\x1B[27ms aute irure "); } - TEST(SelectionTest, HBoxSelection) { - auto component = Renderer([&] { - return hbox({ text("Lorem ipsum dolor"), - text("Ut enim ad minim")}); + return hbox({text("Lorem ipsum dolor"), text("Ut enim ad minim")}); }); auto screen = ScreenInteractive::FixedSize(40, 1); @@ -166,15 +136,14 @@ TEST(SelectionTest, HBoxSelection) { Render(screen, component->Render().get(), selection); - EXPECT_EQ(screen.ToString(), "Lo\x1B[7mrem ipsum dolorUt e\x1B[27mnim ad minim "); + EXPECT_EQ(screen.ToString(), + "Lo\x1B[7mrem ipsum dolorUt e\x1B[27mnim ad minim "); } TEST(SelectionTest, HBoxSaturatedSelection) { - auto component = Renderer([&] { - return hbox({ text("Lorem ipsum dolor"), - text("Ut enim ad minim"), - text("Duis aute irure")}); + return hbox({text("Lorem ipsum dolor"), text("Ut enim ad minim"), + text("Duis aute irure")}); }); auto screen = ScreenInteractive::FixedSize(60, 1); @@ -183,7 +152,9 @@ TEST(SelectionTest, HBoxSaturatedSelection) { Render(screen, component->Render().get(), selection); - EXPECT_EQ(screen.ToString(), "Lo\x1B[7mrem ipsum dolorUt enim ad minimDui\x1B[27ms aute irure "); + EXPECT_EQ(screen.ToString(), + "Lo\x1B[7mrem ipsum dolorUt enim ad minimDui\x1B[27ms aute irure " + " "); } } // namespace ftxui diff --git a/src/ftxui/dom/text.cpp b/src/ftxui/dom/text.cpp index c7bfbc56..2a16aaa5 100644 --- a/src/ftxui/dom/text.cpp +++ b/src/ftxui/dom/text.cpp @@ -39,8 +39,6 @@ class Text : public Node { has_selection = true; selection_start_ = selection_saturated.GetBox().x_min; selection_end_ = selection_saturated.GetBox().x_max; - - selectionTransform = selection.GetOption().transform; } void Render(Screen& screen) override { @@ -61,6 +59,7 @@ class Text : public Node { screen.PixelAt(x, y).character = cell; if (has_selection) { + auto selectionTransform = screen.GetSelectionStyle(); if ((x >= selection_start_) && (x <= selection_end_)) { selectionTransform(screen.PixelAt(x, y)); } diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 7bd64e29..beb3870a 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -544,4 +544,16 @@ const std::string& Screen::Hyperlink(std::uint8_t id) const { return hyperlinks_[id]; } +/// @brief Return the current selection style. +/// @see SetSelectionStyle +const Screen::SelectionStyle& Screen::GetSelectionStyle() const { + return selection_style_; +} + +/// @brief Set the current selection style. +/// @see GetSelectionStyle +void Screen::SetSelectionStyle(SelectionStyle decorator) { + selection_style_ = decorator; +} + } // namespace ftxui