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();
106 auto transform_func =
110 if (content->empty()) {
116 return transform_func({
117 std::move(element), hovered_, is_focused,
124 const std::vector<std::string> lines = Split(*content);
126 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
130 int cursor_char_index = cursor_position();
131 for (
const auto& line : lines) {
132 if (cursor_char_index <= (
int)line.size()) {
136 cursor_char_index -= line.size() + 1;
141 elements.push_back(
text(
"") | focused);
144 elements.reserve(lines.size());
145 for (
size_t i = 0; i < lines.size(); ++i) {
146 const std::string& line = lines[i];
149 if (
int(i) != cursor_line) {
150 elements.push_back(Text(line));
155 if (cursor_char_index >= (
int)line.size()) {
156 elements.push_back(
hbox({
165 const int glyph_start = cursor_char_index;
166 const int glyph_end =
GlyphNext(line, glyph_start);
167 const std::string part_before_cursor = line.substr(0, glyph_start);
168 const std::string part_at_cursor =
169 line.substr(glyph_start, glyph_end - glyph_start);
170 const std::string part_after_cursor = line.substr(glyph_end);
171 auto element =
hbox({
172 Text(part_before_cursor),
173 Text(part_at_cursor) | focused |
reflect(cursor_box_),
174 Text(part_after_cursor),
177 elements.push_back(element);
180 auto element =
vbox(std::move(elements)) |
frame;
181 return transform_func({
182 std::move(element), hovered_, is_focused,
188 Element Text(
const std::string& input) {
194 out.reserve(10 + input.size() * 3 / 2);
195 for (
size_t i = 0; i < input.size(); ++i) {
201 bool HandleBackspace() {
202 if (cursor_position() == 0) {
205 const size_t start =
GlyphPrevious(content(), cursor_position());
206 const size_t end = cursor_position();
207 content->erase(start, end - start);
208 cursor_position() = start;
212 bool HandleDelete() {
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 HandleArrowLeft() {
223 if (cursor_position() == 0) {
227 cursor_position() =
GlyphPrevious(content(), cursor_position());
231 bool HandleArrowRight() {
232 if (cursor_position() == (
int)content->size()) {
236 cursor_position() =
GlyphNext(content(), cursor_position());
240 size_t CursorColumn() {
241 size_t iter = cursor_position();
248 if (content()[iter] ==
'\n') {
251 width += GlyphWidth(content(), iter);
257 void MoveCursorColumn(
int columns) {
258 while (columns > 0) {
259 if (cursor_position() == (
int)content().
size() ||
260 content()[cursor_position()] ==
'\n') {
264 columns -= GlyphWidth(content(), cursor_position());
265 cursor_position() =
GlyphNext(content(), cursor_position());
269 bool HandleArrowUp() {
270 if (cursor_position() == 0) {
274 const size_t columns = CursorColumn();
278 if (cursor_position() == 0) {
281 const size_t previous =
GlyphPrevious(content(), cursor_position());
282 if (content()[previous] ==
'\n') {
285 cursor_position() = previous;
287 cursor_position() =
GlyphPrevious(content(), cursor_position());
289 if (cursor_position() == 0) {
292 const size_t previous =
GlyphPrevious(content(), cursor_position());
293 if (content()[previous] ==
'\n') {
296 cursor_position() = previous;
299 MoveCursorColumn(columns);
303 bool HandleArrowDown() {
304 if (cursor_position() == (
int)content->size()) {
308 const size_t columns = CursorColumn();
312 if (content()[cursor_position()] ==
'\n') {
315 cursor_position() =
GlyphNext(content(), cursor_position());
316 if (cursor_position() == (
int)content().
size()) {
320 cursor_position() =
GlyphNext(content(), cursor_position());
322 MoveCursorColumn(columns);
327 cursor_position() = 0;
332 cursor_position() = content->size();
336 bool HandleReturn() {
338 HandleCharacter(
"\n");
344 bool HandleCharacter(
const std::string& character) {
345 content->insert(cursor_position(), character);
346 cursor_position() += character.size();
352 bool OnEvent(Event event)
override {
353 cursor_position() =
util::clamp(cursor_position(), 0, (
int)content->size());
356 return HandleReturn();
358 if (event.is_character()) {
359 return HandleCharacter(event.character());
361 if (event.is_mouse()) {
362 return HandleMouse(event);
365 return HandleBackspace();
368 return HandleDelete();
371 return HandleArrowLeft();
374 return HandleArrowRight();
377 return HandleArrowUp();
380 return HandleArrowDown();
389 return HandleLeftCtrl();
392 return HandleRightCtrl();
398 bool HandleLeftCtrl() {
399 if (cursor_position() == 0) {
404 while (cursor_position()) {
405 const size_t previous =
GlyphPrevious(content(), cursor_position());
406 if (IsWordCharacter(content(), previous)) {
409 cursor_position() = previous;
412 while (cursor_position()) {
413 const size_t previous =
GlyphPrevious(content(), cursor_position());
414 if (!IsWordCharacter(content(), previous)) {
417 cursor_position() = previous;
422 bool HandleRightCtrl() {
423 if (cursor_position() == (
int)content().
size()) {
428 while (cursor_position() < (
int)content().
size()) {
429 cursor_position() =
GlyphNext(content(), cursor_position());
430 if (IsWordCharacter(content(), cursor_position())) {
435 while (cursor_position() < (
int)content().
size()) {
436 const size_t next =
GlyphNext(content(), cursor_position());
437 if (!IsWordCharacter(content(), cursor_position())) {
440 cursor_position() = next;
446 bool HandleMouse(Event event) {
447 hovered_ = box_.Contain(event.mouse().x,
461 if (content->empty()) {
462 cursor_position() = 0;
467 std::vector<std::string> lines = Split(*content);
469 int cursor_char_index = cursor_position();
470 for (
const auto& line : lines) {
471 if (cursor_char_index <= (
int)line.size()) {
475 cursor_char_index -= line.size() + 1;
478 const int cursor_column =
479 string_width(lines[cursor_line].substr(0, cursor_char_index));
481 int new_cursor_column = cursor_column +
event.mouse().x - cursor_box_.x_min;
482 int new_cursor_line = cursor_line +
event.mouse().y - cursor_box_.y_min;
485 new_cursor_line = std::max(std::min(new_cursor_line, (
int)lines.size()), 0);
487 const std::string empty_string;
488 const std::string& line = new_cursor_line < (int)lines.size()
489 ? lines[new_cursor_line]
493 if (new_cursor_column == cursor_column &&
494 new_cursor_line == cursor_line) {
499 cursor_position() = 0;
500 for (
int i = 0; i < new_cursor_line; ++i) {
501 cursor_position() += lines[i].size() + 1;
503 while (new_cursor_column > 0) {
504 new_cursor_column -= GlyphWidth(content(), cursor_position());
505 cursor_position() =
GlyphNext(content(), cursor_position());
512 bool Focusable() const final {
return true; }
514 bool hovered_ =
false;
574 option.
content = std::move(content);
600 option.
content = std::move(content);
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< 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
Element text(std::wstring text)
Display a piece of unicode text.
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.
size_t GlyphPrevious(const std::string &input, size_t start)
std::shared_ptr< ComponentBase > Component
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 ArrowRightCtrl
static const Event ArrowRight