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