diff options
Diffstat (limited to 'xbmc/input/touch/generic')
-rw-r--r-- | xbmc/input/touch/generic/CMakeLists.txt | 14 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchActionHandler.cpp | 212 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchActionHandler.h | 94 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchInputHandler.cpp | 397 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchInputHandler.h | 98 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchPinchDetector.cpp | 87 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchPinchDetector.h | 32 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchRotateDetector.cpp | 117 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchRotateDetector.h | 36 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp | 176 | ||||
-rw-r--r-- | xbmc/input/touch/generic/GenericTouchSwipeDetector.h | 49 | ||||
-rw-r--r-- | xbmc/input/touch/generic/IGenericTouchGestureDetector.h | 91 |
12 files changed, 1403 insertions, 0 deletions
diff --git a/xbmc/input/touch/generic/CMakeLists.txt b/xbmc/input/touch/generic/CMakeLists.txt new file mode 100644 index 0000000..d5e8adc --- /dev/null +++ b/xbmc/input/touch/generic/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES GenericTouchActionHandler.cpp + GenericTouchInputHandler.cpp + GenericTouchPinchDetector.cpp + GenericTouchRotateDetector.cpp + GenericTouchSwipeDetector.cpp) + +set(HEADERS GenericTouchActionHandler.h + GenericTouchInputHandler.h + GenericTouchPinchDetector.h + GenericTouchRotateDetector.h + GenericTouchSwipeDetector.h + IGenericTouchGestureDetector.h) + +core_add_library(input_touch_generic) diff --git a/xbmc/input/touch/generic/GenericTouchActionHandler.cpp b/xbmc/input/touch/generic/GenericTouchActionHandler.cpp new file mode 100644 index 0000000..d601074 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchActionHandler.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2013-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 "GenericTouchActionHandler.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" + +#include <cmath> + +using namespace KODI::MESSAGING; + +CGenericTouchActionHandler& CGenericTouchActionHandler::GetInstance() +{ + static CGenericTouchActionHandler sTouchAction; + return sTouchAction; +} + +void CGenericTouchActionHandler::OnTouchAbort() +{ + sendEvent(ACTION_GESTURE_ABORT, 0.0f, 0.0f); +} + +bool CGenericTouchActionHandler::OnSingleTouchStart(float x, float y) +{ + focusControl(x, y); + + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchHold(float x, float y) +{ + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchEnd(float x, float y) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchDown(float x, float y, int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchHold(float x, float y, int32_t pointers /* = 2 */) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchUp(float x, float y, int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnTouchGestureStart(float x, float y) +{ + sendEvent(ACTION_GESTURE_BEGIN, x, y, 0.0f, 0.0f); + + return true; +} + +bool CGenericTouchActionHandler::OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + sendEvent(ACTION_GESTURE_PAN, x, y, offsetX, offsetY, velocityX, velocityY); + + return true; +} + +bool CGenericTouchActionHandler::OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + sendEvent(ACTION_GESTURE_END, velocityX, velocityY, x, y, offsetX, offsetY); + + return true; +} + +void CGenericTouchActionHandler::OnTap(float x, float y, int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + sendEvent(ACTION_TOUCH_TAP, x, y, 0.0f, 0.0f, 0.0f, 0.0f, pointers); +} + +void CGenericTouchActionHandler::OnLongPress(float x, float y, int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + sendEvent(ACTION_TOUCH_LONGPRESS, x, y, 0.0f, 0.0f, 0.0f, 0.0f, pointers); +} + +void CGenericTouchActionHandler::OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + int actionId = 0; + if (direction == TouchMoveDirectionLeft) + actionId = ACTION_GESTURE_SWIPE_LEFT; + else if (direction == TouchMoveDirectionRight) + actionId = ACTION_GESTURE_SWIPE_RIGHT; + else if (direction == TouchMoveDirectionUp) + actionId = ACTION_GESTURE_SWIPE_UP; + else if (direction == TouchMoveDirectionDown) + actionId = ACTION_GESTURE_SWIPE_DOWN; + else + return; + + sendEvent(actionId, xUp, yUp, velocityX, velocityY, xDown, yDown, pointers); +} + +void CGenericTouchActionHandler::OnZoomPinch(float centerX, float centerY, float zoomFactor) +{ + sendEvent(ACTION_GESTURE_ZOOM, centerX, centerY, zoomFactor, 0.0f); +} + +void CGenericTouchActionHandler::OnRotate(float centerX, float centerY, float angle) +{ + sendEvent(ACTION_GESTURE_ROTATE, centerX, centerY, angle, 0.0f); +} + +int CGenericTouchActionHandler::QuerySupportedGestures(float x, float y) +{ + CGUIMessage msg(GUI_MSG_GESTURE_NOTIFY, 0, 0, static_cast<int>(std::round(x)), + static_cast<int>(std::round(y))); + if (!CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg)) + return 0; + + int result = 0; + if (msg.GetPointer()) + { + int* p = static_cast<int*>(msg.GetPointer()); + msg.SetPointer(nullptr); + result = *p; + delete p; + } + return result; +} + +void CGenericTouchActionHandler::sendEvent(int actionId, + float x, + float y, + float x2 /* = 0.0f */, + float y2 /* = 0.0f */, + float x3, + float y3, + int pointers /* = 1 */) +{ + XBMC_Event newEvent{}; + newEvent.type = XBMC_TOUCH; + + newEvent.touch.action = actionId; + newEvent.touch.x = x; + newEvent.touch.y = y; + newEvent.touch.x2 = x2; + newEvent.touch.y2 = y2; + newEvent.touch.x3 = x3; + newEvent.touch.y3 = y3; + newEvent.touch.pointers = pointers; + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); +} + +void CGenericTouchActionHandler::focusControl(float x, float y) +{ + XBMC_Event newEvent{}; + newEvent.type = XBMC_SETFOCUS; + + newEvent.focus.x = static_cast<int>(std::round(x)); + newEvent.focus.y = static_cast<int>(std::round(y)); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); +} diff --git a/xbmc/input/touch/generic/GenericTouchActionHandler.h b/xbmc/input/touch/generic/GenericTouchActionHandler.h new file mode 100644 index 0000000..e8697ce --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchActionHandler.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchActionHandler.h" + +/*! + * \ingroup touch_generic + * \brief Generic implementation of ITouchActionHandler to translate + * touch actions into XBMC specific and mappable actions. + * + * \sa ITouchActionHandler + */ +class CGenericTouchActionHandler : public ITouchActionHandler +{ +public: + /*! + \brief Get an instance of the touch input manager + */ + static CGenericTouchActionHandler& GetInstance(); + + // implementation of ITouchActionHandler + void OnTouchAbort() override; + + bool OnSingleTouchStart(float x, float y) override; + bool OnSingleTouchHold(float x, float y) override; + bool OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnSingleTouchEnd(float x, float y) override; + + bool OnMultiTouchDown(float x, float y, int32_t pointer) override; + bool OnMultiTouchHold(float x, float y, int32_t pointers = 2) override; + bool OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) override; + bool OnMultiTouchUp(float x, float y, int32_t pointer) override; + + bool OnTouchGestureStart(float x, float y) override; + bool OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + + // convenience events + void OnTap(float x, float y, int32_t pointers = 1) override; + void OnLongPress(float x, float y, int32_t pointers = 1) override; + void OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers = 1) override; + void OnZoomPinch(float centerX, float centerY, float zoomFactor) override; + void OnRotate(float centerX, float centerY, float angle) override; + + /*! + \brief Asks the control at the given coordinates for a list of the supported gestures. + + \param x The x coordinate (with sub-pixel) of the touch + \param y The y coordinate (with sub-pixel) of the touch + + \return EVENT_RESULT value of bitwise ORed gestures. + */ + int QuerySupportedGestures(float x, float y); + +private: + // private construction, and no assignments; use the provided singleton methods + CGenericTouchActionHandler() = default; + CGenericTouchActionHandler(const CGenericTouchActionHandler&) = delete; + CGenericTouchActionHandler const& operator=(CGenericTouchActionHandler const&) = delete; + ~CGenericTouchActionHandler() override = default; + + void sendEvent(int actionId, + float x, + float y, + float x2 = 0.0f, + float y2 = 0.0f, + float x3 = 0.0f, + float y3 = 0.0f, + int pointers = 1); + void focusControl(float x, float y); +}; diff --git a/xbmc/input/touch/generic/GenericTouchInputHandler.cpp b/xbmc/input/touch/generic/GenericTouchInputHandler.cpp new file mode 100644 index 0000000..6290860 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchInputHandler.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012-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 "GenericTouchInputHandler.h" + +#include "input/touch/generic/GenericTouchPinchDetector.h" +#include "input/touch/generic/GenericTouchRotateDetector.h" +#include "input/touch/generic/GenericTouchSwipeDetector.h" +#include "utils/log.h" + +#include <algorithm> +#include <cmath> +#include <mutex> + +using namespace std::chrono_literals; + +namespace +{ +constexpr auto TOUCH_HOLD_TIMEOUT = 500ms; +} + +CGenericTouchInputHandler::CGenericTouchInputHandler() : m_holdTimer(new CTimer(this)) +{ +} + +CGenericTouchInputHandler::~CGenericTouchInputHandler() = default; + +CGenericTouchInputHandler& CGenericTouchInputHandler::GetInstance() +{ + static CGenericTouchInputHandler sTouchInput; + return sTouchInput; +} + +float CGenericTouchInputHandler::AdjustPointerSize(float size) +{ + if (size > 0.0f) + return size; + else + // Set a default size if touch input layer does not have anything useful, + // approx. 3.2mm + return m_dpi / 8.0f; +} + +bool CGenericTouchInputHandler::HandleTouchInput(TouchInput event, + float x, + float y, + int64_t time, + int32_t pointer /* = 0 */, + float size /* = 0.0f */) +{ + if (time < 0 || pointer < 0 || pointer >= MAX_POINTERS) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + + bool result = true; + + m_pointers[pointer].current.x = x; + m_pointers[pointer].current.y = y; + m_pointers[pointer].current.time = time; + + switch (event) + { + case TouchInputAbort: + { + triggerDetectors(event, pointer); + + setGestureState(TouchGestureUnknown); + for (auto& pointer : m_pointers) + pointer.reset(); + + OnTouchAbort(); + break; + } + + case TouchInputDown: + { + m_pointers[pointer].down.x = x; + m_pointers[pointer].down.y = y; + m_pointers[pointer].down.time = time; + m_pointers[pointer].moving = false; + m_pointers[pointer].size = AdjustPointerSize(size); + + // If this is the down event of the primary pointer + // we start by assuming that it's a single touch + if (pointer == 0) + { + // create new gesture detectors + m_detectors.emplace(new CGenericTouchSwipeDetector(this, m_dpi)); + m_detectors.emplace(new CGenericTouchPinchDetector(this, m_dpi)); + m_detectors.emplace(new CGenericTouchRotateDetector(this, m_dpi)); + triggerDetectors(event, pointer); + + setGestureState(TouchGestureSingleTouch); + result = OnSingleTouchStart(x, y); + + m_holdTimer->Start(TOUCH_HOLD_TIMEOUT); + } + // Otherwise it's the down event of another pointer + else + { + triggerDetectors(event, pointer); + + // If we so far assumed single touch or still have the primary + // pointer of a previous multi touch pressed down, we can update to multi touch + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold || + m_gestureState == TouchGestureMultiTouchDone) + { + result = OnMultiTouchDown(x, y, pointer); + m_holdTimer->Stop(true); + + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold) + m_holdTimer->Start(TOUCH_HOLD_TIMEOUT); + + setGestureState(TouchGestureMultiTouchStart); + } + // Otherwise we should ignore this pointer + else + { + m_pointers[pointer].reset(); + break; + } + } + return result; + } + + case TouchInputUp: + { + // unexpected event => abort + if (!m_pointers[pointer].valid() || m_gestureState == TouchGestureUnknown) + break; + + triggerDetectors(event, pointer); + + m_holdTimer->Stop(false); + + // Just a single tap with a pointer + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold) + { + result = OnSingleTouchEnd(x, y); + + if (m_gestureState == TouchGestureSingleTouch) + OnTap(x, y, 1); + } + // A pan gesture started with a single pointer (ignoring any other pointers) + else if (m_gestureState == TouchGesturePan) + { + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY, false); + + result = OnTouchGestureEnd(x, y, x - m_pointers[pointer].down.x, + y - m_pointers[pointer].down.y, velocityX, velocityY); + } + // we are in multi-touch + else + result = OnMultiTouchUp(x, y, pointer); + + // If we were in multi touch mode and lifted one pointer + // we can go into the TouchGestureMultiTouchDone state which will allow + // the user to go back into multi touch mode without lifting the primary pointer + if (m_gestureState == TouchGestureMultiTouchStart || + m_gestureState == TouchGestureMultiTouchHold || m_gestureState == TouchGestureMultiTouch) + { + setGestureState(TouchGestureMultiTouchDone); + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (pointer == 0) + { + m_pointers[0] = m_pointers[1]; + pointer = 1; + } + } + // Otherwise abort + else + { + if (m_gestureState == TouchGestureMultiTouchDone) + { + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY, false); + + result = OnTouchGestureEnd(x, y, x - m_pointers[pointer].down.x, + y - m_pointers[pointer].down.y, velocityX, velocityY); + + // if neither of the two pointers moved we have a single tap with multiple pointers + if (m_gestureStateOld != TouchGestureMultiTouchHold && + m_gestureStateOld != TouchGestureMultiTouch) + OnTap(std::abs((m_pointers[0].down.x + m_pointers[1].down.x) / 2), + std::abs((m_pointers[0].down.y + m_pointers[1].down.y) / 2), 2); + } + + setGestureState(TouchGestureUnknown); + } + m_pointers[pointer].reset(); + + return result; + } + + case TouchInputMove: + { + // unexpected event => abort + if (!m_pointers[pointer].valid() || m_gestureState == TouchGestureUnknown || + m_gestureState == TouchGestureMultiTouchDone) + break; + + bool moving = std::any_of(m_pointers.cbegin(), m_pointers.cend(), + [](Pointer const& p) { return p.valid() && p.moving; }); + + if (moving) + { + m_holdTimer->Stop(); + + // the touch is moving so we start a gesture + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureMultiTouchStart) + result = OnTouchGestureStart(m_pointers[pointer].down.x, m_pointers[pointer].down.y); + } + + triggerDetectors(event, pointer); + + // Check if the touch has moved far enough to count as movement + if ((m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureMultiTouchStart) && + !m_pointers[pointer].moving) + break; + + if (m_gestureState == TouchGestureSingleTouch) + { + m_pointers[pointer].last.copy(m_pointers[pointer].down); + setGestureState(TouchGesturePan); + } + else if (m_gestureState == TouchGestureMultiTouchStart) + { + setGestureState(TouchGestureMultiTouch); + + // set the starting point + saveLastTouch(); + } + + float offsetX = x - m_pointers[pointer].last.x; + float offsetY = y - m_pointers[pointer].last.y; + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY); + + if (m_pointers[pointer].moving && + (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold || m_gestureState == TouchGesturePan)) + result = OnSingleTouchMove(x, y, offsetX, offsetY, velocityX, velocityY); + + // Let's see if we have a pan gesture (i.e. the primary and only pointer moving) + if (m_gestureState == TouchGesturePan) + { + result = OnTouchGesturePan(x, y, offsetX, offsetY, velocityX, velocityY); + + m_pointers[pointer].last.x = x; + m_pointers[pointer].last.y = y; + } + else if (m_gestureState == TouchGestureMultiTouch) + { + if (moving) + result = OnMultiTouchMove(x, y, offsetX, offsetY, velocityX, velocityY, pointer); + } + else + break; + + return result; + } + + default: + CLog::Log(LOGDEBUG, "CGenericTouchInputHandler: unknown TouchInput"); + break; + } + + return false; +} + +bool CGenericTouchInputHandler::UpdateTouchPointer( + int32_t pointer, float x, float y, int64_t time, float size /* = 0.0f */) +{ + if (pointer < 0 || pointer >= MAX_POINTERS) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + + m_pointers[pointer].last.copy(m_pointers[pointer].current); + + m_pointers[pointer].current.x = x; + m_pointers[pointer].current.y = y; + m_pointers[pointer].current.time = time; + m_pointers[pointer].size = AdjustPointerSize(size); + + // calculate whether the pointer has moved at all + if (!m_pointers[pointer].moving) + { + CVector down = m_pointers[pointer].down; + CVector current = m_pointers[pointer].current; + CVector distance = down - current; + + if (distance.length() > m_pointers[pointer].size) + m_pointers[pointer].moving = true; + } + + for (auto const& detector : m_detectors) + detector->OnTouchUpdate(pointer, m_pointers[pointer]); + + return true; +} + +void CGenericTouchInputHandler::saveLastTouch() +{ + for (auto& pointer : m_pointers) + pointer.last.copy(pointer.current); +} + +void CGenericTouchInputHandler::OnTimeout() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + + switch (m_gestureState) + { + case TouchGestureSingleTouch: + setGestureState(TouchGestureSingleTouchHold); + + OnSingleTouchHold(m_pointers[0].down.x, m_pointers[0].down.y); + OnLongPress(m_pointers[0].down.x, m_pointers[0].down.y, 1); + break; + + case TouchGestureMultiTouchStart: + if (!m_pointers[0].moving && !m_pointers[1].moving) + { + setGestureState(TouchGestureMultiTouchHold); + + OnMultiTouchHold(m_pointers[0].down.x, m_pointers[0].down.y); + OnLongPress(std::abs((m_pointers[0].down.x + m_pointers[1].down.x) / 2), + std::abs((m_pointers[0].down.y + m_pointers[1].down.y) / 2), 2); + } + break; + + default: + break; + } +} + +void CGenericTouchInputHandler::triggerDetectors(TouchInput event, int32_t pointer) +{ + switch (event) + { + case TouchInputAbort: + { + m_detectors.clear(); + break; + } + + case TouchInputDown: + { + for (auto const& detector : m_detectors) + detector->OnTouchDown(pointer, m_pointers[pointer]); + break; + } + + case TouchInputUp: + { + for (auto const& detector : m_detectors) + detector->OnTouchUp(pointer, m_pointers[pointer]); + break; + } + + case TouchInputMove: + { + for (auto const& detector : m_detectors) + detector->OnTouchMove(pointer, m_pointers[pointer]); + break; + } + + default: + return; + } + + for (auto it = m_detectors.begin(); it != m_detectors.end();) + { + if ((*it)->IsDone()) + it = m_detectors.erase(it); + else + it++; + } +} diff --git a/xbmc/input/touch/generic/GenericTouchInputHandler.h b/xbmc/input/touch/generic/GenericTouchInputHandler.h new file mode 100644 index 0000000..154f5f2 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchInputHandler.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include "input/touch/ITouchInputHandler.h" +#include "input/touch/TouchTypes.h" +#include "threads/CriticalSection.h" +#include "threads/Timer.h" + +#include <array> +#include <memory> +#include <set> + +class IGenericTouchGestureDetector; + +/*! + * \ingroup touch_generic + * \brief Generic implementation of ITouchInputHandler to handle low level (raw) + * touch events and translate them into touch actions which are passed + * on to the registered ITouchActionHandler implementation. + * + * The generic implementation supports single a double touch and hold + * actions and basic gesture recognition for panning, swiping, pinching/zooming + * and rotating. + * + * \sa ITouchInputHandler + */ +class CGenericTouchInputHandler : public ITouchInputHandler, private ITimerCallback +{ +public: + /*! + \brief Get an instance of the touch input manager + */ + static CGenericTouchInputHandler& GetInstance(); + static constexpr int MAX_POINTERS = 2; + + // implementation of ITouchInputHandler + bool HandleTouchInput(TouchInput event, + float x, + float y, + int64_t time, + int32_t pointer = 0, + float size = 0.0f) override; + bool UpdateTouchPointer( + int32_t pointer, float x, float y, int64_t time, float size = 0.0f) override; + +private: + // private construction, and no assignments; use the provided singleton methods + CGenericTouchInputHandler(); + ~CGenericTouchInputHandler() override; + CGenericTouchInputHandler(const CGenericTouchInputHandler&) = delete; + CGenericTouchInputHandler const& operator=(CGenericTouchInputHandler const&) = delete; + + typedef enum + { + TouchGestureUnknown = 0, + // only primary pointer active but stationary so far + TouchGestureSingleTouch, + // primary pointer active but stationary for a certain time + TouchGestureSingleTouchHold, + // primary pointer moving + TouchGesturePan, + // at least two pointers active but stationary so far + TouchGestureMultiTouchStart, + // at least two pointers active but stationary for a certain time + TouchGestureMultiTouchHold, + // at least two pointers active and moving + TouchGestureMultiTouch, + // all but primary pointer have been lifted + TouchGestureMultiTouchDone + } TouchGestureState; + + // implementation of ITimerCallback + void OnTimeout() override; + + void saveLastTouch(); + void setGestureState(TouchGestureState gestureState) + { + m_gestureStateOld = m_gestureState; + m_gestureState = gestureState; + } + void triggerDetectors(TouchInput event, int32_t pointer); + float AdjustPointerSize(float size); + + CCriticalSection m_critical; + std::unique_ptr<CTimer> m_holdTimer; + std::array<Pointer, MAX_POINTERS> m_pointers; + std::set<std::unique_ptr<IGenericTouchGestureDetector>> m_detectors; + + TouchGestureState m_gestureState = TouchGestureUnknown; + TouchGestureState m_gestureStateOld = TouchGestureUnknown; +}; diff --git a/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp b/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp new file mode 100644 index 0000000..3cff00d --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013-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 "GenericTouchPinchDetector.h" + +bool CGenericTouchPinchDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + m_pointers[index] = pointer; + return true; +} + +bool CGenericTouchPinchDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (index == 0) + { + m_pointers[0] = m_pointers[1]; + index = 1; + } + + m_pointers[index].reset(); + + if (!m_pointers[0].valid() && !m_pointers[1].valid()) + m_done = true; + + return true; +} + +bool CGenericTouchPinchDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + + const Pointer& primaryPointer = m_pointers[0]; + const Pointer& secondaryPointer = m_pointers[1]; + + if (!primaryPointer.valid() || !secondaryPointer.valid() || + (!primaryPointer.moving && !secondaryPointer.moving)) + return false; + + // calculate zoom/pinch + CVector primary = primaryPointer.down; + CVector secondary = secondaryPointer.down; + CVector diagonal = primary - secondary; + + float baseDiffLength = diagonal.length(); + if (baseDiffLength != 0.0f) + { + CVector primaryNow = primaryPointer.current; + CVector secondaryNow = secondaryPointer.current; + CVector diagonalNow = primaryNow - secondaryNow; + float curDiffLength = diagonalNow.length(); + + float centerX = (primary.x + secondary.x) / 2; + float centerY = (primary.y + secondary.y) / 2; + + float zoom = curDiffLength / baseDiffLength; + + OnZoomPinch(centerX, centerY, zoom); + } + + return true; +} diff --git a/xbmc/input/touch/generic/GenericTouchPinchDetector.h b/xbmc/input/touch/generic/GenericTouchPinchDetector.h new file mode 100644 index 0000000..191e950 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchPinchDetector.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect pinch/zoom + * gestures with at least two active touch pointers. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchPinchDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchPinchDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi) + { + } + ~CGenericTouchPinchDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; +}; diff --git a/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp b/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp new file mode 100644 index 0000000..79db638 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013-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 "GenericTouchRotateDetector.h" + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795028842 +#endif + +CGenericTouchRotateDetector::CGenericTouchRotateDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi), m_angle(0.0f) +{ +} + +bool CGenericTouchRotateDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + m_pointers[index] = pointer; + m_angle = 0.0f; + return true; +} + +bool CGenericTouchRotateDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (index == 0) + { + m_pointers[0] = m_pointers[1]; + index = 1; + } + + m_pointers[index].reset(); + + if (!m_pointers[0].valid() && !m_pointers[1].valid()) + m_done = true; + + return true; +} + +bool CGenericTouchRotateDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + + Pointer& primaryPointer = m_pointers[0]; + Pointer& secondaryPointer = m_pointers[1]; + + if (!primaryPointer.valid() || !secondaryPointer.valid() || + (!primaryPointer.moving && !secondaryPointer.moving)) + return false; + + CVector last = primaryPointer.last - secondaryPointer.last; + CVector current = primaryPointer.current - secondaryPointer.current; + + float length = last.length() * current.length(); + if (length != 0.0f) + { + float centerX = (primaryPointer.current.x + secondaryPointer.current.x) / 2; + float centerY = (primaryPointer.current.y + secondaryPointer.current.y) / 2; + float scalar = last.scalar(current); + float angle = acos(scalar / length) * 180.0f / static_cast<float>(M_PI); + + // make sure the result of acos is a valid number + if (angle == angle) + { + // calculate the direction of the rotation using the + // z-component of the cross-product of last and current + float direction = last.x * current.y - current.x * last.y; + if (direction < 0.0f) + m_angle -= angle; + else + m_angle += angle; + + OnRotate(centerX, centerY, m_angle); + } + } + + return true; +} + +bool CGenericTouchRotateDetector::OnTouchUpdate(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + return true; +} diff --git a/xbmc/input/touch/generic/GenericTouchRotateDetector.h b/xbmc/input/touch/generic/GenericTouchRotateDetector.h new file mode 100644 index 0000000..695879a --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchRotateDetector.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect rotation + * gestures with at least two active touch pointers. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchRotateDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchRotateDetector(ITouchActionHandler* handler, float dpi); + ~CGenericTouchRotateDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; + bool OnTouchUpdate(unsigned int index, const Pointer& pointer) override; + +private: + /*! + * \brief Angle of the detected rotation + */ + float m_angle; +}; diff --git a/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp b/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp new file mode 100644 index 0000000..7d772b5 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2013-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. + */ + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#include "GenericTouchSwipeDetector.h" + +#include <math.h> +#include <stdlib.h> + +// maximum time between touch down and up (in nanoseconds) +#define SWIPE_MAX_TIME 500000000 +// maximum swipe distance between touch down and up (in multiples of screen DPI) +#define SWIPE_MIN_DISTANCE 0.5f +// original maximum variance of the touch movement +#define SWIPE_MAX_VARIANCE 0.2f +// tangents of the maximum angle (20 degrees) the touch movement may vary in a +// direction perpendicular to the swipe direction (in radians) +// => tan(20 deg) = tan(20 * M_PI / 180) +#define SWIPE_MAX_VARIANCE_ANGLE 0.36397023f + +CGenericTouchSwipeDetector::CGenericTouchSwipeDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi), + m_directions(TouchMoveDirectionLeft | TouchMoveDirectionRight | TouchMoveDirectionUp | + TouchMoveDirectionDown), + m_swipeDetected(false), + m_size(0) +{ +} + +bool CGenericTouchSwipeDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + m_size += 1; + if (m_size > 1) + return true; + + // reset all values + m_done = false; + m_swipeDetected = false; + m_directions = TouchMoveDirectionLeft | TouchMoveDirectionRight | TouchMoveDirectionUp | + TouchMoveDirectionDown; + + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + m_size -= 1; + if (m_done) + return false; + + m_done = true; + + // check if a swipe has been detected and if it has a valid direction + if (!m_swipeDetected || m_directions == TouchMoveDirectionNone) + return false; + + // check if the swipe has been performed in the proper time span + if ((pointer.current.time - pointer.down.time) > SWIPE_MAX_TIME) + return false; + + // calculate the velocity of the swipe + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + pointer.velocity(velocityX, velocityY, false); + + // call the OnSwipe() callback + OnSwipe((TouchMoveDirection)m_directions, pointer.down.x, pointer.down.y, pointer.current.x, + pointer.current.y, velocityX, velocityY, m_size + 1); + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + // only handle swipes of moved pointers + if (index >= m_size || m_done || !pointer.moving) + return false; + + float deltaXmovement = pointer.current.x - pointer.last.x; + float deltaYmovement = pointer.current.y - pointer.last.y; + + if (deltaXmovement > 0.0f) + m_directions &= ~TouchMoveDirectionLeft; + else if (deltaXmovement < 0.0f) + m_directions &= ~TouchMoveDirectionRight; + + if (deltaYmovement > 0.0f) + m_directions &= ~TouchMoveDirectionUp; + else if (deltaYmovement < 0.0f) + m_directions &= ~TouchMoveDirectionDown; + + if (m_directions == TouchMoveDirectionNone) + { + m_done = true; + return false; + } + + float deltaXabs = fabs(pointer.current.x - pointer.down.x); + float deltaYabs = fabs(pointer.current.y - pointer.down.y); + float varXabs = deltaYabs * SWIPE_MAX_VARIANCE_ANGLE + (m_dpi * SWIPE_MAX_VARIANCE) / 2; + float varYabs = deltaXabs * SWIPE_MAX_VARIANCE_ANGLE + (m_dpi * SWIPE_MAX_VARIANCE) / 2; + + if (m_directions & TouchMoveDirectionLeft) + { + // check if the movement went too much in Y direction + if (deltaYabs > varYabs) + m_directions &= ~TouchMoveDirectionLeft; + // check if the movement went far enough in the X direction + else if (deltaXabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionRight) + { + // check if the movement went too much in Y direction + if (deltaYabs > varYabs) + m_directions &= ~TouchMoveDirectionRight; + // check if the movement went far enough in the X direction + else if (deltaXabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionUp) + { + // check if the movement went too much in X direction + if (deltaXabs > varXabs) + m_directions &= ~TouchMoveDirectionUp; + // check if the movement went far enough in the Y direction + else if (deltaYabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionDown) + { + // check if the movement went too much in X direction + if (deltaXabs > varXabs) + m_directions &= ~TouchMoveDirectionDown; + // check if the movement went far enough in the Y direction + else if (deltaYabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions == TouchMoveDirectionNone) + { + m_done = true; + return false; + } + + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchUpdate(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + return OnTouchMove(index, pointer); +} diff --git a/xbmc/input/touch/generic/GenericTouchSwipeDetector.h b/xbmc/input/touch/generic/GenericTouchSwipeDetector.h new file mode 100644 index 0000000..7fe88b1 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchSwipeDetector.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect swipe + * gestures in any direction. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchSwipeDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchSwipeDetector(ITouchActionHandler* handler, float dpi); + ~CGenericTouchSwipeDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; + bool OnTouchUpdate(unsigned int index, const Pointer& pointer) override; + +private: + /*! + * \brief Swipe directions that are still possible to detect + * + * The directions are stored as a combination (bitwise OR) of + * TouchMoveDirection enum values + * + * \sa TouchMoveDirection + */ + unsigned int m_directions; + /*! + * \brief Whether a swipe gesture has been detected or not + */ + bool m_swipeDetected; + /*! + * \brief Number of active pointers + */ + unsigned int m_size; +}; diff --git a/xbmc/input/touch/generic/IGenericTouchGestureDetector.h b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h new file mode 100644 index 0000000..7e2cb88 --- /dev/null +++ b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchInputHandling.h" +#include "input/touch/TouchTypes.h" + +#include <array> + +/*! + * \ingroup touch_generic + * \brief Interface defining methods to perform gesture recognition + */ +class IGenericTouchGestureDetector : public ITouchInputHandling +{ +public: + IGenericTouchGestureDetector(ITouchActionHandler* handler, float dpi) : m_done(false), m_dpi(dpi) + { + RegisterHandler(handler); + } + ~IGenericTouchGestureDetector() override = default; + static constexpr int MAX_POINTERS = 2; + + /*! + * \brief Check whether the gesture recognition is finished or not + * + * \return True if the gesture recognition is finished otherwise false + */ + bool IsDone() { return m_done; } + + /*! + * \brief A new touch pointer has been recognised. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchDown(unsigned int index, const Pointer& pointer) = 0; + /*! + * \brief An active touch pointer has vanished. + * + * If the first touch pointer is lifted and there are more active touch + * pointers, the remaining pointers change their index. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchUp(unsigned int index, const Pointer& pointer) { return false; } + /*! + * \brief An active touch pointer has moved. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchMove(unsigned int index, const Pointer& pointer) { return false; } + /*! + * \brief An active touch pointer's values have been updated but no event has + * occurred. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchUpdate(unsigned int index, const Pointer& pointer) { return false; } + +protected: + /*! + * \brief Whether the gesture recognition is finished or not + */ + bool m_done; + /*! + * \brief DPI value of the touch screen + */ + float m_dpi; + /*! + * \brief Local list of all known touch pointers + */ + std::array<Pointer, MAX_POINTERS> m_pointers; +}; |