carb/input/InputUtils.h
File members: carb/input/InputUtils.h
// Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../logging/Log.h"
#include "IInput.h"
#include <map>
#include <string>
#include <cstring>
#include <functional>
namespace carb
{
namespace input
{
// Name Mapping //
#ifndef DOXYGEN_BUILD
static constexpr struct
{
DeviceType ident;
const char* name;
} g_deviceTypeToName[] = {
# define CARBINPUT_DEVICETYPE_NAME(a, b) { (a), (b) },
# include "detail/DeviceTypeName.h"
# undef CARBINPUT_DEVICETYPE_NAME
};
static_assert(CARB_COUNTOF(g_deviceTypeToName) == unsigned(DeviceType::eCount), "Add missing entries");
static constexpr struct
{
KeyboardInput ident;
const char* name;
} kKeyboardInputCodeName[] = {
# define CARBINPUT_KEYBOARDINPUT_NAME(a, b) { (a), (b) },
# include "detail/KeyboardInputName.h"
# undef CARBINPUT_KEYBOARDINPUT_NAME
};
static_assert(CARB_COUNTOF(kKeyboardInputCodeName) == unsigned(KeyboardInput::eCount), "Add missing entries");
static constexpr struct
{
KeyboardModifierFlags ident;
const char* name;
} kModifierFlagName[] = {
# define CARBINPUT_KEYBOARDMODIFIERFLAG_NAME(a, b) { (a), (b) },
# include "detail/KeyboardModifierFlagName.h"
# undef CARBINPUT_KEYBOARDMODIFIERFLAG_NAME
};
static_assert(CARB_COUNTOF(kModifierFlagName) == kKeyboardModifierFlagCount, "Add missing entries");
static constexpr struct
{
MouseInput ident;
const char* name;
} kMouseInputCodeName[] = {
# define CARBINPUT_MOUSEINPUT_NAME(a, b) { (a), (b) },
# include "detail/MouseInputName.h"
# undef CARBINPUT_MOUSEINPUT_NAME
};
static_assert(CARB_COUNTOF(kMouseInputCodeName) == unsigned(MouseInput::eCount), "Add missing entries");
static constexpr struct
{
GamepadInput ident;
const char* name;
} kGamepadInputCodeName[] = {
# define CARBINPUT_GAMEPADINPUT_NAME(a, b) { (a), (b) },
# include "detail/GamepadInputName.h"
# undef CARBINPUT_GAMEPADINPUT_NAME
};
static_assert(CARB_COUNTOF(kGamepadInputCodeName) == unsigned(GamepadInput::eCount), "Add missing entries");
#endif
inline const char* getDeviceTypeString(DeviceType deviceType)
{
// doxygen seems to incorrectly detect this macro as a 'member' of something (the function?!)
// and warns about it even if it _is_ documented or marked as '@private'. Similar patterns
// with macros are also used elsewhere so it's not clear why these uses here are problematic.
// The only way to avoid the warning seems to be with "#ifndef DOXYGEN_SHOULD_SKIP_THIS".
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_DEVICETYPE_NAME(a, b) \
case (a): \
return (b);
#endif
switch (deviceType)
{
#include "detail/DeviceTypeName.h"
default:
return "Unknown";
}
#undef CARBINPUT_DEVICETYPE_NAME
}
inline DeviceType getDeviceTypeFromString(const char* deviceTypeString)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_DEVICETYPE_NAME(a, b) \
case carb::fnv1aHash(b): \
return (a);
#endif
switch (carb::hashString(deviceTypeString))
{
#include "detail/DeviceTypeName.h"
default:
return DeviceType::eUnknown;
}
#undef CARBINPUT_DEVICETYPE_NAME
}
inline const char* getKeyboardInputString(KeyboardInput key)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_KEYBOARDINPUT_NAME(a, b) \
case (a): \
return (b);
#endif
switch (key)
{
#include "detail/KeyboardInputName.h"
default:
return "";
}
#undef CARBINPUT_KEYBOARDINPUT_NAME
}
inline KeyboardInput getKeyboardInputFromString(const char* inputString)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_KEYBOARDINPUT_NAME(a, b) \
case carb::fnv1aHash(b): \
return (a);
#endif
switch (carb::hashString(inputString))
{
#include "detail/KeyboardInputName.h"
default:
return KeyboardInput::eUnknown;
}
#undef CARBINPUT_KEYBOARDINPUT_NAME
}
inline const char* getModifierFlagString(KeyboardModifierFlags flag)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_KEYBOARDMODIFIERFLAG_NAME(a, b) \
case (a): \
return (b);
#endif
switch (flag)
{
#include "detail/KeyboardModifierFlagName.h"
default:
return "";
}
#undef CARBINPUT_KEYBOARDMODIFIERFLAG_NAME
}
inline KeyboardModifierFlags getModifierFlagFromString(const char* inputString)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_KEYBOARDMODIFIERFLAG_NAME(a, b) \
case carb::fnv1aHash(b): \
return (a);
#endif
switch (carb::hashString(inputString))
{
#include "detail/KeyboardModifierFlagName.h"
default:
return 0;
}
#undef CARBINPUT_KEYBOARDMODIFIERFLAG_NAME
}
constexpr char kDeviceNameSeparator[] = "::";
constexpr char kModifierSeparator[] = " + ";
inline std::string getModifierFlagsString(KeyboardModifierFlags mod)
{
std::string res = "";
for (const auto& desc : kModifierFlagName)
{
const auto& flag = desc.ident;
if ((mod & flag) != flag)
continue;
if (!res.empty())
res += kModifierSeparator;
res += desc.name;
}
return res;
}
inline KeyboardModifierFlags getModifierFlagsFromString(const char* modString)
{
KeyboardModifierFlags res = KeyboardModifierFlags(0);
const size_t kModifierSeparatorSize = strlen(kModifierSeparator);
std::string modifierNameString;
const char* modifierName = modString;
while (true)
{
const char* modifierNameEnd = strstr(modifierName, kModifierSeparator);
if (modifierNameEnd)
{
modifierNameString = std::string(modifierName, modifierNameEnd - modifierName);
}
else
{
modifierNameString = std::string(modifierName);
}
KeyboardModifierFlags mod = getModifierFlagFromString(modifierNameString.c_str());
if (mod)
{
res = (KeyboardModifierFlags)((uint32_t)res | (uint32_t)mod);
}
else
{
CARB_LOG_VERBOSE("Unknown hotkey modifier encountered: %s in %s", modifierNameString.c_str(), modString);
}
if (!modifierNameEnd)
{
break;
}
modifierName = modifierNameEnd;
modifierName += kModifierSeparatorSize;
}
return res;
}
inline const char* getMouseInputString(MouseInput key)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_MOUSEINPUT_NAME(a, b) \
case (a): \
return (b);
#endif
switch (key)
{
#include "detail/MouseInputName.h"
default:
return "";
}
#undef CARBINPUT_MOUSEINPUT_NAME
}
inline MouseInput getMouseInputFromString(const char* inputString)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_MOUSEINPUT_NAME(a, b) \
case carb::fnv1aHash(b): \
return (a);
#endif
switch (carb::hashString(inputString))
{
#include "detail/MouseInputName.h"
default:
return MouseInput::eCount;
}
#undef CARBINPUT_MOUSEINPUT_NAME
}
inline const char* getGamepadInputString(GamepadInput key)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_GAMEPADINPUT_NAME(a, b) \
case (a): \
return (b);
#endif
switch (key)
{
#include "detail/GamepadInputName.h"
default:
return "";
}
#undef CARBINPUT_GAMEPADINPUT_NAME
}
inline GamepadInput getGamepadInputFromString(const char* inputString)
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
# define CARBINPUT_GAMEPADINPUT_NAME(a, b) \
case carb::fnv1aHash(b): \
return (a);
#endif
switch (carb::hashString(inputString))
{
#include "detail/GamepadInputName.h"
default:
return GamepadInput::eCount;
}
#undef CARBINPUT_GAMEPADINPUT_NAME
}
enum class PreviousButtonState
{
kUp,
kDown
};
inline PreviousButtonState toPreviousButtonState(bool wasDown)
{
return wasDown ? PreviousButtonState::kDown : PreviousButtonState::kUp;
}
enum class CurrentButtonState
{
kUp,
kDown
};
inline CurrentButtonState toCurrentButtonState(bool isDown)
{
return isDown ? CurrentButtonState::kDown : CurrentButtonState::kUp;
}
inline ButtonFlags toButtonFlags(PreviousButtonState previousButtonState, CurrentButtonState currentButtonState)
{
ButtonFlags flags = 0;
if (currentButtonState == CurrentButtonState::kDown)
{
flags = kButtonFlagStateDown;
if (previousButtonState == PreviousButtonState::kUp)
flags |= kButtonFlagTransitionDown;
}
else
{
flags = kButtonFlagStateUp;
if (previousButtonState == PreviousButtonState::kDown)
flags |= kButtonFlagTransitionUp;
}
return flags;
}
inline std::string getDeviceNameString(DeviceType deviceType, const char* deviceId)
{
if ((size_t)deviceType >= (size_t)DeviceType::eCount)
return "";
std::string result = getDeviceTypeString(deviceType);
if (deviceId)
{
result.append("[");
result.append(deviceId);
result.append("]");
}
return result;
}
inline void parseDeviceNameString(const char* deviceName, DeviceType* deviceType, std::string* deviceId)
{
if (!deviceName)
{
CARB_LOG_WARN("parseDeviceNameString: Empty device name");
if (deviceType)
{
*deviceType = DeviceType::eCount;
}
return;
}
const char* deviceIdString = strstr(deviceName, "[");
if (deviceType)
{
if (deviceIdString)
{
std::string deviceTypeString(deviceName, deviceIdString - deviceName);
*deviceType = getDeviceTypeFromString(deviceTypeString.c_str());
}
else
{
*deviceType = getDeviceTypeFromString(deviceName);
}
}
if (deviceId)
{
if (deviceIdString)
{
const char* deviceNameEnd = deviceIdString + strlen(deviceIdString);
*deviceId = std::string(deviceIdString + 1, deviceNameEnd - deviceIdString - 2);
}
else
{
*deviceId = "";
}
}
}
inline bool getDeviceInputFromString(const char* deviceInputString,
DeviceType* deviceTypeOut,
KeyboardInput* keyboardInputOut,
MouseInput* mouseInputOut,
GamepadInput* gamepadInputOut,
std::string* deviceIdOut = nullptr)
{
if (!deviceTypeOut)
return false;
const char* deviceInputStringTrimmed = deviceInputString;
// Skip initial spaces
while (*deviceInputStringTrimmed == ' ')
++deviceInputStringTrimmed;
// Skip device name
const char* inputNameString = strstr(deviceInputStringTrimmed, kDeviceNameSeparator);
std::string deviceName;
// No device name specified - fall back
if (!inputNameString)
inputNameString = deviceInputStringTrimmed;
else
{
deviceName = std::string(deviceInputStringTrimmed, inputNameString - deviceInputStringTrimmed);
const size_t kDeviceNameSeparatorLen = strlen(kDeviceNameSeparator);
inputNameString += kDeviceNameSeparatorLen;
}
parseDeviceNameString(deviceName.c_str(), deviceTypeOut, deviceIdOut);
if ((*deviceTypeOut == DeviceType::eKeyboard) && keyboardInputOut)
{
KeyboardInput keyboardInput = getKeyboardInputFromString(inputNameString);
*keyboardInputOut = keyboardInput;
return (keyboardInput != KeyboardInput::eCount);
}
if ((*deviceTypeOut == DeviceType::eMouse) && mouseInputOut)
{
MouseInput mouseInput = getMouseInputFromString(inputNameString);
*mouseInputOut = mouseInput;
return (mouseInput != MouseInput::eCount);
}
if ((*deviceTypeOut == DeviceType::eGamepad) && gamepadInputOut)
{
GamepadInput gamepadInput = getGamepadInputFromString(inputNameString);
*gamepadInputOut = gamepadInput;
return (gamepadInput != GamepadInput::eCount);
}
return false;
}
inline ActionMappingDesc getActionMappingDescFromString(const char* hotkeyString, std::string* deviceId)
{
const size_t kModifierSeparatorSize = strlen(kModifierSeparator);
ActionMappingDesc actionMappingDesc;
actionMappingDesc.keyboard = nullptr;
actionMappingDesc.mouse = nullptr;
actionMappingDesc.gamepad = nullptr;
actionMappingDesc.modifiers = (KeyboardModifierFlags)0;
std::string modifierNameString;
const char* modifierName = hotkeyString;
while (true)
{
const char* modifierNameEnd = strstr(modifierName, kModifierSeparator);
if (modifierNameEnd)
{
modifierNameString = std::string(modifierName, modifierNameEnd - modifierName);
}
else
{
modifierNameString = std::string(modifierName);
}
KeyboardModifierFlags mod = getModifierFlagFromString(modifierNameString.c_str());
if (mod)
{
actionMappingDesc.modifiers = (KeyboardModifierFlags)((uint32_t)actionMappingDesc.modifiers | (uint32_t)mod);
}
else
{
getDeviceInputFromString(modifierNameString.c_str(), &actionMappingDesc.deviceType,
&actionMappingDesc.keyboardInput, &actionMappingDesc.mouseInput,
&actionMappingDesc.gamepadInput, deviceId);
}
if (!modifierNameEnd)
{
break;
}
modifierName = modifierNameEnd;
modifierName += kModifierSeparatorSize;
}
return actionMappingDesc;
}
inline std::string getStringFromActionMappingDesc(const ActionMappingDesc& actionMappingDesc,
const char* deviceName = nullptr)
{
std::string result = getModifierFlagsString(actionMappingDesc.modifiers);
if (!result.empty())
{
result.append(kModifierSeparator);
}
if (deviceName)
{
result.append(deviceName);
}
else
{
result.append(getDeviceTypeString(actionMappingDesc.deviceType));
}
result.append(kDeviceNameSeparator);
switch (actionMappingDesc.deviceType)
{
case DeviceType::eKeyboard:
{
result.append(getKeyboardInputString(actionMappingDesc.keyboardInput));
break;
}
case DeviceType::eMouse:
{
result.append(getMouseInputString(actionMappingDesc.mouseInput));
break;
}
case DeviceType::eGamepad:
{
result.append(getGamepadInputString(actionMappingDesc.gamepadInput));
break;
}
default:
{
break;
}
}
return result;
}
inline bool setDefaultActionMapping(IInput* input,
ActionMappingSetHandle actionMappingSet,
const char* actionName,
const ActionMappingDesc& desc)
{
size_t actionMappingsCount = input->getActionMappingCount(actionMappingSet, actionName);
if (actionMappingsCount > 0)
{
return false;
}
input->addActionMapping(actionMappingSet, actionName, desc);
return true;
}
template <typename Functor>
inline SubscriptionId subscribeToKeyboardEvents(IInput* input, Keyboard* keyboard, Functor&& functor)
{
return input->subscribeToKeyboardEvents(
keyboard,
[](const KeyboardEvent& evt, void* userData) -> bool { return (*static_cast<Functor*>(userData))(evt); },
&functor);
}
template <typename Functor>
inline SubscriptionId subscribeToMouseEvents(IInput* input, Mouse* mouse, Functor&& functor)
{
return input->subscribeToMouseEvents(
mouse, [](const MouseEvent& evt, void* userData) -> bool { return (*static_cast<Functor*>(userData))(evt); },
&functor);
}
template <typename Functor>
inline SubscriptionId subscribeToGamepadEvents(IInput* input, Gamepad* gamepad, Functor&& functor)
{
return input->subscribeToGamepadEvents(
gamepad, [](const GamepadEvent& evt, void* userData) -> bool { return (*static_cast<Functor*>(userData))(evt); },
&functor);
}
template <typename Functor>
inline SubscriptionId subscribeToGamepadConnectionEvents(IInput* input, Functor&& functor)
{
return input->subscribeToGamepadConnectionEvents(
[](const GamepadConnectionEvent& evt, void* userData) { (*static_cast<Functor*>(userData))(evt); }, &functor);
}
template <typename Functor>
inline SubscriptionId subscribeToActionEvents(IInput* input,
ActionMappingSetHandle actionMappingSet,
const char* actionName,
Functor&& functor)
{
return input->subscribeToActionEvents(
actionMappingSet, actionName,
[](const ActionEvent& evt, void* userData) -> bool { return (*static_cast<Functor*>(userData))(evt); }, &functor);
}
template <typename Callable>
inline void filterBufferedEvents(IInput* input, Callable&& callable)
{
using Func = std::decay_t<Callable>;
input->filterBufferedEvents(
[](InputEvent& evt, void* userData) { return (*static_cast<Func*>(userData))(evt); }, &callable);
}
} // namespace input
} // namespace carb