forked from cataclysmbnteam/Cataclysm-BN
-
Notifications
You must be signed in to change notification settings - Fork 0
/
inventory_ui.h
727 lines (594 loc) · 24.8 KB
/
inventory_ui.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
#pragma once
#ifndef CATA_SRC_INVENTORY_UI_H
#define CATA_SRC_INVENTORY_UI_H
#include <cassert>
#include <climits>
#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <array>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "color.h"
#include "cursesdef.h"
#include "input.h"
#include "item_handling_util.h"
#include "item_location.h"
#include "memory_fast.h"
#include "pimpl.h"
#include "units.h"
class Character;
class item;
class item_category;
class player;
class string_input_popup;
struct tripoint;
class ui_adaptor;
using excluded_stack = std::pair<const item *, int>;
using excluded_stacks = std::map<const item *, int>;
enum class navigation_mode : int {
ITEM = 0,
CATEGORY
};
enum class scroll_direction : int {
FORWARD = 1,
BACKWARD = -1
};
struct navigation_mode_data;
struct inventory_input;
class inventory_entry
{
public:
std::vector<item_location> locations;
size_t chosen_count = 0;
int custom_invlet = INT_MIN;
std::string cached_name;
inventory_entry() = default;
inventory_entry( const item_category *custom_category ) :
custom_category( custom_category )
{}
// Copy with new category. Used to copy entries into the "selected"
// category when they are selected.
inventory_entry( const inventory_entry &entry, const item_category *custom_category ) :
inventory_entry( entry ) {
this->custom_category = custom_category;
}
inventory_entry( const std::vector<item_location> &locations,
const item_category *custom_category = nullptr,
bool enabled = true ) :
locations( locations ),
custom_category( custom_category ),
enabled( enabled )
{}
bool operator==( const inventory_entry &other ) const;
bool operator!=( const inventory_entry &other ) const {
return !( *this == other );
}
operator bool() const {
return !is_null();
}
/** Whether the entry is null (dummy) */
bool is_null() const {
return get_category_ptr() == nullptr;
}
/**
* Whether the entry is an item.
*/
bool is_item() const {
return !locations.empty();
}
/** Whether the entry is a category */
bool is_category() const {
return !is_null() && !is_item();
}
/** Whether the entry can be selected */
bool is_selectable() const {
return is_item() && enabled;
}
const item_location &any_item() const {
assert( !locations.empty() );
return locations.front();
}
/** Pointer to first item in relevant stack on character. */
const item *item_stack_on_character() const {
assert( !locations.empty() );
return locations.front().get_item();
}
size_t get_stack_size() const {
return locations.size();
}
int get_total_charges() const;
int get_selected_charges() const;
size_t get_available_count() const;
const item_category *get_category_ptr() const;
int get_invlet() const;
nc_color get_invlet_color() const;
void update_cache();
private:
const item_category *custom_category = nullptr;
bool enabled = true;
};
class inventory_selector_preset
{
public:
inventory_selector_preset();
virtual ~inventory_selector_preset() = default;
/** Does this entry satisfy the basic preset conditions? */
virtual bool is_shown( const item_location & ) const {
return true;
}
/**
* The reason why this entry cannot be selected.
* @return Either the reason of denial or empty string if it's accepted.
*/
virtual std::string get_denial( const item_location & ) const {
return std::string();
}
/** Whether the first item is considered to go before the second. */
virtual bool sort_compare( const inventory_entry &lhs, const inventory_entry &rhs ) const;
/** Color that will be used to display the entry string. */
virtual nc_color get_color( const inventory_entry &entry ) const;
std::string get_denial( const inventory_entry &entry ) const;
/** Text in the cell */
std::string get_cell_text( const inventory_entry &entry, size_t cell_index ) const;
/** @return Whether the cell is a stub */
bool is_stub_cell( const inventory_entry &entry, size_t cell_index ) const;
/** Number of cells in the preset. */
size_t get_cells_count() const {
return cells.size();
}
/** Whether items should make new stacks if components differ */
bool get_checking_components() const {
return check_components;
}
virtual std::function<bool( const inventory_entry & )> get_filter( const std::string &filter )
const;
protected:
/** Text of the first column (default: item name) */
virtual std::string get_caption( const inventory_entry &entry ) const;
/**
* Append a new cell to the preset.
* @param func The function that returns text for the cell.
* @param title Title of the cell.
* @param stub The cell won't be "revealed" if it contains only this value
*/
void append_cell( const std::function<std::string( const item_location & )> &func,
const std::string &title = std::string(),
const std::string &stub = std::string() );
void append_cell( const std::function<std::string( const inventory_entry & )> &func,
const std::string &title = std::string(),
const std::string &stub = std::string() );
bool check_components = false;
private:
class cell_t
{
public:
cell_t( const std::function<std::string( const inventory_entry & )> &func,
const std::string &title, const std::string &stub ) :
title( title ),
stub( stub ),
func( func ) {}
std::string get_text( const inventory_entry &entry ) const;
std::string title;
std::string stub;
private:
std::function<std::string( const inventory_entry & )> func;
};
std::vector<cell_t> cells;
};
const inventory_selector_preset default_preset;
class inventory_column
{
public:
inventory_column( const inventory_selector_preset &preset = default_preset ) : preset( preset ) {
cells.resize( preset.get_cells_count() );
}
virtual ~inventory_column() = default;
bool empty() const {
return entries.empty();
}
/**
* Can this column be activated?
* @return Whether the column contains selectable entries.
* Note: independent from 'allows_selecting'
*/
virtual bool activatable() const;
/** Is this column visible? */
bool visible() const {
return !empty() && visibility && preset.get_cells_count() > 0;
}
/**
* Does this column allow selecting?
* "Cosmetic" columns (list of selected items) can explicitly prohibit selecting.
* Note: independent from 'activatable'
*/
virtual bool allows_selecting() const {
return true;
}
size_t page_index() const {
return page_of( page_offset );
}
size_t pages_count() const {
return page_of( entries.size() + entries_per_page - 1 );
}
bool has_available_choices() const;
bool is_selected( const inventory_entry &entry ) const;
/**
* Does this entry belong to the selected category?
* When @ref navigation_mode::ITEM is used it's equivalent to @ref is_selected().
*/
bool is_selected_by_category( const inventory_entry &entry ) const;
const inventory_entry &get_selected() const;
std::vector<inventory_entry *> get_all_selected() const;
std::vector<inventory_entry *> get_entries(
const std::function<bool( const inventory_entry &entry )> &filter_func ) const;
inventory_entry *find_by_invlet( int invlet ) const;
void draw( const catacurses::window &win, point pos ) const;
void add_entry( const inventory_entry &entry );
void move_entries_to( inventory_column &dest );
void clear();
void set_stack_favorite( const item_location &location, bool favorite );
/** Selects the specified location. */
bool select( const item_location &loc );
/**
* Change the selection.
* @param new_index Index of the entry to select.
* @param dir If the entry is not selectable, move in the specified direction
*/
void select( size_t new_index, scroll_direction dir );
size_t get_selected_index() {
return selected_index;
}
void set_multiselect( bool multiselect ) {
this->multiselect = multiselect;
}
void set_visibility( bool visibility ) {
this->visibility = visibility;
}
void set_width( size_t new_width, const std::vector<inventory_column *> &all_columns );
void set_height( size_t new_height );
size_t get_width() const;
size_t get_height() const;
/** Expands the column to fit the new entry. */
void expand_to_fit( const inventory_entry &entry );
/** Resets width to original (unchanged). */
virtual void reset_width( const std::vector<inventory_column *> &all_columns );
/** Returns next custom inventory letter. */
int reassign_custom_invlets( const player &p, int min_invlet, int max_invlet );
/** Reorder entries, repopulate titles, adjust to the new height. */
virtual void prepare_paging( const std::string &filter = "" );
/**
* Event handlers
*/
virtual void on_input( const inventory_input &input );
/** The entry has been changed. */
virtual void on_change( const inventory_entry & ) {}
/** The column has been activated. */
virtual void on_activate() {
active = true;
}
/** The column has been deactivated. */
virtual void on_deactivate() {
active = false;
}
/** Selection mode has been changed. */
virtual void on_mode_change( navigation_mode mode ) {
this->mode = mode;
}
void set_filter( const std::string &filter );
protected:
struct entry_cell_cache_t {
bool assigned = false;
nc_color color = c_unset;
std::string denial;
std::vector<std::string> text;
};
/**
* Move the selection.
*/
void move_selection( scroll_direction dir );
void move_selection_page( scroll_direction dir );
size_t next_selectable_index( size_t index, scroll_direction dir ) const;
size_t page_of( size_t index ) const;
size_t page_of( const inventory_entry &entry ) const;
/**
* Indentation of the entry.
* @param entry The entry to check
* @returns Either left indent when it's zero, or a gap between cells.
*/
size_t get_entry_indent( const inventory_entry &entry ) const;
/**
* Overall cell width.
* If corresponding cell is not empty (its width is greater than zero),
* then a value returned by inventory_column::get_entry_indent() is added to the result.
*/
size_t get_entry_cell_width( size_t index, size_t cell_index ) const;
size_t get_entry_cell_width( const inventory_entry &entry, size_t cell_index ) const;
/** Sum of the cell widths */
size_t get_cells_width() const;
entry_cell_cache_t make_entry_cell_cache( const inventory_entry &entry ) const;
const entry_cell_cache_t &get_entry_cell_cache( size_t index ) const;
const inventory_selector_preset &preset;
std::vector<inventory_entry> entries;
std::vector<inventory_entry> entries_unfiltered;
navigation_mode mode = navigation_mode::ITEM;
bool active = false;
bool multiselect = false;
bool paging_is_valid = false;
bool visibility = true;
size_t selected_index = 0;
size_t page_offset = 0;
size_t entries_per_page = std::numeric_limits<size_t>::max();
size_t height = std::numeric_limits<size_t>::max();
size_t reserved_width = 0;
private:
struct cell_t {
size_t current_width = 0; /// Current cell widths (can be affected by set_width())
size_t real_width = 0; /// Minimal cell widths (to embrace all the entries nicely)
bool visible() const {
return current_width > 0;
}
/** @return Gap before the cell. Negative value means the cell is shrunk */
int gap() const {
return current_width - real_width;
}
};
std::vector<cell_t> cells;
mutable std::vector<entry_cell_cache_t> entries_cell_cache;
/** @return Number of visible cells */
size_t visible_cells() const;
};
class selection_column : public inventory_column
{
public:
selection_column( const std::string &id, const std::string &name );
~selection_column() override;
bool activatable() const override {
return inventory_column::activatable() && pages_count() > 1;
}
bool allows_selecting() const override {
return false;
}
void reset_width( const std::vector<inventory_column *> &all_columns ) override;
void prepare_paging( const std::string &filter = "" ) override;
void on_change( const inventory_entry &entry ) override;
void on_mode_change( navigation_mode ) override {
// Intentionally ignore mode change.
}
private:
const pimpl<item_category> selected_cat;
inventory_entry last_changed;
};
class inventory_selector
{
public:
inventory_selector( player &u, const inventory_selector_preset &preset = default_preset );
virtual ~inventory_selector();
/** These functions add items from map / vehicles. */
void add_character_items( Character &character );
void add_map_items( const tripoint &target );
void add_vehicle_items( const tripoint &target );
void add_nearby_items( int radius = 1 );
/** Remove all items */
void clear_items();
/** Assigns a title that will be shown on top of the menu. */
void set_title( const std::string &title ) {
this->title = title;
}
/** Assigns a hint. */
void set_hint( const std::string &hint ) {
this->hint = hint;
}
/** Specify whether the header should show stats (weight and volume). */
void set_display_stats( bool display_stats ) {
this->display_stats = display_stats;
}
/** @return true when the selector is empty. */
bool empty() const;
/** @return true when there are enabled entries to select. */
bool has_available_choices() const;
/** Apply filter string to all columns */
void set_filter( const std::string &str );
/** Get last filter string set by set_filter or entered by player */
std::string get_filter() const;
// An array of cells for the stat lines. Example: ["Weight (kg)", "10", "/", "20"].
using stat = std::array<std::string, 4>;
using stats = std::array<stat, 2>;
bool keep_open = false;
protected:
player &u;
const inventory_selector_preset &preset;
/**
* The input context for navigation, already contains some actions for movement.
*/
input_context ctxt;
const item_category *naturalize_category( const item_category &category,
const tripoint &pos );
void add_entry( inventory_column &target_column,
std::vector<item_location> &&locations,
const item_category *custom_category = nullptr );
void add_item( inventory_column &target_column,
item_location &&location,
const item_category *custom_category = nullptr );
void add_items( inventory_column &target_column,
const std::function<item_location( item * )> &locator,
const std::vector<std::list<item *>> &stacks,
const item_category *custom_category = nullptr );
inventory_input get_input();
/** Given an action from the input_context, try to act according to it. */
void on_input( const inventory_input &input );
/** Entry has been changed */
void on_change( const inventory_entry &entry );
shared_ptr_fast<ui_adaptor> create_or_get_ui_adaptor();
size_t get_layout_width() const;
size_t get_layout_height() const;
void set_filter();
/** Tackles screen overflow */
virtual void rearrange_columns( size_t client_width );
static stats get_weight_and_volume_stats(
units::mass weight_carried, units::mass weight_capacity,
const units::volume &volume_carried, const units::volume &volume_capacity );
/** Get stats to display in top right.
*
* By default, computes volume/weight numbers for @c u */
virtual stats get_raw_stats() const;
std::vector<std::string> get_stats() const;
std::pair<std::string, nc_color> get_footer( navigation_mode m ) const;
size_t get_header_height() const;
size_t get_header_min_width() const;
size_t get_footer_min_width() const;
/** @return an entry from all entries by its invlet */
inventory_entry *find_entry_by_invlet( int invlet ) const;
const std::vector<inventory_column *> &get_all_columns() const {
return columns;
}
std::vector<inventory_column *> get_visible_columns() const;
private:
// These functions are called from resizing/redraw callbacks of ui_adaptor
// and should not be made protected or public.
void prepare_layout( size_t client_width, size_t client_height );
void prepare_layout();
void resize_window( int width, int height );
void refresh_window() const;
void draw_header( const catacurses::window &w ) const;
void draw_footer( const catacurses::window &w ) const;
void draw_columns( const catacurses::window &w ) const;
void draw_frame( const catacurses::window &w ) const;
public:
/**
* Select a location
* @param loc Location to select
* @return true on success.
*/
bool select( const item_location &loc );
const inventory_entry &get_selected() {
return get_active_column().get_selected();
}
void select_position( std::pair<size_t, size_t> position ) {
prepare_layout();
set_active_column( position.first );
get_active_column().select( position.second, scroll_direction::BACKWARD );
}
std::pair<size_t, size_t> get_selection_position() {
std::pair<size_t, size_t> position;
position.first = active_column_index;
position.second = get_active_column().get_selected_index();
return position;
}
inventory_column &get_column( size_t index ) const;
inventory_column &get_active_column() const {
return get_column( active_column_index );
}
void set_active_column( size_t index );
protected:
size_t get_columns_width( const std::vector<inventory_column *> &columns ) const;
/** @return Percentage of the window occupied by columns */
double get_columns_occupancy_ratio( size_t client_width ) const;
/** @return Do the visible columns need to be center-aligned */
bool are_columns_centered( size_t client_width ) const;
/** @return Are visible columns wider than available width */
bool is_overflown( size_t client_width ) const;
bool is_active_column( const inventory_column &column ) const {
return &column == &get_active_column();
}
void append_column( inventory_column &column );
/**
* Activates either previous or next column.
* @param dir Forward - next column, backward - previous.
*/
void toggle_active_column( scroll_direction dir );
void refresh_active_column() {
if( !get_active_column().activatable() ) {
toggle_active_column( scroll_direction::FORWARD );
}
}
void toggle_navigation_mode();
const navigation_mode_data &get_navigation_data( navigation_mode m ) const;
private:
catacurses::window w_inv;
weak_ptr_fast<ui_adaptor> ui;
std::unique_ptr<string_input_popup> spopup;
std::vector<inventory_column *> columns;
std::string title;
std::string hint;
size_t active_column_index;
std::list<item_category> categories;
navigation_mode mode;
inventory_column own_inv_column; // Column for own inventory items
inventory_column own_gear_column; // Column for own gear (weapon, armor) items
inventory_column map_column; // Column for map and vehicle items
const int border = 1; // Width of the window border
std::string filter;
bool is_empty = true;
bool display_stats = true;
public:
std::string action_bound_to_key( char key ) const;
/** Returns all keys in the current context which are bound to an action. Warning: may contain duplicates. */
std::vector<char> all_bound_keys( ) const;
};
inventory_selector::stat display_stat( const std::string &caption, int cur_value, int max_value,
const std::function<std::string( int )> &disp_func );
class inventory_pick_selector : public inventory_selector
{
public:
inventory_pick_selector( player &p,
const inventory_selector_preset &preset = default_preset ) :
inventory_selector( p, preset ) {}
item_location execute();
};
class inventory_multiselector : public inventory_selector
{
public:
inventory_multiselector( player &p, const inventory_selector_preset &preset = default_preset,
const std::string &selection_column_title = "" );
protected:
void rearrange_columns( size_t client_width ) override;
private:
std::unique_ptr<inventory_column> selection_col;
};
class inventory_compare_selector : public inventory_multiselector
{
public:
inventory_compare_selector( player &p );
std::pair<const item *, const item *> execute();
protected:
std::vector<const item *> compared;
void toggle_entry( inventory_entry *entry );
};
// This and inventory_drop_selectors should probably both inherit from a higher-abstraction "action selector".
// Should accept a function to calculate dummy values.
class inventory_iuse_selector : public inventory_multiselector
{
public:
using GetStats = std::function<stats( const std::map<const item *, int> & )>;
inventory_iuse_selector( player &p,
const std::string &selector_title,
const inventory_selector_preset &preset = default_preset,
const GetStats & = {} );
std::list<iuse_location> execute();
protected:
stats get_raw_stats() const override;
void set_chosen_count( inventory_entry &entry, size_t count );
private:
GetStats get_stats;
std::map<const item *, std::vector<iuse_location>> to_use;
};
class inventory_drop_selector : public inventory_multiselector
{
public:
inventory_drop_selector( player &p,
const inventory_selector_preset &preset = default_preset );
drop_locations execute();
protected:
stats get_raw_stats() const override;
/** Toggle item dropping */
void set_chosen_count( inventory_entry &entry, size_t count );
void process_selected( int &count, const std::vector<inventory_entry *> &selected );
private:
excluded_stacks dropping;
};
#endif // CATA_SRC_INVENTORY_UI_H