From f9ab9f7f1c949c9da13f42443208a6e9b0e41d70 Mon Sep 17 00:00:00 2001 From: kgv Date: Sun, 25 Apr 2021 17:51:49 +0300 Subject: [PATCH] 0032433: Visualization, TKService - introduce Wasm_Window implementing Aspect_Window interface using Emscripten SDK Introduced Wasm_Window implementing Aspect_Window interface. Aspect_WindowInputListener has been extended by touch input callbacks (moved from AIS_ViewController), which now implements redirection of single taps to UpdateMouseClick(). AIS_ViewController::FetchNavigationKeys() now requests more frames even if Delta is zero, but navigation keys are pressed - indicated by a new flag AIS_WalkDelta::IsDefined(). Fixed missing implementation of Xw_Window::DisplayConnection() getter. The property has been moved to the base class Aspect_Window. Removed unused Aspect_Convert.hxx. DRAWEXE targeting Wasm: - added exposing of FS interface so that it is possible uploading/downloading files to/from emulated file system on JavaScript level; - added printer redirecting messages to Module.printMessage callback accepting message gravity; - Run_Appli() now skips std::cin when Module.noExitRuntime is set. --- adm/UDLIST | 1 + samples/webgl/CMakeLists.txt | 1 - samples/webgl/WasmOcctView.cpp | 328 ++------ samples/webgl/WasmOcctView.h | 31 +- samples/webgl/WasmVKeys.h | 264 ------- src/AIS/AIS_ViewController.cxx | 76 +- src/AIS/AIS_ViewController.hxx | 16 +- src/AIS/AIS_WalkDelta.hxx | 9 +- src/Aspect/Aspect_Convert.hxx | 86 -- src/Aspect/Aspect_Window.hxx | 30 +- src/Aspect/Aspect_WindowInputListener.cxx | 64 ++ src/Aspect/Aspect_WindowInputListener.hxx | 38 + src/Aspect/FILES | 1 - src/Cocoa/Cocoa_Window.mm | 1 - src/DRAWEXE/CMakeLists.txt | 1 + src/DRAWEXE/DRAWEXE.cxx | 87 ++- src/DRAWEXE/DRAWEXE.html | 109 ++- src/Draw/Draw_Window.cxx | 28 +- src/TKService/PACKAGES | 1 + src/ViewerTest/ViewerTest_EventManager.cxx | 223 +++++- src/ViewerTest/ViewerTest_EventManager.hxx | 12 + src/ViewerTest/ViewerTest_ViewerCommands.cxx | 123 +-- src/WNT/WNT_Window.cxx | 1 - src/Wasm/FILES | 2 + src/Wasm/Wasm_Window.cxx | 779 +++++++++++++++++++ src/Wasm/Wasm_Window.hxx | 185 +++++ src/Xw/Xw_Window.cxx | 5 +- src/Xw/Xw_Window.hxx | 4 - 28 files changed, 1737 insertions(+), 769 deletions(-) delete mode 100644 samples/webgl/WasmVKeys.h delete mode 100644 src/Aspect/Aspect_Convert.hxx create mode 100644 src/Wasm/FILES create mode 100644 src/Wasm/Wasm_Window.cxx create mode 100644 src/Wasm/Wasm_Window.hxx diff --git a/adm/UDLIST b/adm/UDLIST index 5a1820665a..f5d2176244 100644 --- a/adm/UDLIST +++ b/adm/UDLIST @@ -216,6 +216,7 @@ n SelectMgr n StdPrs n StdSelect n V3d +n Wasm n WNT n Xw n Cocoa diff --git a/samples/webgl/CMakeLists.txt b/samples/webgl/CMakeLists.txt index 1f7d3af3bd..f5f1e80396 100644 --- a/samples/webgl/CMakeLists.txt +++ b/samples/webgl/CMakeLists.txt @@ -28,7 +28,6 @@ if (NOT "${SOURCE_MAP_BASE}" STREQUAL "") endif() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s MODULARIZE=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='createOccViewerModule'") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap']") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --extern-post-js ${CMAKE_CURRENT_SOURCE_DIR}/occt-webgl-viewer.js") INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) diff --git a/samples/webgl/WasmOcctView.cpp b/samples/webgl/WasmOcctView.cpp index c1a30ea709..057bf084e3 100644 --- a/samples/webgl/WasmOcctView.cpp +++ b/samples/webgl/WasmOcctView.cpp @@ -21,14 +21,12 @@ #include "WasmOcctView.h" -#include "WasmVKeys.h" #include "WasmOcctPixMap.h" #include #include #include #include -#include #include #include #include @@ -36,6 +34,7 @@ #include #include #include +#include #include #include @@ -50,25 +49,6 @@ namespace { - EM_JS(int, jsCanvasGetWidth, (), { - return Module.canvas.width; - }); - - EM_JS(int, jsCanvasGetHeight, (), { - return Module.canvas.height; - }); - - EM_JS(float, jsDevicePixelRatio, (), { - var aDevicePixelRatio = window.devicePixelRatio || 1; - return aDevicePixelRatio; - }); - - //! Return cavas size in pixels. - static Graphic3d_Vec2i jsCanvasSize() - { - return Graphic3d_Vec2i (jsCanvasGetWidth(), jsCanvasGetHeight()); - } - //! Auxiliary wrapper for loading model. struct ModelAsyncLoader { @@ -199,7 +179,7 @@ void WasmOcctView::run() // ================================================================ void WasmOcctView::initWindow() { - myDevicePixelRatio = jsDevicePixelRatio(); + myDevicePixelRatio = emscripten_get_device_pixel_ratio(); myCanvasId = THE_CANVAS_ID; const char* aTargetId = !myCanvasId.IsEmpty() ? myCanvasId.ToCString() : EMSCRIPTEN_EVENT_TARGET_WINDOW; const EM_BOOL toUseCapture = EM_TRUE; @@ -340,13 +320,8 @@ bool WasmOcctView::initViewer() } } - Handle(Aspect_NeutralWindow) aWindow = new Aspect_NeutralWindow(); - Graphic3d_Vec2i aWinSize = jsCanvasSize(); - if (aWinSize.x() < 10 || aWinSize.y() < 10) - { - Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning); - } - aWindow->SetSize (aWinSize.x(), aWinSize.y()); + Handle(Wasm_Window) aWindow = new Wasm_Window (THE_CANVAS_ID); + aWindow->Size (myWinSizeOld.x(), myWinSizeOld.y()); myTextStyle = new Prs3d_TextAspect(); myTextStyle->SetFont (Font_NOF_ASCII_MONO); @@ -404,30 +379,34 @@ void WasmOcctView::initDemoScene() } // ================================================================ -// Function : UpdateView +// Function : ProcessInput // Purpose : // ================================================================ -void WasmOcctView::UpdateView() +void WasmOcctView::ProcessInput() { if (!myView.IsNull()) { - myView->Invalidate(); - updateView(); + // Queue onRedrawView()/redrawView callback to redraw canvas after all user input is flushed by browser. + // Redrawing viewer on every single message would be a pointless waste of resources, + // as user will see only the last drawn frame due to WebGL implementation details. + if (++myUpdateRequests == 1) + { + emscripten_async_call (onRedrawView, this, 0); + } } } // ================================================================ -// Function : updateView +// Function : UpdateView // Purpose : // ================================================================ -void WasmOcctView::updateView() +void WasmOcctView::UpdateView() { if (!myView.IsNull()) { - if (++myUpdateRequests == 1) - { - emscripten_async_call (onRedrawView, this, 0); - } + myView->Invalidate(); + // queue next onRedrawView()/redrawView() + ProcessInput(); } } @@ -452,14 +431,6 @@ void WasmOcctView::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCt { myUpdateRequests = 0; AIS_ViewController::handleViewRedraw (theCtx, theView); - - for (NCollection_DataMap::Iterator aNavKeyIter (myNavKeyMap); - !myToAskNextFrame && aNavKeyIter.More(); aNavKeyIter.Next()) - { - const Aspect_VKey aVKey = aNavKeyIter.Key() & ~Aspect_VKeyFlags_ALL; - myToAskNextFrame = myKeys.IsKeyDown (aVKey); - } - if (myToAskNextFrame) { // ask more frames @@ -481,23 +452,21 @@ EM_BOOL WasmOcctView::onResizeEvent (int theEventType, const EmscriptenUiEvent* return EM_FALSE; } - Handle(Aspect_NeutralWindow) aWindow = Handle(Aspect_NeutralWindow)::DownCast (myView->Window()); - Graphic3d_Vec2i aWinSizeOld, aWinSizeNew (jsCanvasSize()); - if (aWinSizeNew.x() < 10 || aWinSizeNew.y() < 10) - { - Message::DefaultMessenger()->Send (TCollection_AsciiString ("Warning: invalid canvas size"), Message_Warning); - } - aWindow->Size (aWinSizeOld.x(), aWinSizeOld.y()); - const float aPixelRatio = jsDevicePixelRatio(); - if (aWinSizeNew != aWinSizeOld + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + Graphic3d_Vec2i aWinSizeNew; + aWindow->DoResize(); + aWindow->Size (aWinSizeNew.x(), aWinSizeNew.y()); + const float aPixelRatio = emscripten_get_device_pixel_ratio(); + if (aWinSizeNew != myWinSizeOld || aPixelRatio != myDevicePixelRatio) { + myWinSizeOld = aWinSizeNew; if (myDevicePixelRatio != aPixelRatio) { myDevicePixelRatio = aPixelRatio; initPixelScaleRatio(); } - aWindow->SetSize (aWinSizeNew.x(), aWinSizeNew.y()); + myView->MustBeResized(); myView->Invalidate(); myView->Redraw(); @@ -517,73 +486,8 @@ EM_BOOL WasmOcctView::onMouseEvent (int theEventType, const EmscriptenMouseEvent return EM_FALSE; } - Graphic3d_Vec2i aWinSize; - myView->Window()->Size (aWinSize.x(), aWinSize.y()); - const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->targetX, theEvent->targetY)); - Aspect_VKeyFlags aFlags = 0; - if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; } - if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; } - if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; } - if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; } - - const bool isEmulated = false; - const Aspect_VKeyMouse aButtons = WasmVKeys_MouseButtonsFromNative (theEvent->buttons); - switch (theEventType) - { - case EMSCRIPTEN_EVENT_MOUSEMOVE: - { - if ((aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() - || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) - && PressedMouseButtons() == Aspect_VKeyMouse_NONE) - { - return EM_FALSE; - } - if (UpdateMousePosition (aNewPos, aButtons, aFlags, isEmulated)) - { - updateView(); - } - break; - } - case EMSCRIPTEN_EVENT_MOUSEDOWN: - case EMSCRIPTEN_EVENT_MOUSEUP: - { - if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() - || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) - { - return EM_FALSE; - } - if (UpdateMouseButtons (aNewPos, aButtons, aFlags, isEmulated)) - { - updateView(); - } - break; - } - case EMSCRIPTEN_EVENT_CLICK: - case EMSCRIPTEN_EVENT_DBLCLICK: - { - if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() - || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) - { - return EM_FALSE; - } - break; - } - case EMSCRIPTEN_EVENT_MOUSEENTER: - { - break; - } - case EMSCRIPTEN_EVENT_MOUSELEAVE: - { - // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive, - // so we have to forget current state... - if (UpdateMouseButtons (aNewPos, Aspect_VKeyMouse_NONE, aFlags, isEmulated)) - { - updateView(); - } - break; - } - } - return EM_TRUE; + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + return aWindow->ProcessMouseEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE; } // ================================================================ @@ -598,40 +502,8 @@ EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent return EM_FALSE; } - Graphic3d_Vec2i aWinSize; - myView->Window()->Size (aWinSize.x(), aWinSize.y()); - const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (theEvent->mouse.targetX, theEvent->mouse.targetY)); - if (aNewPos.x() < 0 || aNewPos.x() > aWinSize.x() - || aNewPos.y() < 0 || aNewPos.y() > aWinSize.y()) - { - return EM_FALSE; - } - - double aDelta = 0.0; - switch (theEvent->deltaMode) - { - case DOM_DELTA_PIXEL: - { - aDelta = theEvent->deltaY / (5.0 * myDevicePixelRatio); - break; - } - case DOM_DELTA_LINE: - { - aDelta = theEvent->deltaY * 8.0; - break; - } - case DOM_DELTA_PAGE: - { - aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0; - break; - } - } - - if (UpdateZoom (Aspect_ScrollDelta (aNewPos, -aDelta))) - { - updateView(); - } - return EM_TRUE; + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + return aWindow->ProcessWheelEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE; } // ================================================================ @@ -640,90 +512,13 @@ EM_BOOL WasmOcctView::onWheelEvent (int theEventType, const EmscriptenWheelEvent // ================================================================ EM_BOOL WasmOcctView::onTouchEvent (int theEventType, const EmscriptenTouchEvent* theEvent) { - const double aClickTolerance = 5.0; if (myView.IsNull()) { return EM_FALSE; } - Graphic3d_Vec2i aWinSize; - myView->Window()->Size (aWinSize.x(), aWinSize.y()); - bool hasUpdates = false; - for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter) - { - const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter]; - if (!aTouch.isChanged) - { - continue; - } - - const Standard_Size aTouchId = (Standard_Size )aTouch.identifier; - const Graphic3d_Vec2i aNewPos = convertPointToBacking (Graphic3d_Vec2i (aTouch.targetX, aTouch.targetY)); - switch (theEventType) - { - case EMSCRIPTEN_EVENT_TOUCHSTART: - { - if (aNewPos.x() >= 0 && aNewPos.x() < aWinSize.x() - && aNewPos.y() >= 0 && aNewPos.y() < aWinSize.y()) - { - hasUpdates = true; - AddTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos)); - myClickTouch.From.SetValues (-1.0, -1.0); - if (myTouchPoints.Extent() == 1) - { - myClickTouch.From = Graphic3d_Vec2d (aNewPos); - } - } - break; - } - case EMSCRIPTEN_EVENT_TOUCHMOVE: - { - const int anOldIndex = myTouchPoints.FindIndex (aTouchId); - if (anOldIndex != 0) - { - hasUpdates = true; - UpdateTouchPoint (aTouchId, Graphic3d_Vec2d (aNewPos)); - if (myTouchPoints.Extent() == 1 - && (myClickTouch.From - Graphic3d_Vec2d (aNewPos)).cwiseAbs().maxComp() > aClickTolerance) - { - myClickTouch.From.SetValues (-1.0, -1.0); - } - } - break; - } - case EMSCRIPTEN_EVENT_TOUCHEND: - case EMSCRIPTEN_EVENT_TOUCHCANCEL: - { - if (RemoveTouchPoint (aTouchId)) - { - if (myTouchPoints.IsEmpty() - && myClickTouch.From.minComp() >= 0.0) - { - if (myDoubleTapTimer.IsStarted() - && myDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt) - { - myView->FitAll (0.01, false); - myView->Invalidate(); - } - else - { - myDoubleTapTimer.Stop(); - myDoubleTapTimer.Reset(); - myDoubleTapTimer.Start(); - SelectInViewer (Graphic3d_Vec2i (myClickTouch.From), AIS_SelectionScheme_Replace); - } - } - hasUpdates = true; - } - break; - } - } - } - if (hasUpdates) - { - updateView(); - } - return hasUpdates || !myTouchPoints.IsEmpty() ? EM_TRUE : EM_FALSE; + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + return aWindow->ProcessTouchEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE; } // ================================================================ @@ -775,35 +570,34 @@ EM_BOOL WasmOcctView::onKeyDownEvent (int theEventType, const EmscriptenKeyboard return EM_FALSE; } - const double aTimeStamp = EventTime(); - const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode); - if (aVKey == Aspect_VKey_UNKNOWN) - { - return EM_FALSE; - } - if (theEvent->repeat == EM_TRUE) - { - return EM_FALSE; - } + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE; +} +//======================================================================= +//function : KeyDown +//purpose : +//======================================================================= +void WasmOcctView::KeyDown (Aspect_VKey theKey, + double theTime, + double thePressure) +{ const unsigned int aModifOld = myKeys.Modifiers(); - AIS_ViewController::KeyDown (aVKey, aTimeStamp); + AIS_ViewController::KeyDown (theKey, theTime, thePressure); const unsigned int aModifNew = myKeys.Modifiers(); if (aModifNew != aModifOld - && navigationKeyModifierSwitch (aModifOld, aModifNew, aTimeStamp)) + && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime)) { // modifier key just pressed } Aspect_VKey anAction = Aspect_VKey_UNKNOWN; - if (myNavKeyMap.Find (aVKey | myKeys.Modifiers(), anAction) + if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction) && anAction != Aspect_VKey_UNKNOWN) { - AIS_ViewController::KeyDown (anAction, aTimeStamp); - UpdateView(); + AIS_ViewController::KeyDown (anAction, theTime, thePressure); } - return EM_FALSE; } // ================================================================ @@ -818,32 +612,36 @@ EM_BOOL WasmOcctView::onKeyUpEvent (int theEventType, const EmscriptenKeyboardEv return EM_FALSE; } - const double aTimeStamp = EventTime(); - const Aspect_VKey aVKey = WasmVKeys_VirtualKeyFromNative (theEvent->keyCode); - if (aVKey == Aspect_VKey_UNKNOWN) - { - return EM_FALSE; - } + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (myView->Window()); + return aWindow->ProcessKeyEvent (*this, theEventType, theEvent) ? EM_TRUE : EM_FALSE; +} +//======================================================================= +//function : KeyUp +//purpose : +//======================================================================= +void WasmOcctView::KeyUp (Aspect_VKey theKey, + double theTime) +{ const unsigned int aModifOld = myKeys.Modifiers(); - AIS_ViewController::KeyUp (aVKey, aTimeStamp); + AIS_ViewController::KeyUp (theKey, theTime); Aspect_VKey anAction = Aspect_VKey_UNKNOWN; - if (myNavKeyMap.Find (aVKey | myKeys.Modifiers(), anAction) + if (myNavKeyMap.Find (theKey | myKeys.Modifiers(), anAction) && anAction != Aspect_VKey_UNKNOWN) { - AIS_ViewController::KeyUp (anAction, aTimeStamp); - UpdateView(); + AIS_ViewController::KeyUp (anAction, theTime); + processKeyPress (anAction); } const unsigned int aModifNew = myKeys.Modifiers(); if (aModifNew != aModifOld - && navigationKeyModifierSwitch (aModifOld, aModifNew, aTimeStamp)) + && navigationKeyModifierSwitch (aModifOld, aModifNew, theTime)) { // modifier key released } - return processKeyPress (aVKey | aModifNew) ? EM_TRUE : EM_FALSE; + processKeyPress (theKey | aModifNew); } //============================================================================== diff --git a/samples/webgl/WasmOcctView.h b/samples/webgl/WasmOcctView.h index 3acf35e86e..626ba00d8a 100644 --- a/samples/webgl/WasmOcctView.h +++ b/samples/webgl/WasmOcctView.h @@ -136,9 +136,6 @@ private: //! Application event loop. void mainloop(); - //! Request view redrawing. - void updateView(); - //! Flush events and redraw view. void redrawView(); @@ -146,25 +143,24 @@ private: virtual void handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx, const Handle(V3d_View)& theView) override; + //! Schedule processing of window input events with the next repaint event. + virtual void ProcessInput() override; + + //! Handle key down event. + virtual void KeyDown (Aspect_VKey theKey, + double theTime, + double thePressure) override; + + //! Handle key up event. + virtual void KeyUp (Aspect_VKey theKey, + double theTime) override; + //! Dump WebGL context information. void dumpGlInfo (bool theIsBasic); //! Initialize pixel scale ratio. void initPixelScaleRatio(); - //! Return point from logical units to backing store. - Graphic3d_Vec2d convertPointToBacking (const Graphic3d_Vec2d& thePnt) const - { - return thePnt * myDevicePixelRatio; - } - - //! Return point from logical units to backing store. - Graphic3d_Vec2i convertPointToBacking (const Graphic3d_Vec2i& thePnt) const - { - Graphic3d_Vec2d aPnt = Graphic3d_Vec2d (thePnt) * myDevicePixelRatio + Graphic3d_Vec2d (0.5); - return Graphic3d_Vec2i (aPnt); - } - //! @name Emscripten callbacks private: //! Window resize event. @@ -245,8 +241,7 @@ private: Handle(Prs3d_TextAspect) myTextStyle; //!< text style for OSD elements Handle(AIS_ViewCube) myViewCube; //!< view cube object TCollection_AsciiString myCanvasId; //!< canvas element id on HTML page - Aspect_Touch myClickTouch; //!< single touch position for handling clicks - OSD_Timer myDoubleTapTimer; //!< timer for handling double tap + Graphic3d_Vec2i myWinSizeOld; float myDevicePixelRatio; //!< device pixel ratio for handling high DPI displays unsigned int myUpdateRequests; //!< counter for unhandled update requests diff --git a/samples/webgl/WasmVKeys.h b/samples/webgl/WasmVKeys.h deleted file mode 100644 index 3c8bb326a0..0000000000 --- a/samples/webgl/WasmVKeys.h +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2019 OPEN CASCADE SAS -// -// This file is part of the examples of the Open CASCADE Technology software library. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE - -#ifndef _WasmVKeys_HeaderFile -#define _WasmVKeys_HeaderFile - -#include - -#include - -//! Convert Emscripten mouse buttons into Aspect_VKeyMouse. -inline Aspect_VKeyMouse WasmVKeys_MouseButtonsFromNative (unsigned short theButtons) -{ - Aspect_VKeyMouse aButtons = Aspect_VKeyMouse_NONE; - if ((theButtons & 0x1) != 0) - { - aButtons |= Aspect_VKeyMouse_LeftButton; - } - if ((theButtons & 0x2) != 0) - { - aButtons |= Aspect_VKeyMouse_RightButton; - } - if ((theButtons & 0x4) != 0) - { - aButtons |= Aspect_VKeyMouse_MiddleButton; - } - return aButtons; -} - -//! Convert DOM virtual key into Aspect_VKey. -inline Aspect_VKey WasmVKeys_VirtualKeyFromNative (Standard_Integer theKey) -{ - if (theKey >= DOM_VK_0 - && theKey <= DOM_VK_9) - { - // numpad keys - return Aspect_VKey((theKey - DOM_VK_0) + Aspect_VKey_0); - } - if (theKey >= DOM_VK_A - && theKey <= DOM_VK_Z) - { - // main latin alphabet keys - return Aspect_VKey((theKey - DOM_VK_A) + Aspect_VKey_A); - } - if (theKey >= DOM_VK_F1 - && theKey <= DOM_VK_F24) - { - // special keys - if (theKey <= DOM_VK_F12) - { - return Aspect_VKey((theKey - DOM_VK_F1) + Aspect_VKey_F1); - } - return Aspect_VKey_UNKNOWN; - } - if (theKey >= DOM_VK_NUMPAD0 - && theKey <= DOM_VK_NUMPAD9) - { - // numpad keys - return Aspect_VKey((theKey - DOM_VK_NUMPAD0) + Aspect_VKey_Numpad0); - } - - switch (theKey) - { - case DOM_VK_CANCEL: - case DOM_VK_HELP: - return Aspect_VKey_UNKNOWN; - case DOM_VK_BACK_SPACE: - return Aspect_VKey_Backspace; - case DOM_VK_TAB: - return Aspect_VKey_Tab; - case DOM_VK_CLEAR: - return Aspect_VKey_UNKNOWN; - case DOM_VK_RETURN: - case DOM_VK_ENTER: - return Aspect_VKey_Enter; - case DOM_VK_SHIFT: - return Aspect_VKey_Shift; - case DOM_VK_CONTROL: - return Aspect_VKey_Control; - case DOM_VK_ALT: - return Aspect_VKey_Alt; - case DOM_VK_PAUSE: - case DOM_VK_CAPS_LOCK: - case DOM_VK_KANA: - //case DOM_VK_HANGUL: - case DOM_VK_EISU: - case DOM_VK_JUNJA: - case DOM_VK_FINAL: - case DOM_VK_HANJA: - //case DOM_VK_KANJI: - return Aspect_VKey_UNKNOWN; - case DOM_VK_ESCAPE: - return Aspect_VKey_Escape; - case DOM_VK_CONVERT: - case DOM_VK_NONCONVERT: - case DOM_VK_ACCEPT: - case DOM_VK_MODECHANGE: - return Aspect_VKey_UNKNOWN; - case DOM_VK_SPACE: - return Aspect_VKey_Space; - case DOM_VK_PAGE_UP: - return Aspect_VKey_PageUp; - case DOM_VK_PAGE_DOWN: - return Aspect_VKey_PageDown; - case DOM_VK_END: - return Aspect_VKey_End; - case DOM_VK_HOME: - return Aspect_VKey_Home; - case DOM_VK_LEFT: - return Aspect_VKey_Left; - case DOM_VK_UP: - return Aspect_VKey_Up; - case DOM_VK_RIGHT: - return Aspect_VKey_Right; - case DOM_VK_DOWN: - return Aspect_VKey_Down; - case DOM_VK_SELECT: - case DOM_VK_PRINT: - case DOM_VK_EXECUTE: - case DOM_VK_PRINTSCREEN: - case DOM_VK_INSERT: - return Aspect_VKey_UNKNOWN; - case DOM_VK_DELETE: - return Aspect_VKey_Delete; - case DOM_VK_COLON: - return Aspect_VKey_Comma; - case DOM_VK_SEMICOLON: - return Aspect_VKey_Semicolon; - case DOM_VK_LESS_THAN: - return Aspect_VKey_UNKNOWN; - case DOM_VK_EQUALS: - return Aspect_VKey_Equal; - case DOM_VK_GREATER_THAN: - return Aspect_VKey_UNKNOWN; - case DOM_VK_QUESTION_MARK: - return Aspect_VKey_Slash; - case DOM_VK_AT: // @ key - return Aspect_VKey_UNKNOWN; - case DOM_VK_WIN: - return Aspect_VKey_Meta; - case DOM_VK_CONTEXT_MENU: - case DOM_VK_SLEEP: - return Aspect_VKey_UNKNOWN; - case DOM_VK_MULTIPLY: - return Aspect_VKey_NumpadMultiply; - case DOM_VK_ADD: - return Aspect_VKey_NumpadAdd; - case DOM_VK_SEPARATOR: - return Aspect_VKey_UNKNOWN; - case DOM_VK_SUBTRACT: - return Aspect_VKey_NumpadSubtract; - case DOM_VK_DECIMAL: - return Aspect_VKey_UNKNOWN; - case DOM_VK_DIVIDE: - return Aspect_VKey_NumpadDivide; - case DOM_VK_NUM_LOCK: - return Aspect_VKey_Numlock; - case DOM_VK_SCROLL_LOCK: - return Aspect_VKey_Scroll; - case DOM_VK_WIN_OEM_FJ_JISHO: - case DOM_VK_WIN_OEM_FJ_MASSHOU: - case DOM_VK_WIN_OEM_FJ_TOUROKU: - case DOM_VK_WIN_OEM_FJ_LOYA: - case DOM_VK_WIN_OEM_FJ_ROYA: - case DOM_VK_CIRCUMFLEX: - return Aspect_VKey_UNKNOWN; - case DOM_VK_EXCLAMATION: - case DOM_VK_DOUBLE_QUOTE: - //case DOM_VK_HASH: - case DOM_VK_DOLLAR: - case DOM_VK_PERCENT: - case DOM_VK_AMPERSAND: - case DOM_VK_UNDERSCORE: - case DOM_VK_OPEN_PAREN: - case DOM_VK_CLOSE_PAREN: - case DOM_VK_ASTERISK: - return Aspect_VKey_UNKNOWN; - case DOM_VK_PLUS: - return Aspect_VKey_Plus; - case DOM_VK_PIPE: - case DOM_VK_HYPHEN_MINUS: - return Aspect_VKey_UNKNOWN; - case DOM_VK_OPEN_CURLY_BRACKET: - return Aspect_VKey_BracketLeft; - case DOM_VK_CLOSE_CURLY_BRACKET: - return Aspect_VKey_BracketRight; - case DOM_VK_TILDE: - return Aspect_VKey_Tilde; - case DOM_VK_VOLUME_MUTE: - return Aspect_VKey_VolumeMute; - case DOM_VK_VOLUME_DOWN: - return Aspect_VKey_VolumeDown; - case DOM_VK_VOLUME_UP: - return Aspect_VKey_VolumeUp; - case DOM_VK_COMMA: - return Aspect_VKey_Comma; - case DOM_VK_PERIOD: - return Aspect_VKey_Period; - case DOM_VK_SLASH: - return Aspect_VKey_Slash; - case DOM_VK_BACK_QUOTE: - return Aspect_VKey_UNKNOWN; - case DOM_VK_OPEN_BRACKET: - return Aspect_VKey_BracketLeft; - case DOM_VK_BACK_SLASH: - return Aspect_VKey_Backslash; - case DOM_VK_CLOSE_BRACKET: - return Aspect_VKey_BracketRight; - case DOM_VK_QUOTE: - return Aspect_VKey_UNKNOWN; - case DOM_VK_META: - return Aspect_VKey_Meta; - case DOM_VK_ALTGR: - return Aspect_VKey_Alt; - case DOM_VK_WIN_ICO_HELP: - case DOM_VK_WIN_ICO_00: - case DOM_VK_WIN_ICO_CLEAR: - case DOM_VK_WIN_OEM_RESET: - case DOM_VK_WIN_OEM_JUMP: - case DOM_VK_WIN_OEM_PA1: - case DOM_VK_WIN_OEM_PA2: - case DOM_VK_WIN_OEM_PA3: - case DOM_VK_WIN_OEM_WSCTRL: - case DOM_VK_WIN_OEM_CUSEL: - case DOM_VK_WIN_OEM_ATTN: - case DOM_VK_WIN_OEM_FINISH: - case DOM_VK_WIN_OEM_COPY: - case DOM_VK_WIN_OEM_AUTO: - case DOM_VK_WIN_OEM_ENLW: - case DOM_VK_WIN_OEM_BACKTAB: - case DOM_VK_ATTN: - case DOM_VK_CRSEL: - case DOM_VK_EXSEL: - case DOM_VK_EREOF: - return Aspect_VKey_UNKNOWN; - case DOM_VK_PLAY: - return Aspect_VKey_MediaPlayPause; - case DOM_VK_ZOOM: - case DOM_VK_PA1: - case DOM_VK_WIN_OEM_CLEAR: - return Aspect_VKey_UNKNOWN; - } - return Aspect_VKey_UNKNOWN; -} - -#endif // _WasmVKeys_HeaderFile diff --git a/src/AIS/AIS_ViewController.cxx b/src/AIS/AIS_ViewController.cxx index 2630dca3b2..009d7ce651 100644 --- a/src/AIS/AIS_ViewController.cxx +++ b/src/AIS/AIS_ViewController.cxx @@ -86,6 +86,7 @@ AIS_ViewController::AIS_ViewController() myMouseStopDragOnUnclick (false), // myTouchToleranceScale (1.0f), + myTouchClickThresholdPx (3.0f), myTouchRotationThresholdPx (6.0f), myTouchZRotationThreshold (float(2.0 * M_PI / 180.0)), myTouchPanThresholdPx (4.0f), @@ -1010,14 +1011,12 @@ void AIS_ViewController::AddTouchPoint (Standard_Size theId, Standard_Boolean theClearBefore) { myUI.MoveTo.ToHilight = false; - if (theClearBefore) - { - RemoveTouchPoint ((Standard_Size )-1); - } + Aspect_WindowInputListener::AddTouchPoint (theId, thePnt, theClearBefore); - myTouchPoints.Add (theId, Aspect_Touch (thePnt, false)); + myTouchClick.From = Graphic3d_Vec2d (-1.0); if (myTouchPoints.Extent() == 1) { + myTouchClick.From = thePnt; myUpdateStartPointRot = true; myStartRotCoord = thePnt; if (myToAllowDragging) @@ -1043,18 +1042,9 @@ void AIS_ViewController::AddTouchPoint (Standard_Size theId, bool AIS_ViewController::RemoveTouchPoint (Standard_Size theId, Standard_Boolean theClearSelectPnts) { - if (theId == (Standard_Size )-1) + if (!Aspect_WindowInputListener::RemoveTouchPoint (theId, theClearSelectPnts)) { - myTouchPoints.Clear (false); - } - else - { - const Standard_Integer anOldExtent = myTouchPoints.Extent(); - myTouchPoints.RemoveKey (theId); - if (myTouchPoints.Extent() == anOldExtent) - { - return false; - } + return false; } if (myTouchPoints.Extent() == 1) @@ -1079,6 +1069,30 @@ bool AIS_ViewController::RemoveTouchPoint (Standard_Size theId, } myUI.Dragging.ToStop = true; + + if (theId == (Standard_Size )-1) + { + // abort clicking + myTouchClick.From = Graphic3d_Vec2d (-1); + } + else if (myTouchClick.From.minComp() >= 0.0) + { + bool isDoubleClick = false; + if (myTouchDoubleTapTimer.IsStarted() + && myTouchDoubleTapTimer.ElapsedTime() <= myMouseDoubleClickInt) + { + isDoubleClick = true; + } + else + { + myTouchDoubleTapTimer.Stop(); + myTouchDoubleTapTimer.Reset(); + myTouchDoubleTapTimer.Start(); + } + + // emulate mouse click + UpdateMouseClick (Graphic3d_Vec2i (myTouchClick.From), Aspect_VKeyMouse_LeftButton, Aspect_VKeyFlags_NONE, isDoubleClick); + } } myUI.IsNewGesture = true; return true; @@ -1091,13 +1105,13 @@ bool AIS_ViewController::RemoveTouchPoint (Standard_Size theId, void AIS_ViewController::UpdateTouchPoint (Standard_Size theId, const Graphic3d_Vec2d& thePnt) { - if (Aspect_Touch* aTouch = myTouchPoints.ChangeSeek (theId)) - { - aTouch->To = thePnt; - } - else + Aspect_WindowInputListener::UpdateTouchPoint (theId, thePnt); + + const double aTouchTol = double(myTouchToleranceScale) * double(myTouchClickThresholdPx); + if (myTouchPoints.Extent() == 1 + && (myTouchClick.From - thePnt).cwiseAbs().maxComp() > aTouchTol) { - AddTouchPoint (theId, thePnt); + myTouchClick.From.SetValues (-1.0, -1.0); } } @@ -1195,12 +1209,14 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavJump, aNewEventTime, aDuration)) { myKeys.KeyUp (Aspect_VKey_NavJump, aNewEventTime); + aWalk.SetDefined (true); aWalk.SetJumping (true); } if (!aWalk.IsJumping() && theCrouchRatio < 1.0 && myKeys.HoldDuration (Aspect_VKey_NavCrouch, aNewEventTime, aDuration)) { + aWalk.SetDefined (true); aWalk.SetRunning (false); aWalk.SetCrouching (true); } @@ -1215,6 +1231,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa { double aProgress = Abs (Min (aMaxDuration, aDuration)); aProgress *= aRunRatio; + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Forward].Value += aProgress; aWalk[AIS_WalkTranslation_Forward].Pressure = aPressure; aWalk[AIS_WalkTranslation_Forward].Duration = aDuration; @@ -1223,6 +1240,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa { double aProgress = Abs (Min (aMaxDuration, aDuration)); aProgress *= aRunRatio; + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Forward].Value += -aProgress; aWalk[AIS_WalkTranslation_Forward].Pressure = aPressure; aWalk[AIS_WalkTranslation_Forward].Duration = aDuration; @@ -1231,6 +1249,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa { double aProgress = Abs (Min (aMaxDuration, aDuration)); aProgress *= aRunRatio; + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Side].Value = -aProgress; aWalk[AIS_WalkTranslation_Side].Pressure = aPressure; aWalk[AIS_WalkTranslation_Side].Duration = aDuration; @@ -1239,6 +1258,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa { double aProgress = Abs (Min (aMaxDuration, aDuration)); aProgress *= aRunRatio; + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Side].Value = aProgress; aWalk[AIS_WalkTranslation_Side].Pressure = aPressure; aWalk[AIS_WalkTranslation_Side].Duration = aDuration; @@ -1246,6 +1266,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavLookLeft, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Yaw].Value = aProgress; aWalk[AIS_WalkRotation_Yaw].Pressure = aPressure; aWalk[AIS_WalkRotation_Yaw].Duration = aDuration; @@ -1253,6 +1274,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavLookRight, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Yaw].Value = -aProgress; aWalk[AIS_WalkRotation_Yaw].Pressure = aPressure; aWalk[AIS_WalkRotation_Yaw].Duration = aDuration; @@ -1260,6 +1282,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavLookUp, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Pitch].Value = !myToInvertPitch ? -aProgress : aProgress; aWalk[AIS_WalkRotation_Pitch].Pressure = aPressure; aWalk[AIS_WalkRotation_Pitch].Duration = aDuration; @@ -1267,6 +1290,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavLookDown, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Pitch].Value = !myToInvertPitch ? aProgress : -aProgress; aWalk[AIS_WalkRotation_Pitch].Pressure = aPressure; aWalk[AIS_WalkRotation_Pitch].Duration = aDuration; @@ -1274,6 +1298,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavRollCCW, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Roll].Value = -aProgress; aWalk[AIS_WalkRotation_Roll].Pressure = aPressure; aWalk[AIS_WalkRotation_Roll].Duration = aDuration; @@ -1281,6 +1306,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavRollCW, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)) * aPressure; + aWalk.SetDefined (true); aWalk[AIS_WalkRotation_Roll].Value = aProgress; aWalk[AIS_WalkRotation_Roll].Pressure = aPressure; aWalk[AIS_WalkRotation_Roll].Duration = aDuration; @@ -1288,6 +1314,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavSlideUp, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)); + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Up].Value = aProgress; aWalk[AIS_WalkTranslation_Up].Pressure = aPressure; aWalk[AIS_WalkTranslation_Up].Duration = aDuration; @@ -1295,6 +1322,7 @@ AIS_WalkDelta AIS_ViewController::FetchNavigationKeys (Standard_Real theCrouchRa if (myKeys.HoldDuration (Aspect_VKey_NavSlideDown, aNewEventTime, aDuration, aPressure)) { double aProgress = Abs (Min (aMaxDuration, aDuration)); + aWalk.SetDefined (true); aWalk[AIS_WalkTranslation_Up].Value = -aProgress; aWalk[AIS_WalkTranslation_Up].Pressure = aPressure; aWalk[AIS_WalkTranslation_Up].Duration = aDuration; @@ -1956,6 +1984,10 @@ AIS_WalkDelta AIS_ViewController::handleNavigationKeys (const Handle(AIS_Interac } if (aWalk.IsEmpty()) { + if (aWalk.IsDefined()) + { + setAskNextFrame(); + } return aWalk; } else if (myGL.OrbitRotation.ToRotate diff --git a/src/AIS/AIS_ViewController.hxx b/src/AIS/AIS_ViewController.hxx index 37d436fa9d..8187275546 100644 --- a/src/AIS/AIS_ViewController.hxx +++ b/src/AIS/AIS_ViewController.hxx @@ -372,9 +372,6 @@ public: //! @name multi-touch input //! Set scale factor for adjusting tolerances for starting multi-touch gestures. void SetTouchToleranceScale (float theTolerance) { myTouchToleranceScale = theTolerance; } - //! Return TRUE if touches map is not empty. - bool HasTouchPoints() const { return !myTouchPoints.IsEmpty(); } - //! Add touch point with the given ID. //! This method is expected to be called from UI thread. //! @param theId touch unique identifier @@ -382,7 +379,7 @@ public: //! @name multi-touch input //! @param theClearBefore if TRUE previously registered touches will be removed Standard_EXPORT virtual void AddTouchPoint (Standard_Size theId, const Graphic3d_Vec2d& thePnt, - Standard_Boolean theClearBefore = false); + Standard_Boolean theClearBefore = false) Standard_OVERRIDE; //! Remove touch point with the given ID. //! This method is expected to be called from UI thread. @@ -390,7 +387,7 @@ public: //! @name multi-touch input //! @param theClearSelectPnts if TRUE will initiate clearing of selection points //! @return TRUE if point has been removed Standard_EXPORT virtual bool RemoveTouchPoint (Standard_Size theId, - Standard_Boolean theClearSelectPnts = false); + Standard_Boolean theClearSelectPnts = false) Standard_OVERRIDE; //! Update touch point with the given ID. //! If point with specified ID was not registered before, it will be added. @@ -398,7 +395,9 @@ public: //! @name multi-touch input //! @param theId touch unique identifier //! @param thePnt touch coordinates Standard_EXPORT virtual void UpdateTouchPoint (Standard_Size theId, - const Graphic3d_Vec2d& thePnt); + const Graphic3d_Vec2d& thePnt) Standard_OVERRIDE; + + using Aspect_WindowInputListener::HasTouchPoints; public: //! @name 3d mouse input @@ -749,13 +748,16 @@ protected: //! @name mouse input variables protected: //! @name multi-touch input variables Standard_ShortReal myTouchToleranceScale; //!< tolerance scale factor; 1.0 by default + Standard_ShortReal myTouchClickThresholdPx; //!< touch click threshold in pixels; 3 by default Standard_ShortReal myTouchRotationThresholdPx; //!< threshold for starting one-touch rotation gesture in pixels; 6 by default Standard_ShortReal myTouchZRotationThreshold; //!< threshold for starting two-touch Z-rotation gesture in radians; 2 degrees by default Standard_ShortReal myTouchPanThresholdPx; //!< threshold for starting two-touch panning gesture in pixels; 4 by default Standard_ShortReal myTouchZoomThresholdPx; //!< threshold for starting two-touch zoom (pitch) gesture in pixels; 6 by default Standard_ShortReal myTouchZoomRatio; //!< distance ratio for mapping two-touch zoom (pitch) gesture from pixels to zoom; 0.13 by default - Aspect_TouchMap myTouchPoints; //!< map of active touches + Aspect_Touch myTouchClick; //!< single touch position for handling clicks + OSD_Timer myTouchDoubleTapTimer; //!< timer for handling double tap + Graphic3d_Vec2d myStartPanCoord; //!< touch coordinates at the moment of starting panning gesture Graphic3d_Vec2d myStartRotCoord; //!< touch coordinates at the moment of starting rotating gesture Standard_Integer myNbTouchesLast; //!< number of touches within previous gesture flush to track gesture changes diff --git a/src/AIS/AIS_WalkDelta.hxx b/src/AIS/AIS_WalkDelta.hxx index 40b0eaeb53..ef68b67ad5 100644 --- a/src/AIS/AIS_WalkDelta.hxx +++ b/src/AIS/AIS_WalkDelta.hxx @@ -51,7 +51,7 @@ struct AIS_WalkDelta { //! Empty constructor. AIS_WalkDelta() - : myIsJumping (false), myIsCrouching (false), myIsRunning (false) {} + : myIsDefined (false), myIsJumping (false), myIsCrouching (false), myIsRunning (false) {} //! Return translation component. const AIS_WalkPart& operator[] (AIS_WalkTranslation thePart) const { return myTranslation[thePart]; } @@ -83,6 +83,12 @@ struct AIS_WalkDelta //! Set running state. void SetRunning (bool theIsRunning) { myIsRunning = theIsRunning; } + //! Return TRUE if navigation keys are pressed even if delta from the previous frame is empty. + bool IsDefined() const { return myIsDefined || !IsEmpty(); } + + //! Set if any navigation key is pressed. + void SetDefined (bool theIsDefined) { myIsDefined = theIsDefined; } + //! Return TRUE when both Rotation and Translation deltas are empty. bool IsEmpty() const { return !ToMove() && !ToRotate(); } @@ -106,6 +112,7 @@ private: AIS_WalkPart myTranslation[3]; AIS_WalkPart myRotation[3]; + bool myIsDefined; bool myIsJumping; bool myIsCrouching; bool myIsRunning; diff --git a/src/Aspect/Aspect_Convert.hxx b/src/Aspect/Aspect_Convert.hxx deleted file mode 100644 index 048f3be16c..0000000000 --- a/src/Aspect/Aspect_Convert.hxx +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 1999-2014 OPEN CASCADE SAS -// -// This file is part of Open CASCADE Technology software library. -// -// This library is free software; you can redistribute it and/or modify it under -// the terms of the GNU Lesser General Public License version 2.1 as published -// by the Free Software Foundation, with special exception defined in the file -// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT -// distribution for complete text of the license and disclaimer of any warranty. -// -// Alternatively, this file may be used under the terms of Open CASCADE -// commercial license or contractual agreement. - -#ifndef _Aspect_Convert_HeaderFile -#define _Aspect_Convert_HeaderFile - -#include -#include - -//! Auxiliary functions for DCU <-> Pixels conversions. -namespace Aspect_Convert -{ - - inline Standard_Integer Round (Standard_Real theValue) - { - return Standard_Integer(theValue + (theValue >= 0 ? 0.5 : -0.5 )); - } - - inline void ConvertCoordinates (const Standard_Integer theParentPxSizeX, const Standard_Integer theParentPxSizeY, - const Standard_Real theQCenterX, const Standard_Real theQCenterY, - const Standard_Real theQSizeX, const Standard_Real theQSizeY, - Standard_Integer& thePxLeft, Standard_Integer& thePxTop, - Standard_Integer& thePxSizeX, Standard_Integer& thePxSizeY) - { - Standard_Real theParentSizeMin = Min (theParentPxSizeX, theParentPxSizeY); - thePxSizeX = Round (theQSizeX * theParentSizeMin); - thePxSizeY = Round (theQSizeY * theParentSizeMin); - Standard_Integer thePxCenterX = Round(theQCenterX * Standard_Real (theParentPxSizeX)); - Standard_Integer thePxCenterY = Round((1.0 - theQCenterY) * Standard_Real (theParentPxSizeY)); - thePxLeft = thePxCenterX - thePxSizeX / 2; - thePxTop = thePxCenterY - thePxSizeY / 2; - } - - inline void ConvertCoordinates (const Standard_Integer theParentPxSizeX, const Standard_Integer theParentPxSizeY, - const Standard_Integer thePxLeft, const Standard_Integer thePxTop, - const Standard_Integer thePxSizeX, const Standard_Integer thePxSizeY, - Standard_Real& theQCenterX, Standard_Real& theQCenterY, - Standard_Real& theQSizeX, Standard_Real& theQSizeY) - { - Standard_Real theParentSizeMin = Min (theParentPxSizeX, theParentPxSizeY); - theQSizeX = Standard_Real(thePxSizeX) / theParentSizeMin; - theQSizeY = Standard_Real(thePxSizeY) / theParentSizeMin; - Standard_Integer thePxCenterX = thePxLeft + thePxSizeX / 2; - Standard_Integer thePxCenterY = thePxTop + thePxSizeY / 2; - theQCenterX = Standard_Real (thePxCenterX) / Standard_Real (theParentPxSizeX); - theQCenterY = 1.0 - Standard_Real (thePxCenterY) / Standard_Real (theParentPxSizeY); - } - - inline void FitIn (const Standard_Integer theParentPxSizeX, const Standard_Integer theParentPxSizeY, - Standard_Integer& thePxLeft, Standard_Integer& thePxTop, - Standard_Integer& thePxSizeX, Standard_Integer& thePxSizeY) - { - if (thePxLeft < 0) - { - //thePxSizeX -= 2 * thePxLeft; - thePxLeft = 0; - } - if ((thePxLeft + thePxSizeX) > theParentPxSizeX) - { - thePxSizeX = theParentPxSizeX - thePxLeft; - } - - if (thePxTop < 0) - { - //thePxSizeY -= 2 * thePxTop; - thePxTop = 0; - } - if ((thePxTop + thePxSizeY) > theParentPxSizeY) - { - thePxSizeY = theParentPxSizeY - thePxTop; - } - } - -} // namespace Aspect_Convert - -#endif /* _Aspect_Convert_HeaderFile */ diff --git a/src/Aspect/Aspect_Window.hxx b/src/Aspect/Aspect_Window.hxx index 64513cde44..c7153f0bfd 100644 --- a/src/Aspect/Aspect_Window.hxx +++ b/src/Aspect/Aspect_Window.hxx @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ DEFINE_STANDARD_HANDLE(Aspect_Window, Standard_Transient) //! Defines a window. class Aspect_Window : public Standard_Transient { - + DEFINE_STANDARD_RTTIEXT(Aspect_Window, Standard_Transient) public: //! Modifies the window background. @@ -102,6 +103,9 @@ public: //! Returns native Window FB config (GLXFBConfig on Xlib) Standard_EXPORT virtual Aspect_FBConfig NativeFBConfig() const = 0; + //! Returns connection to Display or NULL. + const Handle(Aspect_DisplayConnection)& DisplayConnection() const { return myDisplay; } + //! Sets window title. virtual void SetTitle (const TCollection_AsciiString& theTitle) { (void )theTitle; } @@ -114,12 +118,29 @@ public: //! on platforms implementing thread-unsafe connections to display. //! NULL can be passed instead otherwise. virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp) { (void )theDisp; } - + +public: + + //! Return device pixel ratio (logical to backing store scale factor). + virtual Standard_Real DevicePixelRatio() const { return 1.0; } + + //! Convert point from logical units into backing store units. + virtual Graphic3d_Vec2d ConvertPointToBacking (const Graphic3d_Vec2d& thePnt) const + { + return thePnt * DevicePixelRatio(); + } + + //! Convert point from backing store units to logical units. + virtual Graphic3d_Vec2d ConvertPointFromBacking (const Graphic3d_Vec2d& thePnt) const + { + return thePnt / DevicePixelRatio(); + } + +public: + //! Dumps the content of me into the stream Standard_EXPORT void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const; - DEFINE_STANDARD_RTTIEXT(Aspect_Window,Standard_Transient) - protected: //! Initializes the data of a Window. @@ -127,6 +148,7 @@ protected: protected: + Handle(Aspect_DisplayConnection) myDisplay; //!< Display connection Aspect_Background MyBackground; Aspect_GradientBackground MyGradientBackground; Aspect_FillMethod MyBackgroundFillMethod; diff --git a/src/Aspect/Aspect_WindowInputListener.cxx b/src/Aspect/Aspect_WindowInputListener.cxx index ed6478b812..1fb21ebecd 100644 --- a/src/Aspect/Aspect_WindowInputListener.cxx +++ b/src/Aspect/Aspect_WindowInputListener.cxx @@ -75,6 +75,70 @@ void Aspect_WindowInputListener::KeyFromAxis (Aspect_VKey theNegative, myKeys.KeyFromAxis (theNegative, thePositive, theTime, thePressure); } +// ======================================================================= +// function : AddTouchPoint +// purpose : +// ======================================================================= +void Aspect_WindowInputListener::AddTouchPoint (Standard_Size theId, + const Graphic3d_Vec2d& thePnt, + Standard_Boolean theClearBefore) +{ + if (theClearBefore) + { + RemoveTouchPoint ((Standard_Size )-1); + } + + myTouchPoints.Add (theId, Aspect_Touch (thePnt, false)); +} + +// ======================================================================= +// function : RemoveTouchPoint +// purpose : +// ======================================================================= +bool Aspect_WindowInputListener::RemoveTouchPoint (Standard_Size theId, + Standard_Boolean theClearSelectPnts) +{ + (void )theClearSelectPnts; + if (theId == (Standard_Size )-1) + { + myTouchPoints.Clear (false); + } + else + { + const Standard_Integer anOldExtent = myTouchPoints.Extent(); + myTouchPoints.RemoveKey (theId); + if (myTouchPoints.Extent() == anOldExtent) + { + return false; + } + } + + if (myTouchPoints.Extent() == 1) + { + // avoid incorrect transition from pinch to one finger + Aspect_Touch& aFirstTouch = myTouchPoints.ChangeFromIndex (1); + aFirstTouch.To = aFirstTouch.From; + } + return true; +} + +// ======================================================================= +// function : UpdateTouchPoint +// purpose : +// ======================================================================= +void Aspect_WindowInputListener::UpdateTouchPoint (Standard_Size theId, + const Graphic3d_Vec2d& thePnt) +{ + if (Aspect_Touch* aTouch = myTouchPoints.ChangeSeek (theId)) + { + aTouch->To = thePnt; + } + else + { + AddTouchPoint (theId, thePnt); + } +} + // ======================================================================= // function : update3dMouseTranslation // purpose : diff --git a/src/Aspect/Aspect_WindowInputListener.hxx b/src/Aspect/Aspect_WindowInputListener.hxx index 88e8825e71..4ca36566d7 100644 --- a/src/Aspect/Aspect_WindowInputListener.hxx +++ b/src/Aspect/Aspect_WindowInputListener.hxx @@ -15,6 +15,7 @@ #define _Aspect_WindowInputListener_HeaderFile #include +#include #include #include #include @@ -159,6 +160,39 @@ public: //! @name mouse input //! Return last mouse position. const Graphic3d_Vec2i& LastMousePosition() const { return myMousePositionLast; } +public: //! @name multi-touch input + + //! Return TRUE if touches map is not empty. + bool HasTouchPoints() const { return !myTouchPoints.IsEmpty(); } + + //! Return map of active touches. + const Aspect_TouchMap& TouchPoints() const { return myTouchPoints; } + + //! Add touch point with the given ID. + //! This method is expected to be called from UI thread. + //! @param theId touch unique identifier + //! @param thePnt touch coordinates + //! @param theClearBefore if TRUE previously registered touches will be removed + Standard_EXPORT virtual void AddTouchPoint (Standard_Size theId, + const Graphic3d_Vec2d& thePnt, + Standard_Boolean theClearBefore = false); + + //! Remove touch point with the given ID. + //! This method is expected to be called from UI thread. + //! @param theId touch unique identifier + //! @param theClearSelectPnts if TRUE will initiate clearing of selection points + //! @return TRUE if point has been removed + Standard_EXPORT virtual bool RemoveTouchPoint (Standard_Size theId, + Standard_Boolean theClearSelectPnts = false); + + //! Update touch point with the given ID. + //! If point with specified ID was not registered before, it will be added. + //! This method is expected to be called from UI thread. + //! @param theId touch unique identifier + //! @param thePnt touch coordinates + Standard_EXPORT virtual void UpdateTouchPoint (Standard_Size theId, + const Graphic3d_Vec2d& thePnt); + public: //! @name 3d mouse input //! Return acceleration ratio for translation event; 2.0 by default. @@ -222,6 +256,10 @@ protected: //! @name mouse input variables Aspect_VKeyMouse myMousePressed; //!< active mouse buttons Aspect_VKeyFlags myMouseModifiers; //!< active key modifiers passed with last mouse event +protected: + + Aspect_TouchMap myTouchPoints; //!< map of active touches + protected: //! @name 3d mouse input variables bool my3dMouseButtonState[32];//!< cached button state diff --git a/src/Aspect/FILES b/src/Aspect/FILES index e33a7c57d8..492dcb79a4 100755 --- a/src/Aspect/FILES +++ b/src/Aspect/FILES @@ -6,7 +6,6 @@ Aspect_Background.hxx Aspect_CircularGrid.cxx Aspect_CircularGrid.hxx Aspect_ColorSpace.hxx -Aspect_Convert.hxx Aspect_Display.hxx Aspect_DisplayConnection.cxx Aspect_DisplayConnection.hxx diff --git a/src/Cocoa/Cocoa_Window.mm b/src/Cocoa/Cocoa_Window.mm index bd8c2e3c42..d491b11e17 100644 --- a/src/Cocoa/Cocoa_Window.mm +++ b/src/Cocoa/Cocoa_Window.mm @@ -26,7 +26,6 @@ #include #include -#include #include IMPLEMENT_STANDARD_RTTIEXT(Cocoa_Window,Aspect_Window) diff --git a/src/DRAWEXE/CMakeLists.txt b/src/DRAWEXE/CMakeLists.txt index 3c2b63144b..02dd26a9d4 100644 --- a/src/DRAWEXE/CMakeLists.txt +++ b/src/DRAWEXE/CMakeLists.txt @@ -49,6 +49,7 @@ if (EMSCRIPTEN) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s MODULARIZE=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORT_NAME='createDRAWEXE'") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s \"EXTRA_EXPORTED_RUNTIME_METHODS=['FS']\"") # Embed Draw Harness .tcl scripts at recognizable location. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../DrawResources@/DrawResources") diff --git a/src/DRAWEXE/DRAWEXE.cxx b/src/DRAWEXE/DRAWEXE.cxx index 0f4a2f2312..f76eefd835 100644 --- a/src/DRAWEXE/DRAWEXE.cxx +++ b/src/DRAWEXE/DRAWEXE.cxx @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,85 @@ #include #endif +Standard_IMPORT Standard_Boolean Draw_Interprete (const char* theCommand); + +#if defined(__EMSCRIPTEN__) +#include +#include + +//! Draw Harness interface for JavaScript. +class DRAWEXE +{ +public: + //! Evaluate Tcl command. + static int eval (const std::string& theCommand) + { + int aRes = 0; + try + { + OCC_CATCH_SIGNALS + //aRes = Draw::GetInterpretor().Eval (theCommand.c_str()); + aRes = Draw_Interprete (theCommand.c_str()) ? 1 : 0; + } + catch (Standard_Failure& anExcept) + { + std::cout << "Failed to evaluate command: " << anExcept.GetMessageString() << std::endl; + } + return aRes; + } + + //! Check if Tcl command is complete. + static bool isComplete (const std::string& theCommand) + { + return Draw::GetInterpretor().Complete (theCommand.c_str()); + } +}; + +//! Print message to Module.printMessage callback. +EM_JS(void, occJSPrintMessage, (const char* theStr, int theGravity), { + if (Module.printMessage != undefined && Module.printMessage != null) { + Module.printMessage (UTF8ToString(theStr), theGravity); + } else if (Module.print != undefined && Module.print != null) { + Module.print (UTF8ToString(theStr)); + } else { + //console.info (UTF8ToString(theStr)); + } +}); + +//! Auxiliary printer to a Module.printMessage callback accepting text and gravity. +class DRAWEXE_WasmModulePrinter : public Message_Printer +{ + DEFINE_STANDARD_RTTI_INLINE(DRAWEXE_WasmModulePrinter, Message_Printer) +public: + + //! Main constructor. + DRAWEXE_WasmModulePrinter (const Message_Gravity theTraceLevel = Message_Info) + { + SetTraceLevel (theTraceLevel); + } + + //! Destructor. + virtual ~DRAWEXE_WasmModulePrinter() {} + +protected: + + //! Puts a message. + virtual void send (const TCollection_AsciiString& theString, + const Message_Gravity theGravity) const Standard_OVERRIDE + { + if (theGravity >= myTraceLevel) + { + occJSPrintMessage (theString.ToCString(), (int )theGravity); + } + } +}; + +EMSCRIPTEN_BINDINGS(DRAWEXE) { + emscripten::function("eval", &DRAWEXE::eval); + emscripten::function("isComplete", &DRAWEXE::isComplete); +} +#endif + #ifdef OCCT_NO_PLUGINS //! Mimic pload command by loading pre-defined set of statically linked plugins. static Standard_Integer Pload (Draw_Interpretor& theDI, @@ -154,8 +234,13 @@ void Draw_InitAppli (Draw_Interpretor& theDI) { #if defined(__EMSCRIPTEN__) // open JavaScript console within the Browser to see this output - Handle(Message_PrinterSystemLog) aJSConsolePrinter = new Message_PrinterSystemLog ("DRAWEXE"); + Message_Gravity aGravity = Message_Info; + Handle(Message_PrinterSystemLog) aJSConsolePrinter = new Message_PrinterSystemLog ("DRAWEXE", aGravity); Message::DefaultMessenger()->AddPrinter (aJSConsolePrinter); + // replace printer into std::cout by a printer into a custom callback Module.printMessage accepting message gravity + Message::DefaultMessenger()->RemovePrinters (STANDARD_TYPE(Message_PrinterOStream)); + Handle(DRAWEXE_WasmModulePrinter) aJSModulePrinter = new DRAWEXE_WasmModulePrinter (aGravity); + Message::DefaultMessenger()->AddPrinter (aJSModulePrinter); #endif Draw::Commands (theDI); diff --git a/src/DRAWEXE/DRAWEXE.html b/src/DRAWEXE/DRAWEXE.html index 4147bf1a24..fc4852731f 100644 --- a/src/DRAWEXE/DRAWEXE.html +++ b/src/DRAWEXE/DRAWEXE.html @@ -11,10 +11,81 @@
-

