1 #pragma once
2 #ifndef CATA_SRC_UI_H
3 #define CATA_SRC_UI_H
4 
5 #include <functional>
6 #include <initializer_list>
7 #include <iosfwd>
8 #include <map>
9 #include <memory>
10 #include <new>
11 #include <string>
12 #include <type_traits>
13 #include <utility>
14 #include <vector>
15 
16 #include "color.h"
17 #include "cuboid_rectangle.h"
18 #include "cursesdef.h"
19 #include "input.h"
20 #include "memory_fast.h"
21 #include "optional.h"
22 #include "pimpl.h"
23 #include "point.h"
24 #include "string_formatter.h"
25 
26 class translation;
27 
28 ////////////////////////////////////////////////////////////////////////////////////
29 /**
30  * uilist constants
31  */
32 const int UILIST_ERROR = -1024;
33 const int UILIST_WAIT_INPUT = -1025;
34 const int UILIST_UNBOUND = -1026;
35 const int UILIST_CANCEL = -1027;
36 const int UILIST_TIMEOUT = -1028;
37 const int UILIST_ADDITIONAL = -1029;
38 const int MENU_AUTOASSIGN = -1;
39 
40 class string_input_popup;
41 class ui_adaptor;
42 
43 catacurses::window new_centered_win( int nlines, int ncols );
44 
45 /**
46  * mvwzstr: line of text with horizontal offset and color
47  */
48 
49 struct mvwzstr {
50     int left = 0;
51     nc_color color = c_unset;
52     std::string txt;
53     int sym = 0;
54 };
55 
56 /**
57  * uilist_entry: entry line for uilist
58  */
59 struct uilist_entry {
60     int retval;                 // return this int
61     bool enabled;               // darken, and forbid scrolling if hilight_disabled is false
62     bool force_color = false;   // Never darken this option
63     // cata::nullopt: automatically assign an unassigned hotkey
64     // input_event(): disable hotkey
65     cata::optional<input_event> hotkey;
66     std::string txt;            // what it says on the tin
67     std::string desc;           // optional, possibly longer, description
68     std::string ctxt;           // second column text
69     nc_color hotkey_color;
70     nc_color text_color;
71     mvwzstr extratxt;
72 
73     // In the following constructors, int K only support letters (a-z, A-Z) and
74     // digits (0-9), MENU_AUTOASSIGN, and 0 or ' ' (disable hotkey). Other
75     // values may not work under keycode mode.
76     explicit uilist_entry( const std::string &T );
77     uilist_entry( const std::string &T, const std::string &D );
78     uilist_entry( const std::string &T, int K );
79     uilist_entry( const std::string &T, const cata::optional<input_event> &K );
80     uilist_entry( int R, bool E, int K, const std::string &T );
81     uilist_entry( int R, bool E, const cata::optional<input_event> &K,
82                   const std::string &T );
83     uilist_entry( int R, bool E, int K, const std::string &T, const std::string &D );
84     uilist_entry( int R, bool E, int K, const std::string &T, const std::string &D,
85                   const std::string &C );
86     uilist_entry( int R, bool E, const cata::optional<input_event> &K,
87                   const std::string &T, const std::string &D,
88                   const std::string &C );
89     uilist_entry( int R, bool E, int K, const std::string &T,
90                   const nc_color &H, const nc_color &C );
91     template<typename Enum, typename... Args,
92              typename = std::enable_if_t<std::is_enum<Enum>::value>>
uilist_entryuilist_entry93     explicit uilist_entry( Enum e, Args && ... args ) :
94         uilist_entry( static_cast<int>( e ), std::forward<Args>( args )... )
95     {}
96 
97     inclusive_rectangle<point> drawn_rect;
98 };
99 
100 /**
101  * Generic multi-function callback for highlighted items, key presses, and window control. Example:
102  *
103  * class monmenu_cb: public uilist_callback {
104  *   public:
105  *   bool key(int ch, int num, uilist * menu) {
106  *     if ( ch == 'k' && num > 0 ) {
107  *       std::vector<monster> * game_z=static_cast<std::vector<monster>*>(myptr);
108  *       game_z[num]->dead = true;
109  *     }
110  *   }
111  *   void refresh( uilist *menu ) {
112  *       if( menu->selected >= 0 && static_cast<size_t>( menu->selected ) < game_z.size() ) {
113  *           mvwprintz( menu->window, 0, 0, c_red, "( %s )",game_z[menu->selected]->name() );
114  *           wnoutrefresh( menu->window );
115  *       }
116  *   }
117  * }
118  * uilist monmenu;
119  * for( size_t i = 0; i < z.size(); ++i ) {
120  *   monmenu.addentry( z[i].name );
121  * }
122  * monmenu_cb * cb;
123  * cb->setptr( &g->z );
124  * monmenu.callback = cb
125  * monmenu.query();
126  *
127  */
128 class uilist;
129 
130 /**
131 * uilist::query() handles most input events first,
132 * and then passes the event to the callback if it can't handle it.
133 *
134 * The callback returninig a boolean false signifies that the callback can't "handle the
135 * event completely". This is unchanged before or after the PR.
136 * @{
137 */
138 class uilist_callback
139 {
140     public:
141 
142         /**
143         * After a new item is selected, call this once
144         */
select(uilist *)145         virtual void select( uilist * ) {}
key(const input_context &,const input_event &,int,uilist *)146         virtual bool key( const input_context &, const input_event &/*key*/, int /*entnum*/,
147                           uilist * ) {
148             return false;
149         }
refresh(uilist *)150         virtual void refresh( uilist * ) {}
151         virtual ~uilist_callback() = default;
152 };
153 /*@}*/
154 /**
155  * uilist: scrolling vertical list menu
156  */
157 
158 class uilist // NOLINT(cata-xy)
159 {
160     public:
161         class size_scalar
162         {
163             public:
164                 struct auto_assign {
165                 };
166 
167                 size_scalar &operator=( auto_assign );
168                 size_scalar &operator=( int val );
169                 size_scalar &operator=( const std::function<int()> &fun );
170 
171                 friend class uilist;
172 
173             private:
174                 std::function<int()> fun;
175         };
176 
177         class pos_scalar
178         {
179             public:
180                 struct auto_assign {
181                 };
182 
183                 pos_scalar &operator=( auto_assign );
184                 pos_scalar &operator=( int val );
185                 // the parameter to the function is the corresponding size vector element
186                 // (width for x, height for y)
187                 pos_scalar &operator=( const std::function<int( int )> &fun );
188 
189                 friend class uilist;
190 
191             private:
192                 std::function<int( int )> fun;
193         };
194 
195         uilist();
196         // query() will be called at the end of these convenience constructors
197         uilist( const std::string &msg, const std::vector<uilist_entry> &opts );
198         uilist( const std::string &msg, const std::vector<std::string> &opts );
199         uilist( const std::string &msg, std::initializer_list<const char *const> opts );
200 
201         ~uilist();
202 
203         // whether to report invalid color tag with debug message.
204         void color_error( bool report );
205 
206         void init();
207         void setup();
208         // initialize the window or reposition it after screen size change.
209         void reposition( ui_adaptor &ui );
210         void show();
211         bool scrollby( int scrollby );
212         void query( bool loop = true, int timeout = -1 );
213         void filterlist();
214         // In add_entry/add_entry_desc/add_entry_col, int k only support letters
215         // (a-z, A-Z) and digits (0-9), MENU_AUTOASSIGN, and 0 or ' ' (disable
216         // hotkey). Other values may not work under keycode mode.
217         void addentry( const std::string &str );
218         void addentry( int r, bool e, int k, const std::string &str );
219         void addentry( int r, bool e, const cata::optional<input_event> &k,
220                        const std::string &str );
221         template<typename K, typename ...Args>
addentry(const int r,const bool e,K && k,const char * const format,Args &&...args)222         void addentry( const int r, const bool e, K &&k, const char *const format, Args &&... args ) {
223             return addentry( r, e, std::forward<K>( k ),
224                              string_format( format, std::forward<Args>( args )... ) );
225         }
226         void addentry_desc( const std::string &str, const std::string &desc );
227         void addentry_desc( int r, bool e, int k, const std::string &str, const std::string &desc );
228         void addentry_col( int r, bool e, int k, const std::string &str, const std::string &column,
229                            const std::string &desc = "" );
230         void addentry_col( int r, bool e, const cata::optional<input_event> &k,
231                            const std::string &str, const std::string &column,
232                            const std::string &desc = std::string() );
233         void settext( const std::string &str );
234 
235         void reset();
236 
237         // Can be called before `uilist::query` to keep the uilist on UI stack after
238         // `uilist::query` returns. The returned `ui_adaptor` is cleared when the
239         // `uilist` is deconstructed.
240         //
241         // Example:
242         //     shared_ptr_fast<ui_adaptor> ui = menu.create_or_get_ui_adaptor();
243         //     menu.query()
244         //     // before `ui` or `menu` is deconstructed, the menu will always be
245         //     // displayed on screen.
246         shared_ptr_fast<ui_adaptor> create_or_get_ui_adaptor();
247         // NOLINTNEXTLINE(google-explicit-constructor)
248         operator int() const;
249 
250     private:
251         int scroll_amount_from_action( const std::string &action );
252         void apply_scrollbar();
253         // This function assumes it's being called from `query` and should
254         // not be made public.
255         void inputfilter();
256 
257     public:
258         // Parameters
259         // TODO change to setters
260         std::string title;
261         std::string text;
262         // basically the same as desc, except it doesn't change based on selection
263         std::string footer_text;
264         std::vector<uilist_entry> entries;
265 
266         std::string input_category;
267         std::vector<std::pair<std::string, translation>> additional_actions;
268 
269         nc_color border_color;
270         nc_color text_color;
271         nc_color title_color;
272         nc_color hilight_color;
273         nc_color hotkey_color;
274         nc_color disabled_color;
275 
276         uilist_callback *callback;
277 
278         pos_scalar w_x_setup;
279         pos_scalar w_y_setup;
280         size_scalar w_width_setup;
281         size_scalar w_height_setup;
282 
283         int textwidth = 0;
284 
285         size_scalar pad_left_setup;
286         size_scalar pad_right_setup;
287 
288         // Maximum number of lines to be allocated for displaying descriptions.
289         // This only serves as a hint, not a hard limit, so the number of lines
290         // may still exceed this value when for example the description text is
291         // long enough.
292         int desc_lines_hint = 0;
293         bool desc_enabled = false;
294 
295         bool filtering = false;
296         bool filtering_nocase = false;
297 
298         // return on selecting disabled entry, default false
299         bool allow_disabled = false;
300         // return UILIST_UNBOUND on keys unbound & unhandled by callback, default false
301         bool allow_anykey = false;
302         // return UILIST_CANCEL on "QUIT" action, default true
303         bool allow_cancel = true;
304         // return UILIST_ADDITIONAL if the input action is inside `additional_actions`
305         // and unhandled by callback, default false.
306         bool allow_additional = false;
307         bool hilight_disabled = false;
308 
309     private:
310         report_color_error _color_error = report_color_error::yes;
311 
312     public:
313         // Iternal states
314         // TODO make private
315         std::vector<std::string> textformatted;
316 
317         catacurses::window window;
318         int w_x = 0;
319         int w_y = 0;
320         int w_width = 0;
321         int w_height = 0;
322 
323         int pad_left = 0;
324         int pad_right = 0;
325 
326         int vshift = 0;
327 
328         int fselected = 0;
329 
330     private:
331         std::vector<int> fentries;
332         std::map<input_event, int, std::function<bool( const input_event &, const input_event & )>>
333         keymap { input_event::compare_type_mod_code };
334 
335         weak_ptr_fast<ui_adaptor> ui;
336 
337         std::unique_ptr<string_input_popup> filter_popup;
338         std::string filter;
339 
340         int max_entry_len = 0;
341         int max_column_len = 0;
342 
343         int vmax = 0;
344 
345         int desc_lines = 0;
346 
347         bool started = false;
348 
349         uilist_entry *find_entry_by_coordinate( const point &p );
350 
351     public:
352         // Results
353         // TODO change to getters
354         std::string ret_act;
355         input_event ret_evt;
356         int ret = 0;
357         int selected = 0;
358 };
359 
360 /**
361  * Callback for uilist that pairs menu entries with points
362  * When an entry is selected, view will be centered on the paired point
363  */
364 class pointmenu_cb : public uilist_callback
365 {
366     private:
367         struct impl_t;
368 
369         pimpl<impl_t> impl;
370     public:
371         explicit pointmenu_cb( const std::vector< tripoint > &pts );
372         ~pointmenu_cb() override;
373         void select( uilist *menu ) override;
374 };
375 
376 #endif // CATA_SRC_UI_H
377