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
17namespace ftxui {
18
19class 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
98class 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
181class 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
217class 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
241class 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
299namespace 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/// ```
339Component 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/// ```
382Component 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/// ```
405Component 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:108
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