From 455998d759b6a5cd3e0a8f5d80865387222d0762 Mon Sep 17 00:00:00 2001 From: Arthur Sonzogni Date: Sun, 25 Jun 2023 17:22:05 +0200 Subject: [PATCH] Remove Ref and add new interfaces. (#686) 1. Stop taking Ref in Component constructors. Instead, use the XxxOption directly. Passing by copy avoid problems developers had where one was shared in between multiple component, causing issues. 2. Add variants of most component constructors taking a struct only. This replaces: https://github.com/ArthurSonzogni/FTXUI/pull/670 This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/426 --- .clang-tidy | 3 + CHANGELOG.md | 13 + cmake/ftxui_test.cmake | 4 +- examples/component/button_in_frame.cpp | 7 +- examples/component/menu.cpp | 2 +- examples/component/menu2.cpp | 4 +- examples/component/menu_style.cpp | 28 +- include/ftxui/component/component.hpp | 29 +- include/ftxui/component/component_options.hpp | 20 +- include/ftxui/screen/screen.hpp | 2 +- include/ftxui/util/ref.hpp | 52 ++- src/ftxui/component/button.cpp | 273 +++++++------ src/ftxui/component/checkbox.cpp | 37 +- src/ftxui/component/collapsible.cpp | 3 +- src/ftxui/component/component_options.cpp | 12 +- src/ftxui/component/input.cpp | 266 +++++++------ src/ftxui/component/input_test.cpp | 375 +++++++++--------- src/ftxui/component/menu.cpp | 301 ++++++++------ src/ftxui/component/menu_test.cpp | 30 +- src/ftxui/component/radiobox.cpp | 79 ++-- src/ftxui/component/slider.cpp | 33 +- src/ftxui/component/toggle_test.cpp | 4 +- src/ftxui/dom/canvas.cpp | 2 + src/ftxui/dom/hyperlink.cpp | 7 +- src/ftxui/screen/screen.cpp | 5 +- src/ftxui/screen/string.cpp | 20 +- 26 files changed, 918 insertions(+), 693 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index cf1723d..d775961 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,6 +1,8 @@ --- Checks: "*, + -*-magic-numbers, -*-narrowing-conversions + -*-unnecessary-value-param, -*-uppercase-literal-suffix, -abseil-*, -altera-*, @@ -9,6 +11,7 @@ Checks: "*, -cppcoreguidelines-non-private-member-variables-in-classes, -fuchsia-*, -google-*, + -hicpp-signed-bitwise, -llvm*, -misc-no-recursion, -misc-non-private-member-variables-in-classes, diff --git a/CHANGELOG.md b/CHANGELOG.md index 200b7a1..f9f0e34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,23 @@ current (development) - Breaking: GaugeDirection enum is renamed Direction - Breaking: Direction enum is renamed WidthOrHeight - Breaking: Remove `ComponentBase` copy constructor/assignment. +- Breaking: MenuOption::entries is renamed MenuOption::entries_option. +- Breaking: Ref becomes XxxOption in component constructors. - Feature: `ResizeableSplit` now support arbitrary element as a separator. - Feature: `input` is now supporting multiple lines. - Feature: `input` style is now customizeable. - Bugfix: Support F1-F5 from OS terminal. +- Feature: Add struct based constructor: + ```cpp + Component Button(ButtonOption options); + Component Checkbox(CheckboxOption options); + Component Input(InputOption options); + Component Menu(MenuOption options); + Component MenuEntry(MenuEntryOption options); + Component Radiobox(RadioboxOption options); + Component Slider(SliderOption options); + Component ResizableSplit(ResizableSplitOption options); + ``` ### Dom - Feature: Add `hyperlink` decorator. For instance: diff --git a/cmake/ftxui_test.cmake b/cmake/ftxui_test.cmake index 4c1b15a..71caa69 100644 --- a/cmake/ftxui_test.cmake +++ b/cmake/ftxui_test.cmake @@ -69,4 +69,6 @@ gtest_discover_tests(ftxui-tests DISCOVERY_TIMEOUT 600 ) -set(CMAKE_CTEST_ARGUMENTS "--rerun-failed --output-on-failure") +#set(CMAKE_CTEST_ARGUMENTS "--rerun-failed --output-on-failure") +#set_tests_properties(gen_init_queries PROPERTIES FIXTURES_SETUP f_init_queries) +#set_tests_properties(test PROPERTIES FIXTURES_REQUIRED f_init_queries) diff --git a/examples/component/button_in_frame.cpp b/examples/component/button_in_frame.cpp index 44034d6..26ba622 100644 --- a/examples/component/button_in_frame.cpp +++ b/examples/component/button_in_frame.cpp @@ -15,13 +15,12 @@ int main() { int counter = 0; auto on_click = [&] { counter++; }; - auto button_style = ButtonOption::Animated(Color::Default, Color::GrayDark, - Color::Default, Color::White); + auto style = ButtonOption::Animated(Color::Default, Color::GrayDark, + Color::Default, Color::White); auto container = Container::Vertical({}); for (int i = 0; i < 30; ++i) { - auto button = - Button("Button " + std::to_string(i), on_click, &button_style); + auto button = Button("Button " + std::to_string(i), on_click, style); container->Add(button); } diff --git a/examples/component/menu.cpp b/examples/component/menu.cpp index a0d03c6..0794691 100644 --- a/examples/component/menu.cpp +++ b/examples/component/menu.cpp @@ -21,7 +21,7 @@ int main() { MenuOption option; option.on_enter = screen.ExitLoopClosure(); - auto menu = Menu(&entries, &selected, &option); + auto menu = Menu(&entries, &selected, option); screen.Loop(menu); diff --git a/examples/component/menu2.cpp b/examples/component/menu2.cpp index 88713df..9854f80 100644 --- a/examples/component/menu2.cpp +++ b/examples/component/menu2.cpp @@ -27,9 +27,9 @@ int main() { int left_menu_selected = 0; int right_menu_selected = 0; Component left_menu_ = - Menu(&left_menu_entries, &left_menu_selected, &menu_option); + Menu(&left_menu_entries, &left_menu_selected, menu_option); Component right_menu_ = - Menu(&right_menu_entries, &right_menu_selected, &menu_option); + Menu(&right_menu_entries, &right_menu_selected, menu_option); Component container = Container::Horizontal({ left_menu_, diff --git a/examples/component/menu_style.cpp b/examples/component/menu_style.cpp index b792417..b9fd71a 100644 --- a/examples/component/menu_style.cpp +++ b/examples/component/menu_style.cpp @@ -110,7 +110,7 @@ int main() { Component VMenu1(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { state.label = (state.active ? "> " : " ") + state.label; Element e = text(state.label); if (state.focused) @@ -124,7 +124,7 @@ Component VMenu1(std::vector* entries, int* selected) { Component VMenu2(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { state.label += (state.active ? " <" : " "); Element e = hbox(filler(), text(state.label)); if (state.focused) @@ -138,7 +138,7 @@ Component VMenu2(std::vector* entries, int* selected) { Component VMenu3(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { Element e = state.active ? text("[" + state.label + "]") : text(" " + state.label + " "); if (state.focused) @@ -155,7 +155,7 @@ Component VMenu3(std::vector* entries, int* selected) { Component VMenu4(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { if (state.active && state.focused) { return text(state.label) | color(Color::Yellow) | bgcolor(Color::Black) | bold; @@ -175,7 +175,7 @@ Component VMenu4(std::vector* entries, int* selected) { Component VMenu5(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { auto element = text(state.label); if (state.active && state.focused) { return element | borderDouble; @@ -201,19 +201,19 @@ Component VMenu6(std::vector* entries, int* selected) { Component VMenu7(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.animated_colors.foreground.enabled = true; - option.entries.animated_colors.background.enabled = true; - option.entries.animated_colors.background.active = Color::Red; - option.entries.animated_colors.background.inactive = Color::Black; - option.entries.animated_colors.foreground.active = Color::White; - option.entries.animated_colors.foreground.inactive = Color::Red; + option.entries_option.animated_colors.foreground.enabled = true; + option.entries_option.animated_colors.background.enabled = true; + option.entries_option.animated_colors.background.active = Color::Red; + option.entries_option.animated_colors.background.inactive = Color::Black; + option.entries_option.animated_colors.foreground.active = Color::White; + option.entries_option.animated_colors.foreground.inactive = Color::Red; return Menu(entries, selected, option); } Component VMenu8(std::vector* entries, int* selected) { auto option = MenuOption::Vertical(); - option.entries.animated_colors.foreground.Set(Color::Red, Color::White, - std::chrono::milliseconds(500)); + option.entries_option.animated_colors.foreground.Set( + Color::Red, Color::White, std::chrono::milliseconds(500)); return Menu(entries, selected, option); } @@ -240,7 +240,7 @@ Component HMenu5(std::vector* entries, int* selected) { auto option = MenuOption::HorizontalAnimated(); option.underline.SetAnimation(std::chrono::milliseconds(1500), animation::easing::ElasticOut); - option.entries.transform = [](EntryState state) { + option.entries_option.transform = [](EntryState state) { Element e = text(state.label) | hcenter | flex; if (state.active && state.focused) e = e | bold; diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index a9289ae..1842bf4 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -40,37 +40,42 @@ Component Vertical(Components children, int* selector); Component Horizontal(Components children); Component Horizontal(Components children, int* selector); Component Tab(Components children, int* selector); - } // namespace Container +Component Button(ButtonOption options); Component Button(ConstStringRef label, std::function on_click, - Ref = ButtonOption::Simple()); + ButtonOption options = ButtonOption::Simple()); +Component Checkbox(CheckboxOption options); Component Checkbox(ConstStringRef label, bool* checked, - Ref option = CheckboxOption::Simple()); + CheckboxOption options = CheckboxOption::Simple()); -Component Input(StringRef content, Ref option = {}); +Component Input(InputOption options = {}); +Component Input(StringRef content, InputOption options = {}); Component Input(StringRef content, StringRef placeholder, - Ref option = {}); + InputOption options = {}); +Component Menu(MenuOption options); Component Menu(ConstStringListRef entries, int* selected_, - Ref = MenuOption::Vertical()); -Component MenuEntry(ConstStringRef label, Ref = {}); - -Component Dropdown(ConstStringListRef entries, int* selected); + MenuOption options = MenuOption::Vertical()); +Component MenuEntry(MenuEntryOption options); +Component MenuEntry(ConstStringRef label, MenuEntryOption options = {}); +Component Radiobox(RadioboxOption options); Component Radiobox(ConstStringListRef entries, int* selected_, - Ref option = {}); + RadioboxOption options = {}); + +Component Dropdown(ConstStringListRef entries, int* selected); Component Toggle(ConstStringListRef entries, int* selected); // General slider constructor: template -Component Slider(SliderOption options = {}); +Component Slider(SliderOption options); // Shorthand without the `SliderOption` constructor: Component Slider(ConstStringRef label, @@ -89,11 +94,11 @@ Component Slider(ConstStringRef label, ConstRef max = 100l, ConstRef increment = 5l); +Component ResizableSplit(ResizableSplitOption options); Component ResizableSplitLeft(Component main, Component back, int* main_size); Component ResizableSplitRight(Component main, Component back, int* main_size); Component ResizableSplitTop(Component main, Component back, int* main_size); Component ResizableSplitBottom(Component main, Component back, int* main_size); -Component ResizableSplit(ResizableSplitOption options); Component Renderer(Component child, std::function); Component Renderer(std::function); diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index c5b330f..0967306 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -72,6 +72,7 @@ struct AnimatedColorsOption { /// @brief Option for the MenuEntry component. /// @ingroup component struct MenuEntryOption { + ConstStringRef label = "MenuEntry"; std::function transform; AnimatedColorsOption animated_colors; }; @@ -86,9 +87,12 @@ struct MenuOption { static MenuOption VerticalAnimated(); static MenuOption Toggle(); + ConstStringListRef entries; ///> The list of entries. + Ref selected = 0; ///> The index of the selected entry. + // Style: UnderlineOption underline; - MenuEntryOption entries; + MenuEntryOption entries_option; Direction direction = Direction::Down; std::function elements_prefix; std::function elements_infix; @@ -115,6 +119,9 @@ struct ButtonOption { Color background_active, Color foreground_active); + ConstStringRef label = "Button"; + std::function on_click = [] {}; + // Style: std::function transform; AnimatedColorsOption animated_colors; @@ -126,6 +133,10 @@ struct CheckboxOption { // Standard constructors: static CheckboxOption Simple(); + ConstStringRef label = "Checkbox"; + + Ref checked = false; + // Style: std::function transform; @@ -153,6 +164,9 @@ struct InputOption { /// @brief A white on black style with high margins: static InputOption Spacious(); + /// The content of the input. + StringRef content = ""; + /// The content of the input when it's empty. StringRef placeholder = ""; @@ -176,6 +190,10 @@ struct RadioboxOption { // Standard constructors: static RadioboxOption Simple(); + // Content: + ConstStringListRef entries; + Ref selected = 0; + // Style: std::function transform; diff --git a/include/ftxui/screen/screen.hpp b/include/ftxui/screen/screen.hpp index f73557c..68cb331 100644 --- a/include/ftxui/screen/screen.hpp +++ b/include/ftxui/screen/screen.hpp @@ -106,7 +106,7 @@ class Screen { // 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(std::string link); + uint8_t RegisterHyperlink(const std::string& link); const std::string& Hyperlink(uint8_t id) const; Box stencil; diff --git a/include/ftxui/util/ref.hpp b/include/ftxui/util/ref.hpp index c650bc4..e47743f 100644 --- a/include/ftxui/util/ref.hpp +++ b/include/ftxui/util/ref.hpp @@ -13,6 +13,12 @@ class ConstRef { ConstRef() {} ConstRef(T t) : owned_(t) {} ConstRef(const T* t) : address_(t) {} + ConstRef(const ConstRef& t) : owned_(t.owned_), address_(t.address_) {} + ConstRef& operator=(const ConstRef& t) { + owned_ = t.owned_; + address_ = t.address_; + return *this; + } const T& operator*() const { return address_ ? *address_ : owned_; } const T& operator()() const { return address_ ? *address_ : owned_; } const T* operator->() const { return address_ ? address_ : &owned_; } @@ -30,6 +36,12 @@ class Ref { Ref(const T& t) : owned_(t) {} Ref(T&& t) : owned_(std::forward(t)) {} Ref(T* t) : owned_(), address_(t) {} + Ref(const Ref& t) : owned_(t.owned_), address_(t.address_) {} + Ref& operator=(const Ref& t) { + owned_ = t.owned_; + address_ = t.address_; + return *this; + } T& operator*() { return address_ ? *address_ : owned_; } T& operator()() { return address_ ? *address_ : owned_; } T* operator->() { return address_ ? address_ : &owned_; } @@ -47,6 +59,12 @@ class StringRef { StringRef(std::string ref) : owned_(std::move(ref)) {} StringRef(const wchar_t* ref) : StringRef(to_string(std::wstring(ref))) {} StringRef(const char* ref) : StringRef(std::string(ref)) {} + StringRef(const StringRef& t) : owned_(t.owned_), address_(t.address_) {} + StringRef& operator=(const StringRef& t) { + owned_ = t.owned_; + address_ = t.address_; + return *this; + } std::string& operator*() { return address_ ? *address_ : owned_; } std::string& operator()() { return address_ ? *address_ : owned_; } std::string* operator->() { return address_ ? address_ : &owned_; } @@ -67,6 +85,18 @@ class ConstStringRef { ConstStringRef(const wchar_t* ref) : ConstStringRef(std::wstring(ref)) {} ConstStringRef(const char* ref) : ConstStringRef(to_wstring(std::string(ref))) {} + ConstStringRef(const ConstStringRef& t) + : owned_(t.owned_), address_(t.address_) {} + ConstStringRef& operator=(const ConstStringRef& t) { + owned_ = t.owned_; + address_ = t.address_; + return *this; + } + ConstStringRef& operator=(ConstStringRef&& t) { + owned_ = std::move(t.owned_); + address_ = t.address_; + return *this; + } const std::string& operator()() const { return address_ ? *address_ : owned_; } @@ -76,19 +106,35 @@ class ConstStringRef { } private: - const std::string owned_; + std::string owned_; const std::string* address_ = nullptr; }; /// @brief An adapter. Reference a list of strings. class ConstStringListRef { public: + ConstStringListRef() = default; ConstStringListRef(const std::vector* ref) : ref_(ref) {} ConstStringListRef(const std::vector* ref) : ref_wide_(ref) {} - size_t size() const { return ref_ ? ref_->size() : ref_wide_->size(); } + size_t size() const { + if (ref_) { + return ref_->size(); + } + if (ref_wide_) { + return ref_wide_->size(); + } + return 0; + } + std::string operator[](size_t i) const { - return ref_ ? (*ref_)[i] : to_string((*ref_wide_)[i]); + if (ref_) { + return (*ref_)[i]; + } + if (ref_wide_) { + return to_string((*ref_wide_)[i]); + } + return ""; } private: diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index 1c05d83..c558dfb 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -30,7 +30,148 @@ Element DefaultTransform(EntryState params) { // NOLINT return element; } +class ButtonBase : public ComponentBase, public ButtonOption { + public: + explicit ButtonBase(ButtonOption option) : ButtonOption(std::move(option)) {} + + // Component implementation: + Element Render() override { + const bool active = Active(); + const bool focused = Focused(); + const bool focused_or_hover = focused || mouse_hover_; + + float target = focused_or_hover ? 1.f : 0.f; // NOLINT + if (target != animator_background_.to()) { + SetAnimationTarget(target); + } + + auto focus_management = focused ? focus : active ? select : nothing; + const EntryState state = { + *label, + false, + active, + focused_or_hover, + }; + + auto element = (transform ? transform : DefaultTransform) // + (state); + return element | AnimatedColorStyle() | focus_management | reflect(box_); + } + + Decorator AnimatedColorStyle() { + Decorator style = nothing; + if (animated_colors.background.enabled) { + style = style | + bgcolor(Color::Interpolate(animation_foreground_, // + animated_colors.background.inactive, + animated_colors.background.active)); + } + if (animated_colors.foreground.enabled) { + style = + style | color(Color::Interpolate(animation_foreground_, // + animated_colors.foreground.inactive, + animated_colors.foreground.active)); + } + return style; + } + + void SetAnimationTarget(float target) { + if (animated_colors.foreground.enabled) { + animator_foreground_ = animation::Animator( + &animation_foreground_, target, animated_colors.foreground.duration, + animated_colors.foreground.function); + } + if (animated_colors.background.enabled) { + animator_background_ = animation::Animator( + &animation_background_, target, animated_colors.background.duration, + animated_colors.background.function); + } + } + + void OnAnimation(animation::Params& p) override { + animator_background_.OnAnimation(p); + animator_foreground_.OnAnimation(p); + } + + void OnClick() { + on_click(); + animation_background_ = 0.5F; // NOLINT + animation_foreground_ = 0.5F; // NOLINT + SetAnimationTarget(1.F); // NOLINT + } + + bool OnEvent(Event event) override { + if (event.is_mouse()) { + return OnMouseEvent(event); + } + + if (event == Event::Return) { + OnClick(); + return true; + } + return false; + } + + bool OnMouseEvent(Event event) { + mouse_hover_ = + box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event); + + if (!mouse_hover_) { + return false; + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed) { + TakeFocus(); + OnClick(); + return true; + } + + return false; + } + + bool Focusable() const final { return true; } + + private: + bool mouse_hover_ = false; + Box box_; + ButtonOption option_; + float animation_background_ = 0; + float animation_foreground_ = 0; + animation::Animator animator_background_ = + animation::Animator(&animation_background_); + animation::Animator animator_foreground_ = + animation::Animator(&animation_foreground_); +}; + } // namespace + // +/// @brief Draw a button. Execute a function when clicked. +/// @param option Additional optional parameters. +/// @ingroup component +/// @see ButtonBase +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::FitComponent(); +/// Component button = Button({ +/// .label = "Click to quit", +/// .on_click = screen.ExitLoopClosure(), +/// }); +/// screen.Loop(button) +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ┌─────────────┐ +/// │Click to quit│ +/// └─────────────┘ +/// ``` +Component Button(ButtonOption option) { + return Make(std::move(option)); +} /// @brief Draw a button. Execute a function when clicked. /// @param label The label of the button. @@ -55,135 +196,13 @@ Element DefaultTransform(EntryState params) { // NOLINT /// │Click to quit│ /// └─────────────┘ /// ``` -// NOLINTNEXTLINE(readability-function-cognitive-complexity) +// NOLINTNEXTLINE Component Button(ConstStringRef label, std::function on_click, - Ref option) { - class Impl : public ComponentBase { - public: - Impl(ConstStringRef label, - std::function on_click, - Ref option) - : label_(std::move(label)), - on_click_(std::move(on_click)), - option_(std::move(option)) {} - - // Component implementation: - Element Render() override { - const bool active = Active(); - const bool focused = Focused(); - const bool focused_or_hover = focused || mouse_hover_; - - float target = focused_or_hover ? 1.f : 0.f; // NOLINT - if (target != animator_background_.to()) { - SetAnimationTarget(target); - } - - auto focus_management = focused ? focus : active ? select : nothing; - const EntryState state = { - *label_, - false, - active, - focused_or_hover, - }; - - auto element = - (option_->transform ? option_->transform : DefaultTransform) // - (state); - return element | AnimatedColorStyle() | focus_management | reflect(box_); - } - - Decorator AnimatedColorStyle() { - Decorator style = nothing; - if (option_->animated_colors.background.enabled) { - style = style | bgcolor(Color::Interpolate( - animation_foreground_, // - option_->animated_colors.background.inactive, - option_->animated_colors.background.active)); - } - if (option_->animated_colors.foreground.enabled) { - style = style | color(Color::Interpolate( - animation_foreground_, // - option_->animated_colors.foreground.inactive, - option_->animated_colors.foreground.active)); - } - return style; - } - - void SetAnimationTarget(float target) { - if (option_->animated_colors.foreground.enabled) { - animator_foreground_ = - animation::Animator(&animation_foreground_, target, - option_->animated_colors.foreground.duration, - option_->animated_colors.foreground.function); - } - if (option_->animated_colors.background.enabled) { - animator_background_ = - animation::Animator(&animation_background_, target, - option_->animated_colors.background.duration, - option_->animated_colors.background.function); - } - } - - void OnAnimation(animation::Params& p) override { - animator_background_.OnAnimation(p); - animator_foreground_.OnAnimation(p); - } - - void OnClick() { - on_click_(); - animation_background_ = 0.5F; // NOLINT - animation_foreground_ = 0.5F; // NOLINT - SetAnimationTarget(1.F); // NOLINT - } - - bool OnEvent(Event event) override { - if (event.is_mouse()) { - return OnMouseEvent(event); - } - - if (event == Event::Return) { - OnClick(); - return true; - } - return false; - } - - bool OnMouseEvent(Event event) { - mouse_hover_ = - box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event); - - if (!mouse_hover_) { - return false; - } - - if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed) { - TakeFocus(); - OnClick(); - return true; - } - - return false; - } - - bool Focusable() const final { return true; } - - private: - ConstStringRef label_; - std::function on_click_; - bool mouse_hover_ = false; - Box box_; - Ref option_; - float animation_background_ = 0; - float animation_foreground_ = 0; - animation::Animator animator_background_ = - animation::Animator(&animation_background_); - animation::Animator animator_foreground_ = - animation::Animator(&animation_foreground_); - }; - - return Make(std::move(label), std::move(on_click), std::move(option)); + ButtonOption option) { + option.label = label; + option.on_click = std::move(on_click); + return Make(std::move(option)); } } // namespace ftxui diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index 14a05ff..e65ac6b 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -14,10 +14,10 @@ namespace ftxui { namespace { -class CheckboxBase : public ComponentBase { +class CheckboxBase : public ComponentBase, public CheckboxOption { public: - CheckboxBase(ConstStringRef label, bool* state, Ref option) - : label_(std::move(label)), state_(state), option_(std::move(option)) {} + explicit CheckboxBase(CheckboxOption option) + : CheckboxOption(std::move(option)) {} private: // Component implementation. @@ -25,15 +25,14 @@ class CheckboxBase : public ComponentBase { const bool is_focused = Focused(); const bool is_active = Active(); auto focus_management = is_focused ? focus : is_active ? select : nothing; - auto state = EntryState{ - *label_, - *state_, + auto entry_state = EntryState{ + *label, + *checked, is_active, is_focused || hovered_, }; - auto element = - (option_->transform ? option_->transform - : CheckboxOption::Simple().transform)(state); + auto element = (transform ? transform : CheckboxOption::Simple().transform)( + entry_state); return element | focus_management | reflect(box_); } @@ -48,8 +47,8 @@ class CheckboxBase : public ComponentBase { hovered_ = false; if (event == Event::Character(' ') || event == Event::Return) { - *state_ = !*state_; - option_->on_change(); + *checked = !*checked; + on_change(); TakeFocus(); return true; } @@ -69,8 +68,8 @@ class CheckboxBase : public ComponentBase { if (event.mouse().button == Mouse::Left && event.mouse().motion == Mouse::Pressed) { - *state_ = !*state_; - option_->on_change(); + *checked = !*checked; + on_change(); return true; } @@ -79,10 +78,7 @@ class CheckboxBase : public ComponentBase { bool Focusable() const final { return true; } - ConstStringRef label_; - bool* const state_; bool hovered_ = false; - Ref option_; Box box_; }; } // namespace @@ -109,10 +105,11 @@ class CheckboxBase : public ComponentBase { /// ```bash /// ☐ Make a sandwitch /// ``` -Component Checkbox(ConstStringRef label, - bool* checked, - Ref option) { - return Make(std::move(label), checked, std::move(option)); +// NOLINTNEXTLINE +Component Checkbox(ConstStringRef label, bool* checked, CheckboxOption option) { + option.label = label; + option.checked = checked; + return Make(std::move(option)); } } // namespace ftxui diff --git a/src/ftxui/component/collapsible.cpp b/src/ftxui/component/collapsible.cpp index 2adf205..995efa2 100644 --- a/src/ftxui/component/collapsible.cpp +++ b/src/ftxui/component/collapsible.cpp @@ -27,6 +27,7 @@ namespace ftxui { /// ▼ Show details ///
///  ``` +// NOLINTNEXTLINE Component Collapsible(ConstStringRef label, Component child, Ref show) { class Impl : public ComponentBase { public: @@ -44,7 +45,7 @@ Component Collapsible(ConstStringRef label, Component child, Ref show) { return hbox({prefix, t}); }; Add(Container::Vertical({ - Checkbox(std::move(label), show_.operator->(), opt), + Checkbox(label, show_.operator->(), opt), Maybe(std::move(child), show_.operator->()), })); } diff --git a/src/ftxui/component/component_options.cpp b/src/ftxui/component/component_options.cpp index aaebf19..5e25ef2 100644 --- a/src/ftxui/component/component_options.cpp +++ b/src/ftxui/component/component_options.cpp @@ -48,7 +48,7 @@ void UnderlineOption::SetAnimationFunction( MenuOption MenuOption::Horizontal() { MenuOption option; option.direction = Direction::Right; - option.entries.transform = [](const EntryState& state) { + option.entries_option.transform = [](const EntryState& state) { Element e = text(state.label); if (state.focused) { e |= inverted; @@ -76,7 +76,7 @@ MenuOption MenuOption::HorizontalAnimated() { // static MenuOption MenuOption::Vertical() { MenuOption option; - option.entries.transform = [](const EntryState& state) { + option.entries_option.transform = [](const EntryState& state) { Element e = text((state.active ? "> " : " ") + state.label); // NOLINT if (state.focused) { e |= inverted; @@ -95,7 +95,7 @@ MenuOption MenuOption::Vertical() { // static MenuOption MenuOption::VerticalAnimated() { auto option = MenuOption::Vertical(); - option.entries.transform = [](const EntryState& state) { + option.entries_option.transform = [](const EntryState& state) { Element e = text(state.label); if (state.focused) { e |= inverted; @@ -124,9 +124,9 @@ MenuOption MenuOption::Toggle() { ButtonOption ButtonOption::Ascii() { ButtonOption option; option.transform = [](const EntryState& s) { - const std::string label = s.focused ? "[" + s.label + "]" // - : " " + s.label + " "; - return text(label); + const std::string t = s.focused ? "[" + s.label + "]" // + : " " + s.label + " "; + return text(t); }; return option; } diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index 1f49d91..0f32946 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -34,17 +34,19 @@ std::vector Split(const std::string& input) { output.push_back(line); } if (input.back() == '\n') { - output.push_back(""); + output.emplace_back(""); } return output; } size_t GlyphWidth(const std::string& input, size_t iter) { uint32_t ucs = 0; - if (!EatCodePoint(input, iter, &iter, &ucs)) + if (!EatCodePoint(input, iter, &iter, &ucs)) { return 0; - if (IsFullWidth(ucs)) + } + if (IsFullWidth(ucs)) { return 2; + } return 1; } @@ -86,11 +88,10 @@ bool IsWordCharacter(const std::string& input, size_t iter) { } // An input box. The user can type text into it. -class InputBase : public ComponentBase { +class InputBase : public ComponentBase, public InputOption { public: // NOLINTNEXTLINE - InputBase(StringRef content, Ref option) - : content_(std::move(content)), option_(std::move(option)) {} + InputBase(InputOption option) : InputOption(std::move(option)) {} private: // Component implementation: @@ -99,17 +100,17 @@ class InputBase : public ComponentBase { const auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select; - auto transform = option_->transform ? option_->transform - : InputOption::Default().transform; + auto transform_func = + transform ? transform : InputOption::Default().transform; // placeholder. - if (content_->empty()) { - auto element = text(option_->placeholder()) | xflex | frame; + if (content->empty()) { + auto element = text(placeholder()) | xflex | frame; if (is_focused) { element |= focus; } - return transform({ + return transform_func({ std::move(element), hovered_, is_focused, true // placeholder }) | @@ -117,14 +118,13 @@ class InputBase : public ComponentBase { } Elements elements; - const std::vector lines = Split(*content_); + const std::vector lines = Split(*content); - int& cursor_position = option_->cursor_position(); - cursor_position = util::clamp(cursor_position, 0, (int)content_->size()); + cursor_position() = util::clamp(cursor_position(), 0, (int)content->size()); // Find the line and index of the cursor. int cursor_line = 0; - int cursor_char_index = cursor_position; + int cursor_char_index = cursor_position(); for (const auto& line : lines) { if (cursor_char_index <= (int)line.size()) { break; @@ -175,7 +175,7 @@ class InputBase : public ComponentBase { } auto element = vbox(std::move(elements)) | frame; - return transform({ + return transform_func({ std::move(element), hovered_, is_focused, false // placeholder }) | @@ -183,7 +183,7 @@ class InputBase : public ComponentBase { } Element Text(const std::string& input) { - if (!option_->password()) { + if (!password()) { return text(input); } @@ -196,108 +196,101 @@ class InputBase : public ComponentBase { } bool HandleBackspace() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == 0) { + if (cursor_position() == 0) { return false; } - const size_t start = GlyphPrevious(content_(), cursor_position); - const size_t end = cursor_position; - content_->erase(start, end - start); - cursor_position = start; + const size_t start = GlyphPrevious(content(), cursor_position()); + const size_t end = cursor_position(); + content->erase(start, end - start); + cursor_position() = start; return true; } bool HandleDelete() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == (int)content_->size()) { + if (cursor_position() == (int)content->size()) { return false; } - const size_t start = cursor_position; - const size_t end = GlyphNext(content_(), cursor_position); - content_->erase(start, end - start); + const size_t start = cursor_position(); + const size_t end = GlyphNext(content(), cursor_position()); + content->erase(start, end - start); return true; } bool HandleArrowLeft() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == 0) { + if (cursor_position() == 0) { return false; } - cursor_position = GlyphPrevious(content_(), cursor_position); + cursor_position() = GlyphPrevious(content(), cursor_position()); return true; } bool HandleArrowRight() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == (int)content_->size()) { + if (cursor_position() == (int)content->size()) { return false; } - cursor_position = GlyphNext(content_(), cursor_position); + cursor_position() = GlyphNext(content(), cursor_position()); return true; } size_t CursorColumn() { - int& cursor_position = option_->cursor_position(); - size_t iter = cursor_position; + size_t iter = cursor_position(); int width = 0; while (true) { if (iter == 0) { break; } - iter = GlyphPrevious(content_(), iter); - if (content_()[iter] == '\n') { + iter = GlyphPrevious(content(), iter); + if (content()[iter] == '\n') { break; } - width += GlyphWidth(content_(), iter); + width += GlyphWidth(content(), iter); } return width; } // Move the cursor `columns` on the right, if possible. void MoveCursorColumn(int columns) { - int& cursor_position = option_->cursor_position(); while (columns > 0) { - if (cursor_position == (int)content_().size() || - content_()[cursor_position] == '\n') { + if (cursor_position() == (int)content().size() || + content()[cursor_position()] == '\n') { return; } - columns -= GlyphWidth(content_(), cursor_position); - cursor_position = GlyphNext(content_(), cursor_position); + columns -= GlyphWidth(content(), cursor_position()); + cursor_position() = GlyphNext(content(), cursor_position()); } } bool HandleArrowUp() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == 0) { + if (cursor_position() == 0) { return false; } - size_t columns = CursorColumn(); + const size_t columns = CursorColumn(); // Move cursor at the beginning of 2 lines above. while (true) { - if (cursor_position == 0) { + if (cursor_position() == 0) { return true; } - size_t previous = GlyphPrevious(content_(), cursor_position); - if (content_()[previous] == '\n') { + const size_t previous = GlyphPrevious(content(), cursor_position()); + if (content()[previous] == '\n') { break; } - cursor_position = previous; + cursor_position() = previous; } - cursor_position = GlyphPrevious(content_(), cursor_position); + cursor_position() = GlyphPrevious(content(), cursor_position()); while (true) { - if (cursor_position == 0) { + if (cursor_position() == 0) { break; } - size_t previous = GlyphPrevious(content_(), cursor_position); - if (content_()[previous] == '\n') { + const size_t previous = GlyphPrevious(content(), cursor_position()); + if (content()[previous] == '\n') { break; } - cursor_position = previous; + cursor_position() = previous; } MoveCursorColumn(columns); @@ -305,61 +298,56 @@ class InputBase : public ComponentBase { } bool HandleArrowDown() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == (int)content_->size()) { + if (cursor_position() == (int)content->size()) { return false; } - size_t columns = CursorColumn(); + const size_t columns = CursorColumn(); // Move cursor at the beginning of the next line while (true) { - if (content_()[cursor_position] == '\n') { + if (content()[cursor_position()] == '\n') { break; } - cursor_position = GlyphNext(content_(), cursor_position); - if (cursor_position == (int)content_().size()) { + cursor_position() = GlyphNext(content(), cursor_position()); + if (cursor_position() == (int)content().size()) { return true; } } - cursor_position = GlyphNext(content_(), cursor_position); + cursor_position() = GlyphNext(content(), cursor_position()); MoveCursorColumn(columns); return true; } bool HandleHome() { - int& cursor_position = option_->cursor_position(); - cursor_position = 0; + cursor_position() = 0; return true; } bool HandleEnd() { - int& cursor_position = option_->cursor_position(); - cursor_position = content_->size(); + cursor_position() = content->size(); return true; } bool HandleReturn() { - if (option_->multiline()) { + if (multiline()) { HandleCharacter("\n"); } - option_->on_enter(); + on_enter(); return true; } bool HandleCharacter(const std::string& character) { - int& cursor_position = option_->cursor_position(); - content_->insert(cursor_position, character); - cursor_position += character.size(); - option_->on_change(); + content->insert(cursor_position(), character); + cursor_position() += character.size(); + on_change(); return true; } bool OnEvent(Event event) override { - int& cursor_position = option_->cursor_position(); - cursor_position = util::clamp(cursor_position, 0, (int)content_->size()); + cursor_position() = util::clamp(cursor_position(), 0, (int)content->size()); if (event == Event::Return) { return HandleReturn(); @@ -405,50 +393,48 @@ class InputBase : public ComponentBase { } bool HandleLeftCtrl() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == 0) { + if (cursor_position() == 0) { return false; } // Move left, as long as left it not a word. - while (cursor_position) { - size_t previous = GlyphPrevious(content_(), cursor_position); - if (IsWordCharacter(content_(), previous)) { + while (cursor_position()) { + const size_t previous = GlyphPrevious(content(), cursor_position()); + if (IsWordCharacter(content(), previous)) { break; } - cursor_position = previous; + cursor_position() = previous; } // Move left, as long as left is a word character: - while (cursor_position) { - size_t previous = GlyphPrevious(content_(), cursor_position); - if (!IsWordCharacter(content_(), previous)) { + while (cursor_position()) { + const size_t previous = GlyphPrevious(content(), cursor_position()); + if (!IsWordCharacter(content(), previous)) { break; } - cursor_position = previous; + cursor_position() = previous; } return true; } bool HandleRightCtrl() { - int& cursor_position = option_->cursor_position(); - if (cursor_position == (int)content_().size()) { + if (cursor_position() == (int)content().size()) { return false; } // Move right, until entering a word. - while (cursor_position < (int)content_().size()) { - cursor_position = GlyphNext(content_(), cursor_position); - if (IsWordCharacter(content_(), cursor_position)) { + while (cursor_position() < (int)content().size()) { + cursor_position() = GlyphNext(content(), cursor_position()); + if (IsWordCharacter(content(), cursor_position())) { break; } } // Move right, as long as right is a word character: - while (cursor_position < (int)content_().size()) { - size_t next = GlyphNext(content_(), cursor_position); - if (!IsWordCharacter(content_(), cursor_position)) { + while (cursor_position() < (int)content().size()) { + const size_t next = GlyphNext(content(), cursor_position()); + if (!IsWordCharacter(content(), cursor_position())) { break; } - cursor_position = next; + cursor_position() = next; } return true; @@ -469,16 +455,15 @@ class InputBase : public ComponentBase { TakeFocus(); - if (content_->empty()) { - option_->cursor_position() = 0; + if (content->empty()) { + cursor_position() = 0; return true; } // Find the line and index of the cursor. - std::vector lines = Split(*content_); - int& cursor_position = option_->cursor_position(); + std::vector lines = Split(*content); int cursor_line = 0; - int cursor_char_index = cursor_position; + int cursor_char_index = cursor_position(); for (const auto& line : lines) { if (cursor_char_index <= (int)line.size()) { break; @@ -487,7 +472,7 @@ class InputBase : public ComponentBase { cursor_char_index -= line.size() + 1; cursor_line++; } - int cursor_column = + const int cursor_column = string_width(lines[cursor_line].substr(0, cursor_char_index)); int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min; @@ -496,7 +481,7 @@ class InputBase : public ComponentBase { // Fix the new cursor position: new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0); - std::string empty_string; + const std::string empty_string; const std::string& line = new_cursor_line < (int)lines.size() ? lines[new_cursor_line] : empty_string; @@ -508,31 +493,56 @@ class InputBase : public ComponentBase { } // Convert back the new_cursor_{line,column} toward cursor_position: - cursor_position = 0; + cursor_position() = 0; for (int i = 0; i < new_cursor_line; ++i) { - cursor_position += lines[i].size() + 1; + cursor_position() += lines[i].size() + 1; } while (new_cursor_column > 0) { - new_cursor_column -= GlyphWidth(content_(), cursor_position); - cursor_position = GlyphNext(content_(), cursor_position); + new_cursor_column -= GlyphWidth(content(), cursor_position()); + cursor_position() = GlyphNext(content(), cursor_position()); } - option_->on_change(); + on_change(); return true; } bool Focusable() const final { return true; } bool hovered_ = false; - StringRef content_; Box box_; Box cursor_box_; - Ref option_; }; } // namespace +/// @brief An input box for editing text. +/// @param option Additional optional parameters. +/// @ingroup component +/// @see InputBase +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::FitComponent(); +/// std::string content= ""; +/// std::string placeholder = "placeholder"; +/// Component input = Input({ +/// .content = &content, +/// .placeholder = &placeholder, +/// }) +/// screen.Loop(input); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// placeholder +/// ``` +Component Input(InputOption option) { + return Make(std::move(option)); +} + /// @brief An input box for editing text. /// @param content The editable content. /// @param option Additional optional parameters. @@ -545,7 +555,10 @@ class InputBase : public ComponentBase { /// auto screen = ScreenInteractive::FitComponent(); /// std::string content= ""; /// std::string placeholder = "placeholder"; -/// Component input = Input(&content, &placeholder); +/// Component input = Input(content, { +/// .placeholder = &placeholder, +/// .password = true, +/// }) /// screen.Loop(input); /// ``` /// @@ -554,15 +567,36 @@ class InputBase : public ComponentBase { /// ```bash /// placeholder /// ``` -Component Input(StringRef content, Ref option) { - return Make(std::move(content), std::move(option)); +Component Input(StringRef content, InputOption option) { + option.content = content; + return Make(std::move(option)); } -Component Input(StringRef content, - StringRef placeholder, - Ref option) { - option->placeholder = placeholder; - return Make(std::move(content), std::move(option)); +/// @brief An input box for editing text. +/// @param content The editable content. +/// @param option Additional optional parameters. +/// @ingroup component +/// @see InputBase +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::FitComponent(); +/// std::string content= ""; +/// std::string placeholder = "placeholder"; +/// Component input = Input(content, placeholder); +/// screen.Loop(input); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// placeholder +/// ``` +Component Input(StringRef content, StringRef placeholder, InputOption option) { + option.content = content; + option.placeholder = placeholder; + return Make(std::move(option)); } } // namespace ftxui diff --git a/src/ftxui/component/input_test.cpp b/src/ftxui/component/input_test.cpp index 1644059..175ff75 100644 --- a/src/ftxui/component/input_test.cpp +++ b/src/ftxui/component/input_test.cpp @@ -16,32 +16,36 @@ namespace ftxui { TEST(InputTest, Init) { std::string content; + int cursor_position = 0; auto option = InputOption(); - Component input = Input(&content, &option); - EXPECT_EQ(option.cursor_position(), 0); + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); + EXPECT_EQ(cursor_position, 0); } TEST(InputTest, Type) { std::string content; - std::string placeholder; - auto option = InputOption(); - Component input = Input(&content, &option); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); input->OnEvent(Event::Character("a")); EXPECT_EQ(content, "a"); - EXPECT_EQ(option.cursor_position(), 1); + EXPECT_EQ(cursor_position, 1); input->OnEvent(Event::Character('b')); EXPECT_EQ(content, "ab"); - EXPECT_EQ(option.cursor_position(), 2); + EXPECT_EQ(cursor_position, 2); input->OnEvent(Event::Return); EXPECT_EQ(content, "ab\n"); - EXPECT_EQ(option.cursor_position(), 3); + EXPECT_EQ(cursor_position, 3); input->OnEvent(Event::Character('c')); EXPECT_EQ(content, "ab\nc"); - EXPECT_EQ(option.cursor_position(), 4); + EXPECT_EQ(cursor_position, 4); auto document = input->Render(); @@ -55,57 +59,59 @@ TEST(InputTest, Type) { TEST(InputTest, ArrowLeftRight) { std::string content = "abc测测a测\na测\n"; - auto option = InputOption(); - auto input = Input(&content, &option); - EXPECT_EQ(option.cursor_position(), 0); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); + EXPECT_EQ(cursor_position, 0); EXPECT_FALSE(input->OnEvent(Event::ArrowLeft)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 1); + EXPECT_EQ(cursor_position, 1); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 1); + EXPECT_EQ(cursor_position, 1); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 2); + EXPECT_EQ(cursor_position, 2); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 3); + EXPECT_EQ(cursor_position, 3); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 6); + EXPECT_EQ(cursor_position, 6); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 9); + EXPECT_EQ(cursor_position, 9); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 10); + EXPECT_EQ(cursor_position, 10); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 13); + EXPECT_EQ(cursor_position, 13); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 14); + EXPECT_EQ(cursor_position, 14); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 15); + EXPECT_EQ(cursor_position, 15); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 18); + EXPECT_EQ(cursor_position, 18); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 19); + EXPECT_EQ(cursor_position, 19); EXPECT_FALSE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 19); + EXPECT_EQ(cursor_position, 19); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); - EXPECT_EQ(option.cursor_position(), 18); + EXPECT_EQ(cursor_position, 18); } TEST(InputTest, ArrowUpDown) { @@ -117,70 +123,75 @@ TEST(InputTest, ArrowUpDown) { "00\n" "0\n" ""; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 4); + EXPECT_EQ(cursor_position, 4); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 11); + EXPECT_EQ(cursor_position, 11); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 21); + EXPECT_EQ(cursor_position, 21); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 29); + EXPECT_EQ(cursor_position, 29); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 36); + EXPECT_EQ(cursor_position, 36); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 40); + EXPECT_EQ(cursor_position, 40); EXPECT_FALSE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 40); + EXPECT_EQ(cursor_position, 40); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 36); + EXPECT_EQ(cursor_position, 36); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 29); + EXPECT_EQ(cursor_position, 29); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 21); + EXPECT_EQ(cursor_position, 21); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 11); + EXPECT_EQ(cursor_position, 11); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 4); + EXPECT_EQ(cursor_position, 4); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_FALSE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); - EXPECT_EQ(option.cursor_position(), 3); + EXPECT_EQ(cursor_position, 3); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 7); + EXPECT_EQ(cursor_position, 7); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 14); + EXPECT_EQ(cursor_position, 14); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 24); + EXPECT_EQ(cursor_position, 24); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 32); + EXPECT_EQ(cursor_position, 32); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 39); + EXPECT_EQ(cursor_position, 39); EXPECT_TRUE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 40); + EXPECT_EQ(cursor_position, 40); EXPECT_FALSE(input->OnEvent(Event::ArrowDown)); - EXPECT_EQ(option.cursor_position(), 40); + EXPECT_EQ(cursor_position, 40); - option.cursor_position() = 39; + cursor_position = 39; EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 32); + EXPECT_EQ(cursor_position, 32); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 24); + EXPECT_EQ(cursor_position, 24); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 14); + EXPECT_EQ(cursor_position, 14); EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); - EXPECT_EQ(option.cursor_position(), 7); + EXPECT_EQ(cursor_position, 7); } TEST(InputTest, Insert) { std::string content; - Component input = Input(&content); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::Character('a'))); EXPECT_TRUE(input->OnEvent(Event::Character('b'))); @@ -213,8 +224,10 @@ TEST(InputTest, Insert) { TEST(InputTest, Home) { std::string content; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::Character('a'))); EXPECT_TRUE(input->OnEvent(Event::Character('b'))); @@ -224,21 +237,22 @@ TEST(InputTest, Home) { EXPECT_TRUE(input->OnEvent(Event::Character('b'))); EXPECT_TRUE(input->OnEvent(Event::Character('c'))); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); EXPECT_TRUE(input->OnEvent(Event::Home)); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); EXPECT_TRUE(input->OnEvent(Event::Character('-'))); - EXPECT_EQ(option.cursor_position(), 1u); + EXPECT_EQ(cursor_position, 1u); EXPECT_EQ(content, "-abc\n测bc"); } TEST(InputTest, End) { std::string content; - std::string placeholder; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + Component input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::Character('a'))); EXPECT_TRUE(input->OnEvent(Event::Character('b'))); @@ -250,17 +264,18 @@ TEST(InputTest, End) { EXPECT_TRUE(input->OnEvent(Event::ArrowUp)); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 2u); + EXPECT_EQ(cursor_position, 2u); input->OnEvent(Event::End); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); } TEST(InputTest, Delete) { std::string content; - std::string placeholder; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + auto input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::Character('a'))); EXPECT_TRUE(input->OnEvent(Event::Character('b'))); @@ -271,38 +286,38 @@ TEST(InputTest, Delete) { EXPECT_TRUE(input->OnEvent(Event::Character('c'))); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); EXPECT_FALSE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 8u); + EXPECT_EQ(cursor_position, 8u); EXPECT_TRUE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abc\n测b"); - EXPECT_EQ(option.cursor_position(), 8u); + EXPECT_EQ(cursor_position, 8u); EXPECT_FALSE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abc\n测b"); - EXPECT_EQ(option.cursor_position(), 8u); + EXPECT_EQ(cursor_position, 8u); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_TRUE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abc\nb"); - EXPECT_EQ(option.cursor_position(), 4u); + EXPECT_EQ(cursor_position, 4u); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_TRUE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abcb"); - EXPECT_EQ(option.cursor_position(), 3u); + EXPECT_EQ(cursor_position, 3u); EXPECT_TRUE(input->OnEvent(Event::Delete)); EXPECT_EQ(content, "abc"); - EXPECT_EQ(option.cursor_position(), 3u); + EXPECT_EQ(cursor_position, 3u); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); @@ -318,9 +333,10 @@ TEST(InputTest, Delete) { TEST(InputTest, Backspace) { std::string content; - std::string placeholder; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + auto input = Input(&content, { + .cursor_position = &cursor_position, + }); EXPECT_TRUE(input->OnEvent(Event::Character('a'))); EXPECT_TRUE(input->OnEvent(Event::Character('b'))); @@ -331,45 +347,45 @@ TEST(InputTest, Backspace) { EXPECT_TRUE(input->OnEvent(Event::Character('c'))); EXPECT_EQ(content, "abc\n测bc"); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "abc\n测b"); - EXPECT_EQ(option.cursor_position(), 8u); + EXPECT_EQ(cursor_position, 8u); EXPECT_TRUE(input->OnEvent(Event::ArrowLeft)); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "abc\nb"); - EXPECT_EQ(option.cursor_position(), 4u); + EXPECT_EQ(cursor_position, 4u); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "abcb"); - EXPECT_EQ(option.cursor_position(), 3u); + EXPECT_EQ(cursor_position, 3u); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "abb"); - EXPECT_EQ(option.cursor_position(), 2u); + EXPECT_EQ(cursor_position, 2u); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "ab"); - EXPECT_EQ(option.cursor_position(), 1u); + EXPECT_EQ(cursor_position, 1u); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "b"); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); EXPECT_FALSE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, "b"); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); EXPECT_TRUE(input->OnEvent(Event::ArrowRight)); EXPECT_TRUE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, ""); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); EXPECT_FALSE(input->OnEvent(Event::Backspace)); EXPECT_EQ(content, ""); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); } TEST(InputTest, CtrlArrow) { @@ -377,192 +393,193 @@ TEST(InputTest, CtrlArrow) { "word word 测ord wo测d word\n" "coucou coucou coucou\n" "coucou coucou coucou\n"; - std::string placeholder; - auto option = InputOption(); - option.cursor_position = 1000; - auto input = Input(&content, &option); + int cursor_position = 1000; + auto input = Input(&content, { + .cursor_position = &cursor_position, + }); // Use CTRL+Left several time EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 67); + EXPECT_EQ(cursor_position, 67); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 60); + EXPECT_EQ(cursor_position, 60); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 53); + EXPECT_EQ(cursor_position, 53); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 46); + EXPECT_EQ(cursor_position, 46); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 39); + EXPECT_EQ(cursor_position, 39); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 29); + EXPECT_EQ(cursor_position, 29); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 24); + EXPECT_EQ(cursor_position, 24); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 17); + EXPECT_EQ(cursor_position, 17); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 10); + EXPECT_EQ(cursor_position, 10); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 5); + EXPECT_EQ(cursor_position, 5); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_FALSE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 4); + EXPECT_EQ(cursor_position, 4); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 9); + EXPECT_EQ(cursor_position, 9); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 16); + EXPECT_EQ(cursor_position, 16); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 23); + EXPECT_EQ(cursor_position, 23); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 28); + EXPECT_EQ(cursor_position, 28); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 35); + EXPECT_EQ(cursor_position, 35); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 45); + EXPECT_EQ(cursor_position, 45); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 52); + EXPECT_EQ(cursor_position, 52); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 59); + EXPECT_EQ(cursor_position, 59); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 66); + EXPECT_EQ(cursor_position, 66); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 73); + EXPECT_EQ(cursor_position, 73); } TEST(InputTest, CtrlArrowLeft2) { std::string content = " word word 测ord wo测d word "; - auto option = InputOption(); - option.cursor_position = 33; - auto input = Input(&content, &option); + int cursor_position = 33; + auto input = Input(&content, { + .cursor_position = &cursor_position, + }); // Use CTRL+Left several time EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 31); + EXPECT_EQ(cursor_position, 31); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 23); + EXPECT_EQ(cursor_position, 23); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 15); + EXPECT_EQ(cursor_position, 15); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 9); + EXPECT_EQ(cursor_position, 9); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 3); + EXPECT_EQ(cursor_position, 3); EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); EXPECT_FALSE(input->OnEvent(Event::ArrowLeftCtrl)); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); } TEST(InputTest, CtrlArrowRight) { std::string content = "word word 测ord wo测d word\n" "coucou dfqdsf jmlkjm"; - - auto option = InputOption(); - option.cursor_position = 2; - auto input = Input(&content, &option); + int cursor_position = 2; + auto input = Input(&content, {.cursor_position = &cursor_position}); // Use CTRL+Left several time EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 4); + EXPECT_EQ(cursor_position, 4); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 9); + EXPECT_EQ(cursor_position, 9); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 16); + EXPECT_EQ(cursor_position, 16); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 23); + EXPECT_EQ(cursor_position, 23); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 28); + EXPECT_EQ(cursor_position, 28); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 35); + EXPECT_EQ(cursor_position, 35); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 42); + EXPECT_EQ(cursor_position, 42); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 49); + EXPECT_EQ(cursor_position, 49); EXPECT_FALSE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 49); + EXPECT_EQ(cursor_position, 49); } TEST(InputTest, CtrlArrowRight2) { std::string content = " word word 测ord wo测d word "; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + auto input = Input(&content, {.cursor_position = &cursor_position}); // Use CTRL+Left several time EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 7); + EXPECT_EQ(cursor_position, 7); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 13); + EXPECT_EQ(cursor_position, 13); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 21); + EXPECT_EQ(cursor_position, 21); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 29); + EXPECT_EQ(cursor_position, 29); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 35); + EXPECT_EQ(cursor_position, 35); EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 38); + EXPECT_EQ(cursor_position, 38); EXPECT_FALSE(input->OnEvent(Event::ArrowRightCtrl)); - EXPECT_EQ(option.cursor_position(), 38); + EXPECT_EQ(cursor_position, 38); } TEST(InputTest, TypePassword) { std::string content; std::string placeholder; - auto option = InputOption(); - option.cursor_position = 0; - option.password = true; - Component input = Input(&content, &placeholder, &option); + int cursor_position = 0; + Component input = Input(&content, &placeholder, + { + .password = true, + .cursor_position = &cursor_position, + }); input->OnEvent(Event::Character('a')); EXPECT_EQ(content, "a"); - EXPECT_EQ(option.cursor_position(), 1u); + EXPECT_EQ(cursor_position, 1u); input->OnEvent(Event::Character('b')); EXPECT_EQ(content, "ab"); - EXPECT_EQ(option.cursor_position(), 2u); + EXPECT_EQ(cursor_position, 2u); auto document = input->Render(); auto screen = Screen::Create(Dimension::Fit(document)); @@ -573,8 +590,8 @@ TEST(InputTest, TypePassword) { TEST(InputTest, MouseClick) { std::string content; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + auto input = Input(&content, {.cursor_position = &cursor_position}); input->OnEvent(Event::Character("a")); input->OnEvent(Event::Character("b")); @@ -588,7 +605,7 @@ TEST(InputTest, MouseClick) { input->OnEvent(Event::Return); EXPECT_EQ(content, "abcd\nabcd\n"); - EXPECT_EQ(option.cursor_position(), 10u); + EXPECT_EQ(cursor_position, 10u); auto render = [&] { auto document = input->Render(); @@ -596,7 +613,7 @@ TEST(InputTest, MouseClick) { Render(screen, document); }; render(); - EXPECT_EQ(option.cursor_position(), 10u); + EXPECT_EQ(cursor_position, 10u); Mouse mouse; mouse.button = Mouse::Button::Left; @@ -609,67 +626,67 @@ TEST(InputTest, MouseClick) { mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 0u); + EXPECT_EQ(cursor_position, 0u); mouse.x = 2; mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 2u); + EXPECT_EQ(cursor_position, 2u); mouse.x = 2; mouse.y = 0; EXPECT_FALSE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 2u); + EXPECT_EQ(cursor_position, 2u); mouse.x = 1; mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 1u); + EXPECT_EQ(cursor_position, 1u); mouse.x = 3; mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 3u); + EXPECT_EQ(cursor_position, 3u); mouse.x = 4; mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 4u); + EXPECT_EQ(cursor_position, 4u); mouse.x = 5; mouse.y = 0; EXPECT_FALSE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 4u); + EXPECT_EQ(cursor_position, 4u); mouse.x = 5; mouse.y = 1; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 9u); + EXPECT_EQ(cursor_position, 9u); mouse.x = 1; mouse.y = 1; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 6u); + EXPECT_EQ(cursor_position, 6u); mouse.x = 4; mouse.y = 2; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 10u); + EXPECT_EQ(cursor_position, 10u); } TEST(InputTest, MouseClickComplex) { std::string content; - auto option = InputOption(); - auto input = Input(&content, &option); + int cursor_position = 0; + auto input = Input(&content, {.cursor_position = &cursor_position}); input->OnEvent(Event::Character("测")); input->OnEvent(Event::Character("试")); @@ -681,7 +698,7 @@ TEST(InputTest, MouseClickComplex) { input->OnEvent(Event::Character("a⃒")); input->OnEvent(Event::Character("ā")); - EXPECT_EQ(option.cursor_position(), 27u); + EXPECT_EQ(cursor_position, 27u); auto render = [&] { auto document = input->Render(); @@ -701,25 +718,25 @@ TEST(InputTest, MouseClickComplex) { mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 0); + EXPECT_EQ(cursor_position, 0); mouse.x = 0; mouse.y = 1; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 14); + EXPECT_EQ(cursor_position, 14); mouse.x = 1; mouse.y = 0; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 3); + EXPECT_EQ(cursor_position, 3); mouse.x = 1; mouse.y = 1; EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse))); render(); - EXPECT_EQ(option.cursor_position(), 17); + EXPECT_EQ(cursor_position, 17); } TEST(InputTest, OnEnter) { @@ -727,7 +744,7 @@ TEST(InputTest, OnEnter) { auto option = InputOption(); bool on_enter_called = false; option.on_enter = [&] { on_enter_called = true; }; - Component input = Input(&content, &option); + Component input = Input(&content, option); EXPECT_FALSE(on_enter_called); EXPECT_TRUE(input->OnEvent(Event::Return)); diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index 1b693b4..f182ea3 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -65,30 +65,29 @@ bool IsHorizontal(Direction direction) { /// @brief A list of items. The user can navigate through them. /// @ingroup component -class MenuBase : public ComponentBase { +class MenuBase : public ComponentBase, public MenuOption { public: - MenuBase(ConstStringListRef entries, int* selected, Ref option) - : entries_(entries), selected_(selected), option_(std::move(option)) {} + explicit MenuBase(MenuOption option) : MenuOption(std::move(option)) {} - bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); } + bool IsHorizontal() { return ftxui::IsHorizontal(direction); } void OnChange() { - if (option_->on_change) { - option_->on_change(); + if (on_change) { + on_change(); } } void OnEnter() { - if (option_->on_enter) { - option_->on_enter(); + if (on_enter) { + on_enter(); } } void Clamp() { - if (*selected_ != selected_previous_) { + if (selected() != selected_previous_) { SelectedTakeFocus(); } boxes_.resize(size()); - *selected_ = util::clamp(*selected_, 0, size() - 1); + selected() = util::clamp(selected(), 0, size() - 1); selected_previous_ = util::clamp(selected_previous_, 0, size() - 1); selected_focus_ = util::clamp(selected_focus_, 0, size() - 1); focused_entry() = util::clamp(focused_entry(), 0, size() - 1); @@ -111,19 +110,19 @@ class MenuBase : public ComponentBase { Elements elements; const bool is_menu_focused = Focused(); - if (option_->elements_prefix) { - elements.push_back(option_->elements_prefix()); + if (elements_prefix) { + elements.push_back(elements_prefix()); } elements.reserve(size()); for (int i = 0; i < size(); ++i) { - if (i != 0 && option_->elements_infix) { - elements.push_back(option_->elements_infix()); + if (i != 0 && elements_infix) { + elements.push_back(elements_infix()); } const bool is_focused = (focused_entry() == i) && is_menu_focused; - const bool is_selected = (*selected_ == i); + const bool is_selected = (selected() == i); const EntryState state = { - entries_[i], + entries[i], false, is_selected, is_focused, @@ -133,24 +132,24 @@ class MenuBase : public ComponentBase { is_menu_focused && (selected_focus_ == i) ? focus : nothing; const Element element = - (option_->entries.transform ? option_->entries.transform - : DefaultOptionTransform) // + (entries_option.transform ? entries_option.transform + : DefaultOptionTransform) // (state); elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) | focus_management); } - if (option_->elements_postfix) { - elements.push_back(option_->elements_postfix()); + if (elements_postfix) { + elements.push_back(elements_postfix()); } - if (IsInverted(option_->direction)) { + if (IsInverted(direction)) { std::reverse(elements.begin(), elements.end()); } const Element bar = IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements)); - if (!option_->underline.enabled) { + if (!underline.enabled) { return bar | reflect(box_); } @@ -158,15 +157,15 @@ class MenuBase : public ComponentBase { return vbox({ bar | xflex, separatorHSelector(first_, second_, // - option_->underline.color_active, - option_->underline.color_inactive), + underline.color_active, + underline.color_inactive), }) | reflect(box_); } else { return hbox({ separatorVSelector(first_, second_, // - option_->underline.color_active, - option_->underline.color_inactive), + underline.color_active, + underline.color_inactive), bar | yflex, }) | reflect(box_); @@ -174,17 +173,17 @@ class MenuBase : public ComponentBase { } void SelectedTakeFocus() { - selected_previous_ = *selected_; - selected_focus_ = *selected_; + selected_previous_ = selected(); + selected_focus_ = selected(); } void OnUp() { - switch (option_->direction) { + switch (direction) { case Direction::Up: - (*selected_)++; + selected()++; break; case Direction::Down: - (*selected_)--; + selected()--; break; case Direction::Left: case Direction::Right: @@ -193,12 +192,12 @@ class MenuBase : public ComponentBase { } void OnDown() { - switch (option_->direction) { + switch (direction) { case Direction::Up: - (*selected_)--; + selected()--; break; case Direction::Down: - (*selected_)++; + selected()++; break; case Direction::Left: case Direction::Right: @@ -207,12 +206,12 @@ class MenuBase : public ComponentBase { } void OnLeft() { - switch (option_->direction) { + switch (direction) { case Direction::Left: - (*selected_)++; + selected()++; break; case Direction::Right: - (*selected_)--; + selected()--; break; case Direction::Down: case Direction::Up: @@ -221,12 +220,12 @@ class MenuBase : public ComponentBase { } void OnRight() { - switch (option_->direction) { + switch (direction) { case Direction::Left: - (*selected_)--; + selected()--; break; case Direction::Right: - (*selected_)++; + selected()++; break; case Direction::Down: case Direction::Up: @@ -246,7 +245,7 @@ class MenuBase : public ComponentBase { } if (Focused()) { - const int old_selected = *selected_; + const int old_selected = selected(); if (event == Event::ArrowUp || event == Event::Character('k')) { OnUp(); } @@ -260,28 +259,28 @@ class MenuBase : public ComponentBase { OnRight(); } if (event == Event::PageUp) { - (*selected_) -= box_.y_max - box_.y_min; + selected() -= box_.y_max - box_.y_min; } if (event == Event::PageDown) { - (*selected_) += box_.y_max - box_.y_min; + selected() += box_.y_max - box_.y_min; } if (event == Event::Home) { - (*selected_) = 0; + selected() = 0; } if (event == Event::End) { - (*selected_) = size() - 1; + selected() = size() - 1; } if (event == Event::Tab && size()) { - *selected_ = (*selected_ + 1) % size(); + selected() = (selected() + 1) % size(); } if (event == Event::TabReverse && size()) { - *selected_ = (*selected_ + size() - 1) % size(); + selected() = (selected() + size() - 1) % size(); } - *selected_ = util::clamp(*selected_, 0, size() - 1); + selected() = util::clamp(selected(), 0, size() - 1); - if (*selected_ != old_selected) { - focused_entry() = *selected_; + if (selected() != old_selected) { + focused_entry() = selected(); SelectedTakeFocus(); OnChange(); return true; @@ -318,9 +317,9 @@ class MenuBase : public ComponentBase { focused_entry() = i; if (event.mouse().button == Mouse::Left && event.mouse().motion == Mouse::Released) { - if (*selected_ != i) { - *selected_ = i; - selected_previous_ = *selected_; + if (selected() != i) { + selected() = i; + selected_previous_ = selected(); OnChange(); } return true; @@ -333,18 +332,18 @@ class MenuBase : public ComponentBase { if (!box_.Contain(event.mouse().x, event.mouse().y)) { return false; } - const int old_selected = *selected_; + const int old_selected = selected(); if (event.mouse().button == Mouse::WheelUp) { - (*selected_)--; + selected()--; } if (event.mouse().button == Mouse::WheelDown) { - (*selected_)++; + selected()++; } - *selected_ = util::clamp(*selected_, 0, size() - 1); + selected() = util::clamp(selected(), 0, size() - 1); - if (*selected_ != old_selected) { + if (selected() != old_selected) { SelectedTakeFocus(); OnChange(); } @@ -381,41 +380,41 @@ class MenuBase : public ComponentBase { const bool is_menu_focused = Focused(); for (int i = 0; i < size(); ++i) { const bool is_focused = (focused_entry() == i) && is_menu_focused; - const bool is_selected = (*selected_ == i); + const bool is_selected = (selected() == i); float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT if (animator_background_[i].to() != target) { animator_background_[i] = animation::Animator( &animation_background_[i], target, - option_->entries.animated_colors.background.duration, - option_->entries.animated_colors.background.function); + entries_option.animated_colors.background.duration, + entries_option.animated_colors.background.function); animator_foreground_[i] = animation::Animator( &animation_foreground_[i], target, - option_->entries.animated_colors.foreground.duration, - option_->entries.animated_colors.foreground.function); + entries_option.animated_colors.foreground.duration, + entries_option.animated_colors.foreground.function); } } } Decorator AnimatedColorStyle(int i) { Decorator style = nothing; - if (option_->entries.animated_colors.foreground.enabled) { + if (entries_option.animated_colors.foreground.enabled) { style = style | color(Color::Interpolate( animation_foreground_[i], - option_->entries.animated_colors.foreground.inactive, - option_->entries.animated_colors.foreground.active)); + entries_option.animated_colors.foreground.inactive, + entries_option.animated_colors.foreground.active)); } - if (option_->entries.animated_colors.background.enabled) { + if (entries_option.animated_colors.background.enabled) { style = style | bgcolor(Color::Interpolate( animation_background_[i], - option_->entries.animated_colors.background.inactive, - option_->entries.animated_colors.background.active)); + entries_option.animated_colors.background.inactive, + entries_option.animated_colors.background.active)); } return style; } void UpdateUnderlineTarget() { - if (!option_->underline.enabled) { + if (!underline.enabled) { return; } @@ -426,66 +425,93 @@ class MenuBase : public ComponentBase { if (FirstTarget() >= animator_first_.to()) { animator_first_ = animation::Animator( - &first_, FirstTarget(), option_->underline.follower_duration, - option_->underline.follower_function, - option_->underline.follower_delay); + &first_, FirstTarget(), underline.follower_duration, + underline.follower_function, underline.follower_delay); animator_second_ = animation::Animator( - &second_, SecondTarget(), option_->underline.leader_duration, - option_->underline.leader_function, option_->underline.leader_delay); + &second_, SecondTarget(), underline.leader_duration, + underline.leader_function, underline.leader_delay); } else { animator_first_ = animation::Animator( - &first_, FirstTarget(), option_->underline.leader_duration, - option_->underline.leader_function, option_->underline.leader_delay); + &first_, FirstTarget(), underline.leader_duration, + underline.leader_function, underline.leader_delay); animator_second_ = animation::Animator( - &second_, SecondTarget(), option_->underline.follower_duration, - option_->underline.follower_function, - option_->underline.follower_delay); + &second_, SecondTarget(), underline.follower_duration, + underline.follower_function, underline.follower_delay); } } - bool Focusable() const final { return entries_.size(); } - int& focused_entry() { return option_->focused_entry(); } - int size() const { return int(entries_.size()); } + bool Focusable() const final { return entries.size(); } + int size() const { return int(entries.size()); } float FirstTarget() { if (boxes_.empty()) { return 0.F; } - const int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min - : boxes_[*selected_].y_min - box_.y_min; + const int value = IsHorizontal() ? boxes_[selected()].x_min - box_.x_min + : boxes_[selected()].y_min - box_.y_min; return float(value); } float SecondTarget() { if (boxes_.empty()) { return 0.F; } - const int value = IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min - : boxes_[*selected_].y_max - box_.y_min; + const int value = IsHorizontal() ? boxes_[selected()].x_max - box_.x_min + : boxes_[selected()].y_max - box_.y_min; return float(value); } protected: - ConstStringListRef entries_; - int* selected_; - int selected_previous_ = *selected_; - int selected_focus_ = *selected_; - Ref option_; + int selected_previous_ = selected(); + int selected_focus_ = selected(); + // Mouse click support: std::vector boxes_; Box box_; + // Animation support: float first_ = 0.F; float second_ = 0.F; animation::Animator animator_first_ = animation::Animator(&first_, 0.F); animation::Animator animator_second_ = animation::Animator(&second_, 0.F); - std::vector animator_background_; std::vector animator_foreground_; std::vector animation_background_; std::vector animation_foreground_; }; +/// @brief A list of text. The focused element is selected. +/// @param option a structure containing all the paramters. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::TerminalOutput(); +/// std::vector entries = { +/// "entry 1", +/// "entry 2", +/// "entry 3", +/// }; +/// int selected = 0; +/// auto menu = Menu({ +/// .entries = &entries, +/// .selected = &selected, +/// }); +/// screen.Loop(menu); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// > entry 1 +/// entry 2 +/// entry 3 +/// ``` +Component Menu(MenuOption option) { + return Make(std::move(option)); +} + /// @brief A list of text. The focused element is selected. /// @param entries The list of entries in the menu. /// @param selected The index of the currently selected element. @@ -513,10 +539,10 @@ class MenuBase : public ComponentBase { /// entry 2 /// entry 3 /// ``` -Component Menu(ConstStringListRef entries, - int* selected, - Ref option) { - return Make(entries, selected, std::move(option)); +Component Menu(ConstStringListRef entries, int* selected, MenuOption option) { + option.entries = entries; + option.selected = selected; + return Menu(std::move(option)); } /// @brief An horizontal list of elements. The user can navigate through them. @@ -554,11 +580,41 @@ Component Toggle(ConstStringListRef entries, int* selected) { /// entry 2 /// entry 3 /// ``` -Component MenuEntry(ConstStringRef label, Ref option) { - class Impl : public ComponentBase { +Component MenuEntry(ConstStringRef label, MenuEntryOption option) { + option.label = label; + return MenuEntry(std::move(option)); +} + +/// @brief A specific menu entry. They can be put into a Container::Vertical to +/// form a menu. +/// @param option The parameters. +/// @ingroup component +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::TerminalOutput(); +/// int selected = 0; +/// auto menu = Container::Vertical({ +/// MenuEntry({.label = "entry 1"}), +/// MenuEntry({.label = "entry 2"}), +/// MenuEntry({.label = "entry 3"}), +/// }, &selected); +/// screen.Loop(menu); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// > entry 1 +/// entry 2 +/// entry 3 +/// ``` +Component MenuEntry(MenuEntryOption option) { + class Impl : public ComponentBase, public MenuEntryOption { public: - Impl(ConstStringRef label, Ref option) - : label_(std::move(label)), option_(std::move(option)) {} + explicit Impl(MenuEntryOption option) + : MenuEntryOption(std::move(option)) {} private: Element Render() override { @@ -566,14 +622,14 @@ Component MenuEntry(ConstStringRef label, Ref option) { UpdateAnimationTarget(); const EntryState state = { - *label_, + label(), false, hovered_, focused, }; const Element element = - (option_->transform ? option_->transform : DefaultOptionTransform) // + (transform ? transform : DefaultOptionTransform) // (state); auto focus_management = focused ? select : nothing; @@ -586,30 +642,28 @@ Component MenuEntry(ConstStringRef label, Ref option) { if (target == animator_background_.to()) { return; } - animator_background_ = - animation::Animator(&animation_background_, target, - option_->animated_colors.background.duration, - option_->animated_colors.background.function); - animator_foreground_ = - animation::Animator(&animation_foreground_, target, - option_->animated_colors.foreground.duration, - option_->animated_colors.foreground.function); + animator_background_ = animation::Animator( + &animation_background_, target, animated_colors.background.duration, + animated_colors.background.function); + animator_foreground_ = animation::Animator( + &animation_foreground_, target, animated_colors.foreground.duration, + animated_colors.foreground.function); } Decorator AnimatedColorStyle() { Decorator style = nothing; - if (option_->animated_colors.foreground.enabled) { - style = style | color(Color::Interpolate( - animation_foreground_, - option_->animated_colors.foreground.inactive, - option_->animated_colors.foreground.active)); + if (animated_colors.foreground.enabled) { + style = style | + color(Color::Interpolate(animation_foreground_, + animated_colors.foreground.inactive, + animated_colors.foreground.active)); } - if (option_->animated_colors.background.enabled) { - style = style | bgcolor(Color::Interpolate( - animation_background_, - option_->animated_colors.background.inactive, - option_->animated_colors.background.active)); + if (animated_colors.background.enabled) { + style = style | + bgcolor(Color::Interpolate(animation_background_, + animated_colors.background.inactive, + animated_colors.background.active)); } return style; } @@ -640,8 +694,7 @@ Component MenuEntry(ConstStringRef label, Ref option) { animator_foreground_.OnAnimation(params); } - ConstStringRef label_; - Ref option_; + MenuEntryOption option_; Box box_; bool hovered_ = false; @@ -653,7 +706,7 @@ Component MenuEntry(ConstStringRef label, Ref option) { animation::Animator(&animation_foreground_, 0.F); }; - return Make(std::move(label), std::move(option)); + return Make(std::move(option)); } } // namespace ftxui diff --git a/src/ftxui/component/menu_test.cpp b/src/ftxui/component/menu_test.cpp index 4eda990..910948f 100644 --- a/src/ftxui/component/menu_test.cpp +++ b/src/ftxui/component/menu_test.cpp @@ -23,9 +23,11 @@ TEST(MenuTest, RemoveEntries) { int focused_entry = 0; int selected = 0; std::vector entries = {"1", "2", "3"}; - MenuOption option; - option.focused_entry = &focused_entry; - auto menu = Menu(&entries, &selected, option); + auto menu = Menu({ + .entries = &entries, + .selected = &selected, + .focused_entry = &focused_entry, + }); EXPECT_EQ(selected, 0); EXPECT_EQ(focused_entry, 0); @@ -52,10 +54,8 @@ TEST(MenuTest, DirectionDown) { int selected = 0; std::vector entries = {"1", "2", "3"}; MenuOption option; - auto menu = Menu(&entries, &selected, &option); + auto menu = Menu(&entries, &selected, {.direction = Direction::Down}); - selected = 0; - option.direction = Direction::Down; Screen screen(4, 3); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -80,9 +80,7 @@ TEST(MenuTest, DirectionDown) { TEST(MenuTest, DirectionsUp) { int selected = 0; std::vector entries = {"1", "2", "3"}; - MenuOption option; - auto menu = Menu(&entries, &selected, &option); - option.direction = Direction::Up; + auto menu = Menu(&entries, &selected, {.direction = Direction::Up}); Screen screen(4, 3); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -106,9 +104,7 @@ TEST(MenuTest, DirectionsUp) { TEST(MenuTest, DirectionsRight) { int selected = 0; std::vector entries = {"1", "2", "3"}; - MenuOption option; - auto menu = Menu(&entries, &selected, &option); - option.direction = Direction::Right; + auto menu = Menu(&entries, &selected, {.direction = Direction::Right}); Screen screen(10, 1); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -132,9 +128,7 @@ TEST(MenuTest, DirectionsRight) { TEST(MenuTest, DirectionsLeft) { int selected = 0; std::vector entries = {"1", "2", "3"}; - MenuOption option; - auto menu = Menu(&entries, &selected, &option); - option.direction = Direction::Left; + auto menu = Menu(&entries, &selected, {.direction = Direction::Left}); Screen screen(10, 1); Render(screen, menu->Render()); EXPECT_EQ(screen.ToString(), @@ -158,8 +152,7 @@ TEST(MenuTest, DirectionsLeft) { TEST(MenuTest, AnimationsHorizontal) { int selected = 0; std::vector entries = {"1", "2", "3"}; - auto option = MenuOption::HorizontalAnimated(); - auto menu = Menu(&entries, &selected, &option); + auto menu = Menu(&entries, &selected, MenuOption::HorizontalAnimated()); { Screen screen(4, 3); Render(screen, menu->Render()); @@ -195,8 +188,7 @@ TEST(MenuTest, AnimationsHorizontal) { TEST(MenuTest, AnimationsVertical) { int selected = 0; std::vector entries = {"1", "2", "3"}; - auto option = MenuOption::VerticalAnimated(); - auto menu = Menu(&entries, &selected, &option); + auto menu = Menu(&entries, &selected, MenuOption::VerticalAnimated()); { Screen screen(10, 3); Render(screen, menu->Render()); diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index 10f4bf0..00ac702 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -21,12 +21,9 @@ namespace { /// @brief A list of selectable element. One and only one can be selected at /// the same time. /// @ingroup component -class RadioboxBase : public ComponentBase { +class RadioboxBase : public ComponentBase, public RadioboxOption { public: - RadioboxBase(ConstStringListRef entries, - int* selected, - Ref option) - : entries_(entries), selected_(selected), option_(std::move(option)) {} + explicit RadioboxBase(RadioboxOption option) : RadioboxOption(option) {} private: Element Render() override { @@ -41,14 +38,13 @@ class RadioboxBase : public ComponentBase { : is_menu_focused ? focus : select; auto state = EntryState{ - entries_[i], - *selected_ == i, + entries[i], + selected() == i, is_selected, is_focused, }; auto element = - (option_->transform ? option_->transform - : RadioboxOption::Simple().transform)(state); + (transform ? transform : RadioboxOption::Simple().transform)(state); elements.push_back(element | focus_management | reflect(boxes_[i])); } @@ -97,14 +93,14 @@ class RadioboxBase : public ComponentBase { if (hovered_ != old_hovered) { focused_entry() = hovered_; - option_->on_change(); + on_change(); return true; } } if (event == Event::Character(' ') || event == Event::Return) { - *selected_ = hovered_; - option_->on_change(); + selected() = hovered_; + on_change(); return true; } @@ -126,9 +122,9 @@ class RadioboxBase : public ComponentBase { focused_entry() = i; if (event.mouse().button == Mouse::Left && event.mouse().motion == Mouse::Released) { - if (*selected_ != i) { - *selected_ = i; - option_->on_change(); + if (selected() != i) { + selected() = i; + on_change(); } return true; @@ -154,7 +150,7 @@ class RadioboxBase : public ComponentBase { hovered_ = util::clamp(hovered_, 0, size() - 1); if (hovered_ != old_hovered) { - option_->on_change(); + on_change(); } return true; @@ -162,25 +158,54 @@ class RadioboxBase : public ComponentBase { void Clamp() { boxes_.resize(size()); - *selected_ = util::clamp(*selected_, 0, size() - 1); + selected() = util::clamp(selected(), 0, size() - 1); focused_entry() = util::clamp(focused_entry(), 0, size() - 1); hovered_ = util::clamp(hovered_, 0, size() - 1); } - bool Focusable() const final { return entries_.size(); } - int& focused_entry() { return option_->focused_entry(); } - int size() const { return int(entries_.size()); } + bool Focusable() const final { return entries.size(); } + int size() const { return int(entries.size()); } - ConstStringListRef entries_; - int* selected_; - int hovered_ = *selected_; + int hovered_ = selected(); std::vector boxes_; Box box_; - Ref option_; }; } // namespace +/// @brief A list of element, where only one can be selected. +/// @param option The parameters +/// @ingroup component +/// @see RadioboxBase +/// +/// ### Example +/// +/// ```cpp +/// auto screen = ScreenInteractive::TerminalOutput(); +/// std::vector entries = { +/// "entry 1", +/// "entry 2", +/// "entry 3", +/// }; +/// int selected = 0; +/// auto menu = Radiobox({ +/// .entries = entries, +/// .selected = &selected, +/// }); +/// screen.Loop(menu); +/// ``` +/// +/// ### Output +/// +/// ```bash +/// ◉ entry 1 +/// ○ entry 2 +/// ○ entry 3 +/// ``` +Component Radiobox(RadioboxOption option) { + return Make(std::move(option)); +} + /// @brief A list of element, where only one can be selected. /// @param entries The list of entries in the list. /// @param selected The index of the currently selected element. @@ -211,8 +236,10 @@ class RadioboxBase : public ComponentBase { /// ``` Component Radiobox(ConstStringListRef entries, int* selected, - Ref option) { - return Make(entries, selected, std::move(option)); + RadioboxOption option) { + option.entries = entries; + option.selected = selected; + return Make(std::move(option)); } } // namespace ftxui diff --git a/src/ftxui/component/slider.cpp b/src/ftxui/component/slider.cpp index dc90148..59199a8 100644 --- a/src/ftxui/component/slider.cpp +++ b/src/ftxui/component/slider.cpp @@ -35,24 +35,24 @@ Decorator flexDirection(Direction direction) { template class SliderBase : public ComponentBase { public: - explicit SliderBase(Ref> options) - : value_(options->value), - min_(options->min), - max_(options->max), - increment_(options->increment), + explicit SliderBase(SliderOption options) + : value_(options.value), + min_(options.min), + max_(options.max), + increment_(options.increment), options_(options) {} Element Render() override { - auto gauge_color = Focused() ? color(options_->color_active) - : color(options_->color_inactive); + auto gauge_color = Focused() ? color(options_.color_active) + : color(options_.color_inactive); const float percent = float(value_() - min_()) / float(max_() - min_()); - return gaugeDirection(percent, options_->direction) | - flexDirection(options_->direction) | reflect(gauge_box_) | + return gaugeDirection(percent, options_.direction) | + flexDirection(options_.direction) | reflect(gauge_box_) | gauge_color; } void OnLeft() { - switch (options_->direction) { + switch (options_.direction) { case Direction::Right: value_() -= increment_(); break; @@ -66,7 +66,7 @@ class SliderBase : public ComponentBase { } void OnRight() { - switch (options_->direction) { + switch (options_.direction) { case Direction::Right: value_() += increment_(); break; @@ -80,7 +80,7 @@ class SliderBase : public ComponentBase { } void OnUp() { - switch (options_->direction) { + switch (options_.direction) { case Direction::Up: value_() -= increment_(); break; @@ -94,7 +94,7 @@ class SliderBase : public ComponentBase { } void OnDown() { - switch (options_->direction) { + switch (options_.direction) { case Direction::Down: value_() -= increment_(); break; @@ -153,7 +153,7 @@ class SliderBase : public ComponentBase { } if (captured_mouse_) { - switch (options_->direction) { + switch (options_.direction) { case Direction::Right: { value_() = min_() + (event.mouse().x - gauge_box_.x_min) * (max_() - min_()) / @@ -192,15 +192,14 @@ class SliderBase : public ComponentBase { ConstRef min_; ConstRef max_; ConstRef increment_; - Ref> options_; + SliderOption options_; Box gauge_box_; CapturedMouse captured_mouse_; }; class SliderWithLabel : public ComponentBase { public: - SliderWithLabel(ConstStringRef label, Component inner) - : label_(std::move(label)) { + SliderWithLabel(ConstStringRef label, Component inner) : label_(label) { Add(std::move(inner)); SetActiveChild(ChildAt(0)); } diff --git a/src/ftxui/component/toggle_test.cpp b/src/ftxui/component/toggle_test.cpp index b3dd5b0..722a27c 100644 --- a/src/ftxui/component/toggle_test.cpp +++ b/src/ftxui/component/toggle_test.cpp @@ -91,7 +91,7 @@ TEST(ToggleTest, OnChange) { auto option = MenuOption::Toggle(); option.on_change = [&] { counter++; }; - auto toggle = Menu(&entries, &selected, &option); + auto toggle = Menu(&entries, &selected, option); EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left. EXPECT_EQ(counter, 0); @@ -120,7 +120,7 @@ TEST(ToggleTest, OnEnter) { auto option = MenuOption::Toggle(); option.on_enter = [&] { counter++; }; - auto toggle = Menu(&entries, &selected, &option); + auto toggle = Menu(&entries, &selected, option); EXPECT_FALSE(toggle->OnEvent(Event::ArrowLeft)); // Reached far left. EXPECT_TRUE(toggle->OnEvent(Event::Return)); diff --git a/src/ftxui/dom/canvas.cpp b/src/ftxui/dom/canvas.cpp index 5270ad9..58ca4e3 100644 --- a/src/ftxui/dom/canvas.cpp +++ b/src/ftxui/dom/canvas.cpp @@ -845,9 +845,11 @@ class CanvasNodeBase : public Node { } // namespace /// @brief Produce an element from a Canvas, or a reference to a Canvas. +// NOLINTNEXTLINE Element canvas(ConstRef canvas) { class Impl : public CanvasNodeBase { public: + // NOLINTNEXTLINE explicit Impl(ConstRef canvas) : canvas_(std::move(canvas)) { requirement_.min_x = (canvas_->width() + 1) / 2; requirement_.min_y = (canvas_->height() + 3) / 4; diff --git a/src/ftxui/dom/hyperlink.cpp b/src/ftxui/dom/hyperlink.cpp index 6ce8a7a..4d2dd05 100644 --- a/src/ftxui/dom/hyperlink.cpp +++ b/src/ftxui/dom/hyperlink.cpp @@ -13,10 +13,10 @@ namespace ftxui { class Hyperlink : public NodeDecorator { public: Hyperlink(Element child, std::string link) - : NodeDecorator(std::move(child)), link_(link) {} + : NodeDecorator(std::move(child)), link_(std::move(link)) {} void Render(Screen& screen) override { - uint8_t hyperlink_id = screen.RegisterHyperlink(link_); + const uint8_t hyperlink_id = screen.RegisterHyperlink(link_); 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).hyperlink = hyperlink_id; @@ -44,7 +44,7 @@ class Hyperlink : public NodeDecorator { /// hyperlink("https://github.com/ArthurSonzogni/FTXUI", "link"); /// ``` Element hyperlink(std::string link, Element child) { - return std::make_shared(std::move(child), link); + return std::make_shared(std::move(child), std::move(link)); } /// @brief Decorate using an hyperlink. @@ -61,6 +61,7 @@ Element hyperlink(std::string link, Element child) { /// Element document = /// text("red") | hyperlink("https://github.com/Arthursonzogni/FTXUI"); /// ``` +// NOLINTNEXTLINE Decorator hyperlink(std::string link) { return [link](Element child) { return hyperlink(link, std::move(child)); }; } diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index 0f2e377..dd0570f 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -1,5 +1,6 @@ #include // for size_t #include // for operator<<, stringstream, basic_ostream, flush, cout, ostream +#include #include // for _Rb_tree_const_iterator, map, operator!=, operator== #include // for allocator, allocator_traits<>::value_type #include // IWYU pragma: keep @@ -553,13 +554,13 @@ void Screen::ApplyShader() { } // clang-format on -uint8_t Screen::RegisterHyperlink(std::string link) { +uint8_t Screen::RegisterHyperlink(const std::string& link) { for (size_t i = 0; i < hyperlinks_.size(); ++i) { if (hyperlinks_[i] == link) { return i; } } - if (hyperlinks_.size() == 255) { + if (hyperlinks_.size() == std::numeric_limits::max()) { return 0; } hyperlinks_.push_back(link); diff --git a/src/ftxui/screen/string.cpp b/src/ftxui/screen/string.cpp index 37305e7..b477acb 100644 --- a/src/ftxui/screen/string.cpp +++ b/src/ftxui/screen/string.cpp @@ -7,22 +7,17 @@ #include "ftxui/screen/string.hpp" -#include // for size_t -#include // for array -#include // for uint32_t, uint8_t, uint16_t, int32_t -#include // for string, basic_string, wstring -#include // for _Swallow_assign, ignore +#include // for array +#include // for size_t +#include // for uint32_t, uint8_t, uint16_t, int32_t +#include // for string, basic_string, wstring +#include // for _Swallow_assign, ignore #include "ftxui/screen/deprecated.hpp" // for wchar_width, wstring_width #include "ftxui/screen/string_internal.hpp" // for WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, GlyphCount, GlyphIterate, GlyphNext, GlyphPrevious, IsCombining, IsControl, IsFullWidth, Utf8ToWordBreakProperty namespace { -using ftxui::EatCodePoint; -using ftxui::IsCombining; -using ftxui::IsControl; -using ftxui::IsFullWidth; - struct Interval { uint32_t first; uint32_t last; @@ -1565,8 +1560,9 @@ bool IsControl(uint32_t ucs) { if (ucs == 0) { return true; } - if (ucs < 32) { // NOLINT - return ucs != 10; // 10 => Line feed. + if (ucs < 32) { // NOLINT + const uint32_t LINE_FEED = 10; + return ucs != LINE_FEED; } if (ucs >= 0x7f && ucs < 0xa0) { // NOLINT return true;