summaryrefslogtreecommitdiffstats
path: root/xbmc/input/touch/generic/GenericTouchInputHandler.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/input/touch/generic/GenericTouchInputHandler.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/input/touch/generic/GenericTouchInputHandler.cpp')
-rw-r--r--xbmc/input/touch/generic/GenericTouchInputHandler.cpp397
1 files changed, 397 insertions, 0 deletions
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++;
+ }
+}