From cf56e7a2bcd3e077a3aba8b1cfd1c489cabb4452 Mon Sep 17 00:00:00 2001 From: Raphael Dumusc Date: Wed, 31 Aug 2016 11:47:45 +0200 Subject: [PATCH 1/2] Intoduce PINCH event to replace WHEEL event [DISCL-386] Minor additions to the simplestreamer demo application. --- apps/DesktopStreamer/Stream.cpp | 3 ++ apps/SimpleStreamer/main.cpp | 66 +++++++++++++++++++++++---------- deflect/Event.h | 7 ++-- deflect/qt/EventReceiver.cpp | 1 + doc/Changelog.md | 3 ++ 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/apps/DesktopStreamer/Stream.cpp b/apps/DesktopStreamer/Stream.cpp index bff09ec..e559892 100644 --- a/apps/DesktopStreamer/Stream.cpp +++ b/apps/DesktopStreamer/Stream.cpp @@ -110,6 +110,9 @@ bool processEvents( const bool interact ) case deflect::Event::EVT_KEY_PRESS: case deflect::Event::EVT_KEY_RELEASE: case deflect::Event::EVT_VIEW_SIZE_CHANGED: + case deflect::Event::EVT_TAP_AND_HOLD: + case deflect::Event::EVT_PAN: + case deflect::Event::EVT_PINCH: default: break; } diff --git a/apps/SimpleStreamer/main.cpp b/apps/SimpleStreamer/main.cpp index 323d579..78daa5a 100644 --- a/apps/SimpleStreamer/main.cpp +++ b/apps/SimpleStreamer/main.cpp @@ -140,7 +140,7 @@ void initGLWindow( int argc, char** argv ) glClearColor( 0.5, 0.5, 0.5, 1.0 ); glEnable( GL_DEPTH_TEST ); - glEnable( GL_LIGHTING) ; + glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); } @@ -180,6 +180,8 @@ void display() // angles of camera rotation and zoom factor static float angleX = 0.f; static float angleY = 0.f; + static float offsetX = 0.f; + static float offsetY = 0.f; static float zoom = 1.f; // Render the teapot @@ -193,8 +195,10 @@ void display() glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); - glRotatef( angleX, 0.0, 1.0, 0.0 ); - glRotatef( angleY, -1.0, 0.0, 0.0 ); + glTranslatef( offsetX, -offsetY, 0.f ); + + glRotatef( angleX, 0.f, 1.f, 0.f ); + glRotatef( angleY, -1.f, 0.f, 0.f ); glScalef( zoom, zoom, zoom ); glutSolidTeapot( 1.f ); @@ -237,25 +241,49 @@ void display() { const deflect::Event& event = deflectStream->getEvent(); - if( event.type == deflect::Event::EVT_CLOSE ) + switch( event.type ) { + case deflect::Event::EVT_CLOSE: std::cout << "Received close..." << std::endl; exit( 0 ); - } - - const float newMouseX = event.mouseX; - const float newMouseY = event.mouseY; - - if( event.mouseLeft ) - { - angleX += (newMouseX - mouseX) * 360.f; - angleY += (newMouseY - mouseY) * 360.f; - } - else if( event.mouseRight ) - zoom += (newMouseY - mouseY); - - mouseX = newMouseX; - mouseY = newMouseY; + case deflect::Event::EVT_PINCH: + zoom += std::copysign( std::sqrt( event.dx * event.dx + + event.dy * event.dy ), + event.dx + event.dy ); + break; + case deflect::Event::EVT_PRESS: + mouseX = event.mouseX; + mouseY = event.mouseY; + break; + case deflect::Event::EVT_MOVE: + case deflect::Event::EVT_RELEASE: + if( event.mouseLeft ) + { + angleX += (event.mouseX - mouseX) * 360.f; + angleY += (event.mouseY - mouseY) * 360.f; + } + mouseX = event.mouseX; + mouseY = event.mouseY; + break; + case deflect::Event::EVT_PAN: + offsetX += event.dx; + offsetY += event.dy; + mouseX = event.mouseX; + mouseY = event.mouseY; + break; + case deflect::Event::EVT_KEY_PRESS: + if( event.key == ' ' ) + { + angleX = 0.f; + angleY = 0.f; + offsetX = 0.f; + offsetY = 0.f; + zoom = 1.f; + } + break; + default: + break; + }; } } else diff --git a/deflect/Event.h b/deflect/Event.h index d7830b4..fcf606b 100644 --- a/deflect/Event.h +++ b/deflect/Event.h @@ -86,7 +86,8 @@ struct Event EVT_KEY_RELEASE, EVT_VIEW_SIZE_CHANGED, EVT_TAP_AND_HOLD, - EVT_PAN + EVT_PAN, + EVT_PINCH }; /** The type of event */ @@ -96,8 +97,8 @@ struct Event //@{ double mouseX; /**< Normalized X mouse/touch position relative to the window */ double mouseY; /**< Normalized Y mouse/touch position relative to the window */ - double dx; /**< Normalized horizontal delta for scroll events / delta in pixels for wheel events. */ - double dy; /**< Normalized vertical delta for scroll events / delta in pixels for wheel events. */ + double dx; /**< Normalized horizontal delta for pan/pinch events / delta in pixels for wheel events. */ + double dy; /**< Normalized vertical delta for pan/pinch events / delta in pixels for wheel events. */ bool mouseLeft; /**< The state of the left mouse button (pressed=true) */ bool mouseRight; /**< The state of the right mouse button (pressed=true) */ bool mouseMiddle; /**< The state of the middle mouse button (pressed=true) */ diff --git a/deflect/qt/EventReceiver.cpp b/deflect/qt/EventReceiver.cpp index c57a887..7b41adb 100644 --- a/deflect/qt/EventReceiver.cpp +++ b/deflect/qt/EventReceiver.cpp @@ -119,6 +119,7 @@ void EventReceiver::_onEvent( int socket ) break; case Event::EVT_CLICK: case Event::EVT_DOUBLECLICK: + case Event::EVT_PINCH: default: break; } diff --git a/doc/Changelog.md b/doc/Changelog.md index 7a1ceb2..350f4dc 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -5,6 +5,9 @@ Changelog {#Changelog} ### 0.12.0 (git master) +* [128](https://github.com/BlueBrain/Deflect/pull/128) + The *wheel* event has been replaced by a more aptly named *pinch* event. + Minor additions to the simplestreamer demo application. * [126](https://github.com/BlueBrain/Deflect/pull/126) DesktopStreamer: The list of default hosts can be configured using the CMake variable DEFLECT_DESKTOPSTREAMER_HOSTS. From 4c4ba0ec1f52a4939329988714e1f4ff3987de21 Mon Sep 17 00:00:00 2001 From: Raphael Dumusc Date: Tue, 13 Sep 2016 14:55:59 +0200 Subject: [PATCH 2/2] Transmit full touch points events to clients [DISCL-386] DeflectQt: New TouchInjector class for touch events (moved from Tide) --- deflect/Event.h | 61 ++++++++++-- deflect/qt/CMakeLists.txt | 18 +++- deflect/qt/EventReceiver.cpp | 30 ++++-- deflect/qt/EventReceiver.h | 21 ++-- deflect/qt/QmlStreamerImpl.cpp | 120 ++++++++++------------- deflect/qt/QmlStreamerImpl.h | 23 +++-- deflect/qt/TouchInjector.cpp | 173 +++++++++++++++++++++++++++++++++ deflect/qt/TouchInjector.h | 115 ++++++++++++++++++++++ doc/Changelog.md | 13 ++- tests/cpp/ServerTests.cpp | 2 +- 10 files changed, 472 insertions(+), 104 deletions(-) create mode 100644 deflect/qt/TouchInjector.cpp create mode 100644 deflect/qt/TouchInjector.h diff --git a/deflect/Event.h b/deflect/Event.h index fcf606b..6b6b20d 100644 --- a/deflect/Event.h +++ b/deflect/Event.h @@ -1,6 +1,6 @@ /*********************************************************************/ -/* Copyright (c) 2013, EPFL/Blue Brain Project */ -/* Raphael Dumusc */ +/* Copyright (c) 2013-2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -60,6 +60,52 @@ namespace deflect * Typically used to forward user inputs from a window to classes that * generate content for it. * + * Events can be divided into several categories, listed below. Applications can + * decide to handle all the events or limit themselves to any subset that is + * appropriate. + * + * For example, an application which supports multiple touch points natively may + * want to use unprocessed touch points, while still assigning some specific + * actions to high-level gestures such as swipe events. + * + * For a concrete code example of basic event processing, users can refer to + * the simplestreamer demo application. + * + * Stream events + * - EVT_VIEW_SIZE_CHANGED < remote stream window resized by user + * - EVT_CLOSE < remote stream window closed by user + * + * Basic interaction + * - EVT_PRESS < touch/mouse button press (single touch point) + * - EVT_MOVE < touch point/mouse move (single touch point) + * - EVT_RELEASE < touch/mouse button release (single touch point) + * - EVT_WHEEL < mouse wheel event (no longer sent by Tide >= 1.2, replaced by EVT_PINCH) + * + * Basic gestures + * - EVT_CLICK < tap/click with one or more fingers/buttons (key field contains number of points) + * - EVT_DOUBLECLICK < double tap/click with one or more fingers/button (key field contains number of points) + * - EVT_TAP_AND_HOLD < tap and hold with one or more fingers/buttons (key field contains number of points) + * + * Advanced gestures + * - EVT_PAN < pan with two or more fingers (key field contains number of points) + * - EVT_PINCH < pinch with two finger + * - EVT_SWIPE_LEFT < swipe left with two fingers + * - EVT_SWIPE_RIGHT < swipe right with two fingers + * - EVT_SWIPE_UP < swipe up with two fingers + * - EVT_SWIPE_DOWN < swipe down with two fingers + * + * Unprocessed touch points + * - EVT_TOUCH_ADD < touch point added (key field contains point id) + * - EVT_TOUCH_UPDATE < touch point moved (key field contains point id) + * - EVT_TOUCH_REMOVE < touch point removed (key field contains point id) + * + * Keybord events + * - EVT_KEY_PRESS < key pressed + * - EVT_KEY_RELEASE < key released + * + * Other: + * - EVT_NONE < normally unused + * * @version 1.0 */ struct Event @@ -87,7 +133,10 @@ struct Event EVT_VIEW_SIZE_CHANGED, EVT_TAP_AND_HOLD, EVT_PAN, - EVT_PINCH + EVT_PINCH, + EVT_TOUCH_ADD, + EVT_TOUCH_UPDATE, + EVT_TOUCH_REMOVE }; /** The type of event */ @@ -97,8 +146,8 @@ struct Event //@{ double mouseX; /**< Normalized X mouse/touch position relative to the window */ double mouseY; /**< Normalized Y mouse/touch position relative to the window */ - double dx; /**< Normalized horizontal delta for pan/pinch events / delta in pixels for wheel events. */ - double dy; /**< Normalized vertical delta for pan/pinch events / delta in pixels for wheel events. */ + double dx; /**< Normalized horizontal delta for pan/pinch events / delta in pixels for wheel events */ + double dy; /**< Normalized vertical delta for pan/pinch events / delta in pixels for wheel events */ bool mouseLeft; /**< The state of the left mouse button (pressed=true) */ bool mouseRight; /**< The state of the right mouse button (pressed=true) */ bool mouseMiddle; /**< The state of the middle mouse button (pressed=true) */ @@ -106,7 +155,7 @@ struct Event /** @name Keyboard events */ //@{ - int key; /**< The key code, see QKeyEvent::key() */ + int key; /**< The key code, see QKeyEvent::key() / number of fingers for gestures / point id for touch events */ int modifiers; /**< The keyboard modifiers, see QKeyEvent::modifiers() */ char text[UNICODE_TEXT_SIZE]; /**< Carries unicode for key, see QKeyEvent::text() */ //@} diff --git a/deflect/qt/CMakeLists.txt b/deflect/qt/CMakeLists.txt index dfb921d..95fdfa0 100644 --- a/deflect/qt/CMakeLists.txt +++ b/deflect/qt/CMakeLists.txt @@ -3,9 +3,21 @@ # Daniel Nachbaur # Raphael Dumusc -set(DEFLECTQT_HEADERS EventReceiver.h QmlGestures.h QmlStreamerImpl.h) -set(DEFLECTQT_PUBLIC_HEADERS QmlStreamer.h) -set(DEFLECTQT_SOURCES EventReceiver.cpp QmlStreamer.cpp QmlStreamerImpl.cpp) +set(DEFLECTQT_HEADERS + EventReceiver.h + QmlGestures.h + QmlStreamerImpl.h +) +set(DEFLECTQT_PUBLIC_HEADERS + QmlStreamer.h + TouchInjector.h +) +set(DEFLECTQT_SOURCES + EventReceiver.cpp + QmlStreamer.cpp + QmlStreamerImpl.cpp + TouchInjector.cpp +) set(DEFLECTQT_LINK_LIBRARIES PUBLIC Deflect Qt5::Quick PRIVATE Qt5::Qml) set(DEFLECTQT_INCLUDE_NAME deflect/qt) diff --git a/deflect/qt/EventReceiver.cpp b/deflect/qt/EventReceiver.cpp index 7b41adb..5e3e251 100644 --- a/deflect/qt/EventReceiver.cpp +++ b/deflect/qt/EventReceiver.cpp @@ -1,6 +1,7 @@ /*********************************************************************/ -/* Copyright (c) 2015, EPFL/Blue Brain Project */ +/* Copyright (c) 2015-2016, EPFL/Blue Brain Project */ /* Daniel.Nachbaur */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -62,8 +63,11 @@ EventReceiver::EventReceiver( Stream& stream ) _timer->start( 1 ); } -EventReceiver::~EventReceiver() +EventReceiver::~EventReceiver() {} + +inline QPointF _pos( const Event& deflectEvent ) { + return QPointF{ deflectEvent.mouseX, deflectEvent.mouseY }; } void EventReceiver::_onEvent( int socket ) @@ -82,20 +86,16 @@ void EventReceiver::_onEvent( int socket ) QCoreApplication::quit(); break; case Event::EVT_PRESS: - emit pressed( deflectEvent.mouseX, deflectEvent.mouseY ); + emit pressed( _pos( deflectEvent )); break; case Event::EVT_RELEASE: - emit released( deflectEvent.mouseX, deflectEvent.mouseY ); + emit released( _pos( deflectEvent )); break; case Event::EVT_MOVE: - emit moved( deflectEvent.mouseX, deflectEvent.mouseY ); + emit moved( _pos( deflectEvent )); break; case Event::EVT_VIEW_SIZE_CHANGED: - emit resized( deflectEvent.dx, deflectEvent.dy ); - break; - case Event::EVT_WHEEL: - emit wheeled( deflectEvent.mouseX, deflectEvent.mouseY, - deflectEvent.dy ); + emit resized( QSize{ int(deflectEvent.dx), int(deflectEvent.dy) } ); break; case Event::EVT_SWIPE_LEFT: emit swipeLeft(); @@ -117,9 +117,19 @@ void EventReceiver::_onEvent( int socket ) emit keyRelease( deflectEvent.key, deflectEvent.modifiers, QString::fromStdString( deflectEvent.text )); break; + case Event::EVT_TOUCH_ADD: + emit touchPointAdded( deflectEvent.key, _pos( deflectEvent )); + break; + case Event::EVT_TOUCH_UPDATE: + emit touchPointUpdated( deflectEvent.key, _pos( deflectEvent )); + break; + case Event::EVT_TOUCH_REMOVE: + emit touchPointRemoved( deflectEvent.key, _pos( deflectEvent )); + break; case Event::EVT_CLICK: case Event::EVT_DOUBLECLICK: case Event::EVT_PINCH: + case Event::EVT_WHEEL: default: break; } diff --git a/deflect/qt/EventReceiver.h b/deflect/qt/EventReceiver.h index cacac77..90b99fd 100644 --- a/deflect/qt/EventReceiver.h +++ b/deflect/qt/EventReceiver.h @@ -1,6 +1,7 @@ /*********************************************************************/ -/* Copyright (c) 2015, EPFL/Blue Brain Project */ +/* Copyright (c) 2015-2016, EPFL/Blue Brain Project */ /* Daniel.Nachbaur */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -41,6 +42,8 @@ #define EVENTRECEIVER_H #include +#include +#include #include #include @@ -60,18 +63,24 @@ class EventReceiver : public QObject ~EventReceiver(); signals: - void pressed( double x, double y ); - void released( double x, double y ); - void moved( double x, double y ); - void resized( double x, double y ); - void wheeled( double x, double y, double dy ); + void pressed( QPointF position ); + void released( QPointF position ); + void moved( QPointF position ); + + void resized( QSize newSize ); + void keyPress( int key, int modifiers, QString text ); void keyRelease( int key, int modifiers, QString text ); + void swipeLeft(); void swipeRight(); void swipeUp(); void swipeDown(); + void touchPointAdded( int id, QPointF position ); + void touchPointUpdated( int id, QPointF position ); + void touchPointRemoved( int id, QPointF position ); + private slots: void _onEvent( int socket ); diff --git a/deflect/qt/QmlStreamerImpl.cpp b/deflect/qt/QmlStreamerImpl.cpp index 375e0d9..2ac712a 100644 --- a/deflect/qt/QmlStreamerImpl.cpp +++ b/deflect/qt/QmlStreamerImpl.cpp @@ -1,6 +1,7 @@ /*********************************************************************/ -/* Copyright (c) 2015, EPFL/Blue Brain Project */ +/* Copyright (c) 2015-2016, EPFL/Blue Brain Project */ /* Daniel.Nachbaur */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -40,6 +41,7 @@ #include "QmlStreamerImpl.h" #include "EventReceiver.h" #include "QmlGestures.h" +#include "TouchInjector.h" #include #include @@ -108,17 +110,21 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, , _stream( nullptr ) , _eventHandler( nullptr ) , _qmlGestures( new QmlGestures ) + , _touchInjector( new TouchInjector( *_quickWindow, + std::bind( &Impl::_mapToScene, this, + std::placeholders::_1 ))) , _streaming( false ) , _streamHost( streamHost ) , _streamId( streamId ) , _mouseMode( false ) { + _mouseModeTimer.setSingleShot( true ); + _mouseModeTimer.setInterval( TOUCH_TAPANDHOLD_TIMEOUT_MS ); + // Expose stream gestures to qml objects _qmlEngine->rootContext()->setContextProperty( GESTURES_CONTEXT_PROPERTY, _qmlGestures ); - _device.setType( QTouchDevice::TouchScreen ); - setSurfaceType( QSurface::OpenGLSurface ); // Qt Quick may need a depth and stencil buffer @@ -282,69 +288,35 @@ void QmlStreamer::Impl::_requestRender() _renderTimer = startTimer( 5, Qt::PreciseTimer ); } -void QmlStreamer::Impl::_onPressed( double x_, double y_ ) +void QmlStreamer::Impl::_onPressed( const QPointF pos ) { - auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); - touchPoint.setState( Qt::TouchPointPressed ); - - _startMouseModeSwitchDetection( touchPoint.pos( )); - - auto e = new QTouchEvent( QEvent::TouchBegin, &_device, Qt::NoModifier, - Qt::TouchPointPressed, { touchPoint } ); - QCoreApplication::postEvent( _quickWindow, e ); + _startMouseModeSwitchDetection( _mapToScene( pos )); } -void QmlStreamer::Impl::_onMoved( double x_, double y_ ) +void QmlStreamer::Impl::_onMoved( const QPointF pos ) { if( _mouseMode ) { - const QPoint pos( x_ * width(), y_ * height( )); - _sendMouseEvent( QEvent::MouseMove, pos ); + _sendMouseEvent( QEvent::MouseMove, _mapToScene( pos )); return; } - - auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); - touchPoint.setState( Qt::TouchPointMoved ); - if( _mouseModeTimer.isActive( )) - _touchCurrentPos = touchPoint.pos(); - - auto e = new QTouchEvent( QEvent::TouchUpdate, &_device, Qt::NoModifier, - Qt::TouchPointMoved, { touchPoint } ); - QCoreApplication::postEvent( _quickWindow, e ); + _touchCurrentPos = _mapToScene( pos ); } -void QmlStreamer::Impl::_onReleased( double x_, double y_ ) +void QmlStreamer::Impl::_onReleased( const QPointF pos ) { _mouseModeTimer.stop(); if( _mouseMode ) { - const QPoint pos( x_ * width(), y_ * height( )); - _sendMouseEvent( QEvent::MouseButtonRelease, pos ); - _mouseMode = false; - return; + _sendMouseEvent( QEvent::MouseButtonRelease, _mapToScene( pos )); + _switchBackToTouchMode(); } - - auto touchPoint = _makeTouchPoint( 0, { x_, y_ }); - touchPoint.setState( Qt::TouchPointReleased ); - - auto e = new QTouchEvent( QEvent::TouchEnd, &_device, Qt::NoModifier, - Qt::TouchPointReleased, { touchPoint } ); - QCoreApplication::postEvent( _quickWindow, e ); } -void QmlStreamer::Impl::_onWheeled( double x_, double y_, double deltaY ) +void QmlStreamer::Impl::_onResized( const QSize newSize ) { - const QPoint point( x_ * width(), y_ * height( )); - QWheelEvent* e = new QWheelEvent( point, deltaY, Qt::NoButton, - Qt::NoModifier, Qt::Vertical ); - QCoreApplication::postEvent( _quickWindow, e ); -} - -void QmlStreamer::Impl::_onResized( double x_, double y_ ) -{ - QResizeEvent* resizeEvent_ = new QResizeEvent( QSize( x_, y_ ), size( )); - QCoreApplication::postEvent( this, resizeEvent_ ); + QCoreApplication::postEvent( this, new QResizeEvent( newSize, size( ))); } void QmlStreamer::Impl::_onKeyPress( int key, int modifiers, QString text ) @@ -475,16 +447,23 @@ bool QmlStreamer::Impl::_setupDeflectStream() _streaming = true; _eventHandler = new EventReceiver( *_stream ); + + // inject touch events + _connectTouchInjector(); + + // used for mouse mode switch for Qml WebengineView connect( _eventHandler, &EventReceiver::pressed, this, &QmlStreamer::Impl::_onPressed ); connect( _eventHandler, &EventReceiver::released, this, &QmlStreamer::Impl::_onReleased ); connect( _eventHandler, &EventReceiver::moved, this, &QmlStreamer::Impl::_onMoved ); + + // handle view resize connect( _eventHandler, &EventReceiver::resized, this, &QmlStreamer::Impl::_onResized ); - connect( _eventHandler, &EventReceiver::wheeled, - this, &QmlStreamer::Impl::_onWheeled ); + + // inject key events connect( _eventHandler, &EventReceiver::keyPress, this, &QmlStreamer::Impl::_onKeyPress ); connect( _eventHandler, &EventReceiver::keyRelease, @@ -517,12 +496,22 @@ void QmlStreamer::Impl::_updateSizes( const QSize& size_ ) } } +void QmlStreamer::Impl::_connectTouchInjector() +{ + connect( _eventHandler, &EventReceiver::touchPointAdded, + _touchInjector, &TouchInjector::addTouchPoint ); + connect( _eventHandler, &EventReceiver::touchPointUpdated, + _touchInjector, &TouchInjector::updateTouchPoint ); + connect( _eventHandler, &EventReceiver::touchPointRemoved, + _touchInjector, &TouchInjector::removeTouchPoint ); +} + void QmlStreamer::Impl::_startMouseModeSwitchDetection( const QPointF& pos ) { - auto item = _rootItem->childAt( pos.x(), pos.y()); + const auto item = _rootItem->childAt( pos.x(), pos.y( )); if( item->objectName() == WEBENGINEVIEW_OBJECT_NAME ) { - _mouseModeTimer.start( TOUCH_TAPANDHOLD_TIMEOUT_MS ); + _mouseModeTimer.start(); _touchStartPos = pos; _touchCurrentPos = pos; } @@ -536,12 +525,23 @@ bool QmlStreamer::Impl::_touchIsTapAndHold() void QmlStreamer::Impl::_switchFromTouchToMouseMode() { - _onReleased( _touchCurrentPos.x() / width(), - _touchCurrentPos.y() / height( )); + _eventHandler->disconnect( _touchInjector ); + _touchInjector->removeAllTouchPoints(); _mouseMode = true; _sendMouseEvent( QEvent::MouseButtonPress, _touchCurrentPos ); } +void QmlStreamer::Impl::_switchBackToTouchMode() +{ + _mouseMode = false; + _connectTouchInjector(); +} + +QPointF QmlStreamer::Impl::_mapToScene( const QPointF& normalizedPos ) const +{ + return QPointF{ normalizedPos.x() * width(), normalizedPos.y() * height() }; +} + void QmlStreamer::Impl::_sendMouseEvent( const QEvent::Type eventType, const QPointF& pos ) { @@ -550,20 +550,6 @@ void QmlStreamer::Impl::_sendMouseEvent( const QEvent::Type eventType, QCoreApplication::postEvent( _quickWindow, e ); } -QTouchEvent::TouchPoint -QmlStreamer::Impl::_makeTouchPoint( const int id, const QPointF& normPos ) const -{ - const QPoint pos( normPos.x() * width(), normPos.y() * height( )); - - QTouchEvent::TouchPoint touchPoint( id ); - touchPoint.setPressure( 1.0 ); - touchPoint.setPos( pos ); - touchPoint.setScreenPos( pos ); - touchPoint.setNormalizedPos( normPos ); - - return touchPoint; -} - void QmlStreamer::Impl::resizeEvent( QResizeEvent* e ) { _updateSizes( e->size( )); diff --git a/deflect/qt/QmlStreamerImpl.h b/deflect/qt/QmlStreamerImpl.h index 037797c..4df7604 100644 --- a/deflect/qt/QmlStreamerImpl.h +++ b/deflect/qt/QmlStreamerImpl.h @@ -1,6 +1,7 @@ /*********************************************************************/ -/* Copyright (c) 2015, EPFL/Blue Brain Project */ +/* Copyright (c) 2015-2016, EPFL/Blue Brain Project */ /* Daniel.Nachbaur */ +/* Raphael Dumusc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or */ @@ -65,6 +66,7 @@ namespace qt class EventReceiver; class QmlGestures; +class TouchInjector; class QmlStreamer::Impl : public QWindow { @@ -94,11 +96,12 @@ private slots: void _render(); void _requestRender(); - void _onPressed( double, double ); - void _onReleased( double, double ); - void _onMoved( double, double ); - void _onResized( double, double ); - void _onWheeled( double, double, double ); + void _onPressed( QPointF position ); + void _onReleased( QPointF position ); + void _onMoved( QPointF position ); + + void _onResized( QSize newSize ); + void _onKeyPress( int key, int modifiers, QString text ); void _onKeyRelease( int key, int modifiers, QString text ); @@ -111,13 +114,16 @@ private slots: std::string _getDeflectStreamIdentifier() const; bool _setupDeflectStream(); void _updateSizes( const QSize& size ); - QTouchEvent::TouchPoint _makeTouchPoint( int id, const QPointF& pos ) const; + void _connectTouchInjector(); void _startMouseModeSwitchDetection( const QPointF& pos ); bool _touchIsTapAndHold(); void _switchFromTouchToMouseMode(); + void _switchBackToTouchMode(); void _sendMouseEvent( QEvent::Type eventType, const QPointF& pos ); + QPointF _mapToScene( const QPointF& normalizedPos ) const; + QOpenGLContext* _context; QOffscreenSurface* _offscreenSurface; QQuickRenderControl* _renderControl; @@ -133,13 +139,12 @@ private slots: Stream* _stream; EventReceiver* _eventHandler; QmlGestures* _qmlGestures; + TouchInjector* _touchInjector; bool _streaming; const std::string _streamHost; const std::string _streamId; SizeHints _sizeHints; - QTouchDevice _device; - QTimer _mouseModeTimer; bool _mouseMode; QPointF _touchStartPos; diff --git a/deflect/qt/TouchInjector.cpp b/deflect/qt/TouchInjector.cpp new file mode 100644 index 0000000..472ad05 --- /dev/null +++ b/deflect/qt/TouchInjector.cpp @@ -0,0 +1,173 @@ +/*********************************************************************/ +/* 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 The University of Texas at Austin. */ +/*********************************************************************/ + +#include "TouchInjector.h" + +#include + +namespace deflect +{ +namespace qt +{ + +TouchInjector::TouchInjector( QObject& target, MapToSceneFunc mapFunc ) + : _target( target ) + , _mapToSceneFunction( mapFunc ) +{ + _device.setType( QTouchDevice::TouchScreen ); +} + +void TouchInjector::addTouchPoint( const int id, const QPointF position ) +{ + if( _touchPointMap.contains( id )) + return; + + _handleEvent( id, position, QEvent::TouchBegin ); +} + +void TouchInjector::updateTouchPoint( const int id, const QPointF position ) +{ + if( !_touchPointMap.contains( id )) + return; + + _handleEvent( id, position, QEvent::TouchUpdate ); +} + +void TouchInjector::removeTouchPoint( const int id, const QPointF position ) +{ + if( !_touchPointMap.contains( id )) + return; + + _handleEvent( id, position, QEvent::TouchEnd ); +} + +void TouchInjector::removeAllTouchPoints() +{ + const auto ids = _touchPointMap.keys(); + for( auto id : ids ) + removeTouchPoint( id, _touchPointMap[id].normalizedPos( )); +} + +void _fillTouchBegin( QTouchEvent::TouchPoint& touchPoint ) +{ + 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 _fill( QTouchEvent::TouchPoint& touchPoint, + const QTouchEvent::TouchPoint& prevPoint ) +{ + touchPoint.setStartPos( prevPoint.startPos( )); + touchPoint.setStartScreenPos( prevPoint.startScreenPos( )); + touchPoint.setStartNormalizedPos( prevPoint.startNormalizedPos( )); + + touchPoint.setLastPos( prevPoint.pos( )); + touchPoint.setLastScreenPos( prevPoint.screenPos( )); + touchPoint.setLastNormalizedPos( prevPoint.normalizedPos( )); +} + +Qt::TouchPointStates _getTouchPointState( const QEvent::Type eventType ) +{ + switch( eventType ) + { + case QEvent::TouchBegin: + return Qt::TouchPointPressed; + case QEvent::TouchUpdate: + return Qt::TouchPointMoved; + case QEvent::TouchEnd: + return Qt::TouchPointReleased; + default: + throw std::runtime_error( + QString( "unexpected QEvent::Type (%1)" ).arg( + int( eventType )).toStdString( )); + } +} + +void TouchInjector::_handleEvent( const int id, const QPointF& normalizedPos, + const QEvent::Type eventType ) +{ + const QPointF screenPos = _mapToSceneFunction( normalizedPos ); + + QTouchEvent::TouchPoint touchPoint( id ); + touchPoint.setPressure( 1.0 ); + touchPoint.setPos( screenPos ); + touchPoint.setScreenPos( screenPos ); + touchPoint.setNormalizedPos( normalizedPos ); + + switch( eventType ) + { + case QEvent::TouchBegin: + _fillTouchBegin( touchPoint ); + break; + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + _fill( touchPoint, _touchPointMap.value( id )); + break; + default: + return; + } + + const auto touchPointState = _getTouchPointState( eventType ); + touchPoint.setState( touchPointState ); + _touchPointMap.insert( id, touchPoint ); + + QEvent::Type touchEventType = eventType; + if( touchEventType == QEvent::TouchEnd ) + touchEventType = _touchPointMap.isEmpty() ? QEvent::TouchEnd + : QEvent::TouchUpdate; + + QEvent* touchEvent = new QTouchEvent( touchEventType, &_device, + Qt::NoModifier, touchPointState, + _touchPointMap.values( )); + QCoreApplication::postEvent( &_target, touchEvent ); + + // Prepare state for next call to handle event + if( eventType == QEvent::TouchEnd ) + _touchPointMap.remove( id ); + else + _touchPointMap[id].setState( Qt::TouchPointStationary ); +} + +} +} diff --git a/deflect/qt/TouchInjector.h b/deflect/qt/TouchInjector.h new file mode 100644 index 0000000..9ffad0f --- /dev/null +++ b/deflect/qt/TouchInjector.h @@ -0,0 +1,115 @@ +/*********************************************************************/ +/* 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 The University of Texas at Austin. */ +/*********************************************************************/ + +#ifndef TOUCHINJECTOR_H +#define TOUCHINJECTOR_H + +#include +#include + +#include + +namespace deflect +{ +namespace qt +{ + +/** + * Inject complete QTouchEvent from separate touch added/updated/removed events. + */ +class TouchInjector : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY( TouchInjector ) + +public: + /** Function to map normalized coordinates to scene / window coordinates. */ + using MapToSceneFunc = std::function; + + /** + * Constructor. + * @param target the target QObject that should receive the touch events + * @param mapFunc the function to generate scene / window coordinates + */ + TouchInjector( QObject& target, MapToSceneFunc mapFunc ); + + /** + * Insert a new touch point. + * + * Does nothing if a point with the same id already exists. + * @param id the identifier for the point + * @param position the initial normalized position of the point + */ + void addTouchPoint( int id, QPointF position ); + + /** + * Update an existing touch point. + * + * Does nothing if the given point has not been added or was removed. + * @param id the identifier for the point + * @param position the new normalized position of the point + */ + void updateTouchPoint( int id, QPointF position ); + + /** + * Remove an existing touch point. + * + * Does nothing if the given point has not been added or was removed. + * @param id the identifier for the point + * @param position the new normalized position of the point + */ + void removeTouchPoint( int id, QPointF position ); + + /** Remove all touch points. */ + void removeAllTouchPoints(); + +private: + void _handleEvent( const int id, const QPointF& normalizedPos, + const QEvent::Type eventType ); + + QObject& _target; + MapToSceneFunc _mapToSceneFunction; + QTouchDevice _device; + QMap _touchPointMap; +}; + +} +} + +#endif diff --git a/doc/Changelog.md b/doc/Changelog.md index 350f4dc..fd43e82 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -6,8 +6,17 @@ Changelog {#Changelog} ### 0.12.0 (git master) * [128](https://github.com/BlueBrain/Deflect/pull/128) - The *wheel* event has been replaced by a more aptly named *pinch* event. - Minor additions to the simplestreamer demo application. + New events for transmitting all touch points in addition to existing gestures: + - Gives the ability to handle more than one touch point in applications (e.g. + draw with multiple fingers on a whiteboard). + - Simplifies integration for applications that natively handle multitouch + events, like Qml-based ones. + - DeflectQt uses a new TouchInjector class for sending touch events to Qt + (moved from Tide). + In addition: + - A new *pinch* event was added. Clients of Tide >= 1.2 have to adapt their + code to use it instead of *wheel* events, which are no longer sent. + - Minor additions to the simplestreamer demo application. * [126](https://github.com/BlueBrain/Deflect/pull/126) DesktopStreamer: The list of default hosts can be configured using the CMake variable DEFLECT_DESKTOPSTREAMER_HOSTS. diff --git a/tests/cpp/ServerTests.cpp b/tests/cpp/ServerTests.cpp index 7bdbe5d..01e9be7 100644 --- a/tests/cpp/ServerTests.cpp +++ b/tests/cpp/ServerTests.cpp @@ -213,7 +213,7 @@ BOOST_AUTO_TEST_CASE( testDataReceivedByServer ) for( size_t i = 0; i < 20; ++i ) { mutex.lock(); - received.wait( &mutex, 100 /*ms*/ ); + received.wait( &mutex, 500 /*ms*/ ); if( receivedState ) { BOOST_CHECK_EQUAL( streamId.toStdString(),