changeset 457:3b4df9925db6 am-touch-events

added support for 'touch' in mouse trackers. This is still a bit hacky and we need to refactor it to make it clean. Thanks to that, Pan and zoom are available together with 2 touches
author Alain Mazy <alain@mazy.be>
date Thu, 24 Jan 2019 16:42:27 +0100
parents b70fcc134ba4
children e74f9271d653
files Applications/Qt/QCairoWidget.cpp Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp Applications/Samples/SimpleViewer/MainWidgetInteractor.h Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp Applications/Samples/SimpleViewer/ThumbnailInteractor.h Applications/Samples/SimpleViewerApplicationSingleFile.h Applications/Samples/SingleFrameApplication.h Applications/Samples/SingleFrameEditorApplication.h Framework/Layers/CircleMeasureTracker.cpp Framework/Layers/CircleMeasureTracker.h Framework/Layers/LineMeasureTracker.cpp Framework/Layers/LineMeasureTracker.h Framework/Radiography/RadiographyLayerCropTracker.cpp Framework/Radiography/RadiographyLayerCropTracker.h Framework/Radiography/RadiographyLayerMoveTracker.cpp Framework/Radiography/RadiographyLayerMoveTracker.h Framework/Radiography/RadiographyLayerResizeTracker.cpp Framework/Radiography/RadiographyLayerResizeTracker.h Framework/Radiography/RadiographyLayerRotateTracker.cpp Framework/Radiography/RadiographyLayerRotateTracker.h Framework/Radiography/RadiographyWindowingTracker.cpp Framework/Radiography/RadiographyWindowingTracker.h Framework/Toolbox/ViewportGeometry.cpp Framework/Toolbox/ViewportGeometry.h Framework/Viewport/IMouseTracker.h Framework/Viewport/IViewport.h Framework/Viewport/WidgetViewport.cpp Framework/Viewport/WidgetViewport.h Framework/Widgets/EmptyWidget.h Framework/Widgets/IWidget.h Framework/Widgets/IWorldSceneInteractor.h Framework/Widgets/IWorldSceneMouseTracker.h Framework/Widgets/LayoutWidget.cpp Framework/Widgets/LayoutWidget.h Framework/Widgets/PanMouseTracker.cpp Framework/Widgets/PanMouseTracker.h Framework/Widgets/PanZoomMouseTracker.cpp Framework/Widgets/PanZoomMouseTracker.h Framework/Widgets/TestCairoWidget.cpp Framework/Widgets/TestCairoWidget.h Framework/Widgets/TestWorldSceneWidget.cpp Framework/Widgets/WorldSceneWidget.cpp Framework/Widgets/WorldSceneWidget.h Framework/Widgets/ZoomMouseTracker.cpp Framework/Widgets/ZoomMouseTracker.h Platforms/Wasm/Defaults.cpp Platforms/Wasm/wasm-viewport.ts Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 48 files changed, 572 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Qt/QCairoWidget.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Qt/QCairoWidget.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -125,7 +125,7 @@
 
   {
     OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_);
-    locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers);
+    locker.GetCentralViewport().MouseDown(button, event->pos().x(), event->pos().y(), stoneModifiers, std::vector<OrthancStone::Touch>());
   }
 }
 
@@ -140,7 +140,7 @@
 void QCairoWidget::mouseMoveEvent(QMouseEvent* event)
 {
   OrthancStone::NativeStoneApplicationContext::GlobalMutexLocker locker(*context_);
-  locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y());
+  locker.GetCentralViewport().MouseMove(event->pos().x(), event->pos().y(), std::vector<OrthancStone::Touch>());
 }
 
 
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -32,7 +32,8 @@
                                                                     int viewportY,
                                                                     double x,
                                                                     double y,
-                                                                    IStatusBar* statusBar)
+                                                                    IStatusBar* statusBar,
+                                                                    const std::vector<Touch>& displayTouches)
   {
     if (button == MouseButton_Left)
     {
--- a/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SimpleViewer/MainWidgetInteractor.h	Thu Jan 24 16:42:27 2019 +0100
@@ -48,7 +48,8 @@
                                                         int viewportY,
                                                         double x,
                                                         double y,
-                                                        IStatusBar* statusBar);
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& displayTouches);
 
     virtual void MouseOver(CairoContext& context,
                            WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -32,7 +32,8 @@
                                                                    int viewportY,
                                                                    double x,
                                                                    double y,
-                                                                   IStatusBar* statusBar)
+                                                                   IStatusBar* statusBar,
+                                                                   const std::vector<Touch>& displayTouches)
   {
     if (button == MouseButton_Left)
     {
--- a/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SimpleViewer/ThumbnailInteractor.h	Thu Jan 24 16:42:27 2019 +0100
@@ -47,7 +47,8 @@
                                                         int viewportY,
                                                         double x,
                                                         double y,
-                                                        IStatusBar* statusBar);
+                                                        IStatusBar* statusBar,
+                                                        const std::vector<Touch>& displayTouches);
 
     virtual void MouseOver(CairoContext& context,
                            WorldSceneWidget& widget,
--- a/Applications/Samples/SimpleViewerApplicationSingleFile.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SimpleViewerApplicationSingleFile.h	Thu Jan 24 16:42:27 2019 +0100
@@ -66,7 +66,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar)
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& displayTouches)
         {
           if (button == MouseButton_Left)
           {
--- a/Applications/Samples/SingleFrameApplication.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SingleFrameApplication.h	Thu Jan 24 16:42:27 2019 +0100
@@ -60,7 +60,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar)
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& displayTouches)
         {
           return NULL;
         }
