From 60ba15ac07877bf78d30e809e270791cbe0247d4 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Mon, 18 Mar 2024 23:00:21 +0100 Subject: [PATCH] Add full customization. --- examples/component/CMakeLists.txt | 1 + examples/component/dropdown_custom.cpp | 69 +++++++++ include/ftxui/component/component.hpp | 5 +- include/ftxui/component/component_options.hpp | 20 +-- src/ftxui/component/dropdown.cpp | 132 +++++++++--------- 5 files changed, 150 insertions(+), 77 deletions(-) create mode 100644 examples/component/dropdown_custom.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 661fee06..5d80fd8d 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -11,6 +11,7 @@ example(collapsible) example(composition) example(custom_loop) example(dropdown) +example(dropdown_custom) example(flexbox_gallery) example(focus) example(focus_cursor) diff --git a/examples/component/dropdown_custom.cpp b/examples/component/dropdown_custom.cpp new file mode 100644 index 00000000..e07581d1 --- /dev/null +++ b/examples/component/dropdown_custom.cpp @@ -0,0 +1,69 @@ +// 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 // for basic_string, string, allocator +#include // 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 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 screen = ScreenInteractive::FitComponent(); + screen.Loop(Container::Horizontal({ + dropdown_1, + dropdown_2, + })); +} diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index fd4aec02..36eb605c 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -74,9 +74,8 @@ Component Radiobox(ConstStringListRef entries, int* selected_, RadioboxOption options = {}); -Component Dropdown(ConstStringListRef entries, - int* selected, - DropdownOption options = {}); +Component Dropdown(ConstStringListRef entries, int* selected); +Component Dropdown(DropdownOption options); Component Toggle(ConstStringListRef entries, int* selected); diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index 1f4990ae..881a9829 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -148,15 +148,6 @@ struct CheckboxOption { std::function on_change = [] {}; }; -/// @brief Option for the Dropdown component. -/// @ingroup component -struct DropdownOption { - bool border = true; - - // Observer: - /// Called when the user change the state. - std::function on_change = [] {}; -}; /// @brief Used to define style for the Input component. struct InputState { @@ -273,6 +264,17 @@ struct WindowOptions { std::function render; }; +/// @brief Option for the Dropdown component. +/// @ingroup component +/// A dropdown menu is a checkbox opening/closing a radiobox. +struct DropdownOption { + Ref open = false; + CheckboxOption checkbox; + RadioboxOption radiobox; + std::function + transform; +}; + } // namespace ftxui #endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */ diff --git a/src/ftxui/component/dropdown.cpp b/src/ftxui/component/dropdown.cpp index 7602334b..2ff5c22f 100644 --- a/src/ftxui/component/dropdown.cpp +++ b/src/ftxui/component/dropdown.cpp @@ -19,100 +19,102 @@ namespace ftxui { /// @ingroup component /// @param entries The list of entries to display. /// @param selected The index of the selected entry. -Component Dropdown(ConstStringListRef entries, int* selected, DropdownOption option) { - class Impl : public ComponentBase { +Component Dropdown(ConstStringListRef entries, int* selected) { + 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, DropdownOption dropDownOption) - : 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); - - RadioboxOption radioboxOption; - radioboxOption.on_change = dropDownOption.on_change; - - radiobox_ = Radiobox(entries_, selected_, radioboxOption); + 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), })); - - border_ = dropDownOption.border; } Element Render() override { - *selected_ = util::clamp(*selected_, 0, int(entries_.size()) - 1); - title_ = entries_[static_cast(*selected_)]; - if (show_) { - const int max_height = 12; - auto element = vbox({ - checkbox_->Render(), - separator(), - radiobox_->Render() | vscroll_indicator | frame | - size(HEIGHT, LESS_THAN, max_height), - }); - - if (border_) - { - element |= border; - } + radiobox.selected = + util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1); + checkbox.label = + radiobox.entries[static_cast(radiobox.selected())]; - return element; - } - - auto element = checkbox_->Render(); - - if (border_) - { - element |= border; - } - - return vbox({ - element, - 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 radiobox) { + if (open) { + const int max_height = 12; + return vbox({ + checkbox, + separator(), + radiobox | vscroll_indicator | frame | + size(HEIGHT, LESS_THAN, max_height), + }) | + border; + } + return vbox({checkbox, filler()}) | border; + }; + } + } + private: - ConstStringListRef entries_; - bool show_ = false; - bool border_ = true; - int* selected_; - std::string title_; + Ref open_; + Ref selected_; Component checkbox_; Component radiobox_; }; - return Make(entries, selected, option); + return Make(option); } } // namespace ftxui