From 3933854f4bcb6e8c7f52b3f742fecbb00979d721 Mon Sep 17 00:00:00 2001 From: RenechCDDA <84619419+RenechCDDA@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:07:59 -0400 Subject: [PATCH] Follower rules UI: Hotkeys,focus follows selection, can close window by pressing close button. Also fixed this in mission ui --- src/mission_ui.cpp | 2 +- src/npctalk_rules.cpp | 183 +++++++++++++++++++++++++++++++++--------- src/npctalk_rules.h | 3 +- 3 files changed, 146 insertions(+), 42 deletions(-) diff --git a/src/mission_ui.cpp b/src/mission_ui.cpp index ae7402d497496..68ad4fb11238f 100644 --- a/src/mission_ui.cpp +++ b/src/mission_ui.cpp @@ -104,7 +104,7 @@ void mission_ui::draw_mission_ui() p_impl.last_action = ctxt.handle_input(); - if( p_impl.last_action == "QUIT" ) { + if( p_impl.last_action == "QUIT" || !p_impl.get_is_open() ) { break; } } diff --git a/src/npctalk_rules.cpp b/src/npctalk_rules.cpp index 91021bafb1361..81ae27bc5d793 100644 --- a/src/npctalk_rules.cpp +++ b/src/npctalk_rules.cpp @@ -58,12 +58,14 @@ void follower_rules_ui::draw_follower_rules_ui( npc *guy ) input_context ctxt; follower_rules_ui_impl p_impl; p_impl.set_npc_pointer_to( guy ); + p_impl.input_ptr = &ctxt; ctxt.register_navigate_ui_list(); ctxt.register_action( "MOUSE_MOVE" ); ctxt.register_action( "CONFIRM", to_translation( "Set, toggle, or reset selected rule" ) ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "QUIT" ); + ctxt.register_action( "ANY_INPUT" ); // This is still bizarrely necessary for imgui ctxt.set_timeout( 10 ); @@ -73,7 +75,7 @@ void follower_rules_ui::draw_follower_rules_ui( npc *guy ) p_impl.last_action = ctxt.handle_input(); - if( p_impl.last_action == "QUIT" ) { + if( p_impl.last_action == "QUIT" || !p_impl.get_is_open() ) { break; } } @@ -92,6 +94,16 @@ std::string follower_rules_ui_impl::get_parsed( std::string initial_string ) } +void follower_rules_ui_impl::print_hotkey( input_event &hotkey ) +{ + // Padding spaces intentional, so it's obvious that the fake "Hotkey:" header refers to these. + // TODO: Just reimplement everything as a table...? Would avoid this sort of thing. + // But surely not *everything* needs to be a table... + draw_colored_text( string_format( " %s ", static_cast( hotkey.sequence.front() ) ), + c_green ); + ImGui::SameLine(); +} + void follower_rules_ui_impl::draw_controls() { if( !guy ) { @@ -102,20 +114,34 @@ void follower_rules_ui_impl::draw_controls() ImGui::SetWindowSize( ImVec2( window_width, window_height ), ImGuiCond_Once ); + ImGui::InvisibleButton( "TOP_OF_WINDOW_KB_SCROLL_SELECTABLE", ImVec2() ); + + const hotkey_queue &hotkeys = hotkey_queue::alphabets(); + input_event assigned_hotkey = input_ptr->first_unassigned_hotkey( hotkeys ); + input_event pressed_key = input_ptr->get_raw_input(); + draw_colored_text( string_format( _( "Rules for your follower, %s" ), guy->disp_name() ) ); ImGui::Separator(); + draw_colored_text( _( "Hotkey:" ) ); + ImGui::NewLine(); - if( ImGui::Button( _( "Default ALL" ) ) ) { - npc_follower_rules default_values; //Call the constructor and let *it* tell us what the default is - guy->rules = default_values; + print_hotkey( assigned_hotkey ); + if( ImGui::Button( _( "Default ALL" ) ) || pressed_key == assigned_hotkey ) { + ImGui::SetKeyboardFocusHere( -1 ); + if( query_yn( _( "Really set all of this follower's rules to their default values?" ) ) ) { + npc_follower_rules default_values; //Call the constructor and let *it* tell us what the default is + guy->rules = default_values; + } } int rule_number = 0; /* Handle all of our regular, boolean rules */ for( std::pair rule_data : ally_rule_strs ) { ImGui::PushID( rule_number ); + assigned_hotkey = input_ptr->next_unassigned_hotkey( hotkeys, assigned_hotkey ); rule_number++; ImGui::NewLine(); + print_hotkey( assigned_hotkey ); const ally_rule_data &this_rule = rule_data.second; bool rule_enabled = false; std::string rules_text; @@ -125,7 +151,8 @@ void follower_rules_ui_impl::draw_controls() } else { rules_text = string_format( "%s", get_parsed( this_rule.rule_false_text ) ); } - if( ImGui::Button( _( "Change" ) ) ) { + if( ImGui::Button( _( "Change" ) ) || pressed_key == assigned_hotkey ) { + ImGui::SetKeyboardFocusHere( -1 ); guy->rules.toggle_flag( this_rule.rule ); guy->rules.toggle_specific_override_state( this_rule.rule, !rule_enabled ); } @@ -138,44 +165,27 @@ void follower_rules_ui_impl::draw_controls() draw_colored_text( rules_text ); ImGui::PopID(); } - /* Shows CBM rules, but only if the character has bionics*/ - if( !guy->get_bionics().empty() ) { - ImGui::Separator(); - ImGui::NewLine(); - for( std::pair recharge_rule : recharge_map ) { - ImGui::PushID( rule_number ); - rule_number++; - int percent = static_cast( recharge_rule.first ); - std::string button_label = std::to_string( percent ) + "%"; - ImGui::SameLine(); - if( ImGui::Button( button_label.c_str() ) ) { - guy->rules.cbm_recharge = recharge_rule.first; - } - ImGui::PopID(); - } - ImGui::NewLine(); - draw_colored_text( string_format( "%s", get_parsed( recharge_map[guy->rules.cbm_recharge] ) ) ); - - ImGui::Separator(); - ImGui::NewLine(); - for( std::pair reserve_rule : reserve_map ) { - ImGui::PushID( rule_number ); - rule_number++; - int percent = static_cast( reserve_rule.first ); - std::string button_label = std::to_string( percent ) + "%"; - ImGui::SameLine(); - if( ImGui::Button( button_label.c_str() ) ) { - guy->rules.cbm_reserve = reserve_rule.first; - } - ImGui::PopID(); - } - ImGui::NewLine(); - draw_colored_text( string_format( "%s", get_parsed( reserve_map[guy->rules.cbm_reserve] ) ) ); - } // Engagement rules require their own set of buttons, each instruction is unique ImGui::Separator(); ImGui::NewLine(); + assigned_hotkey = input_ptr->next_unassigned_hotkey( hotkeys, assigned_hotkey ); + print_hotkey( assigned_hotkey ); + if( ImGui::InvisibleButton( "ENGAGEMENT_RULES", ImVec2() ) || pressed_key == assigned_hotkey ) { + combat_engagement &rule = guy->rules.engagement; + auto &this_map = engagement_rules; + // Wrapping the map. Means we can press the hotkey repeatedly to cycle through all of + // the available settings for this rule. + if( this_map.upper_bound( rule ) == this_map.end() ) { + // Then we have the *last* entry the map, so wrap to the first element + rule = this_map.begin()->first; + } else { + // Assign the next possible value contained in the map. + rule = this_map.upper_bound( rule )->first; + } + int offset = distance( this_map.begin(), this_map.find( rule ) ); + ImGui::SetKeyboardFocusHere( offset ); + } int engagement_rule_number = 0; for( std::pair engagement_rule : engagement_rules ) { ImGui::PushID( rule_number ); @@ -186,15 +196,32 @@ void follower_rules_ui_impl::draw_controls() ImGui::SameLine(); if( ImGui::Button( button_label.c_str() ) ) { guy->rules.engagement = engagement_rule.first; + ImGui::SetKeyboardFocusHere( -1 ); } ImGui::PopID(); } + ImGui::SameLine(); + draw_colored_text( _( "Engagement rules" ), c_white ); ImGui::NewLine(); draw_colored_text( string_format( "%s", get_parsed( engagement_rules[guy->rules.engagement] ) ) ); - // Lastly, aiming rule also has a non-boolean set of values + // Aiming rule also has a non-boolean set of values ImGui::Separator(); ImGui::NewLine(); + assigned_hotkey = input_ptr->next_unassigned_hotkey( hotkeys, assigned_hotkey ); + print_hotkey( assigned_hotkey ); + // This button implementation is a boilerplate copy of the engagement rules one. Look there for comments + if( ImGui::InvisibleButton( "AIMING_RULES", ImVec2() ) || pressed_key == assigned_hotkey ) { + aim_rule &rule = guy->rules.aim; + auto &this_map = aim_rule_map; + if( this_map.upper_bound( rule ) == this_map.end() ) { + rule = this_map.begin()->first; + } else { + rule = this_map.upper_bound( rule )->first; + } + int offset = distance( this_map.begin(), this_map.find( rule ) ); + ImGui::SetKeyboardFocusHere( offset ); + } int aim_rule_number = 0; for( std::pair aiming_rule : aim_rule_map ) { ImGui::PushID( rule_number ); @@ -205,9 +232,85 @@ void follower_rules_ui_impl::draw_controls() ImGui::SameLine(); if( ImGui::Button( button_label.c_str() ) ) { guy->rules.aim = aiming_rule.first; + ImGui::SetKeyboardFocusHere( -1 ); } ImGui::PopID(); } + ImGui::SameLine(); + draw_colored_text( _( "Aiming rules" ), c_white ); ImGui::NewLine(); draw_colored_text( string_format( "%s", get_parsed( aim_rule_map[guy->rules.aim] ) ) ); + + /* Shows CBM rules, but only if the character has bionics. Must be last because it will + only appear sometimes and we don't want hotkeys to be different depending on whether + the character has bionics. That's bad for muscle memory! */ + if( !guy->get_bionics().empty() ) { + ImGui::Separator(); + ImGui::NewLine(); + assigned_hotkey = input_ptr->next_unassigned_hotkey( hotkeys, assigned_hotkey ); + print_hotkey( assigned_hotkey ); + // This button implementation is a boilerplate copy of the engagement rules one. Look there for comments + if( ImGui::InvisibleButton( "RECHARGE_RULES", ImVec2() ) || pressed_key == assigned_hotkey ) { + cbm_recharge_rule &rule = guy->rules.cbm_recharge; + auto &this_map = recharge_map; + if( this_map.upper_bound( rule ) == this_map.end() ) { + rule = this_map.begin()->first; + } else { + rule = this_map.upper_bound( rule )->first; + } + int offset = distance( this_map.begin(), this_map.find( rule ) ); + ImGui::SetKeyboardFocusHere( offset ); + } + for( std::pair recharge_rule : recharge_map ) { + ImGui::PushID( rule_number ); + rule_number++; + int percent = static_cast( recharge_rule.first ); + std::string button_label = std::to_string( percent ) + "%"; + ImGui::SameLine(); + if( ImGui::Button( button_label.c_str() ) ) { + guy->rules.cbm_recharge = recharge_rule.first; + ImGui::SetKeyboardFocusHere( -1 ); + } + ImGui::PopID(); + } + ImGui::SameLine(); + draw_colored_text( _( "CBM recharging rules" ), c_white ); + ImGui::NewLine(); + draw_colored_text( string_format( "%s", get_parsed( recharge_map[guy->rules.cbm_recharge] ) ) ); + + ImGui::Separator(); + ImGui::NewLine(); + assigned_hotkey = input_ptr->next_unassigned_hotkey( hotkeys, assigned_hotkey ); + print_hotkey( assigned_hotkey ); + // This button implementation is a boilerplate copy of the engagement rules one. Look there for comments + if( ImGui::InvisibleButton( "RESERVE_RULES", ImVec2() ) || pressed_key == assigned_hotkey ) { + cbm_reserve_rule &rule = guy->rules.cbm_reserve; + auto &this_map = reserve_map; + if( this_map.upper_bound( rule ) == this_map.end() ) { + rule = this_map.begin()->first; + } else { + rule = this_map.upper_bound( rule )->first; + } + int offset = distance( this_map.begin(), this_map.find( rule ) ); + ImGui::SetKeyboardFocusHere( offset ); + } + for( std::pair reserve_rule : reserve_map ) { + ImGui::PushID( rule_number ); + rule_number++; + int percent = static_cast( reserve_rule.first ); + std::string button_label = std::to_string( percent ) + "%"; + ImGui::SameLine(); + if( ImGui::Button( button_label.c_str() ) ) { + guy->rules.cbm_reserve = reserve_rule.first; + ImGui::SetKeyboardFocusHere( -1 ); + } + ImGui::PopID(); + } + ImGui::SameLine(); + draw_colored_text( _( "CBM reserve rules" ), c_white ); + ImGui::NewLine(); + draw_colored_text( string_format( "%s", get_parsed( reserve_map[guy->rules.cbm_reserve] ) ) ); + } + + ImGui::InvisibleButton( "BOTTOM_OF_WINDOW_KB_SCROLL_SELECTABLE", ImVec2() ); } diff --git a/src/npctalk_rules.h b/src/npctalk_rules.h index e824fd541fa84..49e5b0ac0d537 100644 --- a/src/npctalk_rules.h +++ b/src/npctalk_rules.h @@ -33,6 +33,7 @@ class follower_rules_ui_impl : public cataimgui::window { public: std::string last_action; + input_context *input_ptr = nullptr; explicit follower_rules_ui_impl() : cataimgui::window( _( "Rules for your follower" ), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove ) { } @@ -42,10 +43,10 @@ class follower_rules_ui_impl : public cataimgui::window private: npc *guy = nullptr; std::string get_parsed( std::string initial_string ); + void print_hotkey( input_event &hotkey ); size_t window_width = str_width_to_pixels( TERMX ) / 2; size_t window_height = str_height_to_pixels( TERMY ) / 2; - size_t table_column_width = window_width / 2; protected: void draw_controls() override;