--- a/Applications/Samples/SingleFrameEditorApplication.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Thu Jan 24 16:42:27 2019 +0100
@@ -96,7 +96,8 @@
                                                           int viewportY,
                                                           double x,
                                                           double y,
-                                                          IStatusBar* statusBar)
+                                                          IStatusBar* statusBar,
+                                                          const std::vector<Touch>& displayTouches)
       {
         RadiographyWidget& widget = dynamic_cast<RadiographyWidget&>(worldWidget);
 
--- a/Framework/Layers/CircleMeasureTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Layers/CircleMeasureTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -91,7 +91,9 @@
   void CircleMeasureTracker::MouseMove(int displayX,
                                        int displayY,
                                        double x,
-                                       double y)
+                                       double y,
+                                       const std::vector<Touch>& displayTouches,
+                                       const std::vector<Touch>& sceneTouches)
   {
     x2_ = x;
     y2_ = y;
--- a/Framework/Layers/CircleMeasureTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Layers/CircleMeasureTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -72,6 +72,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Layers/LineMeasureTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Layers/LineMeasureTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -87,7 +87,9 @@
   void LineMeasureTracker::MouseMove(int displayX,
                                      int displayY,
                                      double x,
-                                     double y)
+                                     double y,
+                                     const std::vector<Touch>& displayTouches,
+                                     const std::vector<Touch>& sceneTouches)
   {
     x2_ = x;
     y2_ = y;
--- a/Framework/Layers/LineMeasureTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Layers/LineMeasureTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -71,6 +71,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerCropTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerCropTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -101,7 +101,9 @@
   void RadiographyLayerCropTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     if (accessor_.IsValid())
     {
--- a/Framework/Radiography/RadiographyLayerCropTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerCropTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -63,6 +63,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -97,7 +97,9 @@
   void RadiographyLayerMoveTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     if (accessor_.IsValid())
     {
--- a/Framework/Radiography/RadiographyLayerMoveTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerMoveTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -61,6 +61,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -159,7 +159,9 @@
   void RadiographyLayerResizeTracker::MouseMove(int displayX,
                                                 int displayY,
                                                 double sceneX,
-                                                double sceneY)
+                                                double sceneY,
+                                                const std::vector<Touch>& displayTouches,
+                                                const std::vector<Touch>& sceneTouches)
   {
     static const double ROUND_SCALING = 0.1;
         
--- a/Framework/Radiography/RadiographyLayerResizeTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerResizeTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -66,6 +66,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -132,7 +132,9 @@
   void RadiographyLayerRotateTracker::MouseMove(int displayX,
                                                 int displayY,
                                                 double sceneX,
-                                                double sceneY)
+                                                double sceneY,
+                                                const std::vector<Touch>& displayTouches,
+                                                const std::vector<Touch>& sceneTouches)
   {
     static const double ROUND_ANGLE = 15.0 / 180.0 * boost::math::constants::pi<double>(); 
         
--- a/Framework/Radiography/RadiographyLayerRotateTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyLayerRotateTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -68,6 +68,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Radiography/RadiographyWindowingTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyWindowingTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -163,7 +163,9 @@
   void RadiographyWindowingTracker::MouseMove(int displayX,
                                               int displayY,
                                               double sceneX,
-                                              double sceneY)
+                                              double sceneY,
+                                              const std::vector<Touch>& displayTouches,
+                                              const std::vector<Touch>& sceneTouches)
   {
     // This follows the behavior of the Osimis Web viewer:
     // https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/app/viewport/image-plugins/windowing-viewport-tool.class.js
--- a/Framework/Radiography/RadiographyWindowingTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Radiography/RadiographyWindowingTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -82,6 +82,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY);
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Framework/Toolbox/ViewportGeometry.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Toolbox/ViewportGeometry.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -121,6 +121,18 @@
   }
 
 
+  void ViewportGeometry::MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                                               const std::vector<Touch>& displayTouches) const
+  {
+    double sceneX, sceneY;
+    sceneTouches.clear();
+    for (size_t t = 0; t < displayTouches.size(); t++)
+    {
+      MapPixelCenterToScene(sceneX, sceneY, displayTouches[t].x, displayTouches[t].y);
+      sceneTouches.push_back(Touch((float)sceneX, (float)sceneY));
+    }
+  }
+
   void ViewportGeometry::MapPixelCenterToScene(double& sceneX,
                                                double& sceneY,
                                                int x,
--- a/Framework/Toolbox/ViewportGeometry.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Toolbox/ViewportGeometry.h	Thu Jan 24 16:42:27 2019 +0100
@@ -24,6 +24,7 @@
 #include "../Viewport/CairoContext.h"
 #include "Extent2D.h"
 #include "LinearAlgebra.h"
+#include "../Viewport/IMouseTracker.h"  // to include "Touch" definition
 
 namespace OrthancStone
 {
@@ -69,6 +70,9 @@
                                int x,
                                int y) const;
 
+    void MapPixelCenterToScene(std::vector<Touch>& sceneTouches /* out */,
+                               const std::vector<Touch>& displayTouches) const;
+
     void MapSceneToDisplay(int& displayX /* out */,
                            int& displayY /* out */,
                            double x,
--- a/Framework/Viewport/IMouseTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Viewport/IMouseTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -22,9 +22,28 @@
 #pragma once
 
 #include "CairoSurface.h"
+#include <vector>
 
 namespace OrthancStone
 {
+  struct Touch
+  {
+    float x;
+    float y;
+
+    Touch(float x, float y)
+    : x(x),
+      y(y)
+    {
+    }
+    Touch()
+      : x(0.0f),
+        y(0.0f)
+    {
+    }
+  };
+
+
   // this is tracking a mouse in screen coordinates/pixels unlike
   // the IWorldSceneMouseTracker that is tracking a mouse
   // in scene coordinates/mm.
@@ -41,6 +60,9 @@
 
     // Returns "true" iff. the background scene must be repainted
     virtual void MouseMove(int x, 
-                           int y) = 0;
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
+
+    virtual bool IsTouchTracker() const {return false;}
   };
 }
--- a/Framework/Viewport/IViewport.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Viewport/IViewport.h	Thu Jan 24 16:42:27 2019 +0100
@@ -26,6 +26,7 @@
 #include "../Messages/IObservable.h"
 
 #include <Core/Images/ImageAccessor.h>
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
 
 namespace OrthancStone
 {
@@ -58,12 +59,14 @@
     virtual void MouseDown(MouseButton button,
                            int x,
                            int y,
-                           KeyboardModifiers modifiers) = 0;
+                           KeyboardModifiers modifiers,
+                           const std::vector<Touch>& touches) = 0;
 
     virtual void MouseUp() = 0;
 
     virtual void MouseMove(int x, 
-                           int y) = 0;
+                           int y,
+                           const std::vector<Touch>& displayTouches) = 0;
 
     virtual void MouseEnter() = 0;
 
