30std::vector<std::string> SplitLines(std::string_view input) {
31 std::vector<std::string> output;
33 size_t end = input.find(
'\n');
34 while (end != std::string_view::npos) {
35 output.push_back(std::string(input.substr(start, end - start)));
37 end = input.find(
'\n', start);
39 output.push_back(std::string(input.substr(start)));
43size_t GlyphWidth(std::string_view input,
size_t iter) {
54bool IsWordCodePoint(uint32_t codepoint) {
82bool IsWordCharacter(std::string_view input,
size_t iter) {
88 return IsWordCodePoint(ucs);
92class InputBase :
public ComponentBase,
public InputOption {
95 InputBase(InputOption option) : InputOption(std::move(option)) {}
100 const bool is_focused = Focused();
101 const auto focused = (!is_focused && !hovered_) ? focus
102 : insert() ? focusCursorBarBlinking
103 : focusCursorBlockBlinking;
105 auto transform_func =
106 transform ? transform : InputOption::Default().transform;
109 if (content->empty()) {
112 return transform_func({
113 std::move(
element), hovered_, is_focused,
120 const std::vector<std::string> lines = SplitLines(*content);
122 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
126 int cursor_char_index = cursor_position();
127 for (
const auto& line : lines) {
128 if (cursor_char_index <= (
int)line.size()) {
132 cursor_char_index -=
static_cast<int>(line.size() + 1);
137 elements.push_back(
text(
"") | focused);
140 elements.reserve(lines.size());
141 for (
size_t i = 0; i < lines.size(); ++i) {
142 const std::string& line = lines[i];
145 if (
int(i) != cursor_line) {
146 elements.push_back(
Text(line));
151 const std::string cursor_cell = is_focused ?
" " :
"";
152 if (cursor_char_index >= (
int)line.size()) {
156 text(cursor_cell) | focused |
reflect(cursor_box_),
163 const int glyph_start = cursor_char_index;
164 const int glyph_end =
static_cast<int>(
GlyphNext(line, glyph_start));
165 const std::string part_before_cursor = line.substr(0, glyph_start);
166 const std::string part_at_cursor =
167 line.substr(glyph_start, glyph_end - glyph_start);
168 const std::string part_after_cursor = line.substr(glyph_end);
170 Text(part_before_cursor),
171 Text(part_at_cursor) | focused |
reflect(cursor_box_),
172 Text(part_after_cursor),
179 return transform_func({
180 std::move(
element), hovered_, is_focused,
193 out.reserve(glyph_count * 3);
194 for (
size_t i = 0; i < glyph_count; ++i) {
200 bool HandleBackspace() {
201 if (cursor_position() == 0) {
204 const size_t start =
GlyphPrevious(content(), cursor_position());
205 const size_t end = cursor_position();
206 content->erase(start, end - start);
207 cursor_position() =
static_cast<int>(start);
208 App::PostEventOrExecute(on_change);
212 bool HandleBackspaceWord() {
213 if (cursor_position() == 0) {
217 size_t start = cursor_position();
219 if (content()[previous] ==
'\n') {
225 if (content()[prev] ==
'\n' || IsWordCharacter(content(), prev)) {
234 if (content()[prev] ==
'\n' || !IsWordCharacter(content(), prev)) {
241 const size_t end = cursor_position();
242 content->erase(start, end - start);
243 cursor_position() =
static_cast<int>(start);
244 App::PostEventOrExecute(on_change);
248 bool HandleDeleteWord() {
249 if (cursor_position() == (
int)content->size()) {
253 size_t end = cursor_position();
254 if (content()[end] ==
'\n') {
258 while (end < content->
size() && content()[end] !=
'\n' &&
259 !IsWordCharacter(content(), end)) {
264 while (end < content->
size() && content()[end] !=
'\n' &&
265 IsWordCharacter(content(), end)) {
270 const size_t start = cursor_position();
271 content->erase(start, end - start);
272 App::PostEventOrExecute(on_change);
277 bool HandleBackspaceLine() {
278 if (cursor_position() == 0) {
282 size_t start = cursor_position();
284 if (content()[previous] ==
'\n') {
289 if (content()[prev] ==
'\n') {
296 const size_t end = cursor_position();
297 content->erase(start, end - start);
298 cursor_position() =
static_cast<int>(start);
299 App::PostEventOrExecute(on_change);
304 if (cursor_position() == (
int)content->size()) {
307 const size_t start = cursor_position();
308 const size_t end =
GlyphNext(content(), cursor_position());
309 content->erase(start, end - start);
313 bool HandleDelete() {
315 App::PostEventOrExecute(on_change);
321 bool HandleArrowLeft() {
322 if (cursor_position() == 0) {
327 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
331 bool HandleArrowRight() {
332 if (cursor_position() == (
int)content->size()) {
337 static_cast<int>(
GlyphNext(content(), cursor_position()));
341 size_t CursorColumn() {
342 size_t iter = cursor_position();
349 if (content()[iter] ==
'\n') {
355 width +=
static_cast<int>(GlyphWidth(content(), iter));
362 void MoveCursorColumn(
int columns) {
363 while (columns > 0) {
364 if (cursor_position() == (
int)content().
size() ||
365 content()[cursor_position()] ==
'\n') {
372 columns -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
375 static_cast<int>(
GlyphNext(content(), cursor_position()));
379 bool HandleArrowUp() {
380 if (cursor_position() == 0) {
384 const size_t columns = CursorColumn();
388 if (cursor_position() == 0) {
391 const size_t previous =
GlyphPrevious(content(), cursor_position());
392 if (content()[previous] ==
'\n') {
395 cursor_position() =
static_cast<int>(previous);
398 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
400 if (cursor_position() == 0) {
403 const size_t previous =
GlyphPrevious(content(), cursor_position());
404 if (content()[previous] ==
'\n') {
407 cursor_position() =
static_cast<int>(previous);
410 MoveCursorColumn(
static_cast<int>(columns));
414 bool HandleArrowDown() {
415 if (cursor_position() == (
int)content->size()) {
419 const size_t columns = CursorColumn();
423 if (content()[cursor_position()] ==
'\n') {
427 static_cast<int>(
GlyphNext(content(), cursor_position()));
428 if (cursor_position() == (
int)content().
size()) {
433 static_cast<int>(
GlyphNext(content(), cursor_position()));
435 MoveCursorColumn(
static_cast<int>(columns));
440 cursor_position() = 0;
445 cursor_position() =
static_cast<int>(content->size());
449 bool HandleLineStart() {
450 while (cursor_position() > 0) {
451 const size_t previous =
GlyphPrevious(content(), cursor_position());
452 if (content()[previous] ==
'\n') {
455 cursor_position() =
static_cast<int>(previous);
460 bool HandleLineEnd() {
461 while (cursor_position() < (
int)content->size() &&
462 content()[cursor_position()] !=
'\n') {
464 static_cast<int>(
GlyphNext(content(), cursor_position()));
469 bool HandleReturn() {
471 HandleCharacter(
"\n");
473 App::PostEventOrExecute(on_enter);
477 bool HandleCharacter(
const std::string& character) {
478 if (!insert() && cursor_position() < (
int)content->size() &&
479 content()[cursor_position()] !=
'\n') {
482 content->insert(cursor_position(), character);
483 cursor_position() +=
static_cast<int>(character.size());
484 App::PostEventOrExecute(on_change);
488 bool OnEvent(Event event)
override {
489 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
491 if (event == Event::Return) {
492 return HandleReturn();
494 if (event.is_character()) {
495 return HandleCharacter(event.character());
497 if (event.is_mouse()) {
498 return HandleMouse(event);
500 if (event == Event::Backspace) {
501 return HandleBackspace();
503 if (event == Event::BackspaceAlt || event == Event::AltBackspace ||
504 event == Event::CtrlW) {
505 return HandleBackspaceWord();
507 if (event == Event::CtrlU) {
508 return HandleBackspaceLine();
510 if (event == Event::Delete) {
511 return HandleDelete();
513 if (event == Event::DeleteAlt || event == Event::DeleteCtrl ||
514 event == Event::AltD) {
515 return HandleDeleteWord();
517 if (event == Event::ArrowLeft) {
518 return HandleArrowLeft();
520 if (event == Event::ArrowRight) {
521 return HandleArrowRight();
523 if (event == Event::ArrowUp) {
524 return HandleArrowUp();
526 if (event == Event::ArrowDown) {
527 return HandleArrowDown();
529 if (event == Event::Home) {
532 if (event == Event::End) {
535 if (event == Event::CtrlA) {
536 return HandleLineStart();
538 if (event == Event::CtrlE) {
539 return HandleLineEnd();
541 if (event == Event::ArrowLeftCtrl || event == Event::ArrowLeftAlt ||
542 event == Event::AltB) {
543 return HandleLeftCtrl();
545 if (event == Event::ArrowRightCtrl || event == Event::ArrowRightAlt ||
546 event == Event::AltF) {
547 return HandleRightCtrl();
549 if (event == Event::ArrowUpAlt) {
550 return HandleArrowUp();
552 if (event == Event::ArrowDownAlt) {
553 return HandleArrowDown();
555 if (event == Event::Insert) {
556 return HandleInsert();
561 bool HandleLeftCtrl() {
562 if (cursor_position() == 0) {
567 while (cursor_position()) {
568 const size_t previous =
GlyphPrevious(content(), cursor_position());
569 if (IsWordCharacter(content(), previous)) {
572 cursor_position() =
static_cast<int>(previous);
575 while (cursor_position()) {
576 const size_t previous =
GlyphPrevious(content(), cursor_position());
577 if (!IsWordCharacter(content(), previous)) {
580 cursor_position() =
static_cast<int>(previous);
585 bool HandleRightCtrl() {
586 if (cursor_position() == (
int)content().
size()) {
591 while (cursor_position() < (
int)content().
size()) {
593 static_cast<int>(
GlyphNext(content(), cursor_position()));
594 if (IsWordCharacter(content(), cursor_position())) {
599 while (cursor_position() < (
int)content().
size()) {
600 const size_t next =
GlyphNext(content(), cursor_position());
601 if (!IsWordCharacter(content(), cursor_position())) {
604 cursor_position() =
static_cast<int>(next);
610 bool HandleMouse(Event event) {
611 hovered_ = box_.Contain(event.mouse().x,
618 if (event.mouse().button != Mouse::Left) {
621 if (event.mouse().motion != Mouse::Pressed) {
627 if (content->empty()) {
628 cursor_position() = 0;
633 std::vector<std::string> lines = SplitLines(*content);
635 int cursor_char_index = cursor_position();
636 for (
const auto& line : lines) {
637 if (cursor_char_index <= (
int)line.size()) {
641 cursor_char_index -=
static_cast<int>(line.size() + 1);
644 const int cursor_column =
646 ?
GlyphCount(lines[cursor_line].substr(0, cursor_char_index))
647 : string_width(lines[cursor_line].substr(0, cursor_char_index));
649 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
650 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
653 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
655 const std::string empty_string;
656 const std::string& line = new_cursor_line < (
int)lines.size()
657 ? lines[new_cursor_line]
663 if (new_cursor_column == cursor_column &&
664 new_cursor_line == cursor_line) {
669 cursor_position() = 0;
670 for (
int i = 0; i < new_cursor_line; ++i) {
671 cursor_position() +=
static_cast<int>(lines[i].size() + 1);
673 while (new_cursor_column > 0) {
675 new_cursor_column -= 1;
678 static_cast<int>(GlyphWidth(content(), cursor_position()));
681 static_cast<int>(
GlyphNext(content(), cursor_position()));
684 App::PostEventOrExecute(on_change);
688 bool HandleInsert() {
689 insert() = !insert();
693 bool Focusable() const final {
return true; }
695 bool hovered_ =
false;
755 option.content = std::move(content);
782 option.content = std::move(content);
783 option.placeholder = std::move(placeholder);
Component Input(StringRef content, InputOption options={})
An input box for editing text.
Element text(std::string_view text)
Display a piece of UTF8 encoded unicode text.
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Element vbox(Elements children)
A container displaying elements vertically one by one.
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
size_t GlyphPrevious(std::string_view input, size_t start)
std::shared_ptr< T > Make(Args &&... args)
std::shared_ptr< Node > Element
int string_width(std::string_view input)
Element hbox(Elements children)
A container displaying elements horizontally one by one.
std::vector< Element > Elements
bool EatCodePoint(std::string_view input, size_t start, size_t *end, uint32_t *ucs)
int GlyphCount(std::string_view input)
Decorator reflect(Box &box)
bool IsFullWidth(uint32_t ucs)
Element frame(Element child)
Allow an element to be displayed inside a 'virtual' area. It size can be larger than its container....
std::shared_ptr< ComponentBase > Component
size_t GlyphNext(std::string_view input, size_t start)