qskinny/src/controls/QskPanGestureRecognizer.cpp

244 lines
6.1 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
#include "QskPanGestureRecognizer.h"
#include "QskGesture.h"
#include "QskEvent.h"
2018-07-19 14:10:48 +02:00
#include <qquickitem.h>
#include <qcoreapplication.h>
#include <qmath.h>
#include <qline.h>
2017-07-21 18:21:34 +02:00
static inline qreal qskDistance( const QPointF& from, const QPointF& to,
Qt::Orientations orientations )
{
if ( orientations == Qt::Horizontal )
return to.x() - from.x();
if ( orientations == Qt::Vertical )
return to.y() - from.y();
const qreal dx = to.x() - from.x();
const qreal dy = to.y() - from.y();
return qSqrt( dx * dx + dy * dy );
}
static inline qreal qskAngle( const QPointF& from, const QPointF& to,
Qt::Orientations orientations )
{
if ( orientations == Qt::Horizontal )
return ( to.x() >= from.x() ) ? 0.0 : 180.0;
if ( orientations == Qt::Vertical )
return ( to.y() >= from.y() ) ? 270.0 : 90.0;
return QLineF( from, to ).angle();
}
static void qskSendPanGestureEvent(
QQuickItem* item, QskGesture::State state, qreal velocity, qreal angle,
const QPointF& origin, const QPointF& lastPosition, const QPointF& position )
{
QskPanGesture gesture;
gesture.setState( state );
gesture.setAngle( angle );
gesture.setVelocity( velocity );
gesture.setOrigin( origin );
gesture.setLastPosition( lastPosition );
gesture.setPosition( position );
QskGestureEvent event( &gesture, false );
QCoreApplication::sendEvent( item, &event );
}
namespace
{
class VelocityTracker
{
public:
VelocityTracker()
{
reset();
}
void record( int elapsed, qreal velocity )
{
if ( ( velocity != 0.0 ) && ( m_values[m_pos].velocity != 0.0 ) )
{
if ( ( velocity > 0.0 ) != ( m_values[m_pos].velocity > 0.0 ) )
reset(); // direction has changed
}
m_values[m_pos].elapsed = elapsed;
m_values[m_pos].velocity = velocity;
m_pos = ( m_pos + 1 ) % Count;
}
inline void reset()
{
for ( int i = 0; i < Count; i++ )
{
m_values[i].elapsed = -1;
m_values[i].velocity = 0;
}
m_pos = 0;
}
inline qreal velocity( ulong elapsed ) const
{
qreal sum = 0;
int numVelocities = 0;
for ( int i = 0; i < Count; i++ )
{
const auto& v = m_values[i];
// only swipe events within the last 500 ms will be considered
if ( v.elapsed > 0 && ( elapsed - v.elapsed ) <= 500 )
{
sum += v.velocity;
numVelocities++;
}
}
return ( numVelocities > 0 ) ? ( sum / numVelocities ) : 0.0;
}
private:
int m_pos;
enum { Count = 3 };
struct
{
int elapsed;
qreal velocity;
} m_values[ Count ];
};
}
class QskPanGestureRecognizer::PrivateData
{
public:
PrivateData():
orientations( Qt::Horizontal | Qt::Vertical ),
minDistance( 15 ),
timestamp( 0.0 ),
angle( 0.0 )
{
}
Qt::Orientations orientations;
int minDistance;
ulong timestamp; // timestamp of the last mouse event
qreal angle;
QPointF origin;
QPointF pos; // position of the last mouse event
VelocityTracker velocityTracker;
};
QskPanGestureRecognizer::QskPanGestureRecognizer():
m_data( new PrivateData() )
{
}
QskPanGestureRecognizer::~QskPanGestureRecognizer()
{
}
void QskPanGestureRecognizer::setOrientations( Qt::Orientations orientations )
{
m_data->orientations = orientations;
}
Qt::Orientations QskPanGestureRecognizer::orientations() const
{
return m_data->orientations;
}
void QskPanGestureRecognizer::setMinDistance( int pixels )
{
m_data->minDistance = qMax( pixels, 0 );
}
int QskPanGestureRecognizer::minDistance() const
{
return m_data->minDistance;
}
void QskPanGestureRecognizer::pressEvent( const QMouseEvent* event )
{
m_data->origin = m_data->pos = event->localPos();
m_data->timestamp = timestamp();
m_data->velocityTracker.reset();
}
void QskPanGestureRecognizer::moveEvent( const QMouseEvent* event )
{
const ulong elapsed = event->timestamp() - m_data->timestamp;
const ulong elapsedTotal = event->timestamp() - timestamp();
const QPointF oldPos = m_data->pos;
m_data->timestamp = event->timestamp();
m_data->pos = event->localPos();
if ( elapsedTotal > 0 ) // ???
{
const qreal dist = qskDistance( oldPos, m_data->pos, m_data->orientations );
const qreal velocity = dist / ( elapsed / 1000.0 );
m_data->velocityTracker.record( elapsedTotal, velocity );
}
bool started = false;
if ( state() != QskGestureRecognizer::Accepted )
{
const qreal dist = qskDistance(
m_data->origin, m_data->pos, m_data->orientations );
if ( ( dist >= m_data->minDistance ) || ( -dist >= m_data->minDistance ) )
{
accept();
started = true;
}
}
m_data->angle = qskAngle( oldPos, m_data->pos, m_data->orientations );
if ( state() == QskGestureRecognizer::Accepted )
{
const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal );
if ( started )
{
qskSendPanGestureEvent( watchedItem(), QskGesture::Started,
velocity, m_data->angle, m_data->origin, m_data->origin, m_data->pos );
}
else
{
qskSendPanGestureEvent( watchedItem(), QskGesture::Updated,
velocity, m_data->angle, m_data->origin, oldPos, m_data->pos );
}
}
}
void QskPanGestureRecognizer::releaseEvent( const QMouseEvent* event )
{
if ( state() == QskGestureRecognizer::Accepted )
{
const ulong elapsedTotal = event->timestamp() - timestamp();
const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal );
qskSendPanGestureEvent( watchedItem(), QskGesture::Finished,
velocity, m_data->angle, m_data->origin, m_data->pos, m_data->pos );
}
}