FTXUI  5.0.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
menu.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, fill_n, reverse
5#include <chrono> // for milliseconds
6#include <ftxui/dom/direction.hpp> // for Direction, Direction::Down, Direction::Left, Direction::Right, Direction::Up
7#include <functional> // for function
8#include <string> // for operator+, string
9#include <utility> // for move
10#include <vector> // for vector, __alloc_traits<>::value_type
11
12#include "ftxui/component/animation.hpp" // for Animator, Linear
13#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
14#include "ftxui/component/component_base.hpp" // for ComponentBase
15#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState
16#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
17#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
18#include "ftxui/component/screen_interactive.hpp" // for Component
19#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
20#include "ftxui/screen/box.hpp" // for Box
21#include "ftxui/screen/color.hpp" // for Color
22#include "ftxui/screen/util.hpp" // for clamp
23#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
24
25namespace ftxui {
26
27namespace {
28
29Element DefaultOptionTransform(const EntryState& state) {
30 std::string label = (state.active ? "> " : " ") + state.label; // NOLINT
31 Element e = text(std::move(label));
32 if (state.focused) {
33 e = e | inverted;
34 }
35 if (state.active) {
36 e = e | bold;
37 }
38 return e;
39}
40
41bool IsInverted(Direction direction) {
42 switch (direction) {
43 case Direction::Up:
44 case Direction::Left:
45 return true;
46 case Direction::Down:
48 return false;
49 }
50 return false; // NOT_REACHED()
51}
52
53bool IsHorizontal(Direction direction) {
54 switch (direction) {
55 case Direction::Left:
57 return true;
58 case Direction::Down:
59 case Direction::Up:
60 return false;
61 }
62 return false; // NOT_REACHED()
63}
64
65} // namespace
66
67/// @brief A list of items. The user can navigate through them.
68/// @ingroup component
69class MenuBase : public ComponentBase, public MenuOption {
70 public:
71 explicit MenuBase(const MenuOption& option) : MenuOption(option) {}
72
73 bool IsHorizontal() { return ftxui::IsHorizontal(direction); }
74 void OnChange() {
75 if (on_change) {
76 on_change();
77 }
78 }
79
80 void OnEnter() {
81 if (on_enter) {
82 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);
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
113 const bool is_menu_focused = Focused();
114 if (elements_prefix) {
115 elements.push_back(elements_prefix());
116 }
117 elements.reserve(size());
118 for (int i = 0; i < size(); ++i) {
119 if (i != 0 && elements_infix) {
120 elements.push_back(elements_infix());
121 }
122 const bool is_focused = (focused_entry() == i) && is_menu_focused;
123 const bool is_selected = (selected() == i);
124
125 const EntryState state = {
126 entries[i], false, is_selected, is_focused, i,
127 };
128
129 auto focus_management = (selected_focus_ != i) ? nothing
131 : select;
132
133 const Element element =
136 (state);
137 elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
139 }
140 if (elements_postfix) {
141 elements.push_back(elements_postfix());
142 }
143
144 if (IsInverted(direction)) {
145 std::reverse(elements.begin(), elements.end());
146 }
147
148 const Element bar =
149 IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
150
151 if (!underline.enabled) {
152 return bar | reflect(box_);
153 }
154
155 if (IsHorizontal()) {
156 return vbox({
157 bar | xflex,
158 separatorHSelector(first_, second_, //
161 }) |
162 reflect(box_);
163 } else {
164 return hbox({
165 separatorVSelector(first_, second_, //
168 bar | yflex,
169 }) |
170 reflect(box_);
171 }
172 }
173
174 void SelectedTakeFocus() {
175 selected_previous_ = selected();
176 selected_focus_ = selected();
177 }
178
179 void OnUp() {
180 switch (direction) {
181 case Direction::Up:
182 selected()++;
183 break;
184 case Direction::Down:
185 selected()--;
186 break;
187 case Direction::Left:
188 case Direction::Right:
189 break;
190 }
191 }
192
193 void OnDown() {
194 switch (direction) {
195 case Direction::Up:
196 selected()--;
197 break;
198 case Direction::Down:
199 selected()++;
200 break;
201 case Direction::Left:
202 case Direction::Right:
203 break;
204 }
205 }
206
207 void OnLeft() {
208 switch (direction) {
209 case Direction::Left:
210 selected()++;
211 break;
212 case Direction::Right:
213 selected()--;
214 break;
215 case Direction::Down:
216 case Direction::Up:
217 break;
218 }
219 }
220
221 void OnRight() {
222 switch (direction) {
223 case Direction::Left:
224 selected()--;
225 break;
226 case Direction::Right:
227 selected()++;
228 break;
229 case Direction::Down:
230 case Direction::Up:
231 break;
232 }
233 }
234
235 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
236 bool OnEvent(Event event) override {
237 Clamp();
238 if (!CaptureMouse(event)) {
239 return false;
240 }
241
242 if (event.is_mouse()) {
243 return OnMouseEvent(event);
244 }
245
246 if (Focused()) {
247 const int old_selected = selected();
248 if (event == Event::ArrowUp || event == Event::Character('k')) {
249 OnUp();
250 }
251 if (event == Event::ArrowDown || event == Event::Character('j')) {
252 OnDown();
253 }
254 if (event == Event::ArrowLeft || event == Event::Character('h')) {
255 OnLeft();
256 }
257 if (event == Event::ArrowRight || event == Event::Character('l')) {
258 OnRight();
259 }
260 if (event == Event::PageUp) {
261 selected() -= box_.y_max - box_.y_min;
262 }
263 if (event == Event::PageDown) {
264 selected() += box_.y_max - box_.y_min;
265 }
266 if (event == Event::Home) {
267 selected() = 0;
268 }
269 if (event == Event::End) {
270 selected() = size() - 1;
271 }
272 if (event == Event::Tab && size()) {
273 selected() = (selected() + 1) % size();
274 }
275 if (event == Event::TabReverse && size()) {
276 selected() = (selected() + size() - 1) % size();
277 }
278
279 selected() = util::clamp(selected(), 0, size() - 1);
280
281 if (selected() != old_selected) {
283 SelectedTakeFocus();
284 OnChange();
285 return true;
286 }
287 }
288
289 if (event == Event::Return) {
290 OnEnter();
291 return true;
292 }
293
294 return false;
295 }
296
297 bool OnMouseEvent(Event event) {
298 if (event.mouse().button == Mouse::WheelDown ||
299 event.mouse().button == Mouse::WheelUp) {
300 return OnMouseWheel(event);
301 }
302
303 if (event.mouse().button != Mouse::None &&
304 event.mouse().button != Mouse::Left) {
305 return false;
306 }
307 if (!CaptureMouse(event)) {
308 return false;
309 }
310 for (int i = 0; i < size(); ++i) {
311 if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
312 continue;
313 }
314
315 TakeFocus();
316 focused_entry() = i;
317
318 if (event.mouse().button == Mouse::Left &&
319 event.mouse().motion == Mouse::Pressed) {
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 const int len = size();
366 animator_background_.reserve(len);
367 animator_foreground_.reserve(len);
368 for (int i = 0; i < len; ++i) {
369 animation_background_[i] = 0.F;
370 animation_foreground_[i] = 0.F;
371 animator_background_.emplace_back(&animation_background_[i], 0.F,
372 std::chrono::milliseconds(0),
373 animation::easing::Linear);
374 animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
375 std::chrono::milliseconds(0),
376 animation::easing::Linear);
377 }
378 }
379
380 const bool is_menu_focused = Focused();
381 for (int i = 0; i < size(); ++i) {
382 const bool is_focused = (focused_entry() == i) && is_menu_focused;
383 const bool is_selected = (selected() == i);
384 float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT
385 if (animator_background_[i].to() != target) {
386 animator_background_[i] = animation::Animator(
387 &animation_background_[i], target,
390 animator_foreground_[i] = animation::Animator(
391 &animation_foreground_[i], target,
394 }
395 }
396 }
397
398 Decorator AnimatedColorStyle(int i) {
402 animation_foreground_[i],
405 }
406
409 animation_background_[i],
412 }
413 return style;
414 }
415
416 void UpdateUnderlineTarget() {
417 if (!underline.enabled) {
418 return;
419 }
420
421 if (FirstTarget() == animator_first_.to() &&
422 SecondTarget() == animator_second_.to()) {
423 return;
424 }
425
426 if (FirstTarget() >= animator_first_.to()) {
427 animator_first_ = animation::Animator(
428 &first_, FirstTarget(), underline.follower_duration,
430
431 animator_second_ = animation::Animator(
432 &second_, SecondTarget(), underline.leader_duration,
434 } else {
435 animator_first_ = animation::Animator(
436 &first_, FirstTarget(), underline.leader_duration,
438
439 animator_second_ = animation::Animator(
440 &second_, SecondTarget(), underline.follower_duration,
442 }
443 }
444
445 bool Focusable() const final { return entries.size(); }
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 int selected_previous_ = selected();
466 int selected_focus_ = selected();
467
468 // Mouse click support:
469 std::vector<Box> boxes_;
470 Box box_;
471
472 // Animation support:
473 float first_ = 0.F;
474 float second_ = 0.F;
475 animation::Animator animator_first_ = animation::Animator(&first_, 0.F);
476 animation::Animator animator_second_ = animation::Animator(&second_, 0.F);
477 std::vector<animation::Animator> animator_background_;
478 std::vector<animation::Animator> animator_foreground_;
479 std::vector<float> animation_background_;
480 std::vector<float> animation_foreground_;
481};
482
483/// @brief A list of text. The focused element is selected.
484/// @param option a structure containing all the paramters.
485/// @ingroup component
486///
487/// ### Example
488///
489/// ```cpp
490/// auto screen = ScreenInteractive::TerminalOutput();
491/// std::vector<std::string> entries = {
492/// "entry 1",
493/// "entry 2",
494/// "entry 3",
495/// };
496/// int selected = 0;
497/// auto menu = Menu({
498/// .entries = &entries,
499/// .selected = &selected,
500/// });
501/// screen.Loop(menu);
502/// ```
503///
504/// ### Output
505///
506/// ```bash
507/// > entry 1
508/// entry 2
509/// entry 3
510/// ```
511// NOLINTNEXTLINE
513 return Make<MenuBase>(std::move(option));
514}
515
516/// @brief A list of text. The focused element is selected.
517/// @param entries The list of entries in the menu.
518/// @param selected The index of the currently selected element.
519/// @param option Additional optional parameters.
520/// @ingroup component
521///
522/// ### Example
523///
524/// ```cpp
525/// auto screen = ScreenInteractive::TerminalOutput();
526/// std::vector<std::string> entries = {
527/// "entry 1",
528/// "entry 2",
529/// "entry 3",
530/// };
531/// int selected = 0;
532/// auto menu = Menu(&entries, &selected);
533/// screen.Loop(menu);
534/// ```
535///
536/// ### Output
537///
538/// ```bash
539/// > entry 1
540/// entry 2
541/// entry 3
542/// ```
544 option.entries = std::move(entries);
545 option.selected = selected;
546 return Menu(option);
547}
548
549/// @brief An horizontal list of elements. The user can navigate through them.
550/// @param entries The list of selectable entries to display.
551/// @param selected Reference the selected entry.
552/// See also |Menu|.
553/// @ingroup component
554Component Toggle(ConstStringListRef entries, int* selected) {
555 return Menu(std::move(entries), selected, MenuOption::Toggle());
556}
557
558/// @brief A specific menu entry. They can be put into a Container::Vertical to
559/// form a menu.
560/// @param label The text drawn representing this element.
561/// @param option Additional optional parameters.
562/// @ingroup component
563///
564/// ### Example
565///
566/// ```cpp
567/// auto screen = ScreenInteractive::TerminalOutput();
568/// int selected = 0;
569/// auto menu = Container::Vertical({
570/// MenuEntry("entry 1"),
571/// MenuEntry("entry 2"),
572/// MenuEntry("entry 3"),
573/// }, &selected);
574/// screen.Loop(menu);
575/// ```
576///
577/// ### Output
578///
579/// ```bash
580/// > entry 1
581/// entry 2
582/// entry 3
583/// ```
585 option.label = std::move(label);
586 return MenuEntry(std::move(option));
587}
588
589/// @brief A specific menu entry. They can be put into a Container::Vertical to
590/// form a menu.
591/// @param option The parameters.
592/// @ingroup component
593///
594/// ### Example
595///
596/// ```cpp
597/// auto screen = ScreenInteractive::TerminalOutput();
598/// int selected = 0;
599/// auto menu = Container::Vertical({
600/// MenuEntry({.label = "entry 1"}),
601/// MenuEntry({.label = "entry 2"}),
602/// MenuEntry({.label = "entry 3"}),
603/// }, &selected);
604/// screen.Loop(menu);
605/// ```
606///
607/// ### Output
608///
609/// ```bash
610/// > entry 1
611/// entry 2
612/// entry 3
613/// ```
615 class Impl : public ComponentBase, public MenuEntryOption {
616 public:
617 explicit Impl(MenuEntryOption option)
618 : MenuEntryOption(std::move(option)) {}
619
620 private:
621 Element Render() override {
622 const bool focused = Focused();
623 UpdateAnimationTarget();
624
625 const EntryState state{
626 label(), false, hovered_, focused, Index(),
627 };
628
629 const Element element =
630 (transform ? transform : DefaultOptionTransform) //
631 (state);
632
633 auto focus_management = focused ? select : nothing;
634 return element | AnimatedColorStyle() | focus_management | reflect(box_);
635 }
636
637 void UpdateAnimationTarget() {
638 const bool focused = Focused();
639 float target = focused ? 1.F : hovered_ ? 0.5F : 0.F; // NOLINT
640 if (target == animator_background_.to()) {
641 return;
642 }
643 animator_background_ = animation::Animator(
644 &animation_background_, target, animated_colors.background.duration,
645 animated_colors.background.function);
646 animator_foreground_ = animation::Animator(
647 &animation_foreground_, target, animated_colors.foreground.duration,
648 animated_colors.foreground.function);
649 }
650
651 Decorator AnimatedColorStyle() {
653 if (animated_colors.foreground.enabled) {
654 style = style |
655 color(Color::Interpolate(animation_foreground_,
656 animated_colors.foreground.inactive,
657 animated_colors.foreground.active));
658 }
659
660 if (animated_colors.background.enabled) {
661 style = style |
662 bgcolor(Color::Interpolate(animation_background_,
663 animated_colors.background.inactive,
664 animated_colors.background.active));
665 }
666 return style;
667 }
668
669 bool Focusable() const override { return true; }
670 bool OnEvent(Event event) override {
671 if (!event.is_mouse()) {
672 return false;
673 }
674
675 hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
676
677 if (!hovered_) {
678 return false;
679 }
680
681 if (event.mouse().button == Mouse::Left &&
682 event.mouse().motion == Mouse::Pressed) {
683 TakeFocus();
684 return true;
685 }
686
687 return false;
688 }
689
690 void OnAnimation(animation::Params& params) override {
691 animator_background_.OnAnimation(params);
692 animator_foreground_.OnAnimation(params);
693 }
694
695 MenuEntryOption option_;
696 Box box_;
697 bool hovered_ = false;
698
699 float animation_background_ = 0.F;
700 float animation_foreground_ = 0.F;
701 animation::Animator animator_background_ =
702 animation::Animator(&animation_background_, 0.F);
703 animation::Animator animator_foreground_ =
704 animation::Animator(&animation_foreground_, 0.F);
705 };
706
707 return Make<Impl>(std::move(option));
708}
709
710} // namespace ftxui
static Color Interpolate(float t, const Color &a, const Color &b)
Definition color.cpp:212
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:116
size_t size() const
Definition ref.hpp:167
An adapter. Own or reference a constant string. For convenience, this class convert multiple immutabl...
Definition ref.hpp:94
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:11
Decorator bgcolor(Color)
Decorate using a background color.
Definition color.cpp:124
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition flex.cpp:129
std::function< Element(Element)> Decorator
Definition elements.hpp:24
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:28
Decorator size(WidthOrHeight, Constraint, int value)
Apply a constraint on the size of an element.
Definition size.cpp:89
std::shared_ptr< Node > Element
Definition elements.hpp:22
Component Menu(MenuOption options)
A list of text. The focused element is selected.
Definition menu.cpp:512
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:26
Component MenuEntry(MenuEntryOption options)
A specific menu entry. They can be put into a Container::Vertical to form a menu.
Definition menu.cpp:614
std::shared_ptr< ComponentBase > Component
Component Toggle(ConstStringListRef entries, int *selected)
An horizontal list of elements. The user can navigate through them.
Definition menu.cpp:554
Element bold(Element)
Use a bold font, for elements with more emphasis.
Definition bold.cpp:33
Element yflex(Element)
Expand/Minimize if possible/needed on the Y axis.
Definition flex.cpp:135
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 hbox(Elements)
A container displaying elements horizontally one by one.
Definition hbox.cpp:96
std::function< Element(const EntryState &state)> transform
Element inverted(Element)
Add a filter that will invert the foreground and the background colors.
Definition inverted.cpp:34
Element text(std::wstring text)
Display a piece of unicode text.
Definition text.cpp:159
std::vector< Element > Elements
Definition elements.hpp:23
Element select(Element)
Set the child to be the one selected among its siblings.
Definition frame.cpp:149
Element focus(Element)
Set the child to be the one in focus globally.
Definition frame.cpp:156
Decorator reflect(Box &box)
Definition reflect.cpp:43
AnimatedColorsOption animated_colors
void Render(Screen &screen, const Element &element)
Display an element on a ftxui::Screen.
Definition node.cpp:72
Decorator color(Color)
Decorate using a foreground color.
Definition color.cpp:110
Element vbox(Elements)
A container displaying elements vertically one by one.
Definition vbox.cpp:97
arguments for |ButtonOption::transform|, |CheckboxOption::transform|, |Radiobox::transform|,...
Option for the MenuEntry component.
animation::easing::Function function
Represent an event. It can be key press event, a terminal resize, or more ...
Definition event.hpp:27
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 Return
Definition event.hpp:51
static const Event ArrowLeft
Definition event.hpp:38
static const Event ArrowRight
Definition event.hpp:39
Option for the Menu component.
std::function< Element()> elements_prefix
static MenuOption Toggle()
Standard options for a horitontal menu with some separator. This can be useful to implement a tab bar...
MenuEntryOption entries_option
std::function< void()> on_enter
‍Called when the selected entry changes.
UnderlineOption underline
‍The index of the selected entry.
ConstStringListRef entries
Ref< int > focused_entry
‍Called when the user presses enter.
std::function< Element()> elements_infix
std::function< Element()> elements_postfix
std::function< void()> on_change
Ref< int > selected
‍The list of entries.
animation::Duration follower_duration
animation::easing::Function leader_function
animation::Duration follower_delay
animation::Duration leader_duration
animation::easing::Function follower_function
animation::Duration leader_delay