From b2d480904cfa5a8c48ddbafef3f79ca390e9ab40 Mon Sep 17 00:00:00 2001 From: Zhilkin Serg Date: Wed, 11 Dec 2024 19:55:04 +0300 Subject: [PATCH] Migrate ma details menu to imgui --- data/raw/keybindings.json | 28 ++++ src/martialarts.cpp | 323 +++++++++++++++++++++++++------------- 2 files changed, 240 insertions(+), 111 deletions(-) diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 4d005f545b637..c917de2aa789d 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -4875,6 +4875,34 @@ "name": "Toggle NPC group", "bindings": [ { "input_method": "keyboard_any", "key": "n" } ] }, + { + "type": "keybinding", + "id": "TOGGLE_GENERAL_INFO_GROUP", + "category": "MA_DETAILS_UI", + "name": "Toggle general info group", + "bindings": [ { "input_method": "keyboard_any", "key": "g" } ] + }, + { + "type": "keybinding", + "id": "TOGGLE_BUFFS_GROUP", + "category": "MA_DETAILS_UI", + "name": "Toggle buffs group", + "bindings": [ { "input_method": "keyboard_any", "key": "b" } ] + }, + { + "type": "keybinding", + "id": "TOGGLE_TECHNIQUES_GROUP", + "category": "MA_DETAILS_UI", + "name": "Toggle techniques group", + "bindings": [ { "input_method": "keyboard_any", "key": "t" } ] + }, + { + "type": "keybinding", + "id": "TOGGLE_WEAPONS_GROUP", + "category": "MA_DETAILS_UI", + "name": "Toggle weapons group", + "bindings": [ { "input_method": "keyboard_any", "key": "w" } ] + }, { "type": "keybinding", "id": "HELP_KEYBINDINGS", diff --git a/src/martialarts.cpp b/src/martialarts.cpp index 08f0e89a9e8e7..32db72ba66598 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -2182,79 +2182,141 @@ std::string ma_technique::get_description() const return dump; } -bool ma_style_callback::key( const input_context &ctxt, const input_event &event, int entnum, - uilist * ) +class ma_details_ui { - const std::string &action = ctxt.input_to_action( event ); - if( action != "SHOW_DESCRIPTION" ) { - return false; - } - matype_id style_selected; - const size_t index = entnum; - if( index >= offset && index - offset < styles.size() ) { - style_selected = styles[index - offset]; + friend class ma_details_ui_impl; + public: + void draw_ma_details_ui( const matype_id &style_selected ); +}; + +class ma_details_ui_impl : public cataimgui::window +{ + public: + std::string last_action; + explicit ma_details_ui_impl( const matype_id &style_selected ) : cataimgui::window( + string_format( _( "Martial art style details - %s" ), + style_selected.obj().name.translated().c_str() ), + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav ) { + ma_style = style_selected; + }; + void init_data(); + + private: + void draw_ma_details_text() const; + + size_t window_width = ImGui::GetMainViewport()->Size.x * 8 / 9; + size_t window_height = ImGui::GetMainViewport()->Size.y * 8 / 9; + + bool general_info_group_collapsed = false; + bool buffs_group_collapsed = false; + bool techniques_group_collapsed = false; + bool weapons_group_collapsed = false; + + matype_id ma_style; + std::vector general_info_text; + std::map buffs_text; + std::map techniques_text; + std::map weapons_text; + int buffs_total = 0; + int weapons_total = 0; + + protected: + void draw_controls() override; +}; + +void ma_details_ui::draw_ma_details_ui( const matype_id &style_selected ) +{ + input_context ctxt( "MA_DETAILS_UI" ); + ma_details_ui_impl p_impl( style_selected ); + + p_impl.init_data(); + + ctxt.register_navigate_ui_list(); + ctxt.register_leftright(); + ctxt.register_action( "TOGGLE_GENERAL_INFO_GROUP" ); + ctxt.register_action( "TOGGLE_BUFFS_GROUP" ); + ctxt.register_action( "TOGGLE_TECHNIQUES_GROUP" ); + ctxt.register_action( "TOGGLE_WEAPONS_GROUP" ); + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "QUIT" ); + // Smooths out our handling, makes tabs load immediately after input instead of waiting for next. + ctxt.set_timeout( 10 ); + + while( true ) { + ui_manager::redraw_invalidated(); + + p_impl.last_action = ctxt.handle_input(); + + if( p_impl.last_action == "QUIT" || !p_impl.get_is_open() ) { + break; + } } - if( !style_selected.str().empty() ) { - const martialart &ma = style_selected.obj(); +} + +void ma_details_ui_impl::init_data() +{ + general_info_text.clear(); + buffs_text.clear(); + techniques_text.clear(); + weapons_text.clear(); + + buffs_total = 0; + weapons_total = 0; - std::string buffer; + if( !ma_style.str().empty() ) { + + const martialart &ma = ma_style.obj(); if( ma.force_unarmed ) { - buffer += _( "This style forces you to use unarmed strikes, even if wielding a weapon." ); - buffer += "\n"; + general_info_text.emplace_back( + _( "This style forces you to use unarmed strikes, even if wielding a weapon." ) ); } else if( ma.allow_all_weapons ) { - buffer += _( "This style can be used with all weapons." ); - buffer += "\n"; + general_info_text.emplace_back( _( "This style can be used with all weapons." ) ); } else if( ma.strictly_melee ) { - buffer += _( "This is an armed combat style." ); - buffer += "\n"; + general_info_text.emplace_back( _( "This is an armed combat style." ) ); } - buffer += "--\n"; - if( ma.arm_block_with_bio_armor_arms || ma.arm_block != 99 || - ma.leg_block_with_bio_armor_legs || ma.leg_block != 99 || + ma.leg_block_with_bio_armor_legs || ma.leg_block != 99 || ma.nonstandard_block != 99 ) { Character &u = get_player_character(); - int unarmed_skill = u.get_skill_level( skill_unarmed ); + int unarmed_skill = u.get_skill_level( skill_unarmed ); if( u.has_active_bionic( bio_cqb ) ) { unarmed_skill = BIO_CQB_LEVEL; } if( ma.arm_block_with_bio_armor_arms ) { - buffer += _( "You can arm block by installing the Arms Alloy Plating CBM" ); - buffer += "\n"; + general_info_text.emplace_back( + _( "You can arm block by installing the Arms Alloy Plating CBM" ) ); } else if( ma.arm_block != 99 ) { - buffer += string_format( - _( "You can arm block at unarmed combat: %s/%s" ), - unarmed_skill, ma.arm_block ) + "\n"; + general_info_text.emplace_back( string_format( + _( "You can arm block at unarmed combat: %s/%s" ), + unarmed_skill, ma.arm_block ) ); } if( ma.leg_block_with_bio_armor_legs ) { - buffer += _( "You can leg block by installing the Legs Alloy Plating CBM" ); - buffer += "\n"; + general_info_text.emplace_back( + _( "You can leg block by installing the Legs Alloy Plating CBM" ) ); } else if( ma.leg_block != 99 ) { - buffer += string_format( - _( "You can leg block at unarmed combat: %s/%s" ), - unarmed_skill, ma.leg_block ); - buffer += "\n"; - } - if( ma.nonstandard_block != 99 ) { - buffer += string_format( - _( "You can block with mutated limbs at unarmed combat: %s/%s" ), - unarmed_skill, ma.nonstandard_block ); - buffer += "\n"; + general_info_text.emplace_back( string_format( + _( "You can leg block at unarmed combat: %s/%s" ), + unarmed_skill, ma.leg_block ) ); + if( ma.nonstandard_block != 99 ) { + general_info_text.emplace_back( string_format( + _( "You can block with mutated limbs at unarmed combat: %s/%s" ), + unarmed_skill, ma.nonstandard_block ) ); + } } - buffer += "--\n"; } auto buff_desc = [&]( const std::string & title, const std::vector &buffs, bool passive = false ) { if( !buffs.empty() ) { - buffer += string_format( _( "
%s buffs:
" ), title ); for( const auto &buff : buffs ) { - buffer += "\n" + buff->get_description( passive ); + buffs_total++; + buffs_text[ title ] = buff->get_description( passive ); } - buffer += "--\n"; } }; @@ -2271,9 +2333,7 @@ bool ma_style_callback::key( const input_context &ctxt, const input_event &event buff_desc( _( "Get hit" ), ma.ongethit_buffs ); for( const auto &tech : ma.techniques ) { - buffer += string_format( _( "
Technique:
%s " ), - tech.obj().name ) + "\n"; - buffer += tech.obj().get_description() + "--\n"; + techniques_text[ tech.obj().name.translated() ] = tech.obj().get_description(); } // Copy set to vector for sorting @@ -2281,7 +2341,7 @@ bool ma_style_callback::key( const input_context &ctxt, const input_event &event std::copy( ma.weapons.begin(), ma.weapons.end(), std::back_inserter( valid_ma_weapons ) ); for( const itype *itp : item_controller->all() ) { const itype_id &weap_id = itp->get_id(); - if( ma.has_weapon( weap_id ) ) { + if( ma.has_weapon( weap_id ) ) { valid_ma_weapons.emplace_back( weap_id ); } } @@ -2314,9 +2374,9 @@ bool ma_style_callback::key( const input_context &ctxt, const input_event &event // Weapons that are uncategorized or not in the martial art's weapon categories weaps_by_cat[weapon_category_OTHER_INVALID_WEAP_CAT].push_back( wname ); } + weapons_total++; } - buffer += std::string( "" ) + _( "Weapons" ) + std::string( "" ) + "\n"; bool has_other_cat = false; for( auto &weaps : weaps_by_cat ) { if( weaps.first == weapon_category_OTHER_INVALID_WEAP_CAT ) { @@ -2333,83 +2393,124 @@ bool ma_style_callback::key( const input_context &ctxt, const input_event &event w_cat = weaps.first.str() + " - MISSING JSON DEFINITION"; } - buffer += std::string( "
" ) + w_cat + std::string( ":
" ); - buffer += enumerate_as_string( weaps.second ) + "\n"; + weapons_text.emplace( w_cat, enumerate_as_string( weaps.second ) ); } if( has_other_cat ) { std::vector &weaps = weaps_by_cat[weapon_category_OTHER_INVALID_WEAP_CAT]; weaps.erase( std::unique( weaps.begin(), weaps.end() ), weaps.end() ); - buffer += std::string( "
" ) + _( "OTHER" ) + std::string( ":
" ); - buffer += enumerate_as_string( weaps ) + "\n"; + weapons_text.emplace( _( "OTHER" ), enumerate_as_string( weaps ) ); } - buffer += "--\n"; } + } +} - catacurses::window w; +void ma_details_ui_impl::draw_ma_details_text() const +{ + // TODO: Need to make proper width calculations + const float window_width_in_chars = window_width * 0.4; - const std::string text = replace_colors( buffer ); - int width = 0; - int height = 0; - int iLines = 0; - int selected = 0; + if( !general_info_text.empty() && + ImGui::CollapsingHeader( _( "General info" ), + general_info_group_collapsed ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen ) ) { + for( const auto &entry : general_info_text ) { + cataimgui::draw_colored_text( entry, window_width_in_chars ); + ImGui::NewLine(); + } + } - ui_adaptor ui; - ui.on_screen_resize( [&]( ui_adaptor & ui ) { - w = catacurses::newwin( TERMY * 0.9, FULL_SCREEN_WIDTH, - point( TERMX - FULL_SCREEN_WIDTH, TERMY * 0.1 ) / 2 ); + if( !buffs_text.empty() && + ImGui::CollapsingHeader( string_format( _( "Buffs (%d in %d categories)" ), + buffs_total, buffs_text.size() ).c_str(), + buffs_group_collapsed ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen ) ) { + for( const auto &entry : buffs_text ) { + cataimgui::draw_colored_text( string_format( _( "
%s buffs:
" ), entry.first ) ); + ImGui::NewLine(); + cataimgui::draw_colored_text( entry.second, window_width_in_chars ); + ImGui::Separator(); + } + } - width = catacurses::getmaxx( w ) - 4; - height = catacurses::getmaxy( w ) - 2; + if( !techniques_text.empty() && + ImGui::CollapsingHeader( string_format( _( "Techniques (%d)" ), techniques_text.size() ).c_str(), + techniques_group_collapsed ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen ) ) { + for( const auto &entry : techniques_text ) { + cataimgui::draw_colored_text( string_format( _( "
Technique:
%s" ), + entry.first ) ); + ImGui::NewLine(); + cataimgui::draw_colored_text( entry.second, window_width_in_chars ); + ImGui::Separator(); + } + } - const auto vFolded = foldstring( text, width ); - iLines = vFolded.size(); + if( !weapons_text.empty() && + ImGui::CollapsingHeader( string_format( _( "Weapons (%d in %d categories)" ), + weapons_total, weapons_text.size() ).c_str(), + weapons_group_collapsed ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen ) ) { + for( const auto &entry : weapons_text ) { + cataimgui::draw_colored_text( string_format( _( "
%s
" ), entry.first ) ); + ImGui::NewLine(); + cataimgui::draw_colored_text( entry.second, window_width_in_chars ); + ImGui::Separator(); + } + } +} - if( iLines < height ) { - selected = 0; - } else if( selected >= iLines - height ) { - selected = iLines - height; - } +void ma_details_ui_impl::draw_controls() +{ + ImGui::SetWindowSize( ImVec2( window_width, window_height ), ImGuiCond_Once ); - ui.position_from_window( w ); - } ); - ui.mark_resize(); - - scrollbar sb; - - input_context ctxt; - sb.set_draggable( ctxt ); - ctxt.register_navigate_ui_list(); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - - ui.on_redraw( [&]( const ui_adaptor & ) { - werase( w ); - fold_and_print_from( w, point( 2, 1 ), width, selected, c_light_gray, text ); - draw_border( w, BORDER_COLOR, string_format( _( " Style: %s " ), ma.name ) ); - sb.offset_x( 0 ) - .offset_y( 1 ) - .content_size( iLines ) - .viewport_pos( selected ) - .viewport_size( height ) - .slot_color( BORDER_COLOR ) - .scroll_to_last( false ) - .apply( w ); - wnoutrefresh( w ); - } ); + if( last_action == "QUIT" ) { + return; + } else if( last_action == "TOGGLE_GENERAL_INFO_GROUP" ) { + general_info_group_collapsed = !general_info_group_collapsed; + } else if( last_action == "TOGGLE_BUFFS_GROUP" ) { + buffs_group_collapsed = !buffs_group_collapsed; + } else if( last_action == "TOGGLE_TECHNIQUES_GROUP" ) { + techniques_group_collapsed = !techniques_group_collapsed; + } else if( last_action == "TOGGLE_WEAPONS_GROUP" ) { + weapons_group_collapsed = !weapons_group_collapsed; + } else if( last_action == "UP" ) { + ImGui::SetScrollY( ImGui::GetScrollY() - ImGui::GetTextLineHeightWithSpacing() ); + } else if( last_action == "DOWN" ) { + ImGui::SetScrollY( ImGui::GetScrollY() + ImGui::GetTextLineHeightWithSpacing() ); + } else if( last_action == "LEFT" ) { + ImGui::SetScrollX( ImGui::GetScrollX() - ImGui::CalcTextSize( "x" ).x ); + } else if( last_action == "RIGHT" ) { + ImGui::SetScrollX( ImGui::GetScrollX() + ImGui::CalcTextSize( "x" ).x ); + } else if( last_action == "PAGE_UP" ) { + ImGui::SetScrollY( ImGui::GetScrollY() - window_height ); + } else if( last_action == "PAGE_DOWN" ) { + ImGui::SetScrollY( ImGui::GetScrollY() + window_height ); + } else if( last_action == "HOME" ) { + ImGui::SetScrollY( 0 ); + } else if( last_action == "END" ) { + ImGui::SetScrollY( ImGui::GetScrollMaxY() ); + } + + draw_ma_details_text(); +} + +static void show_ma_details_ui( const matype_id &style_selected ) +{ + ma_details_ui new_instance; + new_instance.draw_ma_details_ui( style_selected ); +} - do { - ui_manager::redraw(); - const size_t scroll_lines = catacurses::getmaxy( w ) - 3; - std::string action = ctxt.handle_input(); +bool ma_style_callback::key( const input_context &ctxt, const input_event &event, int entnum, + uilist * ) +{ + const std::string &action = ctxt.input_to_action( event ); + if( entnum == 0 || action != "SHOW_DESCRIPTION" ) { + return false; + } - if( action == "QUIT" ) { - break; - } else if( sb.handle_dragging( action, ctxt.get_coordinates_text( catacurses::stdscr ), - selected ) - || navigate_ui_list( action, selected, scroll_lines, iLines - height + 1, false ) ) { - // NO FURTHER ACTION REQUIRED - } - } while( true ); + matype_id style_selected; + const size_t index = entnum; + if( index >= offset && index - offset < styles.size() ) { + style_selected = styles[index - offset]; } + + show_ma_details_ui( style_selected ); + return true; }