32 std::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(
"");
45 size_t GlyphWidth(
const std::string& input,
size_t iter) {
56 bool IsWordCodePoint(uint32_t codepoint) {
84 bool IsWordCharacter(
const std::string& input,
size_t iter) {
90 return IsWordCodePoint(ucs);
94 class 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,
478 if (content->empty()) {
479 cursor_position() = 0;
484 std::vector<std::string> lines = Split(*content);
486 int cursor_char_index = cursor_position();
487 for (
const auto& line : lines) {
488 if (cursor_char_index <= (
int)line.size()) {
492 cursor_char_index -= line.size() + 1;
495 const int cursor_column =
496 string_width(lines[cursor_line].substr(0, cursor_char_index));
498 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
499 int new_cursor_line = cursor_line +
event.mouse().y - cursor_box_.y_min;
502 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
504 const std::string empty_string;
505 const std::string& line = new_cursor_line < (int)lines.size()
506 ? lines[new_cursor_line]
510 if (new_cursor_column == cursor_column &&
511 new_cursor_line == cursor_line) {
516 cursor_position() = 0;
517 for (
int i = 0; i < new_cursor_line; ++i) {
518 cursor_position() += lines[i].size() + 1;
520 while (new_cursor_column > 0) {
521 new_cursor_column -= GlyphWidth(content(), cursor_position());
522 cursor_position() =
GlyphNext(content(), cursor_position());
529 bool HandleInsert() {
530 insert() = !insert();
534 bool Focusable() const final {
return true; }
536 bool hovered_ =
false;
568 return Make<InputBase>(std::move(option));
596 option.
content = std::move(content);
597 return Make<InputBase>(std::move(option));
622 option.
content = std::move(content);
624 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