FTXUI  4.1.1
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 <functional> // for function
4#include <memory> // for allocator_traits<>::value_type, swap
5#include <string> // for operator+, string
6#include <utility> // for move
7#include <vector> // for vector, __alloc_traits<>::value_type
8
9#include "ftxui/component/animation.hpp" // for Animator, Linear
10#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
11#include "ftxui/component/component.hpp" // for Make, Menu, MenuEntry, Toggle
12#include "ftxui/component/component_base.hpp" // for ComponentBase
13#include "ftxui/component/component_options.hpp" // for MenuOption, MenuEntryOption, MenuOption::Direction, UnderlineOption, AnimatedColorOption, AnimatedColorsOption, EntryState, MenuOption::Down, MenuOption::Left, MenuOption::Right, MenuOption::Up
14#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
15#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Released, Mouse::WheelDown, Mouse::WheelUp, Mouse::None
16#include "ftxui/component/screen_interactive.hpp" // for Component
17#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
18#include "ftxui/screen/box.hpp" // for Box
19#include "ftxui/screen/color.hpp" // for Color
20#include "ftxui/screen/util.hpp" // for clamp
21#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef, ConstStringRef
22
23namespace ftxui {
24
25namespace {
26
27Element DefaultOptionTransform(const EntryState& state) {
28 std::string label = (state.active ? "> " : " ") + state.label; // NOLINT
29 Element e = text(label);
30 if (state.focused) {
31 e = e | inverted;
32 }
33 if (state.active) {
34 e = e | bold;
35 }
36 return e;
37}
38
39bool IsInverted(MenuOption::Direction direction) {
40 switch (direction) {
43 return true;
46 return false;
47 }
48 return false; // NOT_REACHED()
49}
50
51bool IsHorizontal(MenuOption::Direction direction) {
52 switch (direction) {
55 return true;
58 return false;
59 }
60 return false; // NOT_REACHED()
61}
62
63} // namespace
64
65/// @brief A list of items. The user can navigate through them.
66/// @ingroup component
67class MenuBase : public ComponentBase {
68 public:
69 MenuBase(ConstStringListRef entries, int* selected, Ref<MenuOption> option)
70 : entries_(entries), selected_(selected), option_(std::move(option)) {}
71
72 bool IsHorizontal() { return ftxui::IsHorizontal(option_->direction); }
73 void OnChange() {
74 if (option_->on_change) {
75 option_->on_change();
76 }
77 }
78
79 void OnEnter() {
80 if (option_->on_enter) {
81 option_->on_enter();
82 }
83 }
84
85 void Clamp() {
86 if (*selected_ != selected_previous_) {
87 SelectedTakeFocus();
88 }
89 boxes_.resize(size());
90 *selected_ = util::clamp(*selected_, 0, size() - 1);
91 selected_previous_ = util::clamp(selected_previous_, 0, size() - 1);
92 selected_focus_ = util::clamp(selected_focus_, 0, size() - 1);
93 focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
94 }
95
96 void OnAnimation(animation::Params& params) override {
97 animator_first_.OnAnimation(params);
98 animator_second_.OnAnimation(params);
99 for (auto& animator : animator_background_) {
100 animator.OnAnimation(params);
101 }
102 for (auto& animator : animator_foreground_) {
103 animator.OnAnimation(params);
104 }
105 }
106
107 Element Render() override {
108 Clamp();
109 UpdateAnimationTarget();
110
111 Elements elements;
112 const bool is_menu_focused = Focused();
113 if (option_->elements_prefix) {
114 elements.push_back(option_->elements_prefix());
115 }
116 for (int i = 0; i < size(); ++i) {
117 if (i != 0 && option_->elements_infix) {
118 elements.push_back(option_->elements_infix());
119 }
120 const bool is_focused = (focused_entry() == i) && is_menu_focused;
121 const bool is_selected = (*selected_ == i);
122
123 const EntryState state = {
124 entries_[i],
125 false,
126 is_selected,
127 is_focused,
128 };
129
130 auto focus_management =
131 is_menu_focused && (selected_focus_ == i) ? focus : nothing;
132
133 const Element element =
134 (option_->entries.transform ? option_->entries.transform
135 : DefaultOptionTransform) //
136 (state);
137 elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) |
138 focus_management);
139 }
140 if (option_->elements_postfix) {
141 elements.push_back(option_->elements_postfix());
142 }
143
144 if (IsInverted(option_->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 (!option_->underline.enabled) {
152 return bar | reflect(box_);
153 }
154
155 if (IsHorizontal()) {
156 return vbox({
157 bar | xflex,
158 separatorHSelector(first_, second_, //
159 option_->underline.color_active,
160 option_->underline.color_inactive),
161 }) |
162 reflect(box_);
163 } else {
164 return hbox({
165 separatorVSelector(first_, second_, //
166 option_->underline.color_active,
167 option_->underline.color_inactive),
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 (option_->direction) {
182 (*selected_)++;
183 break;
185 (*selected_)--;
186 break;
189 break;
190 }
191 }
192
193 void OnDown() {
194 switch (option_->direction) {
196 (*selected_)--;
197 break;
199 (*selected_)++;
200 break;
203 break;
204 }
205 }
206
207 void OnLeft() {
208 switch (option_->direction) {
210 (*selected_)++;
211 break;
213 (*selected_)--;
214 break;
217 break;
218 }
219 }
220
221 void OnRight() {
222 switch (option_->direction) {
224 (*selected_)--;
225 break;
227 (*selected_)++;
228 break;
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) {
282 focused_entry() = *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 if (event.mouse().button == Mouse::Left &&
318 event.mouse().motion == Mouse::Released) {
319 if (*selected_ != i) {
320 *selected_ = i;
321 selected_previous_ = *selected_;
322 OnChange();
323 }
324 return true;
325 }
326 }
327 return false;
328 }
329
330 bool OnMouseWheel(Event event) {
331 if (!box_.Contain(event.mouse().x, event.mouse().y)) {
332 return false;
333 }
334 const int old_selected = *selected_;
335
336 if (event.mouse().button == Mouse::WheelUp) {
337 (*selected_)--;
338 }
339 if (event.mouse().button == Mouse::WheelDown) {
340 (*selected_)++;
341 }
342
343 *selected_ = util::clamp(*selected_, 0, size() - 1);
344
345 if (*selected_ != old_selected) {
346 SelectedTakeFocus();
347 OnChange();
348 }
349 return true;
350 }
351
352 void UpdateAnimationTarget() {
353 UpdateColorTarget();
354 UpdateUnderlineTarget();
355 }
356
357 void UpdateColorTarget() {
358 if (size() != (int)animation_background_.size()) {
359 animation_background_.resize(size());
360 animation_foreground_.resize(size());
361 animator_background_.clear();
362 animator_foreground_.clear();
363
364 for (int i = 0; i < size(); ++i) {
365 animation_background_[i] = 0.F;
366 animation_foreground_[i] = 0.F;
367 animator_background_.emplace_back(&animation_background_[i], 0.F,
368 std::chrono::milliseconds(0),
370 animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
371 std::chrono::milliseconds(0),
373 }
374 }
375
376 const bool is_menu_focused = Focused();
377 for (int i = 0; i < size(); ++i) {
378 const bool is_focused = (focused_entry() == i) && is_menu_focused;
379 const bool is_selected = (*selected_ == i);
380 float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F; // NOLINT
381 if (animator_background_[i].to() != target) {
382 animator_background_[i] = animation::Animator(
383 &animation_background_[i], target,
384 option_->entries.animated_colors.background.duration,
385 option_->entries.animated_colors.background.function);
386 animator_foreground_[i] = animation::Animator(
387 &animation_foreground_[i], target,
388 option_->entries.animated_colors.foreground.duration,
389 option_->entries.animated_colors.foreground.function);
390 }
391 }
392 }
393
394 Decorator AnimatedColorStyle(int i) {
395 Decorator style = nothing;
396 if (option_->entries.animated_colors.foreground.enabled) {
397 style = style | color(Color::Interpolate(
398 animation_foreground_[i],
399 option_->entries.animated_colors.foreground.inactive,
400 option_->entries.animated_colors.foreground.active));
401 }
402
403 if (option_->entries.animated_colors.background.enabled) {
404 style = style | bgcolor(Color::Interpolate(
405 animation_background_[i],
406 option_->entries.animated_colors.background.inactive,
407 option_->entries.animated_colors.background.active));
408 }
409 return style;
410 }
411
412 void UpdateUnderlineTarget() {
413 if (!option_->underline.enabled) {
414 return;
415 }
416
417 if (FirstTarget() == animator_first_.to() &&
418 SecondTarget() == animator_second_.to()) {
419 return;
420 }
421
422 if (FirstTarget() >= animator_first_.to()) {
423 animator_first_ = animation::Animator(
424 &first_, FirstTarget(), option_->underline.follower_duration,
425 option_->underline.follower_function,
426 option_->underline.follower_delay);
427
428 animator_second_ = animation::Animator(
429 &second_, SecondTarget(), option_->underline.leader_duration,
430 option_->underline.leader_function, option_->underline.leader_delay);
431 } else {
432 animator_first_ = animation::Animator(
433 &first_, FirstTarget(), option_->underline.leader_duration,
434 option_->underline.leader_function, option_->underline.leader_delay);
435
436 animator_second_ = animation::Animator(
437 &second_, SecondTarget(), option_->underline.follower_duration,
438 option_->underline.follower_function,
439 option_->underline.follower_delay);
440 }
441 }
442
443 bool Focusable() const final { return entries_.size(); }
444 int& focused_entry() { return option_->focused_entry(); }
445 int size() const { return int(entries_.size()); }
446 float FirstTarget() {
447 if (boxes_.empty()) {
448 return 0.F;
449 }
450 const int value = IsHorizontal() ? boxes_[*selected_].x_min - box_.x_min
451 : boxes_[*selected_].y_min - box_.y_min;
452 return float(value);
453 }
454 float SecondTarget() {
455 if (boxes_.empty()) {
456 return 0.F;
457 }
458 const int value = IsHorizontal() ? boxes_[*selected_].x_max - box_.x_min
459 : boxes_[*selected_].y_max - box_.y_min;
460 return float(value);
461 }
462
463 protected:
464 ConstStringListRef entries_;
465 int* selected_;
466 int selected_previous_ = *selected_;
467 int selected_focus_ = *selected_;
468 Ref<MenuOption> option_;
469
470 std::vector<Box> boxes_;
471 Box box_;
472
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
478 std::vector<animation::Animator> animator_background_;
479 std::vector<animation::Animator> animator_foreground_;
480 std::vector<float> animation_background_;
481 std::vector<float> animation_foreground_;
482};
483
484/// @brief A list of text. The focused element is selected.
485/// @param entries The list of entries in the menu.
486/// @param selected The index of the currently selected element.
487/// @param option Additional optional parameters.
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(&entries, &selected);
501/// screen.Loop(menu);
502/// ```
503///
504/// ### Output
505///
506/// ```bash
507/// > entry 1
508/// entry 2
509/// entry 3
510/// ```
512 int* selected,
513 Ref<MenuOption> option) {
514 return Make<MenuBase>(entries, selected, std::move(option));
515}
516
517/// @brief An horizontal list of elements. The user can navigate through them.
518/// @param entries The list of selectable entries to display.
519/// @param selected Reference the selected entry.
520/// @param See also |Menu|.
521/// @ingroup component
522Component Toggle(ConstStringListRef entries, int* selected) {
523 return Menu(entries, selected, MenuOption::Toggle());
524}
525
526/// @brief A specific menu entry. They can be put into a Container::Vertical to
527/// form a menu.
528/// @param label The text drawn representing this element.
529/// @param option Additional optional parameters.
530/// @ingroup component
531///
532/// ### Example
533///
534/// ```cpp
535/// auto screen = ScreenInteractive::TerminalOutput();
536/// int selected = 0;
537/// auto menu = Container::Vertical({
538/// MenuEntry("entry 1"),
539/// MenuEntry("entry 2"),
540/// MenuEntry("entry 3"),
541/// }, &selected);
542/// screen.Loop(menu);
543/// ```
544///
545/// ### Output
546///
547/// ```bash
548/// > entry 1
549/// entry 2
550/// entry 3
551/// ```
553 class Impl : public ComponentBase {
554 public:
555 Impl(ConstStringRef label, Ref<MenuEntryOption> option)
556 : label_(std::move(label)), option_(std::move(option)) {}
557
558 private:
559 Element Render() override {
560 const bool focused = Focused();
561 UpdateAnimationTarget();
562
563 const EntryState state = {
564 *label_,
565 false,
566 hovered_,
567 focused,
568 };
569
570 const Element element =
571 (option_->transform ? option_->transform : DefaultOptionTransform) //
572 (state);
573
574 auto focus_management = focused ? select : nothing;
575 return element | AnimatedColorStyle() | focus_management | reflect(box_);
576 }
577
578 void UpdateAnimationTarget() {
579 const bool focused = Focused();
580 float target = focused ? 1.F : hovered_ ? 0.5F : 0.F; // NOLINT
581 if (target == animator_background_.to()) {
582 return;
583 }
584 animator_background_ =
585 animation::Animator(&animation_background_, target,
586 option_->animated_colors.background.duration,
587 option_->animated_colors.background.function);
588 animator_foreground_ =
589 animation::Animator(&animation_foreground_, target,
590 option_->animated_colors.foreground.duration,
591 option_->animated_colors.foreground.function);
592 }
593
594 Decorator AnimatedColorStyle() {
595 Decorator style = nothing;
596 if (option_->animated_colors.foreground.enabled) {
597 style = style | color(Color::Interpolate(
598 animation_foreground_,
599 option_->animated_colors.foreground.inactive,
600 option_->animated_colors.foreground.active));
601 }
602
603 if (option_->animated_colors.background.enabled) {
604 style = style | bgcolor(Color::Interpolate(
605 animation_background_,
606 option_->animated_colors.background.inactive,
607 option_->animated_colors.background.active));
608 }
609 return style;
610 }
611
612 bool Focusable() const override { return true; }
613 bool OnEvent(Event event) override {
614 if (!event.is_mouse()) {
615 return false;
616 }
617
618 hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
619
620 if (!hovered_) {
621 return false;
622 }
623
624 if (event.mouse().button == Mouse::Left &&
625 event.mouse().motion == Mouse::Released) {
626 TakeFocus();
627 return true;
628 }
629
630 return false;
631 }
632
633 void OnAnimation(animation::Params& params) override {
634 animator_background_.OnAnimation(params);
635 animator_foreground_.OnAnimation(params);
636 }
637
638 ConstStringRef label_;
639 Ref<MenuEntryOption> option_;
640 Box box_;
641 bool hovered_ = false;
642
643 float animation_background_ = 0.F;
644 float animation_foreground_ = 0.F;
645 animation::Animator animator_background_ =
646 animation::Animator(&animation_background_, 0.F);
647 animation::Animator animator_foreground_ =
648 animation::Animator(&animation_foreground_, 0.F);
649 };
650
651 return Make<Impl>(std::move(label), std::move(option));
652}
653
654} // namespace ftxui
655
656// Copyright 2020 Arthur Sonzogni. All rights reserved.
657// Use of this source code is governed by the MIT license that can be found in
658// 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:21
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:19
Component Toggle(ConstStringListRef entries, int *selected)
An horizontal list of elements. The user can navigate through them.
Definition menu.cpp:522
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:552
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:20
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:511
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