diff --git a/openVulkanoCpp/Host/iOS/OpenVulkanoView.h b/openVulkanoCpp/Host/iOS/OpenVulkanoView.h new file mode 100644 index 0000000..e432261 --- /dev/null +++ b/openVulkanoCpp/Host/iOS/OpenVulkanoView.h @@ -0,0 +1,17 @@ +/* + * 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 + +#import +#import + +#pragma mark - +#pragma mark OpenVulkanoView + +//@interface OpenVulkanoView : UIView +@interface OpenVulkanoView : MTKView +@end diff --git a/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm b/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm new file mode 100644 index 0000000..ba38f94 --- /dev/null +++ b/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm @@ -0,0 +1,125 @@ +/* + * 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/. + */ + +#import "OpenVulkanoView.h" + +#include "Input/Touch/InputDeviceTouch.hpp" +#include "Input/InputManager.hpp" +#include + +#pragma mark - +#pragma mark OpenVulkanoView + +using namespace OpenVulkano; + +@interface OpenVulkanoView() +{ + CADisplayLink * m_displayLink; + std::map m_touchToIdMap; + Input::InputDeviceTouch m_touchDevice; +} +@end + +@implementation OpenVulkanoView +/** Returns a Metal-compatible layer. */ ++(Class) layerClass { return [CAMetalLayer class]; } + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonInit]; + } + return self; +} + +- (void)commonInit { + [self setMultipleTouchEnabled:YES]; + m_touchDevice.Init(); //TODO link window + Input::InputManager::GetInstance()->RegisterInputDevice(&m_touchDevice); +} + +- (void)dealloc { + for(auto& it : m_touchToIdMap) { + m_touchDevice.RemoveTouch(it.second); + } + Input::InputManager::GetInstance()->UnregisterInputDevice(&m_touchDevice); + m_touchDevice.Close(); + [super dealloc]; +} + +- (Math::Vector2f)getTouchPosition:(UITouch*)touch +{ + CGPoint uitouchLocation = [touch locationInView:touch.view]; + return { uitouchLocation.x * self.contentScaleFactor, uitouchLocation.y * self.contentScaleFactor }; +} + +- (void)touchesBegan:(NSSet*)inTouches withEvent:(UIEvent*)inEvent +{ + for (UITouch* touch in inTouches) + { + Math::Vector2f touchLocation = [self getTouchPosition: touch]; + uint32_t touchId = m_touchDevice.AddTouch(touchLocation); + m_touchDevice.TouchDown(touchId); + m_touchToIdMap.insert(std::make_pair(touch, touchId)); + } +} + +- (void)touchesCancelled:(NSSet*)inTouches withEvent:(UIEvent*)inEvent +{ + for (UITouch* touch in inTouches) + { + Math::Vector2f touchLocation = [self getTouchPosition: touch]; + + auto it = m_touchToIdMap.find(touch); + if (it != m_touchToIdMap.end()) + { + m_touchDevice.TouchUp(it->second); + m_touchDevice.RemoveTouch(it->second); + m_touchToIdMap.erase(it); + } + } +} + +- (void)touchesEnded:(NSSet*)inTouches withEvent:(UIEvent*)inEvent +{ + for (UITouch* touch in inTouches) + { + Math::Vector2f touchLocation = [self getTouchPosition: touch]; + + auto it = m_touchToIdMap.find(touch); + if (it != m_touchToIdMap.end()) + { + m_touchDevice.TouchUp(it->second); + m_touchDevice.RemoveTouch(it->second); + m_touchToIdMap.erase(it); + } + } +} + +- (void)touchesMoved:(NSSet*)inTouches withEvent:(UIEvent*)inEvent +{ + for (UITouch* touch in inTouches) + { + Math::Vector2f touchLocation = [self getTouchPosition: touch]; + + auto it = m_touchToIdMap.find(touch); + if (it != m_touchToIdMap.end()) + { + m_touchDevice.TouchMoved(it->second, touchLocation); + } + } +} + +@end + diff --git a/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.h b/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.h index fec17bf..db0223f 100644 --- a/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.h +++ b/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.h @@ -5,7 +5,6 @@ */ #import -#import #pragma mark - #pragma mark OpenVulkanoViewController @@ -14,10 +13,3 @@ -(void*) makeGraphicsApp; @end - -#pragma mark - -#pragma mark OpenVulkanoView - -@interface OpenVulkanoView : MTKView -@end - diff --git a/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.mm b/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.mm index 4afc421..873bb95 100644 --- a/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.mm +++ b/openVulkanoCpp/Host/iOS/OpenVulkanoViewController.mm @@ -11,6 +11,7 @@ #include "ExampleApps/CubesExampleApp.hpp" #include "Base/UI/IVulkanWindow.hpp" +#import #pragma mark - #pragma mark OpenVulkanoViewController @@ -203,13 +204,3 @@ public: @end -#pragma mark - -#pragma mark OpenVulkanoView - -@implementation OpenVulkanoView - -/** Returns a Metal-compatible layer. */ -+(Class) layerClass { return [CAMetalLayer class]; } - -@end - diff --git a/openVulkanoCpp/Input/InputKey.hpp b/openVulkanoCpp/Input/InputKey.hpp index 450ecd5..90743da 100644 --- a/openVulkanoCpp/Input/InputKey.hpp +++ b/openVulkanoCpp/Input/InputKey.hpp @@ -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; } diff --git a/openVulkanoCpp/Input/Touch/Gesture.hpp b/openVulkanoCpp/Input/Touch/Gesture.hpp new file mode 100644 index 0000000..7c59713 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/Gesture.hpp @@ -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 + +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(); + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GestureInfos.hpp b/openVulkanoCpp/Input/Touch/GestureInfos.hpp new file mode 100644 index 0000000..e3c595b --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GestureInfos.hpp @@ -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 + +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; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GesturePan.cpp b/openVulkanoCpp/Input/Touch/GesturePan.cpp new file mode 100644 index 0000000..73a5316 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GesturePan.cpp @@ -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); + } + } + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GesturePan.hpp b/openVulkanoCpp/Input/Touch/GesturePan.hpp new file mode 100644 index 0000000..1591698 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GesturePan.hpp @@ -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 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 OnPanStarted, OnPanMoved, OnPanEnded; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GesturePinch.cpp b/openVulkanoCpp/Input/Touch/GesturePinch.cpp new file mode 100644 index 0000000..daffc68 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GesturePinch.cpp @@ -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& 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 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 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(); + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GesturePinch.hpp b/openVulkanoCpp/Input/Touch/GesturePinch.hpp new file mode 100644 index 0000000..9cb1223 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GesturePinch.hpp @@ -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 m_pendingTouches; + float m_initialDistance = 0, m_lastDistance; + bool m_paused = false; + + void TryStart(); + + float LinearLength(std::vector 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 OnPinchStarted, OnPinchMoved, OnPinchEnded; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GestureProcessor.cpp b/openVulkanoCpp/Input/Touch/GestureProcessor.cpp new file mode 100644 index 0000000..6c1cade --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GestureProcessor.cpp @@ -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; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GestureProcessor.hpp b/openVulkanoCpp/Input/Touch/GestureProcessor.hpp new file mode 100644 index 0000000..dd585de --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GestureProcessor.hpp @@ -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 +#include +#include + +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 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; + + void SetConflictResolutionDelegate(const ConflictResolutionDelegate& delegate) { m_conflictResolutionDelegate = delegate; } + + private: + ConflictResolutionDelegate m_conflictResolutionDelegate; + //endregion + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GestureTap.cpp b/openVulkanoCpp/Input/Touch/GestureTap.cpp new file mode 100644 index 0000000..2080cc0 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GestureTap.cpp @@ -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::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 + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/GestureTap.hpp b/openVulkanoCpp/Input/Touch/GestureTap.hpp new file mode 100644 index 0000000..4ec4841 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/GestureTap.hpp @@ -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 + +namespace OpenVulkano::Input +{ + class GestureTap final : public Gesture + { + std::chrono::time_point m_startTime; + float m_distance; + Math::Vector2f m_initialPoint; + std::map 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 OnTap; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp b/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp new file mode 100644 index 0000000..9f72d40 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp @@ -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 + +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; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Input/Touch/InputDeviceTouch.hpp b/openVulkanoCpp/Input/Touch/InputDeviceTouch.hpp new file mode 100644 index 0000000..7d0dfa7 --- /dev/null +++ b/openVulkanoCpp/Input/Touch/InputDeviceTouch.hpp @@ -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 +#include + +namespace OpenVulkano::Input +{ + class InputDeviceTouch final : public InputDevice + { + GestureProcessor m_gestureProcessor; + std::vector 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(key)); } + [[nodiscard]] bool ReadButton(int16_t key) const override { return GetButton(static_cast(key)); } + [[nodiscard]] bool ReadButtonUp(int16_t key) const override { return GetButtonUp(static_cast(key)); } + [[nodiscard]] bool ReadButtonDown(int16_t key) const override { return GetButtonDown(static_cast(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 OnTouchAdded, OnTouchDown, OnTouchUp, OnTouchMoved, OnTouchRemoved; + }; +} diff --git a/openVulkanoCpp/Input/Touch/Touch.hpp b/openVulkanoCpp/Input/Touch/Touch.hpp new file mode 100644 index 0000000..fa6111c --- /dev/null +++ b/openVulkanoCpp/Input/Touch/Touch.hpp @@ -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; } + }; +} diff --git a/openVulkanoCpp/Input/Touch/TouchInfo.hpp b/openVulkanoCpp/Input/Touch/TouchInfo.hpp new file mode 100644 index 0000000..dbb7ded --- /dev/null +++ b/openVulkanoCpp/Input/Touch/TouchInfo.hpp @@ -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; + }; +} \ No newline at end of file