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;