Add Touch input handling

This commit is contained in:
2023-10-15 13:39:22 +02:00
parent 57e4352c6c
commit 68fb6d0910
19 changed files with 1468 additions and 18 deletions

View File

@@ -242,6 +242,34 @@ namespace OpenVulkano::Input
};
};
class Touch
{
public:
enum Button: int16_t
{
BUTTON_TAP = 0,
BUTTON_TWO_FINGER_TAP
};
enum Axis : int16_t
{
AXIS_TAP_X = 0,
AXIS_TAP_Y,
AXIS_TAP_DURATION,
AXIS_TAP_TWO_FINGERS_X,
AXIS_TAP_TWO_FINGERS_Y,
AXIS_TAP_TWO_FINGER_DURATION,
AXIS_PAN_X,
AXIS_PAN_Y,
AXIS_PAN_TWO_FINGERS_X,
AXIS_PAN_TWO_FINGERS_Y,
AXIS_PINCH,
AXIS_PINCH_CENTER_X,
AXIS_PINCH_CENTER_Y,
AXIS_LAST = AXIS_PINCH_CENTER_Y
};
};
private:
InputDeviceType deviceType;
InputType type;
@@ -268,6 +296,14 @@ namespace OpenVulkano::Input
deviceType(InputDeviceType::CONTROLLER), type(InputType::AXIS), key(controllerAxis)
{}
InputKey(Touch::Button touchButton) :
deviceType(InputDeviceType::TOUCH), type(InputType::BUTTON), key(touchButton)
{}
InputKey(Touch::Axis touchAxis) :
deviceType(InputDeviceType::TOUCH), type(InputType::AXIS), key(touchAxis)
{}
[[nodiscard]] InputDeviceType GetInputDeviceType() const { return deviceType; }
[[nodiscard]] InputType GetInputType() const { return type; }

View File

@@ -0,0 +1,49 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <cstdint>
namespace OpenVulkano::Input
{
class Touch;
class GestureProcessor;
class Gesture
{
friend class GestureProcessor;
uint32_t m_gestureType = -1;
bool m_active = false;
GestureProcessor* m_processor = nullptr;
public:
static constexpr uint32_t TYPE_TAP = 0;
static constexpr uint32_t TYPE_PAN = 1;
static constexpr uint32_t TYPE_PINCH = 2;
Gesture(uint32_t gestureType) : m_gestureType(gestureType) {}
virtual ~Gesture() = default;
virtual void Cancel() {}
virtual void Reset() {}
virtual void TouchDown(const Touch& touch) {}
virtual void TouchUp(const Touch& touch) {}
virtual void TouchMoved(const Touch& touch) {}
void SetActive(bool active) { m_active = active; }
[[nodiscard]] bool IsActive() const { return m_active; }
[[nodiscard]] uint32_t GetType() const { return m_gestureType; }
// Impl in GestureProcessor.cpp
bool ResolveConflicts();
void RemoveFromProcessor();
};
}

View File

@@ -0,0 +1,34 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Math/Math.hpp"
#include <chrono>
namespace OpenVulkano::Input
{
struct PanInfo
{
Math::Vector2f position;
Math::Vector2f distance;
Math::Vector2f delta;
float scale = 0.0f;
};
struct PinchInfo
{
Math::Vector2f position;
float scale = 0.0f;
};
struct TapInfo
{
Math::Vector2f position;
std::chrono::seconds duration;
};
}

View File

