2023-08-19 19:56:36 +08:00
|
|
|
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
|
|
|
// Use of this source code is governed by the MIT license that can be found in
|
|
|
|
// the LICENSE file.
|
2022-02-05 22:03:45 +08:00
|
|
|
#include <algorithm> // for max, min
|
2022-03-31 08:17:43 +08:00
|
|
|
#include <cstddef> // for size_t
|
2023-06-05 03:06:19 +08:00
|
|
|
#include <cstdint> // for uint32_t
|
2021-07-10 19:20:43 +08:00
|
|
|
#include <functional> // for function
|
2023-05-02 19:32:37 +08:00
|
|
|
#include <memory> // for allocator, shared_ptr, allocator_traits<>::value_type
|
|
|
|
#include <sstream> // for basic_istream, stringstream
|
|
|
|
#include <string> // for string, basic_string, operator==, getline
|
|
|
|
#include <utility> // for move
|
|
|
|
#include <vector> // for vector
|
2021-05-10 02:32:27 +08:00
|
|
|
|
2021-07-10 19:20:43 +08:00
|
|
|
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
|
|
|
|
#include "ftxui/component/component.hpp" // for Make, Input
|
|
|
|
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
|
|
|
#include "ftxui/component/component_options.hpp" // for InputOption
|
2023-05-02 19:32:37 +08:00
|
|
|
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::ArrowUp, Event::Backspace, Event::Delete, Event::End, Event::Home, Event::Return
|
2021-05-10 02:32:27 +08:00
|
|
|
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
|
|
|
#include "ftxui/component/screen_interactive.hpp" // for Component
|
2023-05-02 19:32:37 +08:00
|
|
|
#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
|
2021-07-10 19:20:43 +08:00
|
|
|
#include "ftxui/screen/box.hpp" // for Box
|
2023-05-02 19:32:37 +08:00
|
|
|
#include "ftxui/screen/string.hpp" // for string_width
|
|
|
|
#include "ftxui/screen/string_internal.hpp" // for GlyphNext, GlyphPrevious, WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, IsFullWidth, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
|
|
|
|
#include "ftxui/screen/util.hpp" // for clamp
|
|
|
|
#include "ftxui/util/ref.hpp" // for StringRef, Ref
|
2021-05-10 02:32:27 +08:00
|
|
|
|
|
|
|
namespace ftxui {
|
2020-03-24 04:26:00 +08:00
|
|
|
|
2021-12-13 04:31:54 +08:00
|
|
|
namespace {
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
std::vector<std::string> Split(const std::string& input) {
|
|
|
|
std::vector<std::string> output;
|
|
|
|
std::stringstream ss(input);
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(ss, line)) {
|
|
|
|
output.push_back(line);
|
|
|
|
}
|
|
|
|
if (input.back() == '\n') {
|
2023-06-25 23:22:05 +08:00
|
|
|
output.emplace_back("");
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t GlyphWidth(const std::string& input, size_t iter) {
|
|
|
|
uint32_t ucs = 0;
|
2023-06-25 23:22:05 +08:00
|
|
|
if (!EatCodePoint(input, iter, &iter, &ucs)) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return 0;
|
2023-06-25 23:22:05 +08:00
|
|
|
}
|
|
|
|
if (IsFullWidth(ucs)) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return 2;
|
2023-06-25 23:22:05 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsWordCodePoint(uint32_t codepoint) {
|
|
|
|
switch (CodepointToWordBreakProperty(codepoint)) {
|
2022-10-07 03:16:55 +08:00
|
|
|
case WordBreakProperty::ALetter:
|
|
|
|
case WordBreakProperty::Hebrew_Letter:
|
|
|
|
case WordBreakProperty::Katakana:
|
|
|
|
case WordBreakProperty::Numeric:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case WordBreakProperty::CR:
|
|
|
|
case WordBreakProperty::Double_Quote:
|
|
|
|
case WordBreakProperty::LF:
|
|
|
|
case WordBreakProperty::MidLetter:
|
|
|
|
case WordBreakProperty::MidNum:
|
|
|
|
case WordBreakProperty::MidNumLet:
|
|
|
|
case WordBreakProperty::Newline:
|
|
|
|
case WordBreakProperty::Single_Quote:
|
|
|
|
case WordBreakProperty::WSegSpace:
|
2023-05-02 19:32:37 +08:00
|
|
|
// Unexpected/Unsure
|
2022-10-07 03:16:55 +08:00
|
|
|
case WordBreakProperty::Extend:
|
|
|
|
case WordBreakProperty::ExtendNumLet:
|
|
|
|
case WordBreakProperty::Format:
|
|
|
|
case WordBreakProperty::Regional_Indicator:
|
|
|
|
case WordBreakProperty::ZWJ:
|
|
|
|
return false;
|
2022-10-19 04:58:22 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
return false; // NOT_REACHED();
|
2022-10-16 16:45:11 +08:00
|
|
|
}
|
2022-10-07 03:16:55 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool IsWordCharacter(const std::string& input, size_t iter) {
|
|
|
|
uint32_t ucs = 0;
|
|
|
|
if (!EatCodePoint(input, iter, &iter, &ucs)) {
|
|
|
|
return false;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
return IsWordCodePoint(ucs);
|
2021-12-13 04:31:54 +08:00
|
|
|
}
|
|
|
|
|
2021-07-10 20:23:46 +08:00
|
|
|
// An input box. The user can type text into it.
|
2023-06-25 23:22:05 +08:00
|
|
|
class InputBase : public ComponentBase, public InputOption {
|
2021-07-10 18:29:39 +08:00
|
|
|
public:
|
2023-05-02 19:32:37 +08:00
|
|
|
// NOLINTNEXTLINE
|
2023-06-25 23:22:05 +08:00
|
|
|
InputBase(InputOption option) : InputOption(std::move(option)) {}
|
2021-07-10 18:29:39 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
private:
|
2021-07-10 18:29:39 +08:00
|
|
|
// Component implementation:
|
|
|
|
Element Render() override {
|
2022-12-20 01:51:25 +08:00
|
|
|
const bool is_focused = Focused();
|
2023-08-29 03:07:26 +08:00
|
|
|
const auto focused = (!is_focused && !hovered_) ? select
|
|
|
|
: insert() ? focusCursorBarBlinking
|
|
|
|
: focusCursorBlockBlinking;
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
auto transform_func =
|
|
|
|
transform ? transform : InputOption::Default().transform;
|
2021-07-10 18:29:39 +08:00
|
|
|
|
|
|
|
// placeholder.
|
2023-06-25 23:22:05 +08:00
|
|
|
if (content->empty()) {
|
|
|
|
auto element = text(placeholder()) | xflex | frame;
|
2022-03-31 08:17:43 +08:00
|
|
|
if (is_focused) {
|
2022-11-11 21:09:53 +08:00
|
|
|
element |= focus;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
return transform_func({
|
2023-05-02 19:32:37 +08:00
|
|
|
std::move(element), hovered_, is_focused,
|
|
|
|
true // placeholder
|
|
|
|
}) |
|
|
|
|
reflect(box_);
|
|
|
|
}
|
|
|
|
|
|
|
|
Elements elements;
|
2023-06-25 23:22:05 +08:00
|
|
|
const std::vector<std::string> lines = Split(*content);
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
// Find the line and index of the cursor.
|
|
|
|
int cursor_line = 0;
|
2023-06-25 23:22:05 +08:00
|
|
|
int cursor_char_index = cursor_position();
|
2023-05-02 19:32:37 +08:00
|
|
|
for (const auto& line : lines) {
|
|
|
|
if (cursor_char_index <= (int)line.size()) {
|
|
|
|
break;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
cursor_char_index -= line.size() + 1;
|
|
|
|
cursor_line++;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2019-01-20 05:06:05 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
if (lines.empty()) {
|
|
|
|
elements.push_back(text("") | focused);
|
|
|
|
}
|
|
|
|
|
2023-06-01 01:24:08 +08:00
|
|
|
elements.reserve(lines.size());
|
2023-05-02 19:32:37 +08:00
|
|
|
for (size_t i = 0; i < lines.size(); ++i) {
|
|
|
|
const std::string& line = lines[i];
|
|
|
|
|
|
|
|
// This is not the cursor line.
|
|
|
|
if (int(i) != cursor_line) {
|
|
|
|
elements.push_back(Text(line));
|
|
|
|
continue;
|
2022-12-20 01:51:25 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
// The cursor is at the end of the line.
|
|
|
|
if (cursor_char_index >= (int)line.size()) {
|
|
|
|
elements.push_back(hbox({
|
|
|
|
Text(line),
|
|
|
|
text(" ") | focused | reflect(cursor_box_),
|
|
|
|
}) |
|
|
|
|
xflex);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The cursor is on this line.
|
|
|
|
const int glyph_start = cursor_char_index;
|
|
|
|
const int glyph_end = GlyphNext(line, glyph_start);
|
|
|
|
const std::string part_before_cursor = line.substr(0, glyph_start);
|
|
|
|
const std::string part_at_cursor =
|
|
|
|
line.substr(glyph_start, glyph_end - glyph_start);
|
|
|
|
const std::string part_after_cursor = line.substr(glyph_end);
|
|
|
|
auto element = hbox({
|
|
|
|
Text(part_before_cursor),
|
|
|
|
Text(part_at_cursor) | focused | reflect(cursor_box_),
|
|
|
|
Text(part_after_cursor),
|
|
|
|
}) |
|
|
|
|
xflex;
|
|
|
|
elements.push_back(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto element = vbox(std::move(elements)) | frame;
|
2023-06-25 23:22:05 +08:00
|
|
|
return transform_func({
|
2023-05-02 19:32:37 +08:00
|
|
|
std::move(element), hovered_, is_focused,
|
|
|
|
false // placeholder
|
2021-12-13 04:31:54 +08:00
|
|
|
}) |
|
2023-05-02 19:32:37 +08:00
|
|
|
xflex | reflect(box_);
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2021-04-25 22:58:16 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
Element Text(const std::string& input) {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (!password()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return text(input);
|
|
|
|
}
|
2021-04-25 22:58:16 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
std::string out;
|
|
|
|
out.reserve(10 + input.size() * 3 / 2);
|
|
|
|
for (size_t i = 0; i < input.size(); ++i) {
|
|
|
|
out += "•";
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
return text(out);
|
|
|
|
}
|
2018-10-19 04:58:38 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleBackspace() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t start = GlyphPrevious(content(), cursor_position());
|
|
|
|
const size_t end = cursor_position();
|
|
|
|
content->erase(start, end - start);
|
|
|
|
cursor_position() = start;
|
2023-11-12 00:29:19 +08:00
|
|
|
on_change();
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
2021-07-10 18:29:39 +08:00
|
|
|
|
2023-11-12 00:29:19 +08:00
|
|
|
bool DeleteImpl() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == (int)content->size()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t start = cursor_position();
|
|
|
|
const size_t end = GlyphNext(content(), cursor_position());
|
|
|
|
content->erase(start, end - start);
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
2021-07-10 18:29:39 +08:00
|
|
|
|
2023-11-12 00:29:19 +08:00
|
|
|
bool HandleDelete() {
|
|
|
|
if (DeleteImpl()) {
|
|
|
|
on_change();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleArrowLeft() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2018-10-19 04:58:38 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = GlyphPrevious(content(), cursor_position());
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleArrowRight() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == (int)content->size()) {
|
2019-07-01 06:40:55 +08:00
|
|
|
return false;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2019-07-01 06:40:55 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t CursorColumn() {
|
2023-06-25 23:22:05 +08:00
|
|
|
size_t iter = cursor_position();
|
2023-05-02 19:32:37 +08:00
|
|
|
int width = 0;
|
|
|
|
while (true) {
|
|
|
|
if (iter == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
iter = GlyphPrevious(content(), iter);
|
|
|
|
if (content()[iter] == '\n') {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
width += GlyphWidth(content(), iter);
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
return width;
|
|
|
|
}
|
2018-10-19 04:58:38 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
// Move the cursor `columns` on the right, if possible.
|
|
|
|
void MoveCursorColumn(int columns) {
|
|
|
|
while (columns > 0) {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == (int)content().size() ||
|
|
|
|
content()[cursor_position()] == '\n') {
|
2023-05-02 19:32:37 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
columns -= GlyphWidth(content(), cursor_position());
|
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
2019-01-27 09:33:06 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleArrowUp() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t columns = CursorColumn();
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
// Move cursor at the beginning of 2 lines above.
|
|
|
|
while (true) {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t previous = GlyphPrevious(content(), cursor_position());
|
|
|
|
if (content()[previous] == '\n') {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = previous;
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = GlyphPrevious(content(), cursor_position());
|
2023-05-02 19:32:37 +08:00
|
|
|
while (true) {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t previous = GlyphPrevious(content(), cursor_position());
|
|
|
|
if (content()[previous] == '\n') {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = previous;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
MoveCursorColumn(columns);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleArrowDown() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == (int)content->size()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2018-10-21 20:18:11 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
const size_t columns = CursorColumn();
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
// Move cursor at the beginning of the next line
|
|
|
|
while (true) {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (content()[cursor_position()] == '\n') {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
|
|
|
if (cursor_position() == (int)content().size()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
MoveCursorColumn(columns);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleHome() {
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = 0;
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleEnd() {
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = content->size();
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleReturn() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (multiline()) {
|
2023-06-05 03:34:16 +08:00
|
|
|
HandleCharacter("\n");
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
on_enter();
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HandleCharacter(const std::string& character) {
|
2023-08-29 03:07:26 +08:00
|
|
|
if (!insert() && cursor_position() < (int)content->size() &&
|
|
|
|
content()[cursor_position()] != '\n') {
|
2023-11-12 00:29:19 +08:00
|
|
|
DeleteImpl();
|
2023-08-29 03:07:26 +08:00
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
content->insert(cursor_position(), character);
|
|
|
|
cursor_position() += character.size();
|
|
|
|
on_change();
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OnEvent(Event event) override {
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-05 03:34:16 +08:00
|
|
|
if (event == Event::Return) {
|
|
|
|
return HandleReturn();
|
|
|
|
}
|
2021-07-10 18:29:39 +08:00
|
|
|
if (event.is_character()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return HandleCharacter(event.character());
|
|
|
|
}
|
|
|
|
if (event.is_mouse()) {
|
|
|
|
return HandleMouse(event);
|
|
|
|
}
|
|
|
|
if (event == Event::Backspace) {
|
|
|
|
return HandleBackspace();
|
|
|
|
}
|
|
|
|
if (event == Event::Delete) {
|
|
|
|
return HandleDelete();
|
|
|
|
}
|
|
|
|
if (event == Event::ArrowLeft) {
|
|
|
|
return HandleArrowLeft();
|
|
|
|
}
|
|
|
|
if (event == Event::ArrowRight) {
|
|
|
|
return HandleArrowRight();
|
|
|
|
}
|
|
|
|
if (event == Event::ArrowUp) {
|
|
|
|
return HandleArrowUp();
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
if (event == Event::ArrowDown) {
|
|
|
|
return HandleArrowDown();
|
|
|
|
}
|
|
|
|
if (event == Event::Home) {
|
|
|
|
return HandleHome();
|
|
|
|
}
|
|
|
|
if (event == Event::End) {
|
|
|
|
return HandleEnd();
|
|
|
|
}
|
|
|
|
if (event == Event::ArrowLeftCtrl) {
|
|
|
|
return HandleLeftCtrl();
|
|
|
|
}
|
|
|
|
if (event == Event::ArrowRightCtrl) {
|
|
|
|
return HandleRightCtrl();
|
|
|
|
}
|
2023-08-29 03:07:26 +08:00
|
|
|
if (event == Event::Insert) {
|
|
|
|
return HandleInsert();
|
|
|
|
}
|
2021-07-10 18:29:39 +08:00
|
|
|
return false;
|
2021-03-28 08:01:04 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleLeftCtrl() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == 0) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
// Move left, as long as left it not a word.
|
2023-06-25 23:22:05 +08:00
|
|
|
while (cursor_position()) {
|
|
|
|
const size_t previous = GlyphPrevious(content(), cursor_position());
|
|
|
|
if (IsWordCharacter(content(), previous)) {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = previous;
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
2022-10-07 03:16:55 +08:00
|
|
|
// Move left, as long as left is a word character:
|
2023-06-25 23:22:05 +08:00
|
|
|
while (cursor_position()) {
|
|
|
|
const size_t previous = GlyphPrevious(content(), cursor_position());
|
|
|
|
if (!IsWordCharacter(content(), previous)) {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = previous;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
return true;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleRightCtrl() {
|
2023-06-25 23:22:05 +08:00
|
|
|
if (cursor_position() == (int)content().size()) {
|
2023-05-02 19:32:37 +08:00
|
|
|
return false;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
// Move right, until entering a word.
|
2023-06-25 23:22:05 +08:00
|
|
|
while (cursor_position() < (int)content().size()) {
|
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
|
|
|
if (IsWordCharacter(content(), cursor_position())) {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-10-07 03:16:55 +08:00
|
|
|
// Move right, as long as right is a word character:
|
2023-06-25 23:22:05 +08:00
|
|
|
while (cursor_position() < (int)content().size()) {
|
|
|
|
const size_t next = GlyphNext(content(), cursor_position());
|
|
|
|
if (!IsWordCharacter(content(), cursor_position())) {
|
2023-05-02 19:32:37 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = next;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
return true;
|
2022-10-07 03:16:55 +08:00
|
|
|
}
|
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
bool HandleMouse(Event event) {
|
|
|
|
hovered_ = box_.Contain(event.mouse().x, //
|
|
|
|
event.mouse().y) &&
|
|
|
|
CaptureMouse(event);
|
2022-03-31 08:17:43 +08:00
|
|
|
if (!hovered_) {
|
2021-07-10 18:29:39 +08:00
|
|
|
return false;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-11-07 19:01:17 +08:00
|
|
|
|
|
|
|
if (event.mouse().button != Mouse::Left ||
|
|
|
|
event.mouse().motion != Mouse::Pressed) {
|
2021-07-10 18:29:39 +08:00
|
|
|
return false;
|
2021-11-07 19:01:17 +08:00
|
|
|
}
|
2021-03-28 08:01:04 +08:00
|
|
|
|
2021-07-10 18:29:39 +08:00
|
|
|
TakeFocus();
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
if (content->empty()) {
|
|
|
|
cursor_position() = 0;
|
2021-12-13 04:31:54 +08:00
|
|
|
return true;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2021-12-13 04:31:54 +08:00
|
|
|
|
2023-05-02 19:32:37 +08:00
|
|
|
// Find the line and index of the cursor.
|
2023-06-25 23:22:05 +08:00
|
|
|
std::vector<std::string> lines = Split(*content);
|
2023-05-02 19:32:37 +08:00
|
|
|
int cursor_line = 0;
|
2023-06-25 23:22:05 +08:00
|
|
|
int cursor_char_index = cursor_position();
|
2023-05-02 19:32:37 +08:00
|
|
|
for (const auto& line : lines) {
|
|
|
|
if (cursor_char_index <= (int)line.size()) {
|
2021-12-13 04:31:54 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
cursor_char_index -= line.size() + 1;
|
|
|
|
cursor_line++;
|
2021-12-13 04:31:54 +08:00
|
|
|
}
|
2023-06-25 23:22:05 +08:00
|
|
|
const int cursor_column =
|
2023-05-02 19:32:37 +08:00
|
|
|
string_width(lines[cursor_line].substr(0, cursor_char_index));
|
|
|
|
|
|
|
|
int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min;
|
|
|
|
int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
|
|
|
|
|
|
|
|
// Fix the new cursor position:
|
|
|
|
new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0);
|
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
const std::string empty_string;
|
2023-05-02 19:32:37 +08:00
|
|
|
const std::string& line = new_cursor_line < (int)lines.size()
|
|
|
|
? lines[new_cursor_line]
|
|
|
|
: empty_string;
|
|
|
|
new_cursor_column = util::clamp(new_cursor_column, 0, string_width(line));
|
|
|
|
|
|
|
|
if (new_cursor_column == cursor_column && //
|
|
|
|
new_cursor_line == cursor_line) {
|
|
|
|
return false;
|
2022-03-31 08:17:43 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
|
|
|
// Convert back the new_cursor_{line,column} toward cursor_position:
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() = 0;
|
2023-05-02 19:32:37 +08:00
|
|
|
for (int i = 0; i < new_cursor_line; ++i) {
|
2023-06-25 23:22:05 +08:00
|
|
|
cursor_position() += lines[i].size() + 1;
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
|
|
|
while (new_cursor_column > 0) {
|
2023-06-25 23:22:05 +08:00
|
|
|
new_cursor_column -= GlyphWidth(content(), cursor_position());
|
|
|
|
cursor_position() = GlyphNext(content(), cursor_position());
|
2021-07-10 18:29:39 +08:00
|
|
|
}
|
2023-05-02 19:32:37 +08:00
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
on_change();
|
2018-10-19 04:58:38 +08:00
|
|
|
return true;
|
|
|
|
}
|
2021-08-06 04:40:40 +08:00
|
|
|
|
2023-08-29 03:07:26 +08:00
|
|
|
bool HandleInsert() {
|
|
|
|
insert() = !insert();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-08-07 02:32:33 +08:00
|
|
|
bool Focusable() const final { return true; }
|
2021-08-06 04:40:40 +08:00
|
|
|
|
2021-11-07 19:01:17 +08:00
|
|
|
bool hovered_ = false;
|
2018-10-19 04:58:38 +08:00
|
|
|
|
2021-09-08 15:36:37 +08:00
|
|
|
Box box_;
|
2021-07-10 18:29:39 +08:00
|
|
|
Box cursor_box_;
|
|
|
|
};
|
2021-04-25 22:58:16 +08:00
|
|
|
|
2021-12-13 04:31:54 +08:00
|
|
|
} // namespace
|
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
/// @brief An input box for editing text.
|
|
|
|
/// @param option Additional optional parameters.
|
|
|
|
/// @ingroup component
|
|
|
|
/// @see InputBase
|
|
|
|
///
|
|
|
|
/// ### Example
|
|
|
|
///
|
|
|
|
/// ```cpp
|
|
|
|
/// auto screen = ScreenInteractive::FitComponent();
|
|
|
|
/// std::string content= "";
|
|
|
|
/// std::string placeholder = "placeholder";
|
|
|
|
/// Component input = Input({
|
|
|
|
/// .content = &content,
|
|
|
|
/// .placeholder = &placeholder,
|
|
|
|
/// })
|
|
|
|
/// screen.Loop(input);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ### Output
|
|
|
|
///
|
|
|
|
/// ```bash
|
|
|
|
/// placeholder
|
|
|
|
/// ```
|
|
|
|
Component Input(InputOption option) {
|
|
|
|
return Make<InputBase>(std::move(option));
|
|
|
|
}
|
|
|
|
|
2021-07-10 18:29:39 +08:00
|
|
|
/// @brief An input box for editing text.
|
|
|
|
/// @param content The editable content.
|
2021-07-10 20:23:46 +08:00
|
|
|
/// @param option Additional optional parameters.
|
2021-07-10 18:29:39 +08:00
|
|
|
/// @ingroup component
|
|
|
|
/// @see InputBase
|
|
|
|
///
|
|
|
|
/// ### Example
|
|
|
|
///
|
|
|
|
/// ```cpp
|
|
|
|
/// auto screen = ScreenInteractive::FitComponent();
|
2021-08-09 05:25:20 +08:00
|
|
|
/// std::string content= "";
|
|
|
|
/// std::string placeholder = "placeholder";
|
2023-06-25 23:22:05 +08:00
|
|
|
/// Component input = Input(content, {
|
|
|
|
/// .placeholder = &placeholder,
|
|
|
|
/// .password = true,
|
|
|
|
/// })
|
2021-07-10 18:29:39 +08:00
|
|
|
/// screen.Loop(input);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ### Output
|
|
|
|
///
|
|
|
|
/// ```bash
|
|
|
|
/// placeholder
|
|
|
|
/// ```
|
2023-06-25 23:22:05 +08:00
|
|
|
Component Input(StringRef content, InputOption option) {
|
2023-08-13 03:18:33 +08:00
|
|
|
option.content = std::move(content);
|
2023-06-25 23:22:05 +08:00
|
|
|
return Make<InputBase>(std::move(option));
|
2023-05-02 19:32:37 +08:00
|
|
|
}
|
|
|
|
|
2023-06-25 23:22:05 +08:00
|
|
|
/// @brief An input box for editing text.
|
|
|
|
/// @param content The editable content.
|
|
|
|
/// @param option Additional optional parameters.
|
|
|
|
/// @ingroup component
|
|
|
|
/// @see InputBase
|
|
|
|
///
|
|
|
|
/// ### Example
|
|
|
|
///
|
|
|
|
/// ```cpp
|
|
|
|
/// auto screen = ScreenInteractive::FitComponent();
|
|
|
|
/// std::string content= "";
|
|
|
|
/// std::string placeholder = "placeholder";
|
|
|
|
/// Component input = Input(content, placeholder);
|
|
|
|
/// screen.Loop(input);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ### Output
|
|
|
|
///
|
|
|
|
/// ```bash
|
|
|
|
/// placeholder
|
|
|
|
/// ```
|
|
|
|
Component Input(StringRef content, StringRef placeholder, InputOption option) {
|
2023-08-13 03:18:33 +08:00
|
|
|
option.content = std::move(content);
|
|
|
|
option.placeholder = std::move(placeholder);
|
2023-06-25 23:22:05 +08:00
|
|
|
return Make<InputBase>(std::move(option));
|
2021-04-25 22:58:16 +08:00
|
|
|
}
|
|
|
|
|
2019-01-12 22:00:08 +08:00
|
|
|
} // namespace ftxui
|