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