mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-22 18:59:59 +08:00
Feature: Dropdown options with callback (#826)
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
parent
2216f3a5da
commit
3c9fa60d28
@ -7,6 +7,7 @@ current (development)
|
|||||||
### Component
|
### Component
|
||||||
- Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
|
- Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
|
||||||
option. Added by @mingsheng13.
|
option. Added by @mingsheng13.
|
||||||
|
- Feature: Add `DropdownOption` to configure the dropdown. See #826.
|
||||||
- Bugfix/Breaking change: `Mouse transition`:
|
- Bugfix/Breaking change: `Mouse transition`:
|
||||||
- Detect when the mouse move, as opposed to being pressed.
|
- Detect when the mouse move, as opposed to being pressed.
|
||||||
The Mouse::Moved motion was added.
|
The Mouse::Moved motion was added.
|
||||||
|
@ -11,6 +11,7 @@ example(collapsible)
|
|||||||
example(composition)
|
example(composition)
|
||||||
example(custom_loop)
|
example(custom_loop)
|
||||||
example(dropdown)
|
example(dropdown)
|
||||||
|
example(dropdown_custom)
|
||||||
example(flexbox_gallery)
|
example(flexbox_gallery)
|
||||||
example(focus)
|
example(focus)
|
||||||
example(focus_cursor)
|
example(focus_cursor)
|
||||||
|
104
examples/component/dropdown_custom.cpp
Normal file
104
examples/component/dropdown_custom.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||||
|
// Use of this source code is governed by the MIT license that can be found in
|
||||||
|
// the LICENSE file.
|
||||||
|
#include <string> // for basic_string, string, allocator
|
||||||
|
#include <vector> // for vector
|
||||||
|
|
||||||
|
#include "ftxui/component/captured_mouse.hpp" // for ftxui
|
||||||
|
#include "ftxui/component/component.hpp" // for Dropdown, Horizontal, Vertical
|
||||||
|
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace ftxui;
|
||||||
|
|
||||||
|
std::vector<std::string> entries = {
|
||||||
|
"tribute", "clearance", "ally", "bend", "electronics",
|
||||||
|
"module", "era", "cultural", "sniff", "nationalism",
|
||||||
|
"negotiation", "deliver", "figure", "east", "tribute",
|
||||||
|
"clearance", "ally", "bend", "electronics", "module",
|
||||||
|
"era", "cultural", "sniff", "nationalism", "negotiation",
|
||||||
|
"deliver", "figure", "east", "tribute", "clearance",
|
||||||
|
"ally", "bend", "electronics", "module", "era",
|
||||||
|
"cultural", "sniff", "nationalism", "negotiation", "deliver",
|
||||||
|
"figure", "east",
|
||||||
|
};
|
||||||
|
|
||||||
|
auto dropdown_1 = Dropdown({
|
||||||
|
.radiobox = {.entries = &entries},
|
||||||
|
.transform =
|
||||||
|
[](bool open, Element checkbox, Element radiobox) {
|
||||||
|
if (open) {
|
||||||
|
return vbox({
|
||||||
|
checkbox | inverted,
|
||||||
|
radiobox | vscroll_indicator | frame |
|
||||||
|
size(HEIGHT, LESS_THAN, 10),
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return vbox({
|
||||||
|
checkbox,
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dropdown_2 = Dropdown({
|
||||||
|
.radiobox = {.entries = &entries},
|
||||||
|
.transform =
|
||||||
|
[](bool open, Element checkbox, Element radiobox) {
|
||||||
|
if (open) {
|
||||||
|
return vbox({
|
||||||
|
checkbox | inverted,
|
||||||
|
radiobox | vscroll_indicator | frame |
|
||||||
|
size(HEIGHT, LESS_THAN, 10) | bgcolor(Color::Blue),
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return vbox({
|
||||||
|
checkbox | bgcolor(Color::Blue),
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dropdown_3 = Dropdown({
|
||||||
|
.radiobox =
|
||||||
|
{
|
||||||
|
.entries = &entries,
|
||||||
|
.transform =
|
||||||
|
[](const EntryState& s) {
|
||||||
|
auto t = text(s.label) | borderEmpty;
|
||||||
|
if (s.active) {
|
||||||
|
t |= bold;
|
||||||
|
}
|
||||||
|
if (s.focused) {
|
||||||
|
t |= inverted;
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.transform =
|
||||||
|
[](bool open, Element checkbox, Element radiobox) {
|
||||||
|
checkbox |= borderEmpty;
|
||||||
|
if (open) {
|
||||||
|
return vbox({
|
||||||
|
checkbox | inverted,
|
||||||
|
radiobox | vscroll_indicator | frame |
|
||||||
|
size(HEIGHT, LESS_THAN, 20) | bgcolor(Color::Red),
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return vbox({
|
||||||
|
checkbox | bgcolor(Color::Red),
|
||||||
|
filler(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
auto screen = ScreenInteractive::FitComponent();
|
||||||
|
screen.Loop(Container::Horizontal({
|
||||||
|
dropdown_1,
|
||||||
|
dropdown_2,
|
||||||
|
dropdown_3,
|
||||||
|
}));
|
||||||
|
}
|
@ -494,11 +494,11 @@ int main() {
|
|||||||
"Exit", [&] { screen.Exit(); }, ButtonOption::Animated());
|
"Exit", [&] { screen.Exit(); }, ButtonOption::Animated());
|
||||||
|
|
||||||
auto main_container = Container::Vertical({
|
auto main_container = Container::Vertical({
|
||||||
Container::Horizontal({
|
Container::Horizontal({
|
||||||
tab_selection,
|
tab_selection,
|
||||||
exit_button,
|
exit_button,
|
||||||
}),
|
}),
|
||||||
tab_content,
|
tab_content,
|
||||||
});
|
});
|
||||||
|
|
||||||
auto main_renderer = Renderer(main_container, [&] {
|
auto main_renderer = Renderer(main_container, [&] {
|
||||||
|
@ -75,6 +75,8 @@ Component Radiobox(ConstStringListRef entries,
|
|||||||
RadioboxOption options = {});
|
RadioboxOption options = {});
|
||||||
|
|
||||||
Component Dropdown(ConstStringListRef entries, int* selected);
|
Component Dropdown(ConstStringListRef entries, int* selected);
|
||||||
|
Component Dropdown(DropdownOption options);
|
||||||
|
|
||||||
Component Toggle(ConstStringListRef entries, int* selected);
|
Component Toggle(ConstStringListRef entries, int* selected);
|
||||||
|
|
||||||
// General slider constructor:
|
// General slider constructor:
|
||||||
|
@ -263,6 +263,21 @@ struct WindowOptions {
|
|||||||
std::function<Element(const WindowRenderState&)> render;
|
std::function<Element(const WindowRenderState&)> render;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief Option for the Dropdown component.
|
||||||
|
/// @ingroup component
|
||||||
|
/// A dropdown menu is a checkbox opening/closing a radiobox.
|
||||||
|
struct DropdownOption {
|
||||||
|
/// Whether the dropdown is open or closed:
|
||||||
|
Ref<bool> open = false;
|
||||||
|
// The options for the checkbox:
|
||||||
|
CheckboxOption checkbox;
|
||||||
|
// The options for the radiobox:
|
||||||
|
RadioboxOption radiobox;
|
||||||
|
// The transformation function:
|
||||||
|
std::function<Element(bool open, Element checkbox, Element radiobox)>
|
||||||
|
transform;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ftxui
|
} // namespace ftxui
|
||||||
|
|
||||||
#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */
|
#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */
|
||||||
|
@ -104,7 +104,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
|
|||||||
|
|
||||||
// TODO(arthursonzogni): Consider posting the task to the main loop, instead
|
// TODO(arthursonzogni): Consider posting the task to the main loop, instead
|
||||||
// of invoking it immediately.
|
// of invoking it immediately.
|
||||||
on_click(); // May delete this.
|
on_click(); // May delete this.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OnEvent(Event event) override {
|
bool OnEvent(Event event) override {
|
||||||
@ -113,7 +113,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event == Event::Return) {
|
if (event == Event::Return) {
|
||||||
OnClick(); // May delete this.
|
OnClick(); // May delete this.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -130,7 +130,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
|
|||||||
if (event.mouse().button == Mouse::Left &&
|
if (event.mouse().button == Mouse::Left &&
|
||||||
event.mouse().motion == Mouse::Pressed) {
|
event.mouse().motion == Mouse::Pressed) {
|
||||||
TakeFocus();
|
TakeFocus();
|
||||||
OnClick(); // May delete this.
|
OnClick(); // May delete this.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,79 +20,102 @@ namespace ftxui {
|
|||||||
/// @param entries The list of entries to display.
|
/// @param entries The list of entries to display.
|
||||||
/// @param selected The index of the selected entry.
|
/// @param selected The index of the selected entry.
|
||||||
Component Dropdown(ConstStringListRef entries, int* selected) {
|
Component Dropdown(ConstStringListRef entries, int* selected) {
|
||||||
class Impl : public ComponentBase {
|
DropdownOption option;
|
||||||
|
option.radiobox.entries = entries;
|
||||||
|
option.radiobox.selected = selected;
|
||||||
|
return Dropdown(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief A dropdown menu.
|
||||||
|
/// @ingroup component
|
||||||
|
/// @param option The options for the dropdown.
|
||||||
|
Component Dropdown(DropdownOption option) {
|
||||||
|
class Impl : public ComponentBase, public DropdownOption {
|
||||||
public:
|
public:
|
||||||
Impl(ConstStringListRef entries, int* selected)
|
Impl(DropdownOption option) : DropdownOption(std::move(option)) {
|
||||||
: entries_(entries), selected_(selected) {
|
FillDefault();
|
||||||
CheckboxOption option;
|
checkbox_ = Checkbox(checkbox);
|
||||||
option.transform = [](const EntryState& s) {
|
radiobox_ = Radiobox(radiobox);
|
||||||
auto prefix = text(s.state ? "↓ " : "→ "); // NOLINT
|
|
||||||
auto t = text(s.label);
|
|
||||||
if (s.active) {
|
|
||||||
t |= bold;
|
|
||||||
}
|
|
||||||
if (s.focused) {
|
|
||||||
t |= inverted;
|
|
||||||
}
|
|
||||||
return hbox({prefix, t});
|
|
||||||
};
|
|
||||||
checkbox_ = Checkbox(&title_, &show_, option);
|
|
||||||
radiobox_ = Radiobox(entries_, selected_);
|
|
||||||
|
|
||||||
Add(Container::Vertical({
|
Add(Container::Vertical({
|
||||||
checkbox_,
|
checkbox_,
|
||||||
Maybe(radiobox_, &show_),
|
Maybe(radiobox_, checkbox.checked),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Element Render() override {
|
Element Render() override {
|
||||||
*selected_ = util::clamp(*selected_, 0, int(entries_.size()) - 1);
|
radiobox.selected =
|
||||||
title_ = entries_[static_cast<size_t>(*selected_)];
|
util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1);
|
||||||
if (show_) {
|
checkbox.label =
|
||||||
const int max_height = 12;
|
radiobox.entries[static_cast<size_t>(radiobox.selected())];
|
||||||
return vbox({
|
|
||||||
checkbox_->Render(),
|
|
||||||
separator(),
|
|
||||||
radiobox_->Render() | vscroll_indicator | frame |
|
|
||||||
size(HEIGHT, LESS_THAN, max_height),
|
|
||||||
}) |
|
|
||||||
border;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vbox({
|
return transform(*open_, checkbox_->Render(), radiobox_->Render());
|
||||||
checkbox_->Render() | border,
|
|
||||||
filler(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch focus in between the checkbox and the radiobox when selecting it.
|
// Switch focus in between the checkbox and the radiobox when selecting it.
|
||||||
bool OnEvent(ftxui::Event event) override {
|
bool OnEvent(ftxui::Event event) override {
|
||||||
const bool show_old = show_;
|
const bool show_old = open_();
|
||||||
const int selected_old = *selected_;
|
const int selected_old = selected_();
|
||||||
const bool handled = ComponentBase::OnEvent(event);
|
const bool handled = ComponentBase::OnEvent(event);
|
||||||
|
|
||||||
if (!show_old && show_) {
|
if (!show_old && open_()) {
|
||||||
radiobox_->TakeFocus();
|
radiobox_->TakeFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected_old != *selected_) {
|
if (selected_old != selected_()) {
|
||||||
checkbox_->TakeFocus();
|
checkbox_->TakeFocus();
|
||||||
show_ = false;
|
open_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FillDefault() {
|
||||||
|
open_ = std::move(checkbox.checked);
|
||||||
|
selected_ = std::move(radiobox.selected);
|
||||||
|
checkbox.checked = &*open_;
|
||||||
|
radiobox.selected = &*selected_;
|
||||||
|
|
||||||
|
if (!checkbox.transform) {
|
||||||
|
checkbox.transform = [](const EntryState& s) {
|
||||||
|
auto prefix = text(s.state ? "↓ " : "→ "); // NOLINT
|
||||||
|
auto t = text(s.label);
|
||||||
|
if (s.active) {
|
||||||
|
t |= bold;
|
||||||
|
}
|
||||||
|
if (s.focused) {
|
||||||
|
t |= inverted;
|
||||||
|
}
|
||||||
|
return hbox({prefix, t});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transform) {
|
||||||
|
transform = [](bool open, Element checkbox_element,
|
||||||
|
Element radiobox_element) {
|
||||||
|
if (open) {
|
||||||
|
const int max_height = 12;
|
||||||
|
return vbox({
|
||||||
|
checkbox_element,
|
||||||
|
separator(),
|
||||||
|
radiobox_element | vscroll_indicator | frame |
|
||||||
|
size(HEIGHT, LESS_THAN, max_height),
|
||||||
|
}) |
|
||||||
|
border;
|
||||||
|
}
|
||||||
|
return vbox({checkbox_element, filler()}) | border;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConstStringListRef entries_;
|
Ref<bool> open_;
|
||||||
bool show_ = false;
|
Ref<int> selected_;
|
||||||
int* selected_;
|
|
||||||
std::string title_;
|
|
||||||
Component checkbox_;
|
Component checkbox_;
|
||||||
Component radiobox_;
|
Component radiobox_;
|
||||||
};
|
};
|
||||||
|
|
||||||
return Make<Impl>(entries, selected);
|
return Make<Impl>(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ftxui
|
} // namespace ftxui
|
||||||
|
@ -848,13 +848,13 @@ void ScreenInteractive::Draw(Component component) {
|
|||||||
reset_cursor_position.clear();
|
reset_cursor_position.clear();
|
||||||
|
|
||||||
if (dy != 0) {
|
if (dy != 0) {
|
||||||
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
|
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
|
||||||
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
|
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dx != 0) {
|
if (dx != 0) {
|
||||||
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
|
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
|
||||||
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
|
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor_.shape == Cursor::Hidden) {
|
if (cursor_.shape == Cursor::Hidden) {
|
||||||
|
Loading…
Reference in New Issue
Block a user