changeset 1406:5d7ee14dc1eb

Mouse wheel handler is now OK in SDL and Wasm
author Benjamin Golinvaux <bgo@osimis.io>
date Thu, 30 Apr 2020 00:25:55 +0200
parents e4fe346c021e
children 9c5a8cb3d856 b2b0fc4a5596
files Samples/Common/RtViewerApp.h Samples/Common/RtViewerView.cpp Samples/Common/RtViewerView.h Samples/README.md Samples/Sdl/RtViewer/RtViewerSdl.cpp Samples/Sdl/SdlHelpers.h Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Samples/build-wasm-RtViewer.sh
diffstat 8 files changed, 317 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
--- a/Samples/Common/RtViewerApp.h	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/Common/RtViewerApp.h	Thu Apr 30 00:25:55 2020 +0200
@@ -84,6 +84,8 @@
 #if ORTHANC_ENABLE_SDL
   public:
     void RunSdl(int argc, char* argv[]);
+    void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+                    OrthancStone::IViewportInteractor& interactor);
   private:
     void ProcessOptions(int argc, char* argv[]);
     void HandleApplicationEvent(const SDL_Event& event);
--- a/Samples/Common/RtViewerView.cpp	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/Common/RtViewerView.cpp	Thu Apr 30 00:25:55 2020 +0200
@@ -171,6 +171,35 @@
     lock->Invalidate();
   }
 
+  void RtViewerView::Scroll(int delta)
+  {
+    if (!planes_.empty())
+    {
+      int next = 0;
+      int temp = static_cast<int>(currentPlane_) + delta;
+
+      if (temp < 0)
+      {
+        next = 0;
+      }
+      else if (temp >= static_cast<int>(planes_.size()))
+      {
+        next = static_cast<unsigned int>(planes_.size()) - 1;
+      }
+      else
+      {
+        next = static_cast<size_t>(temp);
+      }
+      LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next;
+
+      if (next != currentPlane_)
+      {
+        currentPlane_ = next;
+        UpdateLayers();
+      }
+    }
+  }
+
   void RtViewerView::RetrieveGeometry()
   {
     const VolumeImageGeometry& geometry = GetApp()->GetMainGeometry();
--- a/Samples/Common/RtViewerView.h	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/Common/RtViewerView.h	Thu Apr 30 00:25:55 2020 +0200
@@ -78,6 +78,8 @@
       unsigned int canvasWidth,
       unsigned int canvasHeight);
 
+    void Scroll(int delta);
+
     void Invalidate();
     void FitContent();
     void RetrieveGeometry();
@@ -101,7 +103,7 @@
   private:
     void SetInfoDisplayMessage(std::string key, std::string value);
     boost::shared_ptr<RtViewerApp> GetApp();
-    static boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId);
+    boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId);
     void DisplayInfoText();
     void HideInfoText();
     void DisplayFloatingCtrlInfoText(const PointerEvent& e);
--- a/Samples/README.md	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/README.md	Thu Apr 30 00:25:55 2020 +0200
@@ -90,6 +90,42 @@
 You'll then be able to open the demo at `http://localhost:8042/single-frame-viewer/index.html`
 
 
+RtViewer
+-----------------
+
+This sample application displays three MPR views of a CT+RTDOSE+RTSTRUCT dataset, loaded from Orthanc. The Orthanc IDs of the dataset must be supplied as URL parameters like:
+
+```
+http://localhost:9979/rtviewer/index.html?loglevel=info&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
+```
+
+(notice the loglevel parameter that can be `warning`, `info` or `trace`, in increasing verbosity order)
+
+This sample uses plain Javascript and requires the 
+Emscripten toolchain and cmake, in addition to a few standard packages.
+
+To build it, just launch the `build-wasm-RtViewer.sh` script from
+this folder.  Optionaly, you can pass the build type as an argument.
+We suggest that you do *not* use the `Debug` configuration unless you really 
+need it, for the additional checks that are made will lead to a very long 
+build time and much slower execution (more severe than with a native non-wasm
+target)
+
+In order to run the sample, you may serve it with the ServeFolders plugin.
+You can i.e: add such a section in your orthanc configuration file:
+
+```
+{
+  "Plugins" : ["LibServeFolders.so],
+  "ServeFolders" : {
+    "/rt-viewer" : "..../out/install-stone-wasm-RtViewer-RelWithDebInfo"
+  }
+}
+```
+
+You'll then be able to open the demo at `http://localhost:8042/rt-viewer/index.html`
+
+
 Native samples
 =================
 
