changeset 1392:ffdb82850e98

Sdl run loop in /Common (might revert if need to customize) Segregation ongoing between sdl wasm Both samples ok SDL msvc v141 x64
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 28 Apr 2020 13:52:21 +0200
parents 32272ecfc6c2
children 27e0a00bd3e8
files Samples/Common/RtViewer.cpp Samples/Sdl/RtViewer/RtViewerSdl.cpp Samples/Sdl/SdlHelpers.h Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Samples/Sdl/SingleFrameViewer/SimpleViewer.cpp
diffstat 5 files changed, 449 insertions(+), 278 deletions(-) [+]
line wrap: on
line diff
--- a/Samples/Common/RtViewer.cpp	Tue Apr 28 13:26:41 2020 +0200
+++ b/Samples/Common/RtViewer.cpp	Tue Apr 28 13:52:21 2020 +0200
@@ -22,7 +22,6 @@
 #include "RtViewer.h"
 
 // Stone of Orthanc
-#include <Framework/OpenGL/SdlOpenGLContext.h>
 #include <Framework/StoneInitialization.h>
 #include <Framework/Scene2D/CairoCompositor.h>
 #include <Framework/Scene2D/ColorTextureSceneLayer.h>
@@ -176,168 +175,7 @@
     scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
   }
 
-  void RtViewerApp::HandleApplicationEvent(
-    const SDL_Event& event)
-  {
-    //DisplayInfoText();
 
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    ICompositor& compositor = lock->GetCompositor();
-
-    if (event.type == SDL_MOUSEMOTION)
-    {
-      int scancodeCount = 0;
-      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-      if (activeTracker_.get() == NULL &&
-          SDL_SCANCODE_LALT < scancodeCount &&
-          keyboardState[SDL_SCANCODE_LALT])
-      {
-        // The "left-ctrl" key is down, while no tracker is present
-        // Let's display the info text
-        PointerEvent e;
-        e.AddPosition(compositor.GetPixelCenterCoordinates(
-          event.button.x, event.button.y));
-
-        DisplayFloatingCtrlInfoText(e);
-      }
-      else
-      {
-        HideInfoText();
-        //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
-        if (activeTracker_.get() != NULL)
-        {
-          //LOG(TRACE) << "(activeTracker_.get() != NULL)";
-          PointerEvent e;
-          e.AddPosition(compositor.GetPixelCenterCoordinates(
-            event.button.x, event.button.y));
-
-          //LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
-          //  "event.button.y = " << event.button.y;
-          LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
-            e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
-
-          activeTracker_->PointerMove(e);
-          if (!activeTracker_->IsAlive())
-            activeTracker_.reset();
-        }
-      }
-    }
-    else if (event.type == SDL_MOUSEBUTTONUP)
-    {
-      if (activeTracker_)
-      {
-        PointerEvent e;
-        e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-        activeTracker_->PointerUp(e);
-        if (!activeTracker_->IsAlive())
-          activeTracker_.reset();
-      }
-    }
-    else if (event.type == SDL_MOUSEBUTTONDOWN)
-    {
-      PointerEvent e;
-      e.AddPosition(compositor.GetPixelCenterCoordinates(
-        event.button.x, event.button.y));
-      if (activeTracker_)
-      {
-        activeTracker_->PointerDown(e);
-        if (!activeTracker_->IsAlive())
-          activeTracker_.reset();
-      }
-      else
-      {
-        // we ATTEMPT to create a tracker if need be
-        activeTracker_ = CreateSuitableTracker(event, e);
-      }
-    }
-    else if (event.type == SDL_KEYDOWN &&
-             event.key.repeat == 0 /* Ignore key bounce */)
-    {
-      switch (event.key.keysym.sym)
-      {
-      case SDLK_ESCAPE:
-        if (activeTracker_)
-        {
-          activeTracker_->Cancel();
-          if (!activeTracker_->IsAlive())
-            activeTracker_.reset();
-        }
-        break;
-
-      case SDLK_r:
-        UpdateLayers();
-        {
-          std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-          lock->Invalidate();
-        }
-        break;
-
-      case SDLK_s:
-        compositor.FitContent(scene);
-        break;
-
-      case SDLK_t:
-        if (!activeTracker_)
-          SelectNextTool();
-        else
-        {
-          LOG(WARNING) << "You cannot change the active tool when an interaction"
-            " is taking place";
-        }
-        break;
-
-      case SDLK_z:
-        LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
-        if (event.key.keysym.mod & KMOD_CTRL)
-        {
-          if (controller.CanUndo())
-          {
-            LOG(TRACE) << "Undoing...";
-            controller.Undo();
-          }
-          else
-          {
-            LOG(WARNING) << "Nothing to undo!!!";
-          }
-        }
-        break;
-
-      case SDLK_y:
-        LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
-        if (event.key.keysym.mod & KMOD_CTRL)
-        {
-          if (controller.CanRedo())
-          {
-            LOG(TRACE) << "Redoing...";
-            controller.Redo();
-          }
-          else
-          {
-            LOG(WARNING) << "Nothing to redo!!!";
-          }
-        }
-        break;
-
-      case SDLK_c:
-        TakeScreenshot(
-          "screenshot.png",
-          compositor.GetCanvasWidth(),
-          compositor.GetCanvasHeight());
-        break;
-
-      default:
-        break;
-      }
-    }
-    else if (viewport_->IsRefreshEvent(event))
-    {
-      // the viewport has been invalidated and requires repaint
-      viewport_->Paint();
-    }
-  }
 
   void RtViewerApp::OnSceneTransformChanged(
     const ViewportController::SceneTransformChanged& message)
