From 4125f54452d25d778f618d80b878712da4fae12a Mon Sep 17 00:00:00 2001 From: Raphael Dumusc Date: Wed, 31 Aug 2016 11:46:55 +0200 Subject: [PATCH 1/2] Deflect: Replace WHEEL event with PINCH event --- .../cpp/core/ContentWindowControllerTests.cpp | 26 ++ tests/cpp/core/WebbrowserTests.cpp | 57 +-- tide/core/ContentInteractionDelegate.h | 2 +- tide/core/PixelStreamInteractionDelegate.cpp | 9 +- tide/core/PixelStreamInteractionDelegate.h | 2 +- tide/core/ZoomInteractionDelegate.cpp | 11 +- tide/core/ZoomInteractionDelegate.h | 2 +- tide/master/MultitouchArea.cpp | 363 ++++++++++-------- tide/master/MultitouchArea.h | 88 +++-- .../localstreamer/WebkitPixelStreamer.cpp | 24 +- .../localstreamer/WebkitPixelStreamer.h | 2 +- tide/master/resources/MasterContentWindow.qml | 5 +- 12 files changed, 349 insertions(+), 242 deletions(-) diff --git a/tests/cpp/core/ContentWindowControllerTests.cpp b/tests/cpp/core/ContentWindowControllerTests.cpp index 87c7a7b5..fe870836 100644 --- a/tests/cpp/core/ContentWindowControllerTests.cpp +++ b/tests/cpp/core/ContentWindowControllerTests.cpp @@ -100,6 +100,32 @@ BOOST_AUTO_TEST_CASE( testResizeAndMove ) BOOST_CHECK_CLOSE( coords.height(), centeredSize.height(), 0.00001 ); } +BOOST_AUTO_TEST_CASE( testScaleByPixelDelta ) +{ + ContentPtr content( new DummyContent ); + content->setDimensions( CONTENT_SIZE ); + ContentWindow window( content ); + + DisplayGroupPtr displayGroup( new DisplayGroup( wallSize )); + ContentWindowController controller( window, *displayGroup ); + + const QRectF& coords = window.getCoordinates(); + const auto pixelDelta = 40.0; + + controller.scale( QPointF(), pixelDelta ); + BOOST_CHECK_EQUAL( coords.height(), CONTENT_SIZE.height() + pixelDelta ); + BOOST_CHECK_EQUAL( coords.width(), + CONTENT_SIZE.width() + pixelDelta * CONTENT_AR ); + BOOST_CHECK_EQUAL( coords.x(), 0 ); + BOOST_CHECK_EQUAL( coords.y(), 0 ); + + controller.scale( coords.bottomRight(), -pixelDelta ); + BOOST_CHECK_EQUAL( coords.size().width(), CONTENT_SIZE.width( )); + BOOST_CHECK_EQUAL( coords.size().height(), CONTENT_SIZE.height( )); + BOOST_CHECK_EQUAL( coords.y(), pixelDelta ); + BOOST_CHECK_CLOSE( coords.x(), pixelDelta * CONTENT_AR, 0.00001 ); +} + ContentWindowPtr makeDummyWindow() { ContentPtr content( new DummyContent ); diff --git a/tests/cpp/core/WebbrowserTests.cpp b/tests/cpp/core/WebbrowserTests.cpp index 80304b4d..750b5778 100644 --- a/tests/cpp/core/WebbrowserTests.cpp +++ b/tests/cpp/core/WebbrowserTests.cpp @@ -68,7 +68,8 @@ BOOST_AUTO_TEST_CASE( test_webgl_support ) return; // load the webgl website, exec() returns when loading is finished - WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize(640,480), EMPTY_PAGE_URL ); + WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize( 640, 480 ), + EMPTY_PAGE_URL ); QObject::connect( streamer->getView(), SIGNAL(loadFinished(bool)), QApplication::instance(), SLOT(quit())); streamer->setUrl( testPageURL( )); @@ -111,9 +112,10 @@ BOOST_AUTO_TEST_CASE( test_webgl_interaction ) return; // load the webgl website, exec() returns when loading is finished - WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize(640, 480), EMPTY_PAGE_URL ); - QObject::connect( streamer->getView(), SIGNAL(loadFinished(bool)), - QApplication::instance(), SLOT(quit())); + WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize( 640, 480 ), + EMPTY_PAGE_URL ); + QObject::connect( streamer->getView(), SIGNAL( loadFinished( bool )), + QApplication::instance(), SLOT( quit( ))); streamer->setUrl( testPageURL( )); QApplication::instance()->exec(); @@ -168,7 +170,8 @@ BOOST_AUTO_TEST_CASE( test_webgl_press_release ) return; // load the webgl website, exec() returns when loading is finished - WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize(640, 480), EMPTY_PAGE_URL ); + WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize( 640, 480 ), + EMPTY_PAGE_URL ); QObject::connect( streamer->getView(), SIGNAL(loadFinished(bool)), QApplication::instance(), SLOT(quit())); streamer->setUrl( testPageURL( )); @@ -211,9 +214,10 @@ BOOST_AUTO_TEST_CASE( test_webgl_wheel ) return; // load the webgl website, exec() returns when loading is finished - WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize(640, 480), EMPTY_PAGE_URL ); - QObject::connect( streamer->getView(), SIGNAL(loadFinished(bool)), - QApplication::instance(), SLOT(quit())); + WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize( 640, 480 ), + EMPTY_PAGE_URL ); + QObject::connect( streamer->getView(), SIGNAL( loadFinished( bool )), + QApplication::instance(), SLOT( quit( ))); streamer->setUrl( testPageURL( )); QApplication::instance()->exec(); @@ -223,27 +227,27 @@ BOOST_AUTO_TEST_CASE( test_webgl_wheel ) BOOST_REQUIRE( frame ); // Normalized mouse coordinates - deflect::Event wheelState; - wheelState.mouseX = 0.1; - wheelState.mouseY = 0.1; - wheelState.dy = 40; - wheelState.type = deflect::Event::EVT_WHEEL; + deflect::Event pinchEvent; + pinchEvent.mouseX = 0.1; + pinchEvent.mouseY = 0.1; + pinchEvent.dy = 40.0 / streamer->size().height(); + pinchEvent.type = deflect::Event::EVT_PINCH; - streamer->processEvent(wheelState); + streamer->processEvent( pinchEvent ); - const int expectedPosX = wheelState.mouseX * streamer->size().width() / + const int expectedPosX = pinchEvent.mouseX * streamer->size().width() / streamer->getView()->zoomFactor(); - const int expectedPosY = wheelState.mouseY * streamer->size().height() / + const int expectedPosY = pinchEvent.mouseY * streamer->size().height() / streamer->getView()->zoomFactor(); - const int expectedWheelDelta = wheelState.dy; + const int expectedWheelDelta = 40; - QString jsX = QString("lastMouseX == %1;").arg(expectedPosX); - QString jsY = QString("lastMouseY == %1;").arg(expectedPosY); - QString jsD = QString("wheelDelta == %1;").arg(expectedWheelDelta); + QString jsX = QString( "lastMouseX == %1;" ).arg( expectedPosX ); + QString jsY = QString( "lastMouseY == %1;" ).arg( expectedPosY ); + QString jsD = QString( "wheelDelta == %1;" ).arg( expectedWheelDelta ); - BOOST_CHECK( frame->evaluateJavaScript(jsX).toBool()); - BOOST_CHECK( frame->evaluateJavaScript(jsY).toBool()); - BOOST_CHECK( frame->evaluateJavaScript(jsD).toBool()); + BOOST_CHECK( frame->evaluateJavaScript( jsX ).toBool( )); + BOOST_CHECK( frame->evaluateJavaScript( jsY ).toBool( )); + BOOST_CHECK( frame->evaluateJavaScript( jsD ).toBool( )); delete streamer; } @@ -254,9 +258,10 @@ BOOST_AUTO_TEST_CASE( test_localstorage ) return; // load the webgl website, exec() returns when loading is finished - WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize(640, 480), testPageURL( )); - QObject::connect( streamer->getView(), SIGNAL(loadFinished(bool)), - QApplication::instance(), SLOT(quit())); + WebkitPixelStreamer* streamer = new WebkitPixelStreamer( QSize( 640, 480 ), + testPageURL( )); + QObject::connect( streamer->getView(), SIGNAL( loadFinished( bool )), + QApplication::instance(), SLOT(quit( ))); QApplication::instance()->exec(); QWebPage* page = streamer->getView()->page(); diff --git a/tide/core/ContentInteractionDelegate.h b/tide/core/ContentInteractionDelegate.h index 87d1cc66..245a8490 100644 --- a/tide/core/ContentInteractionDelegate.h +++ b/tide/core/ContentInteractionDelegate.h @@ -76,7 +76,7 @@ class ContentInteractionDelegate : public QObject Q_INVOKABLE virtual void pan( QPointF position, QPointF delta, uint numPoints ) { Q_UNUSED( position ) Q_UNUSED( delta ) Q_UNUSED( numPoints ) } - Q_INVOKABLE virtual void pinch( QPointF position, qreal pixelDelta ) + Q_INVOKABLE virtual void pinch( QPointF position, QPointF pixelDelta ) { Q_UNUSED( position ) Q_UNUSED( pixelDelta ) } Q_INVOKABLE virtual void swipeLeft() {} Q_INVOKABLE virtual void swipeRight() {} diff --git a/tide/core/PixelStreamInteractionDelegate.cpp b/tide/core/PixelStreamInteractionDelegate.cpp index 71048b5a..34e42f5c 100644 --- a/tide/core/PixelStreamInteractionDelegate.cpp +++ b/tide/core/PixelStreamInteractionDelegate.cpp @@ -114,12 +114,15 @@ void PixelStreamInteractionDelegate::pan( const QPointF position, } void PixelStreamInteractionDelegate::pinch( const QPointF position, - const qreal pixelDelta ) + const QPointF pixelDelta ) { deflect::Event deflectEvent = _getNormEvent( position ); - deflectEvent.type = deflect::Event::EVT_WHEEL; + deflectEvent.type = deflect::Event::EVT_PINCH; deflectEvent.mouseLeft = false; - deflectEvent.dy = pixelDelta; + + const QRectF& win = _contentWindow.getDisplayCoordinates(); + deflectEvent.dx = pixelDelta.x() / win.width(); + deflectEvent.dy = pixelDelta.y() / win.height(); emit notify( deflectEvent ); } diff --git a/tide/core/PixelStreamInteractionDelegate.h b/tide/core/PixelStreamInteractionDelegate.h index 2cc763c4..054911cf 100644 --- a/tide/core/PixelStreamInteractionDelegate.h +++ b/tide/core/PixelStreamInteractionDelegate.h @@ -63,7 +63,7 @@ class PixelStreamInteractionDelegate : public ContentInteractionDelegate void doubleTap( QPointF position ) override; void tapAndHold( QPointF position, uint numPoints ) override; void pan( QPointF position, QPointF delta, uint numPoints ) override; - void pinch( QPointF position, qreal pixelDelta ) override; + void pinch( QPointF position, QPointF pixelDelta ) override; void swipeLeft() override; void swipeRight() override; void swipeUp() override; diff --git a/tide/core/ZoomInteractionDelegate.cpp b/tide/core/ZoomInteractionDelegate.cpp index c4873952..c16c40e3 100644 --- a/tide/core/ZoomInteractionDelegate.cpp +++ b/tide/core/ZoomInteractionDelegate.cpp @@ -63,19 +63,18 @@ void ZoomInteractionDelegate::pan( const QPointF position, const QPointF delta, _moveZoomRect( delta ); } -void ZoomInteractionDelegate::pinch( QPointF position, - const qreal pixelDelta ) +void ZoomInteractionDelegate::pinch( QPointF position, const QPointF pixelDelta ) { const ZoomHelper zoomHelper( _contentWindow ); QRectF contentRect = zoomHelper.getContentRect(); position -= _contentWindow.getDisplayCoordinates().topLeft(); + const auto mode = pixelDelta.x() + pixelDelta.y() > 0 ? + Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio; + QSizeF newSize = contentRect.size(); - newSize.scale( newSize.width() + pixelDelta, - newSize.height() + pixelDelta, - pixelDelta < 0 ? Qt::KeepAspectRatio - : Qt::KeepAspectRatioByExpanding ); + newSize.scale( newSize + QSizeF( pixelDelta.x(), pixelDelta.y( )), mode ); contentRect = ContentWindowController::scaleRectAroundPosition( contentRect, position, diff --git a/tide/core/ZoomInteractionDelegate.h b/tide/core/ZoomInteractionDelegate.h index d06d7fe8..67ac12fb 100644 --- a/tide/core/ZoomInteractionDelegate.h +++ b/tide/core/ZoomInteractionDelegate.h @@ -57,7 +57,7 @@ class ZoomInteractionDelegate : public ContentInteractionDelegate /** @name Touch gesture handlers. */ //@{ void pan( QPointF position, QPointF delta, uint numPoints ) override; - void pinch( QPointF position, qreal pixelDelta ) override; + void pinch( QPointF position, QPointF pixelDelta ) override; //@} /** Adjust the zoom of the window to the aspect ratio of the content. */ diff --git a/tide/master/MultitouchArea.cpp b/tide/master/MultitouchArea.cpp index face9878..eeea8ae8 100644 --- a/tide/master/MultitouchArea.cpp +++ b/tide/master/MultitouchArea.cpp @@ -56,14 +56,7 @@ const uint doubleTapTimeout = 750; MultitouchArea::MultitouchArea( QQuickItem* parent_ ) : QQuickItem( parent_ ) - , _referenceItem( nullptr ) , _panThreshold( defaultPanThresholdPx ) - , _panning( false ) - , _twoFingersDetectionStarted( false ) - , _canBeSwipe( false ) - , _pinching( false ) - , _lastPinchDist( 0.0 ) - , _tapCounter( 0 ) { setAcceptedMouseButtons( Qt::LeftButton ); @@ -71,9 +64,6 @@ MultitouchArea::MultitouchArea( QQuickItem* parent_ ) connect( &_tapAndHoldTimer, &QTimer::timeout, [this]() { emit tapAndHold( _getTouchCenterStartPos(), _getPointsCount( )); }); - - _doubleTapTimer.setInterval( doubleTapTimeout ); - connect( &_doubleTapTimer, &QTimer::timeout, [this]() { _tapCounter = 0; }); } QQuickItem* MultitouchArea::getReferenceItem() const @@ -104,9 +94,9 @@ void MultitouchArea::setPanThreshold( const qreal arg ) emit panThresholdChanged( arg ); } -QPointF MultitouchArea::_getScenePos( const QMouseEvent* mouse ) +QPointF MultitouchArea::_getScenePos( const QMouseEvent& mouse ) { - return mapToItem( _referenceItem, mouse->localPos( )); + return mapToItem( _referenceItem, mouse.localPos( )); } QPointF MultitouchArea::_getScenePos( const QTouchEvent::TouchPoint& point ) @@ -114,62 +104,70 @@ QPointF MultitouchArea::_getScenePos( const QTouchEvent::TouchPoint& point ) return mapToItem( _referenceItem, point.pos( )); } -QPointF MultitouchArea::_getScenePos( const QPointF& itemPos ) +QPointF MultitouchArea::_getScenePos( const QWheelEvent& wheel ) { - return mapToItem( _referenceItem, itemPos ); + return mapToItem( _referenceItem, wheel.posF( )); } -void MultitouchArea::mousePressEvent( QMouseEvent* mouse ) +uint _getButtonsCount( const QMouseEvent& mouse ) { - const auto pos = _getScenePos( mouse ); + uint count = 0; + if( mouse.buttons() & Qt::LeftButton ) + ++count; + if( mouse.buttons() & Qt::RightButton ) + ++count; + if( mouse.buttons() & Qt::MiddleButton ) + ++count; + return count; +} - _mousePrevPos = pos; - emit touchStarted( pos ); +void MultitouchArea::mousePressEvent( QMouseEvent* mouse ) +{ + if( _getButtonsCount( *mouse ) == 1 ) + emit touchStarted( _getScenePos( *mouse )); - _startMultipointGesture( { pos } ); + _handleMultipointGestures( _getPositions( *mouse )); } void MultitouchArea::mouseMoveEvent( QMouseEvent* mouse ) { - const auto pos = _getScenePos( mouse ); - - const QPointF delta = pos - _mousePrevPos; - _mousePrevPos = pos; - - _cancelTapAndHoldGesture(); - - _startPanGesture( pos ); - emit pan( pos, delta, 1 ); + _handleMultipointGestures( _getPositions( *mouse )); } void MultitouchArea::mouseReleaseEvent( QMouseEvent* mouse ) { - const auto pos = _getScenePos( mouse ); - if( !_panning ) - emit tap( pos ); + const auto count = _getButtonsCount( *mouse ); + const auto pos = _getScenePos( *mouse ); - _cancelPanGesture(); - _cancelTapAndHoldGesture(); + if( count == 0 && !_panning ) + emit tap( pos ); - emit touchEnded( pos ); + _handleMultipointGestures( _getPositions( *mouse )); + + if( count == 0 ) + emit touchEnded( pos ); } void MultitouchArea::mouseDoubleClickEvent( QMouseEvent* mouse ) { - emit doubleTap( _getScenePos( mouse )); + emit doubleTap( _getScenePos( *mouse )); } void MultitouchArea::wheelEvent( QWheelEvent* wheel ) { emit pinchStarted(); - emit pinch( _getScenePos( wheel->posF( )), - wheel->angleDelta().y() * wheelFactor ); + const auto delta = wheel->angleDelta(); + emit pinch( _getScenePos( *wheel ), QPointF{ delta } * wheelFactor ); emit pinchEnded(); } void MultitouchArea::touchEvent( QTouchEvent* touch ) { const auto& points = touch->touchPoints(); + + if( touch->type() == QEvent::TouchBegin ) + emit touchStarted( _getScenePos( points.at( 0 ))); + switch( points.size( )) { case 1: @@ -181,71 +179,71 @@ void MultitouchArea::touchEvent( QTouchEvent* touch ) default: break; } - _handleMultipointGestures( points ); + _handleMultipointGestures( _getPositions( points )); + + if( touch->type() == QEvent::TouchEnd ) + emit touchEnded( _getScenePos( points.at( 0 ))); +} + +MultitouchArea::Positions +MultitouchArea::_getPositions( const QMouseEvent& mouse ) +{ + return Positions{ _getButtonsCount( mouse ), _getScenePos( mouse ) }; +} + +MultitouchArea::Positions +MultitouchArea::_getPositions( const TouchPoints& points ) +{ + Positions positions; + for( auto point : points ) + { + if( point.state() != Qt::TouchPointReleased ) + positions.push_back( _getScenePos( point )); + } + return positions; } void MultitouchArea::_handleSinglePoint( const QTouchEvent::TouchPoint& point ) { - const QPointF pos = _getScenePos( point ); + const auto pos = _getScenePos( point ); switch( point.state( )) { case Qt::TouchPointPressed: - emit touchStarted( pos ); - - if( _tapCounter == 0 ) - _startDoubleTapGesture(); - if( ++_tapCounter == 2 ) - { - if( (pos - _tapStartPos).manhattanLength() < doubleTapThresholdPx ) - emit doubleTap( pos ); - _tapCounter = 0; - } - _tapStartPos = pos; + _updateDoubleTapGesture( pos ); break; - case Qt::TouchPointStationary: break; - case Qt::TouchPointMoved: break; - case Qt::TouchPointReleased: if( !_panning ) emit tap( pos ); - - emit touchEnded( pos ); break; } } -void MultitouchArea::_startPanGesture( const QPointF& pos ) +void MultitouchArea::_startDoubleTapGesture( const QPointF& pos ) { - if( _panning ) - return; - - _panning = true; - emit panStarted( pos, _getPointsCount( )); -} - -void MultitouchArea::_cancelPanGesture() -{ - if( !_panning ) - return; - - _panning = false; - emit panEnded(); + _tapStartPos = pos; + _doubleTapTimer.start(); } -void MultitouchArea::_startDoubleTapGesture() +void MultitouchArea::_updateDoubleTapGesture( const QPointF& pos ) { - _doubleTapTimer.start(); + if( _doubleTapTimer.isActive( )) + { + if( (pos - _tapStartPos).manhattanLength() < doubleTapThresholdPx ) + emit doubleTap( pos ); + _cancelDoubleTapGesture(); + } + else + _startDoubleTapGesture( pos ); } void MultitouchArea::_cancelDoubleTapGesture() { _doubleTapTimer.stop(); - _tapCounter = 0; } qreal _getDist( const QPointF& p0, const QPointF& p1 ) @@ -254,16 +252,19 @@ qreal _getDist( const QPointF& p0, const QPointF& p1 ) return std::sqrt( QPointF::dotProduct( dist, dist )); } -qreal MultitouchArea::_getPinchDistance( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ) +QPointF _getCenter( const QPointF& pos0, const QPointF& pos1 ) { - return _getDist(_getScenePos( p0 ), _getScenePos( p1 )) - _initialPinchDist; + return ( pos0 + pos1 ) / 2; } -QPointF MultitouchArea::_getCenter( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ) +QRectF _makeBoundingRect( const QPointF& pos0, const QPointF& pos1 ) { - return ( _getScenePos( p0 ) + _getScenePos( p1 )) / 2; + QRectF rect; + rect.setLeft( std::min( pos0.x(), pos1.x( ))); + rect.setRight( std::max( pos0.x(), pos1.x( ))); + rect.setTop( std::min( pos0.y(), pos1.y( ))); + rect.setBottom( std::max( pos0.y(), pos1.y( ))); + return rect; } void MultitouchArea::_handleTwoPoints( const QTouchEvent::TouchPoint& p0, @@ -272,82 +273,65 @@ void MultitouchArea::_handleTwoPoints( const QTouchEvent::TouchPoint& p0, if( p0.state() == Qt::TouchPointReleased || p1.state() == Qt::TouchPointReleased ) { - _canBeSwipe = false; - _twoFingersDetectionStarted = false; _cancelPinchGesture(); + _cancelSwipeGesture(); return; } - if( !_twoFingersDetectionStarted ) - { - _twoFingersDetectionStarted = true; - _cancelDoubleTapGesture(); + const auto pos0 = _getScenePos( p0 ); + const auto pos1 = _getScenePos( p1 ); - _initialPinchDist = _getDist( _getScenePos( p0 ), _getScenePos( p1 )); - _canBeSwipe = _initialPinchDist < swipeMaxFingersIntervalPx; - _twoFingersStartPos = _getCenter( p0, p1 ); - } - - if( !_pinching && std::abs( _getPinchDistance( p0, p1 )) > pinchThresholdPx) + if( p0.state() == Qt::TouchPointPressed || + p1.state() == Qt::TouchPointPressed ) { - _lastPinchDist = _getPinchDistance( p0, p1 ); - _startPinchGesture(); + _initPinchGesture( pos0, pos1 ); + _initSwipeGesture( pos0, pos1 ); } - if( _pinching ) - { - const auto pinchDist = _getPinchDistance( p0, p1 ); - const auto pinchDelta = pinchDist - _lastPinchDist; - _lastPinchDist = pinchDist; - emit pinch( _getCenter( p0, p1 ), pinchDelta ); - } + _updatePinchGesture( pos0, pos1 ); + _updateSwipeGesture( pos0, pos1 ); +} - if( _canBeSwipe ) - { - const qreal fingersInterval = _getDist( _getScenePos( p0 ), - _getScenePos( p1 )); - if( fingersInterval > swipeMaxFingersIntervalPx ) - _canBeSwipe = false; - else - { - const QPointF twoFingersPos = _getCenter( p0, p1 ); - const qreal dist = _getDist( _twoFingersStartPos, twoFingersPos ); - if( dist > swipeThresholdPx ) - { - const qreal dx = twoFingersPos.x() - _twoFingersStartPos.x(); - const qreal dy = twoFingersPos.y() - _twoFingersStartPos.y(); - - if( std::abs( dx ) > std::abs( dy )) - { - // Horizontal swipe - if( dx > 0.0 ) - emit swipeRight(); - else - emit swipeLeft(); - } - else - { - // Vertical swipe - if( dy > 0.0 ) - emit swipeDown(); - else - emit swipeUp(); - } - _canBeSwipe = false; // Only allow one swipe - } - } - } +void MultitouchArea::_initPinchGesture( const QPointF& pos0, + const QPointF& pos1 ) +{ + const auto twoFingersStartRect = _makeBoundingRect( pos0, pos1 ); + const auto w = twoFingersStartRect.width(); + const auto h = twoFingersStartRect.height(); + _initialPinchDist = std::sqrt( w * w + h * h ); } -void MultitouchArea::_startPinchGesture() +void MultitouchArea::_startPinchGesture( const QPointF& pos0, + const QPointF& pos1 ) { if( _pinching ) return; _pinching = true; + _lastPinchRect = _makeBoundingRect( pos0, pos1 ); emit pinchStarted(); } +void MultitouchArea::_updatePinchGesture( const QPointF& pos0, + const QPointF& pos1 ) +{ + if( !_pinching ) + { + const auto pinchDist = _getDist( pos0, pos1 ); + const auto pinchDelta = std::abs( pinchDist - _initialPinchDist ); + if( pinchDelta > pinchThresholdPx ) + _startPinchGesture( pos0, pos1 ); + else + return; + } + + const auto pinchRect = _makeBoundingRect( pos0, pos1 ); + const auto pinchDelta = pinchRect.size() - _lastPinchRect.size(); + _lastPinchRect = pinchRect; + emit pinch( pinchRect.center(), QPointF{ pinchDelta.width(), + pinchDelta.height() }); +} + void MultitouchArea::_cancelPinchGesture() { if( !_pinching ) @@ -357,29 +341,75 @@ void MultitouchArea::_cancelPinchGesture() emit pinchEnded(); } -void MultitouchArea::_handleMultipointGestures( const TouchPoints& points ) +bool _checkFingersDistanceForSwipe( const QPointF& pos0, const QPointF& pos1 ) { - Positions positions; - bool restart = false; - for( auto point : points ) - { - if( point.state() != Qt::TouchPointReleased ) - positions.push_back( _getScenePos( point )); + const qreal fingersInterval = _getDist( pos0, pos1 ); + return fingersInterval < swipeMaxFingersIntervalPx; +} - restart = restart || point.state() == Qt::TouchPointPressed || - point.state() == Qt::TouchPointReleased; - } - if( positions.empty( )) +void MultitouchArea::_initSwipeGesture( const QPointF& pos0, + const QPointF& pos1 ) +{ + _canBeSwipe = _checkFingersDistanceForSwipe( pos0, pos1 ); + _swipeStartPos = _getCenter( pos0, pos1 ); +} + +void MultitouchArea::_updateSwipeGesture( const QPointF& pos0, + const QPointF& pos1 ) +{ + if( !_canBeSwipe ) + return; + + if( !_checkFingersDistanceForSwipe( pos0, pos1 )) { - _cancelTapAndHoldGesture(); - _cancelPanGesture(); - _touchStartPos.clear(); + _cancelSwipeGesture(); return; } - if( restart ) + + const auto twoFingersPos = _getCenter( pos0, pos1 ); + const auto twoFingersStartPos = _swipeStartPos; + const auto dist = _getDist( twoFingersStartPos, twoFingersPos ); + if( dist > swipeThresholdPx ) + { + const qreal dx = twoFingersPos.x() - twoFingersStartPos.x(); + const qreal dy = twoFingersPos.y() - twoFingersStartPos.y(); + + if( std::abs( dx ) > std::abs( dy )) + { + // Horizontal swipe + if( dx > 0.0 ) + emit swipeRight(); + else + emit swipeLeft(); + } + else + { + // Vertical swipe + if( dy > 0.0 ) + emit swipeDown(); + else + emit swipeUp(); + } + _cancelSwipeGesture(); // Only allow one swipe + } +} + +void MultitouchArea::_cancelSwipeGesture() +{ + _canBeSwipe = false; +} + +void MultitouchArea::_handleMultipointGestures( const Positions& positions ) +{ + if( positions.size() != _touchStartPos.size( )) { - _startMultipointGesture( positions ); + _cancelTapAndHoldGesture(); _cancelPanGesture(); + + _touchStartPos = positions; + + if( !positions.empty( )) + _startTapAndHoldGesture(); return; } @@ -387,6 +417,11 @@ void MultitouchArea::_handleMultipointGestures( const TouchPoints& points ) _updatePanGesture( positions ); } +void MultitouchArea::_startTapAndHoldGesture() +{ + _tapAndHoldTimer.start(); +} + void MultitouchArea::_updateTapAndHoldGesture( const Positions& positions ) { if( _tapAndHoldTimer.isActive( )) @@ -406,12 +441,6 @@ void MultitouchArea::_cancelTapAndHoldIfMoved( const Positions& positions ) } } -void MultitouchArea::_startMultipointGesture( const Positions& positions ) -{ - _touchStartPos = positions; - _tapAndHoldTimer.start(); -} - void MultitouchArea::_cancelTapAndHoldGesture() { _tapAndHoldTimer.stop(); @@ -436,6 +465,15 @@ uint MultitouchArea::_getPointsCount() const return _touchStartPos.size(); } +void MultitouchArea::_startPanGesture( const QPointF& pos ) +{ + if( _panning ) + return; + + _panning = true; + emit panStarted( pos, _getPointsCount( )); +} + void MultitouchArea::_updatePanGesture( const Positions& positions ) { const auto pos = _computeCenter( positions ); @@ -452,3 +490,12 @@ void MultitouchArea::_updatePanGesture( const Positions& positions ) _lastPanPos = pos; } } + +void MultitouchArea::_cancelPanGesture() +{ + if( !_panning ) + return; + + _panning = false; + emit panEnded(); +} diff --git a/tide/master/MultitouchArea.h b/tide/master/MultitouchArea.h index bfd09fb4..8024d1db 100644 --- a/tide/master/MultitouchArea.h +++ b/tide/master/MultitouchArea.h @@ -107,7 +107,7 @@ public slots: void pinchStarted(); /** Emitted for each step of a two-fingers pinch gesture. */ - void pinch( QPointF pos, qreal pixelDelta ); + void pinch( QPointF pos, QPointF pixelDelta ); /** Emitted when a pinch ends (i.e. one of the two fingers is released). */ void pinchEnded(); @@ -145,63 +145,85 @@ public slots: void wheelEvent( QWheelEvent* event ) override; void touchEvent( QTouchEvent* event ) override; - QPointF _getScenePos( const QMouseEvent* mouse ); - QPointF _getScenePos( const QTouchEvent::TouchPoint& point ); - QPointF _getScenePos( const QPointF& itemPos ); - - qreal _getPinchDistance( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ); - QPointF _getCenter( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ); + using Positions = std::vector; + using TouchPoints = QList; - void _handleSinglePoint( const QTouchEvent::TouchPoint& point ); + Positions _getPositions( const QMouseEvent& mouse ); + Positions _getPositions( const TouchPoints& points ); - void _startPanGesture( const QPointF& pos ); - void _cancelPanGesture(); + QPointF _getScenePos( const QMouseEvent& mouse ); + QPointF _getScenePos( const QTouchEvent::TouchPoint& point ); + QPointF _getScenePos( const QWheelEvent& wheel ); - using Positions = std::vector; - using TouchPoints = QList; + /** @name Single-point gestures */ + //@{ + void _handleSinglePoint( const QTouchEvent::TouchPoint& point ); - void _startDoubleTapGesture(); + void _startDoubleTapGesture( const QPointF& pos ); + void _updateDoubleTapGesture( const QPointF& pos ); void _cancelDoubleTapGesture(); + //@} + /** @name Two-point gestures */ + //@{ void _handleTwoPoints( const QTouchEvent::TouchPoint& p0, const QTouchEvent::TouchPoint& p1 ); - void _startPinchGesture(); + + void _initPinchGesture( const QPointF& pos0, const QPointF& pos1 ); + void _startPinchGesture( const QPointF& pos0, const QPointF& pos1 ); + void _updatePinchGesture( const QPointF& pos0, const QPointF& pos1 ); void _cancelPinchGesture(); - void _handleMultipointGestures( const TouchPoints& points ); + void _initSwipeGesture( const QPointF& pos0, const QPointF& pos1 ); + void _updateSwipeGesture( const QPointF& pos0, const QPointF& pos1 ); + void _cancelSwipeGesture(); + //@} + + /** @name Mulit-point gestures */ + //@{ + void _handleMultipointGestures( const Positions& positions ); + void _startTapAndHoldGesture(); void _updateTapAndHoldGesture( const Positions& positions ); - void _startMultipointGesture( const Positions& positions ); void _cancelTapAndHoldIfMoved( const Positions& positions ); void _cancelTapAndHoldGesture(); + QPointF _getTouchCenterStartPos() const; uint _getPointsCount() const; + void _startPanGesture( const QPointF& pos ); void _updatePanGesture( const Positions& positions ); + void _cancelPanGesture(); + //@} - QQuickItem* _referenceItem; - qreal _panThreshold; - - QPointF _mousePrevPos; + QQuickItem* _referenceItem = nullptr; - bool _panning; - QPointF _lastPanPos; + /** @name Single-point gestures */ + //@{ + QPointF _tapStartPos; + QTimer _doubleTapTimer; + //@} - bool _twoFingersDetectionStarted; - bool _canBeSwipe; - bool _pinching; - qreal _lastPinchDist; - QPointF _twoFingersStartPos; + /** @name Two-point gestures */ + //@{ + bool _pinching = false; + qreal _initialPinchDist = 0.0; + QRectF _lastPinchRect; - QPointF _tapStartPos; - uint _tapCounter; - qreal _initialPinchDist; + bool _canBeSwipe = false; + QPointF _swipeStartPos; + //@} + /** @name Mulit-point gestures */ + //@{ Positions _touchStartPos; + QTimer _tapAndHoldTimer; - QTimer _doubleTapTimer; + + qreal _panThreshold; + bool _panning = false; + QPointF _lastPanPos; + //@} }; #endif diff --git a/tide/master/localstreamer/WebkitPixelStreamer.cpp b/tide/master/localstreamer/WebkitPixelStreamer.cpp index 2a2a052b..782d6e6a 100644 --- a/tide/master/localstreamer/WebkitPixelStreamer.cpp +++ b/tide/master/localstreamer/WebkitPixelStreamer.cpp @@ -150,8 +150,8 @@ void WebkitPixelStreamer::processEvent(deflect::Event event_) case deflect::Event::EVT_MOVE: processMoveEvent(event_); break; - case deflect::Event::EVT_WHEEL: - processWheelEvent(event_); + case deflect::Event::EVT_PINCH: + processPinchEvent(event_); break; case deflect::Event::EVT_RELEASE: processReleaseEvent(event_); @@ -244,18 +244,20 @@ void WebkitPixelStreamer::processReleaseEvent(const deflect::Event& releaseEvent _interactionModeActive = false; } -void WebkitPixelStreamer::processWheelEvent(const deflect::Event& wheelEvent) +void WebkitPixelStreamer::processPinchEvent( const deflect::Event& pinchEvent ) { - const QWebHitTestResult& hitResult = performHitTest(wheelEvent); + const QWebHitTestResult& hitResult = performHitTest( pinchEvent ); - if(!hitResult.isNull() && isWebGLElement(hitResult.element())) + if( !hitResult.isNull() && isWebGLElement( hitResult.element( ))) { - const int delta = wheelEvent.dy; - QWheelEvent myEvent(hitResult.pos(), delta, Qt::NoButton, - (Qt::KeyboardModifiers)wheelEvent.modifiers, - Qt::Vertical); - - _webView.page()->event(&myEvent); + const auto dx = pinchEvent.dx * size().width(); + const auto dy = pinchEvent.dy * size().height(); + const auto delta = std::copysign( std::sqrt( dx*dx + dy*dy ), dx + dy ); + + QWheelEvent wheelEvent( hitResult.pos(), delta, Qt::NoButton, + (Qt::KeyboardModifiers)pinchEvent.modifiers, + Qt::Vertical ); + _webView.page()->event( &wheelEvent ); } } diff --git a/tide/master/localstreamer/WebkitPixelStreamer.h b/tide/master/localstreamer/WebkitPixelStreamer.h index 7fdc5a75..464fc010 100644 --- a/tide/master/localstreamer/WebkitPixelStreamer.h +++ b/tide/master/localstreamer/WebkitPixelStreamer.h @@ -115,7 +115,7 @@ private slots: void processPressEvent(const deflect::Event& pressEvent); void processMoveEvent(const deflect::Event& moveEvent); void processReleaseEvent(const deflect::Event& releaseEvent); - void processWheelEvent(const deflect::Event& wheelEvent); + void processPinchEvent(const deflect::Event& wheelEvent); void processKeyPress(const deflect::Event& keyEvent); void processKeyRelease(const deflect::Event& keyEvent); void processViewSizeChange(const deflect::Event& sizeEvent); diff --git a/tide/master/resources/MasterContentWindow.qml b/tide/master/resources/MasterContentWindow.qml index b8a053aa..5caedc4d 100644 --- a/tide/master/resources/MasterContentWindow.qml +++ b/tide/master/resources/MasterContentWindow.qml @@ -54,7 +54,10 @@ BaseContentWindow { } function scaleWindow(center, pixelDelta) { - contentwindow.controller.scale(center, pixelDelta) + var sign = pixelDelta.x + pixelDelta.y > 0 ? 1.0 : -1.0; + var delta = Math.sqrt(pixelDelta.x * pixelDelta.x + + pixelDelta.y * pixelDelta.y) + contentwindow.controller.scale(center, sign * delta) } focus: contentwindow.content.captureInteraction From ae6478c3ab05a92ded8f2e8700df860f08c1853a Mon Sep 17 00:00:00 2001 From: Raphael Dumusc Date: Fri, 9 Sep 2016 17:49:29 +0200 Subject: [PATCH 2/2] MultitouchArea improvements: - Gesture detection moved to separate classes for more clarity and make unit testing easier - Tap and DoubleTap gestures work with any number of fingers - Double-tap a window with two fingers makes it fullscreen --- .gitsubprojects | 2 +- doc/Changelog.md | 7 + tide/core/ContentInteractionDelegate.h | 16 +- tide/core/PixelStreamInteractionDelegate.cpp | 38 +- tide/core/PixelStreamInteractionDelegate.h | 10 +- tide/core/types.h | 1 + tide/master/CMakeLists.txt | 19 +- tide/master/MasterApplication.cpp | 45 +- tide/master/MasterApplication.h | 9 +- tide/master/MultiTouchListener.cpp | 194 -------- tide/master/MultitouchArea.cpp | 426 +++++------------- tide/master/MultitouchArea.h | 125 ++--- tide/master/MultitouchListener.cpp | 74 +++ ...tiTouchListener.h => MultitouchListener.h} | 52 +-- tide/master/multitouch/DoubleTapDetector.cpp | 104 +++++ tide/master/multitouch/DoubleTapDetector.h | 75 +++ tide/master/multitouch/MathUtils.cpp | 88 ++++ tide/master/multitouch/MathUtils.h | 94 ++++ tide/master/multitouch/PanDetector.cpp | 97 ++++ tide/master/multitouch/PanDetector.h | 86 ++++ tide/master/multitouch/PinchDetector.cpp | 94 ++++ tide/master/multitouch/PinchDetector.h | 82 ++++ tide/master/multitouch/SwipeDetector.cpp | 107 +++++ tide/master/multitouch/SwipeDetector.h | 86 ++++ tide/master/multitouch/TapAndHoldDetector.cpp | 81 ++++ tide/master/multitouch/TapAndHoldDetector.h | 75 +++ tide/master/multitouch/TapDetector.cpp | 88 ++++ tide/master/multitouch/TapDetector.h | 76 ++++ tide/master/resources/MasterContentWindow.qml | 18 +- 29 files changed, 1607 insertions(+), 662 deletions(-) delete mode 100644 tide/master/MultiTouchListener.cpp create mode 100644 tide/master/MultitouchListener.cpp rename tide/master/{MultiTouchListener.h => MultitouchListener.h} (68%) create mode 100644 tide/master/multitouch/DoubleTapDetector.cpp create mode 100644 tide/master/multitouch/DoubleTapDetector.h create mode 100644 tide/master/multitouch/MathUtils.cpp create mode 100644 tide/master/multitouch/MathUtils.h create mode 100644 tide/master/multitouch/PanDetector.cpp create mode 100644 tide/master/multitouch/PanDetector.h create mode 100644 tide/master/multitouch/PinchDetector.cpp create mode 100644 tide/master/multitouch/PinchDetector.h create mode 100644 tide/master/multitouch/SwipeDetector.cpp create mode 100644 tide/master/multitouch/SwipeDetector.h create mode 100644 tide/master/multitouch/TapAndHoldDetector.cpp create mode 100644 tide/master/multitouch/TapAndHoldDetector.h create mode 100644 tide/master/multitouch/TapDetector.cpp create mode 100644 tide/master/multitouch/TapDetector.h diff --git a/.gitsubprojects b/.gitsubprojects index 36718c18..b17f8afd 100644 --- a/.gitsubprojects +++ b/.gitsubprojects @@ -1,5 +1,5 @@ # -*- mode: cmake -*- git_subproject(ZeroEQ https://github.com/HBPVIS/ZeroEQ.git aa52615) -git_subproject(Deflect https://github.com/BlueBrain/Deflect.git 37a42c4) +git_subproject(Deflect https://github.com/BlueBrain/Deflect.git 1b0add5) git_subproject(TUIO https://github.com/BlueBrain/TUIO.git fdf5cf7) git_subproject(VirtualKeyboard https://github.com/rdumusc/QtFreeVirtualKeyboard.git ab3486e) diff --git a/doc/Changelog.md b/doc/Changelog.md index 89fb6fe3..1dee9550 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,6 +3,13 @@ Changelog {#changelog} # Release 1.2 (git master) +* [84](https://github.com/BlueBrain/Tide/pull/84): + Multitouch improvements [DISCL-383]: + - Tap and DoubleTap gestures work with any number of fingers + - DoubleTap a window with two fingers to make it fullscreen + Deflect adaptations [DISCL-386]: + - Clients now receive PINCH events instead of WHEEL events + - Clients also receive raw touch events (touch point added, updated, removed) * [83](https://github.com/BlueBrain/Tide/pull/83): Added TIDE_IGNORE_MPI_THREADSAFETY CMake option for Ubuntu 14.04 after #82 * [82](https://github.com/BlueBrain/Tide/pull/82): diff --git a/tide/core/ContentInteractionDelegate.h b/tide/core/ContentInteractionDelegate.h index 245a8490..881d046e 100644 --- a/tide/core/ContentInteractionDelegate.h +++ b/tide/core/ContentInteractionDelegate.h @@ -67,10 +67,18 @@ class ContentInteractionDelegate : public QObject { Q_UNUSED( position ) } Q_INVOKABLE virtual void touchEnd( QPointF position ) { Q_UNUSED( position ) } - Q_INVOKABLE virtual void tap( QPointF position ) - { Q_UNUSED( position ) } - Q_INVOKABLE virtual void doubleTap( QPointF position ) - { Q_UNUSED( position ) } + + Q_INVOKABLE virtual void addTouchPoint( int id, QPointF position ) + { Q_UNUSED( id ) Q_UNUSED( position ) } + Q_INVOKABLE virtual void updateTouchPoint( int id, QPointF position ) + { Q_UNUSED( id ) Q_UNUSED( position ) } + Q_INVOKABLE virtual void removeTouchPoint( int id, QPointF position ) + { Q_UNUSED( id ) Q_UNUSED( position ) } + + Q_INVOKABLE virtual void tap( QPointF position, uint numPoints ) + { Q_UNUSED( position ) Q_UNUSED( numPoints ) } + Q_INVOKABLE virtual void doubleTap( QPointF position, uint numPoints ) + { Q_UNUSED( position ) Q_UNUSED( numPoints ) } Q_INVOKABLE virtual void tapAndHold( QPointF position, uint numPoints ) { Q_UNUSED( position ) Q_UNUSED( numPoints ) } Q_INVOKABLE virtual void pan( QPointF position, QPointF delta, diff --git a/tide/core/PixelStreamInteractionDelegate.cpp b/tide/core/PixelStreamInteractionDelegate.cpp index 34e42f5c..f334f8b1 100644 --- a/tide/core/PixelStreamInteractionDelegate.cpp +++ b/tide/core/PixelStreamInteractionDelegate.cpp @@ -51,6 +51,36 @@ PixelStreamInteractionDelegate::PixelStreamInteractionDelegate( ContentWindow& this, &PixelStreamInteractionDelegate::_sendSizeChangedEvent ); } +void PixelStreamInteractionDelegate::addTouchPoint( const int id, + const QPointF position ) +{ + deflect::Event deflectEvent = _getNormEvent( position ); + deflectEvent.type = deflect::Event::EVT_TOUCH_ADD; + deflectEvent.key = id; + + emit notify( deflectEvent ); +} + +void PixelStreamInteractionDelegate::updateTouchPoint( const int id, + const QPointF position ) +{ + deflect::Event deflectEvent = _getNormEvent( position ); + deflectEvent.type = deflect::Event::EVT_TOUCH_UPDATE; + deflectEvent.key = id; + + emit notify( deflectEvent ); +} + +void PixelStreamInteractionDelegate::removeTouchPoint( const int id, + const QPointF position ) +{ + deflect::Event deflectEvent = _getNormEvent( position ); + deflectEvent.type = deflect::Event::EVT_TOUCH_REMOVE; + deflectEvent.key = id; + + emit notify( deflectEvent ); +} + void PixelStreamInteractionDelegate::touchBegin( const QPointF position ) { deflect::Event deflectEvent = _getNormEvent( position ); @@ -67,18 +97,22 @@ void PixelStreamInteractionDelegate::touchEnd( const QPointF position ) emit notify( deflectEvent ); } -void PixelStreamInteractionDelegate::tap( const QPointF position ) +void PixelStreamInteractionDelegate::tap( const QPointF position, + const uint numPoints ) { deflect::Event deflectEvent = _getNormEvent( position ); deflectEvent.type = deflect::Event::EVT_CLICK; + deflectEvent.key = numPoints; emit notify( deflectEvent ); } -void PixelStreamInteractionDelegate::doubleTap( const QPointF position ) +void PixelStreamInteractionDelegate::doubleTap( const QPointF position, + const uint numPoints ) { deflect::Event deflectEvent = _getNormEvent( position ); deflectEvent.type = deflect::Event::EVT_DOUBLECLICK; + deflectEvent.key = numPoints; emit notify( deflectEvent ); } diff --git a/tide/core/PixelStreamInteractionDelegate.h b/tide/core/PixelStreamInteractionDelegate.h index 054911cf..8342462f 100644 --- a/tide/core/PixelStreamInteractionDelegate.h +++ b/tide/core/PixelStreamInteractionDelegate.h @@ -59,11 +59,17 @@ class PixelStreamInteractionDelegate : public ContentInteractionDelegate //@{ void touchBegin( QPointF position ) override; void touchEnd( QPointF position ) override; - void tap( QPointF position ) override; - void doubleTap( QPointF position ) override; + + void addTouchPoint( int id, QPointF position ) override; + void updateTouchPoint( int id, QPointF position ) override; + void removeTouchPoint( int id, QPointF position ) override; + + void tap( QPointF position, uint numPoints ) override; + void doubleTap( QPointF position, uint numPoints ) override; void tapAndHold( QPointF position, uint numPoints ) override; void pan( QPointF position, QPointF delta, uint numPoints ) override; void pinch( QPointF position, QPointF pixelDelta ) override; + void swipeLeft() override; void swipeRight() override; void swipeUp() override; diff --git a/tide/core/types.h b/tide/core/types.h index 8511770e..3322dbe7 100644 --- a/tide/core/types.h +++ b/tide/core/types.h @@ -117,6 +117,7 @@ typedef std::vector< WallWindowPtr > WallWindowPtrs; typedef std::vector Tiles; typedef std::set Indices; typedef QList ImagePtrs; +typedef std::vector Positions; static const QRectF UNIT_RECTF( 0.0, 0.0, 1.0, 1.0 ); static const QSize UNDEFINED_SIZE( -1, -1 ); diff --git a/tide/master/CMakeLists.txt b/tide/master/CMakeLists.txt index 6c575077..57abd8d9 100644 --- a/tide/master/CMakeLists.txt +++ b/tide/master/CMakeLists.txt @@ -10,6 +10,7 @@ set(TIDEMASTER_LINK_LIBRARIES Qt5::Widgets PRIVATE Qt5::Concurrent + DeflectQt ) list(APPEND TIDEMASTER_PUBLIC_HEADERS @@ -27,6 +28,13 @@ list(APPEND TIDEMASTER_PUBLIC_HEADERS MasterConfiguration.h MasterToForkerChannel.h MasterWindow.h + multitouch/DoubleTapDetector.h + multitouch/MathUtils.h + multitouch/PanDetector.h + multitouch/PinchDetector.h + multitouch/SwipeDetector.h + multitouch/TapAndHoldDetector.h + multitouch/TapDetector.h MultitouchArea.h PixelStreamWindowManager.h ProcessForker.h @@ -52,6 +60,13 @@ list(APPEND TIDEMASTER_SOURCES MasterConfiguration.cpp MasterToForkerChannel.cpp MasterWindow.cpp + multitouch/DoubleTapDetector.cpp + multitouch/MathUtils.cpp + multitouch/PanDetector.cpp + multitouch/PinchDetector.cpp + multitouch/SwipeDetector.cpp + multitouch/TapAndHoldDetector.cpp + multitouch/TapDetector.cpp MultitouchArea.cpp PixelStreamWindowManager.cpp ProcessForker.cpp @@ -87,8 +102,8 @@ elseif(TARGET Qt5::WebKitWidgets) endif() if(TIDE_ENABLE_TUIO_TOUCH_LISTENER) - list(APPEND TIDEMASTER_HEADERS MultiTouchListener.h) - list(APPEND TIDEMASTER_SOURCES MultiTouchListener.cpp) + list(APPEND TIDEMASTER_HEADERS MultitouchListener.h) + list(APPEND TIDEMASTER_SOURCES MultitouchListener.cpp) list(APPEND TIDEMASTER_LINK_LIBRARIES TUIO ${X11_LIBRARIES}) endif() diff --git a/tide/master/MasterApplication.cpp b/tide/master/MasterApplication.cpp index 05fb8291..3add22cb 100644 --- a/tide/master/MasterApplication.cpp +++ b/tide/master/MasterApplication.cpp @@ -58,7 +58,7 @@ #include "log.h" #if TIDE_ENABLE_TUIO_TOUCH_LISTENER -# include "MultiTouchListener.h" +# include "MultitouchListener.h" #endif #if TIDE_ENABLE_REST_INTERFACE @@ -325,14 +325,41 @@ void MasterApplication::_initMPIConnection() void MasterApplication::_initTouchListener() { DisplayGroupView* view = _masterWindow->getDisplayGroupView(); - _touchListener.reset( new MultiTouchListener( - *view, _config->getTotalSize( ))); - connect( _touchListener.get(), &MultiTouchListener::touchPointAdded, - _markers.get(), &Markers::addMarker ); - connect( _touchListener.get(), &MultiTouchListener::touchPointUpdated, - _markers.get(), &Markers::updateMarker ); - connect( _touchListener.get(), &MultiTouchListener::touchPointRemoved, - _markers.get(), &Markers::removeMarker ); + auto mapFunc = std::bind( &DisplayGroupView::mapToWallPos, view, + std::placeholders::_1 ); + _touchInjector.reset( new deflect::qt::TouchInjector{ *view, mapFunc } ); + _touchListener.reset( new MultitouchListener( )); + + connect( _touchListener.get(), &MultitouchListener::touchPointAdded, + _touchInjector.get(), &deflect::qt::TouchInjector::addTouchPoint ); + connect( _touchListener.get(), &MultitouchListener::touchPointUpdated, + _touchInjector.get(), + &deflect::qt::TouchInjector::updateTouchPoint ); + connect( _touchListener.get(), &MultitouchListener::touchPointRemoved, + _touchInjector.get(), + &deflect::qt::TouchInjector::removeTouchPoint ); + + const auto wallSize = _config->getTotalSize(); + auto getWallPos = [wallSize]( const QPointF& normalizedPos ) + { + return QPointF{ normalizedPos.x() * wallSize.width(), + normalizedPos.y() * wallSize.height( )}; + }; + connect( _touchListener.get(), &MultitouchListener::touchPointAdded, + [this, getWallPos]( const int id, const QPointF normalizedPos ) + { + _markers->addMarker( id, getWallPos( normalizedPos )); + }); + connect( _touchListener.get(), &MultitouchListener::touchPointUpdated, + [this, getWallPos]( const int id, const QPointF normalizedPos ) + { + _markers->updateMarker( id, getWallPos( normalizedPos )); + }); + connect( _touchListener.get(), &MultitouchListener::touchPointRemoved, + [this]( const int id, const QPointF ) + { + _markers->removeMarker( id ); + }); connect( view, &DisplayGroupView::mousePressed, [this]( const QPointF pos ) { diff --git a/tide/master/MasterApplication.h b/tide/master/MasterApplication.h index 223a2eed..99096b5d 100644 --- a/tide/master/MasterApplication.h +++ b/tide/master/MasterApplication.h @@ -43,6 +43,10 @@ #include "config.h" #include "types.h" +#if TIDE_ENABLE_TUIO_TOUCH_LISTENER +#include +#endif + #include #include #include @@ -54,7 +58,7 @@ class MasterWindow; class PixelStreamerLauncher; class PixelStreamWindowManager; class MasterConfiguration; -class MultiTouchListener; +class MultitouchListener; class RestInterface; class LoggingUtility; @@ -90,7 +94,8 @@ class MasterApplication : public QApplication std::unique_ptr _pixelStreamerLauncher; std::unique_ptr _pixelStreamWindowManager; #if TIDE_ENABLE_TUIO_TOUCH_LISTENER - std::unique_ptr _touchListener; + std::unique_ptr _touchListener; + std::unique_ptr _touchInjector; #endif #if TIDE_ENABLE_REST_INTERFACE std::unique_ptr _restInterface; diff --git a/tide/master/MultiTouchListener.cpp b/tide/master/MultiTouchListener.cpp deleted file mode 100644 index a53e03a7..00000000 --- a/tide/master/MultiTouchListener.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ -/* Daniel Nachbaur */ -/* All rights reserved. */ -/* */ -/* Redistribution and use in source and binary forms, with or */ -/* without modification, are permitted provided that the following */ -/* conditions are met: */ -/* */ -/* 1. Redistributions of source code must retain the above */ -/* copyright notice, this list of conditions and the following */ -/* disclaimer. */ -/* */ -/* 2. Redistributions in binary form must reproduce the above */ -/* copyright notice, this list of conditions and the following */ -/* disclaimer in the documentation and/or other materials */ -/* provided with the distribution. */ -/* */ -/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ -/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ -/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ -/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ -/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ -/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ -/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ -/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ -/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ -/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ -/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ -/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ -/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ -/* POSSIBILITY OF SUCH DAMAGE. */ -/* */ -/* The views and conclusions contained in the software and */ -/* documentation are those of the authors and should not be */ -/* interpreted as representing official policies, either expressed */ -/* or implied, of Ecole polytechnique federale de Lausanne. */ -/*********************************************************************/ - -#include "MultiTouchListener.h" -#include "DisplayGroupView.h" -#include "log.h" - -#include -#include -#include - -MultiTouchListener::MultiTouchListener( DisplayGroupView& targetView, - const QSize& wallSize ) - : TUIO::TuioListener() - , _targetView( targetView ) - , _device() - , _wallSize( wallSize ) -{ - _device.setType( QTouchDevice::TouchScreen ); - - _client.addTuioListener( this ); - _client.connect(); -} - -MultiTouchListener::~MultiTouchListener() -{ - _client.removeTuioListener( this ); - _client.disconnect(); -} - -void MultiTouchListener::addTuioObject( TUIO::TuioObject* ) -{ -} - -void MultiTouchListener::updateTuioObject( TUIO::TuioObject* ) -{ -} - -void MultiTouchListener::removeTuioObject( TUIO::TuioObject* ) -{ -} - -void MultiTouchListener::addTuioCursor( TUIO::TuioCursor* tcur ) -{ - _handleEvent( tcur, QEvent::TouchBegin ); - emit touchPointAdded( tcur->getCursorID(), _getWallPos( tcur )); -} - -void MultiTouchListener::updateTuioCursor( TUIO::TuioCursor* tcur ) -{ - _handleEvent( tcur, QEvent::TouchUpdate ); - emit touchPointUpdated( tcur->getCursorID(), _getWallPos( tcur )); -} - -void MultiTouchListener::removeTuioCursor( TUIO::TuioCursor* tcur ) -{ - _handleEvent( tcur, QEvent::TouchEnd ); - emit touchPointRemoved( tcur->getCursorID( )); -} - -void MultiTouchListener::refresh( TUIO::TuioTime ) -{ -} - -QPointF MultiTouchListener::_getScreenPos( TUIO::TuioCursor* tcur ) const -{ - return _targetView.mapToWallPos( QPointF( tcur->getX(), tcur->getY( ))); -} - -QPointF MultiTouchListener::_getWallPos( TUIO::TuioCursor* tcur ) const -{ - return QPointF( tcur->getX() * _wallSize.width(), - tcur->getY() * _wallSize.height( )); -} - -void MultiTouchListener::_fillBegin( QTouchEvent::TouchPoint& touchPoint ) const -{ - touchPoint.setStartPos( touchPoint.pos( )); - touchPoint.setStartScreenPos( touchPoint.screenPos( )); - touchPoint.setStartNormalizedPos( touchPoint.normalizedPos( )); - - touchPoint.setLastPos( touchPoint.pos( )); - touchPoint.setLastScreenPos( touchPoint.screenPos( )); - touchPoint.setLastNormalizedPos( touchPoint.normalizedPos( )); -} - -void MultiTouchListener::_fill( QTouchEvent::TouchPoint& touchPoint, - const QTouchEvent::TouchPoint& prevPoint) const -{ - touchPoint.setStartPos( prevPoint.startPos( )); - touchPoint.setStartScreenPos( prevPoint.startScreenPos( )); - touchPoint.setStartNormalizedPos( prevPoint.startNormalizedPos( )); - - touchPoint.setLastPos( prevPoint.pos( )); - touchPoint.setLastScreenPos( prevPoint.screenPos( )); - touchPoint.setLastNormalizedPos( prevPoint.normalizedPos( )); -} - -void MultiTouchListener::_handleEvent( TUIO::TuioCursor* tcur, - const QEvent::Type eventType ) -{ - const QPointF screenPos = _getScreenPos( tcur ); - const QPointF normalizedPos( tcur->getX(), tcur->getY( )); - - QTouchEvent::TouchPoint touchPoint( tcur->getCursorID( )); - touchPoint.setPressure( 1.0 ); - touchPoint.setPos( screenPos ); - touchPoint.setScreenPos( screenPos ); - touchPoint.setNormalizedPos( normalizedPos ); - - Qt::TouchPointStates touchPointStates = 0; - - switch( eventType ) - { - case QEvent::TouchBegin: - touchPointStates = Qt::TouchPointPressed; - _fillBegin( touchPoint ); - break; - - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - { - if( eventType == QEvent::TouchUpdate ) - touchPointStates = tcur->isMoving() ? Qt::TouchPointMoved - : Qt::TouchPointStationary; - else - touchPointStates = Qt::TouchPointReleased; - - const int id = tcur->getCursorID(); - const QTouchEvent::TouchPoint& prevPoint = _touchPointMap.value( id ); - _fill( touchPoint, prevPoint ); - break; - } - - default: - put_flog( LOG_ERROR, "Got wrong touch event type %i", eventType ); - return; - } - - touchPoint.setState( touchPointStates ); - _touchPointMap.insert( tcur->getCursorID(), touchPoint ); - - QEvent::Type touchEventType = eventType; - if( touchEventType == QEvent::TouchEnd ) - touchEventType = _touchPointMap.isEmpty() ? QEvent::TouchEnd - : QEvent::TouchUpdate; - - QEvent* touchEvent = new QTouchEvent( touchEventType, &_device, - Qt::NoModifier, touchPointStates, - _touchPointMap.values( )); - QCoreApplication::postEvent( &_targetView, touchEvent ); - - // Prepare state for next call to handle event - if( eventType == QEvent::TouchEnd ) - _touchPointMap.remove( tcur->getCursorID( )); - else - _touchPointMap[tcur->getCursorID()].setState( Qt::TouchPointStationary ); -} diff --git a/tide/master/MultitouchArea.cpp b/tide/master/MultitouchArea.cpp index eeea8ae8..5ecd20da 100644 --- a/tide/master/MultitouchArea.cpp +++ b/tide/master/MultitouchArea.cpp @@ -39,8 +39,7 @@ #include "MultitouchArea.h" -#include -#include +#include "multitouch/MathUtils.h" namespace { @@ -50,20 +49,52 @@ const qreal pinchThresholdPx = 10; const qreal swipeMaxFingersIntervalPx = 120; const qreal swipeThresholdPx = 80; const qreal doubleTapThresholdPx = 40; -const uint tapAndHoldTimeout = 1000; -const uint doubleTapTimeout = 750; +const uint doubleTapTimeoutMs = 750; +const uint tapAndHoldTimeoutMs = 1000; } MultitouchArea::MultitouchArea( QQuickItem* parent_ ) : QQuickItem( parent_ ) - , _panThreshold( defaultPanThresholdPx ) + , _doubleTapDetector( doubleTapThresholdPx, doubleTapTimeoutMs ) + , _panDetector( defaultPanThresholdPx ) + , _pinchDetector( pinchThresholdPx ) + , _swipeDetector( swipeMaxFingersIntervalPx, swipeThresholdPx ) + , _tapAndHoldDetector( tapAndHoldTimeoutMs, defaultPanThresholdPx ) + , _tapDetector( defaultPanThresholdPx ) { setAcceptedMouseButtons( Qt::LeftButton ); - _tapAndHoldTimer.setInterval( tapAndHoldTimeout ); - connect( &_tapAndHoldTimer, &QTimer::timeout, [this]() { - emit tapAndHold( _getTouchCenterStartPos(), _getPointsCount( )); - }); + connect( &_doubleTapDetector, &DoubleTapDetector::doubleTap, + this, &MultitouchArea::doubleTap ); + + connect( &_panDetector, &PanDetector::panStarted, + this, &MultitouchArea::panStarted ); + connect( &_panDetector, &PanDetector::pan, + this, &MultitouchArea::pan ); + connect( &_panDetector, &PanDetector::panEnded, + this, &MultitouchArea::panEnded ); + + connect( &_pinchDetector, &PinchDetector::pinchStarted, + this, &MultitouchArea::pinchStarted ); + connect( &_pinchDetector, &PinchDetector::pinch, + this, &MultitouchArea::pinch ); + connect( &_pinchDetector, &PinchDetector::pinchEnded, + this, &MultitouchArea::pinchEnded ); + + connect( &_swipeDetector, &SwipeDetector::swipeDown, + this, &MultitouchArea::swipeDown ); + connect( &_swipeDetector, &SwipeDetector::swipeLeft, + this, &MultitouchArea::swipeLeft ); + connect( &_swipeDetector, &SwipeDetector::swipeRight, + this, &MultitouchArea::swipeRight ); + connect( &_swipeDetector, &SwipeDetector::swipeUp, + this, &MultitouchArea::swipeUp ); + + connect( &_tapAndHoldDetector, &TapAndHoldDetector::tapAndHold, + this, &MultitouchArea::tapAndHold ); + + connect( &_tapDetector, &TapDetector::tap, + this, &MultitouchArea::tap ); } QQuickItem* MultitouchArea::getReferenceItem() const @@ -73,7 +104,7 @@ QQuickItem* MultitouchArea::getReferenceItem() const qreal MultitouchArea::getPanThreshold() const { - return _panThreshold; + return _panDetector.getPanThreshold(); } void MultitouchArea::setReferenceItem( QQuickItem* arg ) @@ -87,28 +118,13 @@ void MultitouchArea::setReferenceItem( QQuickItem* arg ) void MultitouchArea::setPanThreshold( const qreal arg ) { - if( _panThreshold == arg ) + if( getPanThreshold() == arg ) return; - _panThreshold = arg; + _panDetector.setPanThreshold( arg ); emit panThresholdChanged( arg ); } -QPointF MultitouchArea::_getScenePos( const QMouseEvent& mouse ) -{ - return mapToItem( _referenceItem, mouse.localPos( )); -} - -QPointF MultitouchArea::_getScenePos( const QTouchEvent::TouchPoint& point ) -{ - return mapToItem( _referenceItem, point.pos( )); -} - -QPointF MultitouchArea::_getScenePos( const QWheelEvent& wheel ) -{ - return mapToItem( _referenceItem, wheel.posF( )); -} - uint _getButtonsCount( const QMouseEvent& mouse ) { uint count = 0; @@ -123,36 +139,39 @@ uint _getButtonsCount( const QMouseEvent& mouse ) void MultitouchArea::mousePressEvent( QMouseEvent* mouse ) { - if( _getButtonsCount( *mouse ) == 1 ) - emit touchStarted( _getScenePos( *mouse )); + const auto buttonsCount = int( _getButtonsCount( *mouse ) ); + const auto pos = _getScenePos( *mouse ); + if( buttonsCount == 1 ) + emit touchStarted( pos ); + + emit touchPointAdded( buttonsCount - 1, pos ); - _handleMultipointGestures( _getPositions( *mouse )); + _handleGestures( _getPositions( *mouse )); } void MultitouchArea::mouseMoveEvent( QMouseEvent* mouse ) { - _handleMultipointGestures( _getPositions( *mouse )); + const auto buttonsCount = int( _getButtonsCount( *mouse ) ); + const auto pos = _getScenePos( *mouse ); + for( int i = 0; i < buttonsCount; ++i ) + emit touchPointUpdated( i, pos ); + + _handleGestures( _getPositions( *mouse )); } void MultitouchArea::mouseReleaseEvent( QMouseEvent* mouse ) { - const auto count = _getButtonsCount( *mouse ); + const auto buttonsCount = int( _getButtonsCount( *mouse ) ); const auto pos = _getScenePos( *mouse ); - if( count == 0 && !_panning ) - emit tap( pos ); + emit touchPointRemoved( buttonsCount, pos ); - _handleMultipointGestures( _getPositions( *mouse )); + _handleGestures( _getPositions( *mouse )); - if( count == 0 ) + if( buttonsCount == 0 ) emit touchEnded( pos ); } -void MultitouchArea::mouseDoubleClickEvent( QMouseEvent* mouse ) -{ - emit doubleTap( _getScenePos( *mouse )); -} - void MultitouchArea::wheelEvent( QWheelEvent* wheel ) { emit pinchStarted(); @@ -168,31 +187,19 @@ void MultitouchArea::touchEvent( QTouchEvent* touch ) if( touch->type() == QEvent::TouchBegin ) emit touchStarted( _getScenePos( points.at( 0 ))); - switch( points.size( )) - { - case 1: - _handleSinglePoint( points.at( 0 )); - break; - case 2: - _handleTwoPoints( points.at( 0 ), points.at( 1 )); - break; - default: - break; - } - _handleMultipointGestures( _getPositions( points )); + _handleGestures( _getPositions( points )); + _handleTouch( points ); if( touch->type() == QEvent::TouchEnd ) emit touchEnded( _getScenePos( points.at( 0 ))); } -MultitouchArea::Positions -MultitouchArea::_getPositions( const QMouseEvent& mouse ) +Positions MultitouchArea::_getPositions( const QMouseEvent& mouse ) { return Positions{ _getButtonsCount( mouse ), _getScenePos( mouse ) }; } -MultitouchArea::Positions -MultitouchArea::_getPositions( const TouchPoints& points ) +Positions MultitouchArea::_getPositions( const TouchPoints& points ) { Positions positions; for( auto point : points ) @@ -203,299 +210,72 @@ MultitouchArea::_getPositions( const TouchPoints& points ) return positions; } -void MultitouchArea::_handleSinglePoint( const QTouchEvent::TouchPoint& point ) -{ - const auto pos = _getScenePos( point ); - - switch( point.state( )) - { - case Qt::TouchPointPressed: - _updateDoubleTapGesture( pos ); - break; - case Qt::TouchPointStationary: - break; - case Qt::TouchPointMoved: - break; - case Qt::TouchPointReleased: - if( !_panning ) - emit tap( pos ); - break; - } -} - -void MultitouchArea::_startDoubleTapGesture( const QPointF& pos ) -{ - _tapStartPos = pos; - _doubleTapTimer.start(); -} - -void MultitouchArea::_updateDoubleTapGesture( const QPointF& pos ) -{ - if( _doubleTapTimer.isActive( )) - { - if( (pos - _tapStartPos).manhattanLength() < doubleTapThresholdPx ) - emit doubleTap( pos ); - _cancelDoubleTapGesture(); - } - else - _startDoubleTapGesture( pos ); -} - -void MultitouchArea::_cancelDoubleTapGesture() -{ - _doubleTapTimer.stop(); -} - -qreal _getDist( const QPointF& p0, const QPointF& p1 ) -{ - const QPointF dist = p1 - p0; - return std::sqrt( QPointF::dotProduct( dist, dist )); -} - -QPointF _getCenter( const QPointF& pos0, const QPointF& pos1 ) -{ - return ( pos0 + pos1 ) / 2; -} - -QRectF _makeBoundingRect( const QPointF& pos0, const QPointF& pos1 ) -{ - QRectF rect; - rect.setLeft( std::min( pos0.x(), pos1.x( ))); - rect.setRight( std::max( pos0.x(), pos1.x( ))); - rect.setTop( std::min( pos0.y(), pos1.y( ))); - rect.setBottom( std::max( pos0.y(), pos1.y( ))); - return rect; -} - -void MultitouchArea::_handleTwoPoints( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ) -{ - if( p0.state() == Qt::TouchPointReleased || - p1.state() == Qt::TouchPointReleased ) - { - _cancelPinchGesture(); - _cancelSwipeGesture(); - return; - } - - const auto pos0 = _getScenePos( p0 ); - const auto pos1 = _getScenePos( p1 ); - - if( p0.state() == Qt::TouchPointPressed || - p1.state() == Qt::TouchPointPressed ) - { - _initPinchGesture( pos0, pos1 ); - _initSwipeGesture( pos0, pos1 ); - } - - _updatePinchGesture( pos0, pos1 ); - _updateSwipeGesture( pos0, pos1 ); -} - -void MultitouchArea::_initPinchGesture( const QPointF& pos0, - const QPointF& pos1 ) -{ - const auto twoFingersStartRect = _makeBoundingRect( pos0, pos1 ); - const auto w = twoFingersStartRect.width(); - const auto h = twoFingersStartRect.height(); - _initialPinchDist = std::sqrt( w * w + h * h ); -} - -void MultitouchArea::_startPinchGesture( const QPointF& pos0, - const QPointF& pos1 ) -{ - if( _pinching ) - return; - - _pinching = true; - _lastPinchRect = _makeBoundingRect( pos0, pos1 ); - emit pinchStarted(); -} - -void MultitouchArea::_updatePinchGesture( const QPointF& pos0, - const QPointF& pos1 ) -{ - if( !_pinching ) - { - const auto pinchDist = _getDist( pos0, pos1 ); - const auto pinchDelta = std::abs( pinchDist - _initialPinchDist ); - if( pinchDelta > pinchThresholdPx ) - _startPinchGesture( pos0, pos1 ); - else - return; - } - - const auto pinchRect = _makeBoundingRect( pos0, pos1 ); - const auto pinchDelta = pinchRect.size() - _lastPinchRect.size(); - _lastPinchRect = pinchRect; - emit pinch( pinchRect.center(), QPointF{ pinchDelta.width(), - pinchDelta.height() }); -} - -void MultitouchArea::_cancelPinchGesture() +QPointF MultitouchArea::_getScenePos( const QMouseEvent& mouse ) { - if( !_pinching ) - return; - - _pinching = false; - emit pinchEnded(); + return mapToItem( _referenceItem, mouse.localPos( )); } -bool _checkFingersDistanceForSwipe( const QPointF& pos0, const QPointF& pos1 ) +QPointF MultitouchArea::_getScenePos( const QTouchEvent::TouchPoint& point ) { - const qreal fingersInterval = _getDist( pos0, pos1 ); - return fingersInterval < swipeMaxFingersIntervalPx; + return mapToItem( _referenceItem, point.pos( )); } -void MultitouchArea::_initSwipeGesture( const QPointF& pos0, - const QPointF& pos1 ) +QPointF MultitouchArea::_getScenePos( const QWheelEvent& wheel ) { - _canBeSwipe = _checkFingersDistanceForSwipe( pos0, pos1 ); - _swipeStartPos = _getCenter( pos0, pos1 ); + return mapToItem( _referenceItem, wheel.posF( )); } -void MultitouchArea::_updateSwipeGesture( const QPointF& pos0, - const QPointF& pos1 ) +void MultitouchArea::_handleGestures( const Positions& positions ) { - if( !_canBeSwipe ) - return; - - if( !_checkFingersDistanceForSwipe( pos0, pos1 )) - { - _cancelSwipeGesture(); - return; - } - - const auto twoFingersPos = _getCenter( pos0, pos1 ); - const auto twoFingersStartPos = _swipeStartPos; - const auto dist = _getDist( twoFingersStartPos, twoFingersPos ); - if( dist > swipeThresholdPx ) + if( positions.size() != _touchStartPos.size( )) { - const qreal dx = twoFingersPos.x() - twoFingersStartPos.x(); - const qreal dy = twoFingersPos.y() - twoFingersStartPos.y(); - - if( std::abs( dx ) > std::abs( dy )) + if( positions.size() == 2 ) { - // Horizontal swipe - if( dx > 0.0 ) - emit swipeRight(); - else - emit swipeLeft(); + _pinchDetector.initGesture( positions.at( 0 ), positions.at( 1 )); + _swipeDetector.initGesture( positions.at( 0 ), positions.at( 1 )); } else { - // Vertical swipe - if( dy > 0.0 ) - emit swipeDown(); - else - emit swipeUp(); + _pinchDetector.cancelGesture(); + _swipeDetector.cancelGesture(); } - _cancelSwipeGesture(); // Only allow one swipe - } -} - -void MultitouchArea::_cancelSwipeGesture() -{ - _canBeSwipe = false; -} - -void MultitouchArea::_handleMultipointGestures( const Positions& positions ) -{ - if( positions.size() != _touchStartPos.size( )) - { - _cancelTapAndHoldGesture(); - _cancelPanGesture(); + _doubleTapDetector.initGesture( positions ); + _tapAndHoldDetector.initGesture( positions ); + _tapDetector.initGesture( positions ); + _panDetector.initGesture( positions ); _touchStartPos = positions; - - if( !positions.empty( )) - _startTapAndHoldGesture(); return; } - _updateTapAndHoldGesture( positions ); - _updatePanGesture( positions ); -} - -void MultitouchArea::_startTapAndHoldGesture() -{ - _tapAndHoldTimer.start(); -} - -void MultitouchArea::_updateTapAndHoldGesture( const Positions& positions ) -{ - if( _tapAndHoldTimer.isActive( )) - _cancelTapAndHoldIfMoved( positions ); -} - -void MultitouchArea::_cancelTapAndHoldIfMoved( const Positions& positions ) -{ - size_t i = 0; - for( const auto& pos : positions ) + if( positions.size() == 2 ) { - if( (pos - _touchStartPos[i++]).manhattanLength() > _panThreshold ) - { - _cancelTapAndHoldGesture(); - return; - } + _pinchDetector.updateGesture( positions.at( 0 ), positions.at( 1 )); + _swipeDetector.updateGesture( positions.at( 0 ), positions.at( 1 )); } + _tapAndHoldDetector.updateGesture( positions ); + _tapDetector.updateGesture( positions ); + _panDetector.updateGesture( positions ); } -void MultitouchArea::_cancelTapAndHoldGesture() -{ - _tapAndHoldTimer.stop(); -} - -template -QPointF _computeCenter( const Container& positions ) -{ - QPointF center; - for( const auto& pos : positions ) - center += pos; - return center / positions.size(); -} - -QPointF MultitouchArea::_getTouchCenterStartPos() const -{ - return _computeCenter( _touchStartPos ); -} - -uint MultitouchArea::_getPointsCount() const -{ - return _touchStartPos.size(); -} - -void MultitouchArea::_startPanGesture( const QPointF& pos ) -{ - if( _panning ) - return; - - _panning = true; - emit panStarted( pos, _getPointsCount( )); -} - -void MultitouchArea::_updatePanGesture( const Positions& positions ) +void MultitouchArea::_handleTouch( const TouchPoints& points ) { - const auto pos = _computeCenter( positions ); - const auto start = _getTouchCenterStartPos(); - - if( !_panning && (pos - start).manhattanLength() > _panThreshold ) - { - _startPanGesture( pos ); - _lastPanPos = pos; - } - if( _panning ) + for( const auto& point : points ) { - emit pan( pos, pos - _lastPanPos, positions.size( )); - _lastPanPos = pos; + switch( point.state( )) + { + case Qt::TouchPointPressed: + emit touchPointAdded( point.id(), _getScenePos( point )); + break; + case Qt::TouchPointMoved: + emit touchPointUpdated( point.id(), _getScenePos( point )); + break; + case Qt::TouchPointReleased: + emit touchPointRemoved( point.id(), _getScenePos( point )); + break; + case Qt::TouchPointStationary: + break; + } } } -void MultitouchArea::_cancelPanGesture() -{ - if( !_panning ) - return; - - _panning = false; - emit panEnded(); -} diff --git a/tide/master/MultitouchArea.h b/tide/master/MultitouchArea.h index 8024d1db..4b4ef57f 100644 --- a/tide/master/MultitouchArea.h +++ b/tide/master/MultitouchArea.h @@ -42,7 +42,12 @@ #include -#include +#include "multitouch/DoubleTapDetector.h" +#include "multitouch/PanDetector.h" +#include "multitouch/PinchDetector.h" +#include "multitouch/SwipeDetector.h" +#include "multitouch/TapAndHoldDetector.h" +#include "multitouch/TapDetector.h" /** * A multipoint touch area to detect touch gestures on Qml objects. @@ -90,50 +95,56 @@ public slots: /** Always emitted for the last finger that is removed from the area. */ void touchEnded( QPointF pos ); - //@} - /** @name Basic one-finger tap events. */ - //@{ - /** Emitted for a one-finger touch and release in-place (i.e. not a pan). */ - void tap( QPointF pos ); + /** Emitted when a new touch point is added. */ + void touchPointAdded( int id, QPointF pos ); - /** Emitted when two taps occur in a fast sequence. */ - void doubleTap( QPointF pos ); + /** Emitted when an existing touch point is updated. */ + void touchPointUpdated( int id, QPointF pos ); + + /** Emitted when an existing touch point is removed. */ + void touchPointRemoved( int id, QPointF pos ); //@} - /** @name Two-fingers gestures. */ + /** @name Two-finger gestures. */ //@{ - /** Emitted when a pinch starts (i.e. two fingers start moving apart). */ + /** @copydoc PinchDetector::pinchStarted */ void pinchStarted(); - /** Emitted for each step of a two-fingers pinch gesture. */ + /** @copydoc PinchDetector::pinch */ void pinch( QPointF pos, QPointF pixelDelta ); - /** Emitted when a pinch ends (i.e. one of the two fingers is released). */ + /** @copydoc PinchDetector::pinchEnded */ void pinchEnded(); - /** Two-fingers swipe to the left. */ + /** @copydoc SwipeDetector::swipeLeft */ void swipeLeft(); - /** Two-fingers swipe to the right. */ + /** @copydoc SwipeDetector::swipeRight */ void swipeRight(); - /** Two-fingers swipe up. */ + /** @copydoc SwipeDetector::swipeUp */ void swipeUp(); - /** Two-fingers swipe down. */ + /** @copydoc SwipeDetector::swipeDown */ void swipeDown(); //@} /** @name Multi-finger gestures. */ //@{ - /** Emitted after a prolonged non-moving touch with one or more fingers. */ + /** @copydoc TapDetector::tap */ + void tap( QPointF pos, uint numPoints ); + + /** @copydoc DoubleTapDetector::doubleTap */ + void doubleTap( QPointF pos, uint numPoints ); + + /** @copydoc TapAndHoldDetector::tapAndHold */ void tapAndHold( QPointF pos, uint numPoints ); - /** Emitted when a pan starts (i.e. one or more finger(s) start moving). */ + /** @copydoc PanDetector::panStarted */ void panStarted( QPointF pos, uint numPoints ); - /** Emitted for each finger movement between panStarted-panEnded. */ + /** @copydoc PanDetector::pan */ void pan( QPointF pos, QPointF delta, uint numPoints ); - /** Emitted when a pan ends (finger released or new finger detected). */ + /** @copydoc PanDetector::panEnded */ void panEnded(); //@} @@ -141,11 +152,9 @@ public slots: void mousePressEvent( QMouseEvent* event ) override; void mouseMoveEvent( QMouseEvent* event ) override; void mouseReleaseEvent( QMouseEvent* event ) override; - void mouseDoubleClickEvent( QMouseEvent* event ) override; void wheelEvent( QWheelEvent* event ) override; void touchEvent( QTouchEvent* event ) override; - using Positions = std::vector; using TouchPoints = QList; Positions _getPositions( const QMouseEvent& mouse ); @@ -155,75 +164,19 @@ public slots: QPointF _getScenePos( const QTouchEvent::TouchPoint& point ); QPointF _getScenePos( const QWheelEvent& wheel ); - /** @name Single-point gestures */ - //@{ - void _handleSinglePoint( const QTouchEvent::TouchPoint& point ); - - void _startDoubleTapGesture( const QPointF& pos ); - void _updateDoubleTapGesture( const QPointF& pos ); - void _cancelDoubleTapGesture(); - //@} - - /** @name Two-point gestures */ - //@{ - void _handleTwoPoints( const QTouchEvent::TouchPoint& p0, - const QTouchEvent::TouchPoint& p1 ); - - void _initPinchGesture( const QPointF& pos0, const QPointF& pos1 ); - void _startPinchGesture( const QPointF& pos0, const QPointF& pos1 ); - void _updatePinchGesture( const QPointF& pos0, const QPointF& pos1 ); - void _cancelPinchGesture(); - - void _initSwipeGesture( const QPointF& pos0, const QPointF& pos1 ); - void _updateSwipeGesture( const QPointF& pos0, const QPointF& pos1 ); - void _cancelSwipeGesture(); - //@} - - /** @name Mulit-point gestures */ - //@{ - void _handleMultipointGestures( const Positions& positions ); - - void _startTapAndHoldGesture(); - void _updateTapAndHoldGesture( const Positions& positions ); - void _cancelTapAndHoldIfMoved( const Positions& positions ); - void _cancelTapAndHoldGesture(); - - QPointF _getTouchCenterStartPos() const; - uint _getPointsCount() const; - - void _startPanGesture( const QPointF& pos ); - void _updatePanGesture( const Positions& positions ); - void _cancelPanGesture(); - //@} + void _handleGestures( const Positions& positions ); + void _handleTouch( const TouchPoints& points ); QQuickItem* _referenceItem = nullptr; - /** @name Single-point gestures */ - //@{ - QPointF _tapStartPos; - QTimer _doubleTapTimer; - //@} - - /** @name Two-point gestures */ - //@{ - bool _pinching = false; - qreal _initialPinchDist = 0.0; - QRectF _lastPinchRect; - - bool _canBeSwipe = false; - QPointF _swipeStartPos; - //@} + DoubleTapDetector _doubleTapDetector; + PanDetector _panDetector; + PinchDetector _pinchDetector; + SwipeDetector _swipeDetector; + TapAndHoldDetector _tapAndHoldDetector; + TapDetector _tapDetector; - /** @name Mulit-point gestures */ - //@{ Positions _touchStartPos; - - QTimer _tapAndHoldTimer; - - qreal _panThreshold; - bool _panning = false; - QPointF _lastPanPos; - //@} }; #endif diff --git a/tide/master/MultitouchListener.cpp b/tide/master/MultitouchListener.cpp new file mode 100644 index 00000000..8c12eee6 --- /dev/null +++ b/tide/master/MultitouchListener.cpp @@ -0,0 +1,74 @@ +/*********************************************************************/ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ +/* Daniel Nachbaur */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "MultitouchListener.h" + +MultitouchListener::MultitouchListener() + : TUIO::TuioListener() +{ + _client.addTuioListener( this ); + _client.connect(); +} + +MultitouchListener::~MultitouchListener() +{ + _client.removeTuioListener( this ); + _client.disconnect(); +} + +inline QPointF _getPos( TUIO::TuioCursor* tcur ) +{ + return QPointF{ tcur->getX(), tcur->getY() }; +} + +void MultitouchListener::addTuioCursor( TUIO::TuioCursor* tcur ) +{ + emit touchPointAdded( tcur->getCursorID(), _getPos( tcur )); +} + +void MultitouchListener::updateTuioCursor( TUIO::TuioCursor* tcur ) +{ + emit touchPointUpdated( tcur->getCursorID(), _getPos( tcur )); +} + +void MultitouchListener::removeTuioCursor( TUIO::TuioCursor* tcur ) +{ + emit touchPointRemoved( tcur->getCursorID( ), _getPos( tcur )); +} diff --git a/tide/master/MultiTouchListener.h b/tide/master/MultitouchListener.h similarity index 68% rename from tide/master/MultiTouchListener.h rename to tide/master/MultitouchListener.h index ec41a831..38ca2ee4 100644 --- a/tide/master/MultiTouchListener.h +++ b/tide/master/MultitouchListener.h @@ -1,6 +1,7 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ /* Daniel Nachbaur */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -37,61 +38,44 @@ /* or implied, of Ecole polytechnique federale de Lausanne. */ /*********************************************************************/ -#ifndef MULTI_TOUCH_LISTENER_H -#define MULTI_TOUCH_LISTENER_H +#ifndef MULTITOUCHLISTENER_H +#define MULTITOUCHLISTENER_H -#include "types.h" - -#include #include +#include #include -#include - -class DisplayGroupView; +#include /** - * Listen to TUIO events and transmit the touch points to a target QGraphicsView. + * Listen to TUIO touch events and emit corresponding QSignals. */ -class MultiTouchListener : public QObject, public TUIO::TuioListener +class MultitouchListener : public QObject, public TUIO::TuioListener { Q_OBJECT + Q_DISABLE_COPY( MultitouchListener ) public: - MultiTouchListener( DisplayGroupView& targetView, const QSize& wallSize ); - ~MultiTouchListener(); + MultitouchListener(); + ~MultitouchListener(); - void addTuioObject( TUIO::TuioObject* tobj ) override; - void updateTuioObject( TUIO::TuioObject* tobj ) override; - void removeTuioObject( TUIO::TuioObject* tobj ) override; + void addTuioObject( TUIO::TuioObject* ) override {} + void updateTuioObject( TUIO::TuioObject* ) override {} + void removeTuioObject( TUIO::TuioObject* ) override {} void addTuioCursor( TUIO::TuioCursor* tcur ) override; void updateTuioCursor( TUIO::TuioCursor* tcur ) override; void removeTuioCursor( TUIO::TuioCursor* tcur ) override; - void refresh( TUIO::TuioTime frameTime ) override; + void refresh( TUIO::TuioTime ) override {} signals: - void touchPointAdded( int id, QPointF position ); - void touchPointUpdated( int id, QPointF position ); - void touchPointRemoved( int id ); + void touchPointAdded( int id, QPointF normalizedPos ); + void touchPointUpdated( int id, QPointF normalizedPos ); + void touchPointRemoved( int id, QPointF normalizedPos ); private: - Q_DISABLE_COPY( MultiTouchListener ) - - QPointF _getScreenPos( TUIO::TuioCursor* tcur ) const; - QPointF _getWallPos( TUIO::TuioCursor* tcur ) const; - - void _fillBegin( QTouchEvent::TouchPoint& touchPoint ) const; - void _fill( QTouchEvent::TouchPoint& touchPoint, - const QTouchEvent::TouchPoint& prevPoint ) const; - void _handleEvent( TUIO::TuioCursor* tcur, QEvent::Type eventType ); - - QMap _touchPointMap; - DisplayGroupView& _targetView; TUIO::TuioClient _client; - QTouchDevice _device; - const QSize _wallSize; }; #endif diff --git a/tide/master/multitouch/DoubleTapDetector.cpp b/tide/master/multitouch/DoubleTapDetector.cpp new file mode 100644 index 00000000..835c623c --- /dev/null +++ b/tide/master/multitouch/DoubleTapDetector.cpp @@ -0,0 +1,104 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "DoubleTapDetector.h" + +#include "MathUtils.h" + +DoubleTapDetector::DoubleTapDetector( const qreal doubleTapThresholdPx, + const uint doubleTapTimeoutMs ) + : _doubleTapThresholdPx( doubleTapThresholdPx ) +{ + _doubleTapTimer.setInterval( doubleTapTimeoutMs ); + _doubleTapTimer.setSingleShot( true ); + + connect( &_doubleTapTimer, &QTimer::timeout, this, + &DoubleTapDetector::cancelGesture ); +} + +void DoubleTapDetector::initGesture( const Positions& positions ) +{ + if( !_canBeDoubleTap ) + { + // adding initial points + if( positions.size() > _touchStartPos.size( )) + { + if( _touchStartPos.empty( )) + _startGesture( positions ); + else + _touchStartPos = positions; + return; + } + + // all points released, decide if potential double tap or abort + if( positions.empty( )) + { + _canBeDoubleTap = _doubleTapTimer.isActive(); + if( !_canBeDoubleTap ) + cancelGesture(); + } + return; + } + + // points pressed again, check for double tap + if( positions.size() == _touchStartPos.size() && + !MathUtils::hasMoved( positions, _touchStartPos, _doubleTapThresholdPx)) + { + emit doubleTap( MathUtils::computeCenter( _touchStartPos ), + _touchStartPos.size( )); + _doubleTapTimer.stop(); + } + + // all points released, reset everything for next detection + if( positions.empty( )) + cancelGesture(); +} + +void DoubleTapDetector::cancelGesture() +{ + _doubleTapTimer.stop(); + _canBeDoubleTap = false; + _touchStartPos.clear(); +} + +void DoubleTapDetector::_startGesture( const Positions& positions ) +{ + _touchStartPos = positions; + _doubleTapTimer.start(); +} diff --git a/tide/master/multitouch/DoubleTapDetector.h b/tide/master/multitouch/DoubleTapDetector.h new file mode 100644 index 00000000..b020b109 --- /dev/null +++ b/tide/master/multitouch/DoubleTapDetector.h @@ -0,0 +1,75 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef DOUBLETAPDETECTOR_H +#define DOUBLETAPDETECTOR_H + +#include "types.h" + +#include +#include + +/** + * Detect double-tap gestures. + */ +class DoubleTapDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( DoubleTapDetector ) + +public: + DoubleTapDetector( qreal doubleTapThresholdPx, uint doubleTapTimeoutMs ); + + void initGesture( const Positions& positions ); + void cancelGesture(); + +signals: + /** Emitted when two taps occur in a fast sequence. */ + void doubleTap( QPointF pos, uint numPoints ); + +private: + const qreal _doubleTapThresholdPx; + Positions _touchStartPos; + QTimer _doubleTapTimer; + bool _canBeDoubleTap = false; + + void _startGesture( const Positions& positions ); +}; + +#endif diff --git a/tide/master/multitouch/MathUtils.cpp b/tide/master/multitouch/MathUtils.cpp new file mode 100644 index 00000000..7559fa01 --- /dev/null +++ b/tide/master/multitouch/MathUtils.cpp @@ -0,0 +1,88 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "MathUtils.h" + +#include + +namespace MathUtils +{ + +QRectF getBoundingRect( const QPointF& p0, const QPointF& p1 ) +{ + QRectF rect; + rect.setLeft( std::min( p0.x(), p1.x( ))); + rect.setRight( std::max( p0.x(), p1.x( ))); + rect.setTop( std::min( p0.y(), p1.y( ))); + rect.setBottom( std::max( p0.y(), p1.y( ))); + return rect; +} + +qreal getDist( const QPointF& p0, const QPointF& p1 ) +{ + const QPointF dist = p1 - p0; + return std::sqrt( QPointF::dotProduct( dist, dist )); +} + +QPointF getCenter( const QPointF& p0, const QPointF& p1 ) +{ + return ( p0 + p1 ) / 2; +} + +QPointF computeCenter( const Positions& positions ) +{ + QPointF center; + for( const auto& pos : positions ) + center += pos; + return center / positions.size(); +} + +bool hasMoved( const Positions& positions, const Positions& startPositions, + const qreal moveThreshold ) +{ + size_t i = 0; + for( const auto& pos : positions ) + { + if( (pos - startPositions[i++]).manhattanLength() > moveThreshold ) + return true; + } + return false; +} + +} diff --git a/tide/master/multitouch/MathUtils.h b/tide/master/multitouch/MathUtils.h new file mode 100644 index 00000000..e99383de --- /dev/null +++ b/tide/master/multitouch/MathUtils.h @@ -0,0 +1,94 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ +#ifndef MATHUTILS_H +#define MATHUTILS_H + +#include + +#include "types.h" + +/** + * Utility math functions used by multitouch gesture detectors. + */ +namespace MathUtils +{ + +/** + * Get the minimal bounding rectangle around two points. + * @param p0 the first corner + * @param p1 the second corner + * @return the bounding rectangle + */ +QRectF getBoundingRect( const QPointF& p0, const QPointF& p1 ); + +/** + * Get the euclidean distance between two points. + * @param p0 the first point + * @param p1 the second point + * @return the distance between the two points. + */ +qreal getDist( const QPointF& p0, const QPointF& p1 ); + +/** + * Get the center of two points. + * @param p0 the first point + * @param p1 the second point + * @return the center of the two points. + */ +QPointF getCenter( const QPointF& p0, const QPointF& p1 ); + +/** + * Compute the center of a list of positions. + * @return the center of the points. + */ +QPointF computeCenter( const Positions& positions ); + +/** + * Check if any point has moved by more than a given threshold. + * @param positions the current positions + * @param startPositions the start positions + * @param moveThreshold the distance threshold + * @return true if any point has moved + */ +bool hasMoved( const Positions& positions, const Positions& startPositions, + qreal moveThreshold ); + +} + +#endif diff --git a/tide/master/multitouch/PanDetector.cpp b/tide/master/multitouch/PanDetector.cpp new file mode 100644 index 00000000..088c98a0 --- /dev/null +++ b/tide/master/multitouch/PanDetector.cpp @@ -0,0 +1,97 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "PanDetector.h" + +#include "multitouch/MathUtils.h" + +PanDetector::PanDetector( const qreal panThreshold ) + : _panThreshold( panThreshold ) +{} + +void PanDetector::initGesture( const Positions& positions ) +{ + cancelGesture(); + + _startPanPos = MathUtils::computeCenter( positions ); +} + +void PanDetector::updateGesture( const Positions& positions ) +{ + const auto pos = MathUtils::computeCenter( positions ); + + if( !_panning && (pos - _startPanPos).manhattanLength() > _panThreshold ) + { + _startGesture( pos, positions.size( )); + _lastPanPos = pos; + } + if( _panning ) + { + emit pan( pos, pos - _lastPanPos, positions.size( )); + _lastPanPos = pos; + } +} + +void PanDetector::cancelGesture() +{ + if( !_panning ) + return; + + _panning = false; + emit panEnded(); +} + +qreal PanDetector::getPanThreshold() const +{ + return _panThreshold; +} + +void PanDetector::setPanThreshold( const qreal arg ) +{ + _panThreshold = arg; +} + +void PanDetector::_startGesture( const QPointF& pos, const uint numPoints ) +{ + if( _panning ) + return; + + _panning = true; + emit panStarted( pos, numPoints ); +} diff --git a/tide/master/multitouch/PanDetector.h b/tide/master/multitouch/PanDetector.h new file mode 100644 index 00000000..cb927eff --- /dev/null +++ b/tide/master/multitouch/PanDetector.h @@ -0,0 +1,86 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef PANDETECTOR_H +#define PANDETECTOR_H + +#include "types.h" + +#include + +/** + * Detect multi-finger pan gestures. + */ +class PanDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( PanDetector ) + +public: + PanDetector( qreal panThreshold ); + + void initGesture( const Positions& positions ); + void updateGesture( const Positions& positions ); + void cancelGesture(); + + bool isPanning() const { return _panning; } + + qreal getPanThreshold() const; + void setPanThreshold( qreal arg ); + +signals: + /** Emitted when a pan starts (i.e. one or more finger(s) start moving). */ + void panStarted( QPointF pos, uint numPoints ); + + /** Emitted for each finger movement between panStarted-panEnded. */ + void pan( QPointF pos, QPointF delta, uint numPoints ); + + /** Emitted when a pan ends (finger released or new finger detected). */ + void panEnded(); + +private: + qreal _panThreshold; + bool _panning = false; + QPointF _startPanPos; + QPointF _lastPanPos; + + void _startGesture( const QPointF& pos, uint numPoints ); +}; + +#endif diff --git a/tide/master/multitouch/PinchDetector.cpp b/tide/master/multitouch/PinchDetector.cpp new file mode 100644 index 00000000..cc2b44ec --- /dev/null +++ b/tide/master/multitouch/PinchDetector.cpp @@ -0,0 +1,94 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "PinchDetector.h" + +#include "multitouch/MathUtils.h" + +#include + +PinchDetector::PinchDetector( const qreal pinchThresholdPx ) + : _pinchThresholdPx( pinchThresholdPx ) +{} + +void PinchDetector::initGesture( const QPointF& pos0, const QPointF& pos1 ) +{ + const auto twoFingersStartRect = MathUtils::getBoundingRect( pos0, pos1 ); + const auto w = twoFingersStartRect.width(); + const auto h = twoFingersStartRect.height(); + _initialPinchDist = std::sqrt( w * w + h * h ); +} + +void PinchDetector::updateGesture( const QPointF& pos0, const QPointF& pos1 ) +{ + if( !_pinching ) + { + const auto pinchDist = MathUtils::getDist( pos0, pos1 ); + const auto pinchDelta = std::abs( pinchDist - _initialPinchDist ); + if( pinchDelta > _pinchThresholdPx ) + _startGesture( pos0, pos1 ); + else + return; + } + + const auto pinchRect = MathUtils::getBoundingRect( pos0, pos1 ); + const auto pinchDelta = pinchRect.size() - _lastPinchRect.size(); + _lastPinchRect = pinchRect; + emit pinch( pinchRect.center(), QPointF{ pinchDelta.width(), + pinchDelta.height() }); +} + +void PinchDetector::cancelGesture() +{ + if( !_pinching ) + return; + + _pinching = false; + emit pinchEnded(); +} + +void PinchDetector::_startGesture( const QPointF& pos0, const QPointF& pos1 ) +{ + if( _pinching ) + return; + + _pinching = true; + _lastPinchRect = MathUtils::getBoundingRect( pos0, pos1 ); + emit pinchStarted(); +} diff --git a/tide/master/multitouch/PinchDetector.h b/tide/master/multitouch/PinchDetector.h new file mode 100644 index 00000000..368e503e --- /dev/null +++ b/tide/master/multitouch/PinchDetector.h @@ -0,0 +1,82 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef PINCHDETECTOR_H +#define PINCHDETECTOR_H + +#include "types.h" + +#include +#include + +/** + * Detect two-finger pinch gestures. + */ +class PinchDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( PinchDetector ) + +public: + explicit PinchDetector( qreal pinchThresholdPx ); + + void initGesture( const QPointF& pos0, const QPointF& pos1 ); + void updateGesture( const QPointF& pos0, const QPointF& pos1 ); + void cancelGesture(); + +signals: + /** Emitted when a pinch starts (i.e. two fingers start moving apart). */ + void pinchStarted(); + + /** Emitted for each step of a two-fingers pinch gesture. */ + void pinch( QPointF pos, QPointF pixelDelta ); + + /** Emitted when a pinch ends (i.e. one of the two fingers is released). */ + void pinchEnded(); + +private: + void _startGesture( const QPointF& pos0, const QPointF& pos1 ); + + const qreal _pinchThresholdPx = 0; + bool _pinching = false; + qreal _initialPinchDist = 0.0; + QRectF _lastPinchRect; +}; + +#endif diff --git a/tide/master/multitouch/SwipeDetector.cpp b/tide/master/multitouch/SwipeDetector.cpp new file mode 100644 index 00000000..81591cde --- /dev/null +++ b/tide/master/multitouch/SwipeDetector.cpp @@ -0,0 +1,107 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "SwipeDetector.h" + +#include "multitouch/MathUtils.h" + +#include + +SwipeDetector::SwipeDetector( const qreal swipeMaxFingersIntervalPx, + const qreal swipeThresholdPx ) + : _swipeMaxFingersIntervalPx( swipeMaxFingersIntervalPx ) + , _swipeThresholdPx( swipeThresholdPx ) +{} + +void SwipeDetector::initGesture( const QPointF& p0, const QPointF& p1 ) +{ + _canBeSwipe = _checkFingersDistanceForSwipe( p0, p1 ); + _swipeStartPos = MathUtils::getCenter( p0, p1 ); +} + +void SwipeDetector::updateGesture( const QPointF& p0, const QPointF& p1 ) +{ + if( !_canBeSwipe ) + return; + + if( !_checkFingersDistanceForSwipe( p0, p1 )) + { + cancelGesture(); + return; + } + + const auto twoFingersPos = MathUtils::getCenter( p0, p1 ); + const auto twoFingersStartPos = _swipeStartPos; + const auto dist = MathUtils::getDist( twoFingersStartPos, twoFingersPos ); + if( dist > _swipeThresholdPx ) + { + const qreal dx = twoFingersPos.x() - twoFingersStartPos.x(); + const qreal dy = twoFingersPos.y() - twoFingersStartPos.y(); + + if( std::abs( dx ) > std::abs( dy )) + { + // Horizontal swipe + if( dx > 0.0 ) + emit swipeRight(); + else + emit swipeLeft(); + } + else + { + // Vertical swipe + if( dy > 0.0 ) + emit swipeDown(); + else + emit swipeUp(); + } + cancelGesture(); // Only allow one swipe + } +} + +void SwipeDetector::cancelGesture() +{ + _canBeSwipe = false; +} + +bool SwipeDetector::_checkFingersDistanceForSwipe( const QPointF& p0, + const QPointF& p1 ) const +{ + const qreal fingersInterval = MathUtils::getDist( p0, p1 ); + return fingersInterval < _swipeMaxFingersIntervalPx; +} diff --git a/tide/master/multitouch/SwipeDetector.h b/tide/master/multitouch/SwipeDetector.h new file mode 100644 index 00000000..3a4823d0 --- /dev/null +++ b/tide/master/multitouch/SwipeDetector.h @@ -0,0 +1,86 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef SWIPEDETECTOR_H +#define SWIPEDETECTOR_H + +#include "types.h" + +#include + +/** + * Detect two-finger swipe gestures. + */ +class SwipeDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( SwipeDetector ) + +public: + SwipeDetector( qreal swipeMaxFingersIntervalPx, qreal swipeThresholdPx ); + + void initGesture( const QPointF& p0, const QPointF& p1 ); + void updateGesture( const QPointF& p0, const QPointF& p1 ); + void cancelGesture(); + +signals: + /** Two-fingers swipe to the left. */ + void swipeLeft(); + + /** Two-fingers swipe to the right. */ + void swipeRight(); + + /** Two-fingers swipe up. */ + void swipeUp(); + + /** Two-fingers swipe down. */ + void swipeDown(); + +private: + const qreal _swipeMaxFingersIntervalPx; + const qreal _swipeThresholdPx; + + bool _canBeSwipe = false; + QPointF _swipeStartPos; + + bool _checkFingersDistanceForSwipe( const QPointF& p0, + const QPointF& p1 ) const; +}; + +#endif diff --git a/tide/master/multitouch/TapAndHoldDetector.cpp b/tide/master/multitouch/TapAndHoldDetector.cpp new file mode 100644 index 00000000..84e8c94c --- /dev/null +++ b/tide/master/multitouch/TapAndHoldDetector.cpp @@ -0,0 +1,81 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "TapAndHoldDetector.h" + +#include "MathUtils.h" + +TapAndHoldDetector::TapAndHoldDetector( const uint tapAndHoldTimeoutMs, + const qreal moveThresholdPx ) + : _moveThresholdPx( moveThresholdPx ) +{ + _tapAndHoldTimer.setInterval( tapAndHoldTimeoutMs ); + connect( &_tapAndHoldTimer, &QTimer::timeout, [this]() { + emit tapAndHold( MathUtils::computeCenter( _touchStartPos ), + _touchStartPos.size( )); + }); +} + +void TapAndHoldDetector::initGesture( const Positions& positions ) +{ + cancelGesture(); + + if( !positions.empty( )) + { + _touchStartPos = positions; + _tapAndHoldTimer.start(); + } +} + +void TapAndHoldDetector::updateGesture( const Positions& positions ) +{ + if( _tapAndHoldTimer.isActive( )) + _cancelGestureIfMoved( positions ); +} + +void TapAndHoldDetector::cancelGesture() +{ + _tapAndHoldTimer.stop(); +} + +void TapAndHoldDetector::_cancelGestureIfMoved( const Positions& positions ) +{ + if( MathUtils::hasMoved( positions, _touchStartPos, _moveThresholdPx )) + cancelGesture(); +} diff --git a/tide/master/multitouch/TapAndHoldDetector.h b/tide/master/multitouch/TapAndHoldDetector.h new file mode 100644 index 00000000..35eda4c4 --- /dev/null +++ b/tide/master/multitouch/TapAndHoldDetector.h @@ -0,0 +1,75 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef TAPANDHOLDDETECTOR_H +#define TAPANDHOLDDETECTOR_H + +#include "types.h" + +#include +#include + +/** + * Detect TapAndHoldGestures. + */ +class TapAndHoldDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( TapAndHoldDetector ) + +public: + TapAndHoldDetector( uint tapAndHoldTimeoutMs, qreal moveThresholdPx ); + + void initGesture( const Positions& positions ); + void updateGesture( const Positions& positions ); + void cancelGesture(); + +signals: + /** Emitted after a prolonged non-moving touch with one or more fingers. */ + void tapAndHold( QPointF pos, uint numPoints ); + +private: + const qreal _moveThresholdPx; + QTimer _tapAndHoldTimer; + Positions _touchStartPos; + + void _cancelGestureIfMoved( const Positions& positions ); +}; + +#endif diff --git a/tide/master/multitouch/TapDetector.cpp b/tide/master/multitouch/TapDetector.cpp new file mode 100644 index 00000000..dce8d737 --- /dev/null +++ b/tide/master/multitouch/TapDetector.cpp @@ -0,0 +1,88 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "TapDetector.h" + +#include "MathUtils.h" + +TapDetector::TapDetector( const qreal moveThresholdPx ) + : _moveThresholdPx( moveThresholdPx ) +{} + +void TapDetector::initGesture( const Positions& positions ) +{ + // Last finger released + if( positions.empty( )) + { + if( !_tapCanceled ) + emit tap( _tapCenter, _numPoints ); + + _tapCanceled = false; + _touchStartPos.clear(); + _numPoints = 0; + return; + } + + if( _tapCanceled ) + return; + + if( positions.size() > _touchStartPos.size( )) + { + _tapCenter = MathUtils::computeCenter( positions ); + _numPoints = positions.size(); + } + _touchStartPos = positions; +} + +void TapDetector::updateGesture( const Positions& positions ) +{ + if( !_tapCanceled ) + _cancelGestureIfMoved( positions ); +} + +void TapDetector::cancelGesture() +{ + _tapCanceled = true; +} + +void TapDetector::_cancelGestureIfMoved( const Positions& positions ) +{ + if( MathUtils::hasMoved( positions, _touchStartPos, _moveThresholdPx )) + cancelGesture(); +} diff --git a/tide/master/multitouch/TapDetector.h b/tide/master/multitouch/TapDetector.h new file mode 100644 index 00000000..0f4b58fb --- /dev/null +++ b/tide/master/multitouch/TapDetector.h @@ -0,0 +1,76 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef TAPDETECTOR_H +#define TAPDETECTOR_H + +#include "types.h" + +#include + +/** + * Detect double-tap gestures. + */ +class TapDetector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( TapDetector ) + +public: + explicit TapDetector( qreal moveThresholdPx ); + + void initGesture( const Positions& positions ); + void updateGesture( const Positions& positions ); + void cancelGesture(); + +signals: + /** Emitted for an n-finger touch and release in-place (i.e. not a pan). */ + void tap( QPointF pos, uint numPoints ); + +private: + const qreal _moveThresholdPx; + QPointF _tapCenter; + uint _numPoints = 0; + Positions _touchStartPos; + bool _tapCanceled = false; + + void _cancelGestureIfMoved( const Positions& positions ); +}; + +#endif diff --git a/tide/master/resources/MasterContentWindow.qml b/tide/master/resources/MasterContentWindow.qml index 5caedc4d..a45771c5 100644 --- a/tide/master/resources/MasterContentWindow.qml +++ b/tide/master/resources/MasterContentWindow.qml @@ -100,7 +100,7 @@ BaseContentWindow { if(contentwindow.isPanel) // force toggle contentwindow.controlsVisible = !contentwindow.controlsVisible } - onDoubleTap: toggleFocusMode() + onDoubleTap: (numPoints > 1) ? toggleFullscreenMode() : toggleFocusMode() onPanStarted: { if(windowActive && contentwindow.state === ContentWindow.NONE) @@ -150,15 +150,27 @@ BaseContentWindow { contentwindow.delegate.touchEnd(pos) contentwindow.content.captureInteraction = false } + onTouchPointAdded: { + if(contentActive) + contentwindow.delegate.addTouchPoint(id, pos) + } + onTouchPointUpdated: { + if(contentActive) + contentwindow.delegate.updateTouchPoint(id, pos) + } + onTouchPointRemoved: { + if(contentActive) + contentwindow.delegate.removeTouchPoint(id, pos) + } onTap: { if(contentActive) - contentwindow.delegate.tap(pos) + contentwindow.delegate.tap(pos, numPoints) else if(windowActive) toggleControlsVisibility() } onDoubleTap: { if(!contentActive && !contentwindow.fullscreen) - toggleFocusMode() + (numPoints > 1) ? toggleFullscreenMode() : toggleFocusMode() } onTapAndHold: { if(contentActive)