comparison OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyViewport.cpp @ 1899:917500c46fe0

moved the Platform folder from the Applications folder to the Stone library itself
author Alain Mazy <am@osimis.io>
date Sat, 29 Jan 2022 12:47:32 +0100
parents
children 184b0aeae1af
comparison
equal deleted inserted replaced
1898:a5e54bd87b25 1899:917500c46fe0
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 **/
22
23
24 #if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
25 # include "WebAssemblyViewport.h"
26 # include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h"
27 # include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h"
28 # include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h"
29 #else
30 // This is the case when using the WebAssembly side module, and this
31 // source file must be compiled within the WebAssembly main module
32 # include <Viewport/WebAssemblyViewport.h>
33 # include <Toolbox/GenericToolbox.h>
34 # include <Scene2DViewport/ViewportController.h>
35 # include <Viewport/DefaultViewportInteractor.h>
36 #endif
37
38
39 #include <OrthancException.h>
40
41 #include <boost/make_shared.hpp>
42 #include <boost/enable_shared_from_this.hpp>
43 #include <boost/math/special_functions/round.hpp>
44
45 namespace OrthancStone
46 {
47 static void ConvertMouseEvent(PointerEvent& target,
48 const EmscriptenMouseEvent& source,
49 const ICompositor& compositor)
50 {
51 int x = static_cast<int>(source.targetX);
52 int y = static_cast<int>(source.targetY);
53
54 switch (source.button)
55 {
56 case 0:
57 target.SetMouseButton(MouseButton_Left);
58 break;
59
60 case 1:
61 target.SetMouseButton(MouseButton_Middle);
62 break;
63
64 case 2:
65 target.SetMouseButton(MouseButton_Right);
66 break;
67
68 default:
69 target.SetMouseButton(MouseButton_None);
70 break;
71 }
72
73 target.AddPosition(compositor.GetPixelCenterCoordinates(x, y));
74 target.SetAltModifier(source.altKey);
75 target.SetControlModifier(source.ctrlKey);
76 target.SetShiftModifier(source.shiftKey);
77 }
78
79
80 class WebAssemblyViewport::WasmLock : public ILock
81 {
82 private:
83 WebAssemblyViewport& that_;
84
85 public:
86 WasmLock(WebAssemblyViewport& that) :
87 that_(that)
88 {
89 }
90
91 virtual bool HasCompositor() const ORTHANC_OVERRIDE
92 {
93 return that_.compositor_.get() != NULL;
94 }
95
96 virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE
97 {
98 if (that_.compositor_.get() == NULL)
99 {
100 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
101 }
102 else
103 {
104 return *that_.compositor_;
105 }
106 }
107
108 virtual ViewportController& GetController() ORTHANC_OVERRIDE
109 {
110 assert(that_.controller_);
111 return *that_.controller_;
112 }
113
114 virtual void Invalidate() ORTHANC_OVERRIDE
115 {
116 that_.Invalidate();
117 }
118
119 virtual void RefreshCanvasSize() ORTHANC_OVERRIDE
120 {
121 that_.RefreshCanvasSize();
122 }
123 };
124
125 EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData)
126 {
127 LOG(TRACE) << __func__;
128
129 WebAssemblyViewport* that =
130 WebAssemblyViewport::DereferenceObjectCookie(userData);
131
132 if (that != NULL)
133 {
134 if (that->compositor_.get() != NULL &&
135 that->controller_ /* should always be true */)
136 {
137 that->Paint(*that->compositor_, *that->controller_);
138 }
139 }
140 else
141 {
142 LOG(TRACE) << "WebAssemblyViewport::OnRequestAnimationFrame: the " <<
143 "WebAssemblyViewport is deleted and Paint will not be called.";
144 }
145 WebAssemblyViewport::ReleaseObjectCookie(userData);
146 LOG(TRACE) << "Exiting: " << __func__;
147 return true;
148 }
149
150 EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
151 {
152 LOG(TRACE) << __func__;
153 WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
154
155 if (that->compositor_.get() != NULL)
156 {
157 that->RefreshCanvasSize();
158 that->Invalidate();
159 }
160
161 LOG(TRACE) << "Exiting: " << __func__;
162 return true;
163 }
164
165
166 EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
167 {
168 WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
169
170 LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector();
171
172 if (that->compositor_.get() != NULL &&
173 that->interactor_.get() != NULL)
174 {
175 PointerEvent pointer;
176 ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
177
178 that->controller_->HandleMousePress(*that->interactor_, pointer,
179 that->compositor_->GetCanvasWidth(),
180 that->compositor_->GetCanvasHeight());
181 that->Invalidate();
182 }
183
184 LOG(TRACE) << "Exiting: " << __func__;
185 return true;
186 }
187
188
189 EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
190 {
191 WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
192
193 if (that->compositor_.get() != NULL)
194 {
195 if (that->controller_->HasActiveTracker())
196 {
197 PointerEvent pointer;
198 ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
199
200 if (that->controller_->HandleMouseMove(pointer))
201 {
202 that->Invalidate();
203 }
204 }
205 else if (that->interactor_.get() != NULL &&
206 that->interactor_->HasMouseHover())
207 {
208 // New in Stone Web viewer 2.0
209 PointerEvent pointer;
210 ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
211 that->interactor_->HandleMouseHover(*that, pointer);
212 }
213 }
214
215 LOG(TRACE) << "Exiting: " << __func__;
216 return true;
217 }
218
219 EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
220 {
221 LOG(TRACE) << __func__;
222 WebAssemblyViewport* that = reinterpret_cast<WebAssemblyViewport*>(userData);
223
224 if (that->compositor_.get() != NULL)
225 {
226 PointerEvent pointer;
227 ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_);
228 that->controller_->HandleMouseRelease(pointer);
229 that->Invalidate();
230 }
231
232 LOG(TRACE) << "Exiting: " << __func__;
233 return true;
234 }
235
236 void* WebAssemblyViewport::CreateObjectCookie()
237 {
238 boost::weak_ptr<WebAssemblyViewport>* weakThisPtr =
239 new boost::weak_ptr<WebAssemblyViewport>();
240
241 *weakThisPtr = shared_from_this();
242
243 void* cookie = reinterpret_cast<void*>(weakThisPtr);
244
245 LOG(TRACE) << "WebAssemblyViewport::CreateObjectCookie() => cookie = "
246 << cookie << "\n";
247
248 return cookie;
249 }
250
251 WebAssemblyViewport* WebAssemblyViewport::DereferenceObjectCookie(void* cookie)
252 {
253 LOG(TRACE) << "WebAssemblyViewport::DereferenceObjectCookie(cookie = "
254 << cookie << ")\n";
255
256 boost::weak_ptr<WebAssemblyViewport>* weakThisPtr =
257 reinterpret_cast<boost::weak_ptr<WebAssemblyViewport>*>(cookie);
258
259 boost::shared_ptr<WebAssemblyViewport> sharedThis = weakThisPtr->lock();
260
261 return sharedThis.get();
262 }
263
264 void WebAssemblyViewport::ReleaseObjectCookie(void* cookie)
265 {
266 LOG(TRACE) << "WebAssemblyViewport::ReleaseObjectCookie(cookie = "
267 << cookie << ")\n";
268
269 boost::weak_ptr<WebAssemblyViewport>* weakThisPtr =
270 reinterpret_cast<boost::weak_ptr<WebAssemblyViewport>*>(cookie);
271
272 delete weakThisPtr;
273 }
274
275 void WebAssemblyViewport::Invalidate()
276 {
277 LOG(TRACE) << "WebAssemblyViewport::Invalidate()\n";
278 long id = emscripten_request_animation_frame(OnRequestAnimationFrame,
279 CreateObjectCookie());
280 //animationFrameCallbackIds_.push_back(id);
281 }
282
283 void WebAssemblyViewport::FitForPrint()
284 {
285 if (compositor_.get() != NULL &&
286 controller_ /* should always be true */)
287 {
288 RefreshCanvasSize();
289 compositor_->FitContent(controller_->GetScene());
290
291 void* cookie = CreateObjectCookie();
292 OnRequestAnimationFrame(0, cookie); // Mandatory to work with Firefox
293 }
294 }
295
296 void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
297 {
298 if (compositor == NULL)
299 {
300 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
301 }
302 else
303 {
304 compositor_.reset(compositor);
305 }
306 }
307
308 #if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
309 // everything OK..... we're using the new setting
310 #else
311 #pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.")
312 #endif
313
314 WebAssemblyViewport::WebAssemblyViewport(
315 const std::string& canvasId, bool enableEmscriptenMouseEvents) :
316 canvasId_(canvasId),
317 #if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1
318 canvasCssSelector_("#" + canvasId),
319 #else
320 canvasCssSelector_(canvasId),
321 #endif
322 interactor_(new DefaultViewportInteractor),
323 enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents),
324 canvasWidth_(0),
325 canvasHeight_(0)
326 {
327 }
328
329 void WebAssemblyViewport::PostConstructor()
330 {
331 boost::shared_ptr<IViewport> viewport = shared_from_this();
332 controller_.reset(new ViewportController(viewport));
333
334 LOG(INFO) << "Initializing Stone viewport on HTML canvas: "
335 << canvasId_;
336
337 if (canvasId_.empty() ||
338 canvasId_[0] == '#')
339 {
340 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
341 "The canvas identifier must not start with '#'");
342 }
343
344 // Disable right-click on the canvas (i.e. context menu)
345 EM_ASM({
346 document.getElementById(UTF8ToString($0)).oncontextmenu =
347 function(event)
348 {
349 event.preventDefault();
350 }
351 },
352 canvasId_.c_str() // $0
353 );
354
355 // It is not possible to monitor the resizing of individual
356 // canvas, so we track the full window of the browser
357 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
358 reinterpret_cast<void*>(this),
359 false,
360 OnResize);
361
362 if (enableEmscriptenMouseEvents_)
363 {
364
365 // if any of this function causes an error in the console, please
366 // make sure you are using the new (as of 1.39.x) version of
367 // emscripten element lookup rules( pass
368 // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker.
369
370 emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
371 reinterpret_cast<void*>(this),
372 false,
373 OnMouseDown);
374
375 emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
376 reinterpret_cast<void*>(this),
377 false,
378 OnMouseMove);
379
380 emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
381 reinterpret_cast<void*>(this),
382 false,
383 OnMouseUp);
384 }
385 }
386
387 WebAssemblyViewport::~WebAssemblyViewport()
388 {
389 LOG(TRACE) << "WebAssemblyViewport::~WebAssemblyViewport()\n";
390
391 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW,
392 reinterpret_cast<void*>(this),
393 false,
394 NULL);
395
396 if (enableEmscriptenMouseEvents_)
397 {
398
399 emscripten_set_mousedown_callback(canvasCssSelector_.c_str(),
400 reinterpret_cast<void*>(this),
401 false,
402 NULL);
403
404 emscripten_set_mousemove_callback(canvasCssSelector_.c_str(),
405 reinterpret_cast<void*>(this),
406 false,
407 NULL);
408
409 emscripten_set_mouseup_callback(canvasCssSelector_.c_str(),
410 reinterpret_cast<void*>(this),
411 false,
412 NULL);
413 }
414 }
415
416 IViewport::ILock* WebAssemblyViewport::Lock()
417 {
418 return new WasmLock(*this);
419 }
420
421 void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor)
422 {
423 if (interactor == NULL)
424 {
425 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
426 }
427 else
428 {
429 interactor_.reset(interactor);
430 }
431 }
432
433
434 void WebAssemblyViewport::RefreshCanvasSize()
435 {
436 double w = -1, h = -1;
437 EMSCRIPTEN_RESULT result =
438 emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h);
439
440 if (result != EMSCRIPTEN_RESULT_SUCCESS)
441 {
442 LOG(WARNING) << "WebAssemblyViewport::RefreshCanvasSize failed to "
443 << "retrieve CSS size for " << GetCanvasCssSelector();
444 }
445
446 /**
447 * Emscripten has the function emscripten_get_element_css_size()
448 * to query the width and height of a named HTML element. I'm
449 * calling this first to get the initial size of the canvas DOM
450 * element, and then call emscripten_set_canvas_size() to
451 * initialize the framebuffer size of the canvas to the same
452 * size as its DOM element.
453 * https://floooh.github.io/2017/02/22/emsc-html.html
454 **/
455 if (w > 0 &&
456 h > 0)
457 {
458 canvasWidth_ = static_cast<unsigned int>(boost::math::iround(w));
459 canvasHeight_ = static_cast<unsigned int>(boost::math::iround(h));
460 }
461 else
462 {
463 canvasWidth_ = 0;
464 canvasHeight_ = 0;
465 }
466
467 emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_);
468
469 if (compositor_.get() != NULL)
470 {
471 compositor_->SetCanvasSize(canvasWidth_, canvasHeight_);
472 }
473 }
474 }