@@ -403,80 +241,6 @@
     lock->Invalidate();
   }
 
-  boost::shared_ptr<IFlexiblePointerTracker> RtViewerApp::CreateSuitableTracker(
-    const SDL_Event& event,
-    const PointerEvent& e)
-  {
-    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
-    ViewportController& controller = lock->GetController();
-    Scene2D& scene = controller.GetScene();
-    ICompositor& compositor = lock->GetCompositor();
-
-    using namespace Orthanc;
-
-    switch (event.button.button)
-    {
-    case SDL_BUTTON_MIDDLE:
-      return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker
-      (viewport_, e));
-
-    case SDL_BUTTON_RIGHT:
-      return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker
-      (viewport_, e, compositor.GetCanvasHeight()));
-
-    case SDL_BUTTON_LEFT:
-    {
-      //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:";
-      // TODO: we need to iterate on the set of measuring tool and perform
-      // a hit test to check if a tracker needs to be created for edition.
-      // Otherwise, depending upon the active tool, we might want to create
-      // a "measuring tool creation" tracker
-
-      // TODO: if there are conflicts, we should prefer a tracker that 
-      // pertains to the type of measuring tool currently selected (TBD?)
-      boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e);
-
-      if (hitTestTracker != NULL)
-      {
-        //LOG(TRACE) << "hitTestTracker != NULL";
-        return hitTestTracker;
-      }
-      else
-      {
-        switch (currentTool_)
-        {
-        case RtViewerGuiTool_Rotate:
-          //LOG(TRACE) << "Creating RotateSceneTracker";
-          return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker(viewport_, e));
-        case RtViewerGuiTool_Pan:
-          return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker(viewport_, e));
-        case RtViewerGuiTool_Zoom:
-          return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker(viewport_, e, compositor.GetCanvasHeight()));
-        //case GuiTool_AngleMeasure:
-        //  return new AngleMeasureTracker(GetScene(), e);
-        //case GuiTool_CircleMeasure:
-        //  return new CircleMeasureTracker(GetScene(), e);
-        //case GuiTool_EllipseMeasure:
-        //  return new EllipseMeasureTracker(GetScene(), e);
-        case RtViewerGuiTool_LineMeasure:
-          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker(viewport_, e));
-        case RtViewerGuiTool_AngleMeasure:
-          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker(viewport_, e));
-        case RtViewerGuiTool_CircleMeasure:
-          LOG(ERROR) << "Not implemented yet!";
-          return boost::shared_ptr<IFlexiblePointerTracker>();
-        case RtViewerGuiTool_EllipseMeasure:
-          LOG(ERROR) << "Not implemented yet!";
-          return boost::shared_ptr<IFlexiblePointerTracker>();
-        default:
-          throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
-        }
-      }
-    }
-    default:
-      return boost::shared_ptr<IFlexiblePointerTracker>();
-    }
-  }
 
 
   RtViewerApp::RtViewerApp()
