Skip to content

Commit

Permalink
Feature: Dropdown options with callback (#826)
Browse files Browse the repository at this point in the history
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
  • Loading branch information
jeptechnology and ArthurSonzogni authored Apr 6, 2024
1 parent 2216f3a commit 3c9fa60
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 56 deletions.
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 @@ namespace ftxui {
/// @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

0 comments on commit 3c9fa60

Please sign in to comment.