1 /* 2 * Copyright (C) 2016-2020 by the Widelands Development Team 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 * 18 */ 19 20 #ifndef WL_UI_BASIC_DROPDOWN_H 21 #define WL_UI_BASIC_DROPDOWN_H 22 23 #include <deque> 24 #include <memory> 25 26 #include "graphic/graphic.h" 27 #include "graphic/image.h" 28 #include "notifications/note_ids.h" 29 #include "notifications/notifications.h" 30 #include "ui_basic/box.h" 31 #include "ui_basic/button.h" 32 #include "ui_basic/listselect.h" 33 #include "ui_basic/panel.h" 34 35 namespace UI { 36 // We use this to make sure that only 1 dropdown is open at the same time. 37 struct NoteDropdown { 38 CAN_BE_SENT_AS_NOTE(NoteId::Dropdown) 39 40 int id; 41 NoteDropdownNoteDropdown42 explicit NoteDropdown(int init_id) : id(init_id) { 43 } 44 }; 45 46 /// The narrow textual dropdown omits the extra push button. 47 /// Use kPictorialMenu if you want to trigger an action without changing the menu button. 48 enum class DropdownType { kTextual, kTextualNarrow, kPictorial, kPictorialMenu }; 49 50 /// Implementation for a dropdown menu that lets the user select a value. 51 class BaseDropdown : public NamedPanel { 52 protected: 53 /// \param parent the parent panel 54 /// \param name a name so that we can reference the dropdown via Lua 55 /// \param x the x-position within 'parent' 56 /// \param y the y-position within 'parent' 57 /// \param list_w the dropdown's width 58 /// \param max_list_items the maximum number of items shown in the list before it starts 59 /// using a scrollbar \param button_dimension the width of the push button in textual 60 /// dropdowns. For pictorial dropdowns, this is both the width and the height of the button. 61 /// \param label a label to prefix to the selected entry on the display button. 62 /// \param type whether this is a textual or pictorial dropdown 63 /// \param style the style used for buttons and background 64 BaseDropdown(Panel* parent, 65 const std::string& name, 66 int32_t x, 67 int32_t y, 68 uint32_t list_w, 69 uint32_t max_list_items, 70 int button_dimension, 71 const std::string& label, 72 const DropdownType type, 73 PanelStyle style, 74 ButtonStyle button_style); 75 ~BaseDropdown() override; 76 77 public: 78 /// An entry was selected 79 boost::signals2::signal<void()> selected; 80 81 /// \return true if an element has been selected from the list 82 bool has_selection() const; 83 84 /// Sets a label that will be prefixed to the currently selected element's name 85 /// and displayed on the display button. 86 void set_label(const std::string& text); 87 88 /// Sets the image for the display button (for pictorial dropdowns). 89 void set_image(const Image* image); 90 91 /// Sets the tooltip for the display button. 92 void set_tooltip(const std::string& text); 93 94 /// Displays an error message on the button instead of the current selection. 95 void set_errored(const std::string& error_message); 96 97 /// Enables/disables the dropdown selection. 98 void set_enabled(bool on); 99 100 /// Whether the dropdown selection is enabled. is_enabled()101 bool is_enabled() const { 102 return is_enabled_; 103 } 104 105 /// Which visual style to use for disabled pictorial dropdowns. 106 void set_disable_style(UI::ButtonDisableStyle disable_style); 107 108 /// Whether the dropdown has no elements to select. empty()109 bool empty() { 110 return size() == 0; 111 } 112 113 /// Whether the dropdown has been opened by the user. 114 bool is_expanded() const; 115 116 /// Move the dropdown. The dropdown's position is relative to the parent in 117 /// pixels. 118 void set_pos(Vector2i point) override; 119 120 /// The number of elements listed in the dropdown. 121 uint32_t size() const; 122 123 /// Handle keypresses 124 bool handle_key(bool down, SDL_Keysym code) override; 125 126 /// Set maximum available height in the UI 127 void set_height(int height); 128 129 /// Toggle the list on and off and position the mouse on the button so that the dropdown won't 130 /// close on us. If this is a menu and nothing was selected yet, select the first item for easier 131 /// keyboard navigation. 132 void toggle(); 133 134 /// If 'open', show the list and position the mouse on the button so that the dropdown won't 135 /// close on us. If this is a menu and nothing was selected yet, select the first item for easier 136 /// keyboard navigation. If not 'open', close the list. 137 void set_list_visibility(bool open); 138 139 void set_size(int nw, int nh) override; 140 void set_desired_size(int w, int h) override; 141 142 /// Expand display button to make enough room for each entry's text. Call this before adding any 143 /// entries. 144 void set_autoexpand_display_button(); 145 notify_list_deleted()146 void notify_list_deleted() { 147 list_ = nullptr; 148 } 149 150 protected: 151 /// Add an element to the list 152 /// \param name the display name of the entry 153 /// \param value the index of the entry 154 /// \param pic an image to illustrate the entry. Can be nullptr for textual dropdowns. 155 /// \param select_this whether this element should be selected 156 /// \param tooltip_text a tooltip for this entry 157 /// \param hotkey a hotkey tip if any 158 /// 159 /// Text conventions: Title Case for the 'name', Sentence case for the 'tooltip_text' 160 void add(const std::string& name, 161 uint32_t value, 162 const Image* pic, 163 const bool select_this, 164 const std::string& tooltip_text, 165 const std::string& hotkey); 166 167 /// \return the index of the selected element 168 uint32_t get_selected() const; 169 170 /// Select the entry. Assumes that it exists. Does not trigger the 'selected' signal. 171 void select(uint32_t entry); 172 173 /// Removes all elements from the list. 174 void clear(); 175 176 /// Automatically collapses the list if the mouse gets too far away from the dropdown, or if it 177 /// loses focus. 178 void think() override; 179 180 private: 181 static void layout_if_alive(int); 182 void layout() override; 183 184 /// Updates the buttons 185 void update(); 186 187 /// Updates the title and tooltip of the display button and triggers a 'selected' signal. 188 void set_value(); 189 /// Toggles the dropdown list on and off and sends a notification if the list is visible 190 /// afterwards. 191 void toggle_list(); 192 /// Toggle the list closed if the dropdown is currently expanded. 193 void close(); 194 195 /// Returns true if the mouse pointer left the vicinity of the dropdown. 196 bool is_mouse_away() const; 197 198 /// Give each dropdown a unique ID 199 static int next_id_; 200 const int id_; 201 std::unique_ptr<Notifications::Subscriber<NoteDropdown>> dropdown_subscriber_; 202 std::unique_ptr<Notifications::Subscriber<GraphicResolutionChanged>> 203 graphic_resolution_changed_subscriber_; 204 205 // Dimensions 206 unsigned int max_list_items_; 207 unsigned int max_list_height_; 208 int list_offset_x_; 209 int list_offset_y_; 210 const int base_height_; 211 const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse 212 UI::Box button_box_; 213 UI::Button* push_button_; // Only used in textual dropdowns 214 UI::Button display_button_; 215 // The list needs to be a pointer for destruction, because we hook into the highest parent that 216 // we can get. 217 UI::Listselect<uintptr_t>* list_; 218 std::string label_; 219 std::string tooltip_; 220 uint32_t current_selection_; 221 DropdownType type_; 222 bool is_enabled_; 223 ButtonStyle button_style_; 224 bool autoexpand_display_button_; 225 }; 226 227 /// A dropdown menu that lets the user select a value of the datatype 'Entry'. 228 template <typename Entry> class Dropdown : public BaseDropdown { 229 public: 230 /// \param parent the parent panel 231 /// \param name a name so that we can reference the dropdown via Lua 232 /// \param x the x-position within 'parent' 233 /// \param y the y-position within 'parent' 234 /// \param list_w the dropdown's width 235 /// \param max_list_items the maximum number of items shown in the list before it starts 236 /// using a scrollbar \param button_dimension the width of the push button in textual 237 /// dropdowns. For pictorial dropdowns, this is both the width and the height of the button. 238 /// \param label a label to prefix to the selected entry on the display button. 239 /// \param type whether this is a textual or pictorial dropdown 240 /// \param style the style used for buttons and background 241 /// Text conventions: Title Case for all elements Dropdown(Panel * parent,const std::string & name,int32_t x,int32_t y,uint32_t list_w,uint32_t max_list_items,int button_dimension,const std::string & label,const DropdownType type,PanelStyle style,ButtonStyle button_style)242 Dropdown(Panel* parent, 243 const std::string& name, 244 int32_t x, 245 int32_t y, 246 uint32_t list_w, 247 uint32_t max_list_items, 248 int button_dimension, 249 const std::string& label, 250 const DropdownType type, 251 PanelStyle style, 252 ButtonStyle button_style) 253 : BaseDropdown(parent, 254 name, 255 x, 256 y, 257 list_w, 258 max_list_items, 259 button_dimension, 260 label, 261 type, 262 style, 263 button_style) { 264 } ~Dropdown()265 ~Dropdown() { 266 entry_cache_.clear(); 267 } 268 269 /// Add an element to the list 270 /// \param name the display name of the entry 271 /// \param value the value for the entry 272 /// \param pic an image to illustrate the entry. Can be nullptr in textual dropdowns 273 /// only. 274 /// \param select_this whether this element should be selected 275 /// \param tooltip_text a tooltip for this entry 276 /// \param hotkey a hotkey tip if any 277 void add(const std::string& name, 278 Entry value, 279 const Image* pic = nullptr, 280 const bool select_this = false, 281 const std::string& tooltip_text = std::string(), 282 const std::string& hotkey = std::string()) { 283 entry_cache_.push_back(std::unique_ptr<Entry>(new Entry(value))); 284 BaseDropdown::add(name, size(), pic, select_this, tooltip_text, hotkey); 285 } 286 287 /// \return the selected element get_selected()288 const Entry& get_selected() const { 289 assert(BaseDropdown::has_selection()); 290 return *entry_cache_[BaseDropdown::get_selected()]; 291 } 292 293 /// Select the entry if it exists. Does not trigger the 'selected' signal. select(const Entry & entry)294 void select(const Entry& entry) { 295 for (uint32_t i = 0; i < entry_cache_.size(); ++i) { 296 if (entry == *entry_cache_[i]) { 297 BaseDropdown::select(i); 298 } 299 } 300 } 301 302 /// Removes all elements from the list. clear()303 void clear() { 304 BaseDropdown::clear(); 305 entry_cache_.clear(); 306 } 307 308 private: 309 // Contains the actual elements. The BaseDropdown registers the indices only. 310 std::deque<std::unique_ptr<Entry>> entry_cache_; 311 }; 312 313 } // namespace UI 314 315 #endif // end of include guard: WL_UI_BASIC_DROPDOWN_H 316