FTXUI  5.0.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
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
16namespace ftxui {
17
18class 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
97class VerticalContainer : public ContainerBase {
98 public:
99 using ContainerBase::ContainerBase;
100
101 Element Render() override {
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
180class HorizontalContainer : public ContainerBase {
181 public:
182 using ContainerBase::ContainerBase;
183
184 Element Render() override {
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
216class 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
240class StackedContainer : public ContainerBase {
241 public:
242 explicit StackedContainer(Components children)
243 : ContainerBase(std::move(children), nullptr) {}
244
245 private:
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
298namespace 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/// ```
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/// ```
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/// ```
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...
bool Focused() const
Returns if the elements if focused by the user. True when the ComponentBase is focused by the user....
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.
virtual bool OnEvent(Event)
Called in response to an event.
Component Horizontal(Components children)
A list of components, drawn one by one horizontally and navigated horizontally using left/right arrow...
Component Vertical(Components children)
A list of components, drawn one by one vertically and navigated vertically using up/down arrow key or...
Component Stacked(Components children)
A list of components to be stacked on top of each other. Events are propagated to the first component...
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...
std::shared_ptr< Node > Element
Definition elements.hpp:22
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
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 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