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