Mercurial > hg > orthanc-stone
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 } |