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