QskGestureRecognizer using event filtering
This commit is contained in:
parent
267c559330
commit
067cffbd7c
@ -345,15 +345,6 @@
|
||||
\return Area, where to lay out the child items
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QskControl::gestureRect
|
||||
|
||||
Returns the area where to accept gestures.
|
||||
The default implementation returns QskQuickItem::rect().
|
||||
|
||||
\sa gestureFilter(), gestureEvent()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QskControl::focusIndicatorRect
|
||||
|
||||
@ -896,10 +887,6 @@
|
||||
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QskControl::gestureFilter
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn void QskControl::gestureEvent
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QskEvent.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskStackBoxAnimator.h>
|
||||
#include <QskPanGestureRecognizer.h>
|
||||
|
||||
#include <QQuickFramebufferObject>
|
||||
#include <QGuiApplication>
|
||||
@ -20,6 +21,23 @@
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace
|
||||
{
|
||||
class PanRecognizer final : public QskPanGestureRecognizer
|
||||
{
|
||||
public:
|
||||
PanRecognizer( MainItem* mainItem )
|
||||
: QskPanGestureRecognizer( mainItem )
|
||||
{
|
||||
setOrientations( Qt::Horizontal | Qt::Vertical );
|
||||
setMinDistance( 50 );
|
||||
setTimeout( 100 );
|
||||
|
||||
setWatchedItem( mainItem );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QPair< Cube::Position, Cube::Edge > Cube::s_neighbors[ Cube::NumPositions ][ Cube::NumEdges ] =
|
||||
{
|
||||
// neighbors of Left side:
|
||||
@ -245,9 +263,7 @@ MainItem::MainItem( QQuickItem* parent )
|
||||
setAcceptedMouseButtons( Qt::LeftButton );
|
||||
setFiltersChildMouseEvents( true );
|
||||
|
||||
m_panRecognizer.setOrientations( Qt::Horizontal | Qt::Vertical );
|
||||
m_panRecognizer.setMinDistance( 50 );
|
||||
m_panRecognizer.setWatchedItem( this );
|
||||
(void) new PanRecognizer( this );
|
||||
|
||||
m_mainLayout->setSpacing( 0 );
|
||||
|
||||
@ -331,24 +347,5 @@ void MainItem::keyPressEvent( QKeyEvent* event )
|
||||
|
||||
m_cube->switchPosition( direction );
|
||||
}
|
||||
bool MainItem::gestureFilter( const QQuickItem* item, const QEvent* event )
|
||||
{
|
||||
auto& recognizer = m_panRecognizer;
|
||||
|
||||
if( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
|
||||
if( ( item != this ) || ( recognizer.timeout() < 0 ) )
|
||||
{
|
||||
if( recognizer.hasProcessedBefore( mouseEvent ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
recognizer.setTimeout( ( item == this ) ? -1 : 100 );
|
||||
}
|
||||
|
||||
return recognizer.processEvent( item, event, false );
|
||||
}
|
||||
|
||||
#include "moc_MainItem.cpp"
|
||||
|
@ -1,11 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QskControl.h>
|
||||
#include <QskPanGestureRecognizer.h>
|
||||
#include <QskStackBox.h>
|
||||
|
||||
#include <QQuickWindow>
|
||||
|
||||
class MenuBar;
|
||||
class QskBox;
|
||||
class QskLinearBox;
|
||||
@ -66,18 +63,17 @@ class MainItem : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskControl;
|
||||
|
||||
public:
|
||||
MainItem( QQuickItem* parent = nullptr );
|
||||
|
||||
protected:
|
||||
void keyPressEvent( QKeyEvent* ) override final;
|
||||
|
||||
bool gestureFilter( const QQuickItem*, const QEvent* ) override final;
|
||||
void gestureEvent( QskGestureEvent* ) override final;
|
||||
|
||||
private:
|
||||
QskLinearBox* m_mainLayout;
|
||||
MenuBar* m_menuBar;
|
||||
Cube* m_cube;
|
||||
QskPanGestureRecognizer m_panRecognizer;
|
||||
};
|
||||
|
@ -16,8 +16,10 @@
|
||||
#include <QskObjectCounter.h>
|
||||
#include <QskPushButton.h>
|
||||
#include <QskScrollArea.h>
|
||||
#include <QskSwipeView.h>
|
||||
#include <QskQuick.h>
|
||||
#include <QskWindow.h>
|
||||
#include <QskRgbValue.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
@ -83,8 +85,9 @@ class Thumbnail : public QskPushButton
|
||||
void mousePressEvent( QMouseEvent* event ) override
|
||||
{
|
||||
/*
|
||||
rgnore events: to check if the pae gesture recoognizer of the scroll
|
||||
area becomes active without timeout ( see QskScrollBox::mousePressEvent )
|
||||
ignore events: to check if the pan gesture recoognizer of the scroll
|
||||
area works, when the event arrives as regular event
|
||||
( not via childMouseEventFilter )
|
||||
*/
|
||||
event->setAccepted( false );
|
||||
}
|
||||
@ -198,6 +201,8 @@ class ScrollArea : public QskScrollArea
|
||||
ScrollArea( QQuickItem* parentItem = nullptr )
|
||||
: QskScrollArea( parentItem )
|
||||
{
|
||||
setMargins( QMarginsF( 25, 25, 5, 5 ) );
|
||||
|
||||
// settings usually done in the skins
|
||||
setBoxBorderMetricsHint( Viewport, 2 );
|
||||
setBoxBorderColorsHint( Viewport, Qt::gray ); // works with most color schemes
|
||||
@ -282,18 +287,42 @@ int main( int argc, char* argv[] )
|
||||
iconGrid->setSizePolicy( QskSizePolicy::MinimumExpanding,
|
||||
QskSizePolicy::MinimumExpanding );
|
||||
|
||||
auto scrollArea = new ScrollArea( box );
|
||||
scrollArea->setMargins( QMarginsF( 25, 25, 5, 5 ) );
|
||||
auto scrollArea = new ScrollArea();
|
||||
scrollArea->setScrolledItem( iconGrid );
|
||||
|
||||
#if 0
|
||||
// for testing nested gestures
|
||||
auto swipeView = new QskSwipeView();
|
||||
|
||||
swipeView->addItem( scrollArea );
|
||||
|
||||
for ( int i = 0; i < 1; i++ )
|
||||
{
|
||||
using namespace QskRgb;
|
||||
|
||||
const QRgb colors[] = { FireBrick, DodgerBlue, OliveDrab, Gold, Wheat };
|
||||
|
||||
auto page = new QskControl();
|
||||
|
||||
const auto index = i % ( sizeof( colors ) / sizeof( colors[0] ) );
|
||||
page->setBackgroundColor( colors[ index ] );
|
||||
|
||||
swipeView->addItem( page );
|
||||
}
|
||||
|
||||
box->addItem( swipeView );
|
||||
#else
|
||||
box->addItem( scrollArea );
|
||||
#endif
|
||||
|
||||
auto focusIndicator = new QskFocusIndicator();
|
||||
focusIndicator->setBoxBorderColorsHint( QskFocusIndicator::Panel, Qt::darkRed );
|
||||
|
||||
QskWindow window;
|
||||
window.resize( 600, 600 );
|
||||
window.addItem( box );
|
||||
window.addItem( focusIndicator );
|
||||
|
||||
window.resize( 600, 600 );
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
|
@ -805,38 +805,45 @@ bool QskControl::event( QEvent* event )
|
||||
|
||||
break;
|
||||
}
|
||||
case QskEvent::GestureFilter:
|
||||
{
|
||||
/*
|
||||
qskMaybeGesture is sending an event, so that it can be manipulated
|
||||
by event filters. F.e QskDrawer wants to add a gesture to
|
||||
some other control to initiate its appearance.
|
||||
*/
|
||||
|
||||
auto ev = static_cast< QskGestureFilterEvent* >( event );
|
||||
|
||||
if ( ev->event()->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( ev->event() );
|
||||
const auto pos =
|
||||
mapFromItem( ev->item(), qskMousePosition( mouseEvent ) );
|
||||
|
||||
if ( !gestureRect().contains( pos ) )
|
||||
{
|
||||
ev->setMaybeGesture( false );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ev->setMaybeGesture( gestureFilter( ev->item(), ev->event() ) );
|
||||
|
||||
break;
|
||||
}
|
||||
case QskEvent::Gesture:
|
||||
{
|
||||
gestureEvent( static_cast< QskGestureEvent* >( event ) );
|
||||
return true;
|
||||
}
|
||||
case QEvent::MouseButtonPress:
|
||||
{
|
||||
/*
|
||||
We need to do a gesture detection with abort criterions
|
||||
first. When it fails the input events will be processed
|
||||
in ascending order along the item tree until an item accepts
|
||||
the event.
|
||||
|
||||
This detection can be done in childMouseEventFilter() for all
|
||||
children - but not for the filtering control ( = this ) itself.
|
||||
So we need to do this here.
|
||||
*/
|
||||
if ( qskMaybeGesture( this, this, event ) )
|
||||
return true;
|
||||
|
||||
const bool ok = Inherited::event( event );
|
||||
|
||||
if ( !event->isAccepted() )
|
||||
{
|
||||
/*
|
||||
When the initial gesture detection failed and the event
|
||||
has not been handled here we do a second gesture detection
|
||||
without passing a child. It can be used for detections
|
||||
without abort criterions.
|
||||
|
||||
An example is the pan gesture detection, that can be started without
|
||||
any timeouts now.
|
||||
*/
|
||||
if ( qskMaybeGesture( this, nullptr, event ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
if ( qskMaybeGesture( this, this, event ) )
|
||||
@ -847,12 +854,25 @@ bool QskControl::event( QEvent* event )
|
||||
|
||||
bool QskControl::childMouseEventFilter( QQuickItem* child, QEvent* event )
|
||||
{
|
||||
return qskMaybeGesture( this, child, event );
|
||||
}
|
||||
/*
|
||||
The strategy implemented in many classes of the Qt development is
|
||||
to analyze the events without blocking the handling of the child.
|
||||
Once a gesture is detected the gesture handling trys to steal the
|
||||
mouse grab hoping for the child to abort its operation.
|
||||
|
||||
bool QskControl::gestureFilter( const QQuickItem*, const QEvent* )
|
||||
{
|
||||
return false;
|
||||
This approach has obvious problems:
|
||||
|
||||
- operations already started on press can't be aborted anymore
|
||||
- the child needs to agree on losing the grab ( setKeepMouseGrab( false ) )
|
||||
- ...
|
||||
|
||||
We implement a different strategy: processing of the events
|
||||
by the children is blocked until the gesture detection has accepted
|
||||
or rejected. In case of a rejection the events will be replayed.
|
||||
( see QskGestureRecognizer )
|
||||
*/
|
||||
|
||||
return qskMaybeGesture( this, child, event );
|
||||
}
|
||||
|
||||
void QskControl::gestureEvent( QskGestureEvent* )
|
||||
@ -978,11 +998,6 @@ QRectF QskControl::layoutRectForSize( const QSizeF& size ) const
|
||||
return qskValidOrEmptyInnerRect( r, margins() );
|
||||
}
|
||||
|
||||
QRectF QskControl::gestureRect() const
|
||||
{
|
||||
return rect();
|
||||
}
|
||||
|
||||
QRectF QskControl::focusIndicatorRect() const
|
||||
{
|
||||
return contentsRect();
|
||||
|
@ -80,7 +80,6 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable
|
||||
QRectF layoutRect() const;
|
||||
|
||||
virtual QRectF layoutRectForSize( const QSizeF& ) const;
|
||||
virtual QRectF gestureRect() const;
|
||||
|
||||
virtual QRectF focusIndicatorRect() const;
|
||||
virtual QRectF focusIndicatorClipRect() const;
|
||||
@ -196,7 +195,6 @@ class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable
|
||||
void hoverLeaveEvent( QHoverEvent* ) override;
|
||||
|
||||
bool childMouseEventFilter( QQuickItem*, QEvent* ) override;
|
||||
virtual bool gestureFilter( const QQuickItem*, const QEvent* );
|
||||
|
||||
void itemChange( ItemChange, const ItemChangeData& ) override;
|
||||
void geometryChange( const QRectF&, const QRectF& ) override;
|
||||
|
@ -92,7 +92,6 @@ void QskPanGesture::setPosition( const QPointF& pos )
|
||||
|
||||
QskSwipeGesture::QskSwipeGesture()
|
||||
: QskGesture( Swipe )
|
||||
, m_velocity( 0.0 )
|
||||
, m_angle( 0.0 )
|
||||
{
|
||||
}
|
||||
@ -101,11 +100,6 @@ QskSwipeGesture::~QskSwipeGesture()
|
||||
{
|
||||
}
|
||||
|
||||
void QskSwipeGesture::setVelocity( qreal velocity )
|
||||
{
|
||||
m_velocity = velocity;
|
||||
}
|
||||
|
||||
void QskSwipeGesture::setAngle( qreal angle )
|
||||
{
|
||||
m_angle = angle;
|
||||
|
@ -136,14 +136,10 @@ class QSK_EXPORT QskSwipeGesture : public QskGesture
|
||||
QskSwipeGesture();
|
||||
~QskSwipeGesture() override;
|
||||
|
||||
void setVelocity( qreal velocity );
|
||||
inline qreal velocity() const { return m_velocity; }
|
||||
|
||||
void setAngle( qreal angle );
|
||||
inline qreal angle() const;
|
||||
inline qreal angle() const { return m_angle; };
|
||||
|
||||
private:
|
||||
qreal m_velocity;
|
||||
qreal m_angle;
|
||||
};
|
||||
|
||||
|
@ -1,241 +1,119 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskGestureRecognizer.h"
|
||||
#include "QskEvent.h"
|
||||
#include "QskQuick.h"
|
||||
|
||||
#include <qbasictimer.h>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qcoreevent.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscopedpointer.h>
|
||||
#include <qvector.h>
|
||||
|
||||
QSK_QT_PRIVATE_BEGIN
|
||||
|
||||
#include <private/qquickwindow_p.h>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK( 6, 3, 0 )
|
||||
#include <private/qeventpoint_p.h>
|
||||
#endif
|
||||
|
||||
QSK_QT_PRIVATE_END
|
||||
|
||||
static QMouseEvent* qskClonedMouseEventAt(
|
||||
const QMouseEvent* event, QPointF* localPos )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
|
||||
auto clonedEvent = QQuickWindowPrivate::cloneMouseEvent(
|
||||
const_cast< QMouseEvent* >( event ), localPos );
|
||||
|
||||
#else
|
||||
auto clonedEvent = event->clone();
|
||||
|
||||
if ( localPos )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 3, 0 )
|
||||
auto& point = QMutableEventPoint::from( clonedEvent->point( 0 ) );
|
||||
|
||||
point.detach();
|
||||
point.setPosition( *localPos );
|
||||
#else
|
||||
auto& point = clonedEvent->point( 0 );
|
||||
|
||||
QMutableEventPoint::detach( point );
|
||||
QMutableEventPoint::setPosition( point, *localPos );
|
||||
#endif
|
||||
}
|
||||
QSK_QT_PRIVATE_BEGIN
|
||||
#include <private/qquickwindow_p.h>
|
||||
QSK_QT_PRIVATE_END
|
||||
#endif
|
||||
|
||||
Q_ASSERT( event->timestamp() == clonedEvent->timestamp() );
|
||||
|
||||
return clonedEvent;
|
||||
}
|
||||
|
||||
static inline QMouseEvent* qskClonedMouseEvent(
|
||||
const QMouseEvent* mouseEvent, const QQuickItem* item = nullptr )
|
||||
static QMouseEvent* qskClonedMouseEvent( const QMouseEvent* event )
|
||||
{
|
||||
QMouseEvent* clonedEvent;
|
||||
auto event = const_cast< QMouseEvent* >( mouseEvent );
|
||||
|
||||
if ( item )
|
||||
{
|
||||
auto localPos = item->mapFromScene( qskMouseScenePosition( event ) );
|
||||
clonedEvent = qskClonedMouseEventAt( event, &localPos );
|
||||
}
|
||||
else
|
||||
{
|
||||
clonedEvent = qskClonedMouseEventAt( event, nullptr );
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
clonedEvent = QQuickWindowPrivate::cloneMouseEvent(
|
||||
const_cast< QMouseEvent* >( event ), nullptr );
|
||||
#else
|
||||
clonedEvent = event->clone();
|
||||
#endif
|
||||
clonedEvent->setAccepted( false );
|
||||
|
||||
return clonedEvent;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/*
|
||||
As we don't want QskGestureRecognizer being a QObject
|
||||
we need some extra timers - usually one per screen.
|
||||
*/
|
||||
|
||||
class Timer final : public QObject
|
||||
{
|
||||
public:
|
||||
void start( int ms, QskGestureRecognizer* recognizer )
|
||||
{
|
||||
if ( m_timer.isActive() )
|
||||
qWarning() << "QskGestureRecognizer: resetting an active timer";
|
||||
|
||||
m_recognizer = recognizer;
|
||||
m_timer.start( ms, this );
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
m_timer.stop();
|
||||
m_recognizer = nullptr;
|
||||
}
|
||||
|
||||
const QskGestureRecognizer* recognizer() const
|
||||
{
|
||||
return m_recognizer;
|
||||
}
|
||||
|
||||
protected:
|
||||
void timerEvent( QTimerEvent* ) override
|
||||
{
|
||||
m_timer.stop();
|
||||
|
||||
if ( m_recognizer )
|
||||
{
|
||||
auto recognizer = m_recognizer;
|
||||
m_recognizer = nullptr;
|
||||
|
||||
recognizer->reject();
|
||||
}
|
||||
}
|
||||
|
||||
QBasicTimer m_timer;
|
||||
QskGestureRecognizer* m_recognizer = nullptr;
|
||||
};
|
||||
|
||||
class TimerTable
|
||||
{
|
||||
public:
|
||||
~TimerTable()
|
||||
{
|
||||
qDeleteAll( m_table );
|
||||
}
|
||||
|
||||
void startTimer( int ms, QskGestureRecognizer* recognizer )
|
||||
{
|
||||
Timer* timer = nullptr;
|
||||
|
||||
for ( auto t : std::as_const( m_table ) )
|
||||
{
|
||||
if ( t->recognizer() == nullptr ||
|
||||
t->recognizer() == recognizer )
|
||||
{
|
||||
timer = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( timer == nullptr )
|
||||
{
|
||||
timer = new Timer();
|
||||
m_table += timer;
|
||||
}
|
||||
|
||||
timer->start( ms, recognizer );
|
||||
}
|
||||
|
||||
void stopTimer( const QskGestureRecognizer* recognizer )
|
||||
{
|
||||
for ( auto timer : std::as_const( m_table ) )
|
||||
{
|
||||
if ( timer->recognizer() == recognizer )
|
||||
{
|
||||
// we keep the timer to be used later again
|
||||
timer->stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
Usually we have not more than one entry.
|
||||
Only when having more than one screen we
|
||||
might have mouse events to be processed
|
||||
simultaneously.
|
||||
*/
|
||||
QVector< Timer* > m_table;
|
||||
};
|
||||
|
||||
class PendingEvents : public QVector< QMouseEvent* >
|
||||
{
|
||||
public:
|
||||
~PendingEvents()
|
||||
{
|
||||
qDeleteAll( *this );
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
qDeleteAll( *this );
|
||||
clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Q_GLOBAL_STATIC( TimerTable, qskTimerTable )
|
||||
|
||||
class QskGestureRecognizer::PrivateData
|
||||
{
|
||||
public:
|
||||
PrivateData()
|
||||
: watchedItem( nullptr )
|
||||
, timestamp( 0 )
|
||||
, timestampProcessed( 0 )
|
||||
, timeout( -1 )
|
||||
, buttons( Qt::NoButton )
|
||||
, state( QskGestureRecognizer::Idle )
|
||||
, isReplayingEvents( false )
|
||||
void startTimer( QskGestureRecognizer* recognizer )
|
||||
{
|
||||
if ( timeoutId >= 0 )
|
||||
{
|
||||
// warning
|
||||
}
|
||||
|
||||
if ( timeout <= 0 )
|
||||
{
|
||||
// warning
|
||||
}
|
||||
|
||||
timeoutId = recognizer->startTimer( timeout );
|
||||
}
|
||||
|
||||
QQuickItem* watchedItem;
|
||||
void stopTimer( QskGestureRecognizer* recognizer )
|
||||
{
|
||||
if ( timeoutId >= 0 )
|
||||
{
|
||||
recognizer->killTimer( timeoutId );
|
||||
timeoutId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
PendingEvents pendingEvents;
|
||||
ulong timestamp;
|
||||
ulong timestampProcessed;
|
||||
inline Qt::MouseButtons effectiveMouseButtons() const
|
||||
{
|
||||
if ( buttons != Qt::NoButton )
|
||||
return buttons;
|
||||
|
||||
int timeout; // ms
|
||||
return watchedItem->acceptedMouseButtons();
|
||||
}
|
||||
|
||||
Qt::MouseButtons buttons;
|
||||
QQuickItem* watchedItem = nullptr;
|
||||
|
||||
int state : 4;
|
||||
bool isReplayingEvents : 1; // not exception safe !!!
|
||||
QVector< QMouseEvent* > pendingEvents;
|
||||
|
||||
quint64 timestampStarted = 0;
|
||||
quint64 timestampProcessed = 0;
|
||||
|
||||
int timeoutId = -1;
|
||||
int timeout = -1; // ms
|
||||
|
||||
Qt::MouseButtons buttons = Qt::NoButton;
|
||||
|
||||
unsigned char state = QskGestureRecognizer::Idle;
|
||||
bool rejectOnTimeout = true;
|
||||
bool expired = false;
|
||||
};
|
||||
|
||||
QskGestureRecognizer::QskGestureRecognizer()
|
||||
: m_data( new PrivateData() )
|
||||
QskGestureRecognizer::QskGestureRecognizer( QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_data( new PrivateData() )
|
||||
{
|
||||
}
|
||||
|
||||
QskGestureRecognizer::~QskGestureRecognizer()
|
||||
{
|
||||
qskTimerTable->stopTimer( this );
|
||||
qDeleteAll( m_data->pendingEvents );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::setWatchedItem( QQuickItem* item )
|
||||
{
|
||||
if ( m_data->watchedItem )
|
||||
{
|
||||
m_data->watchedItem->removeEventFilter( this );
|
||||
reset();
|
||||
}
|
||||
|
||||
m_data->watchedItem = item;
|
||||
|
||||
if ( m_data->watchedItem )
|
||||
m_data->watchedItem->installEventFilter( this );
|
||||
|
||||
/*
|
||||
// doing those here: ???
|
||||
m_data->watchedItem->setFiltersChildMouseEvents();
|
||||
m_data->watchedItem->setAcceptedMouseButtons();
|
||||
*/
|
||||
}
|
||||
|
||||
QQuickItem* QskGestureRecognizer::watchedItem() const
|
||||
@ -253,6 +131,24 @@ Qt::MouseButtons QskGestureRecognizer::acceptedMouseButtons() const
|
||||
return m_data->buttons;
|
||||
}
|
||||
|
||||
QRectF QskGestureRecognizer::gestureRect() const
|
||||
{
|
||||
if ( m_data->watchedItem )
|
||||
return qskItemRect( m_data->watchedItem );
|
||||
|
||||
return QRectF( 0.0, 0.0, -1.0, -1.0 );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::setRejectOnTimeout( bool on )
|
||||
{
|
||||
m_data->rejectOnTimeout = on;
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::rejectOnTimeout() const
|
||||
{
|
||||
return m_data->rejectOnTimeout;
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::setTimeout( int ms )
|
||||
{
|
||||
m_data->timeout = ms;
|
||||
@ -263,29 +159,37 @@ int QskGestureRecognizer::timeout() const
|
||||
return m_data->timeout;
|
||||
}
|
||||
|
||||
ulong QskGestureRecognizer::timestamp() const
|
||||
void QskGestureRecognizer::timerEvent( QTimerEvent* event )
|
||||
{
|
||||
return m_data->timestamp;
|
||||
if ( event->timerId() == m_data->timeoutId )
|
||||
{
|
||||
m_data->stopTimer( this );
|
||||
|
||||
if ( m_data->rejectOnTimeout )
|
||||
{
|
||||
reject();
|
||||
m_data->expired = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Inherited::timerEvent( event );
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::hasProcessedBefore( const QMouseEvent* event ) const
|
||||
quint64 QskGestureRecognizer::timestampStarted() const
|
||||
{
|
||||
return event && ( event->timestamp() <= m_data->timestampProcessed );
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::isReplaying() const
|
||||
{
|
||||
return m_data->isReplayingEvents;
|
||||
return m_data->timestampStarted;
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::setState( State state )
|
||||
{
|
||||
if ( state != m_data->state )
|
||||
{
|
||||
const State oldState = static_cast< QskGestureRecognizer::State >( m_data->state );
|
||||
const auto oldState = static_cast< State >( m_data->state );
|
||||
m_data->state = state;
|
||||
|
||||
stateChanged( oldState, state );
|
||||
Q_EMIT stateChanged( oldState, state );
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,31 +198,113 @@ QskGestureRecognizer::State QskGestureRecognizer::state() const
|
||||
return static_cast< QskGestureRecognizer::State >( m_data->state );
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::processEvent(
|
||||
const QQuickItem* item, const QEvent* event, bool blockReplayedEvents )
|
||||
bool QskGestureRecognizer::eventFilter( QObject* object, QEvent* event)
|
||||
{
|
||||
if ( m_data->isReplayingEvents && blockReplayedEvents )
|
||||
{
|
||||
/*
|
||||
This one is a replayed event after we had decided
|
||||
that this interaction is to be ignored
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& watchedItem = m_data->watchedItem;
|
||||
|
||||
if ( watchedItem == nullptr || !watchedItem->isEnabled() ||
|
||||
!watchedItem->isVisible() || watchedItem->window() == nullptr )
|
||||
if ( ( object == watchedItem ) &&
|
||||
( static_cast< int >( event->type() ) == QskEvent::GestureFilter ) )
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
if ( !watchedItem->isEnabled() || !watchedItem->isVisible()
|
||||
|| watchedItem->window() == nullptr )
|
||||
{
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ev = static_cast< QskGestureFilterEvent* >( event );
|
||||
ev->setMaybeGesture( maybeGesture( ev->item(), ev->event() ) );
|
||||
|
||||
return ev->maybeGesture();
|
||||
}
|
||||
|
||||
QScopedPointer< QMouseEvent > clonedPress;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::maybeGesture(
|
||||
const QQuickItem* item, const QEvent* event )
|
||||
{
|
||||
switch( static_cast< int >( event->type() ) )
|
||||
{
|
||||
case QEvent::UngrabMouse:
|
||||
{
|
||||
if ( m_data->state != Idle )
|
||||
{
|
||||
// someone took our grab away, we have to give up
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case QEvent::MouseButtonPress:
|
||||
{
|
||||
if ( state() != Idle )
|
||||
{
|
||||
qWarning() << "QskGestureRecognizer: pressed, while not being idle";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
|
||||
if ( mouseEvent->timestamp() > m_data->timestampProcessed )
|
||||
{
|
||||
m_data->timestampProcessed = mouseEvent->timestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
A mouse event might appear several times:
|
||||
|
||||
- we ran into a timeout and reposted the events
|
||||
|
||||
- we rejected because the event sequence does
|
||||
not match the acceptance criterions and reposted
|
||||
the events
|
||||
|
||||
- another gesture recognizer for a child item
|
||||
has reposted the events because of the reasons above
|
||||
|
||||
For most situations we can simply skip processing already
|
||||
processed events with the exception of a timeout. Here we might
|
||||
have to retry without timeout, when none of the items was
|
||||
accepting the events. This specific situation is indicated by
|
||||
item set to null.
|
||||
*/
|
||||
|
||||
if ( item || !m_data->expired )
|
||||
return false;
|
||||
}
|
||||
|
||||
return processMouseEvent( item, mouseEvent );
|
||||
}
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
if ( state() <= Idle )
|
||||
return false;
|
||||
|
||||
const auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
return processMouseEvent( item, mouseEvent );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QskGestureRecognizer::processMouseEvent(
|
||||
const QQuickItem* item, const QMouseEvent* event )
|
||||
{
|
||||
auto& watchedItem = m_data->watchedItem;
|
||||
|
||||
const auto pos = watchedItem->mapFromScene( qskMouseScenePosition( event ) );
|
||||
const auto timestamp = event->timestamp();
|
||||
|
||||
if ( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
if ( !gestureRect().contains( pos ) )
|
||||
return false;
|
||||
|
||||
if ( m_data->state != Idle )
|
||||
{
|
||||
// should not happen, when using the recognizer correctly
|
||||
@ -327,12 +313,7 @@ bool QskGestureRecognizer::processEvent(
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::MouseButtons buttons = m_data->buttons;
|
||||
if ( buttons == Qt::NoButton )
|
||||
buttons = watchedItem->acceptedMouseButtons();
|
||||
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
if ( !( buttons & mouseEvent->button() ) )
|
||||
if ( !( m_data->effectiveMouseButtons() & event->button() ) )
|
||||
return false;
|
||||
|
||||
/*
|
||||
@ -342,22 +323,7 @@ bool QskGestureRecognizer::processEvent(
|
||||
if ( !qskGrabMouse( watchedItem ) )
|
||||
return false;
|
||||
|
||||
m_data->timestamp = mouseEvent->timestamp();
|
||||
|
||||
if ( item != watchedItem )
|
||||
{
|
||||
/*
|
||||
The first press happens before having the mouse grab and might
|
||||
have been for a child of watchedItem. Then we create a clone
|
||||
of the event with positions translated into the coordinate system
|
||||
of watchedItem.
|
||||
*/
|
||||
|
||||
clonedPress.reset( qskClonedMouseEvent( mouseEvent, watchedItem ) );
|
||||
|
||||
item = watchedItem;
|
||||
event = clonedPress.data();
|
||||
}
|
||||
m_data->timestampStarted = timestamp;
|
||||
|
||||
if ( m_data->timeout != 0 )
|
||||
{
|
||||
@ -366,134 +332,118 @@ bool QskGestureRecognizer::processEvent(
|
||||
out, that we don't want to handle the mouse event sequence,
|
||||
*/
|
||||
|
||||
m_data->stopTimer( this );
|
||||
|
||||
if ( m_data->timeout > 0 )
|
||||
qskTimerTable->startTimer( m_data->timeout, this );
|
||||
m_data->startTimer( this );
|
||||
|
||||
setState( Pending );
|
||||
}
|
||||
else
|
||||
{
|
||||
setState( Accepted );
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ( item == watchedItem ) && ( m_data->state > Idle ) )
|
||||
if ( m_data->state <= Idle )
|
||||
return false;
|
||||
|
||||
if ( m_data->state == Pending )
|
||||
m_data->pendingEvents += qskClonedMouseEvent( event );
|
||||
|
||||
switch( static_cast< int >( event->type() ) )
|
||||
{
|
||||
switch ( event->type() )
|
||||
case QEvent::MouseButtonPress:
|
||||
processPress( pos, timestamp, item == nullptr );
|
||||
break;
|
||||
|
||||
case QEvent::MouseMove:
|
||||
processMove( pos, timestamp );
|
||||
break;
|
||||
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
case QEvent::MouseButtonPress:
|
||||
if ( m_data->state == Pending )
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
|
||||
|
||||
pressEvent( mouseEvent );
|
||||
return true;
|
||||
reject();
|
||||
}
|
||||
|
||||
case QEvent::MouseMove:
|
||||
else
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
|
||||
|
||||
moveEvent( mouseEvent );
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::MouseButtonRelease:
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
m_data->pendingEvents += qskClonedMouseEvent( mouseEvent );
|
||||
|
||||
if ( m_data->state == Pending )
|
||||
{
|
||||
reject();
|
||||
}
|
||||
else
|
||||
{
|
||||
releaseEvent( mouseEvent );
|
||||
reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::UngrabMouse:
|
||||
{
|
||||
// someone took away our grab, we have to give up
|
||||
processRelease( pos, timestamp );
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::pressEvent( const QMouseEvent* )
|
||||
void QskGestureRecognizer::processPress(
|
||||
const QPointF& pos, quint64 timestamp, bool isFinal )
|
||||
{
|
||||
Q_UNUSED( pos );
|
||||
Q_UNUSED( timestamp );
|
||||
Q_UNUSED( isFinal );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::moveEvent( const QMouseEvent* )
|
||||
void QskGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp )
|
||||
{
|
||||
Q_UNUSED( pos );
|
||||
Q_UNUSED( timestamp );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::releaseEvent( const QMouseEvent* )
|
||||
void QskGestureRecognizer::processRelease( const QPointF& pos, quint64 timestamp )
|
||||
{
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::stateChanged( State from, State to )
|
||||
{
|
||||
Q_UNUSED( from )
|
||||
Q_UNUSED( to )
|
||||
Q_UNUSED( pos );
|
||||
Q_UNUSED( timestamp );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::accept()
|
||||
{
|
||||
qskTimerTable->stopTimer( this );
|
||||
m_data->pendingEvents.reset();
|
||||
m_data->stopTimer( this );
|
||||
|
||||
qDeleteAll( m_data->pendingEvents );
|
||||
m_data->pendingEvents.clear();
|
||||
|
||||
setState( Accepted );
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::reject()
|
||||
{
|
||||
/*
|
||||
Moving the events to a local buffer, so that we can clear
|
||||
m_data->pendingEvents before replaying them.
|
||||
*/
|
||||
const auto events = m_data->pendingEvents;
|
||||
m_data->pendingEvents.clear();
|
||||
|
||||
reset();
|
||||
|
||||
auto watchedItem = m_data->watchedItem;
|
||||
if ( watchedItem == nullptr )
|
||||
return;
|
||||
/*
|
||||
Not 100% sure if we should send or post the events
|
||||
|
||||
const auto window = watchedItem->window();
|
||||
if ( window == nullptr )
|
||||
return;
|
||||
Posting the events adds an extra round trip but we avoid
|
||||
recursive event handler calls, that might not be expected
|
||||
from the implementation of a control.
|
||||
*/
|
||||
|
||||
m_data->isReplayingEvents = true;
|
||||
|
||||
qskUngrabMouse( watchedItem );
|
||||
|
||||
if ( !events.isEmpty() &&
|
||||
( events[ 0 ]->type() == QEvent::MouseButtonPress ) )
|
||||
if ( !events.isEmpty() )
|
||||
{
|
||||
/*
|
||||
In a situation of several recognizers ( f.e a vertical
|
||||
scroll view inside a horizontal swipe view ), we might receive
|
||||
cloned events from another recognizer.
|
||||
To avoid to process them twice we store the most recent timestamp
|
||||
of the cloned events we have already processed, but reposted.
|
||||
*/
|
||||
if ( auto watchedItem = m_data->watchedItem )
|
||||
{
|
||||
if ( const auto window = watchedItem->window() )
|
||||
{
|
||||
for ( auto event : events )
|
||||
QCoreApplication::postEvent( window, event );
|
||||
}
|
||||
}
|
||||
|
||||
m_data->timestampProcessed = events.last()->timestamp();
|
||||
|
||||
for ( auto event : events )
|
||||
QCoreApplication::sendEvent( window, event );
|
||||
#if 0
|
||||
// when using QCoreApplication::sendEvent above
|
||||
qDeleteAll( events );
|
||||
#endif
|
||||
}
|
||||
|
||||
m_data->isReplayingEvents = false;
|
||||
}
|
||||
|
||||
void QskGestureRecognizer::abort()
|
||||
@ -503,12 +453,16 @@ void QskGestureRecognizer::abort()
|
||||
|
||||
void QskGestureRecognizer::reset()
|
||||
{
|
||||
qskTimerTable->stopTimer( this );
|
||||
|
||||
m_data->stopTimer( this );
|
||||
qskUngrabMouse( m_data->watchedItem );
|
||||
|
||||
m_data->pendingEvents.reset();
|
||||
m_data->timestamp = 0;
|
||||
qDeleteAll( m_data->pendingEvents );
|
||||
m_data->pendingEvents.clear();
|
||||
|
||||
m_data->timestampStarted = 0;
|
||||
m_data->expired = false;
|
||||
|
||||
setState( Idle );
|
||||
}
|
||||
|
||||
#include "moc_QskGestureRecognizer.cpp"
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "QskGlobal.h"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qnamespace.h>
|
||||
#include <memory>
|
||||
|
||||
@ -15,8 +16,20 @@ class QQuickItem;
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
|
||||
class QSK_EXPORT QskGestureRecognizer
|
||||
class QSK_EXPORT QskGestureRecognizer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( State state READ state NOTIFY stateChanged )
|
||||
Q_PROPERTY( QQuickItem* watchedItem READ watchedItem WRITE setWatchedItem )
|
||||
|
||||
Q_PROPERTY( Qt::MouseButtons acceptedMouseButtons
|
||||
READ acceptedMouseButtons WRITE setAcceptedMouseButtons )
|
||||
|
||||
Q_PROPERTY( int timeout READ timeout WRITE setTimeout )
|
||||
|
||||
using Inherited = QObject;
|
||||
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
@ -25,8 +38,12 @@ class QSK_EXPORT QskGestureRecognizer
|
||||
Accepted
|
||||
};
|
||||
|
||||
QskGestureRecognizer();
|
||||
virtual ~QskGestureRecognizer();
|
||||
Q_ENUM( State )
|
||||
|
||||
QskGestureRecognizer( QObject* parent = nullptr );
|
||||
~QskGestureRecognizer() override;
|
||||
|
||||
bool eventFilter( QObject* object, QEvent* event) override;
|
||||
|
||||
void setWatchedItem( QQuickItem* );
|
||||
QQuickItem* watchedItem() const;
|
||||
@ -35,13 +52,14 @@ class QSK_EXPORT QskGestureRecognizer
|
||||
void setAcceptedMouseButtons( Qt::MouseButtons );
|
||||
Qt::MouseButtons acceptedMouseButtons() const;
|
||||
|
||||
void setRejectOnTimeout( bool );
|
||||
bool rejectOnTimeout() const;
|
||||
|
||||
void setTimeout( int );
|
||||
int timeout() const;
|
||||
|
||||
// timestamp, when the Idle state had been left
|
||||
ulong timestamp() const;
|
||||
|
||||
bool processEvent( const QQuickItem*, const QEvent*, bool blockReplayedEvents = true );
|
||||
quint64 timestampStarted() const;
|
||||
|
||||
void reject();
|
||||
void accept();
|
||||
@ -49,17 +67,28 @@ class QSK_EXPORT QskGestureRecognizer
|
||||
|
||||
State state() const;
|
||||
|
||||
bool isReplaying() const;
|
||||
bool hasProcessedBefore( const QMouseEvent* ) const;
|
||||
virtual QRectF gestureRect() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void stateChanged( State from, State to );
|
||||
|
||||
protected:
|
||||
virtual void pressEvent( const QMouseEvent* );
|
||||
virtual void moveEvent( const QMouseEvent* );
|
||||
virtual void releaseEvent( const QMouseEvent* );
|
||||
void timerEvent( QTimerEvent* ) override;
|
||||
|
||||
virtual void stateChanged( State from, State to );
|
||||
/*
|
||||
This API works for single-touch gestures and multi-touch gestures,
|
||||
that can be translated to single positions ( f.e 2 finger swipes ).
|
||||
Once we support more complex inputs ( f.e pinch gesture ) we need to
|
||||
make substantial adjustments here.
|
||||
*/
|
||||
virtual void processPress( const QPointF&, quint64 timestamp, bool isFinal );
|
||||
virtual void processMove( const QPointF&, quint64 timestamp );
|
||||
virtual void processRelease( const QPointF&, quint64 timestamp );
|
||||
|
||||
private:
|
||||
bool maybeGesture( const QQuickItem*, const QEvent* );
|
||||
bool processMouseEvent( const QQuickItem*, const QMouseEvent* );
|
||||
|
||||
void setState( State );
|
||||
void reset();
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPanGestureRecognizer.h"
|
||||
#include "QskEvent.h"
|
||||
#include "QskGesture.h"
|
||||
@ -139,20 +144,12 @@ namespace
|
||||
class QskPanGestureRecognizer::PrivateData
|
||||
{
|
||||
public:
|
||||
PrivateData()
|
||||
: orientations( Qt::Horizontal | Qt::Vertical )
|
||||
, minDistance( 15 )
|
||||
, timestamp( 0.0 )
|
||||
, angle( 0.0 )
|
||||
{
|
||||
}
|
||||
Qt::Orientations orientations = Qt::Horizontal | Qt::Vertical;
|
||||
|
||||
Qt::Orientations orientations;
|
||||
int minDistance = 15;
|
||||
|
||||
int minDistance;
|
||||
|
||||
ulong timestamp; // timestamp of the last mouse event
|
||||
qreal angle;
|
||||
quint64 timestampVelocity = 0.0; // timestamp of the last mouse event
|
||||
qreal angle = 0.0;
|
||||
|
||||
QPointF origin;
|
||||
QPointF pos; // position of the last mouse event
|
||||
@ -160,9 +157,11 @@ class QskPanGestureRecognizer::PrivateData
|
||||
VelocityTracker velocityTracker;
|
||||
};
|
||||
|
||||
QskPanGestureRecognizer::QskPanGestureRecognizer()
|
||||
: m_data( new PrivateData() )
|
||||
QskPanGestureRecognizer::QskPanGestureRecognizer( QObject* parent )
|
||||
: QskGestureRecognizer( parent )
|
||||
, m_data( new PrivateData() )
|
||||
{
|
||||
setTimeout( 100 ); // value from the platform ???
|
||||
}
|
||||
|
||||
QskPanGestureRecognizer::~QskPanGestureRecognizer()
|
||||
@ -189,23 +188,29 @@ int QskPanGestureRecognizer::minDistance() const
|
||||
return m_data->minDistance;
|
||||
}
|
||||
|
||||
void QskPanGestureRecognizer::pressEvent( const QMouseEvent* event )
|
||||
void QskPanGestureRecognizer::processPress( const QPointF& pos, quint64, bool isFinal )
|
||||
{
|
||||
m_data->origin = m_data->pos = qskMousePosition( event );
|
||||
m_data->timestamp = timestamp();
|
||||
/*
|
||||
When nobody was interested in the press we can disable the timeout and let
|
||||
the distance of the mouse moves be the only criterion.
|
||||
*/
|
||||
setRejectOnTimeout( !isFinal );
|
||||
|
||||
m_data->origin = m_data->pos = pos;
|
||||
m_data->timestampVelocity = timestampStarted();
|
||||
|
||||
m_data->velocityTracker.reset();
|
||||
}
|
||||
|
||||
void QskPanGestureRecognizer::moveEvent( const QMouseEvent* event )
|
||||
void QskPanGestureRecognizer::processMove( const QPointF& pos, quint64 timestamp )
|
||||
{
|
||||
const ulong elapsed = event->timestamp() - m_data->timestamp;
|
||||
const ulong elapsedTotal = event->timestamp() - timestamp();
|
||||
const ulong elapsed = timestamp - m_data->timestampVelocity;
|
||||
const ulong elapsedTotal = timestamp - timestampStarted();
|
||||
|
||||
const QPointF oldPos = m_data->pos;
|
||||
|
||||
m_data->timestamp = event->timestamp();
|
||||
m_data->pos = qskMousePosition( event );
|
||||
m_data->timestampVelocity = timestamp;
|
||||
m_data->pos = pos;
|
||||
|
||||
if ( elapsedTotal > 0 ) // ???
|
||||
{
|
||||
@ -249,11 +254,11 @@ void QskPanGestureRecognizer::moveEvent( const QMouseEvent* event )
|
||||
}
|
||||
}
|
||||
|
||||
void QskPanGestureRecognizer::releaseEvent( const QMouseEvent* event )
|
||||
void QskPanGestureRecognizer::processRelease( const QPointF&, quint64 timestamp )
|
||||
{
|
||||
if ( state() == QskGestureRecognizer::Accepted )
|
||||
{
|
||||
const ulong elapsedTotal = event->timestamp() - timestamp();
|
||||
const ulong elapsedTotal = timestamp - timestampStarted();
|
||||
const qreal velocity = m_data->velocityTracker.velocity( elapsedTotal );
|
||||
|
||||
qskSendPanGestureEvent( watchedItem(), QskGesture::Finished,
|
||||
|
@ -14,7 +14,7 @@ class QSK_EXPORT QskPanGestureRecognizer : public QskGestureRecognizer
|
||||
using Inherited = QskGestureRecognizer;
|
||||
|
||||
public:
|
||||
QskPanGestureRecognizer();
|
||||
QskPanGestureRecognizer( QObject* = nullptr );
|
||||
~QskPanGestureRecognizer() override;
|
||||
|
||||
void setMinDistance( int pixels );
|
||||
@ -24,9 +24,9 @@ class QSK_EXPORT QskPanGestureRecognizer : public QskGestureRecognizer
|
||||
Qt::Orientations orientations() const;
|
||||
|
||||
private:
|
||||
void pressEvent( const QMouseEvent* ) override;
|
||||
void moveEvent( const QMouseEvent* ) override;
|
||||
void releaseEvent( const QMouseEvent* ) override;
|
||||
void processPress( const QPointF&, quint64 timestamp, bool isFinal ) override;
|
||||
void processMove( const QPointF&, quint64 timestamp ) override;
|
||||
void processRelease( const QPointF&, quint64 timestamp ) override;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
|
@ -20,6 +20,30 @@ static inline constexpr qreal qskViewportPadding()
|
||||
return 10.0; // should be from the skin, TODO ...
|
||||
}
|
||||
|
||||
static inline bool qskIsScrollable(
|
||||
const QskScrollBox* scrollBox, Qt::Orientations orientations )
|
||||
{
|
||||
if ( orientations )
|
||||
{
|
||||
const QSizeF viewSize = scrollBox->viewContentsRect().size();
|
||||
const QSizeF& scrollableSize = scrollBox->scrollableSize();
|
||||
|
||||
if ( orientations & Qt::Vertical )
|
||||
{
|
||||
if ( viewSize.height() < scrollableSize.height() )
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( orientations & Qt::Horizontal )
|
||||
{
|
||||
if ( viewSize.width() < scrollableSize.width() )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class FlickAnimator final : public QskFlickAnimator
|
||||
@ -100,6 +124,29 @@ namespace
|
||||
QPointF m_from;
|
||||
QPointF m_to;
|
||||
};
|
||||
|
||||
class PanRecognizer final : public QskPanGestureRecognizer
|
||||
{
|
||||
using Inherited = QskPanGestureRecognizer;
|
||||
|
||||
public:
|
||||
PanRecognizer( QObject* parent = nullptr )
|
||||
: QskPanGestureRecognizer( parent )
|
||||
{
|
||||
setOrientations( Qt::Horizontal | Qt::Vertical );
|
||||
}
|
||||
|
||||
QRectF gestureRect() const override
|
||||
{
|
||||
if ( auto scrollBox = qobject_cast< const QskScrollBox* >( watchedItem() ) )
|
||||
{
|
||||
if ( qskIsScrollable( scrollBox, orientations() ) )
|
||||
return scrollBox->viewContentsRect();
|
||||
}
|
||||
|
||||
return QRectF( 0.0, 0.0, -1.0, -1.0 ); // empty
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class QskScrollBox::PrivateData
|
||||
@ -108,8 +155,7 @@ class QskScrollBox::PrivateData
|
||||
QPointF scrollPos;
|
||||
QSizeF scrollableSize = QSize( 0.0, 0.0 );
|
||||
|
||||
QskPanGestureRecognizer panRecognizer;
|
||||
int panRecognizerTimeout = 100; // value coming from the platform ???
|
||||
PanRecognizer panRecognizer;
|
||||
|
||||
FlickAnimator flicker;
|
||||
ScrollAnimator scroller;
|
||||
@ -125,13 +171,11 @@ QskScrollBox::QskScrollBox( QQuickItem* parent )
|
||||
m_data->scroller.setScrollBox( this );
|
||||
|
||||
m_data->panRecognizer.setWatchedItem( this );
|
||||
m_data->panRecognizer.setOrientations( Qt::Horizontal | Qt::Vertical );
|
||||
|
||||
setFiltersChildMouseEvents( true );
|
||||
|
||||
setAcceptedMouseButtons( Qt::LeftButton );
|
||||
setWheelEnabled( true );
|
||||
setFiltersChildMouseEvents( true );
|
||||
|
||||
setWheelEnabled( true );
|
||||
setFocusPolicy( Qt::StrongFocus );
|
||||
|
||||
connectWindow( window(), true );
|
||||
@ -185,12 +229,12 @@ void QskScrollBox::setFlickRecognizerTimeout( int timeout )
|
||||
if ( timeout < 0 )
|
||||
timeout = -1;
|
||||
|
||||
m_data->panRecognizerTimeout = timeout;
|
||||
m_data->panRecognizer.setTimeout( timeout );
|
||||
}
|
||||
|
||||
int QskScrollBox::flickRecognizerTimeout() const
|
||||
{
|
||||
return m_data->panRecognizerTimeout;
|
||||
return m_data->panRecognizer.timeout();
|
||||
}
|
||||
|
||||
void QskScrollBox::setFlickableOrientations( Qt::Orientations orientations )
|
||||
@ -250,11 +294,6 @@ QSizeF QskScrollBox::scrollableSize() const
|
||||
return m_data->scrollableSize;
|
||||
}
|
||||
|
||||
QRectF QskScrollBox::gestureRect() const
|
||||
{
|
||||
return viewContentsRect();
|
||||
}
|
||||
|
||||
void QskScrollBox::ensureItemVisible( const QQuickItem* item )
|
||||
{
|
||||
if ( qskIsAncestorOf( this, item ) )
|
||||
@ -362,25 +401,6 @@ void QskScrollBox::geometryChangeEvent( QskGeometryChangeEvent* event )
|
||||
Inherited::geometryChangeEvent( event );
|
||||
}
|
||||
|
||||
void QskScrollBox::mousePressEvent( QMouseEvent* event )
|
||||
{
|
||||
auto& recognizer = m_data->panRecognizer;
|
||||
if ( recognizer.hasProcessedBefore( event ) )
|
||||
{
|
||||
if ( m_data->panRecognizerTimeout != 0 )
|
||||
{
|
||||
recognizer.abort();
|
||||
recognizer.setTimeout( -1 );
|
||||
|
||||
recognizer.processEvent( this, event, false );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return Inherited::mousePressEvent( event );
|
||||
}
|
||||
|
||||
void QskScrollBox::gestureEvent( QskGestureEvent* event )
|
||||
{
|
||||
if ( event->gesture()->type() == QskGesture::Pan )
|
||||
@ -446,75 +466,6 @@ void QskScrollBox::wheelEvent( QWheelEvent* event )
|
||||
|
||||
#endif
|
||||
|
||||
bool QskScrollBox::gestureFilter( const QQuickItem* item, const QEvent* event )
|
||||
{
|
||||
if ( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
// Checking first if panning is possible at all
|
||||
|
||||
bool maybeGesture = false;
|
||||
|
||||
const auto orientations = m_data->panRecognizer.orientations();
|
||||
if ( orientations )
|
||||
{
|
||||
const QSizeF viewSize = viewContentsRect().size();
|
||||
const QSizeF& scrollableSize = m_data->scrollableSize;
|
||||
|
||||
if ( orientations & Qt::Vertical )
|
||||
{
|
||||
if ( viewSize.height() < scrollableSize.height() )
|
||||
maybeGesture = true;
|
||||
}
|
||||
|
||||
if ( orientations & Qt::Horizontal )
|
||||
{
|
||||
if ( viewSize.width() < scrollableSize.width() )
|
||||
maybeGesture = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !maybeGesture )
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
auto& recognizer = m_data->panRecognizer;
|
||||
recognizer.setTimeout( m_data->panRecognizerTimeout );
|
||||
|
||||
if ( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
/*
|
||||
This code is a bit tricky as the filter is called in different situations:
|
||||
|
||||
a) The press was on a child of the view
|
||||
b) The press was on the view
|
||||
|
||||
In case of b) things are simple and we can let the recognizer
|
||||
decide without timeout if it is was a gesture or not.
|
||||
|
||||
In case of a) we give the recognizer some time to decide - usually
|
||||
based on the distances of the following mouse events. If no decision
|
||||
could be made the recognizer aborts and replays the mouse events, so
|
||||
that the children can process them.
|
||||
|
||||
But if a child does not accept the mouse event it will be sent to
|
||||
its parent, finally ending up here for a second time.
|
||||
*/
|
||||
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
if ( recognizer.hasProcessedBefore( mouseEvent ) )
|
||||
{
|
||||
/*
|
||||
Note that the recognizer will be restarted without timout if the
|
||||
event ends up in in mousePressEvent ( = nobody else was interested )
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return recognizer.processEvent( item, event );
|
||||
}
|
||||
|
||||
QPointF QskScrollBox::boundedScrollPos( const QPointF& pos ) const
|
||||
{
|
||||
const QRectF vr = viewContentsRect();
|
||||
|
@ -42,7 +42,6 @@ class QSK_EXPORT QskScrollBox : public QskControl
|
||||
QSizeF scrollableSize() const;
|
||||
|
||||
virtual QRectF viewContentsRect() const = 0;
|
||||
QRectF gestureRect() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void scrolledTo( const QPointF& );
|
||||
@ -65,7 +64,6 @@ class QSK_EXPORT QskScrollBox : public QskControl
|
||||
void geometryChangeEvent( QskGeometryChangeEvent* ) override;
|
||||
void windowChangeEvent( QskWindowChangeEvent* ) override;
|
||||
|
||||
void mousePressEvent( QMouseEvent* ) override;
|
||||
void gestureEvent( QskGestureEvent* ) override;
|
||||
|
||||
#ifndef QT_NO_WHEELEVENT
|
||||
@ -73,7 +71,6 @@ class QSK_EXPORT QskScrollBox : public QskControl
|
||||
virtual QPointF scrollOffset( const QWheelEvent* ) const;
|
||||
#endif
|
||||
|
||||
bool gestureFilter( const QQuickItem*, const QEvent* ) override;
|
||||
void setScrollableSize( const QSizeF& );
|
||||
|
||||
private:
|
||||
|
@ -96,23 +96,6 @@ void QskSwipeView::resetDuration()
|
||||
setDuration( 500 );
|
||||
}
|
||||
|
||||
bool QskSwipeView::gestureFilter( const QQuickItem* item, const QEvent* event )
|
||||
{
|
||||
// see QskScrollBox.cpp
|
||||
|
||||
auto& recognizer = m_data->panRecognizer;
|
||||
|
||||
if ( event->type() == QEvent::MouseButtonPress )
|
||||
{
|
||||
auto mouseEvent = static_cast< const QMouseEvent* >( event );
|
||||
if ( recognizer.hasProcessedBefore( mouseEvent ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return recognizer.processEvent( item, event );
|
||||
|
||||
}
|
||||
|
||||
void QskSwipeView::gestureEvent( QskGestureEvent* event )
|
||||
{
|
||||
const auto gesture = static_cast< const QskPanGesture* >( event->gesture().get() );
|
||||
|
@ -57,7 +57,6 @@ class QSK_EXPORT QskSwipeView : public QskStackBox
|
||||
void swipeDistanceChanged( int );
|
||||
|
||||
protected:
|
||||
bool gestureFilter( const QQuickItem*, const QEvent* ) override;
|
||||
void gestureEvent( QskGestureEvent* ) override;
|
||||
|
||||
private:
|
||||
|
Loading…
x
Reference in New Issue
Block a user