--- a/Framework/Viewport/WidgetViewport.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Viewport/WidgetViewport.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -137,18 +137,36 @@
     return true;
   }
 
+  void WidgetViewport::TouchStart(const std::vector<Touch>& displayTouches)
+  {
+    MouseDown(MouseButton_Left, (int)displayTouches[0].x, (int)displayTouches[0].y, KeyboardModifiers_None, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchMove(const std::vector<Touch>& displayTouches)
+  {
+    MouseMove((int)displayTouches[0].x, (int)displayTouches[0].y, displayTouches); // one touch is equivalent to a mouse tracker without left button -> set the mouse coordinates to the first touch coordinates
+  }
+      
+  void WidgetViewport::TouchEnd(const std::vector<Touch>& displayTouches)
+  {
+    // note: TouchEnd is not triggered when a single touch gesture ends (it is only triggered when
+    // going from 2 touches to 1 touch, ...)
+    MouseUp();
+  }
 
   void WidgetViewport::MouseDown(MouseButton button,
                                  int x,
                                  int y,
-                                 KeyboardModifiers modifiers)
+                                 KeyboardModifiers modifiers,
+                                 const std::vector<Touch>& displayTouches
+                                 )
   {
     lastMouseX_ = x;
     lastMouseY_ = y;
 
     if (centralWidget_.get() != NULL)
     {
-      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers));
+      mouseTracker_.reset(centralWidget_->CreateMouseTracker(button, x, y, modifiers, displayTouches));
     }
     else
     {
@@ -171,7 +189,8 @@
 
 
   void WidgetViewport::MouseMove(int x, 
-                                 int y) 
+                                 int y,
+                                 const std::vector<Touch>& displayTouches)
   {
     if (centralWidget_.get() == NULL)
     {
@@ -185,7 +204,7 @@
     
     if (mouseTracker_.get() != NULL)
     {
-      mouseTracker_->MouseMove(x, y);
+      mouseTracker_->MouseMove(x, y, displayTouches);
       repaint = true;
     }
     else
--- a/Framework/Viewport/WidgetViewport.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Viewport/WidgetViewport.h	Thu Jan 24 16:42:27 2019 +0100
@@ -59,17 +59,25 @@
     virtual void MouseDown(MouseButton button,
                            int x,
                            int y,
-                           KeyboardModifiers modifiers);
+                           KeyboardModifiers modifiers,
+                           const std::vector<Touch>& displayTouches);
 
     virtual void MouseUp();
 
     virtual void MouseMove(int x, 
-                           int y);
+                           int y,
+                           const std::vector<Touch>& displayTouches);
 
     virtual void MouseEnter();
 
     virtual void MouseLeave();
 
