FTXUI 7.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
src/ftxui/component/dropdown.cpp
Go to the documentation of this file.
1// Copyright 2021 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.
5#include <functional> // for function, less
6#include <string> // for string
7
8#include <utility>
9#include "ftxui/component/component.hpp" // for Maybe, Checkbox, Make, Radiobox, Vertical, Dropdown
10#include "ftxui/component/component_base.hpp" // for Component, ComponentBase
11#include "ftxui/component/component_options.hpp" // for CheckboxOption, EntryState
12#include "ftxui/dom/elements.hpp" // for operator|, Element, border, filler, operator|=, separator, size, text, vbox, frame, vscroll_indicator, hbox, HEIGHT, LESS_THAN, bold, inverted
13#include "ftxui/screen/util.hpp" // for clamp
14#include "ftxui/util/ref.hpp" // for ConstStringListRef
15
16namespace ftxui {
17
18namespace {
19// Whether `ref` points to a variable owned by the caller, as opposed to a value
20// stored inside `ref` itself.
21bool IsExternal(const Ref<bool>& ref) {
22 const std::less<const void*> less;
23 const void* address = &*ref;
24 return less(address, &ref) || !less(address, &ref + 1);
25}
26} // namespace
27
28/// @brief A dropdown menu.
29/// @ingroup component
30/// @param entries The list of entries to display.
31/// @param selected The index of the selected entry.
32Component Dropdown(ConstStringListRef entries, int* selected) {
33 DropdownOption option;
34 option.radiobox.entries = std::move(entries);
35 option.radiobox.selected = selected;
36 return Dropdown(option);
37}
38
39/// @brief A dropdown menu.
40/// @ingroup component
41/// @param option The options for the dropdown.
42// NOLINTNEXTLINE
43Component Dropdown(DropdownOption option) {
44 class Impl : public ComponentBase, public DropdownOption {
45 public:
46 explicit Impl(DropdownOption option) : DropdownOption(std::move(option)) {
47 FillDefault();
48 checkbox_ = Checkbox(checkbox);
49 radiobox_ = Radiobox(radiobox);
50
51 container_ = Container::Vertical({
52 checkbox_,
53 Maybe(radiobox_, checkbox.checked),
54 });
55 Add(container_);
56 }
57
58 Element OnRender() override {
59 selected_ =
60 util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1);
61 selected_ = util::clamp(selected_(), 0, int(radiobox.entries.size()) - 1);
62
63 if (selected_() >= 0 && selected_() < int(radiobox.entries.size())) {
64 title_ = radiobox.entries[selected_()];
65 }
66
67 // Close the dropdown when another component takes the focus. This can
68 // happen without this dropdown receiving any event, e.g. when the user
69 // clicks on a sibling dropdown. Move the inner focus back to the
70 // checkbox without stealing the focus from the other component.
71 if (open() && !Focused()) {
72 container_->SetActiveChild(checkbox_);
73 *open = false;
74 }
75
76 return transform(*open, checkbox_->Render(), radiobox_->Render());
77 }
78
79 // Switch focus in between the checkbox and the radiobox when selecting it.
80 bool OnEvent(ftxui::Event event) override {
81 const bool open_old = open();
82 const int selected_old = selected_();
83 bool handled = ComponentBase::OnEvent(event);
84
85 // Transfer focus to the radiobox when the dropdown is opened.
86 if (!open_old && open()) {
87 radiobox_->TakeFocus();
88 }
89
90 // Auto-close the dropdown when the user selects an item, even if the item
91 // it the same as the previous one.
92 if (open_old && open()) {
93 const bool should_close =
94 (selected_() != selected_old) || //
95 (event == Event::Return) || //
96 (event == Event::Character(' ')) || //
97 (event == Event::Escape) || //
98 (event.is_mouse() && event.mouse().button == Mouse::Left &&
99 event.mouse().motion == Mouse::Pressed);
100
101 if (should_close) {
102 checkbox_->TakeFocus();
103 *open = false;
104 handled = true;
105 }
106 }
107
108 return handled;
109 }
110
111 void FillDefault() {
112 // Both `open` and `checkbox.checked` can hold the open state. Prefer the
113 // one bound to an external variable, then the one set to non-default.
114 if (!IsExternal(open) &&
115 (IsExternal(checkbox.checked) || checkbox.checked())) {
116 open = checkbox.checked;
117 }
118 selected_ = radiobox.selected;
119 checkbox.checked = &*open;
120 radiobox.selected = &*selected_;
121 checkbox.label = &title_;
122
123 if (!checkbox.transform) {
124 checkbox.transform = [](const EntryState& s) {
125 auto prefix = text(s.state ? "↓ " : "→ "); // NOLINT
126 auto t = text(s.label);
127 if (s.active) {
128 t |= bold;
129 }
130 if (s.focused) {
131 t |= inverted;
132 }
133 return hbox({prefix, t});
134 };
135 }
136
137 if (!transform) {
138 transform = [](bool is_open, Element checkbox_element,
139 Element radiobox_element) {
140 if (is_open) {
141 const int max_height = 12;
142 return vbox({
143 std::move(checkbox_element),
144 separator(),
145 std::move(radiobox_element) | vscroll_indicator | frame |
146 size(HEIGHT, LESS_THAN, max_height),
147 }) |
148 border;
149 }
150 return vbox({std::move(checkbox_element), filler()}) | border;
151 };
152 }
153 }
154
155 private:
156 Ref<int> selected_;
157 Component container_;
158 Component checkbox_;
159 Component radiobox_;
160 std::string title_;
161 };
162
163 return Make<Impl>(option);
164}
165
166} // namespace ftxui
An adapter. Own or reference an mutable object.
Definition ref.hpp:56
Component Dropdown(ConstStringListRef entries, int *selected)
A dropdown menu.
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
@ LESS_THAN
Definition elements.hpp:194
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:28
std::shared_ptr< Node > Element
Definition app.hpp:25
std::shared_ptr< ComponentBase > Component
Definition app.hpp:23
size_t size
Definition screen.cpp:82