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 if (cursor_char_index >= (
int)line.size()) {
152 elements.push_back(
hbox({
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),
173 elements.push_back(element);
176 auto element =
vbox(std::move(elements)) |
frame;
177 return transform_func({
178 std::move(element), hovered_, is_focused,
191 out.reserve(glyph_count * 3);
192 for (
size_t i = 0; i < glyph_count; ++i) {
198 bool HandleBackspace() {
199 if (cursor_position() == 0) {
202 const size_t start =
GlyphPrevious(content(), cursor_position());
203 const size_t end = cursor_position();
204 content->erase(start, end - start);
205 cursor_position() =
static_cast<int>(start);
211 if (cursor_position() == (
int)content->size()) {
214 const size_t start = cursor_position();
215 const size_t end =
GlyphNext(content(), cursor_position());
216 content->erase(start, end - start);
220 bool HandleDelete() {
228 bool HandleArrowLeft() {
229 if (cursor_position() == 0) {
234 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
238 bool HandleArrowRight() {
239 if (cursor_position() == (
int)content->size()) {
244 static_cast<int>(
GlyphNext(content(), cursor_position()));
248 size_t CursorColumn() {
249 size_t iter = cursor_position();
256 if (content()[iter] ==
'\n') {
262 width +=
static_cast<int>(GlyphWidth(content(), iter));
269 void MoveCursorColumn(
int columns) {
270 while (columns > 0) {
271 if (cursor_position() == (
int)content().
size() ||
272 content()[cursor_position()] ==
'\n') {
279 columns -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
282 static_cast<int>(
GlyphNext(content(), cursor_position()));
286 bool HandleArrowUp() {
287 if (cursor_position() == 0) {
291 const size_t columns = CursorColumn();
295 if (cursor_position() == 0) {
298 const size_t previous =
GlyphPrevious(content(), cursor_position());
299 if (content()[previous] ==
'\n') {
302 cursor_position() =
static_cast<int>(previous);
305 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
307 if (cursor_position() == 0) {
310 const size_t previous =
GlyphPrevious(content(), cursor_position());
311 if (content()[previous] ==
'\n') {
314 cursor_position() =
static_cast<int>(previous);
317 MoveCursorColumn(
static_cast<int>(columns));
321 bool HandleArrowDown() {
322 if (cursor_position() == (
int)content->size()) {
326 const size_t columns = CursorColumn();
330 if (content()[cursor_position()] ==
'\n') {
334 static_cast<int>(
GlyphNext(content(), cursor_position()));
335 if (cursor_position() == (
int)content().
size()) {
340 static_cast<int>(
GlyphNext(content(), cursor_position()));
342 MoveCursorColumn(
static_cast<int>(columns));
347 cursor_position() = 0;
352 cursor_position() =
static_cast<int>(content->size());
356 bool HandleReturn() {
358 HandleCharacter(
"\n");
364 bool HandleCharacter(
const std::string& character) {
365 if (!insert() && cursor_position() < (
int)content->size() &&
366 content()[cursor_position()] !=
'\n') {
369 content->insert(cursor_position(), character);
370 cursor_position() +=
static_cast<int>(character.size());
375 bool OnEvent(Event event)
override {
376 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
379 return HandleReturn();
381 if (event.is_character()) {
382 return HandleCharacter(event.character());
384 if (event.is_mouse()) {
385 return HandleMouse(event);
388 return HandleBackspace();
391 return HandleDelete();
394 return HandleArrowLeft();
397 return HandleArrowRight();
400 return HandleArrowUp();
403 return HandleArrowDown();
412 return HandleLeftCtrl();
415 return HandleRightCtrl();
418 return HandleInsert();
423 bool HandleLeftCtrl() {
424 if (cursor_position() == 0) {
429 while (cursor_position()) {
430 const size_t previous =
GlyphPrevious(content(), cursor_position());
431 if (IsWordCharacter(content(), previous)) {
434 cursor_position() =
static_cast<int>(previous);
437 while (cursor_position()) {
438 const size_t previous =
GlyphPrevious(content(), cursor_position());
439 if (!IsWordCharacter(content(), previous)) {
442 cursor_position() =
static_cast<int>(previous);
447 bool HandleRightCtrl() {
448 if (cursor_position() == (
int)content().
size()) {
453 while (cursor_position() < (
int)content().
size()) {
455 static_cast<int>(
GlyphNext(content(), cursor_position()));
456 if (IsWordCharacter(content(), cursor_position())) {
461 while (cursor_position() < (
int)content().
size()) {
462 const size_t next =
GlyphNext(content(), cursor_position());
463 if (!IsWordCharacter(content(), cursor_position())) {
466 cursor_position() =
static_cast<int>(next);
472 bool HandleMouse(Event event) {
473 hovered_ = box_.Contain(event.mouse().x,
489 if (content->empty()) {
490 cursor_position() = 0;
495 std::vector<std::string> lines = Split(*content);
497 int cursor_char_index = cursor_position();
498 for (
const auto& line : lines) {
499 if (cursor_char_index <= (
int)line.size()) {
503 cursor_char_index -=
static_cast<int>(line.size() + 1);
506 const int cursor_column =
508 ?
GlyphCount(lines[cursor_line].substr(0, cursor_char_index))
509 : string_width(lines[cursor_line].substr(0, cursor_char_index));
511 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
512 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
515 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
517 const std::string empty_string;
518 const std::string& line = new_cursor_line < (
int)lines.size()
519 ? lines[new_cursor_line]
525 if (new_cursor_column == cursor_column &&
526 new_cursor_line == cursor_line) {
531 cursor_position() = 0;
532 for (
int i = 0; i < new_cursor_line; ++i) {
533 cursor_position() +=
static_cast<int>(lines[i].size() + 1);
535 while (new_cursor_column > 0) {
537 new_cursor_column -= 1;
540 static_cast<int>(GlyphWidth(content(), cursor_position()));
543 static_cast<int>(
GlyphNext(content(), cursor_position()));
550 bool HandleInsert() {
551 insert() = !insert();
555 bool Focusable() const final {
return true; }
557 bool hovered_ =
false;
617 option.
content = std::move(content);
644 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)