Output (open JavaScript console):

+

For output - open JavaScript console in your Browser.

diff --git a/src/Draw/Draw_Window.cxx b/src/Draw/Draw_Window.cxx index c30c6bd096..5b111edfe7 100644 --- a/src/Draw/Draw_Window.cxx +++ b/src/Draw/Draw_Window.cxx @@ -39,6 +39,15 @@ #include #endif +#if defined(__EMSCRIPTEN__) + #include + + //! Returns Module.noExitRuntime flag. + EM_JS(bool, occJSModuleNoExitRuntime, (), { + return Module.noExitRuntime === true; + }); +#endif + #ifdef HAVE_TK #if defined(__APPLE__) && !defined(HAVE_XLIB) // use forward declaration for small subset of used Tk functions @@ -266,6 +275,9 @@ Draw_Window::Draw_Window (const char* theTitle, } getDrawWindowList().Append (this); +#else + (void )theParent; + (void )theWin; #endif init (anXY, aSize); @@ -843,7 +855,9 @@ void Draw_Window::DrawString (Standard_Integer theX, Standard_Integer theY, #elif defined(HAVE_XLIB) XDrawString (Draw_WindowDisplay, GetDrawable(), myBase->gc, theX, theY, (char* )theText, strlen(theText)); #else - // + (void )theX; + (void )theY; + (void )theText; #endif } @@ -1200,11 +1214,16 @@ void Run_Appli(Standard_Boolean (*interprete) (const char*)) { Interprete = interprete; + bool toWaitInput = true; +#ifdef __EMSCRIPTEN__ + toWaitInput = !occJSModuleNoExitRuntime(); +#endif + // Commands will come from standard input, so set up an event handler for standard input. // If the input device is aEvaluate the .rc file, if one has been specified, // set up an event handler for standard input, and print a prompt if the input device is a terminal. Tcl_Channel anInChannel = Tcl_GetStdChannel(TCL_STDIN); - if (anInChannel) + if (anInChannel && toWaitInput) { Tcl_CreateChannelHandler (anInChannel, TCL_READABLE, StdinProc, (ClientData )anInChannel); } @@ -1237,6 +1256,11 @@ void Run_Appli(Standard_Boolean (*interprete) (const char*)) // When there are no windows left, Tk_MainLoop returns and we exit. Tk_MainLoop(); #else + if (!toWaitInput) + { + return; + } + for (;;) { Tcl_DoOneEvent (0); // practically the same as Tk_MainLoop() diff --git a/src/TKService/PACKAGES b/src/TKService/PACKAGES index 87a69cc7f1..64e8a10d8e 100755 --- a/src/TKService/PACKAGES +++ b/src/TKService/PACKAGES @@ -3,6 +3,7 @@ Graphic3d Xw Image Media +Wasm WNT Cocoa Font diff --git a/src/ViewerTest/ViewerTest_EventManager.cxx b/src/ViewerTest/ViewerTest_EventManager.cxx index 2a2ed2f2f0..4e8673be5a 100644 --- a/src/ViewerTest/ViewerTest_EventManager.cxx +++ b/src/ViewerTest/ViewerTest_EventManager.cxx @@ -17,12 +17,39 @@ #include #include +#include #include #include #include #include +#include #include #include +#include + +#if defined(_WIN32) + // +#elif defined(HAVE_XLIB) + #include + #include + #include +#elif defined(__EMSCRIPTEN__) + #include + #include + #include + + //! Callback flushing events and redrawing the WebGL canvas. + static void onWasmRedrawView (void* ) + { + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + const Handle(V3d_View)& aView = ViewerTest::CurrentView(); + const Handle(AIS_InteractiveContext)& aCtx = ViewerTest::GetAISContext(); + if (!aViewCtrl.IsNull() && !aView.IsNull() && !aCtx.IsNull()) + { + aViewCtrl->ProcessExpose(); + } + } +#endif Standard_IMPORT Standard_Boolean Draw_Interprete (const char* theCommand); @@ -48,7 +75,8 @@ ViewerTest_EventManager::ViewerTest_EventManager (const Handle(V3d_View)& : myCtx (theCtx), myView (theView), myToPickPnt (Standard_False), - myIsTmpContRedraw (Standard_False) + myIsTmpContRedraw (Standard_False), + myUpdateRequests (0) { myViewAnimation = GlobalViewAnimation(); @@ -73,6 +101,9 @@ ViewerTest_EventManager::ViewerTest_EventManager (const Handle(V3d_View)& addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_Right | Aspect_VKeyFlags_SHIFT); addActionHotKeys (Aspect_VKey_NavSlideUp, Aspect_VKey_Up | Aspect_VKeyFlags_SHIFT); addActionHotKeys (Aspect_VKey_NavSlideDown, Aspect_VKey_Down | Aspect_VKeyFlags_SHIFT); + + // window could be actually not yet set to the View + //SetupWindowCallbacks (theView->Window()); } //======================================================================= @@ -89,6 +120,23 @@ ViewerTest_EventManager::~ViewerTest_EventManager() } } +// ======================================================================= +// function : UpdateMouseClick +// purpose : +// ======================================================================= +bool ViewerTest_EventManager::UpdateMouseClick (const Graphic3d_Vec2i& thePoint, + Aspect_VKeyMouse theButton, + Aspect_VKeyFlags theModifiers, + bool theIsDoubleClick) +{ + if (theIsDoubleClick && !myView.IsNull() && !myCtx.IsNull()) + { + FitAllAuto (myCtx, myView); + return true; + } + return AIS_ViewController::UpdateMouseClick (thePoint, theButton, theModifiers, theIsDoubleClick); +} + //======================================================================= //function : UpdateMouseButtons //purpose : @@ -124,7 +172,6 @@ void ViewerTest_EventManager::ProcessExpose() { if (!myView.IsNull()) { - myView->Invalidate(); FlushViewEvents (myCtx, myView, true); } } @@ -136,6 +183,7 @@ void ViewerTest_EventManager::ProcessExpose() void ViewerTest_EventManager::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx, const Handle(V3d_View)& theView) { + myUpdateRequests = 0; AIS_ViewController::handleViewRedraw (theCtx, theView); // On non-Windows platforms Aspect_Window::InvalidateContent() from rendering thread does not work as expected @@ -148,10 +196,16 @@ void ViewerTest_EventManager::handleViewRedraw (const Handle(AIS_InteractiveCont && (!aRedrawer.IsStarted() || aRedrawer.IsPaused())) { myIsTmpContRedraw = true; - #ifndef _WIN32 + #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) aRedrawer.Start (theView->Window(), 60.0); #endif } + + // ask more frames + ++myUpdateRequests; + #if defined(__EMSCRIPTEN__) + emscripten_async_call (onWasmRedrawView, this, 0); + #endif } else if (myIsTmpContRedraw) { @@ -180,7 +234,9 @@ void ViewerTest_EventManager::ProcessConfigure (bool theIsResized) return; } + myView->Window()->DoResize(); myView->MustBeResized(); + myView->Invalidate(); FlushViewEvents (myCtx, myView, true); } } @@ -191,10 +247,25 @@ void ViewerTest_EventManager::ProcessConfigure (bool theIsResized) //============================================================================== void ViewerTest_EventManager::ProcessInput() { - if (!myView.IsNull()) + if (myView.IsNull()) { - FlushViewEvents (myCtx, myView, true); + return; + } + +#if defined(__EMSCRIPTEN__) + // Queue onWasmRedrawView() callback to redraw canvas after all user input is flushed by browser. + // Redrawing viewer on every single message would be a pointless waste of resources, + // as user will see only the last drawn frame due to WebGL implementation details. + if (++myUpdateRequests == 1) + { + #if defined(__EMSCRIPTEN__) + emscripten_async_call (onWasmRedrawView, this, 0); + #endif } +#else + // handle synchronously + ProcessExpose(); +#endif } // ======================================================================= @@ -538,3 +609,145 @@ void ViewerTest_EventManager::ProcessKeyPress (Aspect_VKey theKey) Draw_Interprete (aCmd.ToCString()); } } + +#if defined(__EMSCRIPTEN__) +//! Handle browser window resize event. +static EM_BOOL onResizeCallback (int theEventType, const EmscriptenUiEvent* theEvent, void* ) +{ + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + if (!aViewCtrl.IsNull() + && !ViewerTest::CurrentView().IsNull()) + { + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window()); + return aWindow->ProcessUiEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE; + } + return EM_FALSE; +} + +//! Handle mouse input event. +static EM_BOOL onWasmMouseCallback (int theEventType, const EmscriptenMouseEvent* theEvent, void* ) +{ + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + if (!aViewCtrl.IsNull() + && !ViewerTest::CurrentView().IsNull()) + { + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window()); + return aWindow->ProcessMouseEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE; + } + return EM_FALSE; +} + +//! Handle mouse wheel event. +static EM_BOOL onWasmWheelCallback (int theEventType, const EmscriptenWheelEvent* theEvent, void* ) +{ + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + if (!aViewCtrl.IsNull() + && !ViewerTest::CurrentView().IsNull()) + { + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window()); + return aWindow->ProcessWheelEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE; + } + return EM_FALSE; +} + +//! Handle touch input event. +static EM_BOOL onWasmTouchCallback (int theEventType, const EmscriptenTouchEvent* theEvent, void* ) +{ + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + if (!aViewCtrl.IsNull() + && !ViewerTest::CurrentView().IsNull()) + { + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window()); + return aWindow->ProcessTouchEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE; + } + return EM_FALSE; +} + +//! Handle keyboard input event. +static EM_BOOL onWasmKeyCallback (int theEventType, const EmscriptenKeyboardEvent* theEvent, void* ) +{ + Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager(); + if (!aViewCtrl.IsNull() + && !ViewerTest::CurrentView().IsNull()) + { + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window()); + aWindow->ProcessKeyEvent (*aViewCtrl, theEventType, theEvent); + return EM_TRUE; + } + return EM_FALSE; +} +#endif + +// ============================================================================== +// function : SetupWindowCallbacks +// purpose : +// ============================================================================== +void ViewerTest_EventManager::SetupWindowCallbacks (const Handle(Aspect_Window)& theWin) +{ +#ifdef _WIN32 + (void )theWin; +#elif defined(HAVE_XLIB) + // X11 + Window anXWin = (Window )theWin->NativeHandle(); + Display* anXDisplay = (Display* )theWin->DisplayConnection()->GetDisplayAspect(); + XSynchronize (anXDisplay, 1); + + // X11 : For keyboard on SUN + XWMHints aWmHints; + memset (&aWmHints, 0, sizeof(aWmHints)); + aWmHints.flags = InputHint; + aWmHints.input = 1; + XSetWMHints (anXDisplay, anXWin, &aWmHints); + + XSelectInput (anXDisplay, anXWin, + ExposureMask | KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask + | StructureNotifyMask + | PointerMotionMask + | Button1MotionMask | Button2MotionMask + | Button3MotionMask | FocusChangeMask); + Atom aDeleteWindowAtom = theWin->DisplayConnection()->GetAtom (Aspect_XA_DELETE_WINDOW); + XSetWMProtocols (anXDisplay, anXWin, &aDeleteWindowAtom, 1); + + XSynchronize (anXDisplay, 0); +#elif defined(__EMSCRIPTEN__) + Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (theWin); + if (aWindow->CanvasId().IsEmpty() + || aWindow->CanvasId() == "#") + { + Message::SendFail ("Error: unable registering callbacks to Module.canvas"); + return; + } + + const char* aTargetId = aWindow->CanvasId().ToCString(); + const EM_BOOL toUseCapture = EM_TRUE; + void* anOpaque = NULL; //this; // unused + + // make sure to clear previously set listeners (e.g. created by another ViewerTest_EventManager instance) + emscripten_html5_remove_all_event_listeners(); + + // resize event implemented only for a window by browsers, + // so that if web application changes canvas size by other means it should use another way to tell OCCT about resize + emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, anOpaque, toUseCapture, onResizeCallback); + + emscripten_set_mousedown_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_mouseup_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_mousemove_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_dblclick_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_click_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_mouseenter_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_mouseleave_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback); + emscripten_set_wheel_callback (aTargetId, anOpaque, toUseCapture, onWasmWheelCallback); + + emscripten_set_touchstart_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback); + emscripten_set_touchend_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback); + emscripten_set_touchmove_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback); + emscripten_set_touchcancel_callback(aTargetId, anOpaque, toUseCapture, onWasmTouchCallback); + + // keyboard input requires a focusable element or EMSCRIPTEN_EVENT_TARGET_WINDOW + emscripten_set_keydown_callback (aTargetId, anOpaque, toUseCapture, onWasmKeyCallback); + emscripten_set_keyup_callback (aTargetId, anOpaque, toUseCapture, onWasmKeyCallback); +#else + (void )theWin; +#endif +} diff --git a/src/ViewerTest/ViewerTest_EventManager.hxx b/src/ViewerTest/ViewerTest_EventManager.hxx index 4b545988ce..23dfe7dcee 100644 --- a/src/ViewerTest/ViewerTest_EventManager.hxx +++ b/src/ViewerTest/ViewerTest_EventManager.hxx @@ -22,6 +22,7 @@ #include class AIS_InteractiveContext; +class Aspect_Window; class V3d_View; DEFINE_STANDARD_HANDLE(ViewerTest_EventManager, Standard_Transient) @@ -58,6 +59,9 @@ public: //! Destructor. Standard_EXPORT virtual ~ViewerTest_EventManager(); + //! Setup or adjust window callbacks. + Standard_EXPORT static void SetupWindowCallbacks (const Handle(Aspect_Window)& theWin); + //! Return interactive context. const Handle(AIS_InteractiveContext)& Context() const { return myCtx; } @@ -75,6 +79,12 @@ public: myPickPntArgVec[2] = theArgZ; } + //! Handle mouse button click event. + Standard_EXPORT virtual bool UpdateMouseClick (const Graphic3d_Vec2i& thePoint, + Aspect_VKeyMouse theButton, + Aspect_VKeyFlags theModifiers, + bool theIsDoubleClick) Standard_OVERRIDE; + //! Handle mouse button press/release event. Standard_EXPORT virtual bool UpdateMouseButtons (const Graphic3d_Vec2i& thePoint, Aspect_VKeyMouse theButtons, @@ -138,6 +148,8 @@ private: Standard_Boolean myToPickPnt; Standard_Boolean myIsTmpContRedraw; + unsigned int myUpdateRequests; //!< counter for unhandled update requests + }; #endif // _ViewerTest_EventManager_HeaderFile diff --git a/src/ViewerTest/ViewerTest_ViewerCommands.cxx b/src/ViewerTest/ViewerTest_ViewerCommands.cxx index 0a37eb4eef..ac31f4e44a 100644 --- a/src/ViewerTest/ViewerTest_ViewerCommands.cxx +++ b/src/ViewerTest/ViewerTest_ViewerCommands.cxx @@ -112,6 +112,9 @@ #include #elif defined(__APPLE__) #include +#elif defined(__EMSCRIPTEN__) + #include + #include #else #include #endif @@ -135,10 +138,32 @@ static void VProcessEvents(ClientData,int); typedef Cocoa_Window ViewerTest_Window; extern void ViewerTest_SetCocoaEventManagerView (const Handle(Cocoa_Window)& theWindow); extern void GetCocoaScreenResolution (Standard_Integer& theWidth, Standard_Integer& theHeight); +#elif defined(__EMSCRIPTEN__) +typedef Wasm_Window ViewerTest_Window; #else typedef Aspect_NeutralWindow ViewerTest_Window; #endif +#if defined(__EMSCRIPTEN__) +//! Return DOM id of default WebGL canvas from Module.canvas. +EM_JS(char*, occJSModuleCanvasId, (), { + const aCanvasId = Module.canvas.id; + const aNbBytes = lengthBytesUTF8 (aCanvasId) + 1; + const aStrPtr = Module._malloc (aNbBytes); + stringToUTF8 (aCanvasId, aStrPtr, aNbBytes); + return aStrPtr; +}); + +//! Return DOM id of default WebGL canvas from Module.canvas. +static TCollection_AsciiString getModuleCanvasId() +{ + char* aRawId = occJSModuleCanvasId(); + TCollection_AsciiString anId (aRawId != NULL ? aRawId : ""); + free (aRawId); + return anId; +} +#endif + static Handle(ViewerTest_Window)& VT_GetWindow() { static Handle(ViewerTest_Window) aWindow; @@ -160,8 +185,6 @@ NCollection_DoubleMap ViewerTest_myV static NCollection_DoubleMap ViewerTest_myContexts; static NCollection_DoubleMap ViewerTest_myDrivers; -static void OSWindowSetup(); - static struct { Quantity_Color FlatColor; @@ -1663,15 +1686,18 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft // window fit in the small screens (actual for remote desktops, see #23003). // The position corresponds to the window's client area, thus some // gap is added for window frame to be visible. - Standard_Integer aPxLeft = 20; - Standard_Integer aPxTop = 40; - Standard_Integer aPxWidth = 409; - Standard_Integer aPxHeight = 409; + Standard_Integer aPxLeft = 20, aPxTop = 40; + Standard_Integer aPxWidth = 409, aPxHeight = 409; + Standard_Boolean isDefViewSize = Standard_True; Standard_Boolean toCreateViewer = Standard_False; const Standard_Boolean isVirtual = Draw_VirtualWindows || theIsVirtual; if (!theViewToClone.IsNull()) { theViewToClone->Window()->Size (aPxWidth, aPxHeight); + isDefViewSize = Standard_False; + #if !defined(__EMSCRIPTEN__) + (void )isDefViewSize; + #endif } Handle(Graphic3d_GraphicDriverFactory) aFactory = Graphic3d_GraphicDriverFactory::DefaultDriverFactory(); @@ -1692,17 +1718,29 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft Handle(Graphic3d_GraphicDriver) aGraphicDriver; ViewerTest_Names aViewNames(theViewName); - if (ViewerTest_myViews.IsBound1 (aViewNames.GetViewName ())) + if (ViewerTest_myViews.IsBound1 (aViewNames.GetViewName())) + { aViewNames.SetViewName (aViewNames.GetViewerName() + "/" + CreateName(ViewerTest_myViews, "View")); + } if (thePxLeft != 0) + { aPxLeft = thePxLeft; + } if (thePxTop != 0) + { aPxTop = thePxTop; + } if (thePxWidth != 0) + { + isDefViewSize = Standard_False; aPxWidth = thePxWidth; + } if (thePxHeight != 0) + { + isDefViewSize = Standard_False; aPxHeight = thePxHeight; + } // Get graphic driver (create it or get from another view) const bool isNewDriver = !ViewerTest_myDrivers.IsBound1 (aViewNames.GetDriverName()); @@ -1862,6 +1900,25 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft aPxLeft, aPxTop, aPxWidth, aPxHeight); ViewerTest_SetCocoaEventManagerView (VT_GetWindow()); +#elif defined(__EMSCRIPTEN__) + // current EGL implementation in Emscripten supports only one global WebGL canvas returned by Module.canvas property; + // the code should be revised for handling multiple canvas elements (which is technically also possible) + TCollection_AsciiString aCanvasId = getModuleCanvasId(); + if (!aCanvasId.IsEmpty()) + { + aCanvasId = TCollection_AsciiString("#") + aCanvasId; + } + + VT_GetWindow() = new Wasm_Window (aCanvasId); + Graphic3d_Vec2i aRealSize; + VT_GetWindow()->Size (aRealSize.x(), aRealSize.y()); + if (!isDefViewSize || (aRealSize.x() <= 0 && aRealSize.y() <= 0)) + { + // Wasm_Window wraps an existing HTML element without creating a new one. + // Keep size defined on a web page instead of defaulting to 409x409 (as in case of other platform), + // but resize canvas if vinit has been called with explicitly specified dimensions. + VT_GetWindow()->SetSizeLogical (Graphic3d_Vec2d (aPxWidth, aPxHeight)); + } #else // not implemented VT_GetWindow() = new Aspect_NeutralWindow(); @@ -1887,7 +1944,8 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft ViewerTest_myViews.Bind (aViewNames.GetViewName(), aView); // Setup for X11 or NT - OSWindowSetup(); + SetDisplayConnection (ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection()); + ViewerTest_EventManager::SetupWindowCallbacks (VT_GetWindow()); // Set parameters for V3d_View and V3d_Viewer const Handle (V3d_View) aV3dView = ViewerTest::CurrentView(); @@ -2540,15 +2598,7 @@ void ActivateView (const TCollection_AsciiString& theViewName, ViewerTest::CurrentView (aView); ViewerTest::SetAISContext (anAISContext); aView->Window()->SetTitle (TCollection_AsciiString("3D View - ") + theViewName + "(*)"); -#if defined(_WIN32) - VT_GetWindow() = Handle(WNT_Window)::DownCast(ViewerTest::CurrentView()->Window()); -#elif defined(HAVE_XLIB) - VT_GetWindow() = Handle(Xw_Window)::DownCast(ViewerTest::CurrentView()->Window()); -#elif defined(__APPLE__) - VT_GetWindow() = Handle(Cocoa_Window)::DownCast(ViewerTest::CurrentView()->Window()); -#else - VT_GetWindow() = Handle(Aspect_NeutralWindow)::DownCast(ViewerTest::CurrentView()->Window()); -#endif + VT_GetWindow() = Handle(ViewerTest_Window)::DownCast(ViewerTest::CurrentView()->Window()); SetDisplayConnection(ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection()); if (theToUpdate) { @@ -3473,45 +3523,6 @@ int ViewerMainLoop (Standard_Integer , const char** ) } #endif -//============================================================================== -//function : OSWindowSetup -//purpose : Setup for the X11 window to be able to catch the event -//============================================================================== -static void OSWindowSetup() -{ -#ifdef _WIN32 - // - -#elif defined(HAVE_XLIB) - // X11 - Window anXWin = VT_GetWindow()->XWindow(); - SetDisplayConnection (ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection()); - Display* aDisplay = (Display* )GetDisplayConnection()->GetDisplayAspect(); - XSynchronize (aDisplay, 1); - - // X11 : For keyboard on SUN - XWMHints aWmHints; - memset (&aWmHints, 0, sizeof(aWmHints)); - aWmHints.flags = InputHint; - aWmHints.input = 1; - XSetWMHints (aDisplay, anXWin, &aWmHints); - - XSelectInput (aDisplay, anXWin, - ExposureMask | KeyPressMask | KeyReleaseMask - | ButtonPressMask | ButtonReleaseMask - | StructureNotifyMask - | PointerMotionMask - | Button1MotionMask | Button2MotionMask - | Button3MotionMask | FocusChangeMask); - Atom aDeleteWindowAtom = GetDisplayConnection()->GetAtom (Aspect_XA_DELETE_WINDOW); - XSetWMProtocols (aDisplay, anXWin, &aDeleteWindowAtom, 1); - - XSynchronize (aDisplay, 0); -#else - // -#endif -} - //============================================================================== //function : VFit //purpose : diff --git a/src/WNT/WNT_Window.cxx b/src/WNT/WNT_Window.cxx index 8f9ed39f2a..d48a9d8505 100644 --- a/src/WNT/WNT_Window.cxx +++ b/src/WNT/WNT_Window.cxx @@ -21,7 +21,6 @@ #if defined(_WIN32) && !defined(OCCT_UWP) -#include #include #include #include diff --git a/src/Wasm/FILES b/src/Wasm/FILES new file mode 100644 index 0000000000..56fc2c6647 --- /dev/null +++ b/src/Wasm/FILES @@ -0,0 +1,2 @@ +Wasm_Window.cxx +Wasm_Window.hxx diff --git a/src/Wasm/Wasm_Window.cxx b/src/Wasm/Wasm_Window.cxx new file mode 100644 index 0000000000..b2a81e3499 --- /dev/null +++ b/src/Wasm/Wasm_Window.cxx @@ -0,0 +1,779 @@ +// Created by: Kirill Gavrilov +// Copyright (c) 2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include + +#if defined(__EMSCRIPTEN__) + #include + #include + #include +#endif + +IMPLEMENT_STANDARD_RTTIEXT(Wasm_Window, Aspect_Window) + +// ======================================================================= +// function : Wasm_Window +// purpose : +// ======================================================================= +Wasm_Window::Wasm_Window (const TCollection_AsciiString& theCanvasId, + const bool theToScaleBacking) +: myCanvasId (theCanvasId), + mySize (0), + myDevicePixelRatio (1.0), + myToScaleBacking (theToScaleBacking), + myIsMapped (true) +{ +#if defined(__EMSCRIPTEN__) + myDevicePixelRatio = emscripten_get_device_pixel_ratio(); + emscripten_get_canvas_element_size (myCanvasId.ToCString(), &mySize.x(), &mySize.y()); + if (myToScaleBacking) + { + myDevicePixelRatio = emscripten_get_device_pixel_ratio(); + Graphic3d_Vec2d aCssSize; + emscripten_get_element_css_size (myCanvasId.ToCString(), &aCssSize.x(), &aCssSize.y()); + Graphic3d_Vec2i aCanvasSize = Graphic3d_Vec2i (aCssSize * myDevicePixelRatio); + if (aCanvasSize != mySize) + { + mySize = aCanvasSize; + emscripten_set_canvas_element_size (myCanvasId.ToCString(), aCanvasSize.x(), aCanvasSize.y()); + emscripten_set_element_css_size (myCanvasId.ToCString(), aCssSize.x(), aCssSize.y()); + } + } +#endif +} + +// ======================================================================= +// function : ~Wasm_Window +// purpose : +// ======================================================================= +Wasm_Window::~Wasm_Window() +{ + // +} + +// ======================================================================= +// function : DoResize +// purpose : +// ======================================================================= +Aspect_TypeOfResize Wasm_Window::DoResize() +{ + if (IsVirtual()) + { + return Aspect_TOR_UNKNOWN; + } + +#if defined(__EMSCRIPTEN__) + emscripten_get_canvas_element_size (myCanvasId.ToCString(), &mySize.x(), &mySize.y()); + if (myToScaleBacking) + { + myDevicePixelRatio = emscripten_get_device_pixel_ratio(); + Graphic3d_Vec2d aCssSize; + emscripten_get_element_css_size (myCanvasId.ToCString(), &aCssSize.x(), &aCssSize.y()); + Graphic3d_Vec2i aCanvasSize = Graphic3d_Vec2i (aCssSize * myDevicePixelRatio); + if (aCanvasSize != mySize) + { + mySize = aCanvasSize; + emscripten_set_canvas_element_size (myCanvasId.ToCString(), aCanvasSize.x(), aCanvasSize.y()); + emscripten_set_element_css_size (myCanvasId.ToCString(), aCssSize.x(), aCssSize.y()); + } + } +#endif + return Aspect_TOR_UNKNOWN; +} + +// ======================================================================= +// function : Ratio +// purpose : +// ======================================================================= +Standard_Real Wasm_Window::Ratio() const +{ + Graphic3d_Vec2i aCanvasSize = mySize; + if (!IsVirtual()) + { + #if defined(__EMSCRIPTEN__) + emscripten_get_canvas_element_size (myCanvasId.ToCString(), &aCanvasSize.x(), &aCanvasSize.y()); + #endif + } + + return (aCanvasSize.x() != 0 && aCanvasSize.y() != 0) + ? Standard_Real(aCanvasSize.x()) / Standard_Real(aCanvasSize.y()) + : 1.0; +} + +// ======================================================================= +// function : Position +// purpose : +// ======================================================================= +void Wasm_Window::Position (Standard_Integer& theX1, Standard_Integer& theY1, + Standard_Integer& theX2, Standard_Integer& theY2) const +{ + theX1 = 0; + theY1 = 0; + if (IsVirtual()) + { + theX2 = mySize.x(); + theY2 = mySize.y(); + return; + } + +#if defined(__EMSCRIPTEN__) + emscripten_get_canvas_element_size (myCanvasId.ToCString(), &theX2, &theY2); +#endif +} + +// ======================================================================= +// function : Size +// purpose : +// ======================================================================= +void Wasm_Window::Size (Standard_Integer& theWidth, + Standard_Integer& theHeight) const +{ + if (IsVirtual()) + { + theWidth = mySize.x(); + theHeight = mySize.y(); + return; + } + +#if defined(__EMSCRIPTEN__) + emscripten_get_canvas_element_size (myCanvasId.ToCString(), &theWidth, &theHeight); +#endif +} + +// ======================================================================= +// function : SetSizeLogical +// purpose : +// ======================================================================= +void Wasm_Window::SetSizeLogical (const Graphic3d_Vec2d& theSize) +{ + mySize = Graphic3d_Vec2i (theSize * myDevicePixelRatio); + if (IsVirtual()) + { + return; + } + +#if defined(__EMSCRIPTEN__) + emscripten_set_canvas_element_size (myCanvasId.ToCString(), mySize.x(), mySize.y()); + emscripten_set_element_css_size (myCanvasId.ToCString(), theSize.x(), theSize.y()); +#endif +} + +// ======================================================================= +// function : SetSizeBacking +// purpose : +// ======================================================================= +void Wasm_Window::SetSizeBacking (const Graphic3d_Vec2i& theSize) +{ + mySize = theSize; + if (IsVirtual()) + { + return; + } + +#if defined(__EMSCRIPTEN__) + Graphic3d_Vec2i aCanvasSize = mySize; + Graphic3d_Vec2d aCssSize = Graphic3d_Vec2d (mySize) / myDevicePixelRatio; + emscripten_set_canvas_element_size (myCanvasId.ToCString(), aCanvasSize.x(), aCanvasSize.y()); + emscripten_set_element_css_size (myCanvasId.ToCString(), aCssSize.x(), aCssSize.y()); +#endif +} + +// ======================================================================= +// function : InvalidateContent +// purpose : +// ======================================================================= +void Wasm_Window::InvalidateContent (const Handle(Aspect_DisplayConnection)& ) +{ + // +} + +// ======================================================================= +// function : ProcessMessage +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessMessage (Aspect_WindowInputListener& theListener, + int theEventType, const void* theEvent) +{ +#if defined(__EMSCRIPTEN__) + switch (theEventType) + { + case EMSCRIPTEN_EVENT_MOUSEMOVE: + case EMSCRIPTEN_EVENT_MOUSEDOWN: + case EMSCRIPTEN_EVENT_MOUSEUP: + case EMSCRIPTEN_EVENT_CLICK: + case EMSCRIPTEN_EVENT_DBLCLICK: + case EMSCRIPTEN_EVENT_MOUSEENTER: + case EMSCRIPTEN_EVENT_MOUSELEAVE: + { + return ProcessMouseEvent (theListener, theEventType, (const EmscriptenMouseEvent* )theEvent); + } + case EMSCRIPTEN_EVENT_TOUCHSTART: + case EMSCRIPTEN_EVENT_TOUCHMOVE: + case EMSCRIPTEN_EVENT_TOUCHEND: + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + { + return ProcessTouchEvent (theListener, theEventType, (const EmscriptenTouchEvent* )theEvent); + } + case EMSCRIPTEN_EVENT_WHEEL: + { + return ProcessWheelEvent (theListener, theEventType, (const EmscriptenWheelEvent* )theEvent); + } + case EMSCRIPTEN_EVENT_KEYDOWN: + case EMSCRIPTEN_EVENT_KEYUP: + case EMSCRIPTEN_EVENT_KEYPRESS: + { + return ProcessKeyEvent (theListener, theEventType, (const EmscriptenKeyboardEvent* )theEvent); + } + case EMSCRIPTEN_EVENT_RESIZE: + case EMSCRIPTEN_EVENT_CANVASRESIZED: + { + return ProcessUiEvent (theListener, theEventType, (const EmscriptenUiEvent* )theEvent); + } + } + return false; +#else + (void )theListener; + (void )theEventType; + (void )theEvent; + return false; +#endif +} + +// ======================================================================= +// function : ProcessMouseEvent +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessMouseEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenMouseEvent* theEvent) +{ +#if defined(__EMSCRIPTEN__) + const Graphic3d_Vec2d aNewPos2d = ConvertPointToBacking (Graphic3d_Vec2d (theEvent->targetX, theEvent->targetY)); + const Graphic3d_Vec2i aNewPos2i = Graphic3d_Vec2i (aNewPos2d + Graphic3d_Vec2d (0.5)); + Aspect_VKeyFlags aFlags = 0; + if (theEvent->ctrlKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_CTRL; } + if (theEvent->shiftKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_SHIFT; } + if (theEvent->altKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_ALT; } + if (theEvent->metaKey == EM_TRUE) { aFlags |= Aspect_VKeyFlags_META; } + + const bool isEmulated = false; + const Aspect_VKeyMouse aButtons = Wasm_Window::MouseButtonsFromNative (theEvent->buttons); + switch (theEventType) + { + case EMSCRIPTEN_EVENT_MOUSEMOVE: + { + if ((aNewPos2i.x() < 0 || aNewPos2i.x() > mySize.x() + || aNewPos2i.y() < 0 || aNewPos2i.y() > mySize.y()) + && theListener.PressedMouseButtons() == Aspect_VKeyMouse_NONE) + { + return false; + } + if (theListener.UpdateMousePosition (aNewPos2i, aButtons, aFlags, isEmulated)) + { + theListener.ProcessInput(); + } + break; + } + case EMSCRIPTEN_EVENT_MOUSEDOWN: + case EMSCRIPTEN_EVENT_MOUSEUP: + { + if (aNewPos2i.x() < 0 || aNewPos2i.x() > mySize.x() + || aNewPos2i.y() < 0 || aNewPos2i.y() > mySize.y()) + { + return false; + } + if (theListener.UpdateMouseButtons (aNewPos2i, aButtons, aFlags, isEmulated)) + { + theListener.ProcessInput(); + } + break; + } + case EMSCRIPTEN_EVENT_CLICK: + case EMSCRIPTEN_EVENT_DBLCLICK: + { + if (aNewPos2i.x() < 0 || aNewPos2i.x() > mySize.x() + || aNewPos2i.y() < 0 || aNewPos2i.y() > mySize.y()) + { + return false; + } + break; + } + case EMSCRIPTEN_EVENT_MOUSEENTER: + { + break; + } + case EMSCRIPTEN_EVENT_MOUSELEAVE: + { + // there is no SetCapture() support, so that mouse unclick events outside canvas will not arrive, + // so we have to forget current state... + if (theListener.UpdateMouseButtons (aNewPos2i, Aspect_VKeyMouse_NONE, aFlags, isEmulated)) + { + theListener.ProcessInput(); + } + break; + } + } + return true; +#else + (void )theListener; + (void )theEventType; + (void )theEvent; + return false; +#endif +} + +// ======================================================================= +// function : ProcessWheelEvent +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessWheelEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenWheelEvent* theEvent) +{ +#if defined(__EMSCRIPTEN__) + if (theEventType != EMSCRIPTEN_EVENT_WHEEL) + { + return false; + } + + const Graphic3d_Vec2d aNewPos2d = ConvertPointToBacking (Graphic3d_Vec2d (theEvent->mouse.targetX, theEvent->mouse.targetY)); + const Graphic3d_Vec2i aNewPos2i = Graphic3d_Vec2i (aNewPos2d + Graphic3d_Vec2d (0.5)); + if (aNewPos2i.x() < 0 || aNewPos2i.x() > mySize.x() + || aNewPos2i.y() < 0 || aNewPos2i.y() > mySize.y()) + { + return false; + } + + double aDelta = 0.0; + switch (theEvent->deltaMode) + { + case DOM_DELTA_PIXEL: + { + aDelta = theEvent->deltaY / (5.0 * DevicePixelRatio()); + break; + } + case DOM_DELTA_LINE: + { + aDelta = theEvent->deltaY * 8.0; + break; + } + case DOM_DELTA_PAGE: + { + aDelta = theEvent->deltaY >= 0.0 ? 24.0 : -24.0; + break; + } + } + aDelta /= 15.0; + + if (theListener.UpdateMouseScroll (Aspect_ScrollDelta (aNewPos2i, -aDelta))) + { + theListener.ProcessInput(); + } + return true; +#else + (void )theListener; + (void )theEventType; + (void )theEvent; + return false; +#endif +} + +// ======================================================================= +// function : ProcessTouchEvent +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessTouchEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenTouchEvent* theEvent) +{ + bool hasUpdates = false; +#if defined(__EMSCRIPTEN__) + if (theEventType != EMSCRIPTEN_EVENT_TOUCHSTART + && theEventType != EMSCRIPTEN_EVENT_TOUCHMOVE + && theEventType != EMSCRIPTEN_EVENT_TOUCHEND + && theEventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) + { + return false; + } + + for (int aTouchIter = 0; aTouchIter < theEvent->numTouches; ++aTouchIter) + { + const EmscriptenTouchPoint& aTouch = theEvent->touches[aTouchIter]; + if (!aTouch.isChanged) + { + continue; + } + + const Standard_Size aTouchId = (Standard_Size )aTouch.identifier; + + const Graphic3d_Vec2d aNewPos2d = ConvertPointToBacking (Graphic3d_Vec2d (aTouch.targetX, aTouch.targetY)); + const Graphic3d_Vec2i aNewPos2i = Graphic3d_Vec2i (aNewPos2d + Graphic3d_Vec2d (0.5)); + switch (theEventType) + { + case EMSCRIPTEN_EVENT_TOUCHSTART: + { + if (aNewPos2i.x() >= 0 && aNewPos2i.x() < mySize.x() + && aNewPos2i.y() >= 0 && aNewPos2i.y() < mySize.y()) + { + hasUpdates = true; + theListener.AddTouchPoint (aTouchId, aNewPos2d); + } + break; + } + case EMSCRIPTEN_EVENT_TOUCHMOVE: + { + const int anOldIndex = theListener.TouchPoints().FindIndex (aTouchId); + if (anOldIndex != 0) + { + hasUpdates = true; + theListener.UpdateTouchPoint (aTouchId, aNewPos2d); + } + break; + } + case EMSCRIPTEN_EVENT_TOUCHEND: + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + { + if (theListener.RemoveTouchPoint (aTouchId)) + { + hasUpdates = true; + } + break; + } + } + } + if (hasUpdates) + { + theListener.ProcessInput(); + } +#else + (void )theEventType; + (void )theEvent; +#endif + return hasUpdates || theListener.HasTouchPoints(); +} + +// ======================================================================= +// function : ProcessKeyEvent +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessKeyEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenKeyboardEvent* theEvent) +{ +#if defined(__EMSCRIPTEN__) + if (theEventType != EMSCRIPTEN_EVENT_KEYDOWN + && theEventType != EMSCRIPTEN_EVENT_KEYUP + && theEventType != EMSCRIPTEN_EVENT_KEYPRESS) + { + return false; + } + + const double aTimeStamp = theListener.EventTime(); + const Aspect_VKey aVKey = Wasm_Window::VirtualKeyFromNative (theEvent->keyCode); + if (aVKey == Aspect_VKey_UNKNOWN) + { + return false; + } + + switch (theEventType) + { + case EMSCRIPTEN_EVENT_KEYDOWN: + { + if (theEvent->repeat == EM_TRUE) + { + return false; + } + + theListener.KeyDown (aVKey, aTimeStamp); + theListener.ProcessInput(); + return false; + } + case EMSCRIPTEN_EVENT_KEYUP: + { + theListener.KeyUp (aVKey, aTimeStamp); + theListener.ProcessInput(); + return false; + } + } +#else + (void )theListener; + (void )theEventType; + (void )theEvent; +#endif + return false; +} + +// ======================================================================= +// function : ProcessUiEvent +// purpose : +// ======================================================================= +bool Wasm_Window::ProcessUiEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenUiEvent* ) +{ +#if defined(__EMSCRIPTEN__) + if (theEventType != EMSCRIPTEN_EVENT_RESIZE + && theEventType != EMSCRIPTEN_EVENT_CANVASRESIZED) + { + return false; + } +#else + (void )theEventType; +#endif + theListener.ProcessConfigure (true); + return true; +} + +// ======================================================================= +// function : MouseButtonsFromNative +// purpose : +// ======================================================================= +Aspect_VKeyMouse Wasm_Window::MouseButtonsFromNative (unsigned short theButtons) +{ + Aspect_VKeyMouse aButtons = Aspect_VKeyMouse_NONE; + if ((theButtons & 0x1) != 0) + { + aButtons |= Aspect_VKeyMouse_LeftButton; + } + if ((theButtons & 0x2) != 0) + { + aButtons |= Aspect_VKeyMouse_RightButton; + } + if ((theButtons & 0x4) != 0) + { + aButtons |= Aspect_VKeyMouse_MiddleButton; + } + return aButtons; +} + +// ======================================================================= +// function : VirtualKeyFromNative +// purpose : +// ======================================================================= +Aspect_VKey Wasm_Window::VirtualKeyFromNative (Standard_Integer theKey) +{ +#if defined(__EMSCRIPTEN__) + if (theKey >= DOM_VK_0 + && theKey <= DOM_VK_9) + { + // numpad keys + return Aspect_VKey((theKey - DOM_VK_0) + Aspect_VKey_0); + } + if (theKey >= DOM_VK_A + && theKey <= DOM_VK_Z) + { + // main latin alphabet keys + return Aspect_VKey((theKey - DOM_VK_A) + Aspect_VKey_A); + } + if (theKey >= DOM_VK_F1 + && theKey <= DOM_VK_F24) + { + // special keys + if (theKey <= DOM_VK_F12) + { + return Aspect_VKey((theKey - DOM_VK_F1) + Aspect_VKey_F1); + } + return Aspect_VKey_UNKNOWN; + } + if (theKey >= DOM_VK_NUMPAD0 + && theKey <= DOM_VK_NUMPAD9) + { + // numpad keys + return Aspect_VKey((theKey - DOM_VK_NUMPAD0) + Aspect_VKey_Numpad0); + } + + switch (theKey) + { + case DOM_VK_CANCEL: + case DOM_VK_HELP: + return Aspect_VKey_UNKNOWN; + case DOM_VK_BACK_SPACE: + return Aspect_VKey_Backspace; + case DOM_VK_TAB: + return Aspect_VKey_Tab; + case DOM_VK_CLEAR: + return Aspect_VKey_UNKNOWN; + case DOM_VK_RETURN: + case DOM_VK_ENTER: + return Aspect_VKey_Enter; + case DOM_VK_SHIFT: + return Aspect_VKey_Shift; + case DOM_VK_CONTROL: + return Aspect_VKey_Control; + case DOM_VK_ALT: + return Aspect_VKey_Alt; + case DOM_VK_PAUSE: + case DOM_VK_CAPS_LOCK: + case DOM_VK_KANA: + //case DOM_VK_HANGUL: + case DOM_VK_EISU: + case DOM_VK_JUNJA: + case DOM_VK_FINAL: + case DOM_VK_HANJA: + //case DOM_VK_KANJI: + return Aspect_VKey_UNKNOWN; + case DOM_VK_ESCAPE: + return Aspect_VKey_Escape; + case DOM_VK_CONVERT: + case DOM_VK_NONCONVERT: + case DOM_VK_ACCEPT: + case DOM_VK_MODECHANGE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_SPACE: + return Aspect_VKey_Space; + case DOM_VK_PAGE_UP: + return Aspect_VKey_PageUp; + case DOM_VK_PAGE_DOWN: + return Aspect_VKey_PageDown; + case DOM_VK_END: + return Aspect_VKey_End; + case DOM_VK_HOME: + return Aspect_VKey_Home; + case DOM_VK_LEFT: + return Aspect_VKey_Left; + case DOM_VK_UP: + return Aspect_VKey_Up; + case DOM_VK_RIGHT: + return Aspect_VKey_Right; + case DOM_VK_DOWN: + return Aspect_VKey_Down; + case DOM_VK_SELECT: + case DOM_VK_PRINT: + case DOM_VK_EXECUTE: + case DOM_VK_PRINTSCREEN: + case DOM_VK_INSERT: + return Aspect_VKey_UNKNOWN; + case DOM_VK_DELETE: + return Aspect_VKey_Delete; + case DOM_VK_COLON: + return Aspect_VKey_Comma; + case DOM_VK_SEMICOLON: + return Aspect_VKey_Semicolon; + case DOM_VK_LESS_THAN: + return Aspect_VKey_UNKNOWN; + case DOM_VK_EQUALS: + return Aspect_VKey_Equal; + case DOM_VK_GREATER_THAN: + return Aspect_VKey_UNKNOWN; + case DOM_VK_QUESTION_MARK: + return Aspect_VKey_Slash; + case DOM_VK_AT: // @ key + return Aspect_VKey_UNKNOWN; + case DOM_VK_WIN: + return Aspect_VKey_Meta; + case DOM_VK_CONTEXT_MENU: + case DOM_VK_SLEEP: + return Aspect_VKey_UNKNOWN; + case DOM_VK_MULTIPLY: + return Aspect_VKey_NumpadMultiply; + case DOM_VK_ADD: + return Aspect_VKey_NumpadAdd; + case DOM_VK_SEPARATOR: + return Aspect_VKey_UNKNOWN; + case DOM_VK_SUBTRACT: + return Aspect_VKey_NumpadSubtract; + case DOM_VK_DECIMAL: + return Aspect_VKey_UNKNOWN; + case DOM_VK_DIVIDE: + return Aspect_VKey_NumpadDivide; + case DOM_VK_NUM_LOCK: + return Aspect_VKey_Numlock; + case DOM_VK_SCROLL_LOCK: + return Aspect_VKey_Scroll; + case DOM_VK_WIN_OEM_FJ_JISHO: + case DOM_VK_WIN_OEM_FJ_MASSHOU: + case DOM_VK_WIN_OEM_FJ_TOUROKU: + case DOM_VK_WIN_OEM_FJ_LOYA: + case DOM_VK_WIN_OEM_FJ_ROYA: + case DOM_VK_CIRCUMFLEX: + return Aspect_VKey_UNKNOWN; + case DOM_VK_EXCLAMATION: + case DOM_VK_DOUBLE_QUOTE: + //case DOM_VK_HASH: + case DOM_VK_DOLLAR: + case DOM_VK_PERCENT: + case DOM_VK_AMPERSAND: + case DOM_VK_UNDERSCORE: + case DOM_VK_OPEN_PAREN: + case DOM_VK_CLOSE_PAREN: + case DOM_VK_ASTERISK: + return Aspect_VKey_UNKNOWN; + case DOM_VK_PLUS: + return Aspect_VKey_Plus; + case DOM_VK_PIPE: + case DOM_VK_HYPHEN_MINUS: + return Aspect_VKey_UNKNOWN; + case DOM_VK_OPEN_CURLY_BRACKET: + return Aspect_VKey_BracketLeft; + case DOM_VK_CLOSE_CURLY_BRACKET: + return Aspect_VKey_BracketRight; + case DOM_VK_TILDE: + return Aspect_VKey_Tilde; + case DOM_VK_VOLUME_MUTE: + return Aspect_VKey_VolumeMute; + case DOM_VK_VOLUME_DOWN: + return Aspect_VKey_VolumeDown; + case DOM_VK_VOLUME_UP: + return Aspect_VKey_VolumeUp; + case DOM_VK_COMMA: + return Aspect_VKey_Comma; + case DOM_VK_PERIOD: + return Aspect_VKey_Period; + case DOM_VK_SLASH: + return Aspect_VKey_Slash; + case DOM_VK_BACK_QUOTE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_OPEN_BRACKET: + return Aspect_VKey_BracketLeft; + case DOM_VK_BACK_SLASH: + return Aspect_VKey_Backslash; + case DOM_VK_CLOSE_BRACKET: + return Aspect_VKey_BracketRight; + case DOM_VK_QUOTE: + return Aspect_VKey_UNKNOWN; + case DOM_VK_META: + return Aspect_VKey_Meta; + case DOM_VK_ALTGR: + return Aspect_VKey_Alt; + case DOM_VK_WIN_ICO_HELP: + case DOM_VK_WIN_ICO_00: + case DOM_VK_WIN_ICO_CLEAR: + case DOM_VK_WIN_OEM_RESET: + case DOM_VK_WIN_OEM_JUMP: + case DOM_VK_WIN_OEM_PA1: + case DOM_VK_WIN_OEM_PA2: + case DOM_VK_WIN_OEM_PA3: + case DOM_VK_WIN_OEM_WSCTRL: + case DOM_VK_WIN_OEM_CUSEL: + case DOM_VK_WIN_OEM_ATTN: + case DOM_VK_WIN_OEM_FINISH: + case DOM_VK_WIN_OEM_COPY: + case DOM_VK_WIN_OEM_AUTO: + case DOM_VK_WIN_OEM_ENLW: + case DOM_VK_WIN_OEM_BACKTAB: + case DOM_VK_ATTN: + case DOM_VK_CRSEL: + case DOM_VK_EXSEL: + case DOM_VK_EREOF: + return Aspect_VKey_UNKNOWN; + case DOM_VK_PLAY: + return Aspect_VKey_MediaPlayPause; + case DOM_VK_ZOOM: + case DOM_VK_PA1: + case DOM_VK_WIN_OEM_CLEAR: + return Aspect_VKey_UNKNOWN; + } +#else + (void )theKey; +#endif + return Aspect_VKey_UNKNOWN; +} diff --git a/src/Wasm/Wasm_Window.hxx b/src/Wasm/Wasm_Window.hxx new file mode 100644 index 0000000000..5485d25d43 --- /dev/null +++ b/src/Wasm/Wasm_Window.hxx @@ -0,0 +1,185 @@ +// Created by: Kirill Gavrilov +// Copyright (c) 2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Wasm_Window_HeaderFile +#define _Wasm_Window_HeaderFile + +#include + +#include +#include +#include + +class Aspect_WindowInputListener; + +struct EmscriptenMouseEvent; +struct EmscriptenWheelEvent; +struct EmscriptenTouchEvent; +struct EmscriptenKeyboardEvent; +struct EmscriptenUiEvent; + +//! This class defines WebAssembly window (HTML5 canvas) intended for creation of OpenGL (WebGL) context. +//! +//! Note that canvas may define an independent dimensions for backing store (WebGL buffer to render) +//! and for CSS (logical units to present buffer onto screen). +//! These dimensions differ when browser is dragged into a high pixel density screen (HiDPI), +//! or when user scales page in the browser (in both cases window.devicePixelRatio JavaScript property becomes not equal to 1.0). +//! +//! By default, Wasm_Window::DoResize() will scale backing store of a canvas basing on DevicePixelRatio() scale factor +//! to ensure canvas content being rendered with the native resolution and not stretched by browser. +//! This, however, might have side effects: +//! - a slow GPU might experience performance issues on drawing into larger buffer (e.g. HiDPI); +//! - user interface displayed in 3D Viewer (e.g. AIS presentations) should be scaled proportionally to be accessible, +//! which might require extra processing at application level. +//! Consider changing ToScaleBacking flag passed to Wasm_Window constructor in case of issues. +class Wasm_Window : public Aspect_Window +{ + DEFINE_STANDARD_RTTIEXT(Wasm_Window, Aspect_Window) +public: + + //! Convert Emscripten mouse buttons into Aspect_VKeyMouse. + Standard_EXPORT static Aspect_VKeyMouse MouseButtonsFromNative (unsigned short theButtons); + + //! Convert DOM virtual key into Aspect_VKey. + Standard_EXPORT static Aspect_VKey VirtualKeyFromNative (Standard_Integer theKey); + +public: + + //! Wraps existing HTML5 canvas into window. + //! @param[in] theCanvasId target HTML element id defined in a querySelector() syntax + //! @param[in] theToScaleBacking when TRUE, window will automatically scale backing store of canvas + //! basing on DevicePixelRatio() scale factor within DoResize() + Standard_EXPORT Wasm_Window (const TCollection_AsciiString& theCanvasId, + const bool theToScaleBacking = true); + + //! Destroys the window. + Standard_EXPORT virtual ~Wasm_Window(); + + //! Return true if window is not hidden. + virtual Standard_Boolean IsMapped() const Standard_OVERRIDE { return myIsMapped; } + + //! Change window mapped flag to TRUE. + virtual void Map() const Standard_OVERRIDE { myIsMapped = Standard_True; } + + //! Change window mapped flag to FALSE. + virtual void Unmap() const Standard_OVERRIDE { myIsMapped = Standard_False; } + + //! Resize window. + //! In case of ToScaleBacking flag, this method will resize the backing store of canvas + //! basing on DevicePixelRatio() scale factor and CSS canvas size. + Standard_EXPORT virtual Aspect_TypeOfResize DoResize() Standard_OVERRIDE; + + //! Apply the mapping change to the window. + virtual Standard_Boolean DoMapping() const Standard_OVERRIDE { return Standard_True; } + + //! Returns window ratio equal to the physical width/height dimensions. + Standard_EXPORT virtual Standard_Real Ratio() const Standard_OVERRIDE; + + //! Returns The Window POSITION in PIXEL + Standard_EXPORT virtual void Position (Standard_Integer& theX1, + Standard_Integer& theY1, + Standard_Integer& theX2, + Standard_Integer& theY2) const Standard_OVERRIDE; + + //! Return the window size in pixels. + Standard_EXPORT virtual void Size (Standard_Integer& theWidth, + Standard_Integer& theHeight) const Standard_OVERRIDE; + + //! Set new window size in logical (density-independent units). + //! Backing store will be resized basing on DevicePixelRatio(). + Standard_EXPORT void SetSizeLogical (const Graphic3d_Vec2d& theSize); + + //! Set new window size in pixels. + //! Logical size of the element will be resized basing on DevicePixelRatio(). + Standard_EXPORT void SetSizeBacking (const Graphic3d_Vec2i& theSize); + + //! Returns canvas id. + const TCollection_AsciiString& CanvasId() const { return myCanvasId; } + + //! Current EGL implementation in Emscripten accepts only 0 for native window id. + virtual Aspect_Drawable NativeHandle() const Standard_OVERRIDE { return 0; } + + //! Always returns 0 for this class. + virtual Aspect_Drawable NativeParentHandle() const Standard_OVERRIDE { return 0; } + + //! Always returns 0 for this class. + virtual Aspect_FBConfig NativeFBConfig() const Standard_OVERRIDE { return 0; } + + //! Return device pixel ratio (logical to backing store scale factor). + virtual Standard_Real DevicePixelRatio() const Standard_OVERRIDE { return myDevicePixelRatio; } + + //! Invalidate entire window content through generation of Expose event. + Standard_EXPORT virtual void InvalidateContent (const Handle(Aspect_DisplayConnection)& theDisp) Standard_OVERRIDE; + +public: + + //! Process a single window message. + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessMessage (Aspect_WindowInputListener& theListener, + int theEventType, const void* theEvent); + + //! Process a mouse input message. + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessMouseEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenMouseEvent* theEvent); + + //! Process a (mouse) wheel input message. + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessWheelEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenWheelEvent* theEvent); + + //! Process a mouse input message. + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessTouchEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenTouchEvent* theEvent); + + //! Process a keyboard input message. + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessKeyEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenKeyboardEvent* theEvent); + + //! Process a UI input message (like window resize). + //! @param[in,out] theListener listener to redirect message + //! @param[in] theEventType message type to process + //! @param[in] theEvent message to process + //! @return TRUE if message has been processed + Standard_EXPORT virtual bool ProcessUiEvent (Aspect_WindowInputListener& theListener, + int theEventType, const EmscriptenUiEvent* theEvent); + +protected: + + TCollection_AsciiString myCanvasId; + Graphic3d_Vec2i mySize; + Standard_Real myDevicePixelRatio; + Standard_Boolean myToScaleBacking; + mutable Standard_Boolean myIsMapped; + +}; + +#endif // _Wasm_Window_HeaderFile diff --git a/src/Xw/Xw_Window.cxx b/src/Xw/Xw_Window.cxx index b3e538d0c8..8ffa13480c 100644 --- a/src/Xw/Xw_Window.cxx +++ b/src/Xw/Xw_Window.cxx @@ -15,7 +15,6 @@ #include -#include #include #include #include @@ -44,7 +43,6 @@ Xw_Window::Xw_Window (const Handle(Aspect_DisplayConnection)& theXDisplay, const Standard_Integer thePxWidth, const Standard_Integer thePxHeight) : Aspect_Window(), - myDisplay (theXDisplay), myXWindow (0), myFBConfig (NULL), myXLeft (thePxLeft), @@ -53,6 +51,7 @@ Xw_Window::Xw_Window (const Handle(Aspect_DisplayConnection)& theXDisplay, myYBottom (thePxTop + thePxHeight), myIsOwnWin (Standard_True) { + myDisplay = theXDisplay; if (thePxWidth <= 0 || thePxHeight <= 0) { throw Aspect_WindowDefinitionError("Xw_Window, Coordinate(s) out of range"); @@ -129,7 +128,6 @@ Xw_Window::Xw_Window (const Handle(Aspect_DisplayConnection)& theXDisplay, const Aspect_Drawable theXWin, const Aspect_FBConfig theFBConfig) : Aspect_Window(), - myDisplay (theXDisplay), myXWindow (theXWin), myFBConfig (theFBConfig), myXLeft (0), @@ -138,6 +136,7 @@ Xw_Window::Xw_Window (const Handle(Aspect_DisplayConnection)& theXDisplay, myYBottom (512), myIsOwnWin (Standard_False) { + myDisplay = theXDisplay; if (theXWin == 0) { throw Aspect_WindowDefinitionError("Xw_Window, given invalid X window"); diff --git a/src/Xw/Xw_Window.hxx b/src/Xw/Xw_Window.hxx index 13cd5595d2..b7d4c4f69f 100644 --- a/src/Xw/Xw_Window.hxx +++ b/src/Xw/Xw_Window.hxx @@ -93,9 +93,6 @@ public: //! @return native Window handle Aspect_Drawable XWindow() const { return myXWindow; } - //! @return connection to X Display - Standard_EXPORT const Handle(Aspect_DisplayConnection)& DisplayConnection() const; - //! @return native Window handle virtual Aspect_Drawable NativeHandle() const Standard_OVERRIDE { @@ -133,7 +130,6 @@ public: protected: - Handle(Aspect_DisplayConnection) myDisplay; //!< X Display connection Aspect_Drawable myXWindow; //!< XLib window handle Aspect_FBConfig myFBConfig; //!< GLXFBConfig Standard_Integer myXLeft; //!< left position in pixels -- 2.20.1