FTXUI  4.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
menu.cpp
Go to the documentation of this file.
1#include <algorithm> // for max, fill_n, reverse
2#include <chrono> // for milliseconds
3#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
4#include <functional> // for function
5#include <memory> // for allocator_traits<>::value_type, swap
6#include <string> // for operator+, string
7#include <utility> // for move
8#include <vector> // for vector, __alloc_traits<>::value_type
9
10#include "ftxui/component/animation.hpp" // for Animator, Linear
11#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
12#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
13#include "ftxui/component/component_base.hpp" // for ComponentBase
14#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState
15#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowRight, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
16#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
17#include "ftxui/component/screen_interactive.hpp" // for Component
18#include "ftxui/dom/elements.hpp" // for operator|, Element, reflect, Decorator, nothing, Elements, bgcolor, color, hbox, separatorHSelector, separatorVSelector, vbox, xflex, yflex, text, bold, focus, inverted, select
19#include "ftxui/screen/box.hpp" // for Box
20#include "ftxui/screen/color.hpp" // for Color
21#include "ftxui/screen/util.hpp" // for clamp
22#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
23
24namespace ftxui {
25
26namespace {
27
28Element DefaultOptionTransform(const EntryState& state) {
29 std::string label = (state.active ? "> " : " ") + state.label; // NOLINT
30 Element e = text(label);
31 if (state.focused) {
32 e = e | inverted;
33 }
34 if (state.active) {
35 e = e | bold;
36 }
37 return e;
38}
39
40bool IsInverted(Direction direction) {
41 switch (direction) {
42 case Direction::Up:
43 case Direction::Left:
44 return true;
45 case Direction::Down:
47 return false;
48 }
49 return false; // NOT_REACHED()
50}
51
52bool IsHorizontal(Direction direction) {
53 switch (direction) {
54 case Direction::Left:
56 return true;
57 case Direction::Down:
58 case Direction::Up:
59 return false;
60 }
61 return false; // NOT_REACHED()
62}
63
64} // namespace
65
66/// @brief A list of items. The user can navigate through them.
67/// @ingroup component
68class MenuBase : public ComponentBase {
69 public:
70 MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
71 : entries_(entries), selected_(selected), option_(std::move(option)) {}
72
73 bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); }
74 void OnChange() {
75 if (option_->on_change) {
76 option_->on_change();
77 }
78 }
79
80 void OnEnter() {
81 if (option_->on_enter) {
82 option_->on_enter();
83 }
84 }
85
86 void Clamp() {
87 if (*selected_ != selected_previous_) {
88 SelectedTakeFocus();
89 }
90 boxes_.resize(size());
91 *selected_ = util::clamp(*selected_, 0, size() - 1);
92 selected_previous_ = util::clamp(selected_previous_, 0, size() - 1);
93 selected_focus_ = util::clamp(selected_focus_, 0, size() - 1);
94 focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
95 }
96
97 void OnAnimation(animation::Params& params) override {
98 animator_first_.OnAnimation(params);
99 animator_second_.OnAnimation(params);
100 for (auto& animator : animator_background_) {
101 animator.OnAnimation(params);
102 }
103 for (auto& animator : animator_foreground_) {
104 animator.OnAnimation(params);
105 }
106 }
107
108 Element Render() override {
109 Clamp();
110 UpdateAnimationTarget();
111
112 Elements elements;
113 const bool is_menu_focused = Focused();
114 if (option_->elements_prefix) {
115 elements.push_back(option_->elements_prefix());
116 }
117 for (int i = 0; i < size(); ++i) {
118 if (i != 0 && option_->elements_infix) {
119 elements.push_back(option_->elements_infix());
120 }
121 const bool is_focused = (focused_entry() == i) && is_menu_focused;
122 const bool is_selected = (*selected_ == i);
123
124 const EntryState state = {
125 entries_[i],
126 false,
127 is_selected,
128 is_focused,
129 };
130
131 auto focus_management =
132 is_menu_focused && (selected_focus_ == i) ? focus : nothing;
133
134 const Element element =
135 (option_->entries.transform ? option_->entries.transform
136 : DefaultOptionTransform) //
137 (state);
138 elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
139 focus_management);
140 }
141 if (option_->elements_postfix) {
142 elements.push_back(option_->elements_postfix());
143 }
144
145 if (IsInverted(option_->direction)) {
146 std::reverse(elements.begin(), elements.end());
147 }
148
149 const Element bar =
150 IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
151
152 if (!option_->underline.enabled) {
153 return bar | reflect(box_);
154 }
155
156 if (IsHorizontal()) {
157 return vbox({
158 bar | xflex,
159 separatorHSelector(first_, second_, //
160 option_->underline.color_active,
161 option_->underline.color_inactive),
162 }) |
163 reflect(box_);
164 } else {
165 return hbox({
166 separatorVSelector(first_, second_, //
167 option_->underline.color_active,
168 option_->underline.color_inactive),
169 bar | yflex,
170 }) |
171 reflect(box_);
172 }
173 }
174
175 void SelectedTakeFocus() {
176 selected_previous_ = *selected_;
177 selected_focus_ = *selected_;
178 }
179
180 void OnUp() {
181 switch (option_->direction) {
182 case Direction::Up:
183 (*selected_)++;
184 break;
185 case Direction::Down:
186 (*selected_)--;
187 break;
188 case Direction::Left:
189 case Direction::Right:
190 break;
191 }
192 }
193
194 void OnDown() {
195 switch (option_->direction) {
196 case Direction::Up:
197 (*selected_)--;
198 break;
199 case Direction::Down:
200 (*selected_)++;
201 break;
202 case Direction::Left:
203 case Direction::Right:
204 break;
205 }
206 }
207
208 void OnLeft() {
209 switch (option_->direction) {
210 case Direction::Left:
211 (*selected_)++;
212 break;
213 case Direction::Right:
214 (*selected_)--;
215 break;
216 case Direction::Down:
217 case Direction::Up:
218 break;
219 }
220 }
221
222 void OnRight() {
223 switch (option_->direction) {
224 case Direction::Left:
225 (*selected_)--;
226 break;
227 case Direction::Right:
228 (*selected_)++;
229 break;
230 case Direction::Down:
231 case Direction::Up:
232 break;
233 }
234 }
235
236 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
237 bool OnEvent(Event event) override {
238 Clamp();
239 if (!CaptureMouse(event)) {
240 return false;
241 }
242
243 if (event.is_mouse()) {
244 return OnMouseEvent(event);
245 }
246
247 if (Focused()) {
248 const int old_selected = *selected_;
249 if (event == Event::ArrowUp || event == Event::Character('k')) {
250 OnUp();
251 }
252 if (event == Event::ArrowDown || event == Event::Character('j')) {
253 OnDown();
254 }
255 if (event == Event::ArrowLeft || event == Event::Character('h')) {
256 OnLeft();
257 }
258 if (event == Event::ArrowRight || event == Event::Character('l')) {
259 OnRight();
260 }
261 if (event == Event::PageUp) {
262 (*selected_) -= box_.y_max - box_.y_min;
263 }
264 if (event == Event::PageDown) {
265 (*selected_) += box_.y_max - box_.y_min;
266 }
267 if (event == Event::Home) {
268 (*selected_) = 0;
269 }
270 if (event == Event::End) {
271 (*selected_) = size() - 1;
272 }
273 if (event == Event::Tab && size()) {
274 *selected_ = (*selected_ + 1) % size();
275 }
276 if (event == Event::TabReverse && size()) {
277 *selected_ = (*selected_ + size() - 1) % size();
278 }
279
280 *selected_ = util::clamp(*selected_, 0, size() - 1);
281
282 if (*selected_ != old_selected) {
283 focused_entry() = *selected_;
284 SelectedTakeFocus();
285 OnChange();
286 return true;
287 }
288 }
289
290 if (event == Event::Return) {
291 OnEnter();
292 return true;
293 }
294
295 return false;
296 }
297
298 bool OnMouseEvent(Event event) {
299 if (event.mouse().button == Mouse::WheelDown ||
300 event.mouse().button == Mouse::WheelUp) {
301 return OnMouseWheel(event);
302 }
303
304 if (event.mouse().button != Mouse::None &&
305 event.mouse().button != Mouse::Left) {
306 return false;
307 }
308 if (!CaptureMouse(event)) {
309 return false;
310 }
311 for (int i = 0; i < size(); ++i) {
312 if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
313 continue;
314 }
315
316 TakeFocus();
317 focused_entry() = i;
318 if (event.mouse().button == Mouse::Left &&
319 event.mouse().motion == Mouse::Released) {
320 if (*selected_ != i) {
321 *selected_ = i;
322 selected_previous_ = *selected_;
323 OnChange();
324 }
325 return true;
326 }
327 }
328 return false;
329 }
330
331 bool OnMouseWheel(Event event) {
332 if (!box_.Contain(event.mouse().x, event.mouse().y)) {
333 return false;
334 }
335 const int old_selected = *selected_;
336
337 if (event.mouse().button == Mouse::WheelUp) {
338 (*selected_)--;
339 }
340 if (event.mouse().button == Mouse::WheelDown) {
341 (*selected_)++;
342 }
343
344 *selected_ = util::clamp(*selected_, 0, size() - 1);
345
346 if (*selected_ != old_selected) {
347 SelectedTakeFocus();
348 OnChange();
349 }
350 return true;
351 }
352
353 void UpdateAnimationTarget() {
354 UpdateColorTarget();
355 UpdateUnderlineTarget();
356 }
357
358 void UpdateColorTarget() {
359 if (size() != (int)animation_background_.size()) {
360 animation_background_.resize(size());
361 animation_foreground_.resize(size());
362 animator_background_.clear();
363 animator_foreground_.clear();
364
365 for (int i = 0; i < size(); ++i) {
366 animation_background_[i] = 0.F;
367 animation_foreground_[i] = 0.F;
368 animator_background_.emplace_back(&animation_background_[i], 0.F,
369 std::chrono::milliseconds(0),
371 animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
372 std::chrono::milliseconds(0),
374 }
375 }
376
377 const bool is_menu_focused = Focused();
378 for (int i = 0; i < size(); ++i) {
379 const bool is_focused = (focused_entry() == i) && is_menu_focused;
380 const bool is_selected = (*selected_ == i);
381 float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT
382 if (animator_background_[i].to() != target) {
383 animator_background_[i] = animation::Animator(
384 &animation_background_[i], target,
385 option_->entries.animated_colors.background.duration,
386 option_->entries.animated_colors.background.function);
387 animator_foreground_[i] = animation::Animator(
388 &animation_foreground_[i], target,
389 option_->entries.animated_colors.foreground.duration,
390 option_->entries.animated_colors.foreground.function);
391 }
392 }
393 }
394
395 Decorator AnimatedColorStyle(int i) {
396 Decorator style = nothing;
397 if (option_->entries.animated_colors.foreground.enabled) {
398 style = style | color(Color::Interpolate(
399 animation_foreground_[i],
400 option_->entries.animated_colors.foreground.inactive,
401 option_->entries.animated_colors.foreground.active));
402 }
403
404 if (option_->entries.animated_colors.background.enabled) {
405 style = style | bgcolor(Color::Interpolate(
406 animation_background_[i],
407 option_->entries.animated_colors.background.inactive,
408 option_->entries.animated_colors.background.active));
409 }
410 return style;
411 }
412
413 void UpdateUnderlineTarget() {
414 if (!option_->underline.enabled) {
415 return;
416 }
417
418 if (FirstTarget() == animator_first_.to() &&
419 SecondTarget() == animator_second_.to()) {
420 return;
421 }
422
423 if (FirstTarget() >= animator_first_.to()) {
424 animator_first_ = animation::Animator(
425 &first_, FirstTarget(), option_->underline.follower_duration,
426 option_->underline.follower_function,
427 option_->underline.follower_delay);
428
429 animator_second_ = animation::Animator(
430 &second_, SecondTarget(), option_->underline.leader_duration,
431 option_->underline.leader_function, option_->underline.leader_delay);
432 } else {
433 animator_first_ = animation::Animator(
434 &first_, FirstTarget(), option_->underline.leader_duration,
435 option_->underline.leader_function, option_->underline.leader_delay);
436
437 animator_second_ = animation::Animator(
438 &second_, SecondTarget(), option_->underline.follower_duration,
439 option_->underline.follower_function,
440 option_->underline.follower_delay);
441 }
442 }
443
444 bool Focusable() const final { return entries_.size(); }
445 int& focused_entry() { return option_->focused_entry(); }
446 int size() const { return int(entries_.size()); }
447 float FirstTarget() {
448 if (boxes_.empty()) {
449 return 0.F;
450 }
451 const int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
452 : boxes_[*selected_].y_min - box_.y_min;
453 return float(value);
454 }
455 float SecondTarget() {
456 if (boxes_.empty()) {
457 return 0.F;
458 }
459 const int value = IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
460 : boxes_[*selected_].y_max - box_.y_min;
461 return float(value);
462 }
463
464 protected:
465 ConstStringListRef entries_;
466 int* selected_;
467 int selected_previous_ = *selected_;
468 int selected_focus_ = *selected_;
469 Ref<MenuOption> option_;
470
471 std::vector<Box> boxes_;
472 Box box_;
473
474 float first_ = 0.F;
475 float second_ = 0.F;
476 animation::Animator animator_first_ = animation::Animator(&first_, 0.F);
477 animation::Animator animator_second_ = animation::Animator(&second_, 0.F);
478
479 std::vector<animation::Animator> animator_background_;
480 std::vector<animation::Animator> animator_foreground_;
481 std::vector<float> animation_background_;
482 std::vector<float> animation_foreground_;
483};
484
485/// @brief A list of text. The focused element is selected.
486/// @param entries The list of entries in the menu.
487/// @param selected The index of the currently selected element.
488/// @param option Additional optional parameters.
489/// @ingroup component
490///
491/// ### Example
492///
493/// ```cpp
494/// auto screen = ScreenInteractive::TerminalOutput();
495/// std::vector<std::string> entries = {
496/// "entry 1",
497/// "entry 2",
498/// "entry 3",
499/// };
500/// int selected = 0;
501/// auto menu = Menu(&entries, &selected);
502/// screen.Loop(menu);
503/// ```
504///
505/// ### Output
506///
507/// ```bash
508/// > entry 1
509/// entry 2
510/// entry 3
511/// ```
513 int* selected,
514 Ref<MenuOption> option) {
515 return Make<MenuBase>(entries, selected, std::move(option));
516}
517
518/// @brief An horizontal list of elements. The user can navigate through them.
519/// @param entries The list of selectable entries to display.
520/// @param selected Reference the selected entry.
521/// @param See also |Menu|.
522/// @ingroup component
523Component Toggle(ConstStringListRef entries, int* selected) {
524 return Menu(entries, selected, MenuOption::Toggle());
525}
526
527/// @brief A specific menu entry. They can be put into a Container::Vertical to
528/// form a menu.
529/// @param label The text drawn representing this element.
530/// @param option Additional optional parameters.
531/// @ingroup component
532///
533/// ### Example
534///
535/// ```cpp
536/// auto screen = ScreenInteractive::TerminalOutput();
537/// int selected = 0;
538/// auto menu = Container::Vertical({
539/// MenuEntry("entry 1"),
540/// MenuEntry("entry 2"),
541/// MenuEntry("entry 3"),
542/// }, &selected);
543/// screen.Loop(menu);
544/// ```
545///
546/// ### Output
547///
548/// ```bash
549/// > entry 1
550/// entry 2
551/// entry 3
552/// ```
554 class Impl : public ComponentBase {
555 public:
556 Impl(ConstStringRef label, Ref<MenuEntryOption> option)
557 : label_(std::move(label)), option_(std::move(option)) {}
558
559 private:
560 Element Render() override {
561 const bool focused = Focused();
562 UpdateAnimationTarget();
563
564 const EntryState state = {
565 *label_,
566 false,
567 hovered_,
568 focused,
569 };
570
571 const Element element =
572 (option_->transform ? option_->transform : DefaultOptionTransform) //
573 (state);
574
575 auto focus_management = focused ? select : nothing;
576 return element | AnimatedColorStyle() | focus_management | reflect(box_);
577 }
578
579 void UpdateAnimationTarget() {
580 const bool focused = Focused();
581 float target = focused ? 1.F : hovered_ ? 0.5F : 0.F; // NOLINT
582 if (target == animator_background_.to()) {
583 return;
584 }
585 animator_background_ =
586 animation::Animator(&animation_background_, target,
587 option_->animated_colors.background.duration,
588 option_->animated_colors.background.function);
589 animator_foreground_ =
590 animation::Animator(&animation_foreground_, target,
591 option_->animated_colors.foreground.duration,
592 option_->animated_colors.foreground.function);
593 }
594
595 Decorator AnimatedColorStyle() {
596 Decorator style = nothing;
597 if (option_->animated_colors.foreground.enabled) {
598 style = style | color(Color::Interpolate(
599 animation_foreground_,
600 option_->animated_colors.foreground.inactive,
601 option_->animated_colors.foreground.active));
602 }
603
604 if (option_->animated_colors.background.enabled) {
605 style = style | bgcolor(Color::Interpolate(
606 animation_background_,
607 option_->animated_colors.background.inactive,
608 option_->animated_colors.background.active));
609 }
610 return style;
611 }
612
613 bool Focusable() const override { return true; }
614 bool OnEvent(Event event) override {
615 if (!event.is_mouse()) {
616 return false;
617 }
618
619 hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
620
621 if (!hovered_) {
622 return false;
623 }
624
625 if (event.mouse().button == Mouse::Left &&
626 event.mouse().motion == Mouse::Released) {
627 TakeFocus();
628 return true;
629 }
630
631 return false;
632 }
633
634 void OnAnimation(animation::Params& params) override {
635 animator_background_.OnAnimation(params);
636 animator_foreground_.OnAnimation(params);
637 }
638
639 ConstStringRef label_;
640 Ref<MenuEntryOption> option_;
641 Box box_;
642 bool hovered_ = false;
643
644 float animation_background_ = 0.F;
645 float animation_foreground_ = 0.F;
646 animation::Animator animator_background_ =
647 animation::Animator(&animation_background_, 0.F);
648 animation::Animator animator_foreground_ =
649 animation::Animator(&animation_foreground_, 0.F);
650 };
651
652 return Make<Impl>(std::move(label), std::move(option));
653}
654
655} // namespace ftxui
656
657// Copyright 2020 Arthur Sonzogni. All rights reserved.
658// Use of this source code is governed by the MIT license that can be found in
659// the LICENSE file.
static Color Interpolate(float t, const Color &a, const Color &b)
Definition color.cpp:172
It implement rendering itself as ftxui::Element. It implement keyboard navigation by responding to ft...
bool Focused() const
Returns if the elements if focused by the user. True when the ComponentBase is focused by the user....
CapturedMouse CaptureMouse(const Event &event)
Take the CapturedMouse if available. There is only one component of them. It represents a component t...
void TakeFocus()
Configure all the ancestors to give focus to this component.
An adapter. Reference a list of strings.
Definition ref.hpp:83
size_t size() const
Definition ref.hpp:88
An adapter. Own or reference a constant string. For convenience, this class convert multiple immutabl...
Definition ref.hpp:60
An adapter. Own or reference an mutable object.
Definition ref.hpp:27
float Linear(float p)
Definition animation.cpp:29
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:6
Decorator bgcolor(Color)
Decorate using a background color.
Definition color.cpp:100
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition flex.cpp:126
std::function< Element(Element)> Decorator
Definition elements.hpp:22
Element separatorVSelector(float up, float down, Color unselected_color, Color selected_color)
Draw an vertical bar, with the area in between up/downcolored differently.
Element nothing(Element element)
A decoration doing absolutely nothing.
Definition util.cpp:27
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:25
std::shared_ptr< Node > Element
Definition elements.hpp:20
Component Toggle(ConstStringListRef entries, int *selected)
An horizontal list of elements. The user can navigate through them.
Definition menu.cpp:523
Element bold(Element)
Use a bold font, for elements with more emphasis.
Definition bold.cpp:28
Element yflex(Element)
Expand/Minimize if possible/needed on the Y axis.
Definition flex.cpp:132
Component MenuEntry(ConstStringRef label, Ref< MenuEntryOption >={})
A specific menu entry. They can be put into a Container::Vertical to form a menu.
Definition menu.cpp:553
Element separatorHSelector(float left, float right, Color unselected_color, Color selected_color)
Draw an horizontal bar, with the area in between left/right colored differently.
Element focus(Element)
Definition frame.cpp:83
Element hbox(Elements)
A container displaying elements horizontally one by one.
Definition hbox.cpp:77
std::vector< Element > Elements
Definition elements.hpp:21
Element inverted(Element)
Add a filter that will invert the foreground and the background colors.
Definition inverted.cpp:29
Element text(std::wstring text)
Display a piece of unicode text.
Definition text.cpp:111
Component Menu(ConstStringListRef entries, int *selected_, Ref< MenuOption >=MenuOption::Vertical())
A list of text. The focused element is selected.
Definition menu.cpp:512
Decorator reflect(Box &box)
Definition reflect.cpp:39
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:44
Element select(Element)
Definition frame.cpp:38
std::shared_ptr< ComponentBase > Component
Decorator color(Color)
Decorate using a foreground color.
Definition color.cpp:86
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition vbox.cpp:78
arguments for |ButtonOption::transform|, |CheckboxOption::transform|, |Radiobox::transform|,...
bool Contain(int x, int y) const
Definition box.cpp:32
int y_min
Definition box.hpp:9
int y_max
Definition box.hpp:10
int x_min
Definition box.hpp:7
Represent an event. It can be key press event, a terminal resize, or more ...
Definition event.hpp:26
static const Event TabReverse
Definition event.hpp:52
static const Event PageUp
Definition event.hpp:58
bool is_mouse() const
Definition event.hpp:68
struct Mouse & mouse()
Definition event.hpp:69
static const Event ArrowUp
Definition event.hpp:38
static const Event Tab
Definition event.hpp:51
static const Event ArrowDown
Definition event.hpp:39
static const Event End
Definition event.hpp:56
static const Event Home
Definition event.hpp:55
static const Event PageDown
Definition event.hpp:59
static const Event Return
Definition event.hpp:49
static const Event ArrowLeft
Definition event.hpp:36
static const Event ArrowRight
Definition event.hpp:37
static MenuOption Toggle()
Button button
Definition mouse.hpp:24
Motion motion
Definition mouse.hpp:27