@@ -793,46 +557,5 @@
       infoTextMap_[key] = value;
     DisplayInfoText();
   }
-
-}
-
-
-boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
-
-void RtViewer_SetInfoDisplayMessage(std::string key, std::string value)
-{
-  boost::shared_ptr<OrthancStone::RtViewerApp> app = g_app.lock();
-  if (app)
-  {
-    app->SetInfoDisplayMessage(key, value);
-  }
 }
 
-/**
- * IMPORTANT: The full arguments to "main()" are needed for SDL on
- * Windows. Otherwise, one gets the linking error "undefined reference
- * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
- **/
-int main(int argc, char* argv[])
-{
-  using namespace OrthancStone;
-
-  StoneInitialize();
-
-  try
-  {
-    boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
-    g_app = app;
-    app->RunSdl(argc,argv);
-  }
-  catch (Orthanc::OrthancException& e)
-  {
-    LOG(ERROR) << "EXCEPTION: " << e.What();
-  }
-
-  StoneFinalize();
-
-  return 0;
-}
-
-
--- a/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Tue Apr 28 13:26:41 2020 +0200
+++ b/Samples/Sdl/RtViewer/RtViewerSdl.cpp	Tue Apr 28 13:52:21 2020 +0200
@@ -19,9 +19,13 @@
  **/
 
 #include "RtViewer.h"
+#include "../SdlHelpers.h"
 #include "SampleHelpers.h"
 
 #include <Framework/StoneException.h>
+#include <Framework/StoneInitialization.h>
+
+#include <Framework/OpenGL/SdlOpenGLContext.h>
 
 #include <boost/program_options.hpp>
 #include <SDL.h>
@@ -130,6 +134,214 @@
       compositor.SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT,
                          FONT_SIZE_1, Orthanc::Encoding_Latin1);
     }