+    virtual void TouchStart(const std::vector<Touch>& touches);
+    
+    virtual void TouchMove(const std::vector<Touch>& touches);
+    
+    virtual void TouchEnd(const std::vector<Touch>& touches);
+
     virtual void MouseWheel(MouseWheelDirection direction,
                             int x,
                             int y,
--- a/Framework/Widgets/EmptyWidget.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/EmptyWidget.h	Thu Jan 24 16:42:27 2019 +0100
@@ -76,7 +76,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers)
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches)
     {
       return NULL;
     }
--- a/Framework/Widgets/IWidget.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/IWidget.h	Thu Jan 24 16:42:27 2019 +0100
@@ -52,7 +52,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers) = 0;
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches) = 0;
 
     virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
                                  int x,
--- a/Framework/Widgets/IWorldSceneInteractor.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/IWorldSceneInteractor.h	Thu Jan 24 16:42:27 2019 +0100
@@ -46,7 +46,8 @@
                                                             int viewportY,
                                                             double x,
                                                             double y,
-                                                            IStatusBar* statusBar) = 0;
+                                                            IStatusBar* statusBar,
+                                                            const std::vector<Touch>& touches) = 0;
 
         virtual void MouseOver(CairoContext& context,
                                WorldSceneWidget& widget,
--- a/Framework/Widgets/IWorldSceneMouseTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/IWorldSceneMouseTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -22,6 +22,7 @@
 #pragma once
 
 #include "../Viewport/CairoContext.h"
+#include "../Viewport/IMouseTracker.h" // only to get the "Touch" definition
 
 namespace OrthancStone
 {
@@ -46,6 +47,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double sceneX,
-                           double sceneY) = 0;
+                           double sceneY,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches) = 0;
   };
 }
--- a/Framework/Widgets/LayoutWidget.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/LayoutWidget.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -68,9 +68,16 @@
     }
 
     virtual void MouseMove(int x, 
-                           int y)
+                           int y,
+                           const std::vector<Touch>& displayTouches)
     {
-      tracker_->MouseMove(x - left_, y - top_);
+      std::vector<Touch> relativeTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        relativeTouches.push_back(Touch((int)displayTouches[t].x - left_, (int)displayTouches[t].y - top_));
+      }
+
+      tracker_->MouseMove(x - left_, y - top_, relativeTouches);
     }
   };
 
