diff --git a/src/magic.cpp b/src/magic.cpp index 91747e6018575..ddcf4eebbf8ba 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -2204,11 +2204,13 @@ class spellcasting_callback : public uilist_callback } 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" ); @@ -2577,6 +2579,21 @@ int known_magic::select_spell( Character &guy ) spell_menu.hilight_disabled = true; spellcasting_callback cb( known_spells, casting_ignore ); spell_menu.callback = &cb; + spell_menu.add_category( "all", _( "All" ) ); + for( const spell *s : known_spells ) { + if( s->can_cast( guy ) && s->spell_class().is_valid() ) { + spell_menu.add_category( s->spell_class().str(), s->spell_class().obj().name() ); + } + } + spell_menu.set_category_filter( [known_spells]( const uilist_entry & entry, + const std::string & key )->bool { + if( key == "all" ) + { + return true; + } + return known_spells[entry.retval]->spell_class().is_valid() && known_spells[entry.retval]->spell_class().str() == key; + } ); + spell_menu.set_category( "all" ); std::set used_invlets{ cb.reserved_invlets }; diff --git a/src/ui.cpp b/src/ui.cpp index b012879fe4c0e..e6cd5f4251e5a 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -297,6 +297,10 @@ void uilist::init() max_column_len = 0; // for calculating space for second column uilist_scrollbar = std::make_unique(); + categories.clear(); + current_category = 0; + category_lines = 2; + input_category = "UILIST"; additional_actions.clear(); } @@ -321,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 ); @@ -376,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 @@ -633,6 +642,13 @@ void uilist::calc_data() desc_enabled = false; } } + if( !categories.empty() ) { + category_lines = 0; + for( const std::pair &pair : categories ) { + // -2 for borders, -2 for padding + category_lines = std::max( category_lines, foldstring( pair.second, w_width - 4 ).size() ); + } + } if( w_auto && w_width > TERMX ) { w_width = TERMX; @@ -641,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( textformatted.size() ); + if( !categories.empty() ) { + additional_lines += category_lines + 1; + } if( desc_enabled ) { additional_lines += desc_lines + 1; // add one for description separator line } @@ -704,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 ) @@ -736,6 +756,13 @@ void uilist::show( ui_adaptor &ui ) 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; if( !textformatted.empty() ) { @@ -743,14 +770,14 @@ void uilist::show( ui_adaptor &ui ) trim_and_print( window, point( 2, 1 + i ), getmaxx( window ) - 4, 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() ); @@ -1102,6 +1129,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( 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() ) { @@ -1229,6 +1264,32 @@ void uilist::set_selected( int index ) selected = std::clamp( index, 0, static_cast( entries.size() - 1 ) ); } +void uilist::add_category( const std::string &key, const std::string &name ) +{ + categories.emplace_back( key, name ); + std::sort( categories.begin(), categories.end(), []( const std::pair &a, + const std::pair &b ) { + return localized_compare( a.second, b.second ); + } ); + const auto itr = std::unique( categories.begin(), categories.end() ); + categories.erase( itr, categories.end() ); +} + +void uilist::set_category( const std::string &key ) +{ + const auto it = std::find_if( categories.begin(), + categories.end(), [key]( std::pair &pair ) { + return pair.first == key; + } ); + current_category = std::distance( categories.begin(), it ); +} + +void uilist::set_category_filter( const + std::function &fun ) +{ + category_filter = fun; +} + struct pointmenu_cb::impl_t { const std::vector< tripoint > &points; int last; // to suppress redrawing diff --git a/src/ui.h b/src/ui.h index a71c824d84355..3da5a8f5ce058 100644 --- a/src/ui.h +++ b/src/ui.h @@ -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 + &fun ); + void reset(); // Can be called before `uilist::query` to keep the uilist on UI stack after @@ -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> categories; + std::function category_filter; + int current_category = 0; + int find_entry_by_coordinate( const point &p ) const; public: