Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions data/raw/keybindings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
"name": "Pan up",
"bindings": [ { "input_method": "keyboard_any", "key": "UP" } ]
},
{
"type": "keybinding",
"id": "SCROLL_FAVORITE",
"category": "SPELL_MENU",
"name": "Toggle spell as favorite",
"bindings": [
{ "input_method": "keyboard_char", "key": "*" },
{ "input_method": "keyboard_code", "key": "KEYPAD_MULTIPLY" },
{ "input_method": "keyboard_code", "key": "8", "mod": [ "shift" ] }
]
},
{
"type": "keybinding",
"id": "UILIST.DOWN",
Expand Down
71 changes: 69 additions & 2 deletions src/magic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,7 @@ void known_magic::serialize( JsonOut &json ) const
}
json.end_array();
json.member( "invlets", invlets );
json.member( "favorites", favorites );

json.end_object();
}
Expand All @@ -1804,6 +1805,7 @@ void known_magic::deserialize( const JsonObject &data )
}
}
data.read( "invlets", invlets );
data.read( "favorites", favorites );
}

bool known_magic::knows_spell( const std::string &sp ) const
Expand Down Expand Up @@ -2113,6 +2115,19 @@ std::vector<spell> Character::spells_known_of_class( const trait_id &spell_class
return ret;
}

static void reflesh_favorite( uilist *menu, std::vector<spell *> known_spells )
{
for( uilist_entry &entry : menu->entries ) {
if( get_player_character().magic->is_favorite( known_spells[entry.retval]->id() ) ) {
entry.extratxt.left = 0;
entry.extratxt.txt = _( "*" );
entry.extratxt.color = c_white;
} else {
entry.extratxt.txt = "";
}
}
}

