diff options
Diffstat (limited to 'xbmc/input/joysticks/keymaps/KeyHandler.cpp')
-rw-r--r-- | xbmc/input/joysticks/keymaps/KeyHandler.cpp | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/xbmc/input/joysticks/keymaps/KeyHandler.cpp b/xbmc/input/joysticks/keymaps/KeyHandler.cpp new file mode 100644 index 0000000..cd0f30a --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeyHandler.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "KeyHandler.h" + +#include "input/IKeymap.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "input/joysticks/interfaces/IKeymapHandler.h" +#include "interfaces/IActionListener.h" + +#include <algorithm> +#include <assert.h> + +using namespace KODI; +using namespace JOYSTICK; + +#define DIGITAL_ANALOG_THRESHOLD 0.5f + +#define HOLD_TIMEOUT_MS 500 +#define REPEAT_TIMEOUT_MS 50 + +CKeyHandler::CKeyHandler(const std::string& keyName, + IActionListener* actionHandler, + const IKeymap* keymap, + IKeymapHandler* keymapHandler) + : m_keyName(keyName), + m_actionHandler(actionHandler), + m_keymap(keymap), + m_keymapHandler(keymapHandler) +{ + assert(m_actionHandler != nullptr); + assert(m_keymap != nullptr); + assert(m_keymapHandler != nullptr); + + Reset(); +} + +void CKeyHandler::Reset() +{ + m_bHeld = false; + m_magnitude = 0.0f; + m_holdStartTimeMs = 0; + m_lastHoldTimeMs = 0; + m_bActionSent = false; + m_lastActionMs = 0; + m_activeWindowId = -1; + m_lastAction = CAction(); +} + +bool CKeyHandler::OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) +{ + return OnAnalogMotion(bPressed ? 1.0f : 0.0f, holdTimeMs); +} + +bool CKeyHandler::OnAnalogMotion(float magnitude, unsigned int motionTimeMs) +{ + // Don't send deactivation event more than once + if (m_magnitude == 0.0f && magnitude == 0.0f) + return false; + + // Get actions for the key + const auto& actionGroup = m_keymap->GetActions(m_keyName); + const int windowId = actionGroup.windowId; + const auto& actions = actionGroup.actions; + + // Calculate press state + const bool bPressed = IsPressed(magnitude); + const bool bJustPressed = bPressed && !m_bHeld; + + if (bJustPressed) + { + // Reset key if just pressed + Reset(); + + // Record hold start time if just pressed + m_holdStartTimeMs = motionTimeMs; + + // Record window ID + if (windowId >= 0) + m_activeWindowId = windowId; + } + + // Calculate holdtime relative to when magnitude crossed the threshold + unsigned int holdTimeMs = 0; + if (bPressed) + holdTimeMs = motionTimeMs - m_holdStartTimeMs; + + // Give priority to actions with hotkeys + std::vector<const KeymapAction*> actionsWithHotkeys; + + for (const auto& action : actions) + { + if (!action.hotkeys.empty()) + actionsWithHotkeys.emplace_back(&action); + } + + CAction dispatchAction = + ProcessActions(std::move(actionsWithHotkeys), windowId, magnitude, holdTimeMs); + + // If that failed, try again with all actions + if (dispatchAction.GetID() == ACTION_NONE) + { + std::vector<const KeymapAction*> allActions; + + allActions.reserve(actions.size()); + for (const auto& action : actions) + allActions.emplace_back(&action); + + dispatchAction = ProcessActions(std::move(allActions), windowId, magnitude, holdTimeMs); + } + + // If specific action was sent last frame but not this one, send a release event + if (dispatchAction.GetID() != m_lastAction.GetID()) + { + if (CActionTranslator::IsAnalog(m_lastAction.GetID()) && m_lastAction.GetAmount() > 0.0f) + { + m_lastAction.ClearAmount(); + m_actionHandler->OnAction(m_lastAction); + } + } + + // Dispatch action + bool bHandled = false; + if (dispatchAction.GetID() != ACTION_NONE) + { + m_actionHandler->OnAction(dispatchAction); + bHandled = true; + } + + m_bHeld = bPressed; + m_magnitude = magnitude; + m_lastHoldTimeMs = holdTimeMs; + m_lastAction = dispatchAction; + + return bHandled; +} + +CAction CKeyHandler::ProcessActions(std::vector<const KeymapAction*> actions, + int windowId, + float magnitude, + unsigned int holdTimeMs) +{ + CAction dispatchAction; + + // Filter out actions without pressed hotkeys + actions.erase(std::remove_if(actions.begin(), actions.end(), + [this](const KeymapAction* action) { + return !m_keymapHandler->HotkeysPressed(action->hotkeys); + }), + actions.end()); + + if (actions.empty()) + return false; + + // Actions are sorted by holdtime, so the final action is the one with the + // greatest holdtime + const KeymapAction& finalAction = **actions.rbegin(); + const unsigned int maxHoldTimeMs = finalAction.holdTimeMs; + + const bool bHasDelay = (maxHoldTimeMs > 0); + if (!bHasDelay) + { + dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs); + } + else + { + // If holdtime has exceeded the last action, execute it now + if (holdTimeMs >= finalAction.holdTimeMs) + { + // Force holdtime to zero for the initial press + if (!m_bActionSent) + holdTimeMs = 0; + else + holdTimeMs -= finalAction.holdTimeMs; + + dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs); + } + else + { + // Calculate press state + const bool bPressed = IsPressed(magnitude); + const bool bJustReleased = m_bHeld && !bPressed; + + // If button was just released, send a release action + if (bJustReleased) + dispatchAction = ProcessRelease(actions, windowId); + } + } + + return dispatchAction; +} + +CAction CKeyHandler::ProcessRelease(std::vector<const KeymapAction*> actions, int windowId) +{ + CAction dispatchAction; + + // Use previous holdtime from before button release + const unsigned int holdTimeMs = m_lastHoldTimeMs; + + // Send an action on release if one occurs before the holdtime + for (auto it = actions.begin(); it != actions.end();) + { + const KeymapAction& action = **it; + + unsigned int thisHoldTime = (*it)->holdTimeMs; + + ++it; + if (it == actions.end()) + break; + + unsigned int nextHoldTime = (*it)->holdTimeMs; + + if (thisHoldTime <= holdTimeMs && holdTimeMs < nextHoldTime) + { + dispatchAction = ProcessAction(action, windowId, 1.0f, 0); + break; + } + } + + return dispatchAction; +} + +CAction CKeyHandler::ProcessAction(const KeymapAction& action, + int windowId, + float magnitude, + unsigned int holdTimeMs) +{ + CAction dispatchAction; + + bool bSendAction = false; + + if (windowId != m_activeWindowId) + { + // Don't send actions if the window has changed since being pressed + } + else if (CActionTranslator::IsAnalog(action.actionId)) + { + bSendAction = true; + } + else if (IsPressed(magnitude)) + { + // Dispatch action if button was pressed this frame + if (holdTimeMs == 0) + bSendAction = true; + else + bSendAction = SendRepeatAction(holdTimeMs); + } + + if (bSendAction) + { + const CAction guiAction(action.actionId, magnitude, 0.0f, action.actionString, holdTimeMs); + m_keymapHandler->OnPress(m_keyName); + m_bActionSent = true; + m_lastActionMs = holdTimeMs; + dispatchAction = guiAction; + } + + return dispatchAction; +} + +bool CKeyHandler::SendRepeatAction(unsigned int holdTimeMs) +{ + bool bSendRepeat = true; + + // Don't send a repeat action if the last key has changed + if (m_keymapHandler->GetLastPressed() != m_keyName) + bSendRepeat = false; + + // Ensure initial timeout has elapsed + else if (holdTimeMs < HOLD_TIMEOUT_MS) + bSendRepeat = false; + + // Ensure repeat timeout has elapsed + else if (holdTimeMs < m_lastActionMs + REPEAT_TIMEOUT_MS) + bSendRepeat = false; + + return bSendRepeat; +} + +bool CKeyHandler::IsPressed(float magnitude) +{ + return magnitude >= DIGITAL_ANALOG_THRESHOLD; +} |