mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-18 09:08:08 +08:00
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:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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->()),
|
||||
}));
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -16,32 +16,36 @@ namespace ftxui {
|
||||
|
||||
TEST(InputTest, Init) {
|
||||
std::string content;
|
||||
int cursor_position = 0;
|
||||
auto option = InputOption();
|
||||
Component input = Input(&content, &option);
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
}
|
||||
|
||||
TEST(InputTest, Type) {
|
||||
std::string content;
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
Component input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
input->OnEvent(Event::Character("a"));
|
||||
EXPECT_EQ(content, "a");
|
||||
EXPECT_EQ(option.cursor_position(), 1);
|
||||
EXPECT_EQ(cursor_position, 1);
|
||||
|
||||
input->OnEvent(Event::Character('b'));
|
||||
EXPECT_EQ(content, "ab");
|
||||
EXPECT_EQ(option.cursor_position(), 2);
|
||||
EXPECT_EQ(cursor_position, 2);
|
||||
|
||||
input->OnEvent(Event::Return);
|
||||
EXPECT_EQ(content, "ab\n");
|
||||
EXPECT_EQ(option.cursor_position(), 3);
|
||||
EXPECT_EQ(cursor_position, 3);
|
||||
|
||||
input->OnEvent(Event::Character('c'));
|
||||
EXPECT_EQ(content, "ab\nc");
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
EXPECT_EQ(cursor_position, 4);
|
||||
|
||||
auto document = input->Render();
|
||||
|
||||
@@ -55,57 +59,59 @@ TEST(InputTest, Type) {
|
||||
|
||||
TEST(InputTest, ArrowLeftRight) {
|
||||
std::string content = "abc测测a测\na测\n";
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 1);
|
||||
EXPECT_EQ(cursor_position, 1);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 1);
|
||||
EXPECT_EQ(cursor_position, 1);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 2);
|
||||
EXPECT_EQ(cursor_position, 2);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 3);
|
||||
EXPECT_EQ(cursor_position, 3);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 6);
|
||||
EXPECT_EQ(cursor_position, 6);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
EXPECT_EQ(cursor_position, 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 10);
|
||||
EXPECT_EQ(cursor_position, 10);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 13);
|
||||
EXPECT_EQ(cursor_position, 13);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 14);
|
||||
EXPECT_EQ(cursor_position, 14);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 15);
|
||||
EXPECT_EQ(cursor_position, 15);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 18);
|
||||
EXPECT_EQ(cursor_position, 18);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 19);
|
||||
EXPECT_EQ(cursor_position, 19);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 19);
|
||||
EXPECT_EQ(cursor_position, 19);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_EQ(option.cursor_position(), 18);
|
||||
EXPECT_EQ(cursor_position, 18);
|
||||
}
|
||||
|
||||
TEST(InputTest, ArrowUpDown) {
|
||||
@@ -117,70 +123,75 @@ TEST(InputTest, ArrowUpDown) {
|
||||
"00\n"
|
||||
"0\n"
|
||||
"";
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
EXPECT_EQ(cursor_position, 4);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 11);
|
||||
EXPECT_EQ(cursor_position, 11);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 21);
|
||||
EXPECT_EQ(cursor_position, 21);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 29);
|
||||
EXPECT_EQ(cursor_position, 29);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 36);
|
||||
EXPECT_EQ(cursor_position, 36);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 40);
|
||||
EXPECT_EQ(cursor_position, 40);
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 40);
|
||||
EXPECT_EQ(cursor_position, 40);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 36);
|
||||
EXPECT_EQ(cursor_position, 36);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 29);
|
||||
EXPECT_EQ(cursor_position, 29);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 21);
|
||||
EXPECT_EQ(cursor_position, 21);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 11);
|
||||
EXPECT_EQ(cursor_position, 11);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
EXPECT_EQ(cursor_position, 4);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_EQ(option.cursor_position(), 3);
|
||||
EXPECT_EQ(cursor_position, 3);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 7);
|
||||
EXPECT_EQ(cursor_position, 7);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 14);
|
||||
EXPECT_EQ(cursor_position, 14);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 24);
|
||||
EXPECT_EQ(cursor_position, 24);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 32);
|
||||
EXPECT_EQ(cursor_position, 32);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 39);
|
||||
EXPECT_EQ(cursor_position, 39);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 40);
|
||||
EXPECT_EQ(cursor_position, 40);
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowDown));
|
||||
EXPECT_EQ(option.cursor_position(), 40);
|
||||
EXPECT_EQ(cursor_position, 40);
|
||||
|
||||
option.cursor_position() = 39;
|
||||
cursor_position = 39;
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 32);
|
||||
EXPECT_EQ(cursor_position, 32);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 24);
|
||||
EXPECT_EQ(cursor_position, 24);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 14);
|
||||
EXPECT_EQ(cursor_position, 14);
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_EQ(option.cursor_position(), 7);
|
||||
EXPECT_EQ(cursor_position, 7);
|
||||
}
|
||||
|
||||
TEST(InputTest, Insert) {
|
||||
std::string content;
|
||||
Component input = Input(&content);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('a')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
@@ -213,8 +224,10 @@ TEST(InputTest, Insert) {
|
||||
|
||||
TEST(InputTest, Home) {
|
||||
std::string content;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('a')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
@@ -224,21 +237,22 @@ TEST(InputTest, Home) {
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('c')));
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Home));
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('-')));
|
||||
EXPECT_EQ(option.cursor_position(), 1u);
|
||||
EXPECT_EQ(cursor_position, 1u);
|
||||
EXPECT_EQ(content, "-abc\n测bc");
|
||||
}
|
||||
|
||||
TEST(InputTest, End) {
|
||||
std::string content;
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('a')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
@@ -250,17 +264,18 @@ TEST(InputTest, End) {
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowUp));
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 2u);
|
||||
EXPECT_EQ(cursor_position, 2u);
|
||||
|
||||
input->OnEvent(Event::End);
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
}
|
||||
|
||||
TEST(InputTest, Delete) {
|
||||
std::string content;
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
auto input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('a')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
@@ -271,38 +286,38 @@ TEST(InputTest, Delete) {
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('c')));
|
||||
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 8u);
|
||||
EXPECT_EQ(cursor_position, 8u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abc\n测b");
|
||||
EXPECT_EQ(option.cursor_position(), 8u);
|
||||
EXPECT_EQ(cursor_position, 8u);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abc\n测b");
|
||||
EXPECT_EQ(option.cursor_position(), 8u);
|
||||
EXPECT_EQ(cursor_position, 8u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abc\nb");
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
EXPECT_EQ(cursor_position, 4u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abcb");
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
EXPECT_EQ(cursor_position, 3u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Delete));
|
||||
EXPECT_EQ(content, "abc");
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
EXPECT_EQ(cursor_position, 3u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
@@ -318,9 +333,10 @@ TEST(InputTest, Delete) {
|
||||
|
||||
TEST(InputTest, Backspace) {
|
||||
std::string content;
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
auto input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('a')));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('b')));
|
||||
@@ -331,45 +347,45 @@ TEST(InputTest, Backspace) {
|
||||
EXPECT_TRUE(input->OnEvent(Event::Character('c')));
|
||||
|
||||
EXPECT_EQ(content, "abc\n测bc");
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "abc\n测b");
|
||||
EXPECT_EQ(option.cursor_position(), 8u);
|
||||
EXPECT_EQ(cursor_position, 8u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeft));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "abc\nb");
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
EXPECT_EQ(cursor_position, 4u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "abcb");
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
EXPECT_EQ(cursor_position, 3u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "abb");
|
||||
EXPECT_EQ(option.cursor_position(), 2u);
|
||||
EXPECT_EQ(cursor_position, 2u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "ab");
|
||||
EXPECT_EQ(option.cursor_position(), 1u);
|
||||
EXPECT_EQ(cursor_position, 1u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "b");
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "b");
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRight));
|
||||
EXPECT_TRUE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "");
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::Backspace));
|
||||
EXPECT_EQ(content, "");
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrow) {
|
||||
@@ -377,192 +393,193 @@ TEST(InputTest, CtrlArrow) {
|
||||
"word word 测ord wo测d word\n"
|
||||
"coucou coucou coucou\n"
|
||||
"coucou coucou coucou\n";
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 1000;
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 1000;
|
||||
auto input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 67);
|
||||
EXPECT_EQ(cursor_position, 67);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 60);
|
||||
EXPECT_EQ(cursor_position, 60);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 53);
|
||||
EXPECT_EQ(cursor_position, 53);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 46);
|
||||
EXPECT_EQ(cursor_position, 46);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 39);
|
||||
EXPECT_EQ(cursor_position, 39);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 29);
|
||||
EXPECT_EQ(cursor_position, 29);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 24);
|
||||
EXPECT_EQ(cursor_position, 24);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 17);
|
||||
EXPECT_EQ(cursor_position, 17);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 10);
|
||||
EXPECT_EQ(cursor_position, 10);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 5);
|
||||
EXPECT_EQ(cursor_position, 5);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
EXPECT_EQ(cursor_position, 4);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
EXPECT_EQ(cursor_position, 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 16);
|
||||
EXPECT_EQ(cursor_position, 16);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 23);
|
||||
EXPECT_EQ(cursor_position, 23);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 28);
|
||||
EXPECT_EQ(cursor_position, 28);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 35);
|
||||
EXPECT_EQ(cursor_position, 35);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 45);
|
||||
EXPECT_EQ(cursor_position, 45);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 52);
|
||||
EXPECT_EQ(cursor_position, 52);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 59);
|
||||
EXPECT_EQ(cursor_position, 59);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 66);
|
||||
EXPECT_EQ(cursor_position, 66);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 73);
|
||||
EXPECT_EQ(cursor_position, 73);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowLeft2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 33;
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 33;
|
||||
auto input = Input(&content, {
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 31);
|
||||
EXPECT_EQ(cursor_position, 31);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 23);
|
||||
EXPECT_EQ(cursor_position, 23);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 15);
|
||||
EXPECT_EQ(cursor_position, 15);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
EXPECT_EQ(cursor_position, 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 3);
|
||||
EXPECT_EQ(cursor_position, 3);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowLeftCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight) {
|
||||
std::string content =
|
||||
"word word 测ord wo测d word\n"
|
||||
"coucou dfqdsf jmlkjm";
|
||||
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 2;
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 2;
|
||||
auto input = Input(&content, {.cursor_position = &cursor_position});
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 4);
|
||||
EXPECT_EQ(cursor_position, 4);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 9);
|
||||
EXPECT_EQ(cursor_position, 9);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 16);
|
||||
EXPECT_EQ(cursor_position, 16);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 23);
|
||||
EXPECT_EQ(cursor_position, 23);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 28);
|
||||
EXPECT_EQ(cursor_position, 28);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 35);
|
||||
EXPECT_EQ(cursor_position, 35);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 42);
|
||||
EXPECT_EQ(cursor_position, 42);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 49);
|
||||
EXPECT_EQ(cursor_position, 49);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 49);
|
||||
EXPECT_EQ(cursor_position, 49);
|
||||
}
|
||||
|
||||
TEST(InputTest, CtrlArrowRight2) {
|
||||
std::string content = " word word 测ord wo测d word ";
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
auto input = Input(&content, {.cursor_position = &cursor_position});
|
||||
|
||||
// Use CTRL+Left several time
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 7);
|
||||
EXPECT_EQ(cursor_position, 7);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 13);
|
||||
EXPECT_EQ(cursor_position, 13);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 21);
|
||||
EXPECT_EQ(cursor_position, 21);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 29);
|
||||
EXPECT_EQ(cursor_position, 29);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 35);
|
||||
EXPECT_EQ(cursor_position, 35);
|
||||
|
||||
EXPECT_TRUE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 38);
|
||||
EXPECT_EQ(cursor_position, 38);
|
||||
|
||||
EXPECT_FALSE(input->OnEvent(Event::ArrowRightCtrl));
|
||||
EXPECT_EQ(option.cursor_position(), 38);
|
||||
EXPECT_EQ(cursor_position, 38);
|
||||
}
|
||||
|
||||
TEST(InputTest, TypePassword) {
|
||||
std::string content;
|
||||
std::string placeholder;
|
||||
auto option = InputOption();
|
||||
option.cursor_position = 0;
|
||||
option.password = true;
|
||||
Component input = Input(&content, &placeholder, &option);
|
||||
int cursor_position = 0;
|
||||
Component input = Input(&content, &placeholder,
|
||||
{
|
||||
.password = true,
|
||||
.cursor_position = &cursor_position,
|
||||
});
|
||||
|
||||
input->OnEvent(Event::Character('a'));
|
||||
EXPECT_EQ(content, "a");
|
||||
EXPECT_EQ(option.cursor_position(), 1u);
|
||||
EXPECT_EQ(cursor_position, 1u);
|
||||
|
||||
input->OnEvent(Event::Character('b'));
|
||||
EXPECT_EQ(content, "ab");
|
||||
EXPECT_EQ(option.cursor_position(), 2u);
|
||||
EXPECT_EQ(cursor_position, 2u);
|
||||
|
||||
auto document = input->Render();
|
||||
auto screen = Screen::Create(Dimension::Fit(document));
|
||||
@@ -573,8 +590,8 @@ TEST(InputTest, TypePassword) {
|
||||
|
||||
TEST(InputTest, MouseClick) {
|
||||
std::string content;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
auto input = Input(&content, {.cursor_position = &cursor_position});
|
||||
|
||||
input->OnEvent(Event::Character("a"));
|
||||
input->OnEvent(Event::Character("b"));
|
||||
@@ -588,7 +605,7 @@ TEST(InputTest, MouseClick) {
|
||||
input->OnEvent(Event::Return);
|
||||
|
||||
EXPECT_EQ(content, "abcd\nabcd\n");
|
||||
EXPECT_EQ(option.cursor_position(), 10u);
|
||||
EXPECT_EQ(cursor_position, 10u);
|
||||
|
||||
auto render = [&] {
|
||||
auto document = input->Render();
|
||||
@@ -596,7 +613,7 @@ TEST(InputTest, MouseClick) {
|
||||
Render(screen, document);
|
||||
};
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 10u);
|
||||
EXPECT_EQ(cursor_position, 10u);
|
||||
|
||||
Mouse mouse;
|
||||
mouse.button = Mouse::Button::Left;
|
||||
@@ -609,67 +626,67 @@ TEST(InputTest, MouseClick) {
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 0u);
|
||||
EXPECT_EQ(cursor_position, 0u);
|
||||
|
||||
mouse.x = 2;
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 2u);
|
||||
EXPECT_EQ(cursor_position, 2u);
|
||||
|
||||
mouse.x = 2;
|
||||
mouse.y = 0;
|
||||
EXPECT_FALSE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 2u);
|
||||
EXPECT_EQ(cursor_position, 2u);
|
||||
|
||||
mouse.x = 1;
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 1u);
|
||||
EXPECT_EQ(cursor_position, 1u);
|
||||
|
||||
mouse.x = 3;
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 3u);
|
||||
EXPECT_EQ(cursor_position, 3u);
|
||||
|
||||
mouse.x = 4;
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
EXPECT_EQ(cursor_position, 4u);
|
||||
|
||||
mouse.x = 5;
|
||||
mouse.y = 0;
|
||||
EXPECT_FALSE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 4u);
|
||||
EXPECT_EQ(cursor_position, 4u);
|
||||
|
||||
mouse.x = 5;
|
||||
mouse.y = 1;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 9u);
|
||||
EXPECT_EQ(cursor_position, 9u);
|
||||
|
||||
mouse.x = 1;
|
||||
mouse.y = 1;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 6u);
|
||||
EXPECT_EQ(cursor_position, 6u);
|
||||
|
||||
mouse.x = 4;
|
||||
mouse.y = 2;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 10u);
|
||||
EXPECT_EQ(cursor_position, 10u);
|
||||
}
|
||||
|
||||
TEST(InputTest, MouseClickComplex) {
|
||||
std::string content;
|
||||
auto option = InputOption();
|
||||
auto input = Input(&content, &option);
|
||||
int cursor_position = 0;
|
||||
auto input = Input(&content, {.cursor_position = &cursor_position});
|
||||
|
||||
input->OnEvent(Event::Character("测"));
|
||||
input->OnEvent(Event::Character("试"));
|
||||
@@ -681,7 +698,7 @@ TEST(InputTest, MouseClickComplex) {
|
||||
input->OnEvent(Event::Character("a⃒"));
|
||||
input->OnEvent(Event::Character("ā"));
|
||||
|
||||
EXPECT_EQ(option.cursor_position(), 27u);
|
||||
EXPECT_EQ(cursor_position, 27u);
|
||||
|
||||
auto render = [&] {
|
||||
auto document = input->Render();
|
||||
@@ -701,25 +718,25 @@ TEST(InputTest, MouseClickComplex) {
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 0);
|
||||
EXPECT_EQ(cursor_position, 0);
|
||||
|
||||
mouse.x = 0;
|
||||
mouse.y = 1;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 14);
|
||||
EXPECT_EQ(cursor_position, 14);
|
||||
|
||||
mouse.x = 1;
|
||||
mouse.y = 0;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 3);
|
||||
EXPECT_EQ(cursor_position, 3);
|
||||
|
||||
mouse.x = 1;
|
||||
mouse.y = 1;
|
||||
EXPECT_TRUE(input->OnEvent(Event::Mouse("", mouse)));
|
||||
render();
|
||||
EXPECT_EQ(option.cursor_position(), 17);
|
||||
EXPECT_EQ(cursor_position, 17);
|
||||
}
|
||||
|
||||
TEST(InputTest, OnEnter) {
|
||||
@@ -727,7 +744,7 @@ TEST(InputTest, OnEnter) {
|
||||
auto option = InputOption();
|
||||
bool on_enter_called = false;
|
||||
option.on_enter = [&] { on_enter_called = true; };
|
||||
Component input = Input(&content, &option);
|
||||
Component input = Input(&content, option);
|
||||
|
||||
EXPECT_FALSE(on_enter_called);
|
||||
EXPECT_TRUE(input->OnEvent(Event::Return));
|
||||
|
@@ -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
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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));
|
||||
|
@@ -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;
|
||||
|
@@ -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)); };
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user