FTXUI  6.0.2
C++ functional terminal UI.
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
input.cpp
Go to the documentation of this file.
1// Copyright 2022 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4#include <algorithm> // for max, min
5#include <cstddef> // for size_t
6#include <cstdint> // for uint32_t
7#include <functional> // for function
8#include <sstream> // for basic_istream, stringstream
9#include <string> // for string, basic_string, operator==, getline
10#include <utility> // for move
11#include <vector> // for vector
12
13#include "ftxui/component/component.hpp" // for Make, Input
14#include "ftxui/component/component_base.hpp" // for ComponentBase
15#include "ftxui/component/component_options.hpp" // for InputOption
16#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
17#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
18#include "ftxui/component/screen_interactive.hpp" // for Component
19#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
20#include "ftxui/screen/box.hpp" // for Box
21#include "ftxui/screen/string.hpp" // for string_width
22#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
23#include "ftxui/screen/util.hpp" // for clamp
24#include "ftxui/util/ref.hpp" // for StringRef, Ref
25
26namespace ftxui {
27
28namespace {
29
30std::vector<std::string> Split(const std::string& input) {
31 std::vector<std::string> output;
32 std::stringstream ss(input);
33 std::string line;
34 while (std::getline(ss, line)) {
35 output.push_back(line);
36 }
37 if (input.back() == '\n') {
38 output.emplace_back("");
39 }
40 return output;
41}
42
43size_t GlyphWidth(const std::string& input, size_t iter) {
44 uint32_t ucs = 0;
45 if (!EatCodePoint(input, iter, &iter, &ucs)) {
46 return 0;
47 }
48 if (IsFullWidth(ucs)) {
49 return 2;
50 }
51 return 1;
52}
53
60 return true;
61
71 // Unexpected/Unsure
77 return false;
78 }
79 return false; // NOT_REACHED();
80}
81
82bool IsWordCharacter(const std::string& input, size_t iter) {
83 uint32_t ucs = 0;
84 if (!EatCodePoint(input, iter, &iter, &ucs)) {
85 return false;
86 }
87
88 return IsWordCodePoint(ucs);
89}
90
91// An input box. The user can type text into it.
92class InputBase : public ComponentBase, public InputOption {
93 public:
94 // NOLINTNEXTLINE
95 InputBase(InputOption option) : InputOption(std::move(option)) {}
96
97 private:
98 // Component implementation:
99 Element OnRender() override {
100 const bool is_focused = Focused();
101 const auto focused = (!is_focused && !hovered_) ? nothing
102 : insert() ? focusCursorBarBlinking
104
105 auto transform_func =
106 transform ? transform : InputOption::Default().transform;
107
108 // placeholder.
109 if (content->empty()) {
110 auto element = text(placeholder()) | xflex | frame;
111
112 return transform_func({
113 std::move(element), hovered_, is_focused,
114 true // placeholder
115 }) |
116 focus | reflect(box_);
117 }
118
120 const std::vector<std::string> lines = Split(*content);
121
122 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
123
124 // Find the line and index of the cursor.
125 int cursor_line = 0;
126 int cursor_char_index = cursor_position();
127 for (const auto& line : lines) {
128 if (cursor_char_index <= (int)line.size()) {
129 break;
130 }
131
132 cursor_char_index -= static_cast<int>(line.size() + 1);
133 cursor_line++;
134 }
135
136 if (lines.empty()) {
137 elements.push_back(text("") | focused);
138 }
139
140 elements.reserve(lines.size());
141 for (size_t i = 0; i < lines.size(); ++i) {
142 const std::string& line = lines[i];
143
144 // This is not the cursor line.
145 if (int(i) != cursor_line) {
146 elements.push_back(Text(line));
147 continue;
148 }
149
150 // The cursor is at the end of the line.
151 if (cursor_char_index >= (int)line.size()) {
152 elements.push_back(hbox({
153 Text(line),
154 text(" ") | focused | reflect(cursor_box_),
155 }) |
156 xflex);
157 continue;
158 }
159
160 // The cursor is on this line.
161 const int glyph_start = cursor_char_index;
162 const int glyph_end = static_cast<int>(GlyphNext(line, glyph_start));
163 const std::string part_before_cursor = line.substr(0, glyph_start);
164 const std::string part_at_cursor =
165 line.substr(glyph_start, glyph_end - glyph_start);
166 const std::string part_after_cursor = line.substr(glyph_end);
167 auto element = hbox({
168 Text(part_before_cursor),
169 Text(part_at_cursor) | focused | reflect(cursor_box_),
170 Text(part_after_cursor),
171 }) |
172 xflex;
173 elements.push_back(element);
174 }
175
176 auto element = vbox(std::move(elements), cursor_line) | frame;
177 return transform_func({
178 std::move(element), hovered_, is_focused,
179 false // placeholder
180 }) |
181 xflex | reflect(box_);
182 }
183
184 Element Text(const std::string& input) {
185 if (!password()) {
186 return text(input);
187 }
188
189 std::string out;
190 out.reserve(10 + input.size() * 3 / 2);
191 for (size_t i = 0; i < input.size(); ++i) {
192 out += "•";
193 }
194 return text(out);
195 }
196
197 bool HandleBackspace() {
198 if (cursor_position() == 0) {
199 return false;
200 }
201 const size_t start = GlyphPrevious(content(), cursor_position());
202 const size_t end = cursor_position();
203 content->erase(start, end - start);
204 cursor_position() = static_cast<int>(start);
205 on_change();
206 return true;
207 }
208
209 bool DeleteImpl() {
210 if (cursor_position() == (int)content->size()) {
211 return false;
212 }
213 const size_t start = cursor_position();
214 const size_t end = GlyphNext(content(), cursor_position());
215 content->erase(start, end - start);
216 return true;
217 }
218
219 bool HandleDelete() {
220 if (DeleteImpl()) {
221 on_change();
222 return true;
223 }
224 return false;
225 }
226
227 bool HandleArrowLeft() {
228 if (cursor_position() == 0) {
229 return false;
230 }
231
232 cursor_position() =
233 static_cast<int>(GlyphPrevious(content(), cursor_position()));
234 return true;
235 }
236
237 bool HandleArrowRight() {
238 if (cursor_position() == (int)content->size()) {
239 return false;
240 }
241
242 cursor_position() =
243 static_cast<int>(GlyphNext(content(), cursor_position()));
244 return true;
245 }
246
247 size_t CursorColumn() {
248 size_t iter = cursor_position();
249 int width = 0;
250 while (true) {
251 if (iter == 0) {
252 break;
253 }
254 iter = GlyphPrevious(content(), iter);
255 if (content()[iter] == '\n') {
256 break;
257 }
258 width += static_cast<int>(GlyphWidth(content(), iter));
259 }
260 return width;
261 }
262
263 // Move the cursor `columns` on the right, if possible.
264 void MoveCursorColumn(int columns) {
265 while (columns > 0) {
266 if (cursor_position() == (int)content().size() ||
267 content()[cursor_position()] == '\n') {
268 return;
269 }
270
271 columns -= static_cast<int>(GlyphWidth(content(), cursor_position()));
272 cursor_position() =
273 static_cast<int>(GlyphNext(content(), cursor_position()));
274 }
275 }
276
277 bool HandleArrowUp() {
278 if (cursor_position() == 0) {
279 return false;
280 }
281
282 const size_t columns = CursorColumn();
283
284 // Move cursor at the beginning of 2 lines above.
285 while (true) {
286 if (cursor_position() == 0) {
287 return true;
288 }
289 const size_t previous = GlyphPrevious(content(), cursor_position());
290 if (content()[previous] == '\n') {
291 break;
292 }
293 cursor_position() = static_cast<int>(previous);
294 }
295 cursor_position() =
296 static_cast<int>(GlyphPrevious(content(), cursor_position()));
297 while (true) {
298 if (cursor_position() == 0) {
299 break;
300 }
301 const size_t previous = GlyphPrevious(content(), cursor_position());
302 if (content()[previous] == '\n') {
303 break;
304 }
305 cursor_position() = static_cast<int>(previous);
306 }
307
308 MoveCursorColumn(static_cast<int>(columns));
309 return true;
310 }
311
312 bool HandleArrowDown() {
313 if (cursor_position() == (int)content->size()) {
314 return false;
315 }
316
317 const size_t columns = CursorColumn();
318
319 // Move cursor at the beginning of the next line
320 while (true) {
321 if (content()[cursor_position()] == '\n') {
322 break;
323 }
324 cursor_position() =
325 static_cast<int>(GlyphNext(content(), cursor_position()));
326 if (cursor_position() == (int)content().size()) {
327 return true;
328 }
329 }
330 cursor_position() =
331 static_cast<int>(GlyphNext(content(), cursor_position()));
332
333 MoveCursorColumn(static_cast<int>(columns));
334 return true;
335 }
336
337 bool HandleHome() {
338 cursor_position() = 0;
339 return true;
340 }
341
342 bool HandleEnd() {
343 cursor_position() = static_cast<int>(content->size());
344 return true;
345 }
346
347 bool HandleReturn() {
348 if (multiline()) {
349 HandleCharacter("\n");
350 }
351 on_enter();
352 return true;
353 }
354
355 bool HandleCharacter(const std::string& character) {
356 if (!insert() && cursor_position() < (int)content->size() &&
357 content()[cursor_position()] != '\n') {
358 DeleteImpl();
359 }
360 content->insert(cursor_position(), character);
361 cursor_position() += static_cast<int>(character.size());
362 on_change();
363 return true;
364 }
365
366 bool OnEvent(Event event) override {
367 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
368
369 if (event == Event::Return) {
370 return HandleReturn();
371 }
372 if (event.is_character()) {
373 return HandleCharacter(event.character());
374 }
375 if (event.is_mouse()) {
376 return HandleMouse(event);
377 }
378 if (event == Event::Backspace) {
379 return HandleBackspace();
380 }
381 if (event == Event::Delete) {
382 return HandleDelete();
383 }
384 if (event == Event::ArrowLeft) {
385 return HandleArrowLeft();
386 }
387 if (event == Event::ArrowRight) {
388 return HandleArrowRight();
389 }
390 if (event == Event::ArrowUp) {
391 return HandleArrowUp();
392 }
393 if (event == Event::ArrowDown) {
394 return HandleArrowDown();
395 }
396 if (event == Event::Home) {
397 return HandleHome();
398 }
399 if (event == Event::End) {
400 return HandleEnd();
401 }
403 return HandleLeftCtrl();
404 }
406 return HandleRightCtrl();
407 }
408 if (event == Event::Insert) {
409 return HandleInsert();
410 }
411 return false;
412 }
413
414 bool HandleLeftCtrl() {
415 if (cursor_position() == 0) {
416 return false;
417 }
418
419 // Move left, as long as left it not a word.
420 while (cursor_position()) {
421 const size_t previous = GlyphPrevious(content(), cursor_position());
422 if (IsWordCharacter(content(), previous)) {
423 break;
424 }
425 cursor_position() = static_cast<int>(previous);
426 }
427 // Move left, as long as left is a word character:
428 while (cursor_position()) {
429 const size_t previous = GlyphPrevious(content(), cursor_position());
430 if (!IsWordCharacter(content(), previous)) {
431 break;
432 }
433 cursor_position() = static_cast<int>(previous);
434 }
435 return true;
436 }
437
438 bool HandleRightCtrl() {
439 if (cursor_position() == (int)content().size()) {
440 return false;
441 }
442
443 // Move right, until entering a word.
444 while (cursor_position() < (int)content().size()) {
445 cursor_position() =
446 static_cast<int>(GlyphNext(content(), cursor_position()));
447 if (IsWordCharacter(content(), cursor_position())) {
448 break;
449 }
450 }
451 // Move right, as long as right is a word character:
452 while (cursor_position() < (int)content().size()) {
453 const size_t next = GlyphNext(content(), cursor_position());
454 if (!IsWordCharacter(content(), cursor_position())) {
455 break;
456 }
457 cursor_position() = static_cast<int>(next);
458 }
459
460 return true;
461 }
462
463 bool HandleMouse(Event event) {
464 hovered_ = box_.Contain(event.mouse().x, //
465 event.mouse().y) &&
466 CaptureMouse(event);
467 if (!hovered_) {
468 return false;
469 }
470
471 if (event.mouse().button != Mouse::Left) {
472 return false;
473 }
474 if (event.mouse().motion != Mouse::Pressed) {
475 return false;
476 }
477
478 TakeFocus();
479
480 if (content->empty()) {
481 cursor_position() = 0;
482 return true;
483 }
484
485 // Find the line and index of the cursor.
486 std::vector<std::string> lines = Split(*content);
487 int cursor_line = 0;
488 int cursor_char_index = cursor_position();
489 for (const auto& line : lines) {
490 if (cursor_char_index <= (int)line.size()) {
491 break;
492 }
493
494 cursor_char_index -= static_cast<int>(line.size() + 1);
495 cursor_line++;
496 }
497 const int cursor_column =
499
500 int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min;
501 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
502
503 // Fix the new cursor position:
504 new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0);
505
506 const std::string empty_string;
507 const std::string& line = new_cursor_line < (int)lines.size()
508 ? lines[new_cursor_line]
509 : empty_string;
511
514 return false;
515 }
516
517 // Convert back the new_cursor_{line,column} toward cursor_position:
518 cursor_position() = 0;
519 for (int i = 0; i < new_cursor_line; ++i) {
520 cursor_position() += static_cast<int>(lines[i].size() + 1);
521 }
522 while (new_cursor_column > 0) {
524 static_cast<int>(GlyphWidth(content(), cursor_position()));
525 cursor_position() =
526 static_cast<int>(GlyphNext(content(), cursor_position()));
527 }
528
529 on_change();
530 return true;
531 }
532
533 bool HandleInsert() {
534 insert() = !insert();
535 return true;
536 }
537
538 bool Focusable() const final { return true; }
539
540 bool hovered_ = false;
541
542 Box box_;
543 Box cursor_box_;
544};
545
546} // namespace
547
548/// @brief An input box for editing text.
549/// @param option Additional optional parameters.
550/// @ingroup component
551/// @see InputBase
552///
553/// ### Example
554///
555/// ```cpp
556/// auto screen = ScreenInteractive::FitComponent();
557/// std::string content= "";
558/// std::string placeholder = "placeholder";
559/// Component input = Input({
560/// .content = &content,
561/// .placeholder = &placeholder,
562/// })
563/// screen.Loop(input);
564/// ```
565///
566/// ### Output
567///
568/// ```bash
569/// placeholder
570/// ```
572 return Make<InputBase>(std::move(option));
573}
574
575/// @brief An input box for editing text.
576/// @param content The editable content.
577/// @param option Additional optional parameters.
578/// @ingroup component
579/// @see InputBase
580///
581/// ### Example
582///
583/// ```cpp
584/// auto screen = ScreenInteractive::FitComponent();
585/// std::string content= "";
586/// std::string placeholder = "placeholder";
587/// Component input = Input(content, {
588/// .placeholder = &placeholder,
589/// .password = true,
590/// })
591/// screen.Loop(input);
592/// ```
593///
594/// ### Output
595///
596/// ```bash
597/// placeholder
598/// ```
600 option.content = std::move(content);
601 return Make<InputBase>(std::move(option));
602}
603
604/// @brief An input box for editing text.
605/// @param content The editable content.
606/// @param option Additional optional parameters.
607/// @ingroup component
608/// @see InputBase
609///
610/// ### Example
611///
612/// ```cpp
613/// auto screen = ScreenInteractive::FitComponent();
614/// std::string content= "";
615/// std::string placeholder = "placeholder";
616/// Component input = Input(content, placeholder);
617/// screen.Loop(input);
618/// ```
619///
620/// ### Output
621///
622/// ```bash
623/// placeholder
624/// ```
626 option.content = std::move(content);
627 option.placeholder = std::move(placeholder);
628 return Make<InputBase>(std::move(option));
629}
630
631} // namespace ftxui
An adapter. Own or reference a constant string. For convenience, this class convert multiple mutable ...
Definition ref.hpp:82
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:11
size_t GlyphNext(const std::string &input, size_t start)
Definition string.cpp:1424
Element focusCursorBarBlinking(Element)
Same as focus, but set the cursor shape to be a blinking bar.
Definition frame.cpp:189
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition flex.cpp:129
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Definition string.cpp:1307
Element nothing(Element element)
A decoration doing absolutely nothing.
Definition util.cpp:28
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Definition size.cpp:89
std::shared_ptr< Node > Element
Definition elements.hpp:22
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
std::shared_ptr< ComponentBase > Component
int string_width(const std::string &)
Definition string.cpp:1330
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition hbox.cpp:94
Element text(std::wstring text)
Display a piece of unicode text.
Definition text.cpp:160
std::vector< Element > Elements
Definition elements.hpp:23
Component Input(InputOption options={})
An input box for editing text.
Definition input.cpp:571
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Definition string.cpp:1174
Element focus(Element)
Set the child to be the one focused among its siblings.
Definition frame.cpp:101
Decorator reflect(Box &box)
Definition reflect.cpp:43
bool IsFullWidth(uint32_t ucs)
Definition string.cpp:1286
Element frame(Element)
Allow an element to be displayed inside a 'virtual' area. It size can be larger than its container....
Definition frame.cpp:118
Element focusCursorBlockBlinking(Element)
Same as focus, but set the cursor shape to be a blinking block.
Definition frame.cpp:161
size_t GlyphPrevious(const std::string &input, size_t start)
Definition string.cpp:1399
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition vbox.cpp:96
static const Event ArrowLeftCtrl
Definition event.hpp:43
static const Event Backspace
Definition event.hpp:49
static const Event ArrowUp
Definition event.hpp:40
static const Event ArrowDown
Definition event.hpp:41
static const Event End
Definition event.hpp:59
static const Event Home
Definition event.hpp:58
static const Event Return
Definition event.hpp:51
static const Event ArrowLeft
Definition event.hpp:38
static const Event Delete
Definition event.hpp:50
static const Event Insert
Definition event.hpp:57
static const Event ArrowRightCtrl
Definition event.hpp:44
static const Event ArrowRight
Definition event.hpp:39
Option for the Input component.
static InputOption Default()
Create the default input style:
std::function< Element(InputState)> transform