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 =
106 transform ? transform : InputOption::Default().transform;
109 if (content->empty()) {
110 auto element = text(placeholder()) | xflex | frame;
112 return transform_func({
113 std::move(element), hovered_, is_focused,
116 focus | reflect(box_);
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({
154 text(
" ") | focused | reflect(cursor_box_),
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,
181 xflex | reflect(box_);
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());
369 if (event == Event::Return) {
370 return HandleReturn();
372 if (event.is_character()) {
373 return HandleCharacter(event.character());
375 if (event.is_mouse()) {
376 return HandleMouse(event);
378 if (event == Event::Backspace) {
379 return HandleBackspace();
381 if (event == Event::Delete) {
382 return HandleDelete();
384 if (event == Event::ArrowLeft) {
385 return HandleArrowLeft();
387 if (event == Event::ArrowRight) {
388 return HandleArrowRight();
390 if (event == Event::ArrowUp) {
391 return HandleArrowUp();
393 if (event == Event::ArrowDown) {
394 return HandleArrowDown();
396 if (event == Event::Home) {
399 if (event == Event::End) {
402 if (event == Event::ArrowLeftCtrl) {
403 return HandleLeftCtrl();
405 if (event == Event::ArrowRightCtrl) {
406 return HandleRightCtrl();
408 if (event == Event::Insert) {
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,
471 if (event.mouse().button != Mouse::Left) {
474 if (event.mouse().motion != Mouse::Pressed) {
480 if (content->empty()) {
481 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 -=
static_cast<int>(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]
508 new_cursor_column = util::clamp(new_cursor_column, 0, string_width(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() +=
static_cast<int>(lines[i].size() + 1);
520 while (new_cursor_column > 0) {
522 static_cast<int>(GlyphWidth(content(), cursor_position()));
524 static_cast<int>(
GlyphNext(content(), cursor_position()));
531 bool HandleInsert() {
532 insert() = !insert();
536 bool Focusable() const final {
return true; }
538 bool hovered_ =
false;
598 option.content = std::move(content);
624Component Input(StringRef content, StringRef placeholder, InputOption option) {
625 option.content = std::move(content);
626 option.placeholder = std::move(placeholder);
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
std::vector< Element > Elements
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Component Input(InputOption options={})
bool IsFullWidth(uint32_t ucs)
size_t GlyphPrevious(const std::string &input, size_t start)
std::shared_ptr< ComponentBase > Component