mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 02:28:11 +08:00 
			
		
		
		
	Feature: Dropdown options with callback (#826)
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
		| @@ -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, | ||||||
|  |   })); | ||||||
|  | } | ||||||
| @@ -491,7 +491,10 @@ int main() { | |||||||
|       &tab_index); |       &tab_index); | ||||||
|  |  | ||||||
|   auto main_container = Container::Vertical({ |   auto main_container = Container::Vertical({ | ||||||
|  |       Container::Horizontal({ | ||||||
|           tab_selection, |           tab_selection, | ||||||
|  |           exit_button, | ||||||
|  |       }), | ||||||
|       tab_content, |       tab_content, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 */ | ||||||
|   | |||||||
| @@ -20,12 +20,64 @@ 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); | ||||||
|  |  | ||||||
|  |       Add(Container::Vertical({ | ||||||
|  |           checkbox_, | ||||||
|  |           Maybe(radiobox_, checkbox.checked), | ||||||
|  |       })); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Element Render() override { | ||||||
|  |       radiobox.selected = | ||||||
|  |           util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1); | ||||||
|  |       checkbox.label = | ||||||
|  |           radiobox.entries[static_cast<size_t>(radiobox.selected())]; | ||||||
|  |  | ||||||
|  |       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 = open_(); | ||||||
|  |       const int selected_old = selected_(); | ||||||
|  |       const bool handled = ComponentBase::OnEvent(event); | ||||||
|  |  | ||||||
|  |       if (!show_old && open_()) { | ||||||
|  |         radiobox_->TakeFocus(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (selected_old != selected_()) { | ||||||
|  |         checkbox_->TakeFocus(); | ||||||
|  |         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 prefix = text(s.state ? "↓ " : "→ ");  // NOLINT | ||||||
|           auto t = text(s.label); |           auto t = text(s.label); | ||||||
|           if (s.active) { |           if (s.active) { | ||||||
| @@ -36,63 +88,34 @@ Component Dropdown(ConstStringListRef entries, int* selected) { | |||||||
|           } |           } | ||||||
|           return hbox({prefix, t}); |           return hbox({prefix, t}); | ||||||
|         }; |         }; | ||||||
|       checkbox_ = Checkbox(&title_, &show_, option); |  | ||||||
|       radiobox_ = Radiobox(entries_, selected_); |  | ||||||
|  |  | ||||||
|       Add(Container::Vertical({ |  | ||||||
|           checkbox_, |  | ||||||
|           Maybe(radiobox_, &show_), |  | ||||||
|       })); |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|     Element Render() override { |       if (!transform) { | ||||||
|       *selected_ = util::clamp(*selected_, 0, int(entries_.size()) - 1); |         transform = [](bool open, Element checkbox_element, | ||||||
|       title_ = entries_[static_cast<size_t>(*selected_)]; |                        Element radiobox_element) { | ||||||
|       if (show_) { |           if (open) { | ||||||
|             const int max_height = 12; |             const int max_height = 12; | ||||||
|             return vbox({ |             return vbox({ | ||||||
|                    checkbox_->Render(), |                        checkbox_element, | ||||||
|                        separator(), |                        separator(), | ||||||
|                    radiobox_->Render() | vscroll_indicator | frame | |                        radiobox_element | vscroll_indicator | frame | | ||||||
|                            size(HEIGHT, LESS_THAN, max_height), |                            size(HEIGHT, LESS_THAN, max_height), | ||||||
|                    }) | |                    }) | | ||||||
|                    border; |                    border; | ||||||
|           } |           } | ||||||
|  |           return vbox({checkbox_element, filler()}) | border; | ||||||
|       return vbox({ |         }; | ||||||
|           checkbox_->Render() | border, |  | ||||||
|           filler(), |  | ||||||
|       }); |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|     // 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 handled = ComponentBase::OnEvent(event); |  | ||||||
|  |  | ||||||
|       if (!show_old && show_) { |  | ||||||
|         radiobox_->TakeFocus(); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (selected_old != *selected_) { |  | ||||||
|         checkbox_->TakeFocus(); |  | ||||||
|         show_ = false; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return handled; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|    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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 James
					James