Remove Ref<XxxOption> and add new interfaces. (#686)

1. Stop taking Ref<XxxOption> in Component constructors. Instead, use
   the XxxOption directly. Passing by copy avoid problems developers had
   where one was shared in between multiple component, causing issues.

2. Add variants of most component constructors taking a struct only.

This replaces:
https://github.com/ArthurSonzogni/FTXUI/pull/670

This fixes:
https://github.com/ArthurSonzogni/FTXUI/issues/426
This commit is contained in:
Arthur Sonzogni 2023-06-25 17:22:05 +02:00 committed by GitHub
parent e73e7f0d68
commit 455998d759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 918 additions and 693 deletions

View File

@ -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,

View File

@ -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<XxxOption> 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<T> options);
Component ResizableSplit(ResizableSplitOption options);
```
### Dom
- Feature: Add `hyperlink` decorator. For instance:

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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_,

View File

@ -110,7 +110,7 @@ int main() {
Component VMenu1(std::vector<std::string>* 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<std::string>* entries, int* selected) {
Component VMenu2(std::vector<std::string>* 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<std::string>* entries, int* selected) {
Component VMenu3(std::vector<std::string>* 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<std::string>* entries, int* selected) {
Component VMenu4(std::vector<std::string>* 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<std::string>* entries, int* selected) {
Component VMenu5(std::vector<std::string>* 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<std::string>* entries, int* selected) {
Component VMenu7(std::vector<std::string>* 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<std::string>* 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<std::string>* 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;

View File

@ -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<void()> on_click,
Ref<ButtonOption> = ButtonOption::Simple());
ButtonOption options = ButtonOption::Simple());
Component Checkbox(CheckboxOption options);
Component Checkbox(ConstStringRef label,
bool* checked,
Ref<CheckboxOption> option = CheckboxOption::Simple());
CheckboxOption options = CheckboxOption::Simple());
Component Input(StringRef content, Ref<InputOption> option = {});
Component Input(InputOption options = {});
Component Input(StringRef content, InputOption options = {});
Component Input(StringRef content,
StringRef placeholder,
Ref<InputOption> option = {});
InputOption options = {});
Component Menu(MenuOption options);
Component Menu(ConstStringListRef entries,
int* selected_,
Ref<MenuOption> = MenuOption::Vertical());
Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> = {});
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<RadioboxOption> option = {});
RadioboxOption options = {});
Component Dropdown(ConstStringListRef entries, int* selected);
Component Toggle(ConstStringListRef entries, int* selected);
// General slider constructor:
template <typename T>
Component Slider(SliderOption<T> options = {});
Component Slider(SliderOption<T> options);
// Shorthand without the `SliderOption` constructor:
Component Slider(ConstStringRef label,
@ -89,11 +94,11 @@ Component Slider(ConstStringRef label,
ConstRef<long> max = 100l,
ConstRef<long> 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<Element()>);
Component Renderer(std::function<Element()>);

View File

@ -72,6 +72,7 @@ struct AnimatedColorsOption {
/// @brief Option for the MenuEntry component.
/// @ingroup component
struct MenuEntryOption {
ConstStringRef label = "MenuEntry";
std::function<Element(const EntryState& state)> transform;
AnimatedColorsOption animated_colors;
};
@ -86,9 +87,12 @@ struct MenuOption {
static MenuOption VerticalAnimated();
static MenuOption Toggle();
ConstStringListRef entries; ///> The list of entries.
Ref<int> selected = 0; ///> The index of the selected entry.
// Style:
UnderlineOption underline;
MenuEntryOption entries;
MenuEntryOption entries_option;
Direction direction = Direction::Down;
std::function<Element()> elements_prefix;
std::function<Element()> elements_infix;
@ -115,6 +119,9 @@ struct ButtonOption {
Color background_active,
Color foreground_active);
ConstStringRef label = "Button";
std::function<void()> on_click = [] {};
// Style:
std::function<Element(const EntryState&)> transform;
AnimatedColorsOption animated_colors;
@ -126,6 +133,10 @@ struct CheckboxOption {
// Standard constructors:
static CheckboxOption Simple();
ConstStringRef label = "Checkbox";
Ref<bool> checked = false;
// Style:
std::function<Element(const EntryState&)> 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<int> selected = 0;
// Style:
std::function<Element(const EntryState&)> transform;

View File

@ -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;

View File

@ -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>(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<std::string>* ref) : ref_(ref) {}
ConstStringListRef(const std::vector<std::wstring>* 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:

View File

@ -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<ButtonBase>(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<void()> on_click,
Ref<ButtonOption> option) {
class Impl : public ComponentBase {
public:
Impl(ConstStringRef label,
std::function<void()> on_click,
Ref<ButtonOption> 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<void()> on_click_;
bool mouse_hover_ = false;
Box box_;
Ref<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_);
};
return Make<Impl>(std::move(label), std::move(on_click), std::move(option));
ButtonOption option) {
option.label = label;
option.on_click = std::move(on_click);
return Make<ButtonBase>(std::move(option));
}
} // namespace ftxui

View File

@ -14,10 +14,10 @@
namespace ftxui {
namespace {
class CheckboxBase : public ComponentBase {
class CheckboxBase : public ComponentBase, public CheckboxOption {
public:
CheckboxBase(ConstStringRef label, bool* state, Ref<CheckboxOption> 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<CheckboxOption> option_;
Box box_;
};
} // namespace
@ -109,10 +105,11 @@ class CheckboxBase : public ComponentBase {
/// ```bash
/// ☐ Make a sandwitch
/// ```
Component Checkbox(ConstStringRef label,
bool* checked,
Ref<CheckboxOption> option) {
return Make<CheckboxBase>(std::move(label), checked, std::move(option));
// NOLINTNEXTLINE
Component Checkbox(ConstStringRef label, bool* checked, CheckboxOption option) {
option.label = label;
option.checked = checked;
return Make<CheckboxBase>(std::move(option));
}
} // namespace ftxui

