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