+
+    /**
+    Create the shared loaders context
+    */
+    loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
+    loadersContext_->StartOracle();
+
+    /**
+    It is very important that the Oracle (responsible for network I/O) be started before creating and firing the 
+    loaders, for any command scheduled by the loader before the oracle is started will be lost.
+    */
+    PrepareLoadersAndSlicers();
+
+    OrthancStone::DefaultViewportInteractor interactor;
+
+    OrthancStoneHelpers::SdlRunLoop(viewport_, interactor);
+
+    loadersContext_->StopOracle();
+  }
+
+#if 0
+  void RtViewerApp::HandleApplicationEvent(
+    const SDL_Event& event)
+  {
+    //DisplayInfoText();
+
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    ICompositor& compositor = lock->GetCompositor();
+
+    if (event.type == SDL_MOUSEMOTION)
+    {
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      if (activeTracker_.get() == NULL &&
+          SDL_SCANCODE_LALT < scancodeCount &&
+          keyboardState[SDL_SCANCODE_LALT])
+      {
+        // The "left-ctrl" key is down, while no tracker is present
+        // Let's display the info text
+        PointerEvent e;
+        e.AddPosition(compositor.GetPixelCenterCoordinates(
+          event.button.x, event.button.y));
+
+        DisplayFloatingCtrlInfoText(e);
+      }
+      else
+      {
+        HideInfoText();
+        //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
+        if (activeTracker_.get() != NULL)
+        {
+          //LOG(TRACE) << "(activeTracker_.get() != NULL)";
+          PointerEvent e;
+          e.AddPosition(compositor.GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+
+          //LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
+          //  "event.button.y = " << event.button.y;
+          LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
+            e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
+
+          activeTracker_->PointerMove(e);
+          if (!activeTracker_->IsAlive())
+            activeTracker_.reset();
+        }
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONUP)
+    {
+      if (activeTracker_)
+      {
+        PointerEvent e;
+        e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+        activeTracker_->PointerUp(e);
+        if (!activeTracker_->IsAlive())
+          activeTracker_.reset();
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      PointerEvent e;
+      e.AddPosition(compositor.GetPixelCenterCoordinates(
+        event.button.x, event.button.y));
+      if (activeTracker_)
+      {
+        activeTracker_->PointerDown(e);
+        if (!activeTracker_->IsAlive())
+          activeTracker_.reset();
+      }
+      else
+      {
+        // we ATTEMPT to create a tracker if need be
+        activeTracker_ = CreateSuitableTracker(event, e);
+      }
+    }
+    else if (event.type == SDL_KEYDOWN &&
+             event.key.repeat == 0 /* Ignore key bounce */)
+    {
+      switch (event.key.keysym.sym)
+      {
+      case SDLK_ESCAPE:
+        if (activeTracker_)
+        {
+          activeTracker_->Cancel();
+          if (!activeTracker_->IsAlive())
+            activeTracker_.reset();
+        }
+        break;
+
+      case SDLK_r:
+        UpdateLayers();
+        {
+          std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+          lock->Invalidate();
+        }
+        break;
+
+      case SDLK_s:
+        compositor.FitContent(scene);
+        break;
+
+      case SDLK_t:
+        if (!activeTracker_)
+          SelectNextTool();
+        else
+        {
+          LOG(WARNING) << "You cannot change the active tool when an interaction"
+            " is taking place";
+        }
+        break;
+
+      case SDLK_z:
+        LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller.CanUndo())
+          {
+            LOG(TRACE) << "Undoing...";
+            controller.Undo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to undo!!!";
+          }
+        }
+        break;
+
+      case SDLK_y:
+        LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller.CanRedo())
+          {
+            LOG(TRACE) << "Redoing...";
+            controller.Redo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to redo!!!";
+          }
+        }
+        break;
+
+      case SDLK_c:
+        TakeScreenshot(
+          "screenshot.png",
+          compositor.GetCanvasWidth(),
+          compositor.GetCanvasHeight());
+        break;
+
+      default:
+        break;
+      }
+    }
+    else if (viewport_->IsRefreshEvent(event))
+    {
+      // the viewport has been invalidated and requires repaint
+      viewport_->Paint();
+    }
+  }
+#endif 
+
+#if 0
+  void RtViewerApp::RunSdl(int argc, char* argv[])
+  {
+    ProcessOptions(argc, argv);
+
+    {
+      std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+      ViewportController& controller = lock->GetController();
+      Scene2D& scene = controller.GetScene();
+      ICompositor& compositor = lock->GetCompositor();
+
+      // False means we do NOT let a hi-DPI aware desktop managedr treat this as a legacy application that requires
+      // scaling.
+      controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight());
+
+      glEnable(GL_DEBUG_OUTPUT);
+      glDebugMessageCallback(OpenGLMessageCallback, 0);
+
+      compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
+                         FONT_SIZE_0, Orthanc::Encoding_Latin1);
+      compositor.SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT,
+                         FONT_SIZE_1, Orthanc::Encoding_Latin1);
+    }
                      //////// from loader
     
     loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
@@ -183,4 +395,113 @@
     }
     loadersContext_->StopOracle();
   }
+#endif
+
+#if 0
+  boost::shared_ptr<IFlexiblePointerTracker> RtViewerApp::CreateSuitableTracker(
+    const SDL_Event& event,
+    const PointerEvent& e)
+  {
+    std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
+    ViewportController& controller = lock->GetController();
+    Scene2D& scene = controller.GetScene();
+    ICompositor& compositor = lock->GetCompositor();
+
+    using namespace Orthanc;
+
+    switch (event.button.button)
+    {
+    case SDL_BUTTON_MIDDLE:
+      return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker
+      (viewport_, e));
+
+    case SDL_BUTTON_RIGHT:
+      return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker
+      (viewport_, e, compositor.GetCanvasHeight()));
+
+    case SDL_BUTTON_LEFT:
+    {
+      //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:";
+      // TODO: we need to iterate on the set of measuring tool and perform
+      // a hit test to check if a tracker needs to be created for edition.
+      // Otherwise, depending upon the active tool, we might want to create
+      // a "measuring tool creation" tracker
+
+      // TODO: if there are conflicts, we should prefer a tracker that 
+      // pertains to the type of measuring tool currently selected (TBD?)
+      boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e);
+
+      if (hitTestTracker != NULL)
+      {
+        //LOG(TRACE) << "hitTestTracker != NULL";
+        return hitTestTracker;
+      }
+      else
+      {
+        switch (currentTool_)
+        {
+        case RtViewerGuiTool_Rotate:
+          //LOG(TRACE) << "Creating RotateSceneTracker";
+          return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker(viewport_, e));
+        case RtViewerGuiTool_Pan:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker(viewport_, e));
+        case RtViewerGuiTool_Zoom:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker(viewport_, e, compositor.GetCanvasHeight()));
+        //case GuiTool_AngleMeasure:
+        //  return new AngleMeasureTracker(GetScene(), e);
+        //case GuiTool_CircleMeasure:
+        //  return new CircleMeasureTracker(GetScene(), e);
+        //case GuiTool_EllipseMeasure:
+        //  return new EllipseMeasureTracker(GetScene(), e);
+        case RtViewerGuiTool_LineMeasure:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker(viewport_, e));
+        case RtViewerGuiTool_AngleMeasure:
+          return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker(viewport_, e));
+        case RtViewerGuiTool_CircleMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return boost::shared_ptr<IFlexiblePointerTracker>();
+        case RtViewerGuiTool_EllipseMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return boost::shared_ptr<IFlexiblePointerTracker>();
+        default:
+          throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
+        }
+      }
+    }
+    default:
+      return boost::shared_ptr<IFlexiblePointerTracker>();
+    }
+  }
+#endif
+
 }
