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