FTXUI 6.1.9
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 OnRender() 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 const int old_selected = *selector_;
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 old_selected != *selector_;
176 }
177
178 Box box_;
179};
180
181class HorizontalContainer : public ContainerBase {
182 public:
183 using ContainerBase::ContainerBase;
184
185 Element OnRender() 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 OnRender() 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 OnRender() 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()); // NOLINT
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(), // NOLINT
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/// int selected_children = 2;
333/// auto container = Container::Vertical({
334/// children_1,
335/// children_2,
336/// children_3,
337/// children_4,
338/// }, &selected_children);
339/// ```
341 return std::make_shared<VerticalContainer>(std::move(children), selector);
342}
343
344/// @brief A list of components, drawn one by one horizontally and navigated
345/// horizontally using left/right arrow key or 'h'/'l' keys.
346/// @param children the list of components.
347/// @ingroup component
348/// @see ContainerBase
349///
350/// ### Example
351///
352/// ```cpp
353/// auto container = Container::Horizontal({
354/// children_1,
355/// children_2,
356/// children_3,
357/// children_4,
358/// });
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/// ```
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/// ```
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
Component Horizontal(Components children, int *selector)
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...
Element text(std::string_view text)
Display a piece of UTF8 encoded unicode text.
Decorator size(WidthOrHeight direction, Constraint constraint, int value)
Apply a constraint on the size of an element.
Element dbox(Elements children_)
Stack several element on top of each other.
Element vbox(Elements children)
A container displaying elements vertically one by one.
Definition vbox.cpp:96
The FTXUI ftxui::Container:: namespace.
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
std::shared_ptr< Node > Element
Definition elements.hpp:24
std::vector< Component > Components
Element hbox(Elements children)
A container displaying elements horizontally one by one.
Definition hbox.cpp:94
std::vector< Element > Elements
Definition elements.hpp:25
Decorator reflect(Box &box)
Definition reflect.cpp:43
std::shared_ptr< ComponentBase > Component
Definition app.hpp:23