29Element DefaultOptionTransform(
const EntryState& state) {
30 std::string label = (state.active ?
"> " :
" ") + state.label;
69class MenuBase :
public ComponentBase,
public MenuOption {
71 explicit MenuBase(
const MenuOption& option) : MenuOption(option) {
72 focused_entry() = selected();
75 bool IsHorizontal() {
return ftxui::IsHorizontal(
direction); }
76 void OnChange() { App::PostEventOrExecute(on_change); }
78 void OnEnter() { App::PostEventOrExecute(on_enter); }
81 if (selected() != selected_previous_) {
84 boxes_.resize(size());
85 selected() =
util::clamp(selected(), 0, size() - 1);
86 selected_previous_ =
util::clamp(selected_previous_, 0, size() - 1);
87 selected_focus_ =
util::clamp(selected_focus_, 0, size() - 1);
88 focused_entry() =
util::clamp(focused_entry(), 0, size() - 1);
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);
97 for (
auto& animator : animator_foreground_) {
98 animator.OnAnimation(params);
104 UpdateAnimationTarget();
107 const bool is_menu_focused = Focused();
108 if (elements_prefix) {
109 elements.push_back(elements_prefix());
111 elements.reserve(size());
112 for (
int i = 0; i < size(); ++i) {
113 if (i != 0 && elements_infix) {
114 elements.push_back(elements_infix());
116 const bool is_focused = (focused_entry() == i) && is_menu_focused;
117 const bool is_selected = (selected() == i);
119 const EntryState state = {
120 std::string(entries[i]),
false, is_selected, is_focused, i,
123 Element element = (entries_option.transform ? entries_option.transform
124 : DefaultOptionTransform)
126 if (selected_focus_ == i) {
129 element |= AnimatedColorStyle(i);
133 if (elements_postfix) {
134 elements.push_back(elements_postfix());
138 std::reverse(elements.begin(), elements.end());
142 IsHorizontal() ?
hbox(std::move(elements)) : vbox(std::move(elements));
144 if (!underline.enabled) {
145 return bar | reflect(box_);
148 if (IsHorizontal()) {
152 underline.color_active,
153 underline.color_inactive),
159 underline.color_active,
160 underline.color_inactive),
167 void SelectedTakeFocus() {
168 selected_previous_ = selected();
169 selected_focus_ = selected();
229 bool OnEvent(Event event)
override {
231 if (!CaptureMouse(event)) {
235 if (event.is_mouse()) {
236 return OnMouseEvent(event);
240 const int old_selected = selected();
241 if (event == Event::ArrowUp || event == Event::Character(
'k')) {
244 if (event == Event::ArrowDown || event == Event::Character(
'j')) {
247 if (event == Event::ArrowLeft || event == Event::Character(
'h')) {
250 if (event == Event::ArrowRight || event == Event::Character(
'l')) {
253 if (event == Event::PageUp) {
254 selected() -= box_.y_max - box_.y_min;
256 if (event == Event::PageDown) {
257 selected() += box_.y_max - box_.y_min;
259 if (event == Event::Home) {
262 if (event == Event::End) {
263 selected() = size() - 1;
265 if (event == Event::Tab && size()) {
266 selected() = (selected() + 1) % size();
268 if (event == Event::TabReverse && size()) {
269 selected() = (selected() + size() - 1) % size();
272 selected() =
util::clamp(selected(), 0, size() - 1);
274 if (selected() != old_selected) {
275 focused_entry() = selected();
282 if (event == Event::Return) {
290 bool OnMouseEvent(Event event) {
291 if (event.mouse().button == Mouse::WheelDown ||
292 event.mouse().button == Mouse::WheelUp) {
293 return OnMouseWheel(event);
296 if (event.mouse().button != Mouse::None &&
297 event.mouse().button != Mouse::Left) {
300 if (!CaptureMouse(event)) {
303 for (
int i = 0; i < size(); ++i) {
304 if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
311 if (event.mouse().button == Mouse::Left &&
312 event.mouse().motion == Mouse::Pressed) {
313 if (selected() != i) {
315 selected_previous_ = selected();
324 bool OnMouseWheel(Event event) {
325 if (!box_.Contain(event.mouse().x, event.mouse().y)) {
328 const int old_selected = selected();
330 if (event.mouse().button == Mouse::WheelUp) {
333 if (event.mouse().button == Mouse::WheelDown) {
337 selected() =
util::clamp(selected(), 0, size() - 1);
339 if (selected() != old_selected) {
346 void UpdateAnimationTarget() {
348 UpdateUnderlineTarget();
351 void UpdateColorTarget() {
352 if (size() !=
int(animation_background_.size())) {
353 animation_background_.resize(size());
354 animation_foreground_.resize(size());
355 animator_background_.clear();
356 animator_foreground_.clear();
358 const int len = size();
359 animator_background_.reserve(len);
360 animator_foreground_.reserve(len);
361 for (
int i = 0; i < len; ++i) {
362 animation_background_[i] = 0.F;
363 animation_foreground_[i] = 0.F;
364 animator_background_.emplace_back(&animation_background_[i], 0.F,
365 std::chrono::milliseconds(0),
367 animator_foreground_.emplace_back(&animation_foreground_[i], 0.F,
368 std::chrono::milliseconds(0),
373 const bool is_menu_focused = Focused();
374 for (
int i = 0; i < size(); ++i) {
375 const bool is_focused = (focused_entry() == i) && is_menu_focused;
376 const bool is_selected = (selected() == i);
377 float target = is_selected ? 1.F : is_focused ? 0.5F : 0.F;
378 if (animator_background_[i].to() != target) {
379 animator_background_[i] = animation::Animator(
380 &animation_background_[i], target,
381 entries_option.animated_colors.background.duration,
382 entries_option.animated_colors.background.function);
383 animator_foreground_[i] = animation::Animator(
384 &animation_foreground_[i], target,
385 entries_option.animated_colors.foreground.duration,
386 entries_option.animated_colors.foreground.function);
393 if (entries_option.animated_colors.foreground.enabled) {
394 style = style |
color(Color::Interpolate(
395 animation_foreground_[i],
396 entries_option.animated_colors.foreground.inactive,
397 entries_option.animated_colors.foreground.active));
400 if (entries_option.animated_colors.background.enabled) {
401 style = style |
bgcolor(Color::Interpolate(
402 animation_background_[i],
403 entries_option.animated_colors.background.inactive,
404 entries_option.animated_colors.background.active));
409 void UpdateUnderlineTarget() {
410 if (!underline.enabled) {
414 if (FirstTarget() == animator_first_.to() &&
415 SecondTarget() == animator_second_.to()) {
419 if (FirstTarget() >= animator_first_.to()) {
420 animator_first_ = animation::Animator(
421 &first_, FirstTarget(), underline.follower_duration,
422 underline.follower_function, underline.follower_delay);
424 animator_second_ = animation::Animator(
425 &second_, SecondTarget(), underline.leader_duration,
426 underline.leader_function, underline.leader_delay);
428 animator_first_ = animation::Animator(
429 &first_, FirstTarget(), underline.leader_duration,
430 underline.leader_function, underline.leader_delay);
432 animator_second_ = animation::Animator(
433 &second_, SecondTarget(), underline.follower_duration,
434 underline.follower_function, underline.follower_delay);
438 bool Focusable() const final {
return entries.size(); }
439 int size()
const {
return int(entries.size()); }
440 float FirstTarget() {
441 if (boxes_.empty()) {
444 const int value = IsHorizontal() ? boxes_[selected()].x_min - box_.x_min
445 : boxes_[selected()].y_min - box_.y_min;
448 float SecondTarget() {
449 if (boxes_.empty()) {
452 const int value = IsHorizontal() ? boxes_[selected()].x_max - box_.x_min
453 : boxes_[selected()].y_max - box_.y_min;
458 int selected_previous_ = selected();
459 int selected_focus_ = selected();
462 std::vector<Box> boxes_;
468 animation::Animator animator_first_ = animation::Animator(&first_, 0.F);
469 animation::Animator animator_second_ = animation::Animator(&second_, 0.F);
470 std::vector<animation::Animator> animator_background_;
471 std::vector<animation::Animator> animator_foreground_;
472 std::vector<float> animation_background_;
473 std::vector<float> animation_foreground_;
536Component Menu(ConstStringListRef entries,
int* selected, MenuOption option) {
537 option.entries = std::move(entries);
538 option.selected = selected;
548 return Menu(std::move(entries), selected, MenuOption::Toggle());
578 option.label = std::move(label);
608 class Impl :
public ComponentBase,
public MenuEntryOption {
610 explicit Impl(MenuEntryOption option)
611 : MenuEntryOption(std::move(option)) {}
615 const bool is_focused = Focused();
616 UpdateAnimationTarget();
618 const EntryState state{
619 std::string(label()),
false, hovered_, is_focused, Index(),
622 Element element = (transform ? transform : DefaultOptionTransform)
629 return element | AnimatedColorStyle() | reflect(box_);
632 void UpdateAnimationTarget() {
633 const bool focused = Focused();
634 float target = focused ? 1.F : hovered_ ? 0.5F : 0.F;
635 if (target == animator_background_.to()) {
638 animator_background_ = animation::Animator(
639 &animation_background_, target, animated_colors.background.duration,
640 animated_colors.background.function);
641 animator_foreground_ = animation::Animator(
642 &animation_foreground_, target, animated_colors.foreground.duration,
643 animated_colors.foreground.function);
648 if (animated_colors.foreground.enabled) {
650 color(Color::Interpolate(animation_foreground_,
651 animated_colors.foreground.inactive,
652 animated_colors.foreground.active));
655 if (animated_colors.background.enabled) {
657 bgcolor(Color::Interpolate(animation_background_,
658 animated_colors.background.inactive,
659 animated_colors.background.active));
664 bool Focusable()
const override {
return true; }
665 bool OnEvent(Event event)
override {
666 if (!event.is_mouse()) {
670 hovered_ = box_.Contain(event.mouse().x, event.mouse().y);
676 if (event.mouse().button == Mouse::Left &&
677 event.mouse().motion == Mouse::Pressed) {
685 void OnAnimation(animation::Params& params)
override {
686 animator_background_.OnAnimation(params);
687 animator_foreground_.OnAnimation(params);
691 bool hovered_ =
false;
693 float animation_background_ = 0.F;
694 float animation_foreground_ = 0.F;
695 animation::Animator animator_background_ =
696 animation::Animator(&animation_background_, 0.F);
697 animation::Animator animator_foreground_ =
698 animation::Animator(&animation_foreground_, 0.F);
Component Toggle(ConstStringListRef entries, int *selected)
An horizontal list of elements. The user can navigate through them.
Component Menu(ConstStringListRef entries, int *selected_, MenuOption options=MenuOption::Vertical())
A list of text. The focused element is selected.
Component MenuEntry(ConstStringRef label, MenuEntryOption options={})
A specific menu entry. They can be put into a Container::Vertical to form a menu.
Element text(std::string_view text)
Display a piece of UTF8 encoded unicode text.
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Element nothing(Element element)
A decoration doing absolutely nothing.
Direction
Direction is an enumeration that represents the four cardinal directions.
Element bold(Element child)
Use a bold font, for elements with more emphasis.
Element yflex(Element)
Expand/Minimize if possible/needed on the Y axis.
Element inverted(Element child)
Add a filter that will invert the foreground and the background colors.
Element focus(Element child)
Set the child to be the one focused among its siblings.
Element bgcolor(Color color, Element child)
Set the background color of an element.
Element color(Color color, Element child)
Set the foreground color of an element.
Element vbox(Elements children)
A container displaying elements vertically one by one.
float Linear(float p)
Modeled after the line y = x.
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
The FTXUI ftxui:: namespace.
Element separatorVSelector(float up, float down, Color unselected_color, Color selected_color)
Draw an vertical bar, with the area in between up/downcolored differently.
std::shared_ptr< T > Make(Args &&... args)
std::shared_ptr< Node > Element
FTXUI_EXPORT(DOM) Element separatorCharacter(std Element separatorHSelector(float left, float right, Color unselected_color, Color selected_color)
Draw a horizontal bar, with the area in between left/right colored differently.
Element hbox(Elements children)
A container displaying elements horizontally one by one.
std::vector< Element > Elements
std::function< Element(Element)> Decorator
Decorator reflect(Box &box)
std::shared_ptr< ComponentBase > Component