30 std::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(
"");
43 size_t GlyphWidth(
const std::string& input,
size_t iter) {
54 bool IsWordCodePoint(uint32_t codepoint) {
82 bool IsWordCharacter(
const std::string& input,
size_t iter) {
88 return IsWordCodePoint(ucs);
92 class 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_) ?
select
105 auto transform_func =
109 if (content->empty()) {
115 return transform_func({
116 std::move(element), hovered_, is_focused,
123 const std::vector<std::string> lines = Split(*content);
125 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
129 int cursor_char_index = cursor_position();
130 for (
const auto& line : lines) {
131 if (cursor_char_index <= (
int)line.size()) {
135 cursor_char_index -=
static_cast<int>(line.size() + 1);
140 elements.push_back(
text(
"") | focused);
143 elements.reserve(lines.size());
144 for (
size_t i = 0; i < lines.size(); ++i) {
145 const std::string& line = lines[i];
148 if (
int(i) != cursor_line) {
149 elements.push_back(Text(line));
154 if (cursor_char_index >= (
int)line.size()) {
155 elements.push_back(
hbox({
164 const int glyph_start = cursor_char_index;
165 const int glyph_end =
static_cast<int>(
GlyphNext(line, glyph_start));
166 const std::string part_before_cursor = line.substr(0, glyph_start);
167 const std::string part_at_cursor =
168 line.substr(glyph_start, glyph_end - glyph_start);
169 const std::string part_after_cursor = line.substr(glyph_end);
170 auto element =
hbox({
171 Text(part_before_cursor),
172 Text(part_at_cursor) | focused |
reflect(cursor_box_),
173 Text(part_after_cursor),
176 elements.push_back(element);
179 auto element =
vbox(std::move(elements)) |
frame;
180 return transform_func({
181 std::move(element), hovered_, is_focused,
187 Element Text(
const std::string& input) {
193 out.reserve(10 + input.size() * 3 / 2);
194 for (
size_t i = 0; i < input.size(); ++i) {
200 bool HandleBackspace() {
201 if (cursor_position() == 0) {
204 const size_t start =
GlyphPrevious(content(), cursor_position());
205 const size_t end = cursor_position();
206 content->erase(start, end - start);
207 cursor_position() =
static_cast<int>(start);
213 if (cursor_position() == (
int)content->size()) {
216 const size_t start = cursor_position();
217 const size_t end =
GlyphNext(content(), cursor_position());
218 content->erase(start, end - start);
222 bool HandleDelete() {
230 bool HandleArrowLeft() {
231 if (cursor_position() == 0) {
236 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
240 bool HandleArrowRight() {
241 if (cursor_position() == (
int)content->size()) {
246 static_cast<int>(
GlyphNext(content(), cursor_position()));
250 size_t CursorColumn() {
251 size_t iter = cursor_position();
258 if (content()[iter] ==
'\n') {
261 width +=
static_cast<int>(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 -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
276 static_cast<int>(
GlyphNext(content(), cursor_position()));
280 bool HandleArrowUp() {
281 if (cursor_position() == 0) {
285 const size_t columns = CursorColumn();
289 if (cursor_position() == 0) {
292 const size_t previous =
GlyphPrevious(content(), cursor_position());
293 if (content()[previous] ==
'\n') {
296 cursor_position() =
static_cast<int>(previous);
299 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
301 if (cursor_position() == 0) {
304 const size_t previous =
GlyphPrevious(content(), cursor_position());
305 if (content()[previous] ==
'\n') {
308 cursor_position() =
static_cast<int>(previous);
311 MoveCursorColumn(
static_cast<int>(columns));
315 bool HandleArrowDown() {
316 if (cursor_position() == (
int)content->size()) {
320 const size_t columns = CursorColumn();
324 if (content()[cursor_position()] ==
'\n') {
328 static_cast<int>(
GlyphNext(content(), cursor_position()));
329 if (cursor_position() == (int)content().size()) {
334 static_cast<int>(
GlyphNext(content(), cursor_position()));
336 MoveCursorColumn(
static_cast<int>(columns));
341 cursor_position() = 0;
346 cursor_position() =
static_cast<int>(content->size());
350 bool HandleReturn() {
352 HandleCharacter(
"\n");
358 bool HandleCharacter(
const std::string& character) {
359 if (!insert() && cursor_position() < (
int)content->size() &&
360 content()[cursor_position()] !=
'\n') {
363 content->insert(cursor_position(), character);
364 cursor_position() +=
static_cast<int>(character.size());
369 bool OnEvent(Event event)
override {
370 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
373 return HandleReturn();
375 if (event.is_character()) {
376 return HandleCharacter(event.character());
378 if (event.is_mouse()) {
379 return HandleMouse(event);
382 return HandleBackspace();
385 return HandleDelete();
388 return HandleArrowLeft();
391 return HandleArrowRight();
394 return HandleArrowUp();
397 return HandleArrowDown();
406 return HandleLeftCtrl();
409 return HandleRightCtrl();
412 return HandleInsert();
417 bool HandleLeftCtrl() {
418 if (cursor_position() == 0) {
423 while (cursor_position()) {
424 const size_t previous =
GlyphPrevious(content(), cursor_position());
425 if (IsWordCharacter(content(), previous)) {
428 cursor_position() =
static_cast<int>(previous);
431 while (cursor_position()) {
432 const size_t previous =
GlyphPrevious(content(), cursor_position());
433 if (!IsWordCharacter(content(), previous)) {
436 cursor_position() =
static_cast<int>(previous);
441 bool HandleRightCtrl() {
442 if (cursor_position() == (
int)content().
size()) {
447 while (cursor_position() < (
int)content().
size()) {
449 static_cast<int>(
GlyphNext(content(), cursor_position()));
450 if (IsWordCharacter(content(), cursor_position())) {
455 while (cursor_position() < (int)content().size()) {
456 const size_t next =
GlyphNext(content(), cursor_position());
457 if (!IsWordCharacter(content(), cursor_position())) {
460 cursor_position() =
static_cast<int>(next);
466 bool HandleMouse(Event event) {
467 hovered_ = box_.Contain(event.mouse().x,
483 if (content->empty()) {
484 cursor_position() = 0;
489 std::vector<std::string> lines = Split(*content);
491 int cursor_char_index = cursor_position();
492 for (
const auto& line : lines) {
493 if (cursor_char_index <= (
int)line.size()) {
497 cursor_char_index -=
static_cast<int>(line.size() + 1);
500 const int cursor_column =
501 string_width(lines[cursor_line].substr(0, cursor_char_index));
503 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
504 int new_cursor_line = cursor_line +
event.mouse().y - cursor_box_.y_min;
507 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
509 const std::string empty_string;
510 const std::string& line = new_cursor_line < (int)lines.size()
511 ? lines[new_cursor_line]
515 if (new_cursor_column == cursor_column &&
516 new_cursor_line == cursor_line) {
521 cursor_position() = 0;
522 for (
int i = 0; i < new_cursor_line; ++i) {
523 cursor_position() +=
static_cast<int>(lines[i].size() + 1);
525 while (new_cursor_column > 0) {
527 static_cast<int>(GlyphWidth(content(), cursor_position()));
529 static_cast<int>(
GlyphNext(content(), cursor_position()));
536 bool HandleInsert() {
537 insert() = !insert();
541 bool Focusable() const final {
return true; }
543 bool hovered_ =
false;
575 return Make<InputBase>(std::move(option));
603 option.
content = std::move(content);
604 return Make<InputBase>(std::move(option));
629 option.
content = std::move(content);
631 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