FTXUI  4.1.1
C++ functional terminal UI.
Loading...
Searching...
No Matches
input.cpp
Go to the documentation of this file.
1#include <algorithm> // for max, min
2#include <cstddef> // for size_t
3#include <functional> // for function
4#include <memory> // for shared_ptr
5#include <string> // for string, allocator
6#include <utility> // for move
7#include <vector> // for vector
8
9#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
10#include "ftxui/component/component.hpp" // for Make, Input
11#include "ftxui/component/component_base.hpp" // for ComponentBase
12#include "ftxui/component/component_options.hpp" // for InputOption
13#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::Backspace, Event::Custom, Event::Delete, Event::End, Event::Home, Event::Return
14#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
15#include "ftxui/component/screen_interactive.hpp" // for Component
16#include "ftxui/dom/elements.hpp" // for operator|, text, Element, reflect, operator|=, flex, inverted, hbox, size, bold, dim, focus, focusCursorBarBlinking, frame, select, Decorator, EQUAL, HEIGHT
17#include "ftxui/screen/box.hpp" // for Box
18#include "ftxui/screen/string.hpp" // for GlyphPosition, WordBreakProperty, GlyphCount, Utf8ToWordBreakProperty, CellToGlyphIndex, 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
19#include "ftxui/screen/util.hpp" // for clamp
20#include "ftxui/util/ref.hpp" // for StringRef, Ref, ConstStringRef
21
22namespace ftxui {
23
24namespace {
25
26// Group together several propertiej so they appear to form a similar group.
27// For instance, letters are grouped with number and form a single word.
28bool IsWordCharacter(WordBreakProperty property) {
29 switch (property) {
34 return true;
35
45 // Unsure:
51 return false;
52 }
53 return true; // NOT_REACHED();
54}
55
56std::string PasswordField(size_t size) {
57 std::string out;
58 out.reserve(2 * size);
59 while (size--) {
60 out += "•";
61 }
62 return out;
63}
64
65// An input box. The user can type text into it.
66class InputBase : public ComponentBase {
67 public:
68 InputBase(StringRef content,
69 ConstStringRef placeholder,
70 Ref<InputOption> option)
71 : content_(std::move(content)),
72 placeholder_(std::move(placeholder)),
73 option_(std::move(option)) {}
74
75 int cursor_position_internal_ = 0;
76 int& cursor_position() {
77 int& opt = option_->cursor_position();
78 if (opt != -1) {
79 return opt;
80 }
81 return cursor_position_internal_;
82 }
83
84 // Component implementation:
85 Element Render() override {
86 std::string password_content;
87 if (option_->password()) {
88 password_content = PasswordField(content_->size());
89 }
90 const std::string& content =
91 option_->password() ? password_content : *content_;
92
93 const int size = GlyphCount(content);
94
95 cursor_position() = std::max(0, std::min<int>(size, cursor_position()));
96 auto main_decorator = flex | ftxui::size(HEIGHT, EQUAL, 1);
97 const bool is_focused = Focused();
98
99 // placeholder.
100 if (size == 0) {
101 auto element = text(*placeholder_) | dim | main_decorator | reflect(box_);
102 if (is_focused) {
103 element |= focus;
104 }
105 if (hovered_ || is_focused) {
106 element |= inverted;
107 }
108 return element;
109 }
110
111 // Not focused.
112 if (!is_focused) {
113 auto element = text(content) | main_decorator | reflect(box_);
114 if (hovered_) {
115 element |= inverted;
116 }
117 return element;
118 }
119
120 const int index_before_cursor = GlyphPosition(content, cursor_position());
121 const int index_after_cursor =
122 GlyphPosition(content, 1, index_before_cursor);
123 const std::string part_before_cursor =
124 content.substr(0, index_before_cursor);
125 std::string part_at_cursor = " ";
126 if (cursor_position() < size) {
127 part_at_cursor = content.substr(index_before_cursor,
128 index_after_cursor - index_before_cursor);
129 }
130 const std::string part_after_cursor = content.substr(index_after_cursor);
131 auto focused = (is_focused || hovered_) ? focusCursorBarBlinking : select;
132 return hbox({
133 text(part_before_cursor),
134 text(part_at_cursor) | focused | reflect(cursor_box_),
135 text(part_after_cursor),
136 }) |
137 flex | frame | bold | main_decorator | reflect(box_);
138 }
139
140 bool OnEvent(Event event) override {
141 cursor_position() =
142 std::max(0, std::min<int>((int)content_->size(), cursor_position()));
143
144 if (event.is_mouse()) {
145 return OnMouseEvent(event);
146 }
147
148 // Backspace.
149 if (event == Event::Backspace) {
150 if (cursor_position() == 0) {
151 return false;
152 }
153 const size_t start = GlyphPosition(*content_, cursor_position() - 1);
154 const size_t end = GlyphPosition(*content_, cursor_position());
155 content_->erase(start, end - start);
156 cursor_position()--;
157 option_->on_change();
158 return true;
159 }
160
161 // Delete
162 if (event == Event::Delete) {
163 if (cursor_position() == int(content_->size())) {
164 return false;
165 }
166 const size_t start = GlyphPosition(*content_, cursor_position());
167 const size_t end = GlyphPosition(*content_, cursor_position() + 1);
168 content_->erase(start, end - start);
169 option_->on_change();
170 return true;
171 }
172
173 // Enter.
174 if (event == Event::Return) {
175 option_->on_enter();
176 return true;
177 }
178
179 if (event == Event::Custom) {
180 return false;
181 }
182
183 // Arrow
184 if (event == Event::ArrowLeft && cursor_position() > 0) {
185 cursor_position()--;
186 return true;
187 }
188
189 if (event == Event::ArrowRight &&
190 cursor_position() < (int)content_->size()) {
191 cursor_position()++;
192 return true;
193 }
194
195 // CTRL + Arrow:
196 if (event == Event::ArrowLeftCtrl) {
197 HandleLeftCtrl();
198 return true;
199 }
200 if (event == Event::ArrowRightCtrl) {
201 HandleRightCtrl();
202 return true;
203 }
204
205 if (event == Event::Home) {
206 cursor_position() = 0;
207 return true;
208 }
209
210 if (event == Event::End) {
211 cursor_position() = GlyphCount(*content_);
212 return true;
213 }
214
215 // Content
216 if (event.is_character()) {
217 const size_t start = GlyphPosition(*content_, cursor_position());
218 content_->insert(start, event.character());
219 cursor_position()++;
220 option_->on_change();
221 return true;
222 }
223 return false;
224 }
225
226 private:
227 void HandleLeftCtrl() {
228 auto properties = Utf8ToWordBreakProperty(*content_);
229
230 // Move left, as long as left is not a word character.
231 while (cursor_position() > 0 &&
232 !IsWordCharacter(properties[cursor_position() - 1])) {
233 cursor_position()--;
234 }
235
236 // Move left, as long as left is a word character:
237 while (cursor_position() > 0 &&
238 IsWordCharacter(properties[cursor_position() - 1])) {
239 cursor_position()--;
240 }
241 }
242
243 void HandleRightCtrl() {
244 auto properties = Utf8ToWordBreakProperty(*content_);
245 const int max = (int)properties.size();
246
247 // Move right, as long as right is not a word character.
248 while (cursor_position() < max &&
249 !IsWordCharacter(properties[cursor_position()])) {
250 cursor_position()++;
251 }
252
253 // Move right, as long as right is a word character:
254 while (cursor_position() < max &&
255 IsWordCharacter(properties[cursor_position()])) {
256 cursor_position()++;
257 }
258 }
259
260 bool OnMouseEvent(Event event) {
261 hovered_ =
262 box_.Contain(event.mouse().x, event.mouse().y) && CaptureMouse(event);
263 if (!hovered_) {
264 return false;
265 }
266
267 if (event.mouse().button != Mouse::Left ||
268 event.mouse().motion != Mouse::Pressed) {
269 return false;
270 }
271
272 TakeFocus();
273 if (content_->empty()) {
274 return true;
275 }
276
277 auto mapping = CellToGlyphIndex(*content_);
278 int original_glyph = cursor_position();
279 original_glyph = util::clamp(original_glyph, 0, int(mapping.size()));
280 size_t original_cell = 0;
281 for (size_t i = 0; i < mapping.size(); i++) {
282 if (mapping[i] == original_glyph) {
283 original_cell = (int)i;
284 break;
285 }
286 }
287 if (mapping[original_cell] != original_glyph) {
288 original_cell = mapping.size();
289 }
290 const int target_cell =
291 int(original_cell) + event.mouse().x - cursor_box_.x_min;
292 int target_glyph = target_cell < (int)mapping.size() ? mapping[target_cell]
293 : (int)mapping.size();
294 target_glyph = util::clamp(target_glyph, 0, GlyphCount(*content_));
295 if (cursor_position() != target_glyph) {
296 cursor_position() = target_glyph;
297 option_->on_change();
298 }
299 return true;
300 }
301
302 bool Focusable() const final { return true; }
303
304 bool hovered_ = false;
305 StringRef content_;
306 ConstStringRef placeholder_;
307
308 Box box_;
309 Box cursor_box_;
310 Ref<InputOption> option_;
311};
312
313} // namespace
314
315/// @brief An input box for editing text.
316/// @param content The editable content.
317/// @param placeholder The text displayed when content is still empty.
318/// @param option Additional optional parameters.
319/// @ingroup component
320/// @see InputBase
321///
322/// ### Example
323///
324/// ```cpp
325/// auto screen = ScreenInteractive::FitComponent();
326/// std::string content= "";
327/// std::string placeholder = "placeholder";
328/// Component input = Input(&content, &placeholder);
329/// screen.Loop(input);
330/// ```
331///
332/// ### Output
333///
334/// ```bash
335/// placeholder
336/// ```
338 ConstStringRef placeholder,
339 Ref<InputOption> option) {
340 return Make<InputBase>(std::move(content), std::move(placeholder),
341 std::move(option));
342}
343
344} // namespace ftxui
345
346// Copyright 2020 Arthur Sonzogni. All rights reserved.
347// Use of this source code is governed by the MIT license that can be found in
348// the LICENSE file.
An adapter. Own or reference a constant string. For convenience, this class convert multiple immutabl...
Definition ref.hpp:60
An adapter. Own or reference an mutable object.
Definition ref.hpp:27
An adapter. Own or reference a constant string. For convenience, this class convert multiple mutable ...
Definition ref.hpp:44
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:6
Element flex(Element)
Make a child element to expand proportionnally to the space left in a container.
Definition flex.cpp:120
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:25
std::shared_ptr< Node > Element
Definition elements.hpp:19
Component Input(StringRef content, ConstStringRef placeholder, Ref< InputOption > option={})
An input box for editing text.
Definition input.cpp:337
Element bold(Element)
Use a bold font, for elements with more emphasis.
Definition bold.cpp:28
Element focus(Element)
Definition frame.cpp:83
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition hbox.cpp:77
Element inverted(Element)
Add a filter that will invert the foreground and the background colors.
Definition inverted.cpp:29
Element text(std::wstring text)
Display a piece of unicode text.
Definition text.cpp:111
int GlyphPosition(const std::string &input, size_t glyph_index, size_t start=0)
Definition string.cpp:1770
std::vector< int > CellToGlyphIndex(const std::string &input)
Definition string.cpp:1798
int GlyphCount(const std::string &input)
Definition string.cpp:1839
Decorator reflect(Box &box)
Definition reflect.cpp:39
WordBreakProperty
Definition string.hpp:31
Element dim(Element)
Use a light font, for elements with less emphasis.
Definition dim.cpp:28
Element frame(Element)
Allow an element to be displayed inside a 'virtual' area. It size can be larger than its container....
Definition frame.cpp:142
std::vector< WordBreakProperty > Utf8ToWordBreakProperty(const std::string &input)
Definition string.cpp:1867
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:44
Decorator size(Direction, Constraint, int value)
Apply a constraint on the size of an element.
Definition size.cpp:85
Element select(Element)
Definition frame.cpp:38
std::shared_ptr< ComponentBase > Component
Element focusCursorBarBlinking(Element)
Definition frame.cpp:183
static const Event ArrowLeftCtrl
Definition event.hpp:41
static const Event Custom
Definition event.hpp:62
static const Event Backspace
Definition event.hpp:47
static const Event End
Definition event.hpp:56
static const Event Home
Definition event.hpp:55
static const Event Return
Definition event.hpp:49
static const Event ArrowLeft
Definition event.hpp:36
static const Event Delete
Definition event.hpp:48
static const Event ArrowRightCtrl
Definition event.hpp:42
static const Event ArrowRight
Definition event.hpp:37