@@ -0,0 +1,174 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "GesturePan.hpp"
namespace OpenVulkano::Input
{
namespace
{
constexpr float MIN_DISTANCE = 20;
constexpr float MIN_DISTANCE2 = MIN_DISTANCE * MIN_DISTANCE;
}
void GesturePan::TryStart(const Touch& touch)
{
uint32_t dragCount = 0, existingActive = 0;
for (const auto& t : m_pendingTouches)
{
if (t.isDrag) dragCount++;
if (t.active) existingActive++;
}
if (dragCount >= m_requiredTouchInputs)
{
if (!IsActive() && !ResolveConflicts()) return;
uint32_t activeCount = existingActive;
for (auto& t : m_pendingTouches)
{
if (t.isDrag && !t.active && activeCount < m_requiredTouchInputs)
{
t.active = true;
activeCount++;
}
}
m_paused = false;
if (IsActive())
{
m_panInfo.position = CalculatePosition();
OnPanMoved(this, m_panInfo);
}
else
{
SetActive(true);
m_panInfo.position = CalculatePosition();
m_panInfo.distance = Math::Vector2f();
m_panInfo.delta = Math::Vector2f();
OnPanStarted.NotifyAll(this, m_panInfo);
}
}
}
Math::Vector2f GesturePan::CalculatePosition() const
{
Math::Vector2f gesturePos = Math::Vector2f();
if (m_pendingTouches.size() >= m_requiredTouchInputs && IsActive() && !m_paused)
{
int activePointers = 0;
for (const auto& t : m_pendingTouches)
{
gesturePos += t.currentPosition;
activePointers++;
}
gesturePos /= activePointers > 0 ? activePointers : 1;
}
return gesturePos;
}
void GesturePan::Cancel()
{
for (auto& pointer : m_pendingTouches)
{
pointer.active = false;
}
SetActive(false);
m_paused = false;
m_panInfo.delta = Math::Vector2f();
OnPanEnded(this, m_panInfo);
m_panInfo.distance = Math::Vector2f();
m_panInfo.position = Math::Vector2f();
}
void GesturePan::TouchDown(const Touch& touch)
{
TouchInfo ti;
ti.touchId = touch.GetId();
ti.currentPosition = touch.GetPosition();
ti.initialPosition = touch.GetPosition();
m_pendingTouches.push_back(ti);
if (IsActive())
{
m_panInfo.position = CalculatePosition();
}
}
void GesturePan::TouchUp(const Touch& touch)
{
if (!m_pendingTouches.empty())
{
for (auto touchIter = m_pendingTouches.begin(); touchIter != m_pendingTouches.end(); touchIter++)
{
if (touch.GetId() == touchIter->touchId)
{
if (touchIter->active) m_paused = true;
m_pendingTouches.erase(touchIter);
break;
}
}
if (m_pendingTouches.empty() && IsActive())
{
Cancel();
}
else if (IsActive())
{
m_panInfo.position = CalculatePosition();
}
}
}
void GesturePan::TouchMoved(const Touch& touch)
{
if (m_pendingTouches.size() > 0)
{
bool isPending = false;
bool isActive = false;
for (auto& t : m_pendingTouches)
{
if (touch.GetId() == t.touchId)
{
t.currentPosition = touch.GetPosition();
Math::Vector2f displacement = t.currentPosition - t.initialPosition;
if (Math::Utils::length2(displacement) > MIN_DISTANCE2)
{
t.isDrag = true;
}
if (t.active) isActive = true;
isPending = true;
}
}
if (isPending)
{
if (!IsActive() || m_paused)
{
TryStart(touch);
}
else if (isActive)
{
Math::Vector2f newPosition = CalculatePosition();
m_panInfo.delta = newPosition - m_panInfo.position;
m_panInfo.distance += m_panInfo.delta;
m_panInfo.position = newPosition;
OnPanMoved.NotifyAll(this, m_panInfo);
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Gesture.hpp"
#include "TouchInfo.hpp"
#include "GestureInfos.hpp"
#include "Base/Event.hpp"
namespace OpenVulkano::Input
{
class GesturePan final : public Gesture
{
PanInfo m_panInfo;
std::vector<TouchInfo> m_pendingTouches;
uint32_t m_requiredTouchInputs;
bool m_paused = false;
void TryStart(const Touch& touch);
Math::Vector2f CalculatePosition() const;
public:
GesturePan(uint32_t requiredTouchInputs)
: Gesture(TYPE_PAN), m_requiredTouchInputs(requiredTouchInputs)
{}
void Cancel() override;
void Reset() override
{
if (IsActive()) Cancel();
m_pendingTouches.clear();
}
void TouchDown(const Touch& touch) override;
void TouchUp(const Touch& touch) override;
void TouchMoved(const Touch& touch) override;
Event<GesturePan*, PanInfo&> OnPanStarted, OnPanMoved, OnPanEnded;
};
}

View File

@@ -0,0 +1,244 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "GesturePinch.hpp"
namespace OpenVulkano::Input
{
namespace
{
constexpr float MIN_DISTANCE = 2;
constexpr float MIN_DISTANCE2 = MIN_DISTANCE * MIN_DISTANCE;
constexpr uint32_t REQUIRED_TOUCHES = 2;
}
void GesturePinch::TryStart()
{
uint32_t dragCount = 0, existingActive = 0;
for (const auto& t : m_pendingTouches)
{
if (t.isDrag) dragCount++;
if (t.active) existingActive++;
}
if (dragCount >= REQUIRED_TOUCHES)
{
if (!IsActive() && !ResolveConflicts()) return;
uint32_t activeCount = existingActive;
for (auto& t : m_pendingTouches)
{
if (t.isDrag && !t.active && activeCount < REQUIRED_TOUCHES)
{
t.active = true;
++activeCount;
}
}
m_paused = false;
if (!IsActive())
{
SetActive(true);
m_initialDistance = CalculateDistance();
float newDistance = m_initialDistance;
if (m_initialDistance > 0)
{
m_pinchInfo.scale = newDistance > 0 ? m_initialDistance / newDistance : m_lastDistance;
m_pinchInfo.position = CalculateCenter();
}
else
{
m_pinchInfo.scale = 1.0f;
m_pinchInfo.position = m_pendingTouches.front().currentPosition;
}
if (m_lastDistance == 0)
{
m_lastDistance = newDistance;
}
// fire event
OnPinchStarted.NotifyAll(this, m_pinchInfo);
m_lastDistance = newDistance;
}
else
{
OnPinchMoved.NotifyAll(this, m_pinchInfo);
}
}
}
float GesturePinch::LinearLength(const std::vector<Math::Vector2f>& points) const
{
auto start = points.begin();
if (start == points.end()) return 0;
auto finish = start + 1;
float sum = 0;
while (finish != points.end())
{
Math::Vector2f displacement = *start - *finish;
sum += Math::Utils::length(displacement);
start = finish++;
}
return sum;
}
float GesturePinch::CalculateDistance() const
{
if (m_pendingTouches.size() >= REQUIRED_TOUCHES && IsActive() && !m_paused)
{
std::vector<Math::Vector2f> positions;
for (const auto& t : m_pendingTouches)
{
if (t.active) positions.push_back(t.currentPosition);
}
return LinearLength(positions);
}
return 0;
}
Math::Vector2f GesturePinch::CalculateCenter() const
{
auto center = Math::Vector2f();
if (m_pendingTouches.size() >= REQUIRED_TOUCHES && IsActive() && !m_paused)
{
std::vector<Math::Vector2f> positions;
for (const auto& t : m_pendingTouches)
{
if (t.active) positions.push_back(t.currentPosition);
}
for (const auto& position : positions)
{
center += position / (float)positions.size();
}
}
return center;
}
void GesturePinch::Cancel()
{
for (auto& t : m_pendingTouches)
{
t.active = false;
}
SetActive(false);
m_paused = false;
OnPinchEnded.NotifyAll(this, m_pinchInfo);
m_pinchInfo.position = {};
}
void GesturePinch::TouchDown(const Touch& touch)
{
TouchInfo ti;
ti.touchId = touch.GetId();
ti.currentPosition = touch.GetPosition();
ti.initialPosition = touch.GetPosition();
ti.delta = touch.GetPosition() - touch.GetLastPosition();
m_pendingTouches.push_back(ti);
}
void GesturePinch::TouchMoved(const Touch& touch)
{
if (!m_pendingTouches.empty())
{
bool isPending = false;
bool isActive = false;
bool oneIsDrag = false;
for (auto& t : m_pendingTouches)
{
if (touch.GetId() == t.touchId)
{
t.currentPosition = touch.GetPosition();
t.delta = touch.GetPosition() - t.initialPosition;
Math::Vector2f displacement = t.currentPosition - t.initialPosition;
if (Math::Utils::length2(displacement) > MIN_DISTANCE2)
{
t.isDrag = true;
oneIsDrag = true;
}
if (t.active) isActive = true;
isPending = true;
}
}
if (oneIsDrag)
{
// Just to activate both cursors
for (auto& t : m_pendingTouches)
{
t.isDrag = true;
}
}
if (isPending)
{
if (!IsActive() || m_paused)
{
TryStart();
}
else if (isActive)
{
float newDistance = CalculateDistance();
if (m_lastDistance == 0)
{
m_lastDistance = newDistance;
}
if (m_initialDistance > 0)
{
m_pinchInfo.scale = newDistance > 0 ? m_initialDistance / newDistance : m_lastDistance;
m_pinchInfo.position = CalculateCenter();
}
else
{
m_pinchInfo.scale = 1.0f;
m_pinchInfo.position = m_pendingTouches.front().currentPosition;
}
OnPinchMoved.NotifyAll(this, m_pinchInfo);
m_lastDistance = newDistance;
}
}
}
}
void GesturePinch::TouchUp(const Touch& touch)
{
if (!m_pendingTouches.empty())
{
for (auto touchIter = m_pendingTouches.begin(); touchIter != m_pendingTouches.end(); touchIter++)
{
if (touch.GetId() == touchIter->touchId)
{
if (touchIter->active) m_paused = true;
m_pendingTouches.erase(touchIter);
break;
}
}
if (m_pendingTouches.empty() && IsActive()) Cancel();
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Gesture.hpp"
#include "TouchInfo.hpp"
#include "GestureInfos.hpp"
#include "Base/Event.hpp"
namespace OpenVulkano::Input
{
class GesturePinch final : public Gesture
{
PinchInfo m_pinchInfo;
std::vector<TouchInfo> m_pendingTouches;
float m_initialDistance = 0, m_lastDistance;
bool m_paused = false;
void TryStart();
float LinearLength(std::vector<Math::Vector2f> const &points) const;
float CalculateDistance() const;
Math::Vector2f CalculateCenter() const;
public:
GesturePinch() : Gesture(TYPE_PINCH) {}
void Cancel() override;
void Reset() override
{
if (IsActive()) Cancel();
m_pendingTouches.clear();
m_initialDistance = 0;
}
void TouchDown(const Touch& touch) override;
void TouchUp(const Touch& touch) override;
void TouchMoved(const Touch& touch) override;
Event<GesturePinch*, PinchInfo&> OnPinchStarted, OnPinchMoved, OnPinchEnded;
};
}

View File

@@ -0,0 +1,142 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "GestureProcessor.hpp"
#include "Touch.hpp"
#include "Gesture.hpp"
#include "Base/Logger.hpp"
namespace OpenVulkano::Input
{
bool Gesture::ResolveConflicts()
{
if (m_processor) return m_processor->ResolveConflicts(this);
return false;
}
void Gesture::RemoveFromProcessor()
{
if (m_processor) m_processor->RemoveGesture(this);
}
GestureProcessor::GestureProcessor()
: m_gesturePan(1), m_gesturePanTwoFingers(2)
{}
GestureProcessor::~GestureProcessor()
{
if (!m_gestures.empty()) Close();
}
void GestureProcessor::Init()
{
AddGesture(&m_gesturePan);
AddGesture(&m_gesturePanTwoFingers);
AddGesture(&m_gesturePinch);
AddGesture(&m_gestureTap);
m_conflictResolutionDelegate = [this](const Gesture* existing, const Gesture* newGesture) {
if(existing == &m_gesturePan && newGesture != &m_gestureTap)
{
Logger::INPUT->debug("Replacing pan with multitouch gesture");
return ConflictResult::NewGesture;
}
return ConflictResult::BothGestures;
};
}
void GestureProcessor::Close()
{
for(const auto& gesture : m_gestures)
{
gesture->m_processor = nullptr;
}
m_gestures.clear();
m_conflictResolutionDelegate = nullptr;
}
void GestureProcessor::AddGesture(Gesture* gesture)
{
gesture->m_processor = this;
m_gestures.push_back(gesture);
}
void GestureProcessor::RemoveGesture(OpenVulkano::Input::Gesture* gesture)
{
for (auto it = m_gestures.begin(); it != m_gestures.end(); it++)
{
if (*it == gesture)
{
(*it)->m_processor = nullptr;
m_gestures.erase(it);
break;
}
}
}
//region forward events
void GestureProcessor::Reset()
{
for (const auto& gesture : m_gestures)
{
gesture->Reset();
}
}
void GestureProcessor::TouchDown(const OpenVulkano::Input::Touch& touch)
{
for (const auto& gesture : m_gestures)
{
gesture->TouchDown(touch);
}
}
void GestureProcessor::TouchUp(const OpenVulkano::Input::Touch& touch)
{
for (const auto& gesture : m_gestures)
{
gesture->TouchUp(touch);
}
}
void GestureProcessor::TouchMoved(const OpenVulkano::Input::Touch& touch)
{
for (const auto& gesture : m_gestures)
{
gesture->TouchMoved(touch);
}
}
//endregion
bool GestureProcessor::ResolveConflicts(Gesture* gesture)
{
if (!m_conflictResolutionDelegate) return true;
bool canActivate = true;
for (const auto& g : m_gestures)
{
if (g != gesture && g->IsActive())
{
ConflictResult conflictResult = m_conflictResolutionDelegate(g, gesture);
switch (conflictResult)
{
case ConflictResult::NeitherGesture:
g->Reset();
canActivate = false;
break;
case ConflictResult::ExistingGesture:
canActivate = false;
break;
case ConflictResult::NewGesture:
g->Reset();
break;
case ConflictResult::BothGestures:
break;
}
}
}
return canActivate;
}
}

View File

@@ -0,0 +1,63 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "GesturePan.hpp"
#include "GesturePinch.hpp"
#include "GestureTap.hpp"
#include <functional>
#include <memory>
#include <vector>
namespace OpenVulkano::Input
{
class GestureProcessor final
{
friend class InputDeviceTouch;
GesturePan m_gesturePan;
GesturePan m_gesturePanTwoFingers;
GesturePinch m_gesturePinch;
GestureTap m_gestureTap;
GestureTap m_gestureTapTwoFingers; // TODO
std::vector<Gesture*> m_gestures;
public:
GestureProcessor();
~GestureProcessor();
void Init();
void Close();
void AddGesture(Gesture* gesture);
void RemoveGesture(Gesture* gesture);
bool ResolveConflicts(Gesture* gesture);
void Reset();
void TouchDown(const Touch& touch);
void TouchUp(const Touch& touch);
void TouchMoved(const Touch& touch);
//region conflict resolution
enum class ConflictResult
{
NeitherGesture,
ExistingGesture,
NewGesture,
BothGestures
};
using ConflictResolutionDelegate = std::function<ConflictResult(const Gesture*, const Gesture*)>;
void SetConflictResolutionDelegate(const ConflictResolutionDelegate& delegate) { m_conflictResolutionDelegate = delegate; }
private:
ConflictResolutionDelegate m_conflictResolutionDelegate;
//endregion
};
}

View File

@@ -0,0 +1,87 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "GestureTap.hpp"
using namespace std::chrono_literals;
namespace OpenVulkano::Input
{
namespace
{
constexpr auto MAX_DURATION = 1s;
constexpr float MAX_DISTANCE = 12.0f;
constexpr float MAX_DISTANCE2 = MAX_DISTANCE * MAX_DISTANCE;
}
int GestureTap::GetTouchCount() const
{
return std::count_if(m_touchMap.begin(), m_touchMap.end(), [](const auto& t) { return t.second; });
}
void GestureTap::Cancel()
{
SetActive(false);
m_tapInfo.position = {};
}
void GestureTap::Reset()
{
Cancel();
m_touchMap.clear();
m_startTime = {};
}
void GestureTap::TouchMoved(const Touch& touch)
{
if (IsActive())
{
if ((std::chrono::steady_clock::now() - m_startTime > MAX_DURATION) ||
(Math::Utils::distance2(touch.GetPosition(), m_initialPoint) > MAX_DISTANCE2) ||
((m_distance += Math::Utils::distance2(touch.GetPosition(), touch.GetLastPosition()) > 3 * MAX_DISTANCE2)))
{
Cancel();
}
}
else
{
m_tapInfo.position = touch.GetPosition();
}
}
void GestureTap::TouchDown(const Touch& touch)
{
m_touchMap[touch.GetId()] = true;
if (1 == m_touchMap.size()) // single tap only
{
m_startTime = std::chrono::steady_clock::now();
m_initialPoint = touch.GetPosition();
m_distance = 0.0f;
m_tapInfo.position = m_initialPoint;
SetActive(true);
}
else
{
Cancel();
}
}
void GestureTap::TouchUp(const Touch& touch)
{
m_touchMap[touch.GetId()] = false;
if (IsActive())
{
m_tapInfo.duration = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - m_startTime);
m_tapInfo.position = touch.GetPosition();
if (m_tapInfo.duration <= MAX_DURATION)
{
OnTap.NotifyAll(this, m_tapInfo);
}
Cancel();
}
if (!GetTouchCount()) Reset(); // empty map if no touch
}
}

View File

@@ -0,0 +1,42 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Gesture.hpp"
#include "Touch.hpp"
#include "GestureInfos.hpp"
#include "Base/Event.hpp"
#include <map>
namespace OpenVulkano::Input
{
class GestureTap final : public Gesture
{
std::chrono::time_point<std::chrono::steady_clock> m_startTime;
float m_distance;
Math::Vector2f m_initialPoint;
std::map<TouchId, bool> m_touchMap;
TapInfo m_tapInfo;
int GetTouchCount() const;
public:
GestureTap() : Gesture(TYPE_TAP) {}
~GestureTap() override = default;
void Cancel() override;
void Reset() override;
void TouchDown(const Touch& touch) override;
void TouchUp(const Touch& touch) override;
void TouchMoved(const Touch& touch) override;
Event<const GestureTap*, const TapInfo&> OnTap;
};
}

View File

@@ -0,0 +1,212 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "InputDeviceTouch.hpp"
#include "Base/Logger.hpp"
#include <algorithm>
namespace OpenVulkano::Input
{
void InputDeviceTouch::Init()
{
InputDevice::Init(InputDeviceType::TOUCH, 0, "Touch");
m_gestureProcessor.Init();
m_gestureProcessor.m_gestureTap.OnTap += EventHandler(this, &InputDeviceTouch::UpdateTap);
m_gestureProcessor.m_gesturePan.OnPanStarted += EventHandler(this, &InputDeviceTouch::UpdatePanStarted);
m_gestureProcessor.m_gesturePan.OnPanMoved += EventHandler(this, &InputDeviceTouch::UpdatePanMoved);
m_gestureProcessor.m_gesturePanTwoFingers.OnPanStarted += EventHandler(this, &InputDeviceTouch::UpdatePanTwoFingersStarted);
m_gestureProcessor.m_gesturePanTwoFingers.OnPanMoved += EventHandler(this, &InputDeviceTouch::UpdatePanTwoFingersMoved);
m_gestureProcessor.m_gesturePinch.OnPinchStarted += EventHandler(this, &InputDeviceTouch::UpdatePinchStarted);
m_gestureProcessor.m_gesturePinch.OnPinchMoved += EventHandler(this, &InputDeviceTouch::UpdatePinchMoved);
}
decltype(InputDeviceTouch::m_touches)::iterator InputDeviceTouch::FindTouch(TouchId id)
{
return std::find_if(m_touches.begin(), m_touches.end(), [id](const Touch& touch)
{
return (id == touch.GetId());
});
}
TouchId InputDeviceTouch::AddTouch(const Math::Vector2f& position)
{
m_touches.emplace_back(m_nextId, position);
OnTouchAdded(m_touches.back());
return m_nextId++;
}
void InputDeviceTouch::TouchDown(TouchId id)
{
auto touchIter = FindTouch(id);
if (touchIter != m_touches.end())
{
touchIter->m_down = true;
if (!m_multiTouch && m_touches.size() > 1)
{
int count = 0;
for (const auto &touch : m_touches)
{
if(touch.IsDown()) count++;
if (count > 1)
{
Logger::INPUT->debug("Begin multitouch");
m_multiTouch = true;
break;
}
}
}
OnTouchDown.NotifyAll(*touchIter);
m_gestureProcessor.TouchDown(*touchIter);
}
else
{
Logger::INPUT->warn("Received touch down event for unknown touch id.");
}
}
void InputDeviceTouch::TouchUp(TouchId id)
{
auto touchIter = FindTouch(id);
if (touchIter != m_touches.end())
{
touchIter->m_down = false;
if (m_multiTouch)
{
bool hasDown = false;
for (const auto &touch : m_touches)
{
if (touch.IsDown())
{
hasDown = true;
break;
}
}
if (!hasDown)
{
Logger::INPUT->debug("End multitouch");
m_multiTouch = false;
}
}
OnTouchUp.NotifyAll(*touchIter);
m_gestureProcessor.TouchUp(*touchIter);
}
else
{
Logger::INPUT->warn("Received touch up for unknown touch id.");
}
}
void InputDeviceTouch::TouchMoved(TouchId id, const Math::Vector2f& position)
{
auto touchIter = FindTouch(id);
if (touchIter != m_touches.end())
{
touchIter->m_lastPosition = touchIter->m_position;
touchIter->m_position = position;
OnTouchMoved.NotifyAll(*touchIter);
m_gestureProcessor.TouchMoved(*touchIter);
}
else
{
Logger::INPUT->warn("Received touch moved for unknown touch id");
}
}
void InputDeviceTouch::RemoveTouch(TouchId id)
{
auto touchIter = FindTouch(id);
if (touchIter != m_touches.end())
{
auto removedTouch = *touchIter;
m_touches.erase(touchIter);
if (m_touches.empty())
{
m_multiTouch = false;
}
OnTouchRemoved.NotifyAll(removedTouch);
}
else
{
Logger::INPUT->warn("Received remove request for unknown touch id");
}
}
void InputDeviceTouch::UpdatePinchStarted(const Gesture* pinch, const PinchInfo& info)
{
m_lastPinchInfo = m_nextPinchInfo = info;
}
void InputDeviceTouch::UpdatePanStarted(const Gesture* pan, const PanInfo& info)
{
m_lastPanInfo = m_nextPanInfo = info;
}
void InputDeviceTouch::UpdatePanTwoFingersStarted(const Gesture* pan, const PanInfo& info)
{
m_last2FPanInfo = m_next2FPanInfo = info;
}
void InputDeviceTouch::UpdatePinchMoved(const Gesture* pinch, const PinchInfo& info)
{
m_nextPinchInfo = info;
}
void InputDeviceTouch::UpdatePanMoved(const Gesture* pan, const PanInfo& info)
{
m_nextPanInfo = info;
}
void InputDeviceTouch::UpdatePanTwoFingersMoved(const Gesture* pan, const PanInfo& info)
{
m_next2FPanInfo = info;
}
void InputDeviceTouch::UpdateTap(const Gesture* tap, const TapInfo& info)
{
m_toBePressedButtons = 1;
m_nextTap = info.position;
m_axes[InputKey::Touch::Axis::AXIS_TAP_DURATION] = info.duration.count();
}
void InputDeviceTouch::Tick()
{
m_lastPressedButtons = m_pressedButtons;
m_pressedButtons = m_toBePressedButtons;
m_toBePressedButtons = 0;
// Tap
auto diff = m_nextTap - m_lastTap;
m_axes[InputKey::Touch::Axis::AXIS_TAP_X] = diff.x;
m_axes[InputKey::Touch::Axis::AXIS_TAP_Y] = diff.y;
m_lastTap = m_nextTap;
// Pan
diff = m_nextPanInfo.position - m_lastPanInfo.position;
m_axes[InputKey::Touch::Axis::AXIS_PAN_X] = diff.x;
m_axes[InputKey::Touch::Axis::AXIS_PAN_Y] = diff.y;
m_lastPanInfo = m_nextPanInfo;
diff = m_next2FPanInfo.position - m_last2FPanInfo.position;
m_axes[InputKey::Touch::Axis::AXIS_PAN_TWO_FINGERS_X] = diff.x;
m_axes[InputKey::Touch::Axis::AXIS_PAN_TWO_FINGERS_Y] = diff.y;
m_next2FPanInfo = m_last2FPanInfo;
// Pinch
m_axes[InputKey::Touch::Axis::AXIS_PINCH] = m_nextPinchInfo.scale;
diff = m_nextPinchInfo.position - m_lastPinchInfo.position;
m_axes[InputKey::Touch::Axis::AXIS_PINCH_CENTER_X] = diff.x;
m_axes[InputKey::Touch::Axis::AXIS_PINCH_CENTER_Y] = diff.y;
}
}

View File

@@ -0,0 +1,88 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Touch.hpp"
#include "GestureProcessor.hpp"
#include "Input/InputDevice.hpp"
#include "Base/Event.hpp"
#include "GestureInfos.hpp"
#include <mutex>
#include <vector>
namespace OpenVulkano::Input
{
class InputDeviceTouch final : public InputDevice
{
GestureProcessor m_gestureProcessor;
std::vector<Touch> m_touches;
TouchId m_nextId = 0;
bool m_multiTouch = false;
uint8_t m_pressedButtons = 0, m_lastPressedButtons = 0, m_toBePressedButtons = 0;
float m_axes[InputKey::Touch::Axis::AXIS_LAST + 1] = { 0 };
Math::Vector2f m_lastTap{}, m_nextTap{};
PinchInfo m_lastPinchInfo, m_nextPinchInfo;
PanInfo m_lastPanInfo, m_nextPanInfo, m_last2FPanInfo, m_next2FPanInfo;
decltype(m_touches)::iterator FindTouch(TouchId id);
[[nodiscard]] bool GetLastButton(InputKey::Touch::Button button) const
{
return m_lastPressedButtons & (1 << button);
}
public:
InputDeviceTouch() = default;
void Init();
void Tick() final;
TouchId AddTouch(const Math::Vector2f& position);
void TouchDown(TouchId id);
void TouchUp(TouchId id);
void TouchMoved(TouchId id, const Math::Vector2f& position);
void RemoveTouch(TouchId id);
void UpdatePinchStarted(const Gesture* pinch, const PinchInfo &info);
void UpdatePinchMoved(const Gesture* pinch, const PinchInfo &info);
void UpdatePanStarted(const Gesture* pan, const PanInfo &info);
void UpdatePanMoved(const Gesture* pan, const PanInfo &info);
void UpdatePanTwoFingersStarted(const Gesture* pan, const PanInfo &info);
void UpdatePanTwoFingersMoved(const Gesture* pan, const PanInfo &info);
void UpdateTap(const Gesture* tap, const TapInfo &info);
[[nodiscard]] float ReadAxis(int16_t key) const override {return GetAxis( static_cast<InputKey::Touch::Axis>(key)); }
[[nodiscard]] bool ReadButton(int16_t key) const override { return GetButton(static_cast<InputKey::Touch::Button>(key)); }
[[nodiscard]] bool ReadButtonUp(int16_t key) const override { return GetButtonUp(static_cast<InputKey::Touch::Button>(key)); }
[[nodiscard]] bool ReadButtonDown(int16_t key) const override { return GetButtonDown(static_cast<InputKey::Touch::Button>(key)); }
[[nodiscard]] const Math::Vector2f& GetTapPosition() const { return m_nextTap; }
[[nodiscard]] float GetAxis(const InputKey::Touch::Axis axis) const { return m_axes[axis]; }
[[nodiscard]] bool GetButton(const InputKey::Touch::Button button) const
{
return m_pressedButtons & (1 << button);
}
[[nodiscard]] bool GetButtonUp(const InputKey::Touch::Button button) const
{
return !GetButton(button) && GetLastButton(button);
}
[[nodiscard]] bool GetButtonDown(const InputKey::Touch::Button button) const
{
return GetButton(button) && !GetLastButton(button);
}
[[nodiscard]] bool HasTouch() const { return !m_touches.empty(); }
[[nodiscard]] bool IsMultiTouch() const { return m_multiTouch; }
Event<Touch&> OnTouchAdded, OnTouchDown, OnTouchUp, OnTouchMoved, OnTouchRemoved;
};
}

View File

@@ -0,0 +1,36 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Math/Math.hpp"
namespace OpenVulkano::Input
{
typedef uint32_t TouchId;
class Touch final
{
friend class InputDeviceTouch;
TouchId m_id;
Math::Vector2f m_position, m_lastPosition;
bool m_down = false;
public:
Touch(TouchId id, const Math::Vector2f& position)
: m_id(id), m_position(position), m_lastPosition(position)
{}
[[nodiscard]] TouchId GetId() const { return m_id; }
[[nodiscard]] Math::Vector2f GetPosition() const { return m_position; }
[[nodiscard]] Math::Vector2f GetLastPosition() const { return m_position; }
[[nodiscard]] bool IsDown() const { return m_down; }
};
}

View File

@@ -0,0 +1,22 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Touch.hpp"
namespace OpenVulkano::Input
{
struct TouchInfo final
{
TouchId touchId;
Math::Vector2f initialPosition;
Math::Vector2f currentPosition;
Math::Vector2f delta;
bool isDrag = false;
bool active = false;
};
}