FTXUI 6.1.9
C++ functional terminal UI.
载入中...
搜索中...
未找到
src/ftxui/component/input.cpp
浏览该文件的文档.
1// 版权所有 2022 Arthur Sonzogni。保留所有权利。
2// 此源代码的使用受 MIT 许可证的约束,该许可证可在以下文件中找到:
3// LICENSE 文件。
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
54bool IsWordCodePoint(uint32_t codepoint) {
55 switch (CodepointToWordBreakProperty(codepoint)) {
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// 一个输入框。用户可以在其中输入文本。
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
103 : focusCursorBlockBlinking;
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
119 Elements elements;
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)) | 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 }
402 if (event == Event::ArrowLeftCtrl) {
403 return HandleLeftCtrl();
404 }
405 if (event == Event::ArrowRightCtrl) {
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 =
498 string_width(lines[cursor_line].substr(0, cursor_char_index));
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;
510 new_cursor_column = util::clamp(new_cursor_column, 0, string_width(line));
511
512 if (new_cursor_column == cursor_column && //
513 new_cursor_line == cursor_line) {
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) {
523 new_cursor_column -=
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 用于编辑文本的输入框。
549/// @param option 其他可选参数。
550/// @ingroup component
551/// @see InputBase
552///
553/// ### 示例
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/// ### 输出
567///
568/// ```bash
569/// placeholder
570/// ```
572 return Make<InputBase>(std::move(option));
573}
574
575/// @brief 用于编辑文本的输入框。
576/// @param content 可编辑内容。
577/// @param option 其他可选参数。
578/// @ingroup component
579/// @see InputBase
580///
581/// ### 示例
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/// ### 输出
595///
596/// ```bash
597/// placeholder
598/// ```
600 option.content = std::move(content);
601 return Make<InputBase>(std::move(option));
602}
603
604/// @brief 用于编辑文本的输入框。
605/// @param content 可编辑内容。
606/// @param placeholder 占位符文本。
607/// @param option 其他可选参数。
608/// @ingroup component
609/// @see InputBase
610///
611/// ### 示例
612///
613/// ```cpp
614/// auto screen = ScreenInteractive::FitComponent();
615/// std::string content= "";
616/// std::string placeholder = "placeholder";
617/// Component input = Input(content, placeholder);
618/// screen.Loop(input);
619/// ```
620///
621/// ### 输出
622///
623/// ```bash
624/// placeholder
625/// ```
626Component Input(StringRef content, StringRef placeholder, InputOption option) {
627 option.content = std::move(content);
628 option.placeholder = std::move(placeholder);
629 return Make<InputBase>(std::move(option));
630}
631
632} // namespace ftxui
一个适配器。拥有或引用一个常量字符串。为了方便,这个 类将多个可变字符串转换为共享表示。
定义 ref.hpp:82
static const Event ArrowLeftCtrl
static InputOption Default()
创建默认输入样式:
static const Event Backspace
static const Event ArrowUp
std::function< Element(InputState)> transform
static const Event ArrowDown
static const Event End
StringRef placeholder
当输入框为空时显示的内容。
static const Event Home
StringRef content
输入框的内容。
static const Event Return
static const Event ArrowLeft
static const Event Delete
static const Event Insert
static const Event ArrowRightCtrl
static const Event ArrowRight
Component Input(InputOption options={})
用于编辑文本的输入框。
Input 组件的选项。
Element xflex(Element)
在 X 轴上尽可能地扩展/收缩。
Decorator size(WidthOrHeight, Constraint, int value)
对元素大小应用约束。
Element text(std::wstring text)
显示一段Unicode文本。
Element vbox(Elements)
垂直一个接一个显示元素的容器。
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
#include "ftxui/component/component_base.hpp" // 用于 ComponentBase
size_t GlyphNext(const std::string &input, size_t start)
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
std::shared_ptr< T > Make(Args &&... args)
std::shared_ptr< Node > Element
int string_width(const std::string &)
Element hbox(Elements)
一个按水平顺序逐一显示元素的容器。
std::vector< Element > Elements
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Decorator reflect(Box &box)
bool IsFullWidth(uint32_t ucs)
Element frame(Element)
允许元素显示在“虚拟”区域内。其大小可以 大于其容器。在这种情况下,只显示较小的部分。 视图是可滚动的,以使获得焦点的元素可见。
size_t GlyphPrevious(const std::string &input, size_t start)
std::shared_ptr< ComponentBase > Component