Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Dropdown options with callback #826

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ current (development)
### Component
- Feature: Add support for `Input`'s insert mode. Add `InputOption::insert`
option. Added by @mingsheng13.
- Feature: Add `DropdownOption` to configure the dropdown. See #826.
- Bugfix/Breaking change: `Mouse transition`:
- Detect when the mouse move, as opposed to being pressed.
The Mouse::Moved motion was added.
Expand Down
1 change: 1 addition & 0 deletions examples/component/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ example(collapsible)
example(composition)
example(custom_loop)
example(dropdown)
example(dropdown_custom)
example(flexbox_gallery)
example(focus)
example(focus_cursor)
Expand Down
104 changes: 104 additions & 0 deletions examples/component/dropdown_custom.cpp
Original file line number Diff line number Diff line change
@@ -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,
}));
}
10 changes: 5 additions & 5 deletions examples/component/homescreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,11 +494,11 @@ int main() {
"Exit", [&] { screen.Exit(); }, ButtonOption::Animated());

auto main_container = Container::Vertical({
Container::Horizontal({
tab_selection,
exit_button,
}),
tab_content,
Container::Horizontal({
tab_selection,
exit_button,
}),
tab_content,
});

auto main_renderer = Renderer(main_container, [&] {
Expand Down
2 changes: 2 additions & 0 deletions include/ftxui/component/component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Component Radiobox(ConstStringListRef entries,
RadioboxOption options = {});

Component Dropdown(ConstStringListRef entries, int* selected);
Component Dropdown(DropdownOption options);

Component Toggle(ConstStringListRef entries, int* selected);

// General slider constructor:
Expand Down
15 changes: 15 additions & 0 deletions include/ftxui/component/component_options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,21 @@ struct WindowOptions {
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

#endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */
6 changes: 3 additions & 3 deletions src/ftxui/component/button.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {

// TODO(arthursonzogni): Consider posting the task to the main loop, instead
// of invoking it immediately.
on_click(); // May delete this.
on_click(); // May delete this.
}

bool OnEvent(Event event) override {
Expand All @@ -113,7 +113,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
}

if (event == Event::Return) {
OnClick(); // May delete this.
OnClick(); // May delete this.
return true;
}
return false;
Expand All @@ -130,7 +130,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Pressed) {
TakeFocus();
OnClick(); // May delete this.
OnClick(); // May delete this.
return true;
}

Expand Down
111 changes: 67 additions & 44 deletions src/ftxui/component/dropdown.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,79 +20,102 @@
/// @param entries The list of entries to display.
/// @param selected The index of the selected entry.
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:
Impl(ConstStringListRef entries, int* selected)
: entries_(entries), selected_(selected) {
CheckboxOption option;
option.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});
};
checkbox_ = Checkbox(&title_, &show_, option);
radiobox_ = Radiobox(entries_, selected_);
Impl(DropdownOption option) : DropdownOption(std::move(option)) {
FillDefault();
checkbox_ = Checkbox(checkbox);
radiobox_ = Radiobox(radiobox);

Add(Container::Vertical({
checkbox_,
Maybe(radiobox_, &show_),
Maybe(radiobox_, checkbox.checked),
}));
}

Element Render() override {
*selected_ = util::clamp(*selected_, 0, int(entries_.size()) - 1);
title_ = entries_[static_cast<size_t>(*selected_)];
if (show_) {
const int max_height = 12;
return vbox({
checkbox_->Render(),
separator(),
radiobox_->Render() | vscroll_indicator | frame |
size(HEIGHT, LESS_THAN, max_height),
}) |
border;
}
radiobox.selected =
util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1);
checkbox.label =
radiobox.entries[static_cast<size_t>(radiobox.selected())];

return vbox({
checkbox_->Render() | border,
filler(),
});
return transform(*open_, checkbox_->Render(), radiobox_->Render());
}

// Switch focus in between the checkbox and the radiobox when selecting it.
bool OnEvent(ftxui::Event event) override {
const bool show_old = show_;
const int selected_old = *selected_;
const bool show_old = open_();
const int selected_old = selected_();
const bool handled = ComponentBase::OnEvent(event);

if (!show_old && show_) {
if (!show_old && open_()) {
radiobox_->TakeFocus();
}

if (selected_old != *selected_) {
if (selected_old != selected_()) {
checkbox_->TakeFocus();
show_ = false;
open_ = false;
}

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,

Check failure on line 94 in src/ftxui/component/dropdown.cpp

View workflow job for this annotation

GitHub Actions / Tests (Linux GCC, ubuntu-latest, gcc, gcov)

declaration of ‘open’ shadows a member of ‘ftxui::Dropdown(DropdownOption)::Impl’ [-Werror=shadow]
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:
ConstStringListRef entries_;
bool show_ = false;
int* selected_;
std::string title_;
Ref<bool> open_;
Ref<int> selected_;
Component checkbox_;
Component radiobox_;
};

return Make<Impl>(entries, selected);
return Make<Impl>(option);
}

} // namespace ftxui
8 changes: 4 additions & 4 deletions src/ftxui/component/screen_interactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,13 +848,13 @@ void ScreenInteractive::Draw(Component component) {
reset_cursor_position.clear();

if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
}

if (dx != 0) {
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
}

if (cursor_.shape == Cursor::Hidden) {
Expand Down
Loading