aa5e67228cdddcb3888bf8a82f5c1379437b4af4
[occt.git] / samples / webgl / WasmOcctView.cpp
1 // Copyright (c) 2019 OPEN CASCADE SAS
2 //
3 // This file is part of the examples of the Open CASCADE Technology software library.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
21
22 #include "WasmOcctView.h"
23
24 #include "WasmVKeys.h"
25
26 #include <AIS_Shape.hxx>
27 #include <AIS_ViewCube.hxx>
28 #include <Aspect_Handle.hxx>
29 #include <Aspect_DisplayConnection.hxx>
30 #include <Aspect_NeutralWindow.hxx>
31 #include <Message.hxx>
32 #include <Message_Messenger.hxx>
33 #include <OpenGl_GraphicDriver.hxx>
34 #include <Prs3d_DatumAspect.hxx>
35
36 #include <iostream>
37
38 #define THE_CANVAS_ID "canvas"
39
40 namespace
41 {
42   EM_JS(int, jsCanvasGetWidth, (), {
43     return canvas.width;
44   });
45
46   EM_JS(int, jsCanvasGetHeight, (), {
47     return canvas.height;
48   });
49
50   EM_JS(float, jsDevicePixelRatio, (), {
51     var aDevicePixelRatio = window.devicePixelRatio || 1;
52     return aDevicePixelRatio;
53   });
54
55   //! Return cavas size in pixels.
56   static Graphic3d_Vec2i jsCanvasSize()
57   {
58     return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight());
59   }
60 }
61
62 // ================================================================
63 // Function : WasmOcctView
64 // Purpose  :
65 // ================================================================
66 WasmOcctView::WasmOcctView()
67 : myDevicePixelRatio (1.0f),
68   myUpdateRequests (0)
69 {
70 }
71
72 // ================================================================
73 // Function : ~WasmOcctView
74 // Purpose  :
75 // ================================================================
76 WasmOcctView::~WasmOcctView()
77 {
78 }
79
80 // ================================================================
81 // Function : run
82 // Purpose  :
83 // ================================================================
84 void WasmOcctView::run()
85 {
86   initWindow();
87   initViewer();
88   initDemoScene();
89   if (myView.IsNull())
90   {
91     return;
92   }
93
94   myView->MustBeResized();
95   myView->Redraw();
96
97   // There is no inifinite message loop, main() will return from here immediately.
98   // Tell that our Module should be left loaded and handle events through callbacks.
99   //emscripten_set_main_loop (redrawView, 60, 1);
100   //emscripten_set_main_loop (redrawView, -1, 1);
101   EM_ASM(Module['noExitRuntime'] = true);
102 }
103
104 // ================================================================
105 // Function : initWindow
106 // Purpose  :
107 // ================================================================
108 void WasmOcctView::initWindow()
109 {
110   myDevicePixelRatio = jsDevicePixelRatio();
111   myCanvasId = THE_CANVAS_ID;
112   const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW;
113   const EM_BOOL toUseCapture = EM_TRUE;
114   emscripten_set_resize_callback     (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onResizeCallback);
115
116   emscripten_set_mousedown_callback  (aTargetId, this, toUseCapture, onMouseCallback);
117   emscripten_set_mouseup_callback    (aTargetId, this, toUseCapture, onMouseCallback);
118   emscripten_set_mousemove_callback  (aTargetId, this, toUseCapture, onMouseCallback);
119   emscripten_set_dblclick_callback   (aTargetId, this, toUseCapture, onMouseCallback);
120   emscripten_set_click_callback      (aTargetId, this, toUseCapture, onMouseCallback);
121   emscripten_set_mouseenter_callback (aTargetId, this, toUseCapture, onMouseCallback);
122   emscripten_set_mouseleave_callback (aTargetId, this, toUseCapture, onMouseCallback);
123   emscripten_set_wheel_callback      (aTargetId, this, toUseCapture, onWheelCallback);
124
125   emscripten_set_touchstart_callback (aTargetId, this, toUseCapture, onTouchCallback);
126   emscripten_set_touchend_callback   (aTargetId, this, toUseCapture, onTouchCallback);
127   emscripten_set_touchmove_callback  (aTargetId, this, toUseCapture, onTouchCallback);
128   emscripten_set_touchcancel_callback(aTargetId, this, toUseCapture, onTouchCallback);
129
130   //emscripten_set_keypress_callback   (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyCallback);
131   emscripten_set_keydown_callback    (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyDownCallback);
132   emscripten_set_keyup_callback      (EMSCRIPTEN_EVENT_TARGET_WINDOW, this, toUseCapture, onKeyUpCallback);
133 }
134
135 // ================================================================
136 // Function : dumpGlInfo
137 // Purpose  :
138 // ================================================================
139 void WasmOcctView::dumpGlInfo (bool theIsBasic)
140 {
141   TColStd_IndexedDataMapOfStringString aGlCapsDict;
142   myView->DiagnosticInformation (aGlCapsDict, theIsBasic ? Graphic3d_DiagnosticInfo_Basic : Graphic3d_DiagnosticInfo_Complete);
143   if (theIsBasic)
144   {
145     TCollection_AsciiString aViewport;
146     aGlCapsDict.FindFromKey ("Viewport", aViewport);
147     aGlCapsDict.Clear();
148     aGlCapsDict.Add ("Viewport", aViewport);
149   }
150   aGlCapsDict.Add ("Display scale", TCollection_AsciiString(myDevicePixelRatio));
151
152   // beautify output
153   {
154     TCollection_AsciiString* aGlVer   = aGlCapsDict.ChangeSeek ("GLversion");
155     TCollection_AsciiString* aGlslVer = aGlCapsDict.ChangeSeek ("GLSLversion");
156     if (aGlVer   != NULL
157      && aGlslVer != NULL)
158     {
159       *aGlVer = *aGlVer + " [GLSL: " + *aGlslVer + "]";
160       aGlslVer->Clear();
161     }
162   }
163
164   TCollection_AsciiString anInfo;
165   for (TColStd_IndexedDataMapOfStringString::Iterator aValueIter (aGlCapsDict); aValueIter.More(); aValueIter.Next())
166   {
167     if (!aValueIter.Value().IsEmpty())
168     {
169       if (!anInfo.IsEmpty())
170       {
171         anInfo += "\n";
172       }
173       anInfo += aValueIter.Key() + ": " + aValueIter.Value();
174     }
175   }
176
177   ::Message::DefaultMessenger()->Send (anInfo, Message_Warning);
178 }
179
180 // ================================================================
181 // Function : initPixelScaleRatio
182 // Purpose  :
183 // ================================================================
184 void WasmOcctView::initPixelScaleRatio()
185 {
186   SetTouchToleranceScale (myDevicePixelRatio);
187   if (!myView.IsNull())
188   {
189     myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
190   }
191   if (!myContext.IsNull())
192   {
193     myContext->SetPixelTolerance (int(myDevicePixelRatio * 6.0));
194     if (!myViewCube.IsNull())
195     {
196       static const double THE_CUBE_SIZE = 60.0;
197       myViewCube->SetSize (myDevicePixelRatio * THE_CUBE_SIZE, false);
198       myViewCube->SetBoxFacetExtension (myViewCube->Size() * 0.15);
199       myViewCube->SetAxesPadding (myViewCube->Size() * 0.10);
200       myViewCube->SetFontHeight  (THE_CUBE_SIZE * 0.16);
201       if (myViewCube->HasInteractiveContext())
202       {
203         myContext->Redisplay (myViewCube, false);
204       }
205     }
206   }
207 }
208
209 // ================================================================
210 // Function : initViewer
211 // Purpose  :
212 // ================================================================
213 bool WasmOcctView::initViewer()
214 {
215   // Build with "--preload-file MyFontFile.ttf" option
216   // and register font in Font Manager to use custom font(s).
217   /*const char* aFontPath = "MyFontFile.ttf";
218   if (Handle(Font_SystemFont) aFont = Font_FontMgr::GetInstance()->CheckFont (aFontPath))
219   {
220     Font_FontMgr::GetInstance()->RegisterFont (aFont, true);
221   }
222   else
223   {
224     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: font '") + aFontPath + "' is not found", Message_Fail);
225   }*/
226
227   Handle(Aspect_DisplayConnection) aDisp;
228   Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver (aDisp, false);
229   aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL
230   if (!aDriver->InitContext())
231   {
232     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: EGL initialization failed"), Message_Fail);
233     return false;
234   }
235
236   Handle(V3d_Viewer) aViewer = new V3d_Viewer (aDriver);
237   aViewer->SetComputedMode (false);
238   aViewer->SetDefaultShadingModel (Graphic3d_TOSM_FRAGMENT);
239   aViewer->SetDefaultLights();
240   aViewer->SetLightOn();
241
242   Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow();
243   Graphic3d_Vec2i aWinSize = jsCanvasSize();
244   if (aWinSize.x() < 10 || aWinSize.y() < 10)
245   {
246     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
247   }
248   aWindow->SetSize (aWinSize.x(), aWinSize.y());
249
250   myTextStyle = new Prs3d_TextAspect();
251   myTextStyle->SetFont (Font_NOF_ASCII_MONO);
252   myTextStyle->SetHeight (12);
253   myTextStyle->Aspect()->SetColor (Quantity_NOC_GRAY95);
254   myTextStyle->Aspect()->SetColorSubTitle (Quantity_NOC_BLACK);
255   myTextStyle->Aspect()->SetDisplayType (Aspect_TODT_SHADOW);
256   myTextStyle->Aspect()->SetTextFontAspect (Font_FA_Bold);
257   myTextStyle->Aspect()->SetTextZoomable (false);
258   myTextStyle->SetHorizontalJustification (Graphic3d_HTA_LEFT);
259   myTextStyle->SetVerticalJustification (Graphic3d_VTA_BOTTOM);
260
261   myView = new V3d_View (aViewer);
262   myView->SetImmediateUpdate (false);
263   myView->ChangeRenderingParams().Resolution = (unsigned int )(96.0 * myDevicePixelRatio + 0.5);
264   myView->ChangeRenderingParams().ToShowStats = true;
265   myView->ChangeRenderingParams().StatsTextAspect = myTextStyle->Aspect();
266   myView->ChangeRenderingParams().StatsTextHeight = (int )myTextStyle->Height();
267   myView->SetWindow (aWindow);
268   dumpGlInfo (false);
269
270   myContext = new AIS_InteractiveContext (aViewer);
271   initPixelScaleRatio();
272   return true;
273 }
274
275 // ================================================================
276 // Function : initDemoScene
277 // Purpose  :
278 // ================================================================
279 void WasmOcctView::initDemoScene()
280 {
281   if (myContext.IsNull())
282   {
283     return;
284   }
285
286   //myView->TriedronDisplay (Aspect_TOTP_LEFT_LOWER, Quantity_NOC_GOLD, 0.08, V3d_WIREFRAME);
287
288   myViewCube = new AIS_ViewCube();
289   // presentation parameters
290   initPixelScaleRatio();
291   myViewCube->SetTransformPersistence (new Graphic3d_TransformPers (Graphic3d_TMF_TriedronPers, Aspect_TOTP_RIGHT_LOWER, Graphic3d_Vec2i (100, 100)));
292   myViewCube->Attributes()->SetDatumAspect (new Prs3d_DatumAspect());
293   myViewCube->Attributes()->DatumAspect()->SetTextAspect (myTextStyle);
294   // animation parameters
295   myViewCube->SetViewAnimation (myViewAnimation);
296   myViewCube->SetFixedAnimationLoop (false);
297   myViewCube->SetAutoStartAnimation (true);
298   myContext->Display (myViewCube, false);
299
300   // Build with "--preload-file MySampleFile.brep" option to load some shapes here.
301 }
302
303
304 // ================================================================
305 // Function : updateView
306 // Purpose  :
307 // ================================================================
308 void WasmOcctView::updateView()
309 {
310   if (!myView.IsNull())
311   {
312     if (++myUpdateRequests == 1)
313     {
314       emscripten_async_call (onRedrawView, this, 0);
315     }
316   }
317 }
318
319 // ================================================================
320 // Function : redrawView
321 // Purpose  :
322 // ================================================================
323 void WasmOcctView::redrawView()
324 {
325   if (!myView.IsNull())
326   {
327     FlushViewEvents (myContext, myView, true);
328   }
329 }
330
331 // ================================================================
332 // Function : handleViewRedraw
333 // Purpose  :
334 // ================================================================
335 void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
336                                      const Handle(V3d_View)& theView)
337 {
338   myUpdateRequests = 0;
339   AIS_ViewController::handleViewRedraw (theCtx, theView);
340   if (myToAskNextFrame)
341   {
342     // ask more frames
343     ++myUpdateRequests;
344     emscripten_async_call (onRedrawView, this, 0);
345   }
346 }
347
348 // ================================================================
349 // Function : onResizeEvent
350 // Purpose  :
351 // ================================================================
352 EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* theEvent)
353 {
354   (void )theEventType; // EMSCRIPTEN_EVENT_RESIZE or EMSCRIPTEN_EVENT_CANVASRESIZED
355   (void )theEvent;
356   if (myView.IsNull())
357   {
358     return EM_FALSE;
359   }
360
361   Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window());
362   Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize());
363   if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10)
364   {
365     Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning);
366   }
367   aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y());
368   const float aPixelRatio = jsDevicePixelRatio();
369   if (aWinSizeNew != aWinSizeOld
370    || aPixelRatio != myDevicePixelRatio)
371   {
372     if (myDevicePixelRatio != aPixelRatio)
373     {
374       myDevicePixelRatio = aPixelRatio;
375       initPixelScaleRatio();
376     }
377     aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y());
378     myView->MustBeResized();
379     myView->Invalidate();
380     myView->Redraw();
381     dumpGlInfo (true);
382   }
383   return EM_TRUE;
384 }
385
386 // ================================================================
387 // Function : onMouseEvent
388 // Purpose  :
389 // ================================================================
390 EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent* theEvent)
391 {
392   if (myView.IsNull())
393   {
394     return EM_FALSE;
395   }
396
397   Graphic3d_Vec2i aWinSize;
398   myView->Window()->Size (aWinSize.x(), aWinSize.y());
399   const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->targetX, theEvent->targetY));
400   Aspect_VKeyFlags aFlags = 0;
401   if (theEvent->ctrlKey  == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL;  }
402   if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; }
403   if (theEvent->altKey   == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT;   }
404   if (theEvent->metaKey  == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META;  }
405
406   const bool isEmulated = false;
407   const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons);
408   switch (theEventType)
409   {
410     case EMSCRIPTEN_EVENT_MOUSEMOVE:
411     {
412       if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
413         || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
414         && PressedMouseButtons() == Aspect_VKeyMouse_NONE)
415       {
416         return EM_FALSE;
417       }
418       if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated))
419       {
420         updateView();
421       }
422       break;
423     }
424     case EMSCRIPTEN_EVENT_MOUSEDOWN:
425     case EMSCRIPTEN_EVENT_MOUSEUP:
426     {
427       if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
428        || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
429       {
430         return EM_FALSE;
431       }
432       if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated))
433       {
434         updateView();
435       }
436       break;
437     }
438     case EMSCRIPTEN_EVENT_CLICK:
439     case EMSCRIPTEN_EVENT_DBLCLICK:
440     {
441       if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
442        || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
443       {
444         return EM_FALSE;
445       }
446       break;
447     }
448     case EMSCRIPTEN_EVENT_MOUSEENTER:
449     {
450       break;
451     }
452     case EMSCRIPTEN_EVENT_MOUSELEAVE:
453     {
454       // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive,
455       // so we have to forget current state...
456       if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated))
457       {
458         updateView();
459       }
460       break;
461     }
462   }
463   return EM_TRUE;
464 }
465
466 // ================================================================
467 // Function : onWheelEvent
468 // Purpose  :
469 // ================================================================
470 EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent* theEvent)
471 {
472   if (myView.IsNull()
473    || theEventType != EMSCRIPTEN_EVENT_WHEEL)
474   {
475     return EM_FALSE;
476   }
477
478   Graphic3d_Vec2i aWinSize;
479   myView->Window()->Size (aWinSize.x(), aWinSize.y());
480   const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.targetX, theEvent->mouse.targetY));
481   if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x()
482    || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y())
483   {
484     return EM_FALSE;
485   }
486
487   double aDelta = 0.0;
488   switch (theEvent->deltaMode)
489   {
490     case DOM_DELTA_PIXEL:
491     {
492       aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio);
493       break;
494     }
495     case DOM_DELTA_LINE:
496     {
497       aDelta = theEvent->deltaY * 8.0;
498       break;
499     }
500     case DOM_DELTA_PAGE:
501     {
502       aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0;
503       break;
504     }
505   }
506
507   if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta)))
508   {
509     updateView();
510   }
511   return EM_TRUE;
512 }
513
514 // ================================================================
515 // Function : onTouchEvent
516 // Purpose  :
517 // ================================================================
518 EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent)
519 {
520   const double aClickTolerance = 5.0;
521   if (myView.IsNull())
522   {
523     return EM_FALSE;
524   }
525
526   Graphic3d_Vec2i aWinSize;
527   myView->Window()->Size (aWinSize.x(), aWinSize.y());
528   bool hasUpdates = false;
529   for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter)
530   {
531     const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter];
532     if (!aTouch.isChanged)
533     {
534       continue;
535     }
536
537     const Standard_Size aTouchId = (Standard_Size )aTouch.identifier;
538     const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.canvasX, aTouch.canvasY));
539     switch (theEventType)
540     {
541       case EMSCRIPTEN_EVENT_TOUCHSTART:
542       {
543         if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x()
544          && aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y())
545         {
546           hasUpdates = true;
547           AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
548           myClickTouch.From.SetValues (-1.0, -1.0);
549           if (myTouchPoints.Extent() == 1)
550           {
551             myClickTouch.From = Graphic3d_Vec2d (aNewPos);
552           }
553         }
554         break;
555       }
556       case EMSCRIPTEN_EVENT_TOUCHMOVE:
557       {
558         const int anOldIndex = myTouchPoints.FindIndex (aTouchId);
559         if (anOldIndex != 0)
560         {
561           hasUpdates = true;
562           UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos));
563           if (myTouchPoints.Extent() == 1
564            && (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance)
565           {
566             myClickTouch.From.SetValues (-1.0, -1.0);
567           }
568         }
569         break;
570       }
571       case EMSCRIPTEN_EVENT_TOUCHEND:
572       case EMSCRIPTEN_EVENT_TOUCHCANCEL:
573       {
574         if (RemoveTouchPoint (aTouchId))
575         {
576           if (myTouchPoints.IsEmpty()
577            && myClickTouch.From.minComp() >= 0.0)
578           {
579             if (myDoubleTapTimer.IsStarted()
580              && myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt)
581             {
582               myView->FitAll (0.01, false);
583               myView->Invalidate();
584             }
585             else
586             {
587               myDoubleTapTimer.Stop();
588               myDoubleTapTimer.Reset();
589               myDoubleTapTimer.Start();
590               SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), false);
591             }
592           }
593           hasUpdates = true;
594         }
595         break;
596       }
597     }
598   }
599   if (hasUpdates)
600   {
601     updateView();
602   }
603   return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE;
604 }
605
606 // ================================================================
607 // Function : onKeyDownEvent
608 // Purpose  :
609 // ================================================================
610 EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
611 {
612   if (myView.IsNull()
613    || theEventType != EMSCRIPTEN_EVENT_KEYDOWN) // EMSCRIPTEN_EVENT_KEYPRESS
614   {
615     return EM_FALSE;
616   }
617
618   const double aTimeStamp = EventTime();
619   const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
620   if (aVKey == Aspect_VKey_UNKNOWN)
621   {
622     return EM_FALSE;
623   }
624
625   if (theEvent->repeat == EM_FALSE)
626   {
627     myKeys.KeyDown (aVKey, aTimeStamp);
628   }
629
630   if (Aspect_VKey2Modifier (aVKey) == 0)
631   {
632     // normal key
633   }
634   return EM_FALSE;
635 }
636
637 // ================================================================
638 // Function : onKeyUpEvent
639 // Purpose  :
640 // ================================================================
641 EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEvent* theEvent)
642 {
643   if (myView.IsNull()
644    || theEventType != EMSCRIPTEN_EVENT_KEYUP)
645   {
646     return EM_FALSE;
647   }
648
649   const double aTimeStamp = EventTime();
650   const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode);
651   if (aVKey == Aspect_VKey_UNKNOWN)
652   {
653     return EM_FALSE;
654   }
655
656   if (theEvent->repeat == EM_TRUE)
657   {
658     return EM_FALSE;
659   }
660
661   const unsigned int aModif = myKeys.Modifiers();
662   myKeys.KeyUp (aVKey, aTimeStamp);
663   if (Aspect_VKey2Modifier (aVKey) == 0)
664   {
665     // normal key released
666     switch (aVKey | aModif)
667     {
668       case Aspect_VKey_F:
669       {
670         myView->FitAll (0.01, false);
671         myView->Invalidate();
672         updateView();
673         return EM_TRUE;
674       }
675     }
676   }
677   return EM_FALSE;
678 }