Loading...
Searching...
No Matches
input.cpp
Go to the documentation of this file.
// 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.
#include <algorithm> // for max, min
#include <cstddef> // for size_t
#include <cstdint> // for uint32_t
#include <functional> // for function
#include <sstream> // for basic_istream, stringstream
#include <string> // for string, basic_string, operator==, getline
#include <utility> // for move
#include <vector> // for vector
#include "ftxui/component/component.hpp" // for Make, Input
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for InputOption
#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
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
#include "ftxui/component/screen_interactive.hpp" // for Component
#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
#include "ftxui/screen/box.hpp" // for Box
#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
namespace ftxui {
namespace {
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') {
output.emplace_back("");
}
return output;
}
size_t GlyphWidth(const std::string& input, size_t iter) {
uint32_t ucs = 0;
if (!EatCodePoint(input, iter, &iter, &ucs)) {
return 0;
}
if (IsFullWidth(ucs)) {
return 2;
}
return 1;
}
bool IsWordCodePoint(uint32_t codepoint) {
switch (CodepointToWordBreakProperty(codepoint)) {
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:
// Unexpected/Unsure
case WordBreakProperty::Extend:
case WordBreakProperty::ExtendNumLet:
case WordBreakProperty::Format:
case WordBreakProperty::Regional_Indicator:
case WordBreakProperty::ZWJ:
return false;
}
return false; // NOT_REACHED();
}
bool IsWordCharacter(const std::string& input, size_t iter) {
uint32_t ucs = 0;
if (!EatCodePoint(input, iter, &iter, &ucs)) {
return false;
}
return IsWordCodePoint(ucs);
}
// An input box. The user can type text into it.
class InputBase : public ComponentBase, public InputOption {
public:
// NOLINTNEXTLINE
InputBase(InputOption option) : InputOption(std::move(option)) {}
private:
// Component implementation:
Element OnRender() override {
const bool is_focused = Focused();
const auto focused = (!is_focused && !hovered_) ? nothing
: insert() ? focusCursorBarBlinking
: focusCursorBlockBlinking;
auto transform_func =
transform ? transform : InputOption::Default().transform;
// placeholder.
if (content->empty()) {
auto element = text(placeholder()) | xflex | frame;
return transform_func({
std::move(element), hovered_, is_focused,
true // placeholder
}) |
focus | reflect(box_);
}
Elements elements;
const std::vector<std::string> lines = Split(*content);
cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
// Find the line and index of the cursor.
int cursor_line = 0;
int cursor_char_index = cursor_position();
for (const auto& line : lines) {
if (cursor_char_index <= (int)line.size()) {
break;
}
cursor_char_index -= static_cast<int>(line.size() + 1);
cursor_line++;
}
if (lines.empty()) {
elements.push_back(text("") | focused);
}
elements.reserve(lines.size());
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;
}
// 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 = static_cast<int>(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), cursor_line) | frame;
return transform_func({
std::move(element), hovered_, is_focused,
false // placeholder
}) |
xflex | reflect(box_);
}
Element Text(const std::string& input) {
if (!password()) {
return text(input);
}
std::string out;
out.reserve(10 + input.size() * 3 / 2);
for (size_t i = 0; i < input.size(); ++i) {
out += "•";
}
return text(out);
}
bool HandleBackspace() {
if (cursor_position() == 0) {
return false;
}
const size_t start = GlyphPrevious(content(), cursor_position());
const size_t end = cursor_position();
content->erase(start, end - start);
cursor_position() = static_cast<int>(start);
on_change();
return true;
}
bool DeleteImpl() {
if (cursor_position() == (int)content->size()) {
return false;
}
const size_t start = cursor_position();
const size_t end = GlyphNext(content(), cursor_position());
content->erase(start, end - start);
return true;
}
bool HandleDelete() {
if (DeleteImpl()) {
on_change();
return true;
}
return false;
}
bool HandleArrowLeft() {
if (cursor_position() == 0) {
return false;
}
cursor_position() =
static_cast<int>(GlyphPrevious(content(), cursor_position()));
return true;
}
bool HandleArrowRight() {
if (cursor_position() == (int)content->size()) {
return false;
}
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
return true;
}
size_t CursorColumn() {
size_t iter = cursor_position();
int width = 0;
while (true) {
if (iter == 0) {
break;
}
iter = GlyphPrevious(content(), iter);
if (content()[iter] == '\n') {
break;
}
width += static_cast<int>(GlyphWidth(content(), iter));
}
return width;
}
// Move the cursor `columns` on the right, if possible.
void MoveCursorColumn(int columns) {
while (columns > 0) {
if (cursor_position() == (int)content().size() ||
content()[cursor_position()] == '\n') {
return;
}
columns -= static_cast<int>(GlyphWidth(content(), cursor_position()));
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
}
}
bool HandleArrowUp() {
if (cursor_position() == 0) {
return false;
}
const size_t columns = CursorColumn();
// Move cursor at the beginning of 2 lines above.
while (true) {
if (cursor_position() == 0) {
return true;
}
const size_t previous = GlyphPrevious(content(), cursor_position());
if (content()[previous] == '\n') {
break;
}
cursor_position() = static_cast<int>(previous);
}
cursor_position() =
static_cast<int>(GlyphPrevious(content(), cursor_position()));
while (true) {
if (cursor_position() == 0) {
break;
}
const size_t previous = GlyphPrevious(content(), cursor_position());
if (content()[previous] == '\n') {
break;
}
cursor_position() = static_cast<int>(previous);
}
MoveCursorColumn(static_cast<int>(columns));
return true;
}
bool HandleArrowDown() {
if (cursor_position() == (int)content->size()) {
return false;
}
const size_t columns = CursorColumn();
// Move cursor at the beginning of the next line
while (true) {
if (content()[cursor_position()] == '\n') {
break;
}
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
if (cursor_position() == (int)content().size()) {
return true;
}
}
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
MoveCursorColumn(static_cast<int>(columns));
return true;
}
bool HandleHome() {
cursor_position() = 0;
return true;
}
bool HandleEnd() {
cursor_position() = static_cast<int>(content->size());
return true;
}
bool HandleReturn() {
if (multiline()) {
HandleCharacter("\n");
}
on_enter();
return true;
}
bool HandleCharacter(const std::string& character) {
if (!insert() && cursor_position() < (int)content->size() &&
content()[cursor_position()] != '\n') {
DeleteImpl();
}
content->insert(cursor_position(), character);
cursor_position() += static_cast<int>(character.size());
on_change();
return true;
}
bool OnEvent(Event event) override {
cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
if (event == Event::Return) {
return HandleReturn();
}
if (event.is_character()) {
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();
}
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();
}
if (event == Event::Insert) {
return HandleInsert();
}
return false;
}
bool HandleLeftCtrl() {
if (cursor_position() == 0) {
return false;
}
// Move left, as long as left it not a word.
while (cursor_position()) {
const size_t previous = GlyphPrevious(content(), cursor_position());
if (IsWordCharacter(content(), previous)) {
break;
}
cursor_position() = static_cast<int>(previous);
}
// Move left, as long as left is a word character:
while (cursor_position()) {
const size_t previous = GlyphPrevious(content(), cursor_position());
if (!IsWordCharacter(content(), previous)) {
break;
}
cursor_position() = static_cast<int>(previous);
}
return true;
}
bool HandleRightCtrl() {
if (cursor_position() == (int)content().size()) {
return false;
}
// Move right, until entering a word.
while (cursor_position() < (int)content().size()) {
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
if (IsWordCharacter(content(), cursor_position())) {
break;
}
}
// Move right, as long as right is a word character:
while (cursor_position() < (int)content().size()) {
const size_t next = GlyphNext(content(), cursor_position());
if (!IsWordCharacter(content(), cursor_position())) {
break;
}
cursor_position() = static_cast<int>(next);
}
return true;
}
bool HandleMouse(Event event) {
hovered_ = box_.Contain(event.mouse().x, //
event.mouse().y) &&
CaptureMouse(event);
if (!hovered_) {
return false;
}
if (event.mouse().button != Mouse::Left) {
return false;
}
if (event.mouse().motion != Mouse::Pressed) {
return false;
}
TakeFocus();
if (content->empty()) {
cursor_position() = 0;
return true;
}
// Find the line and index of the cursor.
std::vector<std::string> lines = Split(*content);
int cursor_line = 0;
int cursor_char_index = cursor_position();
for (const auto& line : lines) {
if (cursor_char_index <= (int)line.size()) {
break;
}
cursor_char_index -= static_cast<int>(line.size() + 1);
cursor_line++;
}
const int cursor_column =
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);
const std::string empty_string;
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;
}
// Convert back the new_cursor_{line,column} toward cursor_position:
cursor_position() = 0;
for (int i = 0; i < new_cursor_line; ++i) {
cursor_position() += static_cast<int>(lines[i].size() + 1);
}
while (new_cursor_column > 0) {
new_cursor_column -=
static_cast<int>(GlyphWidth(content(), cursor_position()));
cursor_position() =
static_cast<int>(GlyphNext(content(), cursor_position()));
}
on_change();
return true;
}
bool HandleInsert() {
insert() = !insert();
return true;
}
bool Focusable() const final { return true; }
bool hovered_ = false;
Box box_;
Box cursor_box_;
};
} // namespace
/// @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));
}
/// @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 = &placeholder,
/// .password = true,
/// })
/// screen.Loop(input);
/// ```
///
/// ### Output
///
/// ```bash
/// placeholder
/// ```
Component Input(StringRef content, InputOption option) {
option.content = std::move(content);
return Make<InputBase>(std::move(option));
}
/// @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) {
option.content = std::move(content);
option.placeholder = std::move(placeholder);
return Make<InputBase>(std::move(option));
}
} // namespace ftxui
box.hpp
ftxui::ComponentBase
It implement rendering itself as ftxui::Element. It implement keyboard navigation by responding to ft...
Definition component_base.hpp:30
ftxui::StringRef
An adapter. Own or reference a constant string. For convenience, this class convert multiple mutable ...
Definition ref.hpp:82
component.hpp
component_base.hpp
component_options.hpp
elements.hpp
event.hpp
mouse.hpp
ftxui::util::clamp
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:11
ftxui
Definition animation.hpp:10
ftxui::GlyphNext
size_t GlyphNext(const std::string &input, size_t start)
Definition string.cpp:1424
ftxui::focusCursorBarBlinking
Element focusCursorBarBlinking(Element)
Same as focus, but set the cursor shape to be a blinking bar.
Definition frame.cpp:189
ftxui::xflex
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition flex.cpp:129
ftxui::CodepointToWordBreakProperty
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Definition string.cpp:1307
ftxui::nothing
Element nothing(Element element)
A decoration doing absolutely nothing.
Definition util.cpp:28
ftxui::size
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Definition size.cpp:89
ftxui::Make
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
ftxui::Element
std::shared_ptr< Node > Element
Definition elements.hpp:22
ftxui::string_width
int string_width(const std::string &)
Definition string.cpp:1330
ftxui::hbox
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition hbox.cpp:94
ftxui::WordBreakProperty::CR
@ CR
Definition string_internal.hpp:42
ftxui::WordBreakProperty::Double_Quote
@ Double_Quote
Definition string_internal.hpp:43
ftxui::WordBreakProperty::Hebrew_Letter
@ Hebrew_Letter
Definition string_internal.hpp:47
ftxui::WordBreakProperty::Extend
@ Extend
Definition string_internal.hpp:44
ftxui::WordBreakProperty::WSegSpace
@ WSegSpace
Definition string_internal.hpp:57
ftxui::WordBreakProperty::MidNumLet
@ MidNumLet
Definition string_internal.hpp:52
ftxui::WordBreakProperty::Format
@ Format
Definition string_internal.hpp:46
ftxui::WordBreakProperty::LF
@ LF
Definition string_internal.hpp:49
ftxui::WordBreakProperty::Newline
@ Newline
Definition string_internal.hpp:53
ftxui::WordBreakProperty::Regional_Indicator
@ Regional_Indicator
Definition string_internal.hpp:55
ftxui::WordBreakProperty::Numeric
@ Numeric
Definition string_internal.hpp:54
ftxui::WordBreakProperty::MidNum
@ MidNum
Definition string_internal.hpp:51
ftxui::WordBreakProperty::ALetter
@ ALetter
Definition string_internal.hpp:41
ftxui::WordBreakProperty::MidLetter
@ MidLetter
Definition string_internal.hpp:50
ftxui::WordBreakProperty::Single_Quote
@ Single_Quote
Definition string_internal.hpp:56
ftxui::WordBreakProperty::ZWJ
@ ZWJ
Definition string_internal.hpp:58
ftxui::WordBreakProperty::ExtendNumLet
@ ExtendNumLet
Definition string_internal.hpp:45
ftxui::WordBreakProperty::Katakana
@ Katakana
Definition string_internal.hpp:48
ftxui::Elements
std::vector< Element > Elements
Definition elements.hpp:23
ftxui::text
Element text(std::wstring text)
Display a piece of unicode text.
Definition text.cpp:160
ftxui::Input
Component Input(InputOption options={})
An input box for editing text.
Definition input.cpp:571
ftxui::EatCodePoint
bool EatCodePoint(const std::string &input, size_t start, size_t *end, uint32_t *ucs)
Definition string.cpp:1174
ftxui::focus
Element focus(Element)
Set the child to be the one focused among its siblings.
Definition frame.cpp:101
ftxui::reflect
Decorator reflect(Box &box)
Definition reflect.cpp:43
ftxui::IsFullWidth
bool IsFullWidth(uint32_t ucs)
Definition string.cpp:1286
ftxui::frame
Element frame(Element)
Allow an element to be displayed inside a 'virtual' area. It size can be larger than its container....
Definition frame.cpp:118
ftxui::focusCursorBlockBlinking
Element focusCursorBlockBlinking(Element)
Same as focus, but set the cursor shape to be a blinking block.
Definition frame.cpp:161
ftxui::GlyphPrevious
size_t GlyphPrevious(const std::string &input, size_t start)
Definition string.cpp:1399
ftxui::Component
std::shared_ptr< ComponentBase > Component
Definition component_base.hpp:24
ftxui::vbox
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition vbox.cpp:96
ref.hpp
screen_interactive.hpp
string.hpp
string_internal.hpp
ftxui::Event::ArrowLeftCtrl
static const Event ArrowLeftCtrl
Definition event.hpp:43
ftxui::Event::Backspace
static const Event Backspace
Definition event.hpp:49
ftxui::Event::ArrowUp
static const Event ArrowUp
Definition event.hpp:40
ftxui::Event::ArrowDown
static const Event ArrowDown
Definition event.hpp:41
ftxui::Event::End
static const Event End
Definition event.hpp:59
ftxui::Event::Home
static const Event Home
Definition event.hpp:58
ftxui::Event::Return
static const Event Return
Definition event.hpp:51
ftxui::Event::ArrowLeft
static const Event ArrowLeft
Definition event.hpp:38
ftxui::Event::Delete
static const Event Delete
Definition event.hpp:50
ftxui::Event::Insert
static const Event Insert
Definition event.hpp:57
ftxui::Event::ArrowRightCtrl
static const Event ArrowRightCtrl
Definition event.hpp:44
ftxui::Event::ArrowRight
static const Event ArrowRight
Definition event.hpp:39
ftxui::InputOption
Option for the Input component.
Definition component_options.hpp:162
ftxui::InputOption::Default
static InputOption Default()
Create the default input style:
Definition component_options.cpp:312
ftxui::InputOption::transform
std::function< Element(InputState)> transform
Definition component_options.hpp:177
ftxui::InputOption::placeholder
StringRef placeholder
The content of the input when it's empty.
Definition component_options.hpp:174
ftxui::InputOption::content
StringRef content
The content of the input.
Definition component_options.hpp:171
ftxui::Mouse::Left
@ Left
Definition mouse.hpp:13
ftxui::Mouse::Pressed
@ Pressed
Definition mouse.hpp:25
util.hpp