@@ -142,3 +178,38 @@
 ```
 ./SingleFrameViewer --orthanc http://localhost:8042 --instance 7fc84013-abef174e-3354ca83-b9cdb2a4-f1a74368
 ```
+
+RtViewer
+---------------
+
+### Windows build 
+
+Here's how to build the SdlSimpleViewer example using Visual Studio 2019
+(the shell is Powershell, but the legacy shell can also be used with some 
+tweaks). This example is meant to be launched from the folder above 
+orthanc-stone.
+
+```
+  $buildRootDir = "out"
+  $buildDirName = "build-stone-sdl-RtViewer-msvc16-x64"
+  $buildDir = Join-Path -Path $buildRootDir -ChildPath $buildDirName
+
+  if (-not (Test-Path $buildDir)) {
+    mkdir -p $buildDir | Out-Null
+  }
+  
+  cd $buildDir
+  
+  cmake -G "Visual Studio 16 2019" -A x64 `
+    -DMSVC_MULTIPLE_PROCESSES=ON `
+    -DALLOW_DOWNLOADS=ON `
+    -DSTATIC_BUILD=ON `
+    -DOPENSSL_NO_CAPIENG=ON `
+    ../../orthanc-stone/Samples/Sdl/RtViewer
+```
+
+Executing `cmake --build .` in the build folder will launch the Microsoft 
+builder on the solution.
+
+Alternatively, you can open and build the solution using Visual Studio 2019.
+
--- a/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Thu Apr 30 00:25:55 2020 +0200
@@ -182,16 +182,7 @@
     StartLoaders();
 
 
-    std::vector<boost::shared_ptr<SdlViewport>> sdlViewports;
-    for(size_t i = 0; i < views_.size(); ++i)
-    {
-      boost::shared_ptr<RtViewerView> view = views_[i];
-      boost::shared_ptr<IViewport> viewport = view->GetViewport();
-      boost::shared_ptr<SdlViewport> sdlViewport = 
-        boost::dynamic_pointer_cast<SdlViewport>(viewport);
-      sdlViewports.push_back(sdlViewport);
-    }
-    OrthancStoneHelpers::SdlRunLoop(sdlViewports, interactor);
+    SdlRunLoop(views_, interactor);
     loadersContext->StopOracle();
   }
 
@@ -216,6 +207,179 @@
     Orthanc::PngWriter writer;
     writer.WriteToFile(target, png);
   }
+
+  static boost::shared_ptr<OrthancStone::RtViewerView> GetViewFromWindowId(
+    const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+    Uint32 windowID)
+  {
+    using namespace OrthancStone;
+    for (size_t i = 0; i < views.size(); ++i)
+    {
+      boost::shared_ptr<OrthancStone::RtViewerView> view = views[i];
+      boost::shared_ptr<IViewport> viewport = view->GetViewport();
+      boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport);
+      Uint32 curWindowID = sdlViewport->GetSdlWindowId();
+      if (windowID == curWindowID)
+        return view;
+    }
+    return NULL;
+  }
+
+  void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views,
+                               OrthancStone::IViewportInteractor& interactor)
+  {
+    using namespace OrthancStone;
+
+    // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views
+    std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports;
+    for (size_t i = 0; i < views.size(); ++i)
+    {
+      boost::shared_ptr<RtViewerView> view = views[i];
+      boost::shared_ptr<IViewport> viewport = view->GetViewport();
+      boost::shared_ptr<SdlViewport> sdlViewport =
+        boost::dynamic_pointer_cast<SdlViewport>(viewport);
+      viewports.push_back(sdlViewport);
+    }
+
+    {
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      bool stop = false;
+      while (!stop)
+      {
+        bool paint = false;
+        SDL_Event event;
+        while (SDL_PollEvent(&event))
+        {
+          if (event.type == SDL_QUIT)
+          {
+            stop = true;
+            break;
+          }
+          else if (event.type == SDL_WINDOWEVENT &&
+                   (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+                    event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, event.window.windowID);
+
+            boost::shared_ptr<SdlViewport> sdlViewport =
+              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+
+            sdlViewport->UpdateSize(event.window.data1, event.window.data2);
+          }
+          else if (event.type == SDL_WINDOWEVENT &&
+                   (event.window.event == SDL_WINDOWEVENT_SHOWN ||
+                    event.window.event == SDL_WINDOWEVENT_EXPOSED))
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, event.window.windowID);
+            boost::shared_ptr<SdlViewport> sdlViewport =
+              boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+            sdlViewport->Paint();
+          }
+          else if (event.type == SDL_KEYDOWN &&
+                   event.key.repeat == 0 /* Ignore key bounce */)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, event.window.windowID);
+
+            switch (event.key.keysym.sym)
+            {
+            case SDLK_f:
+            {
+              boost::shared_ptr<SdlViewport> sdlViewport =
+                boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport());
+              sdlViewport->ToggleMaximize();
+            }
+            break;
+
+            case SDLK_s:
+            {
+              std::unique_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
+              lock->GetCompositor().FitContent(lock->GetController().GetScene());
+              lock->Invalidate();
+            }
+            break;
+
+            case SDLK_q:
+              stop = true;
+              break;
+
+            default:
+              break;
+            }
+          }
+          else if (event.type == SDL_MOUSEBUTTONDOWN ||
+                   event.type == SDL_MOUSEMOTION ||
+                   event.type == SDL_MOUSEBUTTONUP)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, event.window.windowID);
+
+            std::auto_ptr<OrthancStone::IViewport::ILock> lock(view->GetViewport()->Lock());
+            if (lock->HasCompositor())
+            {
+              OrthancStone::PointerEvent p;
+              OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
+                                                   event, keyboardState, scancodeCount);
+
+              switch (event.type)
+              {
+              case SDL_MOUSEBUTTONDOWN:
+                lock->GetController().HandleMousePress(interactor, p,
+                                                       lock->GetCompositor().GetCanvasWidth(),
+                                                       lock->GetCompositor().GetCanvasHeight());
+                lock->Invalidate();
+                break;
+
+              case SDL_MOUSEMOTION:
+                if (lock->GetController().HandleMouseMove(p))
+                {
+                  lock->Invalidate();
+                }
+                break;
+
+              case SDL_MOUSEBUTTONUP:
+                lock->GetController().HandleMouseRelease(p);
+                lock->Invalidate();
+                break;
+
+              default:
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+            }
+          }
+          else if (event.type == SDL_MOUSEWHEEL)
+          {
+            boost::shared_ptr<RtViewerView> view = GetViewFromWindowId(
+              views, event.window.windowID);
+
+            int delta = 0;
+            if (event.wheel.y < 0)
+              delta = -1;
+            if (event.wheel.y > 0)
+              delta = 1;
+
+            view->Scroll(delta);
+          }
+          else
+          {
+            for (size_t i = 0; i < views.size(); ++i)
+            {
+              boost::shared_ptr<SdlViewport> sdlViewport =
+                boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport());
+              if (sdlViewport->IsRefreshEvent(event))
+                sdlViewport->Paint();
+            }
+          }
+        }
+      }
+      // Small delay to avoid using 100% of CPU
+      SDL_Delay(1);
+    }
+  }
 }
 
 boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
--- a/Samples/Sdl/SdlHelpers.h	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/Sdl/SdlHelpers.h	Thu Apr 30 00:25:55 2020 +0200
@@ -114,126 +114,6 @@
     }
     return NULL;
   }
-
-  inline void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::SdlViewport> >& viewports,
-                         OrthancStone::IViewportInteractor& interactor)
-  {
-    using namespace OrthancStone;
-    {
-      int scancodeCount = 0;
-      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-      bool stop = false;
-      while (!stop)
-      {
-        bool paint = false;
-        SDL_Event event;
-        while (SDL_PollEvent(&event))
-        {
-          if (event.type == SDL_QUIT)
-          {
-            stop = true;
-            break;
-          }
-          else if (event.type == SDL_WINDOWEVENT &&
-                   (event.window.event == SDL_WINDOWEVENT_RESIZED ||
-                    event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
-          {
-            boost::shared_ptr<SdlViewport> viewport = GetSdlViewportFromWindowId(
-              viewports, event.window.windowID);
-            viewport->UpdateSize(event.window.data1, event.window.data2);
-          }
-          else if (event.type == SDL_WINDOWEVENT &&
-                   (event.window.event == SDL_WINDOWEVENT_SHOWN ||
-                    event.window.event == SDL_WINDOWEVENT_EXPOSED))
-          {
-            boost::shared_ptr<SdlViewport> viewport = GetSdlViewportFromWindowId(
-              viewports, event.window.windowID);
-            viewport->Paint();
-          }
-          else if (event.type == SDL_KEYDOWN &&
-                   event.key.repeat == 0 /* Ignore key bounce */)
-          {
-            boost::shared_ptr<SdlViewport> viewport = GetSdlViewportFromWindowId(
-              viewports, event.window.windowID);
-
-            switch (event.key.keysym.sym)
-            {
-            case SDLK_f:
-              viewport->ToggleMaximize();
-              break;
-
-            case SDLK_s:
-            {
-              std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock());
-              lock->GetCompositor().FitContent(lock->GetController().GetScene());
-              lock->Invalidate();
-            }
-            break;
-
-            case SDLK_q:
-              stop = true;
-              break;
-
-            default:
-              break;
-            }
-          }
-          else if (event.type == SDL_MOUSEBUTTONDOWN ||
-                   event.type == SDL_MOUSEMOTION ||
-                   event.type == SDL_MOUSEBUTTONUP)
-          {
-            boost::shared_ptr<SdlViewport> viewport = GetSdlViewportFromWindowId(
-              viewports, event.window.windowID);
-
-            std::auto_ptr<OrthancStone::IViewport::ILock> lock(viewport->Lock());
-            if (lock->HasCompositor())
-            {
-              OrthancStone::PointerEvent p;
-              OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(),
-                                                   event, keyboardState, scancodeCount);
-
-              switch (event.type)
-              {
-              case SDL_MOUSEBUTTONDOWN:
-                lock->GetController().HandleMousePress(interactor, p,
-                                                       lock->GetCompositor().GetCanvasWidth(),
-                                                       lock->GetCompositor().GetCanvasHeight());
-                lock->Invalidate();
-                break;
-
-              case SDL_MOUSEMOTION:
-                if (lock->GetController().HandleMouseMove(p))
-                {
-                  lock->Invalidate();
-                }
-                break;
-
-              case SDL_MOUSEBUTTONUP:
-                lock->GetController().HandleMouseRelease(p);
-                lock->Invalidate();
-                break;
-
-              default:
-                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-              }
-            }
-          }
-          else
-          {
-            for (size_t i = 0; i < viewports.size(); ++i)
-            {
-              boost::shared_ptr<SdlViewport> viewport = viewports[i];
-              if (viewport->IsRefreshEvent(event))
-                viewport->Paint();
-            }
-          }
-        }
-      }
-      // Small delay to avoid using 100% of CPU
-      SDL_Delay(1);
-    }
-  }
 }
 
 
--- a/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp	Thu Apr 30 00:25:55 2020 +0200
@@ -75,10 +75,44 @@
 
 namespace OrthancStone
 {
+  //   typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData);
+
+  EM_BOOL RtViewerView_Scroll(int eventType, 
+                              const EmscriptenWheelEvent* wheelEvent, 
+                              void* userData)
+  {
+    RtViewerView* that = reinterpret_cast<RtViewerView*>(userData);
+
+    int delta = 0;
+    if (wheelEvent->deltaY < 0)
+      delta = -1;
+    if (wheelEvent->deltaY > 0)
+      delta = 1;
+
+    that->Scroll(delta);
+
+    return 1;
+  }
+  
   boost::shared_ptr<IViewport> RtViewerView::CreateViewport(
     const std::string& canvasId)
   {
-    return WebGLViewport::Create(canvasId);
+    boost::shared_ptr<IViewport> viewport = WebGLViewport::Create(canvasId);
+
+    void* userData = reinterpret_cast<void*>(this);
+
+    // manually add the mouse wheel handler
+
+    std::string selector = "#" + canvasId;
+
+    emscripten_set_wheel_callback_on_thread(
+      selector.c_str(),
+      userData,
+      false,
+      &RtViewerView_Scroll,
+      EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD);
+
+    return viewport;
   }
 
   void RtViewerView::TakeScreenshot(const std::string& target,
--- a/Samples/build-wasm-RtViewer.sh	Wed Apr 29 22:06:58 2020 +0200
+++ b/Samples/build-wasm-RtViewer.sh	Thu Apr 30 00:25:55 2020 +0200
@@ -2,10 +2,10 @@
 #
 # usage:
 # to build the sample in Debug:
-# ./build-wasm-SingleFrameViewer.sh
+# ./build-wasm-RtViewer.sh
 #
 # to build the sample in Debug:
-# ./build-wasm-SingleFrameViewer.sh Release
+# ./build-wasm-RtViewer.sh Release
 
 set -e
 
@@ -44,4 +44,4 @@
 
 echo "If all went well, the output files can be found in $installFolderName:"
 
-ls $installFolderName
\ No newline at end of file
+ls $installFolderName