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