1 #pragma once 2 #ifndef CATA_SRC_POPUP_H 3 #define CATA_SRC_POPUP_H 4 5 #include <cstddef> 6 #include <functional> 7 #include <iosfwd> 8 #include <memory> 9 #include <string> 10 #include <type_traits> 11 #include <vector> 12 13 #include "color.h" 14 #include "cursesdef.h" 15 #include "input.h" 16 #include "point.h" 17 #include "string_formatter.h" 18 19 class ui_adaptor; 20 21 /** 22 * UI class for displaying messages or querying player input with popups. 23 * 24 * Example: 25 * 26 * std::string action = query_popup() 27 * .context( "YESNOCANCEL" ) // input context to use 28 * .message( "%s", _( "Do you want to save before jumping into the lava?" ) ) 29 * .option( "YES" ) // yes, save before jumping 30 * .option( "NO" ) // no, don't save before jumping 31 * .option( "QUIT" ) // NOOO, I didn't mean to jump into lava! 32 * .cursor( 2 ) // default to the third option `QUIT` 33 * .query() // do the query, and get the input action and event 34 * .action // retrieve the input action 35 * 36 * Please refer to documentation of individual functions for detailed explanation. 37 **/ 38 class query_popup 39 { 40 public: 41 /** 42 * Query result returned by `query_once` and `query`. 43 * 44 * 'wait_input' indicates whether a selection is confirmed, either by 45 * "CONFIRM" action or by the option's corresponding action, or if the 46 * popup is canceled by "QUIT" action when `allow_cancel` is set to true. 47 * It is also false if `allow_anykey` is set to true, or when an error 48 * happened. It is always false when returned by `query`. 49 * 50 * `action` is the selected action, "QUIT" if `allow_cancel` is set to 51 * true and "QUIT" action occurs, or "ANY_INPUT" if `allow_anykey` is 52 * set to true and an unknown action occurs. In `query_once`, action 53 * can also be other actions such as "LEFT" or "RIGHT" which are used 54 * for moving the cursor. If an error occurred, such as when the popup 55 * is not properly set up, `action` will be "ERROR". 56 * 57 * `evt` is the actual `input_event` that triggers the action. Note that 58 * `action` and `evt` do NOT always correspond to each other. For 59 * example, if an action is selected by pressing return ("CONFIRM" action), 60 * `action` will be the selected action, while `evt` will correspond to 61 * "CONFIRM" action. 62 **/ 63 struct result { 64 result(); 65 result( bool wait_input, const std::string &action, const input_event &evt ); 66 67 bool wait_input; 68 std::string action; 69 input_event evt; 70 }; 71 72 /** 73 * Default construction. Note that context and options are not set in 74 * default construction, and calling `query_once` or `query` right after 75 * default construction will return { false, "ERROR", {} }. 76 **/ 77 query_popup(); 78 79 /** 80 * Specify the input context. In addition to being used to handle input 81 * actions, the input context will also be used to generate option text, 82 * so it should always be specified as long as at least one option is 83 * specified with `option()`, even if `query_once` or `query` are not 84 * called afterwards. 85 **/ 86 query_popup &context( const std::string &cat ); 87 /** 88 * Specify the query message. 89 */ 90 template <typename ...Args> message(const std::string & fmt,Args &&...args)91 query_popup &message( const std::string &fmt, Args &&... args ) { 92 assert_format( fmt, std::forward<Args>( args )... ); 93 invalidate_ui(); 94 text = string_format( fmt, std::forward<Args>( args )... ); 95 return *this; 96 } 97 template <typename ...Args> message(const char * const fmt,Args &&...args)98 query_popup &message( const char *const fmt, Args &&... args ) { 99 assert_format( fmt, std::forward<Args>( args )... ); 100 invalidate_ui(); 101 text = string_format( fmt, std::forward<Args>( args )... ); 102 return *this; 103 } 104 /** 105 * Like query_popup::message, but with waiting symbol prepended to the text. 106 **/ 107 template <typename ...Args> wait_message(const nc_color & bar_color,const std::string & fmt,Args &&...args)108 query_popup &wait_message( const nc_color &bar_color, const std::string &fmt, Args &&... args ) { 109 assert_format( fmt, std::forward<Args>( args )... ); 110 invalidate_ui(); 111 text = wait_text( string_format( fmt, std::forward<Args>( args )... ), bar_color ); 112 return *this; 113 } 114 template <typename ...Args> wait_message(const nc_color & bar_color,const char * const fmt,Args &&...args)115 query_popup &wait_message( const nc_color &bar_color, const char *const fmt, Args &&... args ) { 116 assert_format( fmt, std::forward<Args>( args )... ); 117 invalidate_ui(); 118 text = wait_text( string_format( fmt, std::forward<Args>( args )... ), bar_color ); 119 return *this; 120 } 121 template <typename ...Args> wait_message(const std::string & fmt,Args &&...args)122 query_popup &wait_message( const std::string &fmt, Args &&... args ) { 123 assert_format( fmt, std::forward<Args>( args )... ); 124 invalidate_ui(); 125 text = wait_text( string_format( fmt, std::forward<Args>( args )... ) ); 126 return *this; 127 } 128 template <typename ...Args> wait_message(const char * const fmt,Args &&...args)129 query_popup &wait_message( const char *const fmt, Args &&... args ) { 130 assert_format( fmt, std::forward<Args>( args )... ); 131 invalidate_ui(); 132 text = wait_text( string_format( fmt, std::forward<Args>( args )... ) ); 133 return *this; 134 } 135 /** 136 * Specify an action as an option. The action must be present in the 137 * supplied context, either locally or globally. The same applies to 138 * other `option` methods. 139 **/ 140 query_popup &option( const std::string &opt ); 141 /** 142 * Specify an action as an option, and a filter of allowed input events 143 * for this action. This is for compatibility with the "FORCE_CAPITAL_YN" 144 * option. 145 * Note that even if the input event is filtered, it will still select 146 * the respective dialog option, without closing the dialog. 147 */ 148 query_popup &option( const std::string &opt, 149 const std::function<bool( const input_event & )> &filter ); 150 /** 151 * Specify whether non-option actions can be returned. Mouse movement 152 * is always ignored regardless of this setting. 153 **/ 154 query_popup &allow_anykey( bool allow ); 155 /** 156 * Specify whether an implicit cancel option is allowed. This call does 157 * not list the cancel option in the UI. Use `option( "QUIT" )` instead 158 * to explicitly list cancel in the UI. 159 **/ 160 query_popup &allow_cancel( bool allow ); 161 /** 162 * Whether to show the popup on the top of the screen 163 **/ 164 query_popup &on_top( bool top ); 165 /** 166 * Whether to show the popup in `FULL_SCREEN_HEIGHT` and `FULL_SCREEN_WIDTH`. 167 **/ 168 query_popup &full_screen( bool full ); 169 /** 170 * Specify starting cursor position. 171 **/ 172 query_popup &cursor( size_t pos ); 173 /** 174 * Specify the default message color. 175 **/ 176 query_popup &default_color( const nc_color &d_color ); 177 /** 178 * Specify the desired keyboard mode. Used in keybindings menu to assign 179 * actions to input events of the approriate type of the parent UI. 180 */ 181 query_popup &preferred_keyboard_mode( keyboard_mode mode ); 182 183 /** 184 * Draw the UI. An input context should be provided using `context()` 185 * for this function to properly generate option text. 186 **/ 187 void show() const; 188 /** 189 * Query once and return the result. In order for this method to return 190 * valid results, the popup must either have at least one option, or 191 * have `allow_cancel` or `allow_anykey` set to true. Otherwise 192 * { false, "ERROR", {} } is returned. The same applies to `query`. 193 **/ 194 result query_once(); 195 /** 196 * Query until a valid action or an error happens and return the result. 197 */ 198 result query(); 199 200 protected: 201 /** 202 * Create or get a ui_adaptor on the UI stack to handle redrawing and 203 * resizing of the popup. 204 */ 205 std::shared_ptr<ui_adaptor> create_or_get_adaptor(); 206 207 private: 208 struct query_option { 209 query_option( const std::string &action, 210 const std::function<bool( const input_event & )> &filter ); 211 212 std::string action; 213 std::function<bool( const input_event & )> filter; 214 }; 215 216 std::string category; 217 std::string text; 218 std::vector<query_option> options; 219 size_t cur; 220 nc_color default_text_color; 221 bool anykey; 222 bool cancel; 223 bool ontop; 224 bool fullscr; 225 keyboard_mode pref_kbd_mode; 226 227 struct button { 228 button( const std::string &text, const point & ); 229 230 std::string text; 231 point pos; 232 }; 233 234 std::weak_ptr<ui_adaptor> adaptor; 235 236 // UI caches 237 mutable catacurses::window win; 238 mutable std::vector<std::string> folded_msg; 239 mutable std::vector<button> buttons; 240 241 static std::vector<std::vector<std::string>> fold_query( 242 const std::string &category, 243 keyboard_mode pref_kbd_mode, 244 const std::vector<query_option> &options, 245 int max_width, int horz_padding ); 246 void invalidate_ui() const; 247 void init() const; 248 249 template <typename ...Args> assert_format(const std::string &,Args &&...)250 static void assert_format( const std::string &, Args &&... ) { 251 static_assert( sizeof...( Args ) > 0, 252 "Format string should take at least one argument. " 253 "If your message is not a format string, " 254 "use `message( \"%s\", text )` instead." ); 255 } 256 257 static std::string wait_text( const std::string &text, const nc_color &bar_color ); 258 static std::string wait_text( const std::string &text ); 259 }; 260 261 /** 262 * Create a popup on the UI stack that gets displayed but receives no input itself. 263 * Call ui_manager::redraw() to redraw the popup along with other UIs on the stack, 264 * and refresh_display() to force refresh the display if not receiving input after 265 * redraw. The popup stays on the UI stack until its lifetime ends. 266 * 267 * Example: 268 * 269 * if( not_loaded ) { 270 * static_popup popup; 271 * popup.message( "Please wait…" ); 272 * while( loading ) { 273 * ui_manager::redraw(); 274 * refresh_display(); // force redraw since we're not receiving input here 275 * load_part(); 276 * } 277 * } 278 * // Popup removed from UI stack when going out of scope. 279 * // Note that the removal is not visible until the next time `ui_manager::redraw` 280 * // is called. 281 */ 282 class static_popup : public query_popup 283 { 284 public: 285 static_popup(); 286 287 private: 288 std::shared_ptr<ui_adaptor> ui; 289 }; 290 291 #endif // CATA_SRC_POPUP_H 292