diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 655b20cb..12154c06 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -8,6 +8,7 @@ #include "ui/quest_log_screen.hpp" #include "ui/spellbook_screen.hpp" #include "ui/talent_screen.hpp" +#include "ui/keybinding_manager.hpp" #include #include #include @@ -111,6 +112,10 @@ private: bool pendingMinimapNpcDots = false; bool pendingSeparateBags = true; bool pendingAutoLoot = false; + + // Keybinding customization + int pendingRebindAction = -1; // -1 = not rebinding, otherwise action index + bool awaitingKeyPress = false; bool pendingUseOriginalSoundtrack = true; bool pendingShowActionBar2 = true; // Show second action bar above main bar float pendingActionBar2OffsetX = 0.0f; // Horizontal offset from default center position diff --git a/include/ui/keybinding_manager.hpp b/include/ui/keybinding_manager.hpp index b3afcda0..e4987798 100644 --- a/include/ui/keybinding_manager.hpp +++ b/include/ui/keybinding_manager.hpp @@ -23,6 +23,8 @@ public: TOGGLE_MINIMAP, TOGGLE_SETTINGS, TOGGLE_CHAT, + TOGGLE_GUILD_ROSTER, + TOGGLE_DUNGEON_FINDER, ACTION_COUNT }; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index a139f15f..c228bc70 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6402,8 +6402,8 @@ void GameScreen::renderLfgProposalPopup(game::GameHandler& gameHandler) { } void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) { - // O key toggle (WoW default Social/Guild keybind) - if (!ImGui::GetIO().WantCaptureKeyboard && ImGui::IsKeyPressed(ImGuiKey_O)) { + // Guild Roster toggle (customizable keybind) + if (!ImGui::GetIO().WantCaptureKeyboard && KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_GUILD_ROSTER)) { showGuildRoster_ = !showGuildRoster_; if (showGuildRoster_) { // Open friends tab directly if not in guild @@ -9180,6 +9180,108 @@ void GameScreen::renderSettingsWindow() { ImGui::EndTabItem(); } + // ============================================================ + // CONTROLS TAB + // ============================================================ + if (ImGui::BeginTabItem("Controls")) { + ImGui::Spacing(); + + ImGui::Text("Keybindings"); + ImGui::Separator(); + + auto& km = ui::KeybindingManager::getInstance(); + int numActions = km.getActionCount(); + + for (int i = 0; i < numActions; ++i) { + auto action = static_cast(i); + const char* actionName = km.getActionName(action); + ImGuiKey currentKey = km.getKeyForAction(action); + + // Display current binding + ImGui::Text("%s:", actionName); + ImGui::SameLine(200); + + // Get human-readable key name (basic implementation) + const char* keyName = "Unknown"; + if (currentKey >= ImGuiKey_A && currentKey <= ImGuiKey_Z) { + static char keyBuf[16]; + snprintf(keyBuf, sizeof(keyBuf), "%c", 'A' + (currentKey - ImGuiKey_A)); + keyName = keyBuf; + } else if (currentKey >= ImGuiKey_0 && currentKey <= ImGuiKey_9) { + static char keyBuf[16]; + snprintf(keyBuf, sizeof(keyBuf), "%c", '0' + (currentKey - ImGuiKey_0)); + keyName = keyBuf; + } else if (currentKey == ImGuiKey_Escape) { + keyName = "Escape"; + } else if (currentKey == ImGuiKey_Enter) { + keyName = "Enter"; + } else if (currentKey == ImGuiKey_Tab) { + keyName = "Tab"; + } else if (currentKey == ImGuiKey_Space) { + keyName = "Space"; + } else if (currentKey >= ImGuiKey_F1 && currentKey <= ImGuiKey_F12) { + static char keyBuf[16]; + snprintf(keyBuf, sizeof(keyBuf), "F%d", 1 + (currentKey - ImGuiKey_F1)); + keyName = keyBuf; + } + + ImGui::Text("[%s]", keyName); + + // Rebind button + ImGui::SameLine(350); + if (ImGui::Button(awaitingKeyPress && pendingRebindAction == i ? "Waiting..." : "Rebind", ImVec2(100, 0))) { + pendingRebindAction = i; + awaitingKeyPress = true; + } + } + + // Handle key press during rebinding + if (awaitingKeyPress && pendingRebindAction >= 0) { + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Text("Press any key to bind to this action (Esc to cancel)..."); + + // Check for any key press + bool foundKey = false; + ImGuiKey newKey = ImGuiKey_None; + for (int k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; ++k) { + if (ImGui::IsKeyPressed(static_cast(k), false)) { + if (k == ImGuiKey_Escape) { + // Cancel rebinding + awaitingKeyPress = false; + pendingRebindAction = -1; + foundKey = true; + break; + } + newKey = static_cast(k); + foundKey = true; + break; + } + } + + if (foundKey && newKey != ImGuiKey_None) { + auto action = static_cast(pendingRebindAction); + km.setKeyForAction(action, newKey); + awaitingKeyPress = false; + pendingRebindAction = -1; + saveSettings(); + } + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button("Reset to Defaults", ImVec2(-1, 0))) { + km.resetToDefaults(); + awaitingKeyPress = false; + pendingRebindAction = -1; + saveSettings(); + } + + ImGui::EndTabItem(); + } + // ============================================================ // CHAT TAB // ============================================================ @@ -10063,6 +10165,11 @@ void GameScreen::saveSettings() { out << "chat_autojoin_lfg=" << (chatAutoJoinLFG_ ? 1 : 0) << "\n"; out << "chat_autojoin_local=" << (chatAutoJoinLocal_ ? 1 : 0) << "\n"; + out.close(); + + // Save keybindings to the same config file (appends [Keybindings] section) + KeybindingManager::getInstance().saveToConfigFile(path); + LOG_INFO("Settings saved to ", path); } @@ -10176,6 +10283,10 @@ void GameScreen::loadSettings() { else if (key == "chat_autojoin_local") chatAutoJoinLocal_ = (std::stoi(val) != 0); } catch (...) {} } + + // Load keybindings from the same config file + KeybindingManager::getInstance().loadFromConfigFile(path); + LOG_INFO("Settings loaded from ", path); } @@ -11551,8 +11662,8 @@ void GameScreen::renderZoneText() { // Dungeon Finder window (toggle with hotkey or bag-bar button) // --------------------------------------------------------------------------- void GameScreen::renderDungeonFinderWindow(game::GameHandler& gameHandler) { - // Toggle on I key when not typing - if (!chatInputActive && ImGui::IsKeyPressed(ImGuiKey_I, false)) { + // Toggle Dungeon Finder (customizable keybind) + if (!chatInputActive && KeybindingManager::getInstance().isActionPressed(KeybindingManager::Action::TOGGLE_DUNGEON_FINDER)) { showDungeonFinder_ = !showDungeonFinder_; } diff --git a/src/ui/keybinding_manager.cpp b/src/ui/keybinding_manager.cpp index 461d3412..ff04cc58 100644 --- a/src/ui/keybinding_manager.cpp +++ b/src/ui/keybinding_manager.cpp @@ -24,6 +24,8 @@ void KeybindingManager::initializeDefaults() { bindings_[static_cast(Action::TOGGLE_MINIMAP)] = ImGuiKey_M; bindings_[static_cast(Action::TOGGLE_SETTINGS)] = ImGuiKey_Escape; bindings_[static_cast(Action::TOGGLE_CHAT)] = ImGuiKey_Enter; + bindings_[static_cast(Action::TOGGLE_GUILD_ROSTER)] = ImGuiKey_O; + bindings_[static_cast(Action::TOGGLE_DUNGEON_FINDER)] = ImGuiKey_J; // Originally I, reassigned to avoid conflict } bool KeybindingManager::isActionPressed(Action action, bool repeat) { @@ -57,6 +59,8 @@ const char* KeybindingManager::getActionName(Action action) { case Action::TOGGLE_MINIMAP: return "Minimap"; case Action::TOGGLE_SETTINGS: return "Settings"; case Action::TOGGLE_CHAT: return "Chat"; + case Action::TOGGLE_GUILD_ROSTER: return "Guild Roster / Social"; + case Action::TOGGLE_DUNGEON_FINDER: return "Dungeon Finder"; case Action::ACTION_COUNT: break; } return "Unknown"; @@ -114,6 +118,8 @@ void KeybindingManager::loadFromConfigFile(const std::string& filePath) { else if (action == "toggle_minimap") actionIdx = static_cast(Action::TOGGLE_MINIMAP); else if (action == "toggle_settings") actionIdx = static_cast(Action::TOGGLE_SETTINGS); else if (action == "toggle_chat") actionIdx = static_cast(Action::TOGGLE_CHAT); + else if (action == "toggle_guild_roster") actionIdx = static_cast(Action::TOGGLE_GUILD_ROSTER); + else if (action == "toggle_dungeon_finder") actionIdx = static_cast(Action::TOGGLE_DUNGEON_FINDER); if (actionIdx < 0) continue; @@ -198,6 +204,8 @@ void KeybindingManager::saveToConfigFile(const std::string& filePath) const { {Action::TOGGLE_MINIMAP, "toggle_minimap"}, {Action::TOGGLE_SETTINGS, "toggle_settings"}, {Action::TOGGLE_CHAT, "toggle_chat"}, + {Action::TOGGLE_GUILD_ROSTER, "toggle_guild_roster"}, + {Action::TOGGLE_DUNGEON_FINDER, "toggle_dungeon_finder"}, }; for (const auto& [action, nameStr] : actionMap) {