+
+boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
+
+/**
+ * IMPORTANT: The full arguments to "main()" are needed for SDL on
+ * Windows. Otherwise, one gets the linking error "undefined reference
+ * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
+ **/
+int main(int argc, char* argv[])
+{
+  using namespace OrthancStone;
+
+  StoneInitialize();
+
+  try
+  {
+    boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
+    g_app = app;
+    app->RunSdl(argc,argv);
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "EXCEPTION: " << e.What();
+  }
+
+  StoneFinalize();
+
+  return 0;
+}
+
--- a/Samples/Sdl/SdlHelpers.h	Tue Apr 28 13:26:41 2020 +0200
+++ b/Samples/Sdl/SdlHelpers.h	Tue Apr 28 13:52:21 2020 +0200
@@ -4,6 +4,10 @@
 # error This file cannot be used if ORTHANC_ENABLE_SDL != 1
 #endif
 
+#include <Framework/Viewport/SdlViewport.h>
+
+#include <boost/shared_ptr.hpp>
+
 #include <SDL.h>
 
 #include <map>
@@ -11,6 +15,7 @@
 
 namespace OrthancStoneHelpers
 {
+
   inline OrthancStone::KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState,
                                                               const int scancodeCount)
   {
@@ -93,5 +98,121 @@
     p.SetControlModifier(modifiers & KeyboardModifiers_Control);
     p.SetShiftModifier(modifiers & KeyboardModifiers_Shift);
   }
+
+
+  inline void SdlRunLoop(boost::shared_ptr<OrthancStone::SdlViewport> viewport,
+                         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 (viewport->IsRefreshEvent(event))
+          {
+            paint = true;
+          }
+          else if (event.type == SDL_WINDOWEVENT &&
+                   (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+                    event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED))
+          {
+            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))
+          {
+            paint = true;
+          }
+          else if (event.type == SDL_KEYDOWN &&
+                   event.key.repeat == 0 /* Ignore key bounce */)
+          {
+            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)
+          {
+            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);
+              }
+            }
+          }
+        }
+
+        if (paint)
+        {
+          viewport->Paint();
+        }
+
+        // Small delay to avoid using 100% of CPU
+        SDL_Delay(1);
+      }
+    }
+  }
+
+
+
+
 }
 
--- a/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Apr 28 13:26:41 2020 +0200
+++ b/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp	Tue Apr 28 13:52:21 2020 +0200
@@ -117,6 +117,11 @@
 
         OrthancStone::DefaultViewportInteractor interactor;
 
+#if 1
+        OrthancStoneHelpers::SdlRunLoop(viewport, interactor);
+
+#else
+
         {
           int scancodeCount = 0;
           const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
@@ -218,7 +223,7 @@
             SDL_Delay(1);
           }
         }
-
+#endif
         context.StopOracle();
       }
     }
--- a/Samples/Sdl/SingleFrameViewer/SimpleViewer.cpp	Tue Apr 28 13:26:41 2020 +0200
+++ b/Samples/Sdl/SingleFrameViewer/SimpleViewer.cpp	Tue Apr 28 13:52:21 2020 +0200
@@ -1,3 +1,4 @@
+#error TO BE DELETED
 
 #include "SdlSimpleViewerApplication.h"