FTXUI  5.0.0
C++ functional terminal UI.
container.cpp
Go to the documentation of this file.
1 // Copyright 2020 Arthur Sonzogni. All rights reserved.
2 // Use of this source code is governed by the MIT license that can be found in
3 // the LICENSE file.
4 #include <algorithm> // for max, min
5 #include <cstddef> // for size_t
6 #include <memory> // for make_shared, __shared_ptr_access, allocator, shared_ptr, allocator_traits<>::value_type
7 #include <utility> // for move
8 
9 #include "ftxui/component/component.hpp" // for Horizontal, Vertical, Tab
10 #include "ftxui/component/component_base.hpp" // for Components, Component, ComponentBase
11 #include "ftxui/component/event.hpp" // for Event, Event::Tab, Event::TabReverse, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp
12 #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp
13 #include "ftxui/dom/elements.hpp" // for text, Elements, operator|, reflect, Element, hbox, vbox
14 #include "ftxui/screen/box.hpp" // for Box
15 
16 namespace ftxui {
17 
18 class ContainerBase : public ComponentBase {
19  public:
20  ContainerBase(Components children, int* selector)
21  : selector_(selector ? selector : &selected_) {
22  for (Component& child : children) {
23  Add(std::move(child));
24  }
25  }
26 
27  // Component override.
28  bool OnEvent(Event event) override {
29  if (event.is_mouse()) {
30  return OnMouseEvent(event);
31  }
32 
33  if (!Focused()) {
34  return false;
35  }
36 
37  if (ActiveChild() && ActiveChild()->OnEvent(event)) {
38  return true;
39  }
40 
41  return EventHandler(event);
42  }
43 
44  Component ActiveChild() override {
45  if (children_.empty()) {
46  return nullptr;
47  }
48 
49  return children_[static_cast<size_t>(*selector_) % children_.size()];
50  }
51 
52  void SetActiveChild(ComponentBase* child) override {
53  for (size_t i = 0; i < children_.size(); ++i) {
54  if (children_[i].get() == child) {
55  *selector_ = static_cast<int>(i);
56  return;
57  }
58  }
59  }
60 
61  protected:
62  // Handlers
63  virtual bool EventHandler(Event /*unused*/) { return false; } // NOLINT
64 
65  virtual bool OnMouseEvent(Event event) {
66  return ComponentBase::OnEvent(std::move(event));
67  }
68 
69  int selected_ = 0;
70  int* selector_ = nullptr;
71 
72  void MoveSelector(int dir) {
73  for (int i = *selector_ + dir; i >= 0 && i < int(children_.size());
74  i += dir) {
75  if (children_[i]->Focusable()) {
76  *selector_ = i;
77  return;
78  }
79  }
80  }
81 
82  void MoveSelectorWrap(int dir) {
83  if (children_.empty()) {
84  return;
85  }
86  for (size_t offset = 1; offset < children_.size(); ++offset) {
87  const size_t i =
88  (*selector_ + offset * dir + children_.size()) % children_.size();
89  if (children_[i]->Focusable()) {
90  *selector_ = int(i);
91  return;
92  }
93  }
94  }
95 };
96 
97 class VerticalContainer : public ContainerBase {
98  public:
99  using ContainerBase::ContainerBase;
100 
101  Element Render() override {
102  Elements elements;
103  elements.reserve(children_.size());
104  for (auto& it : children_) {
105  elements.push_back(it->Render());
106  }
107  if (elements.empty()) {
108  return text("Empty container") | reflect(box_);
109  }
110  return vbox(std::move(elements)) | reflect(box_);
111  }
112 
113  bool EventHandler(Event event) override {
114  const int old_selected = *selector_;
115  if (event == Event::ArrowUp || event == Event::Character('k')) {
116  MoveSelector(-1);
117  }
118  if (event == Event::ArrowDown || event == Event::Character('j')) {
119  MoveSelector(+1);
120  }
121  if (event == Event::PageUp) {
122  for (int i = 0; i < box_.y_max - box_.y_min; ++i) {
123  MoveSelector(-1);
124  }
125  }
126  if (event == Event::PageDown) {
127  for (int i = 0; i < box_.y_max - box_.y_min; ++i) {
128  MoveSelector(1);
129  }
130  }
131  if (event == Event::Home) {
132  for (size_t i = 0; i < children_.size(); ++i) {
133  MoveSelector(-1);
134  }
135  }
136  if (event == Event::End) {
137  for (size_t i = 0; i < children_.size(); ++i) {
138  MoveSelector(1);
139  }
140  }
141  if (event == Event::Tab) {
142  MoveSelectorWrap(+1);
143  }
144  if (event == Event::TabReverse) {
145  MoveSelectorWrap(-1);
146  }
147 
148  *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
149  return old_selected != *selector_;
150  }
151 
152  bool OnMouseEvent(Event event) override {
153  if (ContainerBase::OnMouseEvent(event)) {
154  return true;
155  }
156 
157  if (event.mouse().button != Mouse::WheelUp &&
158  event.mouse().button != Mouse::WheelDown) {
159  return false;
160  }
161 
162  if (!box_.Contain(event.mouse().x, event.mouse().y)) {
163  return false;
164  }
165 
166  if (event.mouse().button == Mouse::WheelUp) {
167  MoveSelector(-1);
168  }
169  if (event.mouse().button == Mouse::WheelDown) {
170  MoveSelector(+1);
171  }
172  *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
173 
174  return true;
175  }
176 
177  Box box_;
178 };
179 
180 class HorizontalContainer : public ContainerBase {
181  public:
182  using ContainerBase::ContainerBase;
183 
184  Element Render() override {
185  Elements elements;
186  elements.reserve(children_.size());
187  for (auto& it : children_) {
188  elements.push_back(it->Render());
189  }
190  if (elements.empty()) {
191  return text("Empty container");
192  }
193  return hbox(std::move(elements));
194  }
195 
196  bool EventHandler(Event event) override {
197  const int old_selected = *selector_;
198  if (event == Event::ArrowLeft || event == Event::Character('h')) {
199  MoveSelector(-1);
200  }
201  if (event == Event::ArrowRight || event == Event::Character('l')) {
202  MoveSelector(+1);
203  }
204  if (event == Event::Tab) {
205  MoveSelectorWrap(+1);
206  }
207  if (event == Event::TabReverse) {
208  MoveSelectorWrap(-1);
209  }
210 
211  *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_));
212  return old_selected != *selector_;
213  }
214 };
215 
216 class TabContainer : public ContainerBase {
217  public:
218  using ContainerBase::ContainerBase;
219 
220  Element Render() override {
221  const Component active_child = ActiveChild();
222  if (active_child) {
223  return active_child->Render();
224  }
225  return text("Empty container");
226  }
227 
228  bool Focusable() const override {
229  if (children_.empty()) {
230  return false;
231  }
232  return children_[size_t(*selector_) % children_.size()]->Focusable();
233  }
234 
235  bool OnMouseEvent(Event event) override {
236  return ActiveChild() && ActiveChild()->OnEvent(event);
237  }
238 };
239 
240 class StackedContainer : public ContainerBase {
241  public:
242  explicit StackedContainer(Components children)
243  : ContainerBase(std::move(children), nullptr) {}
244 
245  private:
246  Element Render() final {
247  Elements elements;
248  for (auto& child : children_) {
249  elements.push_back(child->Render());
250  }
251  // Reverse the order of the elements.
252  std::reverse(elements.begin(), elements.end());
253  return dbox(std::move(elements));
254  }
255 
256  bool Focusable() const final {
257  for (const auto& child : children_) {
258  if (child->Focusable()) {
259  return true;
260  }
261  }
262  return false;
263  }
264 
265  Component ActiveChild() final {
266  if (children_.empty()) {
267  return nullptr;
268  }
269  return children_[0];
270  }
271 
272  void SetActiveChild(ComponentBase* child) final {
273  if (children_.empty()) {
274  return;
275  }
276 
277  // Find `child` and put it at the beginning without change the order of the
278  // other children.
279  auto it =
280  std::find_if(children_.begin(), children_.end(),
281  [child](const Component& c) { return c.get() == child; });
282  if (it == children_.end()) {
283  return;
284  }
285  std::rotate(children_.begin(), it, it + 1);
286  }
287 
288  bool OnEvent(Event event) final {
289  for (auto& child : children_) {
290  if (child->OnEvent(event)) {
291  return true;
292  }
293  }
294  return false;
295  }
296 };
297 
298 namespace Container {
299 
300 /// @brief A list of components, drawn one by one vertically and navigated
301 /// vertically using up/down arrow key or 'j'/'k' keys.
302 /// @param children the list of components.
303 /// @ingroup component
304 /// @see ContainerBase
305 ///
306 /// ### Example
307 ///
308 /// ```cpp
309 /// auto container = Container::Vertical({
310 /// children_1,
311 /// children_2,
312 /// children_3,
313 /// children_4,
314 /// });
315 /// ```
317  return Vertical(std::move(children), nullptr);
318 }
319 
320 /// @brief A list of components, drawn one by one vertically and navigated
321 /// vertically using up/down arrow key or 'j'/'k' keys.
322 /// This is useful for implementing a Menu for instance.
323 /// @param children the list of components.
324 /// @param selector A reference to the index of the selected children.
325 /// @ingroup component
326 /// @see ContainerBase
327 ///
328 /// ### Example
329 ///
330 /// ```cpp
331 /// auto container = Container::Vertical({
332 /// children_1,
333 /// children_2,
334 /// children_3,
335 /// children_4,
336 /// });
337 /// ```
338 Component Vertical(Components children, int* selector) {
339  return std::make_shared<VerticalContainer>(std::move(children), selector);
340 }
341 
342 /// @brief A list of components, drawn one by one horizontally and navigated
343 /// horizontally using left/right arrow key or 'h'/'l' keys.
344 /// @param children the list of components.
345 /// @ingroup component
346 /// @see ContainerBase
347 ///
348 /// ### Example
349 ///
350 /// ```cpp
351 /// int selected_children = 2;
352 /// auto container = Container::Horizontal({
353 /// children_1,
354 /// children_2,
355 /// children_3,
356 /// children_4,
357 /// }, &selected_children);
358 /// ```
360  return Horizontal(std::move(children), nullptr);
361 }
362 
363 /// @brief A list of components, drawn one by one horizontally and navigated
364 /// horizontally using left/right arrow key or 'h'/'l' keys.
365 /// @param children the list of components.
366 /// @param selector A reference to the index of the selected children.
367 /// @ingroup component
368 /// @see ContainerBase
369 ///
370 /// ### Example
371 ///
372 /// ```cpp
373 /// int selected_children = 2;
374 /// auto container = Container::Horizontal({
375 /// children_1,
376 /// children_2,
377 /// children_3,
378 /// children_4,
379 /// }, selected_children);
380 /// ```
381 Component Horizontal(Components children, int* selector) {
382  return std::make_shared<HorizontalContainer>(std::move(children), selector);
383 }
384 
385 /// @brief A list of components, where only one is drawn and interacted with at
386 /// a time. The |selector| gives the index of the selected component. This is
387 /// useful to implement tabs.
388 /// @param children The list of components.
389 /// @param selector The index of the drawn children.
390 /// @ingroup component
391 /// @see ContainerBase
392 ///
393 /// ### Example
394 ///
395 /// ```cpp
396 /// int tab_drawn = 0;
397 /// auto container = Container::Tab({
398 /// children_1,
399 /// children_2,
400 /// children_3,
401 /// children_4,
402 /// }, &tab_drawn);
403 /// ```
404 Component Tab(Components children, int* selector) {
405  return std::make_shared<TabContainer>(std::move(children), selector);
406 }
407 
408 /// @brief A list of components to be stacked on top of each other.
409 /// Events are propagated to the first component, then the second if not
410 /// handled, etc.
411 /// The components are drawn in the reverse order they are given.
412 /// When a component take focus, it is put at the front, without changing the
413 /// relative order of the other elements.
414 ///
415 /// This should be used with the `Window` component.
416 ///
417 /// @param children The list of components.
418 /// @ingroup component
419 /// @see Window
420 ///
421 /// ### Example
422 ///
423 /// ```cpp
424 /// auto container = Container::Stacked({
425 /// children_1,
426 /// children_2,
427 /// children_3,
428 /// children_4,
429 /// });
430 /// ```
432  return std::make_shared<StackedContainer>(std::move(children));
433 }
434 
435 } // namespace Container
436 
437 } // namespace ftxui
virtual bool Focusable() const
Return true when the component contains focusable elements. The non focusable Components will be skip...
Definition: component.cpp:158
bool Focused() const
Returns if the elements if focused by the user. True when the ComponentBase is focused by the user....
Definition: component.cpp:178
void Add(Component children)
Add a child. @param child The child to be attached.
Definition: component.cpp:73
void SetActiveChild(Component child)
Make the |child| to be the "active" one.
Definition: component.cpp:194
virtual bool OnEvent(Event)
Called in response to an event.
Definition: component.cpp:123
Component Horizontal(Components children)
A list of components, drawn one by one horizontally and navigated horizontally using left/right arrow...
Definition: container.cpp:359
Component Vertical(Components children)
A list of components, drawn one by one vertically and navigated vertically using up/down arrow key or...
Definition: container.cpp:316
Component Stacked(Components children)
A list of components to be stacked on top of each other. Events are propagated to the first component...
Definition: container.cpp:431
Component Tab(Components children, int *selector)
A list of components, where only one is drawn and interacted with at a time. The |selector| gives the...
Definition: container.cpp:404
std::shared_ptr< Node > Element
Definition: elements.hpp:22
std::shared_ptr< ComponentBase > Component
std::vector< Component > Components
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition: hbox.cpp:83
Element text(std::wstring text)
Display a piece of unicode text.
Definition: text.cpp:119
std::vector< Element > Elements
Definition: elements.hpp:23
Element dbox(Elements)
Stack several element on top of each other.
Definition: dbox.cpp:111
Decorator reflect(Box &box)
Definition: reflect.cpp:43
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition: node.cpp:47
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition: vbox.cpp:83
static const Event TabReverse
Definition: event.hpp:54
static const Event PageUp
Definition: event.hpp:60
static Event Character(std::string)
An event corresponding to a given typed character.
Definition: event.cpp:29
static const Event ArrowUp
Definition: event.hpp:40
static const Event Tab
Definition: event.hpp:53
static const Event ArrowDown
Definition: event.hpp:41
static const Event End
Definition: event.hpp:59
static const Event Home
Definition: event.hpp:58
static const Event PageDown
Definition: event.hpp:61
static const Event ArrowLeft
Definition: event.hpp:38
static const Event ArrowRight
Definition: event.hpp:39