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