32std::vector<std::string> Split(
const std::string& input) {
33 std::vector<std::string> output;
34 std::stringstream ss(input);
36 while (std::getline(ss, line)) {
37 output.push_back(line);
39 if (input.back() ==
'\n') {
40 output.emplace_back(
"");
45size_t GlyphWidth(
const std::string& input,
size_t iter) {
56bool IsWordCodePoint(uint32_t codepoint) {
84bool IsWordCharacter(
const std::string& input,
size_t iter) {
90 return IsWordCodePoint(ucs);
94class InputBase :
public ComponentBase,
public InputOption {
97 InputBase(InputOption option) : InputOption(std::move(option)) {}
102 const bool is_focused = Focused();
103 const auto focused = (!is_focused && !hovered_) ?
select
107 auto transform_func =
111 if (content->empty()) {
117 return transform_func({
118 std::move(element), hovered_, is_focused,
125 const std::vector<std::string> lines = Split(*content);
127 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
131 int cursor_char_index = cursor_position();
132 for (
const auto& line : lines) {
133 if (cursor_char_index <= (
int)line.size()) {
137 cursor_char_index -= line.size() + 1;
142 elements.push_back(
text(
"") | focused);
145 elements.reserve(lines.size());
146 for (
size_t i = 0; i < lines.size(); ++i) {
147 const std::string& line = lines[i];
150 if (
int(i) != cursor_line) {
151 elements.push_back(Text(line));
156 if (cursor_char_index >= (
int)line.size()) {
157 elements.push_back(
hbox({
166 const int glyph_start = cursor_char_index;
167 const int glyph_end =
GlyphNext(line, glyph_start);
168 const std::string part_before_cursor = line.substr(0, glyph_start);
169 const std::string part_at_cursor =
170 line.substr(glyph_start, glyph_end - glyph_start);
171 const std::string part_after_cursor = line.substr(glyph_end);
172 auto element =
hbox({
173 Text(part_before_cursor),
174 Text(part_at_cursor) | focused |
reflect(cursor_box_),
175 Text(part_after_cursor),
178 elements.push_back(element);
181 auto element =
vbox(std::move(elements)) |
frame;
182 return transform_func({
183 std::move(element), hovered_, is_focused,
189 Element Text(
const std::string& input) {
195 out.reserve(10 + input.size() * 3 / 2);
196 for (
size_t i = 0; i < input.size(); ++i) {
202 bool HandleBackspace() {
203 if (cursor_position() == 0) {
206 const size_t start =
GlyphPrevious(content(), cursor_position());
207 const size_t end = cursor_position();
208 content->erase(start, end - start);
209 cursor_position() = start;
215 if (cursor_position() == (
int)content->size()) {
218 const size_t start = cursor_position();
219 const size_t end =
GlyphNext(content(), cursor_position());
220 content->erase(start, end - start);
224 bool HandleDelete() {
232 bool HandleArrowLeft() {
233 if (cursor_position() == 0) {
237 cursor_position() =
GlyphPrevious(content(), cursor_position());
241 bool HandleArrowRight() {
242 if (cursor_position() == (
int)content->size()) {
246 cursor_position() =
GlyphNext(content(), cursor_position());
250 size_t CursorColumn() {
251 size_t iter = cursor_position();
258 if (content()[iter] ==
'\n') {
261 width += GlyphWidth(content(), iter);
267 void MoveCursorColumn(
int columns) {
268 while (columns > 0) {
269 if (cursor_position() == (
int)content().
size() ||
270 content()[cursor_position()] ==
'\n') {
274 columns -= GlyphWidth(content(), cursor_position());
275 cursor_position() =
GlyphNext(content(), cursor_position());
279 bool HandleArrowUp() {
280 if (cursor_position() == 0) {
284 const size_t columns = CursorColumn();
288 if (cursor_position() == 0) {
291 const size_t previous =
GlyphPrevious(content(), cursor_position());
292 if (content()[previous] ==
'\n') {
295 cursor_position() = previous;
297 cursor_position() =
GlyphPrevious(content(), cursor_position());
299 if (cursor_position() == 0) {
302 const size_t previous =
GlyphPrevious(content(), cursor_position());
303 if (content()[previous] ==
'\n') {
306 cursor_position() = previous;
309 MoveCursorColumn(columns);
313 bool HandleArrowDown() {
314 if (cursor_position() == (
int)content->size()) {
318 const size_t columns = CursorColumn();
322 if (content()[cursor_position()] ==
'\n') {
325 cursor_position() =
GlyphNext(content(), cursor_position());
326 if (cursor_position() == (
int)content().
size()) {
330 cursor_position() =
GlyphNext(content(), cursor_position());
332 MoveCursorColumn(columns);
337 cursor_position() = 0;
342 cursor_position() = content->size();
346 bool HandleReturn() {
348 HandleCharacter(
"\n");
354 bool HandleCharacter(
const std::string& character) {
355 if (!insert() && cursor_position() < (
int)content->size() &&
356 content()[cursor_position()] !=
'\n') {
359 content->insert(cursor_position(), character);
360 cursor_position() += character.size();
365 bool OnEvent(Event event)
override {
366 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
369 return HandleReturn();
371 if (event.is_character()) {
372 return HandleCharacter(event.character());
374 if (event.is_mouse()) {
375 return HandleMouse(event);
378 return HandleBackspace();
381 return HandleDelete();
384 return HandleArrowLeft();
387 return HandleArrowRight();
390 return HandleArrowUp();
393 return HandleArrowDown();
402 return HandleLeftCtrl();
405 return HandleRightCtrl();
408 return HandleInsert();
413 bool HandleLeftCtrl() {
414 if (cursor_position() == 0) {
419 while (cursor_position()) {
420 const size_t previous =
GlyphPrevious(content(), cursor_position());
421 if (IsWordCharacter(content(), previous)) {
424 cursor_position() = previous;
427 while (cursor_position()) {
428 const size_t previous =
GlyphPrevious(content(), cursor_position());
429 if (!IsWordCharacter(content(), previous)) {
432 cursor_position() = previous;
437 bool HandleRightCtrl() {
438 if (cursor_position() == (
int)content().
size()) {
443 while (cursor_position() < (
int)content().
size()) {
444 cursor_position() =
GlyphNext(content(), cursor_position());
445 if (IsWordCharacter(content(), cursor_position())) {
450 while (cursor_position() < (
int)content().
size()) {
451 const size_t next =
GlyphNext(content(), cursor_position());
452 if (!IsWordCharacter(content(), cursor_position())) {
455 cursor_position() = next;
461 bool HandleMouse(Event event) {
462 hovered_ = box_.Contain(event.mouse().x,
469 if (!event.mouse().IsPressed()) {
475 if (content->empty()) {
476 cursor_position() = 0;
481 std::vector<std::string> lines = Split(*content);
483 int cursor_char_index = cursor_position();
484 for (
const auto& line : lines) {
485 if (cursor_char_index <= (
int)line.size()) {
489 cursor_char_index -= line.size() + 1;
492 const int cursor_column =
493 string_width(lines[cursor_line].substr(0, cursor_char_index));
495 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
496 int new_cursor_line = cursor_line +
event.mouse().y - cursor_box_.y_min;
499 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
501 const std::string empty_string;
502 const std::string& line = new_cursor_line < (int)lines.size()
503 ? lines[new_cursor_line]
507 if (new_cursor_column == cursor_column &&
508 new_cursor_line == cursor_line) {
513 cursor_position() = 0;
514 for (
int i = 0; i < new_cursor_line; ++i) {
515 cursor_position() += lines[i].size() + 1;
517 while (new_cursor_column > 0) {
518 new_cursor_column -= GlyphWidth(content(), cursor_position());
519 cursor_position() =
GlyphNext(content(), cursor_position());
526 bool HandleInsert() {
527 insert() = !insert();
531 bool Focusable() const final {
return true; }
533 bool hovered_ =
false;
565 return Make<InputBase>(std::move(option));
593 option.
content = std::move(content);
594 return Make<InputBase>(std::move(option));
619 option.
content = std::move(content);
621 return Make<InputBase>(std::move(option));
An adapter. Own or reference a constant string. For convenience, this class convert multiple mutable ...
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
size_t GlyphNext(const std::string &input, size_t start)
Element focusCursorBarBlinking(Element)
Same as focus, but set the cursor shape to be a blinking bar.
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
std::shared_ptr< Node > Element
std::shared_ptr< ComponentBase > Component
int string_width(const std::string &)
Element hbox(Elements)
A container displaying elements horizontally one by one.
Element text(std::wstring text)
Display a piece of unicode text.
std::vector< Element > Elements
Component Input(InputOption options={})
An input box for editing text.
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Element select(Element)
Set the child to be the one selected among its siblings.
Element focus(Element)
Set the child to be the one in focus globally.
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....
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Element focusCursorBlockBlinking(Element)
Same as focus, but set the cursor shape to be a blinking block.
size_t GlyphPrevious(const std::string &input, size_t start)
Element vbox(Elements)
A container displaying elements vertically one by one.
static const Event ArrowLeftCtrl
static const Event Backspace
static const Event ArrowUp
static const Event ArrowDown
static const Event Return
static const Event ArrowLeft
static const Event Delete
static const Event Insert
static const Event ArrowRightCtrl
static const Event ArrowRight