comparison Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp @ 1591:5887a4f8594b

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