Feature: Add multi-line input. (#630)

This commit is contained in:
Arthur Sonzogni
2023-05-02 13:32:37 +02:00
committed by GitHub
parent 6215240b37
commit 7de4f8683d
33 changed files with 1471 additions and 690 deletions

View File

@@ -1,7 +1,6 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TestInfo (ptr only), TEST
#include <chrono> // for operator""s, chrono_literals
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string
#include <chrono> // for operator""s, chrono_literals
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string
#include "ftxui/component/animation.hpp" // for Duration, Params
#include "ftxui/component/component.hpp" // for Button, Horizontal
@@ -12,6 +11,7 @@
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_FALSE, EXPECT_TRUE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,12 +1,13 @@
#include <gtest/gtest.h>
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string
#include "ftxui/component/component.hpp" // for Collapsible, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown
#include "ftxui/dom/elements.hpp" // for text, Element
#include "ftxui/dom/elements.hpp" // for Element, text
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_FALSE, EXPECT_TRUE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,11 +1,12 @@
#include "ftxui/component/component_options.hpp"
#include <ftxui/screen/color.hpp> // for Color, Color::Black, Color::White, Color::GrayDark, Color::GrayLight
#include <memory> // for shared_ptr
#include <utility> // for move
#include <ftxui/dom/linear_gradient.hpp> // for LinearGradient
#include <ftxui/screen/color.hpp> // for Color, Color::White, Color::Black, Color::GrayDark, Color::Blue, Color::GrayLight, Color::Red
#include <memory> // for shared_ptr
#include <utility> // for move
#include "ftxui/component/animation.hpp" // for Function, Duration
#include "ftxui/dom/elements.hpp" // for operator|=, text, Element, bold, inverted, operator|, dim, hbox, automerge, borderEmpty, borderLight
#include "ftxui/dom/elements.hpp" // for operator|=, Element, text, bgcolor, inverted, bold, dim, operator|, color, borderEmpty, hbox, automerge, border, borderLight
namespace ftxui {
@@ -257,6 +258,77 @@ RadioboxOption RadioboxOption::Simple() {
return option;
}
// static
InputOption InputOption::Default() {
InputOption option;
option.transform = [](InputState state) {
state.element |= color(Color::White);
if (state.is_placeholder) {
state.element |= dim;
}
if (state.focused) {
state.element |= inverted;
} else if (state.hovered) {
state.element |= bgcolor(Color::GrayDark);
}
return state.element;
};
return option;
}
// static
InputOption InputOption::Spacious() {
InputOption option;
option.transform = [](InputState state) {
state.element |= borderEmpty;
state.element |= color(Color::White);
if (state.is_placeholder) {
state.element |= dim;
}
if (state.focused) {
state.element |= bgcolor(Color::Black);
}
if (state.hovered) {
state.element |= bgcolor(Color::GrayDark);
}
return state.element;
};
return option;
}
// static
InputOption InputOption::Arthur() {
InputOption option;
option.transform = [](InputState state) {
state.element |= borderEmpty;
state.element |= color(Color::White);
if (state.is_placeholder) {
state.element |= dim;
}
if (state.focused) {
state.element |= bgcolor(Color::Black);
} else {
state.element |= bgcolor(LinearGradient(0, Color::Blue, Color::Red));
}
if (state.hovered) {
state.element |= bgcolor(Color::GrayDark);
}
return state.element;
};
return option;
}
} // namespace ftxui
// Copyright 2022 Arthur Sonzogni. All rights reserved.

View File

@@ -1,8 +1,9 @@
#include <gtest/gtest.h>
#include <memory> // for shared_ptr, __shared_ptr_access, allocator, __shared_ptr_access<>::element_type, make_shared
#include <string> // for string
#include "ftxui/component/component.hpp" // for Make
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
#include "gtest/gtest.h" // for Message, TestPartResult, EXPECT_EQ, Test, AssertionResult, TEST, EXPECT_FALSE
namespace ftxui {

View File

@@ -1,9 +1,10 @@
#include <gtest/gtest.h>
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string
#include "ftxui/component/component.hpp" // for Horizontal, Vertical, Button, Tab
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
#include "ftxui/component/event.hpp" // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_FALSE, Test, EXPECT_TRUE, TEST
namespace ftxui {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_FALSE, EXPECT_EQ, Test, EXPECT_TRUE, TestInfo (ptr only), TEST
#include <ftxui/dom/elements.hpp> // for Element, text
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
#include <string> // for string
#include "ftxui/component/component.hpp" // for Hoverable, Horizontal, operator|=, Renderer
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
@@ -8,6 +8,7 @@
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_FALSE, EXPECT_EQ, Test, EXPECT_TRUE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,32 +1,55 @@
#include <stdint.h> // for uint32_t
#include <algorithm> // for max, min
#include <cstddef> // for size_t
#include <functional> // for function
#include <memory> // for shared_ptr
#include <string> // for string, allocator
#include <utility> // for move
#include <vector> // for vector
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type
#include <sstream> // for basic_istream, stringstream
#include <string> // for string, basic_string, operator==, getline
#include <utility> // for move
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component.hpp" // for Make, Input
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Home, Event::Return
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::ArrowUp, Event::Backspace, Event::Delete, Event::End, Event::Home, Event::Return
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
#include "ftxui/component/screen_interactive.hpp" // for Component
#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, operator|=, flex, inverted, hbox, size, bold, dim, focus, focusCursorBarBlinking, frame, select, Decorator, EQUAL, HEIGHT
#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/string.hpp" // for GlyphPosition, WordBreakProperty, GlyphCount, Utf8ToWordBreakProperty, CellToGlyphIndex, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for StringRef, Ref, ConstStringRef
#include "ftxui/screen/string.hpp" // for string_width
#include "ftxui/screen/string_internal.hpp" // for GlyphNext, GlyphPrevious, WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, IsFullWidth, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for StringRef, Ref
namespace ftxui {
namespace {
// Group together several propertiej so they appear to form a similar group.
// For instance, letters are grouped with number and form a single word.
bool IsWordCharacter(WordBreakProperty property) {
switch (property) {
std::vector<std::string> Split(const std::string& input) {
std::vector<std::string> output;
std::stringstream ss(input);
std::string line;
while (std::getline(ss, line)) {
output.push_back(line);
}
if (input.back() == '\n') {
output.push_back("");
}
return output;
}
size_t GlyphWidth(const std::string& input, size_t iter) {
uint32_t ucs = 0;
if (!EatCodePoint(input, iter, &iter, &ucs))
return 0;
if (IsFullWidth(ucs))
return 2;
return 1;
}
bool IsWordCodePoint(uint32_t codepoint) {
switch (CodepointToWordBreakProperty(codepoint)) {
case WordBreakProperty::ALetter:
case WordBreakProperty::Hebrew_Letter:
case WordBreakProperty::Katakana:
@@ -42,7 +65,7 @@ bool IsWordCharacter(WordBreakProperty property) {
case WordBreakProperty::Newline:
case WordBreakProperty::Single_Quote:
case WordBreakProperty::WSegSpace:
// Unsure:
// Unexpected/Unsure
case WordBreakProperty::Extend:
case WordBreakProperty::ExtendNumLet:
case WordBreakProperty::Format:
@@ -50,216 +73,398 @@ bool IsWordCharacter(WordBreakProperty property) {
case WordBreakProperty::ZWJ:
return false;
}
return true; // NOT_REACHED();
return false; // NOT_REACHED();
}
std::string PasswordField(size_t size) {
std::string out;
out.reserve(2 * size);
while (size--) {
out += "";
bool IsWordCharacter(const std::string& input, size_t iter) {
uint32_t ucs = 0;
if (!EatCodePoint(input, iter, &iter, &ucs)) {
return false;
}
return out;
return IsWordCodePoint(ucs);
}
// An input box. The user can type text into it.
class InputBase : public ComponentBase {
public:
InputBase(StringRef content,
ConstStringRef placeholder,
Ref<InputOption> option)
: content_(std::move(content)),
placeholder_(std::move(placeholder)),
option_(std::move(option)) {}
int cursor_position_internal_ = 0;
int& cursor_position() {
int& opt = option_->cursor_position();
if (opt != -1) {
return opt;
}
return cursor_position_internal_;
}
// NOLINTNEXTLINE
InputBase(StringRef content, Ref<InputOption> option)
: content_(std::move(content)), option_(std::move(option)) {}
private:
// Component implementation:
Element Render() override {
std::string password_content;
if (option_->password()) {
password_content = PasswordField(content_->size());
}
const std::string& content =
option_->password() ? password_content : *content_;
const int size = GlyphCount(content);
cursor_position() = std::max(0, std::min<int>(size, cursor_position()));
auto main_decorator = flex | ftxui::size(HEIGHT, EQUAL, 1);
const bool is_focused = Focused();
const auto focused =
(is_focused || hovered_) ? focusCursorBarBlinking : select;
auto transform = option_->transform ? option_->transform
: InputOption::Default().transform;
// placeholder.
if (size == 0) {
auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
if (content_->empty()) {
auto element = text(option_->placeholder()) | xflex | frame;
if (is_focused) {
element |= focus;
}
if (hovered_ || is_focused) {
element |= inverted;
}
return element;
return transform({
std::move(element), hovered_, is_focused,
true // placeholder
}) |
reflect(box_);
}
// Not focused.
if (!is_focused) {
auto element = text(content) | main_decorator | reflect(box_);
if (hovered_) {
element |= inverted;
Elements elements;
std::vector<std::string> lines = Split(*content_);
int& cursor_position = option_->cursor_position();
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;
for (const auto& line : lines) {
if (cursor_char_index <= (int)line.size()) {
break;
}
return element;
cursor_char_index -= line.size() + 1;
cursor_line++;
}
const int index_before_cursor = GlyphPosition(content, cursor_position());
const int index_after_cursor =
GlyphPosition(content, 1, index_before_cursor);
const std::string part_before_cursor =
content.substr(0, index_before_cursor);
std::string part_at_cursor = " ";
if (cursor_position() < size) {
part_at_cursor = content.substr(index_before_cursor,
index_after_cursor - index_before_cursor);
if (lines.empty()) {
elements.push_back(text("") | focused);
}
const std::string part_after_cursor = content.substr(index_after_cursor);
auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select;
return hbox({
text(part_before_cursor),
text(part_at_cursor) | focused | reflect(cursor_box_),
text(part_after_cursor),
for (size_t i = 0; i < lines.size(); ++i) {
const std::string& line = lines[i];
// This is not the cursor line.
if (int(i) != cursor_line) {
elements.push_back(Text(line));
continue;
}
// The cursor is at the end of the line.
if (cursor_char_index >= (int)line.size()) {
elements.push_back(hbox({
Text(line),
text(" ") | focused | reflect(cursor_box_),
}) |
xflex);
continue;
}
// The cursor is on this line.
const int glyph_start = cursor_char_index;
const int glyph_end = GlyphNext(line, glyph_start);
const std::string part_before_cursor = line.substr(0, glyph_start);
const std::string part_at_cursor =
line.substr(glyph_start, glyph_end - glyph_start);
const std::string part_after_cursor = line.substr(glyph_end);
auto element = hbox({
Text(part_before_cursor),
Text(part_at_cursor) | focused | reflect(cursor_box_),
Text(part_after_cursor),
}) |
xflex;
elements.push_back(element);
}
auto element = vbox(std::move(elements)) | frame;
return transform({
std::move(element), hovered_, is_focused,
false // placeholder
}) |
flex | frame | bold | main_decorator | reflect(box_);
xflex | reflect(box_);
}
bool OnEvent(Event event) override {
cursor_position() =
std::max(0, std::min<int>((int)content_->size(), cursor_position()));
if (event.is_mouse()) {
return OnMouseEvent(event);
Element Text(const std::string& input) {
if (!option_->password()) {
return text(input);
}
// Backspace.
if (event == Event::Backspace) {
if (cursor_position() == 0) {
return false;
}
const size_t start = GlyphPosition(*content_, cursor_position() - 1);
const size_t end = GlyphPosition(*content_, cursor_position());
content_->erase(start, end - start);
cursor_position()--;
option_->on_change();
return true;
std::string out;
out.reserve(10 + input.size() * 3 / 2);
for (size_t i = 0; i < input.size(); ++i) {
out += "";
}
return text(out);
}
// Delete
if (event == Event::Delete) {
if (cursor_position() == int(content_->size())) {
return false;
}
const size_t start = GlyphPosition(*content_, cursor_position());
const size_t end = GlyphPosition(*content_, cursor_position() + 1);
content_->erase(start, end - start);
option_->on_change();
return true;
bool HandleBackspace() {
int& cursor_position = option_->cursor_position();
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;
return true;
}
// Enter.
if (event == Event::Return) {
option_->on_enter();
return true;
bool HandleDelete() {
int& cursor_position = option_->cursor_position();
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);
return true;
}
if (event == Event::Custom) {
bool HandleArrowLeft() {
int& cursor_position = option_->cursor_position();
if (cursor_position == 0) {
return false;
}
// Arrow
if (event == Event::ArrowLeft && cursor_position() > 0) {
cursor_position()--;
return true;
cursor_position = GlyphPrevious(content_(), cursor_position);
return true;
}
bool HandleArrowRight() {
int& cursor_position = option_->cursor_position();
if (cursor_position == (int)content_->size()) {
return false;
}
if (event == Event::ArrowRight &&
cursor_position() < static_cast<int>(content_->size())) {
cursor_position()++;
return true;
cursor_position = GlyphNext(content_(), cursor_position);
return true;
}
size_t CursorColumn() {
int& cursor_position = option_->cursor_position();
size_t iter = cursor_position;
int width = 0;
while (true) {
if (iter == 0) {
break;
}
iter = GlyphPrevious(content_(), iter);
if (content_()[iter] == '\n') {
break;
}
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') {
return;
}
columns -= GlyphWidth(content_(), cursor_position);
cursor_position = GlyphNext(content_(), cursor_position);
}
}
bool HandleArrowUp() {
int& cursor_position = option_->cursor_position();
if (cursor_position == 0) {
return false;
}
// CTRL + Arrow:
size_t columns = CursorColumn();
// Move cursor at the beginning of 2 lines above.
while (true) {
if (cursor_position == 0) {
return true;
}
size_t previous = GlyphPrevious(content_(), cursor_position);
if (content_()[previous] == '\n') {
break;
}
cursor_position = previous;
}
cursor_position = GlyphPrevious(content_(), cursor_position);
while (true) {
if (cursor_position == 0) {
break;
}
size_t previous = GlyphPrevious(content_(), cursor_position);
if (content_()[previous] == '\n') {
break;
}
cursor_position = previous;
}
MoveCursorColumn(columns);
return true;
}
bool HandleArrowDown() {
int& cursor_position = option_->cursor_position();
if (cursor_position == (int)content_->size()) {
return false;
}
size_t columns = CursorColumn();
// Move cursor at the beginning of the next line
while (true) {
if (content_()[cursor_position] == '\n') {
break;
}
cursor_position = GlyphNext(content_(), cursor_position);
if (cursor_position == (int)content_().size()) {
return true;
}
}
cursor_position = GlyphNext(content_(), cursor_position);
MoveCursorColumn(columns);
return true;
}
bool HandleHome() {
int& cursor_position = option_->cursor_position();
cursor_position = 0;
return true;
}
bool HandleEnd() {
int& cursor_position = option_->cursor_position();
cursor_position = content_->size();
return true;
}
bool HandleReturn() {
int& cursor_position = option_->cursor_position();
content_->insert(cursor_position, "\n");
cursor_position++;
option_->on_change();
return true;
}
bool HandleCharacter(const std::string& character) {
if (character == "\n" && !option_->multiline()) {
option_->on_enter();
return false;
}
int& cursor_position = option_->cursor_position();
content_->insert(cursor_position, character);
cursor_position += character.size();
option_->on_change();
if (character == "\n") {
option_->on_enter();
}
return true;
}
bool OnEvent(Event event) override {
int& cursor_position = option_->cursor_position();
cursor_position = util::clamp(cursor_position, 0, (int)content_->size());
if (event.is_character()) {
return HandleCharacter(event.character());
}
if (event.is_mouse()) {
return HandleMouse(event);
}
if (event == Event::Backspace) {
return HandleBackspace();
}
if (event == Event::Delete) {
return HandleDelete();
}
if (event == Event::ArrowLeft) {
return HandleArrowLeft();
}
if (event == Event::ArrowRight) {
return HandleArrowRight();
}
if (event == Event::ArrowUp) {
return HandleArrowUp();
}
if (event == Event::ArrowDown) {
return HandleArrowDown();
}
if (event == Event::Home) {
return HandleHome();
}
if (event == Event::End) {
return HandleEnd();
}
if (event == Event::ArrowLeftCtrl) {
HandleLeftCtrl();
return true;
return HandleLeftCtrl();
}
if (event == Event::ArrowRightCtrl) {
HandleRightCtrl();
return true;
return HandleRightCtrl();
}
if (event == Event::Return) {
return HandleReturn();
}
if (event == Event::Home) {
cursor_position() = 0;
return true;
}
if (event == Event::End) {
cursor_position() = GlyphCount(*content_);
return true;
}
// Content
if (event.is_character()) {
const size_t start = GlyphPosition(*content_, cursor_position());
content_->insert(start, event.character());
cursor_position()++;
option_->on_change();
return true;
}
return false;
}
private:
void HandleLeftCtrl() {
auto properties = Utf8ToWordBreakProperty(*content_);
// Move left, as long as left is not a word character.
while (cursor_position() > 0 &&
!IsWordCharacter(properties[cursor_position() - 1])) {
cursor_position()--;
bool HandleLeftCtrl() {
int& cursor_position = option_->cursor_position();
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)) {
break;
}
cursor_position = previous;
}
// Move left, as long as left is a word character:
while (cursor_position() > 0 &&
IsWordCharacter(properties[cursor_position() - 1])) {
cursor_position()--;
while (cursor_position) {
size_t previous = GlyphPrevious(content_(), cursor_position);
if (!IsWordCharacter(content_(), previous)) {
break;
}
cursor_position = previous;
}
return true;
}
void HandleRightCtrl() {
auto properties = Utf8ToWordBreakProperty(*content_);
const int max = properties.size();
// Move right, as long as right is not a word character.
while (cursor_position() < max &&
!IsWordCharacter(properties[cursor_position()])) {
cursor_position()++;
bool HandleRightCtrl() {
int& cursor_position = option_->cursor_position();
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)) {
break;
}
}
// Move right, as long as right is a word character:
while (cursor_position() < max &&
IsWordCharacter(properties[cursor_position()])) {
cursor_position()++;
while (cursor_position < (int)content_().size()) {
size_t next = GlyphNext(content_(), cursor_position);
if (!IsWordCharacter(content_(), cursor_position)) {
break;
}
cursor_position = next;
}
return true;
}
bool OnMouseEvent(Event event) {
hovered_ =
box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
bool HandleMouse(Event event) {
hovered_ = box_.Contain(event.mouse().x, //
event.mouse().y) &&
CaptureMouse(event);
if (!hovered_) {
return false;
}
@@ -270,32 +475,56 @@ class InputBase : public ComponentBase {
}
TakeFocus();
if (content_->empty()) {
option_->cursor_position() = 0;
return true;
}
auto mapping = CellToGlyphIndex(*content_);
int original_glyph = cursor_position();
original_glyph = util::clamp(original_glyph, 0, int(mapping.size()));
size_t original_cell = 0;
for (size_t i = 0; i < mapping.size(); i++) {
if (mapping[i] == original_glyph) {
original_cell = i;
// Find the line and index of the cursor.
std::vector<std::string> lines = Split(*content_);
int& cursor_position = option_->cursor_position();
int cursor_line = 0;
int cursor_char_index = cursor_position;
for (const auto& line : lines) {
if (cursor_char_index <= (int)line.size()) {
break;
}
cursor_char_index -= line.size() + 1;
cursor_line++;
}
if (mapping[original_cell] != original_glyph) {
original_cell = mapping.size();
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;
int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
// 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& line = new_cursor_line < (int)lines.size()
? lines[new_cursor_line]
: empty_string;
new_cursor_column = util::clamp(new_cursor_column, 0, string_width(line));
if (new_cursor_column == cursor_column && //
new_cursor_line == cursor_line) {
return false;
}
const int target_cell =
int(original_cell) + event.mouse().x - cursor_box_.x_min;
int target_glyph = target_cell < int(mapping.size()) ? mapping[target_cell]
: int(mapping.size());
target_glyph = util::clamp(target_glyph, 0, GlyphCount(*content_));
if (cursor_position() != target_glyph) {
cursor_position() = target_glyph;
option_->on_change();
// Convert back the new_cursor_{line,column} toward cursor_position:
cursor_position = 0;
for (int i = 0; i < new_cursor_line; ++i) {
cursor_position += lines[i].size() + 1;
}
while (new_cursor_column > 0) {
new_cursor_column -= GlyphWidth(content_(), cursor_position);
cursor_position = GlyphNext(content_(), cursor_position);
}
option_->on_change();
return true;
}
@@ -303,7 +532,6 @@ class InputBase : public ComponentBase {
bool hovered_ = false;
StringRef content_;
ConstStringRef placeholder_;
Box box_;
Box cursor_box_;
@@ -314,7 +542,6 @@ class InputBase : public ComponentBase {
/// @brief An input box for editing text.
/// @param content The editable content.
/// @param placeholder The text displayed when content is still empty.
/// @param option Additional optional parameters.
/// @ingroup component
/// @see InputBase
@@ -334,15 +561,19 @@ 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,
ConstStringRef placeholder,
StringRef placeholder,
Ref<InputOption> option) {
return Make<InputBase>(std::move(content), std::move(placeholder),
std::move(option));
option->placeholder = placeholder;
return Make<InputBase>(std::move(content), std::move(option));
}
} // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Copyright 2022 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, Test, TestInfo (ptr only), EXPECT_FALSE, TEST
#include <ftxui/dom/elements.hpp> // for yframe
#include <ftxui/dom/node.hpp> // for Render
#include <ftxui/screen/screen.hpp> // for Screen
@@ -11,6 +10,7 @@
#include "ftxui/component/component_options.hpp" // for RadioboxOption
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::End, Event::Home, Event::Tab, Event::TabReverse, Event::PageDown, Event::PageUp, Event::ArrowUp
#include "ftxui/util/ref.hpp" // for Ref
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, Test, EXPECT_FALSE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,8 +1,9 @@
#include <gtest/gtest.h>
#include <string> // for string
#include <thread> // for thread
#include <utility> // for move
#include "ftxui/component/receiver.hpp"
#include "gtest/gtest.h" // for AssertionResult, Message, Test, TestPartResult, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), TEST
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string
#include "ftxui/component/component.hpp" // for ResizableSplit, Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
@@ -9,6 +9,7 @@
#include "ftxui/dom/elements.hpp" // for Element, separatorDouble, text
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,17 +1,17 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, TestInfo (ptr only), EXPECT_FALSE, TEST
#include <array> // for array
#include <cstddef> // for size_t
#include <array> // for array
#include <cstddef> // for size_t
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Pressed, Mouse::Released
#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
#include <ftxui/dom/elements.hpp> // for frame
#include <memory> // for shared_ptr, __shared_ptr_access, allocator
#include <string> // for to_string
#include <string> // for string, to_string
#include "ftxui/component/component.hpp" // for Slider, Vertical, operator|=
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown
#include "ftxui/dom/node.hpp" // for Render
#include "ftxui/screen/screen.hpp" // for Screen
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, Test, EXPECT_EQ, EXPECT_TRUE, EXPECT_FALSE, TEST
// NOLINTBEGIN
namespace ftxui {

View File

@@ -37,12 +37,12 @@ const std::map<std::string, std::string> g_uniformize = {
// Home ESC [ H ESC O H
// End ESC [ F ESC O F
//
{"\x1BOA", "\x1B[A"}, // UP
{"\x1BOB", "\x1B[B"}, // DOWN
{"\x1BOC", "\x1B[C"}, // RIGHT
{"\x1BOD", "\x1B[D"}, // LEFT
{"\x1BOH", "\x1B[H"}, // HOME
{"\x1BOF", "\x1B[F"}, // END
{"\x1BOA", "\x1B[A"}, // UP
{"\x1BOB", "\x1B[B"}, // DOWN
{"\x1BOC", "\x1B[C"}, // RIGHT
{"\x1BOD", "\x1B[D"}, // LEFT
{"\x1BOH", "\x1B[H"}, // HOME
{"\x1BOF", "\x1B[F"}, // END
};

View File

@@ -1,13 +1,13 @@
#include <gtest/gtest.h> // for AssertionResult, Test, Message, TestPartResult, SuiteApiResolver, TestInfo (ptr only), EXPECT_EQ, EXPECT_TRUE, TEST, TestFactoryImpl, EXPECT_FALSE
#include <ftxui/component/mouse.hpp> // for Mouse, Mouse::Left, Mouse::Middle, Mouse::Pressed, Mouse::Released, Mouse::Right
#include <ftxui/component/task.hpp> // for Task
#include <initializer_list> // for initializer_list
#include <memory> // for allocator, unique_ptr
#include <variant> // for get
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::F10, Event::F11, Event::F12, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::Home, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse, Event::Escape
#include "ftxui/component/event.hpp" // for Event, Event::Return, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::Backspace, Event::End, Event::Home, Event::Custom, Event::Delete, Event::F1, Event::F10, Event::F11, Event::F12, Event::F2, Event::F3, Event::F4, Event::F5, Event::F6, Event::F7, Event::F8, Event::F9, Event::PageDown, Event::PageUp, Event::Tab, Event::TabReverse, Event::Escape
#include "ftxui/component/receiver.hpp" // for MakeReceiver, ReceiverImpl
#include "ftxui/component/terminal_input_parser.hpp"
#include "gtest/gtest.h" // for AssertionResult, Test, Message, TestPartResult, EXPECT_EQ, EXPECT_TRUE, TEST, EXPECT_FALSE
// NOLINTBEGIN
namespace ftxui {

View File

@@ -1,14 +1,14 @@
#include <gtest/gtest.h> // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_TRUE, TestInfo (ptr only), EXPECT_FALSE, TEST
#include <functional> // for function
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string, basic_string
#include <vector> // for vector
#include <functional> // for function
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
#include <string> // for string, basic_string
#include <vector> // for vector
#include "ftxui/component/component.hpp" // for Menu, Toggle
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for MenuOption
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Return, Event::Tab, Event::TabReverse
#include "ftxui/util/ref.hpp" // for Ref
#include "gtest/gtest.h" // for AssertionResult, Message, TestPartResult, EXPECT_EQ, Test, EXPECT_TRUE, EXPECT_FALSE, TEST
// NOLINTBEGIN
namespace ftxui {