class spellcasting_callback : public uilist_callback
{
private:
Expand All @@ -2124,14 +2139,14 @@ class spellcasting_callback : public uilist_callback
void draw_spell_info( const uilist *menu );
public:
// invlets reserved for special functions
const std::set<int> reserved_invlets{ 'I', '=' };
const std::set<int> reserved_invlets{ 'I', '=', '*' };
bool casting_ignore;

spellcasting_callback( std::vector<spell *> &spells,
bool casting_ignore ) : known_spells( spells ),
casting_ignore( casting_ignore ) {}
bool key( const input_context &ctxt, const input_event &event, int entnum,
uilist * /*menu*/ ) override {
uilist *menu ) override {
const std::string &action = ctxt.input_to_action( event );
if( action == "CAST_IGNORE" ) {
casting_ignore = !casting_ignore;
Expand All @@ -2155,16 +2170,21 @@ class spellcasting_callback : public uilist_callback
return true;
} else if( action == "SCROLL_UP_SPELL_MENU" || action == "SCROLL_DOWN_SPELL_MENU" ) {
scroll_pos += action == "SCROLL_DOWN_SPELL_MENU" ? 1 : -1;
} else if( action == "SCROLL_FAVORITE" ) {
get_player_character().magic->toggle_favorite( known_spells[entnum]->id() );
reflesh_favorite( menu, known_spells );
}
return false;
}

void refresh( uilist *menu ) override {
const std::string space( menu->pad_right - 2, ' ' );
mvwputch( menu->window, point( menu->w_width - menu->pad_right, 0 ), c_magenta, LINE_OXXX );
mvwputch( menu->window, point( menu->w_width - menu->pad_right, menu->w_height - 1 ), c_magenta,
LINE_XXOX );
for( int i = 1; i < menu->w_height - 1; i++ ) {
mvwputch( menu->window, point( menu->w_width - menu->pad_right, i ), c_magenta, LINE_XOXO );
mvwputch( menu->window, point( menu->w_width - menu->pad_right + 1, i ), menu->text_color, space );
}
std::string ignore_string = casting_ignore ? _( "Ignore Distractions" ) :
_( "Popup Distractions" );
Expand Down Expand Up @@ -2477,6 +2497,20 @@ void known_magic::rem_invlet( const spell_id &sp )
invlets.erase( sp );
}

void known_magic::toggle_favorite( const spell_id &sp )
{
if( favorites.count( sp ) > 0 ) {
favorites.erase( sp );
} else {
favorites.emplace( sp );
}
}

bool known_magic::is_favorite( const spell_id &sp )
{
return favorites.count( sp ) > 0;
}

int known_magic::get_invlet( const spell_id &sp, std::set<int> &used_invlets )
{
auto found = invlets.find( sp );
Expand Down Expand Up @@ -2530,16 +2564,49 @@ int known_magic::select_spell( Character &guy )
spell_menu.additional_actions.emplace_back( "CAST_IGNORE", translation() );
spell_menu.additional_actions.emplace_back( "SCROLL_UP_SPELL_MENU", translation() );
spell_menu.additional_actions.emplace_back( "SCROLL_DOWN_SPELL_MENU", translation() );
spell_menu.additional_actions.emplace_back( "SCROLL_FAVORITE", translation() );
spell_menu.hilight_disabled = true;
spellcasting_callback cb( known_spells, casting_ignore );
spell_menu.callback = &cb;
spell_menu.add_category( "all", _( "All" ) );
spell_menu.add_category( "favorites", _( "Favorites" ) );

std::vector<std::pair<std::string, std::string>> categories;
for( const spell *s : known_spells ) {
if( s->can_cast( guy ) && s->spell_class().is_valid() ) {
categories.emplace_back( s->spell_class().str(), s->spell_class().obj().name() );
std::sort( categories.begin(), categories.end(), []( const std::pair<std::string, std::string> &a,
const std::pair<std::string, std::string> &b ) {
return localized_compare( a.second, b.second );
} );
const auto itr = std::unique( categories.begin(), categories.end() );
categories.erase( itr, categories.end() );
}
}
for( std::pair<std::string, std::string> &cat : categories ) {
spell_menu.add_category( cat.first, cat.second );
}

spell_menu.set_category_filter( [&guy, known_spells]( const uilist_entry & entry,
const std::string & key )->bool {
if( key == "all" )
{
return true;
} else if( key == "favorites" )
{
return guy.magic->is_favorite( known_spells[entry.retval]->id() );
}
return known_spells[entry.retval]->spell_class().is_valid() && known_spells[entry.retval]->spell_class().str() == key;
} );
spell_menu.set_category( "all" );

std::set<int> used_invlets{ cb.reserved_invlets };

for( size_t i = 0; i < known_spells.size(); i++ ) {
spell_menu.addentry( static_cast<int>( i ), known_spells[i]->can_cast( guy ),
get_invlet( known_spells[i]->id(), used_invlets ), known_spells[i]->name() );
}
reflesh_favorite( &spell_menu, known_spells );

spell_menu.query();

Expand Down
5 changes: 5 additions & 0 deletions src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,8 @@ class known_magic
std::map<spell_id, spell> spellbook;
// invlets assigned to spell_id
std::map<spell_id, int> invlets;
// list of favorite spells
std::unordered_set<spell_id> favorites;
// the base mana a Character would start with
int mana_base = 0; // NOLINT(cata-serialize)
// current mana
Expand Down Expand Up @@ -690,6 +692,9 @@ class known_magic
// returns false if invlet is already used
bool set_invlet( const spell_id &sp, int invlet, const std::set<int> &used_invlets );
void rem_invlet( const spell_id &sp );

void toggle_favorite( const spell_id &sp );
bool is_favorite( const spell_id &sp );
private:
// gets length of longest spell name
int get_spellname_max_width();
Expand Down
65 changes: 60 additions & 5 deletions src/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ void uilist::init()
max_entry_len = 0;
max_column_len = 0; // for calculating space for second column
uilist_scrollbar = std::make_unique<scrollbar>();
categories.clear();
current_category = 0;
category_lines = 2;

input_category = "UILIST";
additional_actions.clear();
Expand All @@ -322,6 +325,8 @@ input_context uilist::create_main_input_context() const
ctxt.register_action( "SELECT" );
}
ctxt.register_action( "UILIST.FILTER" );
ctxt.register_action( "LEFT" );
ctxt.register_action( "RIGHT" );
ctxt.register_action( "ANY_INPUT" );
ctxt.register_action( "HELP_KEYBINDINGS" );
uilist_scrollbar->set_draggable( ctxt );
Expand Down Expand Up @@ -377,6 +382,9 @@ void uilist::filterlist()
int f = 0;
for( size_t i = 0; i < entries.size(); i++ ) {
bool visible = true;
if( !categories.empty() && !category_filter( entries[i], categories[current_category].first ) ) {
continue;
}
if( filtering && !filter.empty() ) {
if( filtering_nocase ) {
// case-insensitive match
Expand Down Expand Up @@ -634,6 +642,13 @@ void uilist::calc_data()
desc_enabled = false;
}
}
if( !categories.empty() ) {
category_lines = 0;
for( const std::pair<std::string, std::string> &pair : categories ) {
// -2 for borders, -2 for padding
category_lines = std::max<int>( category_lines, foldstring( pair.second, w_width - 4 ).size() );
}
}

if( w_auto && w_width > TERMX ) {
w_width = TERMX;
Expand All @@ -642,6 +657,9 @@ void uilist::calc_data()
vmax = entries.size();
int additional_lines = 2 + text_separator_line + // add two for top & bottom borders
static_cast<int>( textformatted.size() );
if( !categories.empty() ) {
additional_lines += category_lines + 1;
}
if( desc_enabled ) {
additional_lines += desc_lines + 1; // add one for description separator line
}
Expand Down Expand Up @@ -705,6 +723,7 @@ void uilist::apply_scrollbar()
} else {
estart = 1;
}
estart += category_lines > 0 ? category_lines + 1 : 0;

uilist_scrollbar->offset_x( sbside )
.offset_y( estart )
Expand Down Expand Up @@ -736,6 +755,13 @@ void uilist::show( ui_adaptor &ui )
wprintz( window, title_color, title );
wprintz( window, border_color, " >" );
}
const auto print_line = [&]( int line ) {
mvwputch( window, point( 0, line ), border_color, LINE_XXXO );
for( int i = 1; i < w_width - 1; ++i ) {
mvwputch( window, point( i, line ), border_color, LINE_OXOX );
}
mvwputch( window, point( w_width - 1, line ), border_color, LINE_XOXX );
};

const int text_lines = textformatted.size();
int estart = 1;
Expand All @@ -745,13 +771,14 @@ void uilist::show( ui_adaptor &ui )
text_color, _color_error, "%s", textformatted[i] );
}

