30std::vector<std::string> Split(
const std::string& input) {
31 std::vector<std::string> output;
32 std::stringstream ss(input);
34 while (std::getline(ss, line)) {
35 output.push_back(line);
37 if (input.back() ==
'\n') {
38 output.emplace_back(
"");
43size_t GlyphWidth(
const std::string& input,
size_t iter) {
54bool IsWordCodePoint(uint32_t codepoint) {
82bool IsWordCharacter(
const std::string& 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 =
109 if (content->empty()) {
110 auto element =
text(placeholder()) | focused | xflex |
frame;
112 return transform_func({
113 std::move(element), hovered_, is_focused,
120 const std::vector<std::string> lines = Split(*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()) {
153 elements.push_back(
hbox({
155 text(cursor_cell) | focused |
reflect(cursor_box_),
162 const int glyph_start = cursor_char_index;
163 const int glyph_end =
static_cast<int>(
GlyphNext(line, glyph_start));
164 const std::string part_before_cursor = line.substr(0, glyph_start);
165 const std::string part_at_cursor =
166 line.substr(glyph_start, glyph_end - glyph_start);
167 const std::string part_after_cursor = line.substr(glyph_end);
168 auto element =
hbox({
169 Text(part_before_cursor),
170 Text(part_at_cursor) | focused |
reflect(cursor_box_),
171 Text(part_after_cursor),
174 elements.push_back(element);
177 auto element =
vbox(std::move(elements)) |
frame;
178 return transform_func({
179 std::move(element), hovered_, is_focused,
192 out.reserve(glyph_count * 3);
193 for (
size_t i = 0; i < glyph_count; ++i) {
199 bool HandleBackspace() {
200 if (cursor_position() == 0) {
203 const size_t start =
GlyphPrevious(content(), cursor_position());
204 const size_t end = cursor_position();
205 content->erase(start, end - start);
206 cursor_position() =
static_cast<int>(start);
212 if (cursor_position() == (
int)content->size()) {
215 const size_t start = cursor_position();
216 const size_t end =
GlyphNext(content(), cursor_position());
217 content->erase(start, end - start);
221 bool HandleDelete() {
229 bool HandleArrowLeft() {
230 if (cursor_position() == 0) {
235 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
239 bool HandleArrowRight() {
240 if (cursor_position() == (
int)content->size()) {
245 static_cast<int>(
GlyphNext(content(), cursor_position()));
249 size_t CursorColumn() {
250 size_t iter = cursor_position();
257 if (content()[iter] ==
'\n') {
263 width +=
static_cast<int>(GlyphWidth(content(), iter));
270 void MoveCursorColumn(
int columns) {
271 while (columns > 0) {
272 if (cursor_position() == (
int)content().
size() ||
273 content()[cursor_position()] ==
'\n') {
280 columns -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
283 static_cast<int>(
GlyphNext(content(), cursor_position()));
287 bool HandleArrowUp() {
288 if (cursor_position() == 0) {
292 const size_t columns = CursorColumn();
296 if (cursor_position() == 0) {
299 const size_t previous =
GlyphPrevious(content(), cursor_position());
300 if (content()[previous] ==
'\n') {
303 cursor_position() =
static_cast<int>(previous);
306 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
308 if (cursor_position() == 0) {
311 const size_t previous =
GlyphPrevious(content(), cursor_position());
312 if (content()[previous] ==
'\n') {
315 cursor_position() =
static_cast<int>(previous);
318 MoveCursorColumn(
static_cast<int>(columns));
322 bool HandleArrowDown() {
323 if (cursor_position() == (
int)content->size()) {
327 const size_t columns = CursorColumn();
331 if (content()[cursor_position()] ==
'\n') {
335 static_cast<int>(
GlyphNext(content(), cursor_position()));
336 if (cursor_position() == (
int)content().
size()) {
341 static_cast<int>(
GlyphNext(content(), cursor_position()));
343 MoveCursorColumn(
static_cast<int>(columns));
348 cursor_position() = 0;
353 cursor_position() =
static_cast<int>(content->size());
357 bool HandleReturn() {
359 HandleCharacter(
"\n");
365 bool HandleCharacter(
const std::string& character) {
366 if (!insert() && cursor_position() < (
int)content->size() &&
367 content()[cursor_position()] !=
'\n') {
370 content->insert(cursor_position(), character);
371 cursor_position() +=
static_cast<int>(character.size());
376 bool OnEvent(Event event)
override {
377 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
380 return HandleReturn();
382 if (event.is_character()) {
383 return HandleCharacter(event.character());
385 if (event.is_mouse()) {
386 return HandleMouse(event);
389 return HandleBackspace();
392 return HandleDelete();
395 return HandleArrowLeft();
398 return HandleArrowRight();
401 return HandleArrowUp();
404 return HandleArrowDown();
413 return HandleLeftCtrl();
416 return HandleRightCtrl();
419 return HandleInsert();
424 bool HandleLeftCtrl() {
425 if (cursor_position() == 0) {
430 while (cursor_position()) {
431 const size_t previous =
GlyphPrevious(content(), cursor_position());
432 if (IsWordCharacter(content(), previous)) {
435 cursor_position() =
static_cast<int>(previous);
438 while (cursor_position()) {
439 const size_t previous =
GlyphPrevious(content(), cursor_position());
440 if (!IsWordCharacter(content(), previous)) {
443 cursor_position() =
static_cast<int>(previous);
448 bool HandleRightCtrl() {
449 if (cursor_position() == (
int)content().
size()) {
454 while (cursor_position() < (
int)content().
size()) {
456 static_cast<int>(
GlyphNext(content(), cursor_position()));
457 if (IsWordCharacter(content(), cursor_position())) {
462 while (cursor_position() < (
int)content().
size()) {
463 const size_t next =
GlyphNext(content(), cursor_position());
464 if (!IsWordCharacter(content(), cursor_position())) {
467 cursor_position() =
static_cast<int>(next);
473 bool HandleMouse(Event event) {
474 hovered_ = box_.Contain(event.mouse().x,
490 if (content->empty()) {
491 cursor_position() = 0;
496 std::vector<std::string> lines = Split(*content);
498 int cursor_char_index = cursor_position();
499 for (
const auto& line : lines) {
500 if (cursor_char_index <= (
int)line.size()) {
504 cursor_char_index -=
static_cast<int>(line.size() + 1);
507 const int cursor_column =
509 ?
GlyphCount(lines[cursor_line].substr(0, cursor_char_index))
510 : string_width(lines[cursor_line].substr(0, cursor_char_index));
512 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
513 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
516 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
518 const std::string empty_string;
519 const std::string& line = new_cursor_line < (
int)lines.size()
520 ? lines[new_cursor_line]
526 if (new_cursor_column == cursor_column &&
527 new_cursor_line == cursor_line) {
532 cursor_position() = 0;
533 for (
int i = 0; i < new_cursor_line; ++i) {
534 cursor_position() +=
static_cast<int>(lines[i].size() + 1);
536 while (new_cursor_column > 0) {
538 new_cursor_column -= 1;
541 static_cast<int>(GlyphWidth(content(), cursor_position()));
544 static_cast<int>(
GlyphNext(content(), cursor_position()));
551 bool HandleInsert() {
552 insert() = !insert();
556 bool Focusable() const final {
return true; }
558 bool hovered_ =
false;
618 option.
content = std::move(content);
645 option.
content = std::move(content);
An adapter. Own or reference a constant string. For convenience, this class convert multiple mutable ...
static const Event ArrowLeftCtrl
static InputOption Default()
Create the default input style:
static const Event Backspace
static const Event ArrowUp
std::function< Element(InputState)> transform
static const Event ArrowDown
StringRef placeholder
The content of the input when it's empty.
StringRef content
The content of the input.
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={})
An input box for editing text.
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Element text(std::wstring_view text)
Display a piece of unicode text.
Element vbox(Elements)
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
Element hbox(Elements)
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)
int string_width(std::string_view)
Decorator reflect(Box &box)
bool IsFullWidth(uint32_t ucs)
Element frame(Element)
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)