Step 351: Keybinding Registry (12/12 tests)

This commit is contained in:
Bill
2026-02-16 08:56:45 -07:00
parent 3416c1ea88
commit fc5483e0b6
4 changed files with 336 additions and 1 deletions

View File

@@ -7,6 +7,7 @@
#include <map>
#include <optional>
#include <algorithm>
#include <fstream>
#include <nlohmann/json.hpp>
enum class KeyModifier : uint8_t {
@@ -49,13 +50,39 @@ struct KeyCombo {
if (hasModifier(modifiers, KeyModifier::Alt)) result += "\xE2\x8C\xA5"; // ⌥
if (hasModifier(modifiers, KeyModifier::Shift)) result += "\xE2\x87\xA7"; // ⇧
if (hasModifier(modifiers, KeyModifier::Super)) result += "\xE2\x8C\x98"; // ⌘
result += key;
result += keyToSymbol(key);
} else {
result = toString(); // Linux/Windows uses text form
}
return result;
}
static std::string normalizeKey(const std::string& in) {
std::string k = in;
std::transform(k.begin(), k.end(), k.begin(),
[](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return k;
}
static std::string keyToSymbol(const std::string& keyName) {
std::string k = normalizeKey(keyName);
if (k == "ENTER" || k == "RETURN") return "\xE2\x86\xB5"; // ↵
if (k == "ESC" || k == "ESCAPE") return "\xE2\x8E\x8B"; // ⎋
if (k == "TAB") return "\xE2\x87\xA5"; // ⇥
if (k == "SPACE") return "\xE2\x90\xA0"; // ␠
if (k == "BACKSPACE") return "\xE2\x8C\xAB"; // ⌫
if (k == "DELETE") return "\xE2\x8C\xA6"; // ⌦
return keyName;
}
std::string toSymbolsAuto() const {
#ifdef __APPLE__
return toSymbols(true);
#else
return toSymbols(false);
#endif
}
bool operator==(const KeyCombo& other) const {
return modifiers == other.modifiers && key == other.key;
}
@@ -87,6 +114,23 @@ struct KeyCombo {
static KeyCombo shiftFkey(const std::string& k) {
return {static_cast<uint8_t>(KeyModifier::Shift), k};
}
struct InputState {
bool ctrl = false;
bool alt = false;
bool shift = false;
bool super = false;
std::string key;
};
bool matches(const InputState& input) const {
if (normalizeKey(key) != normalizeKey(input.key)) return false;
if (hasModifier(modifiers, KeyModifier::Ctrl) != input.ctrl) return false;
if (hasModifier(modifiers, KeyModifier::Alt) != input.alt) return false;
if (hasModifier(modifiers, KeyModifier::Shift) != input.shift) return false;
if (hasModifier(modifiers, KeyModifier::Super) != input.super) return false;
return true;
}
};
struct KeyAction {
@@ -141,6 +185,7 @@ public:
}
std::map<std::string, KeyCombo> getAllBindings() const { return bindings_; }
std::map<std::string, KeyCombo> getAll() const { return bindings_; }
int actionCount() const { return static_cast<int>(actions_.size()); }
int bindingCount() const { return static_cast<int>(bindings_.size()); }
@@ -216,6 +261,33 @@ public:
}
}
bool saveToJson(const std::string& path) const {
std::ofstream out(path);
if (!out.good()) return false;
out << toJson().dump(2);
return out.good();
}
bool loadFromJsonFile(const std::string& path) {
std::ifstream in(path);
if (!in.good()) return false;
nlohmann::json j;
try {
in >> j;
loadFromJson(j);
return true;
} catch (...) {
return false;
}
}
std::optional<std::string> processInput(const KeyCombo::InputState& input) const {
for (const auto& [actionId, combo] : bindings_) {
if (combo.matches(input)) return actionId;
}
return std::nullopt;
}
private:
std::map<std::string, KeyAction> actions_;
std::map<std::string, KeyCombo> bindings_;