diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 31870e708f232..71b13641fadec 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -825,6 +825,34 @@ "name": "Move to previous category tab", "bindings": [ { "input_method": "keyboard_char", "key": "<" }, { "input_method": "keyboard_code", "key": ",", "mod": [ "shift" ] } ] }, + { + "type": "keybinding", + "id": "QUIT", + "category": "MODMANAGER_DIALOG", + "name": "Quit", + "bindings": [ + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "mouse", "key": "MOUSE_RIGHT" } + ] + }, + { + "type": "keybinding", + "id": "QUIT", + "category": "OPTIONS", + "name": "Quit", + "bindings": [ + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "mouse", "key": "MOUSE_RIGHT" } + ] + }, { "type": "keybinding", "id": "PICK_RANDOM_WORLDNAME", @@ -838,11 +866,40 @@ }, { "type": "keybinding", - "id": "QUIT", + "//": "Use a prefix here to avoid falling back to QUIT. Otherwise MOUSE_RIGHT is dropped when keyboard_mode is keychar.", + "id": "WORLDGEN_CONFIRM.QUIT", "category": "WORLDGEN_CONFIRM_DIALOG", "name": "Exit worldgen screen", - "//": "separate entry, because the global entry also has 'q' and 'Q' listed, which conflicts with the world name entry feature of this dialog", - "bindings": [ { "input_method": "keyboard_any", "key": "ESC" } ] + "//2": "separate entry, because the global entry also has 'q' and 'Q' listed, which conflicts with the world name entry feature of this dialog", + "bindings": [ { "input_method": "keyboard_any", "key": "ESC" }, { "input_method": "mouse", "key": "MOUSE_RIGHT" } ] + }, + { + "type": "keybinding", + "id": "QUIT", + "category": "DISPLAY_HELP", + "name": "Quit", + "bindings": [ + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "mouse", "key": "MOUSE_RIGHT" } + ] + }, + { + "type": "keybinding", + "id": "QUIT", + "category": "SCROLLABLE_TEXT", + "name": "Quit", + "bindings": [ + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "mouse", "key": "MOUSE_RIGHT" } + ] }, { "type": "keybinding", @@ -1158,6 +1215,20 @@ "name": "Mouse move", "bindings": [ { "input_method": "mouse", "key": "MOUSE_MOVE" } ] }, + { + "type": "keybinding", + "id": "QUIT", + "category": "PICK_WORLD_DIALOG", + "name": "Quit", + "bindings": [ + { "input_method": "keyboard_any", "key": "ESC" }, + { "input_method": "keyboard_any", "key": "q" }, + { "input_method": "keyboard_char", "key": "Q" }, + { "input_method": "keyboard_code", "key": "q", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "SPACE" }, + { "input_method": "mouse", "key": "MOUSE_RIGHT" } + ] + }, { "type": "keybinding", "id": "NEXT_TAB", diff --git a/src/cuboid_rectangle.h b/src/cuboid_rectangle.h index 19ac3e91c9cfc..b951dbd564f06 100644 --- a/src/cuboid_rectangle.h +++ b/src/cuboid_rectangle.h @@ -154,6 +154,42 @@ Tripoint clamp( const Tripoint &p, const inclusive_cuboid &c ) clamp( Traits::z( p ), Traits::z( c.p_min ), Traits::z( c.p_max ) ) ); } +template +int run_for_point_in( const std::map> &m, const Point &p, + const std::function> & )> &func, + bool first_only = true ) +{ + int cnt = 0; + for( const std::pair> &mp : m ) { + if( mp.second.contains( p ) ) { + func( mp ); + cnt++; + if( first_only ) { + break; + } + } + } + return cnt; +} + +template +int run_for_point_in( const std::map> &m, const Point &p, + const std::function> & )> &func, + bool first_only = true ) +{ + int cnt = 0; + for( const std::pair> &mp : m ) { + if( mp.second.contains( p ) ) { + func( mp ); + cnt++; + if( first_only ) { + break; + } + } + } + return cnt; +} + static constexpr rectangle rectangle_zero( point_zero, point_zero ); static constexpr cuboid cuboid_zero( tripoint_zero, tripoint_zero ); diff --git a/src/help.cpp b/src/help.cpp index a98f9d3b276e5..3df8105b7be27 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -92,8 +92,11 @@ std::string help::get_dir_grid() return movement; } -void help::draw_menu( const catacurses::window &win ) const +std::map> help::draw_menu( const catacurses::window &win, + int selected ) const { + std::map> opt_map; + werase( win ); // NOLINTNEXTLINE(cata-use-named-point-constants) int y = fold_and_print( win, point( 1, 0 ), getmaxx( win ) - 2, c_white, @@ -105,16 +108,23 @@ void help::draw_menu( const catacurses::window &win ) const size_t i = 0; for( const auto &text : help_texts ) { const std::string cat_name = text.second.first.translated(); + const int cat_width = utf8_width( remove_color_tags( shortcut_text( c_white, cat_name ) ) ); if( i < half_size ) { - second_column = std::max( second_column, utf8_width( cat_name ) + 4 ); + second_column = std::max( second_column, cat_width + 4 ); } - shortcut_print( win, point( i < half_size ? 1 : second_column, y + i % half_size ), - c_white, c_light_blue, cat_name ); + const point sc_start( i < half_size ? 1 : second_column, y + i % half_size ); + shortcut_print( win, sc_start, selected == text.first ? hilite( c_white ) : c_white, + selected == text.first ? hilite( c_light_blue ) : c_light_blue, cat_name ); ++i; + + opt_map.emplace( text.first, + inclusive_rectangle( sc_start, sc_start + point( cat_width - 1, 0 ) ) ); } wnoutrefresh( win ); + + return opt_map; } std::string help::get_note_colors() @@ -149,19 +159,24 @@ void help::display_help() const init_windows( ui ); ui.on_screen_resize( init_windows ); - input_context ctxt( "default", keyboard_mode::keychar ); + input_context ctxt( "DISPLAY_HELP", keyboard_mode::keychar ); ctxt.register_cardinal(); ctxt.register_action( "QUIT" ); ctxt.register_action( "CONFIRM" ); + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); // for the menu shortcuts ctxt.register_action( "ANY_INPUT" ); std::string action; + std::map> opt_map; + int sel = -1; ui.on_redraw( [&]( const ui_adaptor & ) { draw_border( w_help_border, BORDER_COLOR, _( "Help" ) ); wnoutrefresh( w_help_border ); - draw_menu( w_help ); + opt_map = draw_menu( w_help, sel ); } ); std::map> hotkeys; @@ -172,8 +187,28 @@ void help::display_help() const do { ui_manager::redraw(); + sel = -1; action = ctxt.handle_input(); std::string sInput = ctxt.get_raw_input().text; + + // Mouse selection + if( action == "MOUSE_MOVE" || action == "SELECT" ) { + cata::optional coord = ctxt.get_coordinates_text( w_help ); + if( !!coord ) { + int cnt = run_for_point_in( opt_map, *coord, + [&sel]( const std::pair> &p ) { + sel = p.first; + } ); + if( cnt > 0 && action == "SELECT" ) { + auto iter = hotkeys.find( sel ); + if( iter != hotkeys.end() && !iter->second.empty() ) { + sInput = iter->second.front(); + action = "CONFIRM"; + } + } + } + } + for( const auto &hotkey_entry : hotkeys ) { auto help_text_it = help_texts.find( hotkey_entry.first ); if( help_text_it == help_texts.end() ) { diff --git a/src/help.h b/src/help.h index afd6c7c00eb8e..128e169a97969 100644 --- a/src/help.h +++ b/src/help.h @@ -8,6 +8,8 @@ #include #include +#include "cuboid_rectangle.h" + class JsonIn; class translation; namespace catacurses @@ -23,7 +25,8 @@ class help private: void deserialize( JsonIn &jsin ); - void draw_menu( const catacurses::window &win ) const; + std::map> draw_menu( const catacurses::window &win, + int selected ) const; static std::string get_note_colors(); static std::string get_dir_grid(); diff --git a/src/main_menu.cpp b/src/main_menu.cpp index 934abd5ad5301..8bf0c6827d436 100644 --- a/src/main_menu.cpp +++ b/src/main_menu.cpp @@ -73,16 +73,7 @@ void main_menu::on_move() const void main_menu::on_error() { - if( errflag ) { - return; - } sfx::play_variant_sound( "menu_error", "default", 100 ); - errflag = true; -} - -void main_menu::clear_error() -{ - errflag = false; } //CJK characters have a width of 2, etc @@ -683,6 +674,7 @@ bool main_menu::opening_screen() sel1 = it.second; sel2 = sel1 == getopt( main_menu_opts::LOADCHAR ) ? last_world_pos : 0; sel_line = 0; + on_move(); } if( action == "SELECT" && ( sel1 == getopt( main_menu_opts::HELP ) || sel1 == getopt( main_menu_opts::QUIT ) ) ) { @@ -694,6 +686,9 @@ bool main_menu::opening_screen() } for( const auto &it : main_menu_sub_button_map ) { if( coord.has_value() && it.first.contains( coord.value() ) ) { + if( sel1 != it.second.first || sel2 != it.second.second ) { + on_move(); + } sel1 = it.second.first; sel2 = it.second.second; sel_line = 0; @@ -796,7 +791,6 @@ bool main_menu::opening_screen() if( MAP_SHARING::isSharing() ) { on_error(); popup( _( "Special games don't work with shared maps." ) ); - clear_error(); } else if( sel2 >= 0 && sel2 < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 1 ) { on_out_of_scope cleanup( [&player_character]() { g->gamemode.reset(); @@ -866,7 +860,6 @@ bool main_menu::new_character_tab() if( templates.empty() ) { on_error(); popup( _( "No templates found!" ) ); - clear_error(); return false; } while( true ) { @@ -876,6 +869,7 @@ bool main_menu::new_character_tab() for( const std::string &tmpl : templates ) { mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, tmpl ); } + mmenu.entries.emplace_back( opt_val, true, 'q', _( "<- Back to main menu" ), c_yellow, c_yellow ); mmenu.query(); opt_val = mmenu.ret; if( opt_val < 0 || static_cast( opt_val ) >= templates.size() ) { @@ -994,7 +988,6 @@ bool main_menu::load_character_tab( const std::string &worldname ) on_error(); //~ %s = world name popup( _( "%s has no characters to load!" ), worldname ); - clear_error(); return false; } @@ -1004,6 +997,7 @@ bool main_menu::load_character_tab( const std::string &worldname ) for( const save_t &s : savegames ) { mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, s.decoded_name() ); } + mmenu.entries.emplace_back( opt_val, true, 'q', _( "<- Back to main menu" ), c_yellow, c_yellow ); mmenu.query(); opt_val = mmenu.ret; if( opt_val < 0 || static_cast( opt_val ) >= savegames.size() ) { @@ -1053,6 +1047,7 @@ void main_menu::world_tab( const std::string &worldname ) mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, remove_color_tags( shortcut_text( c_white, it ) ) ); } + mmenu.entries.emplace_back( opt_val, true, 'q', _( "<- Back to main menu" ), c_yellow, c_yellow ); mmenu.query(); opt_val = mmenu.ret; if( opt_val < 0 || static_cast( opt_val ) >= vWorldSubItems.size() ) { diff --git a/src/main_menu.h b/src/main_menu.h index 9b24a515fcd48..82e22663f0458 100644 --- a/src/main_menu.h +++ b/src/main_menu.h @@ -50,13 +50,8 @@ class main_menu // Play a sound whenever the user moves left or right in the main menu or its tabs void on_move() const; - // Flag to be set when first entering an error condition, cleared when leaving it - // Used to prevent error sound from playing repeatedly at input polling rate - bool errflag = false; - // Play a sound *once* when an error occurs in the main menu or its tabs; sets errflag + // Play a sound *once* when an error occurs in the main menu or its tabs void on_error(); - // Clears errflag - void clear_error(); // Tab functions. They return whether a game was started or not. The ones that can never // start a game have a void return type. diff --git a/src/options.cpp b/src/options.cpp index c81f3e7a37c57..87fddd4aa428c 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2718,6 +2718,10 @@ std::string options_manager::show( bool ingame, const bool world_options_only, ingame = false; } + size_t sel_worldgen_tab = 1; + std::map> worldgen_tab_map; + std::map> opt_tab_map; + std::map> opt_line_map; std::map mapLines; mapLines[4] = true; mapLines[60] = true; @@ -2735,11 +2739,17 @@ std::string options_manager::show( bool ingame, const bool world_options_only, ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "HELP_KEYBINDINGS" ); + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); const int iWorldOffset = world_options_only ? 2 : 0; int iMinScreenWidth = 0; const int iTooltipHeight = 6; int iContentHeight = 0; + bool recalc_startpos = false; catacurses::window w_options_border; catacurses::window w_options_tooltip; @@ -2747,6 +2757,7 @@ std::string options_manager::show( bool ingame, const bool world_options_only, catacurses::window w_options; const auto init_windows = [&]( ui_adaptor & ui ) { + recalc_startpos = true; if( OPTIONS.find( "TERMINAL_X" ) != OPTIONS.end() ) { if( OPTIONS_OLD.find( "TERMINAL_X" ) != OPTIONS_OLD.end() ) { OPTIONS_OLD["TERMINAL_X"] = OPTIONS["TERMINAL_X"]; @@ -2784,8 +2795,10 @@ std::string options_manager::show( bool ingame, const bool world_options_only, ui.on_screen_resize( init_windows ); init_windows( ui ); ui.on_redraw( [&]( const ui_adaptor & ) { + opt_line_map.clear(); + opt_tab_map.clear(); if( world_options_only ) { - worldfactory::draw_worldgen_tabs( w_options_border, 1 ); + worldgen_tab_map = worldfactory::draw_worldgen_tabs( w_options_border, sel_worldgen_tab ); } draw_borders_external( w_options_border, iTooltipHeight + 1 + iWorldOffset, mapLines, @@ -2813,7 +2826,9 @@ std::string options_manager::show( bool ingame, const bool world_options_only, } } - calcStartPos( iStartPos, iCurrentLine, iContentHeight, page_items.size() ); + if( recalc_startpos ) { + calcStartPos( iStartPos, iCurrentLine, iContentHeight, page_items.size() ); + } // where the column with the names starts const size_t name_col = 5; @@ -2862,8 +2877,10 @@ std::string options_manager::show( bool ingame, const bool world_options_only, const std::string value = utf8_truncate( current_opt.getValueName(), value_width ); mvwprintz( w_options, point( value_col, line_pos ), - iCurrentLine == i ? hilite( cLineColor ) : cLineColor, - value ); + iCurrentLine == i ? hilite( cLineColor ) : cLineColor, value ); + + opt_line_map.emplace( i, inclusive_rectangle( point( name_col, line_pos ), + point( value_col + value_width - 1, line_pos ) ) ); } scrollbar() @@ -2877,6 +2894,7 @@ std::string options_manager::show( bool ingame, const bool world_options_only, wnoutrefresh( w_options_border ); //Draw Tabs + int tab_x = 0; if( !world_options_only ) { mvwprintz( w_options_header, point( 7, 0 ), c_white, "" ); for( int i = 0; i < static_cast( pages_.size() ); i++ ) { @@ -2890,6 +2908,11 @@ std::string options_manager::show( bool ingame, const bool world_options_only, } wprintz( w_options_header, c_white, "]" ); wputch( w_options_header, BORDER_COLOR, LINE_OXOX ); + tab_x++; + int tab_w = utf8_width( pages_[i].get().name_.translated(), true ); + opt_tab_map.emplace( i, inclusive_rectangle( point( 7 + tab_x, 0 ), + point( 6 + tab_x + tab_w, 0 ) ) ); + tab_x += tab_w + 2; } } @@ -2950,22 +2973,70 @@ std::string options_manager::show( bool ingame, const bool world_options_only, while( true ) { ui_manager::redraw(); + recalc_startpos = false; Page &page = pages_[iCurrentPage]; auto &page_items = page.items_; auto &cOPTIONS = ( ingame || world_options_only ) && iCurrentPage == iWorldOptPage ? ACTIVE_WORLD_OPTIONS : OPTIONS; - const std::string &opt_name = *page_items[iCurrentLine]; - cOpt ¤t_opt = cOPTIONS[opt_name]; - - const std::string action = ctxt.handle_input(); + std::string action = ctxt.handle_input(); if( world_options_only && ( action == "NEXT_TAB" || action == "PREV_TAB" || ( action == "QUIT" && ( !on_quit || on_quit() ) ) ) ) { return action; } + if( action == "MOUSE_MOVE" || action == "SELECT" ) { + bool found_opt = false; + sel_worldgen_tab = 1; + cata::optional coord = ctxt.get_coordinates_text( w_options_border ); + if( world_options_only && !!coord ) { + // worldgen tabs + found_opt = run_for_point_in( worldgen_tab_map, *coord, + [&sel_worldgen_tab]( const std::pair> &p ) { + sel_worldgen_tab = p.first; + } ) > 0; + if( found_opt && action == "SELECT" && sel_worldgen_tab != 1 ) { + return sel_worldgen_tab == 0 ? "PREV_TAB" : "NEXT_TAB"; + } + } + if( !found_opt && !!coord ) { + // option category tabs + coord = ctxt.get_coordinates_text( w_options_header ); + bool new_val = false; + const int psize = pages_.size(); + found_opt = run_for_point_in( opt_tab_map, *coord, + [&iCurrentPage, &new_val, &psize]( const std::pair> &p ) { + new_val = true; + iCurrentPage = clamp( p.first, 0, psize - 1 ); + } ) > 0; + if( new_val ) { + iCurrentLine = 0; + iStartPos = 0; + recalc_startpos = true; + sfx::play_variant_sound( "menu_move", "default", 100 ); + } + } + if( !found_opt ) { + // option lines + coord = ctxt.get_coordinates_text( w_options ); + if( !!coord ) { + const int psize = page_items.size(); + found_opt = run_for_point_in( opt_line_map, *coord, + [&iCurrentLine, &psize]( const std::pair> &p ) { + iCurrentLine = clamp( p.first, 0, psize - 1 ); + } ) > 0; + if( found_opt && action == "SELECT" ) { + action = "CONFIRM"; + } + } + } + } + + const std::string &opt_name = *page_items[iCurrentLine]; + cOpt ¤t_opt = cOPTIONS[opt_name]; + bool hasPrerequisite = current_opt.hasPrerequisite(); bool hasPrerequisiteFulfilled = current_opt.checkPrerequisite(); @@ -2978,20 +3049,22 @@ std::string options_manager::show( bool ingame, const bool world_options_only, const int recmax = static_cast( page_items.size() ); const int scroll_rate = recmax > 20 ? 10 : 3; - if( action == "DOWN" ) { + if( action == "DOWN" || action == "SCROLL_DOWN" ) { do { iCurrentLine++; if( iCurrentLine >= recmax ) { iCurrentLine = 0; } } while( !page_items[iCurrentLine] ); - } else if( action == "UP" ) { + recalc_startpos = true; + } else if( action == "UP" || action == "SCROLL_UP" ) { do { iCurrentLine--; if( iCurrentLine < 0 ) { iCurrentLine = page_items.size() - 1; } } while( !page_items[iCurrentLine] ); + recalc_startpos = true; } else if( action == "PAGE_DOWN" ) { if( iCurrentLine == recmax - 1 ) { iCurrentLine = 0; @@ -3003,6 +3076,7 @@ std::string options_manager::show( bool ingame, const bool world_options_only, iCurrentLine++; } } + recalc_startpos = true; } else if( action == "PAGE_UP" ) { if( iCurrentLine == 0 ) { iCurrentLine = recmax - 1; @@ -3014,13 +3088,17 @@ std::string options_manager::show( bool ingame, const bool world_options_only, iCurrentLine--; } } + recalc_startpos = true; } else if( action == "RIGHT" ) { current_opt.setNext(); + recalc_startpos = true; } else if( action == "LEFT" ) { current_opt.setPrev(); + recalc_startpos = true; } else if( action == "NEXT_TAB" ) { iCurrentLine = 0; iStartPos = 0; + recalc_startpos = true; iCurrentPage++; if( iCurrentPage >= static_cast( pages_.size() ) ) { iCurrentPage = 0; @@ -3029,6 +3107,7 @@ std::string options_manager::show( bool ingame, const bool world_options_only, } else if( action == "PREV_TAB" ) { iCurrentLine = 0; iStartPos = 0; + recalc_startpos = true; iCurrentPage--; if( iCurrentPage < 0 ) { iCurrentPage = pages_.size() - 1; diff --git a/src/output.cpp b/src/output.cpp index 0d0165f9de006..1adcb8eaed637 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1381,9 +1381,11 @@ void draw_subtab( const catacurses::window &w, int iOffsetX, const std::string & } } -void draw_tabs( const catacurses::window &w, const std::vector &tab_texts, - size_t current_tab ) +std::map> draw_tabs( const catacurses::window &w, + const std::vector &tab_texts, size_t current_tab ) { + std::map> tab_map; + int width = getmaxx( w ); for( int i = 0; i < width; i++ ) { mvwputch( w, point( i, 2 ), BORDER_COLOR, LINE_OXOX ); // - @@ -1397,16 +1399,28 @@ void draw_tabs( const catacurses::window &w, const std::vector &tab for( size_t i = 0; i < tab_texts.size(); ++i ) { const std::string &tab_text = tab_texts[i]; draw_tab( w, x, tab_text, i == current_tab ); - x += utf8_width( tab_text, true ) + tab_step; + const int txt_width = utf8_width( tab_text, true ); + tab_map.emplace( i, inclusive_rectangle( point( x, 1 ), point( x + txt_width, 1 ) ) ); + x += txt_width + tab_step; } + + return tab_map; } -void draw_tabs( const catacurses::window &w, const std::vector &tab_texts, - const std::string ¤t_tab ) +std::map> draw_tabs( const catacurses::window &w, + const std::vector &tab_texts, const std::string ¤t_tab ) { auto it = std::find( tab_texts.begin(), tab_texts.end(), current_tab ); cata_assert( it != tab_texts.end() ); - draw_tabs( w, tab_texts, it - tab_texts.begin() ); + std::map> tab_map = + draw_tabs( w, tab_texts, it - tab_texts.begin() ); + std::map> ret_map; + for( size_t i = 0; i < tab_texts.size(); i++ ) { + if( tab_map.count( i ) > 0 ) { + ret_map.emplace( tab_texts.at( i ), tab_map.at( i ) ); + } + } + return ret_map; } best_fit find_best_fit_in_size( const std::vector &size_of_items_to_fit, const int &selected, diff --git a/src/output.h b/src/output.h index c08891521110e..38d8c10194894 100644 --- a/src/output.h +++ b/src/output.h @@ -21,6 +21,7 @@ #include "cata_assert.h" #include "catacharset.h" #include "color.h" +#include "cuboid_rectangle.h" #include "debug.h" #include "enums.h" #include "item.h" @@ -799,11 +800,11 @@ void draw_subtab( const catacurses::window &w, int iOffsetX, const std::string & // ┌──────┐ ┌──────┐ // │ TAB1 │ │ TAB2 │ // ┌─┴──────┴─┘ └───────────┐ -void draw_tabs( const catacurses::window &, const std::vector &tab_texts, - size_t current_tab ); +std::map> draw_tabs( const catacurses::window &, + const std::vector &tab_texts, size_t current_tab ); // As above, but specify current tab by its label rather than position -void draw_tabs( const catacurses::window &, const std::vector &tab_texts, - const std::string ¤t_tab ); +std::map> draw_tabs( const catacurses::window &, + const std::vector &tab_texts, const std::string ¤t_tab ); // This overload of draw_tabs is intended for use when you track the current // tab via some other value (like an enum) linked to each tab. Expected use @@ -818,8 +819,8 @@ void draw_tabs( const catacurses::window &, const std::vector &tab_ template>::value>> -void draw_tabs( const catacurses::window &w, const TabList &tab_list, - const CurrentTab ¤t_tab ) +std::map> draw_tabs( const catacurses::window &w, + const TabList &tab_list, const CurrentTab ¤t_tab ) { std::vector tab_text; std::transform( tab_list.begin(), tab_list.end(), std::back_inserter( tab_text ), @@ -831,7 +832,18 @@ void draw_tabs( const catacurses::window &w, const TabList &tab_list, return pair.first == current_tab; } ); cata_assert( current_tab_it != tab_list.end() ); - draw_tabs( w, tab_text, std::distance( tab_list.begin(), current_tab_it ) ); + std::map> tab_map = + draw_tabs( w, tab_text, std::distance( tab_list.begin(), current_tab_it ) ); + + std::map> ret_map; + size_t i = 0; + for( const typename TabList::value_type &pair : tab_list ) { + if( tab_map.count( i ) > 0 ) { + ret_map.emplace( pair.first, tab_map.at( i ) ); + } + i++; + } + return ret_map; } // Similar to the above, but where the order of tabs is specified separately @@ -839,8 +851,8 @@ void draw_tabs( const catacurses::window &w, const TabList &tab_list, template>::value>> -void draw_tabs( const catacurses::window &w, const TabList &tab_list, const TabKeys &keys, - const CurrentTab ¤t_tab ) +std::map> draw_tabs( const catacurses::window &w, + const TabList &tab_list, const TabKeys &keys, const CurrentTab ¤t_tab ) { std::vector ordered_tab_list; for( const auto &key : keys ) { @@ -848,7 +860,7 @@ void draw_tabs( const catacurses::window &w, const TabList &tab_list, const TabK cata_assert( it != tab_list.end() ); ordered_tab_list.push_back( *it ); } - draw_tabs( w, ordered_tab_list, current_tab ); + return draw_tabs( w, ordered_tab_list, current_tab ); } /** diff --git a/src/popup.cpp b/src/popup.cpp index 4af55c118ce25..2a44b893067ea 100644 --- a/src/popup.cpp +++ b/src/popup.cpp @@ -11,8 +11,8 @@ #include "ui_manager.h" query_popup::query_popup() - : cur( 0 ), default_text_color( c_white ), anykey( false ), cancel( false ), ontop( false ), - fullscr( false ), pref_kbd_mode( keyboard_mode::keycode ) + : cur( 0 ), default_text_color( c_white ), anykey( false ), cancel( false ), + ontop( false ), fullscr( false ), pref_kbd_mode( keyboard_mode::keycode ) { } @@ -286,6 +286,9 @@ query_popup::result query_popup::query_once() for( const auto &opt : options ) { ctxt.register_action( opt.action ); } + // Mouse movement and button + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); } if( anykey ) { ctxt.register_action( "ANY_INPUT" ); @@ -302,6 +305,24 @@ query_popup::result query_popup::query_once() do { res.action = ctxt.handle_input(); res.evt = ctxt.get_raw_input(); + + // If we're tracking mouse movement + if( !options.empty() && ( res.action == "MOUSE_MOVE" || res.action == "SELECT" ) ) { + cata::optional coord = ctxt.get_coordinates_text( win ); + for( size_t i = 0; i < buttons.size(); i++ ) { + if( coord.has_value() && buttons[i].contains( coord.value() ) ) { + if( i != cur ) { + // Mouse-over new button, switch selection + cur = i; + ui_manager::redraw(); + } + if( res.action == "SELECT" ) { + // Left-click to confirm selection + res.action = "CONFIRM"; + } + } + } + } } while( // Always ignore mouse movement ( res.evt.type == input_event_t::mouse && res.evt.get_first_input() == MOUSE_MOVE ) || @@ -398,6 +419,14 @@ query_popup::query_option::query_option( query_popup::button::button( const std::string &text, const point &p ) : text( text ), pos( p ) { + width = utf8_width( text, true ); +} + +bool query_popup::button::contains( const point &p ) const +{ + return p.x >= pos.x + border_width && + p.x < pos.x + width + border_width && + p.y == pos.y + border_width; } static_popup::static_popup() diff --git a/src/popup.h b/src/popup.h index c7b2215179fe0..2c1539d8ec39a 100644 --- a/src/popup.h +++ b/src/popup.h @@ -148,8 +148,7 @@ class query_popup query_popup &option( const std::string &opt, const std::function &filter ); /** - * Specify whether non-option actions can be returned. Mouse movement - * is always ignored regardless of this setting. + * Specify whether non-option actions can be returned. **/ query_popup &allow_anykey( bool allow ); /** @@ -227,9 +226,11 @@ class query_popup struct button { button( const std::string &text, const point & ); + bool contains( const point &p ) const; std::string text; point pos; + int width; }; std::weak_ptr adaptor; diff --git a/src/ui.cpp b/src/ui.cpp index 8300c95125c70..b6bcd5e8527e7 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -307,6 +307,7 @@ input_context uilist::create_main_input_context() const ctxt.register_action( "UILIST.QUIT" ); } ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "UILIST.FILTER" ); ctxt.register_action( "ANY_INPUT" ); @@ -765,7 +766,8 @@ void uilist::show() const utf8_wrapper entry = utf8_wrapper( ei == selected ? remove_color_tags( entries[ ei ].txt ) : entries[ ei ].txt ); point p( pad_left + 4, estart + si ); - entries[ei].drawn_rect = inclusive_rectangle( p, p + point( -1 + max_entry_len, 0 ) ); + entries[ei].drawn_rect = + inclusive_rectangle( p + point( -3, 0 ), p + point( -4 + pad_size, 0 ) ); trim_and_print( window, p, max_entry_len, co, _color_error, "%s", entry.str() ); @@ -1056,7 +1058,8 @@ void uilist::query( bool loop, int timeout ) } } } - } else if( !fentries.empty() && ret_act == "SELECT" ) { + // Only check MOUSE_MOVE when looping internally + } else if( !fentries.empty() && ( ret_act == "SELECT" || ( loop && ret_act == "MOUSE_MOVE" ) ) ) { cata::optional p = ctxt.get_coordinates_text( window ); if( p && window_contains_point_relative( window, p.value() ) ) { const int new_fselected = find_entry_by_coordinate( p.value() ); @@ -1067,11 +1070,13 @@ void uilist::query( bool loop, int timeout ) // function is called again. fselected = new_fselected; selected = fentries[fselected]; - if( enabled || allow_disabled ) { - ret = entries[selected].retval; - } - if( callback != nullptr ) { - callback->select( this ); + if( ret_act == "SELECT" ) { + if( enabled || allow_disabled ) { + ret = entries[selected].retval; + } + if( callback != nullptr ) { + callback->select( this ); + } } } } diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 2f9e92bca3f6e..9124da15c9371 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -26,6 +26,7 @@ #include "output.h" #include "path_info.h" #include "point.h" +#include "sounds.h" #include "string_formatter.h" #include "string_input_popup.h" #include "translations.h" @@ -417,7 +418,10 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) mapLines[3] = true; std::map > world_pages; - size_t sel = 0; + std::map> button_map; + point world_list_top_left; + int world_list_width = 0; + int sel = 0; size_t selpage = 0; catacurses::window w_worlds_border; @@ -427,6 +431,10 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) ui_adaptor ui; + const auto on_move = []( bool is_error ) { + sfx::play_variant_sound( is_error ? "menu_error" : "menu_move", "default", 100 ); + }; + const auto init_windows = [&]( ui_adaptor & ui ) { iContentHeight = TERMY - 3 - iTooltipHeight; iMinScreenWidth = std::max( FULL_SCREEN_WIDTH, TERMX / 2 ); @@ -450,12 +458,16 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) w_worlds = catacurses::newwin( iContentHeight, iMinScreenWidth - 2, point( 1 + iOffsetX, iTooltipHeight + 2 ) ); + world_list_top_left = point( getbegx( w_worlds ), getbegy( w_worlds ) ); + world_list_width = iMinScreenWidth - 2; + ui.position_from_window( w_worlds_border ); }; init_windows( ui ); ui.on_screen_resize( init_windows ); ui.on_redraw( [&]( const ui_adaptor & ) { + button_map.clear(); draw_border( w_worlds_border, BORDER_COLOR, _( "World selection" ) ); mvwputch( w_worlds_border, point( 0, 4 ), BORDER_COLOR, LINE_XXXO ); // |- mvwputch( w_worlds_border, point( iMinScreenWidth - 1, 4 ), BORDER_COLOR, LINE_XOXX ); // -| @@ -496,19 +508,29 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) //Draw World Names for( size_t i = 0; i < world_pages[selpage].size(); ++i ) { + const bool sel_this = static_cast( i ) == sel; + inclusive_rectangle btn( world_list_top_left + point( 4, i ), + world_list_top_left + point( world_list_width - 1, i ) ); + button_map.emplace( i, btn ); + mvwprintz( w_worlds, point( 0, static_cast( i ) ), c_white, "%d", i + 1 ); wmove( w_worlds, point( 4, static_cast( i ) ) ); std::string world_name = ( world_pages[selpage] )[i]; size_t saves_num = get_world( world_name )->world_saves.size(); - if( i == sel ) { - wprintz( w_worlds, c_yellow, ">> " ); + if( sel_this ) { + wprintz( w_worlds, hilite( c_yellow ), "» " ); } else { - wprintz( w_worlds, c_yellow, " " ); + wprintz( w_worlds, c_yellow, " " ); } - wprintz( w_worlds, c_white, "%s (%lu)", world_name, saves_num ); + const std::string txt = string_format( "%s (%lu)", world_name, saves_num ); + const int remaining = world_list_width - ( utf8_width( txt, true ) + 6 ); + wprintz( w_worlds, sel_this ? hilite( c_white ) : c_white, txt ); + if( sel_this && remaining > 0 ) { + wprintz( w_worlds, hilite( c_white ), std::string( remaining, ' ' ) ); + } } //Draw Tabs @@ -542,43 +564,72 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "CONFIRM" ); + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); while( true ) { ui_manager::redraw(); - const std::string action = ctxt.handle_input(); + std::string action = ctxt.handle_input(); const size_t recmax = world_pages[selpage].size(); const size_t scroll_rate = recmax > 20 ? 10 : 3; + // handle mouse click + if( action == "SELECT" || action == "MOUSE_MOVE" ) { + cata::optional coord = ctxt.get_coordinates_text( catacurses::stdscr ); + if( !!coord ) { + int cnt = run_for_point_in( button_map, *coord, + [&sel, &on_move]( const std::pair> &p ) { + if( sel != p.first ) { + on_move( false ); + sel = p.first; + } + } ); + if( cnt > 0 ) { + if( action == "SELECT" ) { + action = "CONFIRM"; + } + ui_manager::redraw(); + } + } + } + if( action == "QUIT" ) { break; - } else if( !world_pages[selpage].empty() && action == "DOWN" ) { + } else if( !world_pages[selpage].empty() && ( action == "DOWN" || action == "SCROLL_DOWN" ) ) { sel++; - if( sel >= recmax ) { + if( sel >= static_cast( recmax ) ) { sel = 0; } - } else if( !world_pages[selpage].empty() && action == "UP" ) { + on_move( recmax < 2 ); + } else if( !world_pages[selpage].empty() && ( action == "UP" || action == "SCROLL_UP" ) ) { if( sel == 0 ) { sel = recmax - 1; } else { sel--; } + on_move( recmax < 2 ); } else if( action == "PAGE_DOWN" ) { - if( sel == recmax - 1 ) { + if( sel == static_cast( recmax ) - 1 ) { sel = 0; } else if( sel + scroll_rate >= recmax ) { sel = recmax - 1; } else { sel += +scroll_rate; } + on_move( recmax < 2 ); } else if( action == "PAGE_UP" ) { if( sel == 0 ) { sel = recmax - 1; - } else if( sel <= scroll_rate ) { + } else if( sel <= static_cast( scroll_rate ) ) { sel = 0; } else { sel += -scroll_rate; } + on_move( recmax < 2 ); } else if( action == "NEXT_TAB" ) { sel = 0; @@ -670,13 +721,16 @@ int worldfactory::show_worldgen_tab_options( const catacurses::window &, WORLDPT return 0; } -void worldfactory::draw_mod_list( const catacurses::window &w, int &start, size_t cursor, - const std::vector &mods, bool is_active_list, - const std::string &text_if_empty, const catacurses::window &w_shift ) +std::map> worldfactory::draw_mod_list( const catacurses::window &w, + int &start, size_t cursor, const std::vector &mods, + bool is_active_list, const std::string &text_if_empty, + const catacurses::window &w_shift, bool recalc_start ) { werase( w ); werase( w_shift ); + std::map> ent_map; + const int iMaxRows = getmaxy( w ); size_t iModNum = mods.size(); size_t iActive = cursor; @@ -719,7 +773,9 @@ void worldfactory::draw_mod_list( const catacurses::window &w, int &start, size_ } } - calcStartPos( start, iActive, iMaxRows, iModNum ); + if( recalc_start ) { + calcStartPos( start, iActive, iMaxRows, iModNum ); + } for( int i = 0; i < start; i++ ) { if( !mSortCategory[i].empty() ) { @@ -760,6 +816,8 @@ void worldfactory::draw_mod_list( const catacurses::window &w, int &start, size_ } mod_entry_name += string_format( _( " [%s]" ), mod_entry_id.str() ); trim_and_print( w, point( 4, iNum - start ), wwidth, mod_entry_color, mod_entry_name ); + ent_map.emplace( std::distance( mods.begin(), iter ), + inclusive_rectangle( point( 1, iNum - start ), point( 3 + wwidth, iNum - start ) ) ); if( w_shift ) { // get shift information for the active item @@ -802,6 +860,8 @@ void worldfactory::draw_mod_list( const catacurses::window &w, int &start, size_ wnoutrefresh( w ); wnoutrefresh( w_shift ); + + return ent_map; } void worldfactory::show_active_world_mods( const std::vector &world_mods ) @@ -809,8 +869,11 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods ui_adaptor ui; catacurses::window w_border; catacurses::window w_mods; + std::map> ent_map; + bool recalc_start = false; const auto init_windows = [&]( ui_adaptor & ui ) { + recalc_start = true; const int iMinScreenWidth = std::max( FULL_SCREEN_WIDTH, TERMX / 2 ); const int iOffsetX = TERMX > FULL_SCREEN_WIDTH ? ( TERMX - iMinScreenWidth ) / 2 : 0; @@ -836,12 +899,19 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods ctxt.register_action( "CONFIRM" ); ctxt.register_action( "HELP_KEYBINDINGS" ); + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "SEC_SELECT" ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); + ui.on_redraw( [&]( const ui_adaptor & ) { draw_border( w_border, BORDER_COLOR, _( "Active world mods" ) ); wnoutrefresh( w_border ); - draw_mod_list( w_mods, start, static_cast( cursor ), world_mods, - true, _( "--NO ACTIVE MODS--" ), catacurses::window() ); + ent_map = draw_mod_list( w_mods, start, static_cast( cursor ), world_mods, + true, _( "--NO ACTIVE MODS--" ), catacurses::window(), recalc_start ); wnoutrefresh( w_mods ); } ); @@ -852,18 +922,30 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods const int recmax = static_cast( num_mods ); const int scroll_rate = recmax > 20 ? 10 : 3; - if( action == "UP" ) { + if( !world_mods.empty() && action == "MOUSE_MOVE" ) { + cata::optional coord = ctxt.get_coordinates_text( w_mods ); + if( !!coord ) { + run_for_point_in( ent_map, *coord, + [&cursor]( const std::pair> &p ) { + cursor = p.first; + } ); + } + } + + if( action == "UP" || action == "SCROLL_UP" ) { cursor--; // If it went under 0, loop back to the end of the list. if( cursor < 0 ) { cursor = recmax - 1; } - } else if( action == "DOWN" ) { + recalc_start = true; + } else if( action == "DOWN" || action == "SCROLL_DOWN" ) { cursor++; // If it went over the end of the list, loop back to the start of the list. if( cursor > recmax - 1 ) { cursor = 0; } + recalc_start = true; } else if( action == "PAGE_DOWN" ) { if( cursor == recmax - 1 ) { cursor = 0; @@ -872,6 +954,7 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods } else { cursor += +scroll_rate; } + recalc_start = true; } else if( action == "PAGE_UP" ) { if( cursor == 0 ) { cursor = recmax - 1; @@ -880,7 +963,9 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods } else { cursor += -scroll_rate; } - } else if( action == "QUIT" || action == "CONFIRM" ) { + recalc_start = true; + } else if( action == "QUIT" || action == "CONFIRM" || + action == "SELECT" || action == "SEC_SELECT" ) { break; } } @@ -920,11 +1005,17 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, ctxt.register_action( "SAVE_DEFAULT_MODS" ); ctxt.register_action( "VIEW_MOD_DESCRIPTION" ); ctxt.register_action( "FILTER" ); + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); point filter_pos; int filter_view_len = 0; std::string current_filter = "init me!"; std::unique_ptr fpopup; + bool recalc_start = false; catacurses::window w_header1; catacurses::window w_header2; @@ -937,6 +1028,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, ui_adaptor ui; const auto init_windows = [&]( ui_adaptor & ui ) { + recalc_start = true; const int iMinScreenWidth = std::max( FULL_SCREEN_WIDTH, TERMX / 2 ); const int iOffsetX = TERMX > FULL_SCREEN_WIDTH ? ( TERMX - iMinScreenWidth ) / 2 : 0; @@ -978,7 +1070,12 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, int startsel[2] = {0, 0}; size_t cursel[2] = {0, 0}; size_t iCurrentTab = 0; + size_t sel_top_tab = 0; std::vector current_tab_mods; + std::map> inact_mod_map; + std::map> act_mod_map; + std::map> mod_tab_map; + std::map> top_tab_map; struct mod_tab { std::string id; @@ -1067,7 +1164,8 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, apply_filter( "" ); ui.on_redraw( [&]( const ui_adaptor & ) { - draw_worldgen_tabs( win, 0 ); + mod_tab_map.clear(); + top_tab_map = draw_worldgen_tabs( win, sel_top_tab ); draw_modselection_borders( win, ctxt ); // Redraw headers @@ -1106,6 +1204,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, } // Draw tab names + int xpos = 0; wmove( win, point( 2, 4 ) ); for( size_t i = 0; i < get_mod_list_tabs().size(); i++ ) { wprintz( win, c_white, "[" ); @@ -1113,6 +1212,10 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, "%s", get_mod_list_tabs()[i].second ); wprintz( win, c_white, "]" ); wputch( win, BORDER_COLOR, LINE_OXOX ); + point tabpos( point( 2 + ++xpos, 4 ) ); + int tabwidth = utf8_width( get_mod_list_tabs()[i].second.translated(), true ); + mod_tab_map.emplace( i, inclusive_rectangle( tabpos, tabpos + point( tabwidth, 0 ) ) ); + xpos += tabwidth + 2; } // Draw filter @@ -1136,12 +1239,12 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, const mod_tab ¤t_tab = all_tabs[iCurrentTab]; const char *msg = current_tab.mods_unfiltered.empty() ? _( "--NO AVAILABLE MODS--" ) : _( "--NO RESULTS FOUND--" ); - draw_mod_list( w_list, startsel[0], cursel[0], current_tab.mods, active_header == 0, - msg, catacurses::window() ); + inact_mod_map = draw_mod_list( w_list, startsel[0], cursel[0], current_tab.mods, + active_header == 0, msg, catacurses::window(), recalc_start ); // Draw active mods - draw_mod_list( w_active, startsel[1], cursel[1], active_mod_order, active_header == 1, - _( "--NO ACTIVE MODS--" ), w_shift ); + act_mod_map = draw_mod_list( w_active, startsel[1], cursel[1], active_mod_order, + active_header == 1, _( "--NO ACTIVE MODS--" ), w_shift, recalc_start ); } ); const auto set_filter = [&]() { @@ -1176,6 +1279,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, while( tab_output == 0 ) { ui_manager::redraw(); + recalc_start = false; const int next_header = ( active_header == 1 ) ? 0 : 1; const int prev_header = ( active_header == 0 ) ? 1 : 0; @@ -1193,15 +1297,85 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, prev_selection; } - const std::string action = ctxt.handle_input(); + std::string action = ctxt.handle_input(); size_t recmax = active_header == 0 ? static_cast( all_tabs[iCurrentTab].mods.size() ) : static_cast( active_mod_order.size() ); size_t scroll_rate = recmax > 20 ? 10 : 3; - if( action == "DOWN" ) { + // Mouse selection + if( action == "MOUSE_MOVE" || action == "SELECT" ) { + bool found_opt = false; + sel_top_tab = 0; + cata::optional coord = ctxt.get_coordinates_text( win ); + if( !!coord ) { + // Mod tabs + bool new_val = false; + found_opt = run_for_point_in( mod_tab_map, *coord, + [&iCurrentTab, &new_val]( const std::pair> &p ) { + if( static_cast( iCurrentTab ) != p.first ) { + new_val = true; + iCurrentTab = clamp( p.first, 0, get_mod_list_tabs().size() - 1 ); + } + } ) > 0; + if( new_val ) { + active_header = 0; + startsel[0] = 0; + cursel[0] = 0; + recalc_start = true; + } + } + if( !found_opt && !!coord ) { + // Top tabs + found_opt = run_for_point_in( top_tab_map, *coord, + [&sel_top_tab]( const std::pair> &p ) { + sel_top_tab = p.first; + } ) > 0; + if( found_opt ) { + if( action == "SELECT" ) { + tab_output = sel_top_tab; + } + } + } + if( !found_opt ) { + // Inactive mod list + coord = ctxt.get_coordinates_text( w_list ); + if( !!coord ) { + found_opt = run_for_point_in( inact_mod_map, *coord, + [&cursel]( const std::pair> &p ) { + cursel[0] = p.first; + } ); + } + if( found_opt ) { + active_header = 0; + if( action == "SELECT" ) { + action = "CONFIRM"; + } + } + } + if( !found_opt ) { + // Active mod list + coord = ctxt.get_coordinates_text( w_active ); + if( !!coord ) { + found_opt = run_for_point_in( act_mod_map, *coord, + [&cursel]( const std::pair> &p ) { + cursel[1] = p.first; + } ); + } + if( found_opt ) { + active_header = 1; + if( action == "SELECT" ) { + action = "CONFIRM"; + } + } + } + } + + if( action == "DOWN" || action == "SCROLL_DOWN" ) { selection = next_selection; - } else if( action == "UP" ) { + recalc_start = true; + } else if( action == "UP" || action == "SCROLL_UP" ) { selection = prev_selection; + recalc_start = true; } else if( action == "PAGE_DOWN" ) { if( selection == recmax - 1 ) { selection = 0; @@ -1210,6 +1384,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, } else { selection += +scroll_rate; } + recalc_start = true; } else if( action == "PAGE_UP" ) { if( selection == 0 ) { selection = recmax - 1; @@ -1218,10 +1393,13 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, } else { selection += -scroll_rate; } + recalc_start = true; } else if( action == "RIGHT" ) { active_header = next_header; + recalc_start = true; } else if( action == "LEFT" ) { active_header = prev_header; + recalc_start = true; } else if( action == "CONFIRM" ) { const std::vector ¤t_tab_mods = all_tabs[iCurrentTab].mods; if( active_header == 0 && !current_tab_mods.empty() ) { @@ -1240,10 +1418,12 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, if( active_header == 1 && active_mod_order.size() > 1 ) { mman_ui->try_shift( '+', cursel[1], active_mod_order ); } + recalc_start = true; } else if( action == "REMOVE_MOD" ) { if( active_header == 1 && active_mod_order.size() > 1 ) { mman_ui->try_shift( '-', cursel[1], active_mod_order ); } + recalc_start = true; } else if( action == "NEXT_CATEGORY_TAB" ) { if( active_header == 0 ) { if( ++iCurrentTab >= get_mod_list_tabs().size() ) { @@ -1252,6 +1432,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, startsel[0] = 0; cursel[0] = 0; + recalc_start = true; } } else if( action == "PREV_CATEGORY_TAB" ) { @@ -1262,6 +1443,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, startsel[0] = 0; cursel[0] = 0; + recalc_start = true; } } else if( action == "NEXT_TAB" ) { tab_output = 1; @@ -1280,6 +1462,7 @@ int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, tab_output = -999; } else if( action == "FILTER" ) { set_filter(); + recalc_start = true; } // RESOLVE INPUTS if( last_selection != selection ) { @@ -1326,10 +1509,13 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL input_context ctxt( "WORLDGEN_CONFIRM_DIALOG", keyboard_mode::keychar ); // dialog actions - ctxt.register_action( "QUIT" ); + ctxt.register_action( "WORLDGEN_CONFIRM.QUIT" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "PICK_RANDOM_WORLDNAME" ); + // mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); // string input popup actions ctxt.register_action( "TEXT.LEFT" ); ctxt.register_action( "TEXT.RIGHT" ); @@ -1366,9 +1552,11 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL bool noname = false; std::string worldname = world->world_name; + std::map> tab_map; + size_t sel_top_tab = 2; ui.on_redraw( [&]( const ui_adaptor & ) { - draw_worldgen_tabs( win, 2 ); + tab_map = draw_worldgen_tabs( win, sel_top_tab ); wnoutrefresh( win ); werase( w_confirmation ); @@ -1398,7 +1586,19 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL worldname = spopup.query_string( false ); const std::string action = ctxt.input_to_action( ctxt.get_raw_input() ); - if( action == "NEXT_TAB" ) { + if( action == "MOUSE_MOVE" || action == "SELECT" ) { + sel_top_tab = 2; + cata::optional coord = ctxt.get_coordinates_text( win ); + if( !!coord ) { + int cnt = run_for_point_in( tab_map, *coord, + [&sel_top_tab]( const std::pair> &p ) { + sel_top_tab = p.first; + } ); + if( cnt > 0 && action == "SELECT" && sel_top_tab != 2 ) { + return sel_top_tab - 2; + } + } + } else if( action == "NEXT_TAB" ) { if( worldname.empty() ) { noname = true; ui_manager::redraw(); @@ -1424,7 +1624,7 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL return -1; } else if( action == "PICK_RANDOM_WORLDNAME" ) { world->world_name = worldname = pick_random_name(); - } else if( action == "QUIT" && ( !on_quit || on_quit() ) ) { + } else if( action == "WORLDGEN_CONFIRM.QUIT" && ( !on_quit || on_quit() ) ) { world->world_name = worldname; return -999; } @@ -1496,7 +1696,8 @@ void worldfactory::draw_modselection_borders( const catacurses::window &win, wnoutrefresh( win ); } -void worldfactory::draw_worldgen_tabs( const catacurses::window &w, size_t current ) +std::map> worldfactory::draw_worldgen_tabs( + const catacurses::window &w, size_t current ) { werase( w ); @@ -1511,8 +1712,10 @@ void worldfactory::draw_worldgen_tabs( const catacurses::window &w, size_t curre std::for_each( tab_strings_translated.begin(), tab_strings_translated.end(), []( std::string & str )->void { str = _( str ); } ); - draw_tabs( w, tab_strings_translated, current ); + std::map> tab_map = + draw_tabs( w, tab_strings_translated, current ); draw_border_below_tabs( w ); + return tab_map; } bool worldfactory::valid_worldname( const std::string &name, bool automated ) diff --git a/src/worldfactory.h b/src/worldfactory.h index 370af0a665880..4f9ed67dcae1b 100644 --- a/src/worldfactory.h +++ b/src/worldfactory.h @@ -10,6 +10,7 @@ #include #include +#include "cuboid_rectangle.h" #include "options.h" #include "pimpl.h" #include "type_id.h" @@ -128,7 +129,8 @@ class worldfactory */ void delete_world( const std::string &worldname, bool delete_folder ); - static void draw_worldgen_tabs( const catacurses::window &w, size_t current ); + static std::map> draw_worldgen_tabs( const catacurses::window &w, + size_t current ); void show_active_world_mods( const std::vector &world_mods ); private: @@ -145,9 +147,10 @@ class worldfactory const std::function &on_quit ); void draw_modselection_borders( const catacurses::window &win, const input_context &ctxtp ); - void draw_mod_list( const catacurses::window &w, int &start, size_t cursor, - const std::vector &mods, bool is_active_list, const std::string &text_if_empty, - const catacurses::window &w_shift ); + std::map> draw_mod_list( const catacurses::window &w, int &start, + size_t cursor, const std::vector &mods, + bool is_active_list, const std::string &text_if_empty, + const catacurses::window &w_shift, bool recalc_start ); WORLDPTR add_world( std::unique_ptr retworld );