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