diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index bd54a24e97..5e0d61f24b 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -289,7 +289,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { return RenderState(startID, pathID, endID); } -PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { +PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const { QUuid pickedID; glm::vec3 intersection, surfaceNormal, direction, origin; auto rayPickResult = std::static_pointer_cast(pickResult); diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 4874cadf0a..59be58d4ac 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -70,7 +70,7 @@ public: static RenderState buildRenderState(const QVariantMap& propMap); protected: - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override; PickedObject getHoveredObject(const PickResultPointer& pickResult) override; Pointer::Buttons getPressedButtons() override; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 80e783cfe4..1a172b8894 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -17,8 +17,6 @@ #include "PickScriptingInterface.h" #include -using namespace controller; - // TODO: make these configurable per pointer static const float WEB_STYLUS_LENGTH = 0.2f; @@ -30,6 +28,10 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f; static const float HOVER_HYSTERESIS = 0.01f; static const float TOUCH_HYSTERESIS = 0.02f; +static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND; +static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; +static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT; + StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) : Pointer(DependencyManager::get()->createStylusPick(props), enabled, hover), _stylusOverlay(stylusOverlay) @@ -115,16 +117,10 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { float distance = stylusPickResult->distance; // If we're triggering on an object, recalculate the distance instead of using the pickResult + glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]); + glm::vec3 direction = -_state.surfaceNormal; if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) { - glm::vec3 intersection; - glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]); - glm::vec3 direction = -_state.surfaceNormal; - if (_state.triggeredObject.type == ENTITY) { - intersection = RayPick::intersectRayWithEntityXYPlane(_state.triggeredObject.objectID, origin, direction); - } else if (_state.triggeredObject.type == OVERLAY) { - intersection = RayPick::intersectRayWithOverlayXYPlane(_state.triggeredObject.objectID, origin, direction); - } - distance = glm::dot(intersection - origin, direction); + distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction); } float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f; @@ -132,6 +128,9 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) { if (_state.triggeredObject.objectID.isNull()) { _state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type); + _state.intersection = findIntersection(_state.triggeredObject, origin, direction); + _state.triggerPos2D = findPos2D(_state.triggeredObject, origin); + _state.triggerStartTime = usecTimestampNow(); _state.surfaceNormal = stylusPickResult->surfaceNormal; _state.triggering = true; } @@ -140,6 +139,9 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { } _state.triggeredObject = PickedObject(); + _state.intersection = glm::vec3(NAN); + _state.triggerPos2D = glm::vec2(NAN); + _state.triggerStartTime = 0; _state.surfaceNormal = glm::vec3(NAN); _state.triggering = false; return false; @@ -159,34 +161,30 @@ Pointer::Buttons StylusPointer::getPressedButtons() { return toReturn; } -PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { +PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const { QUuid pickedID; + glm::vec2 pos2D; glm::vec3 intersection, surfaceNormal, direction, origin; auto stylusPickResult = std::static_pointer_cast(pickResult); if (stylusPickResult) { intersection = stylusPickResult->intersection; - surfaceNormal = _state.surfaceNormal; + surfaceNormal = hover ? stylusPickResult->surfaceNormal : _state.surfaceNormal; const QVariantMap& stylusTip = stylusPickResult->pickVariant; origin = vec3FromVariant(stylusTip["position"]); - direction = -_state.surfaceNormal; + direction = -surfaceNormal; + pos2D = findPos2D(target, origin); pickedID = stylusPickResult->objectID; } - if (pickedID != target.objectID) { - if (target.type == ENTITY) { - intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction); - } else if (target.type == OVERLAY) { - intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction); - } - } - glm::vec2 pos2D; - if (target.type == ENTITY) { - pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, origin); - } else if (target.type == OVERLAY) { - pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, origin); - } else if (target.type == HUD) { - pos2D = DependencyManager::get()->calculatePos2DFromHUD(origin); + // If we just started triggering and we haven't moved too much, don't update intersection and pos2D + if (!_state.triggeredObject.objectID.isNull() && usecTimestampNow() - _state.triggerStartTime < STYLUS_MOVE_DELAY && + glm::distance2(pos2D, _state.triggerPos2D) < TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED) { + pos2D = _state.triggerPos2D; + intersection = _state.intersection; + } else if (pickedID != target.objectID) { + intersection = findIntersection(target, origin, direction); } + return PointerEvent(pos2D, intersection, surfaceNormal, direction); } @@ -203,4 +201,28 @@ void StylusPointer::setRenderState(const std::string& state) { } else if (state == "disabled") { _renderState = DISABLED; } +} + +glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); + case OVERLAY: + return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); + default: + return glm::vec3(NAN); + } +} + +glm::vec2 StylusPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); + case OVERLAY: + return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); + case HUD: + return DependencyManager::get()->calculatePos2DFromHUD(origin); + default: + return glm::vec2(NAN); + } } \ No newline at end of file diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index cc7f2814db..fb2d492798 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -41,7 +41,7 @@ protected: bool shouldHover(const PickResultPointer& pickResult) override; bool shouldTrigger(const PickResultPointer& pickResult) override; - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override; private: void show(const StylusTip& tip); @@ -49,9 +49,13 @@ private: struct TriggerState { PickedObject triggeredObject; + glm::vec3 intersection { NAN }; + glm::vec2 triggerPos2D { NAN }; glm::vec3 surfaceNormal { NAN }; - bool hovering { false }; + quint64 triggerStartTime { 0 }; bool triggering { false }; + + bool hovering { false }; }; TriggerState _state; @@ -67,6 +71,9 @@ private: const OverlayID _stylusOverlay; static bool isWithinBounds(float distance, float min, float max, float hysteresis); + static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); + static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); + }; #endif // hifi_StylusPointer_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 05ff3e289b..60c1905db6 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -45,7 +45,7 @@ extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest Overlays::Overlays() { auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterOverlay); + connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverPointerEvent); connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeavePointerEvent); connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent); @@ -741,7 +741,7 @@ void Overlays::sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerE } void Overlays::sendHoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event) { - emit hoverEnterOverlay(overlayID, event); + hoverEnterPointerEvent(overlayID, event); } void Overlays::sendHoverOverOverlay(const OverlayID& overlayID, const PointerEvent& event) { @@ -938,6 +938,21 @@ bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { return false; } +void Overlays::hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { + // TODO: generalize this to allow any overlay to recieve events + std::shared_ptr thisOverlay; + if (getOverlayType(overlayID) == "web3d") { + thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); + } + if (thisOverlay) { + // Send to web overlay + QMetaObject::invokeMethod(thisOverlay.get(), "hoverEnterOverlay", Q_ARG(PointerEvent, event)); + } + + // emit to scripts + emit hoverEnterOverlay(overlayID, event); +} + void Overlays::hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { // TODO: generalize this to allow any overlay to recieve events std::shared_ptr thisOverlay; @@ -1014,7 +1029,7 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) { // If hovering over a new overlay then enter hover on that overlay. if (rayPickResult.overlayID != _currentHoverOverOverlayID) { - emit hoverEnterOverlay(rayPickResult.overlayID, pointerEvent); + hoverEnterPointerEvent(rayPickResult.overlayID, pointerEvent); } // Hover over current overlay. diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a5ad013246..a8f504bbc5 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -308,6 +308,7 @@ public slots: void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 658b4edab8..3d79f79a9b 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -337,6 +337,14 @@ void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) { _webSurface->setProxyWindow(proxyWindow); } +void Web3DOverlay::hoverEnterOverlay(const PointerEvent& event) { + if (_inputMode == Mouse) { + handlePointerEvent(event); + } else if (_webSurface) { + _webSurface->hoverBeginEvent(event, _touchDevice); + } +} + void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { if (_inputMode == Mouse) { PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), @@ -351,12 +359,6 @@ void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { } void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { - if (event.getType() == PointerEvent::Press) { - _pressed = true; - } else if (event.getType() == PointerEvent::Release) { - _pressed = false; - } - if (_inputMode == Touch) { handlePointerEventAsTouch(event); } else { diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index d2da1f7310..563c1c0c4e 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -39,6 +39,7 @@ public: QObject* getEventHandler(); void setProxyWindow(QWindow* proxyWindow); + Q_INVOKABLE void hoverEnterOverlay(const PointerEvent& event); Q_INVOKABLE void hoverLeaveOverlay(const PointerEvent& event); Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); void handlePointerEventAsTouch(const PointerEvent& event); @@ -97,7 +98,6 @@ private: int _geometryId { 0 }; bool _showKeyboardFocusHighlight{ true }; - bool _pressed{ false }; QTouchDevice _touchDevice; uint8_t _desiredMaxFPS { 10 }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 8493d73db3..fe576b2599 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -83,6 +83,16 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const EntityItemID& entityID, const PointerEvent& event) { + std::shared_ptr thisEntity; + auto entity = getEntity(entityID); + if (entity && entity->getType() == EntityTypes::Web) { + thisEntity = std::static_pointer_cast(renderableForEntityId(entityID)); + } + if (thisEntity) { + QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(PointerEvent, event)); + } + }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const EntityItemID& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 469e1c4b6f..95ab51628b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -314,6 +314,12 @@ void WebEntityRenderer::loadSourceURL() { } } +void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { + if (!_lastLocked && _webSurface) { + _webSurface->hoverBeginEvent(event, _touchDevice); + } +} + void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { if (!_lastLocked && _webSurface) { _webSurface->hoverEndEvent(event, _touchDevice); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 28c024cdbb..309e750f53 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -25,6 +25,7 @@ class WebEntityRenderer : public TypedEntityRenderer { public: WebEntityRenderer(const EntityItemPointer& entity); + Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event); Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event); Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index e17fb8ec65..4138ae5e4c 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -76,11 +76,8 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin Buttons buttons; Buttons newButtons; Buttons sameButtons; - const std::string PRIMARY_BUTTON = "Primary"; - bool primaryPressed = false; if (_enabled && shouldTrigger(pickResult)) { buttons = getPressedButtons(); - primaryPressed = buttons.find(PRIMARY_BUTTON) != buttons.end(); for (const std::string& button : buttons) { if (_prevButtons.find(button) == _prevButtons.end()) { newButtons.insert(button); @@ -97,8 +94,8 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); hoveredEvent.setID(pointerID); - bool releaseOnHoverLeave = !primaryPressed || (!_enabled && _prevEnabled) || (!doHover && _prevDoHover); - hoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); + bool moveOnHoverLeave = (!_enabled && _prevEnabled) || (!doHover && _prevDoHover); + hoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); // if shouldHover && !_prevDoHover, only send hoverBegin if (_enabled && _hover && doHover && !_prevDoHover) { @@ -117,7 +114,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else { PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); prevHoveredEvent.setID(pointerID); - prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); + prevHoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); } @@ -139,7 +136,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else { PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); prevHoveredEvent.setID(pointerID); - prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); + prevHoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } @@ -199,7 +196,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin // Trigger continue for (const std::string& button : sameButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Move); triggeredEvent.setButton(chooseButton(button)); @@ -214,7 +211,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin // Trigger end for (const std::string& button : _prevButtons) { - PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); + PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Release); triggeredEvent.setButton(chooseButton(button)); diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index 0415f00d1e..6acbcf9ca0 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -82,7 +82,7 @@ protected: bool _enabled; bool _hover; - virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const = 0; + virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const = 0; virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0; virtual Buttons getPressedButtons() = 0; diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index d0cba0a1a2..23f435a67c 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -59,14 +59,14 @@ public: uint32_t getButtons() const { return _buttons; } Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; } bool shouldFocus() const { return _shouldFocus; } - bool sendReleaseOnHoverLeave() const { return _releaseOnHoverLeave; } + bool sendMoveOnHoverLeave() const { return _moveOnHoverLeave; } void setID(uint32_t id) { _id = id; } void setType(EventType type) { _type = type; } void setButton(Button button); - void setShouldFocus(bool focus) { _shouldFocus = focus; } - void setReleaseOnHoverLeave(bool releaseOnHoverLeave) { _releaseOnHoverLeave = releaseOnHoverLeave; } void setPos2D(const glm::vec2& pos2D) { _pos2D = pos2D; } + void setShouldFocus(bool focus) { _shouldFocus = focus; } + void setMoveOnHoverLeave(bool moveOnHoverLeave) { _moveOnHoverLeave = moveOnHoverLeave; } static const unsigned int INVALID_POINTER_ID { 0 }; @@ -83,7 +83,7 @@ private: Qt::KeyboardModifiers _keyboardModifiers { Qt::KeyboardModifier::NoModifier }; // set of keys held when event was generated bool _shouldFocus { true }; - bool _releaseOnHoverLeave { true }; + bool _moveOnHoverLeave { true }; }; QDebug& operator<<(QDebug& dbg, const PointerEvent& p); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index c41ef9d4a4..ec54de697f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -96,7 +96,7 @@ OffscreenUi::OffscreenUi() { }); auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenUi::handlePointerEvent); + connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenUi::hoverBeginEvent); connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &OffscreenUi::handlePointerEvent); connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &OffscreenUi::hoverEndEvent); connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &OffscreenUi::handlePointerEvent); @@ -104,6 +104,10 @@ OffscreenUi::OffscreenUi() { connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &OffscreenUi::handlePointerEvent); } +void OffscreenUi::hoverBeginEvent(const PointerEvent& event) { + OffscreenQmlSurface::hoverBeginEvent(event, _touchDevice); +} + void OffscreenUi::hoverEndEvent(const PointerEvent& event) { OffscreenQmlSurface::hoverEndEvent(event, _touchDevice); } diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 0261591295..76e4261cd3 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -250,6 +250,7 @@ public slots: void removeModalDialog(QObject* modal); private slots: + void hoverBeginEvent(const PointerEvent& event); void hoverEndEvent(const PointerEvent& event); void handlePointerEvent(const PointerEvent& event); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 41c3da2a4f..4a187e4b61 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -931,7 +931,7 @@ unsigned int OffscreenQmlSurface::deviceIdByTouchPoint(qreal x, qreal y) { auto mapped = _rootItem->mapFromGlobal(QPoint(x, y)); for (auto pair : _activeTouchPoints) { - if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) { + if (mapped.x() == (int)pair.second.touchPoint.pos().x() && mapped.y() == (int)pair.second.touchPoint.pos().y()) { return pair.first; } } @@ -954,45 +954,51 @@ PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type } } +void OffscreenQmlSurface::hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device) { + handlePointerEvent(event, device); + _activeTouchPoints[event.getID()].hovering = true; +} + void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchDevice& device) { - if (!_paused && _quickWindow && _pressed && event.sendReleaseOnHoverLeave()) { - PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), - event.getButton(), event.getButtons(), event.getKeyboardModifiers()); - handlePointerEvent(endEvent, device); + _activeTouchPoints[event.getID()].hovering = false; + // Send a fake mouse move event if + // - the event told us to + // - we aren't pressing with this ID + if (event.sendMoveOnHoverLeave() || !_activeTouchPoints[event.getID()].pressed) { // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); - handlePointerEvent(endMoveEvent, device); + // If we aren't pressing, we want to release this TouchPoint + handlePointerEvent(endMoveEvent, device, !_activeTouchPoints[event.getID()].pressed); } } -bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device) { +bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release) { // Ignore mouse interaction if we're paused if (_paused || !_quickWindow) { return false; } - if (event.getType() == PointerEvent::Press) { - _pressed = true; - } else if (event.getType() == PointerEvent::Release) { - _pressed = false; - } - QPointF windowPoint(event.getPos2D().x, event.getPos2D().y); Qt::TouchPointState state = Qt::TouchPointStationary; if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { state = Qt::TouchPointPressed; - } else if (event.getType() == PointerEvent::Release) { + } else if (event.getType() == PointerEvent::Release && event.getButton() == PointerEvent::PrimaryButton) { state = Qt::TouchPointReleased; - } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { + } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].touchPoint.pos()) { state = Qt::TouchPointMoved; } + // Remove the touch point if: + // - this was a hover end event and the mouse wasn't pressed + // - this was a release event and we aren't still hovering + auto touchPoint = _activeTouchPoints.find(event.getID()); + bool removeTouchPoint = release || (touchPoint != _activeTouchPoints.end() && !touchPoint->second.hovering && state == Qt::TouchPointReleased); QEvent::Type touchType = QEvent::TouchUpdate; if (_activeTouchPoints.empty()) { // If the first active touch point is being created, send a begin touchType = QEvent::TouchBegin; - } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { + } else if (removeTouchPoint && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { // If the last active touch point is being released, send an end touchType = QEvent::TouchEnd; } @@ -1003,7 +1009,12 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT point.setState(state); point.setPos(windowPoint); point.setScreenPos(windowPoint); - _activeTouchPoints[event.getID()] = point; + _activeTouchPoints[event.getID()].touchPoint = point; + if (state == Qt::TouchPointPressed) { + _activeTouchPoints[event.getID()].pressed = true; + } else if (state == Qt::TouchPointReleased) { + _activeTouchPoints[event.getID()].pressed = false; + } } QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers()); @@ -1011,8 +1022,8 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT QList touchPoints; Qt::TouchPointStates touchPointStates; for (const auto& entry : _activeTouchPoints) { - touchPointStates |= entry.second.state(); - touchPoints.push_back(entry.second); + touchPointStates |= entry.second.touchPoint.state(); + touchPoints.push_back(entry.second.touchPoint); } touchEvent.setWindow(_quickWindow); @@ -1037,7 +1048,6 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT bool eventsAccepted = false; -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install @@ -1046,7 +1056,6 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT QCoreApplication::sendEvent(_quickWindow, &mouseEvent); eventsAccepted &= mouseEvent.isAccepted(); } -#endif if (touchType == QEvent::TouchBegin) { _touchBeginAccepted = QCoreApplication::sendEvent(_quickWindow, &touchEvent); @@ -1055,22 +1064,10 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } eventsAccepted &= touchEvent.isAccepted(); - // If this was a release event, remove the point from the active touch points - if (state == Qt::TouchPointReleased) { + if (removeTouchPoint) { _activeTouchPoints.erase(event.getID()); } -#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) - if (event.getType() == PointerEvent::Move) { - // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install - // need to investigate into why this crash is happening. - //_qmlContext->setContextProperty("lastMousePosition", windowPoint); - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); - eventsAccepted &= mouseEvent.isAccepted(); - } -#endif - return eventsAccepted; } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 8ddfbfe8cc..83fc528890 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -144,8 +144,9 @@ private slots: void onFocusObjectChanged(QObject* newFocus); public slots: + void hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device); void hoverEndEvent(const PointerEvent& event, class QTouchDevice& device); - bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device); + bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release = false); private: QQuickWindow* _quickWindow { nullptr }; @@ -173,9 +174,15 @@ private: QQuickItem* _currentFocusItem { nullptr }; - bool _pressed { false }; + struct TouchState { + QTouchEvent::TouchPoint touchPoint; + bool hovering { false }; + bool pressed { false }; + }; + + bool _pressed; bool _touchBeginAccepted { false }; - std::map _activeTouchPoints; + std::map _activeTouchPoints; }; #endif