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_) ? nothing
102 : insert() ? focusCursorBarBlinking
103 : focusCursorBlockBlinking;
105 auto transform_func =
109 if (content->empty()) {
110 auto element =
text(placeholder()) | 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), cursor_line) |
frame;
177 return transform_func({
178 std::move(element), hovered_, is_focused,
190 out.reserve(10 + input.size() * 3 / 2);
191 for (
size_t i = 0; i < input.size(); ++i) {
197 bool HandleBackspace() {
198 if (cursor_position() == 0) {
201 const size_t start =
GlyphPrevious(content(), cursor_position());
202 const size_t end = cursor_position();
203 content->erase(start, end - start);
204 cursor_position() =
static_cast<int>(start);
210 if (cursor_position() == (
int)content->size()) {
213 const size_t start = cursor_position();
214 const size_t end =
GlyphNext(content(), cursor_position());
215 content->erase(start, end - start);
219 bool HandleDelete() {
227 bool HandleArrowLeft() {
228 if (cursor_position() == 0) {
233 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
237 bool HandleArrowRight() {
238 if (cursor_position() == (
int)content->size()) {
243 static_cast<int>(
GlyphNext(content(), cursor_position()));
247 size_t CursorColumn() {
248 size_t iter = cursor_position();
255 if (content()[iter] ==
'\n') {
258 width +=
static_cast<int>(GlyphWidth(content(), iter));
264 void MoveCursorColumn(
int columns) {
265 while (columns > 0) {
266 if (cursor_position() == (
int)content().
size() ||
267 content()[cursor_position()] ==
'\n') {
271 columns -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
273 static_cast<int>(
GlyphNext(content(), cursor_position()));
277 bool HandleArrowUp() {
278 if (cursor_position() == 0) {
282 const size_t columns = CursorColumn();
286 if (cursor_position() == 0) {
289 const size_t previous =
GlyphPrevious(content(), cursor_position());
290 if (content()[previous] ==
'\n') {
293 cursor_position() =
static_cast<int>(previous);
296 static_cast<int>(
GlyphPrevious(content(), cursor_position()));
298 if (cursor_position() == 0) {
301 const size_t previous =
GlyphPrevious(content(), cursor_position());
302 if (content()[previous] ==
'\n') {
305 cursor_position() =
static_cast<int>(previous);
308 MoveCursorColumn(
static_cast<int>(columns));
312 bool HandleArrowDown() {
313 if (cursor_position() == (
int)content->size()) {
317 const size_t columns = CursorColumn();
321 if (content()[cursor_position()] ==
'\n') {
325 static_cast<int>(
GlyphNext(content(), cursor_position()));
326 if (cursor_position() == (
int)content().
size()) {
331 static_cast<int>(
GlyphNext(content(), cursor_position()));
333 MoveCursorColumn(
static_cast<int>(columns));
338 cursor_position() = 0;
343 cursor_position() =
static_cast<int>(content->size());
347 bool HandleReturn() {
349 HandleCharacter(
"\n");
355 bool HandleCharacter(
const std::string& character) {
356 if (!insert() && cursor_position() < (
int)content->size() &&
357 content()[cursor_position()] !=
'\n') {
360 content->insert(cursor_position(), character);
361 cursor_position() +=
static_cast<int>(character.size());
366 bool OnEvent(Event event)
override {
367 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
370 return HandleReturn();
372 if (event.is_character()) {
373 return HandleCharacter(event.character());
375 if (event.is_mouse()) {
376 return HandleMouse(event);
379 return HandleBackspace();
382 return HandleDelete();
385 return HandleArrowLeft();
388 return HandleArrowRight();
391 return HandleArrowUp();
394 return HandleArrowDown();
403 return HandleLeftCtrl();
406 return HandleRightCtrl();
409 return HandleInsert();
414 bool HandleLeftCtrl() {
415 if (cursor_position() == 0) {
420 while (cursor_position()) {
421 const size_t previous =
GlyphPrevious(content(), cursor_position());
422 if (IsWordCharacter(content(), previous)) {
425 cursor_position() =
static_cast<int>(previous);
428 while (cursor_position()) {
429 const size_t previous =
GlyphPrevious(content(), cursor_position());
430 if (!IsWordCharacter(content(), previous)) {
433 cursor_position() =
static_cast<int>(previous);
438 bool HandleRightCtrl() {
439 if (cursor_position() == (
int)content().
size()) {
444 while (cursor_position() < (
int)content().
size()) {
446 static_cast<int>(
GlyphNext(content(), cursor_position()));
447 if (IsWordCharacter(content(), cursor_position())) {
452 while (cursor_position() < (
int)content().
size()) {
453 const size_t next =
GlyphNext(content(), cursor_position());
454 if (!IsWordCharacter(content(), cursor_position())) {
457 cursor_position() =
static_cast<int>(next);
463 bool HandleMouse(Event event) {
464 hovered_ = box_.Contain(event.mouse().x,
480 if (content->empty()) {
481 cursor_position() = 0;
486 std::vector<std::string> lines = Split(*content);
488 int cursor_char_index = cursor_position();
489 for (
const auto& line : lines) {
490 if (cursor_char_index <= (
int)line.size()) {
494 cursor_char_index -=
static_cast<int>(line.size() + 1);
497 const int cursor_column =
498 string_width(lines[cursor_line].substr(0, cursor_char_index));
500 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
501 int new_cursor_line = cursor_line +
event.mouse().y - cursor_box_.y_min;
504 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
506 const std::string empty_string;
507 const std::string& line = new_cursor_line < (int)lines.size()
508 ? lines[new_cursor_line]
512 if (new_cursor_column == cursor_column &&
513 new_cursor_line == cursor_line) {
518 cursor_position() = 0;
519 for (
int i = 0; i < new_cursor_line; ++i) {
520 cursor_position() +=
static_cast<int>(lines[i].size() + 1);
522 while (new_cursor_column > 0) {
524 static_cast<int>(GlyphWidth(content(), cursor_position()));
526 static_cast<int>(
GlyphNext(content(), cursor_position()));
533 bool HandleInsert() {
534 insert() = !insert();
538 bool Focusable() const final {
return true; }
540 bool hovered_ =
false;
600 option.
content = std::move(content);
627 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 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.
size_t GlyphNext(const std::string &input, size_t start)
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
std::shared_ptr< T > Make(Args &&... args)
std::shared_ptr< Node > Element
int string_width(const std::string &)
Element hbox(Elements)
A container displaying elements horizontally one by one.
std::vector< Element > Elements
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
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....
size_t GlyphPrevious(const std::string &input, size_t start)
std::shared_ptr< ComponentBase > Component