/* * 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(); } } }