mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: add keybinding manager for customizable action shortcuts
Implement KeybindingManager singleton class to support: - Storing and loading keybinding configuration from ini files - Querying whether an action's keybinding was pressed - Runtime rebinding of actions to different keys - Default keybinding set: C=Character, I=Inventory, S=Spellbook, K=Talents, L=Quests, M=Minimap, Esc=Settings, Enter=Chat This is the foundation for user-customizable keybindings. Integration with UI controls and replacement of hard-coded ImGui::IsKeyPressed calls will follow in subsequent improvements.
This commit is contained in:
parent
79c8d93c45
commit
e6741f815a
3 changed files with 338 additions and 0 deletions
|
|
@ -550,6 +550,7 @@ set(WOWEE_SOURCES
|
||||||
src/ui/quest_log_screen.cpp
|
src/ui/quest_log_screen.cpp
|
||||||
src/ui/spellbook_screen.cpp
|
src/ui/spellbook_screen.cpp
|
||||||
src/ui/talent_screen.cpp
|
src/ui/talent_screen.cpp
|
||||||
|
src/ui/keybinding_manager.cpp
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
|
@ -653,6 +654,7 @@ set(WOWEE_HEADERS
|
||||||
include/ui/inventory_screen.hpp
|
include/ui/inventory_screen.hpp
|
||||||
include/ui/spellbook_screen.hpp
|
include/ui/spellbook_screen.hpp
|
||||||
include/ui/talent_screen.hpp
|
include/ui/talent_screen.hpp
|
||||||
|
include/ui/keybinding_manager.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(WOWEE_PLATFORM_SOURCES)
|
set(WOWEE_PLATFORM_SOURCES)
|
||||||
|
|
|
||||||
82
include/ui/keybinding_manager.hpp
Normal file
82
include/ui/keybinding_manager.hpp
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#ifndef WOWEE_KEYBINDING_MANAGER_HPP
|
||||||
|
#define WOWEE_KEYBINDING_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace wowee::ui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages keybinding configuration for in-game actions.
|
||||||
|
* Supports loading/saving from config files and runtime rebinding.
|
||||||
|
*/
|
||||||
|
class KeybindingManager {
|
||||||
|
public:
|
||||||
|
enum class Action {
|
||||||
|
TOGGLE_CHARACTER_SCREEN,
|
||||||
|
TOGGLE_INVENTORY,
|
||||||
|
TOGGLE_SPELLBOOK,
|
||||||
|
TOGGLE_TALENTS,
|
||||||
|
TOGGLE_QUESTS,
|
||||||
|
TOGGLE_MINIMAP,
|
||||||
|
TOGGLE_SETTINGS,
|
||||||
|
TOGGLE_CHAT,
|
||||||
|
ACTION_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static KeybindingManager& getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an action's keybinding was just pressed.
|
||||||
|
* Uses ImGui::IsKeyPressed() internally with the bound key.
|
||||||
|
*/
|
||||||
|
bool isActionPressed(Action action, bool repeat = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently bound key for an action.
|
||||||
|
*/
|
||||||
|
ImGuiKey getKeyForAction(Action action) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebind an action to a different key.
|
||||||
|
*/
|
||||||
|
void setKeyForAction(Action action, ImGuiKey key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all keybindings to defaults.
|
||||||
|
*/
|
||||||
|
void resetToDefaults();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load keybindings from config file.
|
||||||
|
*/
|
||||||
|
void loadFromConfigFile(const std::string& filePath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save keybindings to config file.
|
||||||
|
*/
|
||||||
|
void saveToConfigFile(const std::string& filePath) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get human-readable name for an action.
|
||||||
|
*/
|
||||||
|
static const char* getActionName(Action action);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all actions for iteration.
|
||||||
|
*/
|
||||||
|
static constexpr int getActionCount() { return static_cast<int>(Action::ACTION_COUNT); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
KeybindingManager();
|
||||||
|
|
||||||
|
std::unordered_map<int, ImGuiKey> bindings_; // action -> key
|
||||||
|
|
||||||
|
void initializeDefaults();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace wowee::ui
|
||||||
|
|
||||||
|
#endif // WOWEE_KEYBINDING_MANAGER_HPP
|
||||||
254
src/ui/keybinding_manager.cpp
Normal file
254
src/ui/keybinding_manager.cpp
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
#include "ui/keybinding_manager.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace wowee::ui {
|
||||||
|
|
||||||
|
KeybindingManager& KeybindingManager::getInstance() {
|
||||||
|
static KeybindingManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeybindingManager::KeybindingManager() {
|
||||||
|
initializeDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeybindingManager::initializeDefaults() {
|
||||||
|
// Set default keybindings
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_CHARACTER_SCREEN)] = ImGuiKey_C;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_INVENTORY)] = ImGuiKey_I;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_SPELLBOOK)] = ImGuiKey_S;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_TALENTS)] = ImGuiKey_K;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_QUESTS)] = ImGuiKey_L;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_MINIMAP)] = ImGuiKey_M;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_SETTINGS)] = ImGuiKey_Escape;
|
||||||
|
bindings_[static_cast<int>(Action::TOGGLE_CHAT)] = ImGuiKey_Enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KeybindingManager::isActionPressed(Action action, bool repeat) {
|
||||||
|
auto it = bindings_.find(static_cast<int>(action));
|
||||||
|
if (it == bindings_.end()) return false;
|
||||||
|
return ImGui::IsKeyPressed(it->second, repeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiKey KeybindingManager::getKeyForAction(Action action) const {
|
||||||
|
auto it = bindings_.find(static_cast<int>(action));
|
||||||
|
if (it == bindings_.end()) return ImGuiKey_None;
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeybindingManager::setKeyForAction(Action action, ImGuiKey key) {
|
||||||
|
bindings_[static_cast<int>(action)] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeybindingManager::resetToDefaults() {
|
||||||
|
bindings_.clear();
|
||||||
|
initializeDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* KeybindingManager::getActionName(Action action) {
|
||||||
|
switch (action) {
|
||||||
|
case Action::TOGGLE_CHARACTER_SCREEN: return "Character Screen";
|
||||||
|
case Action::TOGGLE_INVENTORY: return "Inventory";
|
||||||
|
case Action::TOGGLE_SPELLBOOK: return "Spellbook";
|
||||||
|
case Action::TOGGLE_TALENTS: return "Talents";
|
||||||
|
case Action::TOGGLE_QUESTS: return "Quests";
|
||||||
|
case Action::TOGGLE_MINIMAP: return "Minimap";
|
||||||
|
case Action::TOGGLE_SETTINGS: return "Settings";
|
||||||
|
case Action::TOGGLE_CHAT: return "Chat";
|
||||||
|
case Action::ACTION_COUNT: break;
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeybindingManager::loadFromConfigFile(const std::string& filePath) {
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "[KeybindingManager] Failed to open config file: " << filePath << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
bool inKeybindingsSection = false;
|
||||||
|
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
// Trim whitespace
|
||||||
|
size_t start = line.find_first_not_of(" \t\r\n");
|
||||||
|
size_t end = line.find_last_not_of(" \t\r\n");
|
||||||
|
if (start == std::string::npos) continue;
|
||||||
|
line = line.substr(start, end - start + 1);
|
||||||
|
|
||||||
|
// Check for section header
|
||||||
|
if (line == "[Keybindings]") {
|
||||||
|
inKeybindingsSection = true;
|
||||||
|
continue;
|
||||||
|
} else if (line[0] == '[') {
|
||||||
|
inKeybindingsSection = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inKeybindingsSection || line.empty() || line[0] == ';' || line[0] == '#') continue;
|
||||||
|
|
||||||
|
// Parse key=value pair
|
||||||
|
size_t eqPos = line.find('=');
|
||||||
|
if (eqPos == std::string::npos) continue;
|
||||||
|
|
||||||
|
std::string action = line.substr(0, eqPos);
|
||||||
|
std::string keyStr = line.substr(eqPos + 1);
|
||||||
|
|
||||||
|
// Trim key string
|
||||||
|
size_t kStart = keyStr.find_first_not_of(" \t");
|
||||||
|
size_t kEnd = keyStr.find_last_not_of(" \t");
|
||||||
|
if (kStart != std::string::npos) {
|
||||||
|
keyStr = keyStr.substr(kStart, kEnd - kStart + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map action name to enum (simplified mapping)
|
||||||
|
int actionIdx = -1;
|
||||||
|
if (action == "toggle_character_screen") actionIdx = static_cast<int>(Action::TOGGLE_CHARACTER_SCREEN);
|
||||||
|
else if (action == "toggle_inventory") actionIdx = static_cast<int>(Action::TOGGLE_INVENTORY);
|
||||||
|
else if (action == "toggle_spellbook") actionIdx = static_cast<int>(Action::TOGGLE_SPELLBOOK);
|
||||||
|
else if (action == "toggle_talents") actionIdx = static_cast<int>(Action::TOGGLE_TALENTS);
|
||||||
|
else if (action == "toggle_quests") actionIdx = static_cast<int>(Action::TOGGLE_QUESTS);
|
||||||
|
else if (action == "toggle_minimap") actionIdx = static_cast<int>(Action::TOGGLE_MINIMAP);
|
||||||
|
else if (action == "toggle_settings") actionIdx = static_cast<int>(Action::TOGGLE_SETTINGS);
|
||||||
|
else if (action == "toggle_chat") actionIdx = static_cast<int>(Action::TOGGLE_CHAT);
|
||||||
|
|
||||||
|
if (actionIdx < 0) continue;
|
||||||
|
|
||||||
|
// Parse key string to ImGuiKey (simple mapping of common keys)
|
||||||
|
ImGuiKey key = ImGuiKey_None;
|
||||||
|
if (keyStr.length() == 1) {
|
||||||
|
// Single character key (A-Z, 0-9)
|
||||||
|
char c = keyStr[0];
|
||||||
|
if (c >= 'A' && c <= 'Z') {
|
||||||
|
key = static_cast<ImGuiKey>(ImGuiKey_A + (c - 'A'));
|
||||||
|
} else if (c >= '0' && c <= '9') {
|
||||||
|
key = static_cast<ImGuiKey>(ImGuiKey_0 + (c - '0'));
|
||||||
|
}
|
||||||
|
} else if (keyStr == "Escape") {
|
||||||
|
key = ImGuiKey_Escape;
|
||||||
|
} else if (keyStr == "Enter") {
|
||||||
|
key = ImGuiKey_Enter;
|
||||||
|
} else if (keyStr == "Tab") {
|
||||||
|
key = ImGuiKey_Tab;
|
||||||
|
} else if (keyStr == "Backspace") {
|
||||||
|
key = ImGuiKey_Backspace;
|
||||||
|
} else if (keyStr == "Space") {
|
||||||
|
key = ImGuiKey_Space;
|
||||||
|
} else if (keyStr == "Delete") {
|
||||||
|
key = ImGuiKey_Delete;
|
||||||
|
} else if (keyStr == "Home") {
|
||||||
|
key = ImGuiKey_Home;
|
||||||
|
} else if (keyStr == "End") {
|
||||||
|
key = ImGuiKey_End;
|
||||||
|
} else if (keyStr.find("F") == 0 && keyStr.length() <= 3) {
|
||||||
|
// F1-F12 keys
|
||||||
|
int fNum = std::stoi(keyStr.substr(1));
|
||||||
|
if (fNum >= 1 && fNum <= 12) {
|
||||||
|
key = static_cast<ImGuiKey>(ImGuiKey_F1 + (fNum - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != ImGuiKey_None) {
|
||||||
|
bindings_[actionIdx] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
std::cout << "[KeybindingManager] Loaded keybindings from " << filePath << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeybindingManager::saveToConfigFile(const std::string& filePath) const {
|
||||||
|
std::ifstream inFile(filePath);
|
||||||
|
std::string content;
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
// Read existing file, removing [Keybindings] section if it exists
|
||||||
|
bool inKeybindingsSection = false;
|
||||||
|
if (inFile.is_open()) {
|
||||||
|
while (std::getline(inFile, line)) {
|
||||||
|
if (line == "[Keybindings]") {
|
||||||
|
inKeybindingsSection = true;
|
||||||
|
continue;
|
||||||
|
} else if (line[0] == '[') {
|
||||||
|
inKeybindingsSection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inKeybindingsSection) {
|
||||||
|
content += line + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new Keybindings section
|
||||||
|
content += "[Keybindings]\n";
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
Action action;
|
||||||
|
const char* name;
|
||||||
|
} actionMap[] = {
|
||||||
|
{Action::TOGGLE_CHARACTER_SCREEN, "toggle_character_screen"},
|
||||||
|
{Action::TOGGLE_INVENTORY, "toggle_inventory"},
|
||||||
|
{Action::TOGGLE_SPELLBOOK, "toggle_spellbook"},
|
||||||
|
{Action::TOGGLE_TALENTS, "toggle_talents"},
|
||||||
|
{Action::TOGGLE_QUESTS, "toggle_quests"},
|
||||||
|
{Action::TOGGLE_MINIMAP, "toggle_minimap"},
|
||||||
|
{Action::TOGGLE_SETTINGS, "toggle_settings"},
|
||||||
|
{Action::TOGGLE_CHAT, "toggle_chat"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [action, nameStr] : actionMap) {
|
||||||
|
auto it = bindings_.find(static_cast<int>(action));
|
||||||
|
if (it == bindings_.end()) continue;
|
||||||
|
|
||||||
|
ImGuiKey key = it->second;
|
||||||
|
std::string keyStr;
|
||||||
|
|
||||||
|
// Convert ImGuiKey to string
|
||||||
|
if (key >= ImGuiKey_A && key <= ImGuiKey_Z) {
|
||||||
|
keyStr += static_cast<char>('A' + (key - ImGuiKey_A));
|
||||||
|
} else if (key >= ImGuiKey_0 && key <= ImGuiKey_9) {
|
||||||
|
keyStr += static_cast<char>('0' + (key - ImGuiKey_0));
|
||||||
|
} else if (key == ImGuiKey_Escape) {
|
||||||
|
keyStr = "Escape";
|
||||||
|
} else if (key == ImGuiKey_Enter) {
|
||||||
|
keyStr = "Enter";
|
||||||
|
} else if (key == ImGuiKey_Tab) {
|
||||||
|
keyStr = "Tab";
|
||||||
|
} else if (key == ImGuiKey_Backspace) {
|
||||||
|
keyStr = "Backspace";
|
||||||
|
} else if (key == ImGuiKey_Space) {
|
||||||
|
keyStr = "Space";
|
||||||
|
} else if (key == ImGuiKey_Delete) {
|
||||||
|
keyStr = "Delete";
|
||||||
|
} else if (key == ImGuiKey_Home) {
|
||||||
|
keyStr = "Home";
|
||||||
|
} else if (key == ImGuiKey_End) {
|
||||||
|
keyStr = "End";
|
||||||
|
} else if (key >= ImGuiKey_F1 && key <= ImGuiKey_F12) {
|
||||||
|
keyStr = "F" + std::to_string(1 + (key - ImGuiKey_F1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keyStr.empty()) {
|
||||||
|
content += nameStr;
|
||||||
|
content += "=";
|
||||||
|
content += keyStr;
|
||||||
|
content += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back to file
|
||||||
|
std::ofstream outFile(filePath);
|
||||||
|
if (outFile.is_open()) {
|
||||||
|
outFile << content;
|
||||||
|
outFile.close();
|
||||||
|
std::cout << "[KeybindingManager] Saved keybindings to " << filePath << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cerr << "[KeybindingManager] Failed to write config file: " << filePath << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace wowee::ui
|
||||||
Loading…
Add table
Add a link
Reference in a new issue