View File

@ -27,6 +27,7 @@ namespace ftxui {
/// ▼ Show details
/// <details component>
///  ```
// NOLINTNEXTLINE
Component Collapsible(ConstStringRef label, Component child, Ref<bool> show) {
class Impl : public ComponentBase {
public:
@ -44,7 +45,7 @@ Component Collapsible(ConstStringRef label, Component child, Ref<bool> 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->()),
}));
}

View File

@ -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;
}

View File

@ -34,17 +34,19 @@ std::vector<std::string> 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<InputOption> 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<std::string> lines = Split(*content_);
const std::vector<std::string> 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<std::string> lines = Split(*content_);
int& cursor_position = option_->cursor_position();
std::vector<std::string> 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<InputOption> 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<InputBase>(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<InputOption> option) {
return Make<InputBase>(std::move(content), std::move(option));
Component Input(StringRef content, InputOption option) {
option.content = content;
return Make<InputBase>(std::move(option));
}
Component Input(StringRef content,
StringRef placeholder,
Ref<InputOption> option) {
option->placeholder = placeholder;
return Make<InputBase>(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<InputBase>(std::move(option));
}
} // namespace ftxui

View File

@ -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) {
"\n"
"\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));

View File

@ -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<MenuOption> 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<MenuOption> option_;
int selected_previous_ = selected();
int selected_focus_ = selected();
// Mouse click support:
std::vector<Box> 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<animation::Animator> animator_background_;
std::vector<animation::Animator> animator_foreground_;
std::vector<float> animation_background_;
std::vector<float> 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<std::string> 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<MenuBase>(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<MenuOption> option) {
return Make<MenuBase>(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<MenuEntryOption> 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<MenuEntryOption> 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<MenuEntryOption> 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<MenuEntryOption> 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<MenuEntryOption> option) {
animator_foreground_.OnAnimation(params);
}
ConstStringRef label_;
Ref<MenuEntryOption> option_;
MenuEntryOption option_;
Box box_;
bool hovered_ = false;
@ -653,7 +706,7 @@ Component MenuEntry(ConstStringRef label, Ref<MenuEntryOption> option) {
animation::Animator(&animation_foreground_, 0.F);
};
return Make<Impl>(std::move(label), std::move(option));
return Make<Impl>(std::move(option));
}
} // namespace ftxui

View File

@ -23,9 +23,11 @@ TEST(MenuTest, RemoveEntries) {
int focused_entry = 0;
int selected = 0;
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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());

View File

@ -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<RadioboxOption> 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<Box> boxes_;
Box box_;
Ref<RadioboxOption> 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<std::string> 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<RadioboxBase>(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<RadioboxOption> option) {
return Make<RadioboxBase>(entries, selected, std::move(option));
RadioboxOption option) {
option.entries = entries;
option.selected = selected;
return Make<RadioboxBase>(std::move(option));
}
} // namespace ftxui

View File

@ -35,24 +35,24 @@ Decorator flexDirection(Direction direction) {
template <class T>
class SliderBase : public ComponentBase {
public:
explicit SliderBase(Ref<SliderOption<T>> options)
: value_(options->value),
min_(options->min),
max_(options->max),
increment_(options->increment),
explicit SliderBase(SliderOption<T> 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<T> min_;
ConstRef<T> max_;
ConstRef<T> increment_;
Ref<SliderOption<T>> options_;
SliderOption<T> 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));
}

View File

@ -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));

View File

@ -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> canvas) {
class Impl : public CanvasNodeBase {
public:
// NOLINTNEXTLINE
explicit Impl(ConstRef<Canvas> canvas) : canvas_(std::move(canvas)) {
requirement_.min_x = (canvas_->width() + 1) / 2;
requirement_.min_y = (canvas_->height() + 3) / 4;

View File

@ -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<Hyperlink>(std::move(child), link);
return std::make_shared<Hyperlink>(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)); };
}

View File

@ -1,5 +1,6 @@
#include <cstdint> // for size_t
#include <iostream> // for operator<<, stringstream, basic_ostream, flush, cout, ostream
#include <limits>
#include <map> // for _Rb_tree_const_iterator, map, operator!=, operator==
#include <memory> // for allocator, allocator_traits<>::value_type
#include <sstream> // 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<uint8_t>::max()) {
return 0;
}
hyperlinks_.push_back(link);

View File

@ -7,22 +7,17 @@
#include "ftxui/screen/string.hpp"
#include <stddef.h> // for size_t
#include <array> // for array
#include <cstdint> // for uint32_t, uint8_t, uint16_t, int32_t
#include <string> // for string, basic_string, wstring
#include <tuple> // for _Swallow_assign, ignore
#include <array> // for array
#include <cstddef> // for size_t
#include <cstdint> // for uint32_t, uint8_t, uint16_t, int32_t
#include <string> // for string, basic_string, wstring
#include <tuple> // 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;