QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskPanGestureRecognizer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskPanGestureRecognizer.h"
7#include "QskEvent.h"
8#include "QskGesture.h"
9
10#include <qline.h>
11#include <qmath.h>
12#include <qquickitem.h>
13#include <qguiapplication.h>
14#include <qstylehints.h>
15
16static inline bool qskIsInOrientation(
17 const QPointF& from, const QPointF& to, Qt::Orientations orientations )
18{
19 if ( orientations == ( Qt::Horizontal | Qt::Vertical ) )
20 return true;
21
22 const qreal dx = std::fabs( to.x() - from.x() );
23 const qreal dy = std::fabs( to.y() - from.y() );
24
25 const qreal ratio = 0.75;
26
27 if ( orientations == Qt::Horizontal )
28 return ( dx >= ratio * dy );
29
30 if ( orientations == Qt::Vertical )
31 return ( dy >= ratio * dx );
32
33 return false; // should never happen
34}
35
36static inline qreal qskDistance(
37 const QPointF& from, const QPointF& to, Qt::Orientations orientations )
38{
39 if ( orientations == Qt::Horizontal )
40 return std::fabs( to.x() - from.x() );
41
42 if ( orientations == Qt::Vertical )
43 return std::fabs( to.y() - from.y() );
44
45 const qreal dx = to.x() - from.x();
46 const qreal dy = to.y() - from.y();
47
48 return qSqrt( dx * dx + dy * dy );
49}
50
51static inline qreal qskAngle(
52 const QPointF& from, const QPointF& to, Qt::Orientations orientations )
53{
54 if ( orientations == Qt::Horizontal )
55 return ( to.x() >= from.x() ) ? 0.0 : 180.0;
56
57 if ( orientations == Qt::Vertical )
58 return ( to.y() >= from.y() ) ? 270.0 : 90.0;
59
60 return QLineF( from, to ).angle();
61}
62
63static void qskSendPanGestureEvent(
64 QskGestureRecognizer* recognizer, QskGesture::State state,
65 qreal velocity, qreal angle, const QPointF& origin,
66 const QPointF& lastPosition, const QPointF& position )
67{
68 auto item = recognizer->targetItem();
69 if ( item == nullptr )
70 item = recognizer->watchedItem();
71
72 auto gesture = std::make_shared< QskPanGesture >();
73 gesture->setState( state );
74
75 gesture->setAngle( angle );
76 gesture->setVelocity( velocity );
77
78 gesture->setOrigin( origin );
79 gesture->setLastPosition( lastPosition );
80 gesture->setPosition( position );
81
82 QskGestureEvent event( gesture );
83 QCoreApplication::sendEvent( item, &event );
84}
85
86namespace
87{
88 class VelocityTracker
89 {
90 public:
91 VelocityTracker()
92 {
93 reset();
94 }
95
96 void record( int elapsed, qreal velocity )
97 {
98 if ( ( velocity != 0.0 ) && ( m_values[ m_pos ].velocity != 0.0 ) )
99 {
100 if ( ( velocity > 0.0 ) != ( m_values[ m_pos ].velocity > 0.0 ) )
101 reset(); // direction has changed
102 }
103
104 m_values[ m_pos ].elapsed = elapsed;
105 m_values[ m_pos ].velocity = velocity;
106
107 m_pos = ( m_pos + 1 ) % Count;
108 }
109
110 inline void reset()
111 {
112 for ( auto& v : m_values )
113 {
114 v.elapsed = -1;
115 v.velocity = 0;
116 }
117 m_pos = 0;
118 }
119
120 inline qreal velocity( ulong elapsed ) const
121 {
122 qreal sum = 0;
123 int numVelocities = 0;
124
125 for ( const auto& v : m_values )
126 {
127 // only swipe events within the last 500 ms will be considered
128 if ( v.elapsed > 0 && ( elapsed - v.elapsed ) <= 500 )
129 {
130 sum += v.velocity;
131 numVelocities++;
132 }
133 }
134
135 return ( numVelocities > 0 ) ? ( sum / numVelocities ) : 0.0;
136 }
137
138 private:
139 int m_pos = 0;
140 enum { Count = 3 };
141
142 struct
143 {
144 int elapsed = 0;
145 qreal velocity = 0.0;
146 } m_values[ Count ];
147 };
148}
149
150class QskPanGestureRecognizer::PrivateData
151{
152 public:
153 Qt::Orientations orientations = Qt::Horizontal | Qt::Vertical;
154
155 int minDistance = QGuiApplication::styleHints()->startDragDistance() + 5;
156
157 quint64 timestampVelocity = 0.0; // timestamp of the last mouse event
158 qreal angle = 0.0;
159
160 QPointF origin;
161 QPointF pos; // position of the last mouse event
162
163 VelocityTracker velocityTracker;
164};
165
166QskPanGestureRecognizer::QskPanGestureRecognizer( QObject* parent )
167 : QskGestureRecognizer( parent )
168 , m_data( new PrivateData() )
169{
170 setTimeout( 100 ); // value from the platform ???
171}
172
173QskPanGestureRecognizer::~QskPanGestureRecognizer()
174{
175}
176
177void QskPanGestureRecognizer::setOrientations( Qt::Orientations orientations )
178{
179 m_data->orientations = orientations;
180}
181
182Qt::Orientations QskPanGestureRecognizer::orientations() const
183{
184 return m_data->orientations;
185}
186
187void QskPanGestureRecognizer::setMinDistance( int pixels )
188{
189 m_data->minDistance = qMax( pixels, 0 );
190}
191
192int QskPanGestureRecognizer::minDistance() const
193{
194 return m_data->minDistance;
195}
196
197void QskPanGestureRecognizer::processPress( const QPointF& pos, quint64, bool isFinal )
198{
199 /*
200 When nobody was interested in the press we can disable the timeout and let
201 the distance of the mouse moves be the only criterion.
202 */
203 setRejectOnTimeout( !isFinal );
204
205 m_data->origin = m_data->pos = pos;
206 m_data->timestampVelocity = timestampStarted();
207
208 m_data->velocityTracker.reset();
209}
210
211void QskPanGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp )
212{
213 const ulong elapsed = timestamp - m_data->timestampVelocity;
214 const ulong elapsedTotal = timestamp - timestampStarted();
215
216 const QPointF oldPos = m_data->pos;
217
218 m_data->timestampVelocity = timestamp;
219 m_data->pos = pos;
220
221 if ( elapsedTotal > 0 ) // ???
222 {
223 const qreal dist = qskDistance( oldPos, m_data->pos, m_data->orientations );
224
225 const qreal velocity = dist / ( elapsed / 1000.0 );
226 m_data->velocityTracker.record( elapsedTotal, velocity );
227 }
228
229 bool started = false;
230
231 if ( state() != QskGestureRecognizer::Accepted )
232 {
233 const qreal dist = qskDistance(
234 m_data->origin, m_data->pos, m_data->orientations );
235
236 if ( ( qAbs( dist ) >= m_data->minDistance ) &&
237 qskIsInOrientation( m_data->origin, m_data->pos, m_data->orientations ) )
238 {
239 accept();
240 started = true;
241 }
242 }
243
244 m_data->angle = qskAngle( oldPos, m_data->pos, m_data->orientations );
245
246 if ( state() == QskGestureRecognizer::Accepted )
247 {
248 const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal );
249
250 if ( started )
251 {
252 qskSendPanGestureEvent( this, QskGesture::Started,
253 velocity, m_data->angle, m_data->origin, m_data->origin, m_data->pos );
254 }
255 else
256 {
257 qskSendPanGestureEvent( this, QskGesture::Updated,
258 velocity, m_data->angle, m_data->origin, oldPos, m_data->pos );
259 }
260 }
261}
262
263void QskPanGestureRecognizer::processRelease( const QPointF&, quint64 timestamp )
264{
265 if ( state() == QskGestureRecognizer::Accepted )
266 {
267 const ulong elapsedTotal = timestamp - timestampStarted();
268 const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal );
269
270 qskSendPanGestureEvent( this, QskGesture::Finished,
271 velocity, m_data->angle, m_data->origin, m_data->pos, m_data->pos );
272 }
273}