mvwputch( window, point( 0, text_lines + 1 ), border_color, LINE_XXXO );
for( int i = 1; i < w_width - 1; ++i ) {
mvwputch( window, point( i, text_lines + 1 ), border_color, LINE_OXOX );
}
mvwputch( window, point( w_width - 1, text_lines + 1 ), border_color, LINE_XOXX );
print_line( text_lines + 1 );
estart += text_lines + 1; // +1 for the horizontal line.
}
if( !categories.empty() ) {
mvwprintz( window, point( 1, estart ), c_yellow, "<< %s >>", categories[current_category].second );
print_line( estart + category_lines );
estart += category_lines + 1;
}

if( recalc_start ) {
calcStartPos( vshift, fselected, vmax, fentries.size() );
Expand Down Expand Up @@ -1103,6 +1130,14 @@ void uilist::query( bool loop, int timeout )
recalc_start = true;
} else if( filtering && ret_act == "UILIST.FILTER" ) {
inputfilter();
} else if( !categories.empty() && ( ret_act == "LEFT" || ret_act == "RIGHT" ) ) {
current_category += ret_act == "LEFT" ? -1 : 1;
if( current_category < 0 ) {
current_category = categories.size() - 1;
} else if( current_category >= static_cast<int>( categories.size() ) ) {
current_category = 0;
}
filterlist();
} else if( iter != keymap.end() ) {
const auto it = std::find( fentries.begin(), fentries.end(), iter->second );
if( it != fentries.end() ) {
Expand Down Expand Up @@ -1225,6 +1260,26 @@ void uilist::settext( const std::string &str )
text = str;
}

void uilist::add_category( const std::string &key, const std::string &name )
{
categories.emplace_back( key, name );
}

void uilist::set_category( const std::string &key )
{
const auto it = std::find_if( categories.begin(),
categories.end(), [key]( std::pair<std::string, std::string> &pair ) {
return pair.first == key;
} );
current_category = std::distance( categories.begin(), it );
}

void uilist::set_category_filter( const
std::function<bool( const uilist_entry &, const std::string & )> &fun )
{
category_filter = fun;
}

struct pointmenu_cb::impl_t {
const std::vector< tripoint > &points;
int last; // to suppress redrawing
Expand Down
10 changes: 10 additions & 0 deletions src/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ class uilist // NOLINT(cata-xy)
const std::string &desc = std::string() );
void settext( const std::string &str );

void add_category( const std::string &key, const std::string &name );
void set_category( const std::string &key );
void set_category_filter( const std::function<bool( const uilist_entry &, const std::string & )>
&fun );

void reset();

// Can be called before `uilist::query` to keep the uilist on UI stack after
Expand Down Expand Up @@ -501,11 +506,16 @@ class uilist // NOLINT(cata-xy)
int vmax = 0;

int desc_lines = 0;
int category_lines = 0;

bool started = false;

bool recalc_start = false;

std::vector<std::pair<std::string, std::string>> categories;
std::function<bool( const uilist_entry &, const std::string & )> category_filter;
int current_category = 0;

int find_entry_by_coordinate( const point &p ) const;

public:
Expand Down