@@ -150,14 +157,16 @@
     IMouseTracker* CreateMouseTracker(MouseButton button,
                                       int x,
                                       int y,
-                                      KeyboardModifiers modifiers)
+                                      KeyboardModifiers modifiers,
+                                      const std::vector<Touch>& touches)
     {
       if (Contains(x, y))
       {
         IMouseTracker* tracker = widget_->CreateMouseTracker(button, 
                                                              x - left_, 
                                                              y - top_, 
-                                                             modifiers);
+                                                             modifiers,
+                                                             touches);
         if (tracker)
         {
           return new LayoutMouseTracker(tracker, left_, top_, width_, height_);
@@ -413,11 +422,12 @@
   IMouseTracker* LayoutWidget::CreateMouseTracker(MouseButton button,
                                                   int x,
                                                   int y,
-                                                  KeyboardModifiers modifiers)
+                                                  KeyboardModifiers modifiers,
+                                                  const std::vector<Touch>& touches)
   {
     for (size_t i = 0; i < children_.size(); i++)
     {
-      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers);
+      IMouseTracker* tracker = children_[i]->CreateMouseTracker(button, x, y, modifiers, touches);
       if (tracker != NULL)
       {
         return tracker;
--- a/Framework/Widgets/LayoutWidget.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/LayoutWidget.h	Thu Jan 24 16:42:27 2019 +0100
@@ -106,7 +106,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers);
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
 
     virtual void RenderMouseOver(Orthanc::ImageAccessor& target,
                                  int x,
--- a/Framework/Widgets/PanMouseTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/PanMouseTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -46,7 +46,9 @@
   void PanMouseTracker::MouseMove(int displayX,
                                   int displayY,
                                   double x,
-                                  double y)
+                                  double y,
+                                  const std::vector<Touch>& displayTouches,
+                                  const std::vector<Touch>& sceneTouches)
   {
     ViewportGeometry view = that_.GetView();
     view.SetPan(originalPanX_ + (x - downX_) * view.GetZoom(),
--- a/Framework/Widgets/PanMouseTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/PanMouseTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -54,6 +54,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/PanZoomMouseTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -0,0 +1,137 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PanZoomMouseTracker.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <math.h>
+
+namespace OrthancStone
+{
+  Touch GetCenter(const std::vector<Touch>& touches)
+  {
+    return Touch((touches[0].x + touches[1].x) / 2.0f, (touches[0].y + touches[1].y) / 2.0f);
+  }
+
+  double GetDistance(const std::vector<Touch>& touches)
+  {
+    float dx = touches[0].x - touches[1].x;
+    float dy = touches[0].y - touches[1].y;
+    return sqrt((double)(dx * dx) + (double)(dy * dy));
+  }
+
+
+  PanZoomMouseTracker::PanZoomMouseTracker(WorldSceneWidget& that,
+                                           const std::vector<Touch>& startTouches)
+    : that_(that),
+      originalZoom_(that.GetView().GetZoom())
+  {
+    that.GetView().GetPan(originalPanX_, originalPanY_);
+    that.GetView().MapPixelCenterToScene(originalSceneTouches_, startTouches);
+
+    originalDisplayCenter_ = GetCenter(startTouches);
+    originalSceneCenter_ = GetCenter(originalSceneTouches_);
+    originalDisplayDistanceBetweenTouches_ = GetDistance(startTouches);
+
+//    printf("original Pan %f %f\n", originalPanX_, originalPanY_);
+//    printf("original Zoom %f \n", originalZoom_);
+//    printf("original distance %f \n", (float)originalDisplayDistanceBetweenTouches_);
+//    printf("original display touches 0 %f %f\n", startTouches[0].x, startTouches[0].y);
+//    printf("original display touches 1 %f %f\n", startTouches[1].x, startTouches[1].y);
+//    printf("original Scene center %f %f\n", originalSceneCenter_.x, originalSceneCenter_.y);
+
+    unsigned int height = that.GetView().GetDisplayHeight();
+
+    if (height <= 3)
+    {
+      idle_ = true;
+      LOG(WARNING) << "image is too small to zoom (current height = " << height << ")";
+    }
+    else
+    {
+      idle_ = false;
+      normalization_ = 1.0 / static_cast<double>(height - 1);
+    }
+
+  }
+
+
+  void PanZoomMouseTracker::Render(CairoContext& context,
+                                   double zoom)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+
+  void PanZoomMouseTracker::MouseMove(int displayX,
+                                      int displayY,
+                                      double x,
+                                      double y,
+                                      const std::vector<Touch>& displayTouches,
+                                      const std::vector<Touch>& sceneTouches)
+  {
+    ViewportGeometry view = that_.GetView();
+
+//    printf("Display touches 0 %f %f\n", displayTouches[0].x, displayTouches[0].y);
+//    printf("Display touches 1 %f %f\n", displayTouches[1].x, displayTouches[1].y);
+//    printf("Scene touches 0 %f %f\n", sceneTouches[0].x, sceneTouches[0].y);
+//    printf("Scene touches 1 %f %f\n", sceneTouches[1].x, sceneTouches[1].y);
+
+//    printf("zoom = %f\n", view.GetZoom());
+    Touch currentSceneCenter = GetCenter(sceneTouches);
+    double panX = originalPanX_ + (currentSceneCenter.x - originalSceneCenter_.x) * view.GetZoom();
+    double panY = originalPanY_ + (currentSceneCenter.y - originalSceneCenter_.y) * view.GetZoom();
+
+    view.SetPan(panX, panY);
+
+    static const double MIN_ZOOM = -4;
+    static const double MAX_ZOOM = 4;
+
+    if (!idle_)
+    {
+      double currentDistanceBetweenTouches = GetDistance(displayTouches);
+
+      double dy = static_cast<double>(currentDistanceBetweenTouches - originalDisplayDistanceBetweenTouches_) * normalization_;  // In the range [-1,1]
+      double z;
+
+      // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
+      if (dy < -1.0)
+      {
+        z = MIN_ZOOM;
+      }
+      else if (dy > 1.0)
+      {
+        z = MAX_ZOOM;
+      }
+      else
+      {
+        z = MIN_ZOOM + (MAX_ZOOM - MIN_ZOOM) * (dy + 1.0) / 2.0;
+      }
+
+      z = pow(2.0, z);
+
+      view.SetZoom(z * originalZoom_);
+    }
+
+    that_.SetView(view);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Widgets/PanZoomMouseTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "WorldSceneWidget.h"
+
+namespace OrthancStone
+{
+  class PanZoomMouseTracker : public IWorldSceneMouseTracker
+  {
+  private:
+    WorldSceneWidget&  that_;
+    std::vector<Touch> originalSceneTouches_;
+    Touch              originalSceneCenter_;
+    Touch              originalDisplayCenter_;
+    double             originalPanX_;
+    double             originalPanY_;
+    double             originalZoom_;
+    double             originalDisplayDistanceBetweenTouches_;
+    bool               idle_;
+    double             normalization_;
+
+  public:
+    PanZoomMouseTracker(WorldSceneWidget& that,
+                        const std::vector<Touch>& startTouches);
+    
+    virtual bool HasRender() const
+    {
+      return false;
+    }
+
+    virtual void MouseUp()
+    {
+    }
+
+    virtual void Render(CairoContext& context,
+                        double zoom);
+
+    virtual void MouseMove(int displayX,
+                           int displayY,
+                           double x,
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
+  };
+}
--- a/Framework/Widgets/TestCairoWidget.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/TestCairoWidget.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -99,7 +99,8 @@
     IMouseTracker* TestCairoWidget::CreateMouseTracker(MouseButton button,
                                                        int x,
                                                        int y,
-                                                       KeyboardModifiers modifiers)
+                                                       KeyboardModifiers modifiers,
+                                                       const std::vector<Touch>& touches)
     {
       UpdateStatusBar("Click");
       return NULL;
--- a/Framework/Widgets/TestCairoWidget.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/TestCairoWidget.h	Thu Jan 24 16:42:27 2019 +0100
@@ -51,7 +51,8 @@
       virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                                 int x,
                                                 int y,
-                                                KeyboardModifiers modifiers);
+                                                KeyboardModifiers modifiers,
+                                                const std::vector<Touch>& touches);
 
       virtual void MouseWheel(MouseWheelDirection direction,
                               int x,
--- a/Framework/Widgets/TestWorldSceneWidget.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/TestWorldSceneWidget.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -41,7 +41,8 @@
                                                           int viewportY,
                                                           double x,
                                                           double y,
-                                                          IStatusBar* statusBar)
+                                                          IStatusBar* statusBar,
+                                                          const std::vector<Touch>& touches)
       {
         if (statusBar)
         {
--- a/Framework/Widgets/WorldSceneWidget.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/WorldSceneWidget.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -23,6 +23,7 @@
 
 #include "PanMouseTracker.h"
 #include "ZoomMouseTracker.h"
+#include "PanZoomMouseTracker.h"
 
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
@@ -72,11 +73,20 @@
     }
 
     virtual void MouseMove(int x,
-                           int y)
+                           int y,
+                           const std::vector<Touch>& displayTouches)
     {
       double sceneX, sceneY;
       view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
-      tracker_->MouseMove(x, y, sceneX, sceneY);
+
+      std::vector<Touch> sceneTouches;
+      for (size_t t = 0; t < displayTouches.size(); t++)
+      {
+        double sx, sy;
+        view_.MapPixelCenterToScene(sx, sy, (int)displayTouches[t].x, (int)displayTouches[t].y);
+        sceneTouches.push_back(Touch(sx, sy));
+      }
+      tracker_->MouseMove(x, y, sceneX, sceneY, displayTouches, sceneTouches);
     }
   };
 
@@ -145,7 +155,8 @@
   IMouseTracker* WorldSceneWidget::CreateMouseTracker(MouseButton button,
                                                       int x,
                                                       int y,
-                                                      KeyboardModifiers modifiers)
+                                                      KeyboardModifiers modifiers,
+                                                      const std::vector<Touch>& touches)
   {
     double sceneX, sceneY;
     view_.MapPixelCenterToScene(sceneX, sceneY, x, y);
@@ -155,7 +166,7 @@
 
     if (interactor_)
     {
-      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar()));
+      tracker.reset(interactor_->CreateMouseTracker(*this, view_, button, modifiers, x, y, sceneX, sceneY, GetStatusBar(), touches));
     }
     
     if (tracker.get() != NULL)
@@ -164,17 +175,26 @@
     }
     else if (hasDefaultMouseEvents_)
     {
-      switch (button)
+      printf("has default mouse events\n");
+      if (touches.size() == 2)
       {
-        case MouseButton_Middle:
-          return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
+        printf("2 touches !\n");
+        return new SceneMouseTracker(view_, new PanZoomMouseTracker(*this, touches));
+      }
+      else
+      {
+        switch (button)
+        {
+          case MouseButton_Middle:
+            return new SceneMouseTracker(view_, new PanMouseTracker(*this, x, y));
 
-        case MouseButton_Right:
-          return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
+          case MouseButton_Right:
+            return new SceneMouseTracker(view_, new ZoomMouseTracker(*this, x, y));
 
-        default:
-          return NULL;
-      }      
+          default:
+            return NULL;
+        }
+      }
     }
     else
     {
--- a/Framework/Widgets/WorldSceneWidget.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/WorldSceneWidget.h	Thu Jan 24 16:42:27 2019 +0100
@@ -88,7 +88,8 @@
     virtual IMouseTracker* CreateMouseTracker(MouseButton button,
                                               int x,
                                               int y,
-                                              KeyboardModifiers modifiers);
+                                              KeyboardModifiers modifiers,
+                                              const std::vector<Touch>& touches);
 
     virtual void MouseWheel(MouseWheelDirection direction,
                             int x,
--- a/Framework/Widgets/ZoomMouseTracker.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/ZoomMouseTracker.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -61,7 +61,9 @@
   void ZoomMouseTracker::MouseMove(int displayX,
                                    int displayY,
                                    double x,
-                                   double y)
+                                   double y,
+                                   const std::vector<Touch>& displayTouches,
+                                   const std::vector<Touch>& sceneTouches)
   {
     static const double MIN_ZOOM = -4;
     static const double MAX_ZOOM = 4;
--- a/Framework/Widgets/ZoomMouseTracker.h	Wed Jan 23 13:58:51 2019 +0100
+++ b/Framework/Widgets/ZoomMouseTracker.h	Thu Jan 24 16:42:27 2019 +0100
@@ -57,6 +57,8 @@
     virtual void MouseMove(int displayX,
                            int displayY,
                            double x,
-                           double y);
+                           double y,
+                           const std::vector<Touch>& displayTouches,
+                           const std::vector<Touch>& sceneTouches);
   };
 }
