FTXUI 6.1.9
C++ functional terminal UI.
Loading...
Searching...
No Matches
src/ftxui/component/input.cpp
Go to the documentation of this file.
1// Copyright 2022 Arthur Sonzogni. All rights reserved.
2// 本原始碼的使用受 MIT 授權約束,詳情請參閱 LICENSE 檔案。
3#include <algorithm> // for max, min
4#include <cstddef> // for size_t
5#include <cstdint> // for uint32_t
6#include <functional> // for function
7#include <sstream> // for basic_istream, stringstream
8#include <string> // for string, basic_string, operator==, getline
9#include <utility> // for move
10#include <vector> // for vector
11
12#include "ftxui/component/component.hpp" // for Make, Input
13#include "ftxui/component/component_base.hpp" // for ComponentBase
14#include "ftxui/component/component_options.hpp" // for InputOption
15#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
16#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
17#include "ftxui/component/screen_interactive.hpp" // for Component
18#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
19#include "ftxui/screen/box.hpp" // for Box
20#include "ftxui/screen/string.hpp" // for string_width
21#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
22#include "ftxui/screen/util.hpp" // for clamp
23#include "ftxui/util/ref.hpp" // for StringRef, Ref
24
25namespace ftxui {
26
27namespace {
28
29std::vector<std::string> Split(const std::string& input) {
30 std::vector<std::string> output;
31 std::stringstream ss(input);
32 std::string line;
33 while (std::getline(ss, line)) {
34 output.push_back(line);
35 }
36 if (input.back() == '\n') {
37 output.emplace_back("");
38 }
39 return output;
40}
41
42size_t GlyphWidth(const std::string& input, size_t iter) {
43 uint32_t ucs = 0;
44 if (!EatCodePoint(input, iter, &iter, &ucs)) {
45 return 0;
46 }
47 if (IsFullWidth(ucs)) {
48 return 2;
49 }
50 return 1;
51}
52
53bool IsWordCodePoint(uint32_t codepoint) {
54 switch (CodepointToWordBreakProperty(codepoint)) {
59 return true;
60
70 // Unexpected/Unsure
76 return false;
77 }
78 return false; // NOT_REACHED();
79}
80
81bool IsWordCharacter(const std::string& input, size_t iter) {
82 uint32_t ucs = 0;
83 if (!EatCodePoint(input, iter, &iter, &ucs)) {
84 return false;
85 }
86
87 return IsWordCodePoint(ucs);
88}
89
90// 一個輸入框。用戶可以在其中輸入文字。
91class InputBase : public ComponentBase, public InputOption {
92 public:
93 // NOLINTNEXTLINE
94 InputBase(InputOption option) : InputOption(std::move(option)) {}
95
96 private:
97 // Component implementation:
98 Element OnRender() override {
99 const bool is_focused = Focused();
100 const auto focused = (!is_focused && !hovered_) ? nothing
101 : insert() ? focusCursorBarBlinking
102 : focusCursorBlockBlinking;
103
104 auto transform_func =
105 transform ? transform : InputOption::Default().transform;
106
107 // placeholder.
108 if (content->empty()) {
109 auto element = text(placeholder()) | xflex | frame;
110
111 return transform_func({
112 std::move(element), hovered_, is_focused,
113 true // placeholder
114 }) |
115 focus | reflect(box_);
116 }
117
118 Elements elements;
119 const std::vector<std::string> lines = Split(*content);
120
121 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
122
123 // Find the line and index of the cursor.
124 int cursor_line = 0;
125 int cursor_char_index = cursor_position();
126 for (const auto& line : lines) {
127 if (cursor_char_index <= (int)line.size()) {
128 break;
129 }
130
131 cursor_char_index -= static_cast<int>(line.size() + 1);
132 cursor_line++;
133 }
134
135 if (lines.empty()) {
136 elements.push_back(text("") | focused);
137 }
138
139 elements.reserve(lines.size());
140 for (size_t i = 0; i < lines.size(); ++i) {
141 const std::string& line = lines[i];
142
143 // This is not the cursor line.
144 if (int(i) != cursor_line) {
145 elements.push_back(Text(line));
146 continue;
147 }
148
149 // The cursor is at the end of the line.
150 if (cursor_char_index >= (int)line.size()) {
151 elements.push_back(hbox({
152 Text(line),
153 text(" ") | focused | reflect(cursor_box_),
154 }) |
155 xflex);
156 continue;
157 }
158
159 // The cursor is on this line.
160 const int glyph_start = cursor_char_index;
161 const int glyph_end = static_cast<int>(GlyphNext(line, glyph_start));
162 const std::string part_before_cursor = line.substr(0, glyph_start);
163 const std::string part_at_cursor =
164 line.substr(glyph_start, glyph_end - glyph_start);
165 const std::string part_after_cursor = line.substr(glyph_end);
166 auto element = hbox({
167 Text(part_before_cursor),
168 Text(part_at_cursor) | focused | reflect(cursor_box_),
169 Text(part_after_cursor),
170 }) |
171 xflex;
172 elements.push_back(element);
173 }
174
175 auto element = vbox(std::move(elements)) | frame;
176 return transform_func({
177 std::move(element), hovered_, is_focused,
178 false // placeholder
179 }) |
180 xflex | reflect(box_);
181 }
182
183 Element Text(const std::string& input) {
184 if (!password()) {
185 return text(input);
186 }
187
188 std::string out;
189 out.reserve(10 + input.size() * 3 / 2);
190 for (size_t i = 0; i < input.size(); ++i) {
191 out += "•";
192 }
193 return text(out);
194 }
195
196 bool HandleBackspace() {
197 if (cursor_position() == 0) {
198 return false;
199 }
200 const size_t start = GlyphPrevious(content(), cursor_position());
201 const size_t end = cursor_position();
202 content->erase(start, end - start);
203 cursor_position() = static_cast<int>(start);
204 on_change();
205 return true;
206 }
207
208 bool DeleteImpl() {
209 if (cursor_position() == (int)content->size()) {
210 return false;
211 }
212 const size_t start = cursor_position();
213 const size_t end = GlyphNext(content(), cursor_position());
214 content->erase(start, end - start);
215 return true;
216 }
217
218 bool HandleDelete() {
219 if (DeleteImpl()) {
220 on_change();
221 return true;
222 }
223 return false;
224 }
225
226 bool HandleArrowLeft() {
227 if (cursor_position() == 0) {
228 return false;
229 }
230
231 cursor_position() =
232 static_cast<int>(GlyphPrevious(content(), cursor_position()));
233 return true;
234 }
235
236 bool HandleArrowRight() {
237 if (cursor_position() == (int)content->size()) {
238 return false;
239 }
240
241 cursor_position() =
242 static_cast<int>(GlyphNext(content(), cursor_position()));
243 return true;
244 }
245
246 size_t CursorColumn() {
247 size_t iter = cursor_position();
248 int width = 0;
249 while (true) {
250 if (iter == 0) {
251 break;
252 }
253 iter = GlyphPrevious(content(), iter);
254 if (content()[iter] == '\n') {
255 break;
256 }
257 width += static_cast<int>(GlyphWidth(content(), iter));
258 }
259 return width;
260 }
261
262 // Move the cursor `columns` on the right, if possible.
263 void MoveCursorColumn(int columns) {
264 while (columns > 0) {
265 if (cursor_position() == (int)content().size() ||
266 content()[cursor_position()] == '\n') {
267 return;
268 }
269
270 columns -= static_cast<int>(GlyphWidth(content(), cursor_position()));
271 cursor_position() =
272 static_cast<int>(GlyphNext(content(), cursor_position()));
273 }
274 }
275
276 bool HandleArrowUp() {
277 if (cursor_position() == 0) {
278 return false;
279 }
280
281 const size_t columns = CursorColumn();
282
283 // Move cursor at the beginning of 2 lines above.
284 while (true) {
285 if (cursor_position() == 0) {
286 return true;
287 }
288 const size_t previous = GlyphPrevious(content(), cursor_position());
289 if (content()[previous] == '\n') {
290 break;
291 }
292 cursor_position() = static_cast<int>(previous);
293 }
294 cursor_position() =
295 static_cast<int>(GlyphPrevious(content(), cursor_position()));
296 while (true) {
297 if (cursor_position() == 0) {
298 break;
299 }
300 const size_t previous = GlyphPrevious(content(), cursor_position());
301 if (content()[previous] == '\n') {
302 break;
303 }
304 cursor_position() = static_cast<int>(previous);
305 }
306
307 MoveCursorColumn(static_cast<int>(columns));
308 return true;
309 }
310
311 bool HandleArrowDown() {
312 if (cursor_position() == (int)content->size()) {
313 return false;
314 }
315
316 const size_t columns = CursorColumn();
317
318 // Move cursor at the beginning of the next line
319 while (true) {
320 if (content()[cursor_position()] == '\n') {
321 break;
322 }
323 cursor_position() =
324 static_cast<int>(GlyphNext(content(), cursor_position()));
325 if (cursor_position() == (int)content().size()) {
326 return true;
327 }
328 }
329 cursor_position() =
330 static_cast<int>(GlyphNext(content(), cursor_position()));
331
332 MoveCursorColumn(static_cast<int>(columns));
333 return true;
334 }
335
336 bool HandleHome() {
337 cursor_position() = 0;
338 return true;
339 }
340
341 bool HandleEnd() {
342 cursor_position() = static_cast<int>(content->size());
343 return true;
344 }
345
346 bool HandleReturn() {
347 if (multiline()) {
348 HandleCharacter("\n");
349 }
350 on_enter();
351 return true;
352 }
353
354 bool HandleCharacter(const std::string& character) {
355 if (!insert() && cursor_position() < (int)content->size() &&
356 content()[cursor_position()] != '\n') {
357 DeleteImpl();
358 }
359 content->insert(cursor_position(), character);
360 cursor_position() += static_cast<int>(character.size());
361 on_change();
362 return true;
363 }
364
365 bool OnEvent(Event event) override {
366 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
367
368 if (event == Event::Return) {
369 return HandleReturn();
370 }
371 if (event.is_character()) {
372 return HandleCharacter(event.character());
373 }
374 if (event.is_mouse()) {
375 return HandleMouse(event);
376 }
377 if (event == Event::Backspace) {
378 return HandleBackspace();
379 }
380 if (event == Event::Delete) {
381 return HandleDelete();
382 }
383 if (event == Event::ArrowLeft) {
384 return HandleArrowLeft();
385 }
386 if (event == Event::ArrowRight) {
387 return HandleArrowRight();
388 }
389 if (event == Event::ArrowUp) {
390 return HandleArrowUp();
391 }
392 if (event == Event::ArrowDown) {
393 return HandleArrowDown();
394 }
395 if (event == Event::Home) {
396 return HandleHome();
397 }
398 if (event == Event::End) {
399 return HandleEnd();
400 }
401 if (event == Event::ArrowLeftCtrl) {
402 return HandleLeftCtrl();
403 }
404 if (event == Event::ArrowRightCtrl) {
405 return HandleRightCtrl();
406 }
407 if (event == Event::Insert) {
408 return HandleInsert();
409 }
410 return false;
411 }
412
413 bool HandleLeftCtrl() {
414 if (cursor_position() == 0) {
415 return false;
416 }
417
418 // Move left, as long as left it not a word.
419 while (cursor_position()) {
420 const size_t previous = GlyphPrevious(content(), cursor_position());
421 if (IsWordCharacter(content(), previous)) {
422 break;
423 }
424 cursor_position() = static_cast<int>(previous);
425 }
426 // Move left, as long as left is a word character:
427 while (cursor_position()) {
428 const size_t previous = GlyphPrevious(content(), cursor_position());
429 if (!IsWordCharacter(content(), previous)) {
430 break;
431 }
432 cursor_position() = static_cast<int>(previous);
433 }
434 return true;
435 }
436
437 bool HandleRightCtrl() {
438 if (cursor_position() == (int)content().size()) {
439 return false;
440 }
441
442 // Move right, until entering a word.
443 while (cursor_position() < (int)content().size()) {
444 cursor_position() =
445 static_cast<int>(GlyphNext(content(), cursor_position()));
446 if (IsWordCharacter(content(), cursor_position())) {
447 break;
448 }
449 }
450 // Move right, as long as right is a word character:
451 while (cursor_position() < (int)content().size()) {
452 const size_t next = GlyphNext(content(), cursor_position());
453 if (!IsWordCharacter(content(), cursor_position())) {
454 break;
455 }
456 cursor_position() = static_cast<int>(next);
457 }
458
459 return true;
460 }
461
462 bool HandleMouse(Event event) {
463 hovered_ = box_.Contain(event.mouse().x, //
464 event.mouse().y) &&
465 CaptureMouse(event);
466 if (!hovered_) {
467 return false;
468 }
469
470 if (event.mouse().button != Mouse::Left) {
471 return false;
472 }
473 if (event.mouse().motion != Mouse::Pressed) {
474 return false;
475 }
476
477 TakeFocus();
478
479 if (content->empty()) {
480 cursor_position() = 0;
481 return true;
482 }
483
484 // Find the line and index of the cursor.
485 std::vector<std::string> lines = Split(*content);
486 int cursor_line = 0;
487 int cursor_char_index = cursor_position();
488 for (const auto& line : lines) {
489 if (cursor_char_index <= (int)line.size()) {
490 break;
491 }
492
493 cursor_char_index -= static_cast<int>(line.size() + 1);
494 cursor_line++;
495 }
496 const int cursor_column =
497 string_width(lines[cursor_line].substr(0, cursor_char_index));
498
499 int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min;
500 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
501
502 // Fix the new cursor position:
503 new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0);
504
505 const std::string empty_string;
506 const std::string& line = new_cursor_line < (int)lines.size()
507 ? lines[new_cursor_line]
508 : empty_string;
509 new_cursor_column = util::clamp(new_cursor_column, 0, string_width(line));
510
511 if (new_cursor_column == cursor_column && //
512 new_cursor_line == cursor_line) {
513 return false;
514 }
515
516 // Convert back the new_cursor_{line,column} toward cursor_position:
517 cursor_position() = 0;
518 for (int i = 0; i < new_cursor_line; ++i) {
519 cursor_position() += static_cast<int>(lines[i].size() + 1);
520 }
521 while (new_cursor_column > 0) {
522 new_cursor_column -=
523 static_cast<int>(GlyphWidth(content(), cursor_position()));
524 cursor_position() =
525 static_cast<int>(GlyphNext(content(), cursor_position()));
526 }
527
528 on_change();
529 return true;
530 }
531
532 bool HandleInsert() {
533 insert() = !insert();
534 return true;
535 }
536
537 bool Focusable() const final { return true; }
538
539 bool hovered_ = false;
540
541 Box box_;
542 Box cursor_box_;
543};
544
545} // namespace
546
547/// @brief 用於編輯文字的輸入框。
548/// @param option 額外的可選參數。
549/// @ingroup component
550/// @see InputBase
551///
552/// ### Example
553///
554/// ```cpp
555/// auto screen = ScreenInteractive::FitComponent();
556/// std::string content= "";
557/// std::string placeholder = "placeholder";
558/// Component input = Input({
559/// .content = &content,
560/// .placeholder = &placeholder,
561/// })
562/// screen.Loop(input);
563/// ```
564///
565/// ### Output
566///
567/// ```bash
568/// placeholder
569/// ```
571 return Make<InputBase>(std::move(option));
572}
573
574/// @brief 用於編輯文字的輸入框。
575/// @param content 可編輯的內容。
576/// @param option 額外的可選參數。
577/// @ingroup component
578/// @see InputBase
579///
580/// ### Example
581///
582/// ```cpp
583/// auto screen = ScreenInteractive::FitComponent();
584/// std::string content= "";
585/// std::string placeholder = "placeholder";
586/// Component input = Input(content, {
587/// .placeholder = &placeholder,
588/// .password = true,
589/// })
590/// screen.Loop(input);
591/// ```
592///
593/// ### Output
594///
595/// ```bash
596/// placeholder
597/// ```
599 option.content = std::move(content);
600 return Make<InputBase>(std::move(option));
601}
602
603/// @brief 用於編輯文字的輸入框。
604/// @param content 可編輯的內容。
605/// @param placeholder 佔位符文字。
606/// @param option 額外的可選參數。
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/// ```
625Component Input(StringRef content, StringRef placeholder, InputOption option) {
626 option.content = std::move(content);
627 option.placeholder = std::move(placeholder);
628 return Make<InputBase>(std::move(option));
629}
630
631} // namespace ftxui
一個適配器。擁有或引用一個常數字串。為方便起見,此類別將多個可變字串轉換為共享表示。
Definition ref.hpp:81
auto input
Definition gallery.cpp:78
static const Event ArrowLeftCtrl
Definition event.hpp:43
static InputOption Default()
建立預設輸入樣式:
static const Event Backspace
Definition event.hpp:49
static const Event ArrowUp
Definition event.hpp:40
std::function< Element(InputState)> transform
static const Event ArrowDown
Definition event.hpp:41
static const Event End
Definition event.hpp:59
StringRef placeholder
輸入框為空時的內容。
static const Event Home
Definition event.hpp:58
StringRef content
輸入框的內容。
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
Component Input(InputOption options={})
用於編輯文字的輸入框。
Input 元件的選項。
Element xflex(Element)
在 X 軸上盡可能擴展/在需要時最小化。
Definition flex.cpp:146
Decorator size(WidthOrHeight, Constraint, int value)
限制元素的大小。
Element text(std::wstring text)
顯示一段 Unicode 文字。
Definition text.cpp:160
Element vbox(Elements)
一個垂直一個接一個顯示元素的容器。
Definition vbox.cpp:95
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:11
FTXUI 的 ftxui:: 命名空間
Definition animation.hpp:10
size_t GlyphNext(const std::string &input, size_t start)
Definition string.cpp:1408
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Definition string.cpp:1305
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
std::shared_ptr< Node > Element
Definition elements.hpp:22
int string_width(const std::string &)
Definition string.cpp:1328
Element hbox(Elements)
一個逐一水平顯示元素的容器。
Definition hbox.cpp:94
std::vector< Element > Elements
Definition elements.hpp:23
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Definition string.cpp:1172
Decorator reflect(Box &box)
Definition reflect.cpp:42
bool IsFullWidth(uint32_t ucs)
Definition string.cpp:1283
Element frame(Element)
允許元素顯示在「虛擬」區域內。其大小可以大於其容器。在這種情況下,只會顯示較小的一部分。視圖可滾動以使聚焦元素可見。
Definition frame.cpp:116
size_t GlyphPrevious(const std::string &input, size_t start)
Definition string.cpp:1383
std::shared_ptr< ComponentBase > Component