2023-07-01 20:34:43 +00:00
|
|
|
#include "StarInput.hpp"
|
|
|
|
#include "StarAssets.hpp"
|
|
|
|
#include "StarRoot.hpp"
|
2023-07-02 00:55:25 +00:00
|
|
|
#include "StarJsonExtra.hpp"
|
2023-07-01 20:34:43 +00:00
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
const char* InputBindingConfigRoot = "modBindings";
|
|
|
|
|
|
|
|
BiMap<Key, KeyMod> const KeysToMods{
|
|
|
|
{Key::LShift, KeyMod::LShift},
|
|
|
|
{Key::RShift, KeyMod::RShift},
|
|
|
|
{Key::LCtrl, KeyMod::LCtrl},
|
|
|
|
{Key::RCtrl, KeyMod::RCtrl},
|
|
|
|
{Key::LAlt, KeyMod::LAlt},
|
|
|
|
{Key::RAlt, KeyMod::RAlt},
|
|
|
|
{Key::LGui, KeyMod::LGui},
|
|
|
|
{Key::RGui, KeyMod::RGui},
|
|
|
|
{Key::AltGr, KeyMod::AltGr},
|
|
|
|
{Key::ScrollLock, KeyMod::Scroll}
|
|
|
|
};
|
|
|
|
|
2023-07-02 00:55:25 +00:00
|
|
|
const KeyMod KeyModOptional = KeyMod::Num | KeyMod::Caps | KeyMod::Scroll;
|
|
|
|
|
|
|
|
inline bool compareKeyModLenient(KeyMod input, KeyMod test) {
|
|
|
|
input |= KeyModOptional;
|
|
|
|
test |= KeyModOptional;
|
|
|
|
return (test & input) == test;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool compareKeyMod(KeyMod input, KeyMod test) {
|
|
|
|
return (input | (KeyModOptional & ~test)) == (test | KeyModOptional);
|
|
|
|
}
|
|
|
|
|
2023-07-01 22:16:14 +00:00
|
|
|
Json keyModsToJson(KeyMod mod) {
|
|
|
|
JsonArray array;
|
|
|
|
|
2023-11-02 00:13:12 +00:00
|
|
|
if (bool(mod & KeyMod::LShift)) array.emplace_back("LShift");
|
|
|
|
if (bool(mod & KeyMod::RShift)) array.emplace_back("RShift");
|
|
|
|
if (bool(mod & KeyMod::LCtrl )) array.emplace_back("LCtrl" );
|
|
|
|
if (bool(mod & KeyMod::RCtrl )) array.emplace_back("RCtrl" );
|
|
|
|
if (bool(mod & KeyMod::LAlt )) array.emplace_back("LAlt" );
|
|
|
|
if (bool(mod & KeyMod::RAlt )) array.emplace_back("RAlt" );
|
|
|
|
if (bool(mod & KeyMod::LGui )) array.emplace_back("LGui" );
|
|
|
|
if (bool(mod & KeyMod::RGui )) array.emplace_back("RGui" );
|
|
|
|
if (bool(mod & KeyMod::Num )) array.emplace_back("Num" );
|
|
|
|
if (bool(mod & KeyMod::Caps )) array.emplace_back("Caps" );
|
|
|
|
if (bool(mod & KeyMod::AltGr )) array.emplace_back("AltGr" );
|
|
|
|
if (bool(mod & KeyMod::Scroll)) array.emplace_back("Scroll");
|
2023-07-01 22:16:14 +00:00
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
return array.empty() ? Json() : std::move(array);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Optional pointer argument to output calculated priority
|
|
|
|
KeyMod keyModsFromJson(Json const& json, uint8_t* priority = nullptr) {
|
|
|
|
KeyMod mod = KeyMod::NoMod;
|
|
|
|
if (!json.isType(Json::Type::Array))
|
|
|
|
return mod;
|
|
|
|
|
2023-07-04 10:46:26 +00:00
|
|
|
uint8_t modPriority = 0;
|
2023-07-01 22:16:14 +00:00
|
|
|
for (Json const& jMod : json.toArray()) {
|
2023-07-04 10:46:26 +00:00
|
|
|
KeyMod changedMod = mod | KeyModNames.getLeft(jMod.toString());
|
|
|
|
if (mod != changedMod) {
|
|
|
|
mod = changedMod;
|
|
|
|
++modPriority;
|
|
|
|
}
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
2023-07-04 10:46:26 +00:00
|
|
|
if (priority)
|
|
|
|
*priority = modPriority;
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
|
2023-07-01 20:34:43 +00:00
|
|
|
size_t hash<InputVariant>::operator()(InputVariant const& v) const {
|
|
|
|
size_t indexHash = hashOf(v.typeIndex());
|
|
|
|
if (auto key = v.ptr<Key>())
|
|
|
|
hashCombine(indexHash, hashOf(*key));
|
|
|
|
else if (auto mButton = v.ptr<MouseButton>())
|
|
|
|
hashCombine(indexHash, hashOf(*mButton));
|
|
|
|
else if (auto cButton = v.ptr<ControllerButton>())
|
|
|
|
hashCombine(indexHash, hashOf(*cButton));
|
|
|
|
|
|
|
|
return indexHash;
|
|
|
|
}
|
|
|
|
|
2023-07-02 00:55:25 +00:00
|
|
|
Json Input::inputEventToJson(InputEvent const& input) {
|
|
|
|
String type;
|
|
|
|
Json data;
|
|
|
|
|
|
|
|
if (auto keyDown = input.ptr<KeyDownEvent>()) {
|
|
|
|
type = "KeyDown";
|
|
|
|
data = JsonObject{
|
|
|
|
{"key", KeyNames.getRight(keyDown->key)},
|
|
|
|
{"mods", keyModsToJson(keyDown->mods)}
|
|
|
|
};
|
|
|
|
} else if (auto keyUp = input.ptr<KeyUpEvent>()) {
|
|
|
|
type = "KeyUp";
|
|
|
|
data = JsonObject{
|
|
|
|
{"key", KeyNames.getRight(keyUp->key)}
|
|
|
|
};
|
|
|
|
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
|
|
|
|
type = "MouseButtonDown";
|
|
|
|
data = JsonObject{
|
|
|
|
{"mouseButton", MouseButtonNames.getRight(mouseDown->mouseButton)},
|
|
|
|
{"mousePosition", jsonFromVec2I(mouseDown->mousePosition)}
|
|
|
|
};
|
|
|
|
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
|
|
|
|
type = "MouseButtonUp";
|
|
|
|
data = JsonObject{
|
|
|
|
{"mouseButton", MouseButtonNames.getRight(mouseUp->mouseButton)},
|
|
|
|
{"mousePosition", jsonFromVec2I(mouseUp->mousePosition)}
|
|
|
|
};
|
|
|
|
} else if (auto mouseWheel = input.ptr<MouseWheelEvent>()) {
|
|
|
|
type = "MouseWheel";
|
|
|
|
data = JsonObject{
|
2023-10-29 19:18:40 +00:00
|
|
|
{"mouseWheel", mouseWheel->mouseWheel == MouseWheel::Up ? 1 : -1},
|
2023-07-02 00:55:25 +00:00
|
|
|
{"mousePosition", jsonFromVec2I(mouseWheel->mousePosition)}
|
|
|
|
};
|
|
|
|
} else if (auto mouseMove = input.ptr<MouseMoveEvent>()) {
|
|
|
|
type = "MouseMove";
|
|
|
|
data = JsonObject{
|
|
|
|
{"mouseMove", jsonFromVec2I(mouseMove->mouseMove)},
|
|
|
|
{"mousePosition", jsonFromVec2I(mouseMove->mousePosition)}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
return JsonObject{
|
|
|
|
{"type", type},
|
|
|
|
{"data", data}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2023-07-01 22:16:14 +00:00
|
|
|
Input::Bind Input::bindFromJson(Json const& json) {
|
|
|
|
Bind bind;
|
2023-07-02 07:19:54 +00:00
|
|
|
if (json.isNull())
|
|
|
|
return bind;
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
String type = json.getString("type");
|
2023-07-02 00:55:25 +00:00
|
|
|
Json value = json.get("value", {});
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
if (type == "key") {
|
|
|
|
KeyBind keyBind;
|
|
|
|
keyBind.key = KeyNames.getLeft(value.toString());
|
|
|
|
keyBind.mods = keyModsFromJson(json.getArray("mods", {}), &keyBind.priority);
|
2024-02-19 15:55:19 +00:00
|
|
|
bind = std::move(keyBind);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
else if (type == "mouse") {
|
|
|
|
MouseBind mouseBind;
|
|
|
|
mouseBind.button = MouseButtonNames.getLeft(value.toString());
|
|
|
|
mouseBind.mods = keyModsFromJson(json.getArray("mods", {}), &mouseBind.priority);
|
2024-02-19 15:55:19 +00:00
|
|
|
bind = std::move(mouseBind);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
else if (type == "controller") {
|
|
|
|
ControllerBind controllerBind;
|
|
|
|
controllerBind.button = ControllerButtonNames.getLeft(value.toString());
|
|
|
|
controllerBind.controller = json.getUInt("controller", 0);
|
2024-02-19 15:55:19 +00:00
|
|
|
bind = std::move(controllerBind);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return bind;
|
|
|
|
}
|
|
|
|
|
|
|
|
Json Input::bindToJson(Bind const& bind) {
|
|
|
|
if (auto keyBind = bind.ptr<KeyBind>()) {
|
2023-07-02 07:19:54 +00:00
|
|
|
auto obj = JsonObject{
|
2023-07-01 22:16:14 +00:00
|
|
|
{"type", "key"},
|
2023-07-02 07:19:54 +00:00
|
|
|
{"value", KeyNames.getRight(keyBind->key)}
|
|
|
|
}; // don't want empty mods to exist as null entry
|
|
|
|
if (auto mods = keyModsToJson(keyBind->mods))
|
2024-02-19 15:55:19 +00:00
|
|
|
obj.emplace("mods", std::move(mods));
|
|
|
|
return std::move(obj);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
else if (auto mouseBind = bind.ptr<MouseBind>()) {
|
2023-07-02 07:19:54 +00:00
|
|
|
auto obj = JsonObject{
|
2023-07-01 22:16:14 +00:00
|
|
|
{"type", "mouse"},
|
2023-07-02 07:19:54 +00:00
|
|
|
{"value", MouseButtonNames.getRight(mouseBind->button)}
|
2023-07-01 22:16:14 +00:00
|
|
|
};
|
2023-07-02 07:19:54 +00:00
|
|
|
if (auto mods = keyModsToJson(mouseBind->mods))
|
2024-02-19 15:55:19 +00:00
|
|
|
obj.emplace("mods", std::move(mods));
|
|
|
|
return std::move(obj);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
else if (auto controllerBind = bind.ptr<ControllerBind>()) {
|
|
|
|
return JsonObject{
|
|
|
|
{"type", "controller"},
|
|
|
|
{"value", ControllerButtonNames.getRight(controllerBind->button)}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return Json();
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory const& parentCategory) {
|
|
|
|
category = &parentCategory;
|
2023-07-02 07:19:54 +00:00
|
|
|
id = entryId;
|
2023-07-01 22:16:14 +00:00
|
|
|
name = config.getString("name", id);
|
|
|
|
|
|
|
|
for (Json const& jBind : config.getArray("default", {})) {
|
|
|
|
try
|
|
|
|
{ defaultBinds.emplace_back(bindFromJson(jBind)); }
|
|
|
|
catch (JsonException const& e)
|
|
|
|
{ Logger::error("Binds: Error loading default bind in {}.{}: {}", parentCategory.id, id, e.what()); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
void Input::BindEntry::updated() {
|
|
|
|
auto config = Root::singleton().configuration();
|
|
|
|
|
|
|
|
JsonArray array;
|
|
|
|
for (auto const& bind : customBinds)
|
|
|
|
array.emplace_back(bindToJson(bind));
|
|
|
|
|
|
|
|
if (!config->get(InputBindingConfigRoot).isType(Json::Type::Object))
|
|
|
|
config->set(InputBindingConfigRoot, JsonObject());
|
|
|
|
|
|
|
|
String path = strf("{}.{}", InputBindingConfigRoot, category->id);
|
|
|
|
if (!config->getPath(path).isType(Json::Type::Object)) {
|
|
|
|
config->setPath(path, JsonObject{
|
2024-02-19 15:55:19 +00:00
|
|
|
{ id, std::move(array) }
|
2023-07-02 07:19:54 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
path = strf("{}.{}", path, id);
|
|
|
|
config->setPath(path, array);
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::singleton().rebuildMappings();
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::BindRef::BindRef(BindEntry& bindEntry, KeyBind& keyBind) {
|
|
|
|
entry = &bindEntry;
|
|
|
|
priority = keyBind.priority;
|
|
|
|
mods = keyBind.mods;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::BindRef::BindRef(BindEntry& bindEntry, MouseBind& mouseBind) {
|
|
|
|
entry = &bindEntry;
|
|
|
|
priority = mouseBind.priority;
|
|
|
|
mods = mouseBind.mods;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::BindRef::BindRef(BindEntry& bindEntry) {
|
|
|
|
entry = &bindEntry;
|
|
|
|
priority = 0;
|
|
|
|
mods = KeyMod::NoMod;
|
|
|
|
}
|
|
|
|
|
2023-07-01 22:16:14 +00:00
|
|
|
Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig) {
|
2023-07-02 07:19:54 +00:00
|
|
|
id = categoryId;
|
2023-07-01 22:16:14 +00:00
|
|
|
config = categoryConfig;
|
2023-07-02 00:55:25 +00:00
|
|
|
name = config.getString("name", id);
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
ConfigurationPtr userConfig = Root::singletonPtr()->configuration();
|
2023-07-02 07:19:54 +00:00
|
|
|
auto userBindings = userConfig->get(InputBindingConfigRoot);
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
for (auto& pair : config.getObject("binds", {})) {
|
|
|
|
String const& bindId = pair.first;
|
|
|
|
Json const& bindConfig = pair.second;
|
|
|
|
if (!bindConfig.isType(Json::Type::Object))
|
|
|
|
continue;
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
BindEntry& entry = entries.try_emplace(bindId, bindId, bindConfig, *this).first->second;
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
if (userBindings.isType(Json::Type::Object)) {
|
|
|
|
for (auto& jBind : userBindings.queryArray(strf("{}.{}", id, bindId), {})) {
|
|
|
|
try
|
|
|
|
{ entry.customBinds.emplace_back(bindFromJson(jBind)); }
|
|
|
|
catch (JsonException const& e)
|
|
|
|
{ Logger::error("Binds: Error loading user bind in {}.{}: {}", id, bindId, e.what()); }
|
|
|
|
}
|
|
|
|
}
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
if (entry.customBinds.empty())
|
|
|
|
entry.customBinds = entry.defaultBinds;
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-02 00:55:25 +00:00
|
|
|
List<Input::BindEntry*> Input::filterBindEntries(List<Input::BindRef> const& binds, KeyMod mods) const {
|
|
|
|
uint8_t maxPriority = 0;
|
|
|
|
List<BindEntry*> result{};
|
|
|
|
for (const BindRef& bind : binds) {
|
|
|
|
if (bind.priority < maxPriority)
|
|
|
|
break;
|
|
|
|
else if (compareKeyModLenient(mods, bind.mods)) {
|
|
|
|
maxPriority = bind.priority;
|
|
|
|
result.emplace_back(bind.entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
Input::BindEntry* Input::bindEntryPtr(String const& categoryId, String const& bindId) {
|
|
|
|
if (auto category = m_bindCategories.ptr(categoryId)) {
|
|
|
|
if (auto entry = category->entries.ptr(bindId)) {
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::BindEntry& Input::bindEntry(String const& categoryId, String const& bindId) {
|
|
|
|
if (auto ptr = bindEntryPtr(categoryId, bindId))
|
|
|
|
return *ptr;
|
|
|
|
else
|
|
|
|
throw InputException::format("Could not find bind entry {}.{}", categoryId, bindId);
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::InputState* Input::bindStatePtr(String const& categoryId, String const& bindId) {
|
|
|
|
if (auto ptr = bindEntryPtr(categoryId, bindId))
|
|
|
|
return m_bindStates.ptr(ptr);
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2023-07-01 20:34:43 +00:00
|
|
|
Input* Input::s_singleton;
|
|
|
|
|
|
|
|
Input* Input::singletonPtr() {
|
|
|
|
return s_singleton;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input& Input::singleton() {
|
|
|
|
if (!s_singleton)
|
|
|
|
throw InputException("Input::singleton() called with no Input instance available");
|
|
|
|
else
|
|
|
|
return *s_singleton;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input::Input() {
|
|
|
|
if (s_singleton)
|
|
|
|
throw InputException("Singleton Input has been constructed twice");
|
|
|
|
|
|
|
|
s_singleton = this;
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
m_pressedMods = KeyMod::NoMod;
|
|
|
|
|
2023-07-01 22:16:14 +00:00
|
|
|
reload();
|
|
|
|
|
|
|
|
m_rootReloadListener = make_shared<CallbackListener>([&]() {
|
|
|
|
reload();
|
|
|
|
});
|
|
|
|
|
|
|
|
Root::singletonPtr()->registerReloadListener(m_rootReloadListener);
|
2023-07-01 20:34:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Input::~Input() {
|
|
|
|
s_singleton = nullptr;
|
|
|
|
}
|
|
|
|
|
2023-07-02 00:55:25 +00:00
|
|
|
List<std::pair<InputEvent, bool>> const& Input::inputEventsThisFrame() const {
|
|
|
|
return m_inputEvents;
|
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
|
2023-07-01 20:34:43 +00:00
|
|
|
void Input::reset() {
|
2023-07-20 02:52:08 +00:00
|
|
|
m_inputEvents.clear();
|
|
|
|
auto eraseCond = [](auto& p) {
|
|
|
|
if (p.second.held)
|
|
|
|
p.second.reset();
|
|
|
|
return !p.second.held;
|
|
|
|
};
|
2023-07-02 07:19:54 +00:00
|
|
|
|
2023-07-20 02:52:08 +00:00
|
|
|
eraseWhere(m_keyStates, eraseCond);
|
|
|
|
eraseWhere(m_mouseStates, eraseCond);
|
|
|
|
eraseWhere(m_controllerStates, eraseCond);
|
|
|
|
eraseWhere(m_bindStates, eraseCond);
|
2023-07-01 20:34:43 +00:00
|
|
|
}
|
|
|
|
|
2023-07-02 00:55:25 +00:00
|
|
|
void Input::update() {
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
|
|
|
m_inputEvents.emplace_back(input, gameProcessed);
|
2023-07-02 07:19:54 +00:00
|
|
|
if (auto keyDown = input.ptr<KeyDownEvent>()) {
|
|
|
|
auto keyToMod = KeysToMods.rightPtr(keyDown->key);
|
|
|
|
if (keyToMod)
|
|
|
|
m_pressedMods |= *keyToMod;
|
|
|
|
|
|
|
|
if (!gameProcessed && !m_textInputActive) {
|
2023-07-20 02:52:08 +00:00
|
|
|
auto& state = m_keyStates[keyDown->key];
|
|
|
|
if (keyToMod)
|
|
|
|
state.mods |= *keyToMod;
|
|
|
|
state.press();
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
if (auto binds = m_bindMappings.ptr(keyDown->key)) {
|
|
|
|
for (auto bind : filterBindEntries(*binds, keyDown->mods))
|
|
|
|
m_bindStates[bind].press();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (auto keyUp = input.ptr<KeyUpEvent>()) {
|
|
|
|
auto keyToMod = KeysToMods.rightPtr(keyUp->key);
|
|
|
|
if (keyToMod)
|
|
|
|
m_pressedMods &= ~*keyToMod;
|
|
|
|
|
|
|
|
// We need to be able to release input even when gameProcessed is true, but only if it's already down.
|
2023-07-20 02:52:08 +00:00
|
|
|
if (auto state = m_keyStates.ptr(keyUp->key)) {
|
|
|
|
if (keyToMod)
|
|
|
|
state->mods &= ~*keyToMod;
|
2023-07-02 07:19:54 +00:00
|
|
|
state->release();
|
2023-07-20 02:52:08 +00:00
|
|
|
}
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
if (auto binds = m_bindMappings.ptr(keyUp->key)) {
|
|
|
|
for (auto& bind : *binds) {
|
|
|
|
if (auto state = m_bindStates.ptr(bind.entry))
|
|
|
|
state->release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
|
2023-11-01 21:12:21 +00:00
|
|
|
m_mousePosition = mouseDown->mousePosition;
|
2023-07-02 07:19:54 +00:00
|
|
|
if (!gameProcessed) {
|
2023-07-20 02:52:08 +00:00
|
|
|
auto& state = m_mouseStates[mouseDown->mouseButton];
|
|
|
|
state.pressPositions.append(mouseDown->mousePosition);
|
|
|
|
state.press();
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) {
|
|
|
|
for (auto bind : filterBindEntries(*binds, m_pressedMods))
|
|
|
|
m_bindStates[bind].press();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
|
2023-11-01 21:12:21 +00:00
|
|
|
m_mousePosition = mouseUp->mousePosition;
|
2023-07-20 02:52:08 +00:00
|
|
|
if (auto state = m_mouseStates.ptr(mouseUp->mouseButton)) {
|
|
|
|
state->releasePositions.append(mouseUp->mousePosition);
|
2023-07-02 07:19:54 +00:00
|
|
|
state->release();
|
2023-07-20 02:52:08 +00:00
|
|
|
}
|
2023-07-02 00:55:25 +00:00
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) {
|
|
|
|
for (auto& bind : *binds) {
|
|
|
|
if (auto state = m_bindStates.ptr(bind.entry))
|
|
|
|
state->release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-01 21:12:21 +00:00
|
|
|
else if (auto mouseMove = input.ptr<MouseMoveEvent>()) {
|
|
|
|
m_mousePosition = mouseMove->mousePosition;
|
|
|
|
}
|
2023-07-02 00:55:25 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Input::rebuildMappings() {
|
2023-07-02 07:19:54 +00:00
|
|
|
reset();
|
2023-07-02 00:55:25 +00:00
|
|
|
m_bindMappings.clear();
|
2023-07-02 07:19:54 +00:00
|
|
|
|
|
|
|
for (auto& category : m_bindCategories) {
|
|
|
|
for (auto& pair : category.second.entries) {
|
|
|
|
auto& entry = pair.second;
|
|
|
|
for (auto& bind : entry.customBinds) {
|
|
|
|
if (auto keyBind = bind.ptr<KeyBind>())
|
|
|
|
m_bindMappings[keyBind->key].emplace_back(entry, *keyBind);
|
|
|
|
if (auto mouseBind = bind.ptr<MouseBind>())
|
|
|
|
m_bindMappings[mouseBind->button].emplace_back(entry, *mouseBind);
|
|
|
|
if (auto controllerBind = bind.ptr<ControllerBind>())
|
|
|
|
m_bindMappings[controllerBind->button].emplace_back(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& pair : m_bindMappings) {
|
|
|
|
pair.second.sort([](BindRef const& a, BindRef const& b)
|
|
|
|
{ return a.priority > b.priority; });
|
|
|
|
}
|
2023-07-02 00:55:25 +00:00
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
void Input::reload() {;
|
2023-07-01 22:16:14 +00:00
|
|
|
m_bindCategories.clear();
|
|
|
|
|
2023-07-01 20:34:43 +00:00
|
|
|
auto assets = Root::singleton().assets();
|
|
|
|
|
|
|
|
for (auto& bindPath : assets->scanExtension("binds")) {
|
2023-07-01 22:16:14 +00:00
|
|
|
for (auto& pair : assets->json(bindPath).toObject()) {
|
|
|
|
String const& categoryId = pair.first;
|
|
|
|
Json const& categoryConfig = pair.second;
|
|
|
|
if (!categoryConfig.isType(Json::Type::Object))
|
|
|
|
continue;
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
m_bindCategories.try_emplace(categoryId, categoryId, categoryConfig);
|
2023-07-01 22:16:14 +00:00
|
|
|
}
|
2023-07-01 20:34:43 +00:00
|
|
|
}
|
2023-07-01 22:16:14 +00:00
|
|
|
|
|
|
|
size_t count = 0;
|
|
|
|
for (auto& pair : m_bindCategories)
|
|
|
|
count += pair.second.entries.size();
|
|
|
|
|
|
|
|
Logger::info("Binds: Loaded {} bind{}", count, count == 1 ? "" : "s");
|
2023-07-02 00:55:25 +00:00
|
|
|
|
|
|
|
rebuildMappings();
|
2023-07-01 20:34:43 +00:00
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
void Input::setTextInputActive(bool active) {
|
|
|
|
m_textInputActive = active;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) {
|
2023-07-20 02:52:08 +00:00
|
|
|
if (auto state = bindStatePtr(categoryId, bindId)) {
|
2023-07-02 07:19:54 +00:00
|
|
|
if (state->presses)
|
|
|
|
return state->presses;
|
2023-07-20 02:52:08 +00:00
|
|
|
}
|
2023-07-02 07:19:54 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Input::bindHeld(String const& categoryId, String const& bindId) {
|
|
|
|
if (auto state = bindStatePtr(categoryId, bindId))
|
|
|
|
return state->held;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) {
|
2023-07-20 02:52:08 +00:00
|
|
|
if (auto state = bindStatePtr(categoryId, bindId)) {
|
2023-07-02 07:19:54 +00:00
|
|
|
if (state->releases)
|
|
|
|
return state->releases;
|
2023-07-20 02:52:08 +00:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
2023-07-02 07:19:54 +00:00
|
|
|
|
2023-07-20 02:52:08 +00:00
|
|
|
Maybe<unsigned> Input::keyDown(Key key, Maybe<KeyMod> keyMod) {
|
|
|
|
if (auto state = m_keyStates.ptr(key)) {
|
|
|
|
if (state->presses && (!keyMod || compareKeyMod(*keyMod, state->mods)))
|
|
|
|
return state->presses;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Input::keyHeld(Key key) {
|
|
|
|
auto state = m_keyStates.ptr(key);
|
|
|
|
return state && state->held;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<unsigned> Input::keyUp(Key key) {
|
|
|
|
if (auto state = m_keyStates.ptr(key)) {
|
|
|
|
if (state->releases)
|
|
|
|
return state->releases;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<List<Vec2I>> Input::mouseDown(MouseButton button) {
|
|
|
|
if (auto state = m_mouseStates.ptr(button)) {
|
|
|
|
if (state->presses)
|
|
|
|
return state->pressPositions;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Input::mouseHeld(MouseButton button) {
|
|
|
|
auto state = m_mouseStates.ptr(button);
|
|
|
|
return state && state->held;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<List<Vec2I>> Input::mouseUp(MouseButton button) {
|
|
|
|
if (auto state = m_mouseStates.ptr(button)) {
|
|
|
|
if (state->releases)
|
|
|
|
return state->releasePositions;
|
|
|
|
}
|
2023-07-02 07:19:54 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-11-01 21:12:21 +00:00
|
|
|
Vec2I Input::mousePosition() const {
|
|
|
|
return m_mousePosition;
|
|
|
|
}
|
|
|
|
|
2023-07-02 07:19:54 +00:00
|
|
|
void Input::resetBinds(String const& categoryId, String const& bindId) {
|
|
|
|
auto& entry = bindEntry(categoryId, bindId);
|
|
|
|
|
|
|
|
entry.customBinds = entry.defaultBinds;
|
|
|
|
entry.updated();
|
|
|
|
}
|
|
|
|
|
|
|
|
Json Input::getDefaultBinds(String const& categoryId, String const& bindId) {
|
|
|
|
JsonArray array;
|
|
|
|
for (Bind const& bind : bindEntry(categoryId, bindId).defaultBinds)
|
|
|
|
array.emplace_back(bindToJson(bind));
|
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
return std::move(array);
|
2023-07-02 07:19:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Json Input::getBinds(String const& categoryId, String const& bindId) {
|
|
|
|
JsonArray array;
|
|
|
|
for (Bind const& bind : bindEntry(categoryId, bindId).customBinds)
|
|
|
|
array.emplace_back(bindToJson(bind));
|
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
return std::move(array);
|
2023-07-02 07:19:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Input::setBinds(String const& categoryId, String const& bindId, Json const& jBinds) {
|
|
|
|
auto& entry = bindEntry(categoryId, bindId);
|
|
|
|
|
|
|
|
List<Bind> binds;
|
|
|
|
for (Json const& jBind : jBinds.toArray())
|
|
|
|
binds.emplace_back(bindFromJson(jBind));
|
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
entry.customBinds = std::move(binds);
|
2023-07-02 07:19:54 +00:00
|
|
|
entry.updated();
|
|
|
|
}
|
|
|
|
|
2023-07-01 20:34:43 +00:00
|
|
|
}
|