--- a/Platforms/Wasm/Defaults.cpp	Wed Jan 23 13:58:51 2019 +0100
+++ b/Platforms/Wasm/Defaults.cpp	Thu Jan 24 16:42:27 2019 +0100
@@ -49,7 +49,7 @@
 
     viewports_.push_back(viewport);
 
-    printf("There are now %d viewports in C++\n", viewports_.size());
+    printf("There are now %lu viewports in C++\n", viewports_.size());
 
     viewport->SetStatusBar(statusBar_);
 
@@ -64,7 +64,7 @@
   void EMSCRIPTEN_KEEPALIVE ReleaseCppViewport(ViewportHandle viewport) {
     viewports_.remove_if([viewport](const std::shared_ptr<OrthancStone::WidgetViewport>& v) { return v.get() == viewport;});
 
-    printf("There are now %d viewports in C++\n", viewports_.size());
+    printf("There are now %lu viewports in C++\n", viewports_.size());
   }
 
   void EMSCRIPTEN_KEEPALIVE CreateWasmApplication(ViewportHandle viewport) {
@@ -191,7 +191,7 @@
         return;  // Unknown button
     }
 
-    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None /* TODO */);
+    viewport->MouseDown(button, x, y, OrthancStone::KeyboardModifiers_None, std::vector<OrthancStone::Touch>());
   }
   
 
@@ -222,9 +222,82 @@
                                               int x,
                                               int y)
   {
-    viewport->MouseMove(x, y);
+    viewport->MouseMove(x, y, std::vector<OrthancStone::Touch>());
+  }
+
+  void GetTouchVector(std::vector<OrthancStone::Touch>& output,
+                      int touchCount,
+                      float x0,
+                      float y0,
+                      float x1,
+                      float y1,
+                      float x2,
+                      float y2)
+  {
+    // TODO: it might be nice to try to pass all the x0,y0 coordinates as arrays but that's not so easy to pass array between JS and C++
+    if (touchCount > 0)
+    {
+      output.push_back(OrthancStone::Touch(x0, y0));
+    }
+    if (touchCount > 1)
+    {
+      output.push_back(OrthancStone::Touch(x1, y1));
+    }
+    if (touchCount > 2)
+    {
+      output.push_back(OrthancStone::Touch(x2, y2));
+    }
+
   }
-  
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchStart(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch start with %d touches\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchStart(touches);
+  }
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchMove(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch move with %d touches\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchMove(touches);
+  }
+
+  void EMSCRIPTEN_KEEPALIVE ViewportTouchEnd(ViewportHandle viewport,
+                                              int touchCount,
+                                              float x0,
+                                              float y0,
+                                              float x1,
+                                              float y1,
+                                              float x2,
+                                              float y2)
+  {
+    printf("touch end with %d touches remaining\n", touchCount);
+
+    std::vector<OrthancStone::Touch> touches;
+    GetTouchVector(touches, touchCount, x0, y0, x1, y1, x2, y2);
+    viewport->TouchEnd(touches);
+  }
+
   void EMSCRIPTEN_KEEPALIVE ViewportKeyPressed(ViewportHandle viewport,
                                                int key,
                                                const char* keyChar, 
--- a/Platforms/Wasm/wasm-viewport.ts	Wed Jan 23 13:58:51 2019 +0100
+++ b/Platforms/Wasm/wasm-viewport.ts	Thu Jan 24 16:42:27 2019 +0100
@@ -60,6 +60,9 @@
     private ViewportMouseLeave : Function;
     private ViewportMouseWheel : Function;
     private ViewportKeyPressed : Function;
+    private ViewportTouchStart : Function;
+    private ViewportTouchMove : Function;
+    private ViewportTouchEnd : Function;
 
     private pimpl_ : any; // Private pointer to the underlying WebAssembly C++ object
 
@@ -86,6 +89,9 @@
       this.ViewportMouseLeave = this.module_.cwrap('ViewportMouseLeave', null, [ 'number' ]);
       this.ViewportMouseWheel = this.module_.cwrap('ViewportMouseWheel', null, [ 'number', 'number', 'number', 'number', 'number' ]);
       this.ViewportKeyPressed = this.module_.cwrap('ViewportKeyPressed', null, [ 'number', 'number', 'string', 'number', 'number' ]);
+      this.ViewportTouchStart = this.module_.cwrap('ViewportTouchStart', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportTouchMove = this.module_.cwrap('ViewportTouchMove', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
+      this.ViewportTouchEnd = this.module_.cwrap('ViewportTouchEnd', null, [ 'number', 'number', 'number', 'number', 'number', 'number', 'number' ]);
     }
 
     public GetCppViewport() : number {
@@ -192,7 +198,8 @@
       this.htmlCanvas_.addEventListener('mousedown', function(event) {
         var x = event.pageX - this.offsetLeft;
         var y = event.pageY - this.offsetTop;
-        that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/);    
+
+       that.ViewportMouseDown(that.pimpl_, event.button, x, y, 0 /* TODO detect modifier keys*/);    
       });
     
       this.htmlCanvas_.addEventListener('mousemove', function(event) {
@@ -224,12 +231,32 @@
         event.preventDefault();
       });
 
-      this.htmlCanvas_.addEventListener('touchstart', function(event) {
+      this.htmlCanvas_.addEventListener('touchstart', function(event: TouchEvent) {
         // don't propagate events to the whole body (this could zoom the entire page instead of zooming the viewport)
         event.preventDefault();
         event.stopPropagation();
 
-        that.ResetTouch();
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
+        }
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
+        }
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
+        }
+
+        that.ViewportTouchStart(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
       });
     
       this.htmlCanvas_.addEventListener('touchend', function(event) {
@@ -237,7 +264,27 @@
         event.preventDefault();
         event.stopPropagation();
 
-        that.ResetTouch();
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
+        }
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
+        }
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
+        }
+
+        that.ViewportTouchEnd(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
       });
     
       this.htmlCanvas_.addEventListener('touchmove', function(event: TouchEvent) {
@@ -246,6 +293,30 @@
         event.preventDefault();
         event.stopPropagation();
 
+
+        // TODO: find a way to pass the coordinates as an array between JS and C++
+        var x0 = 0;
+        var y0 = 0;
+        var x1 = 0;
+        var y1 = 0;
+        var x2 = 0;
+        var y2 = 0;
+        if (event.targetTouches.length > 0) {
+          x0 = event.targetTouches[0].pageX;
+          y0 = event.targetTouches[0].pageY;
+        }
+        if (event.targetTouches.length > 1) {
+          x1 = event.targetTouches[1].pageX;
+          y1 = event.targetTouches[1].pageY;
+        }
+        if (event.targetTouches.length > 2) {
+          x2 = event.targetTouches[2].pageX;
+          y2 = event.targetTouches[2].pageY;
+        }
+
+        that.ViewportTouchMove(that.pimpl_, event.targetTouches.length, x0, y0, x1, y1, x2, y2);
+        return;
+
         // if (!that.touchGestureInProgress_) {
         //   // starting a new gesture
         //   that.touchCount_ = event.targetTouches.length;
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Jan 23 13:58:51 2019 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Thu Jan 24 16:42:27 2019 +0100
@@ -296,6 +296,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ViewportGeometry.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoContext.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/CairoSurface.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Viewport/IMouseTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/IStatusBar.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h
   ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp
@@ -309,6 +310,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/IWorldSceneMouseTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/LayoutWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanMouseTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Widgets/PanZoomMouseTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/SliceViewerWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestCairoWidget.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/TestWorldSceneWidget.cpp