diff --git a/examples/away.js b/examples/away.js index 18a3fddfce..8d561f2623 100644 --- a/examples/away.js +++ b/examples/away.js @@ -13,13 +13,30 @@ // // Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key. // See MAIN CONTROL, below, for what "paused" actually does. -var OVERLAY_RATIO = 1920 / 1080; +var OVERLAY_WIDTH = 1920; +var OVERLAY_HEIGHT = 1080; +var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT; var OVERLAY_DATA = { + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", color: {red: 255, green: 255, blue: 255}, alpha: 1 }; +var lastOverlayPosition = { x: 0, y: 0, z: 0}; +var OVERLAY_DATA_HMD = { + position: lastOverlayPosition, + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, + url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1, + scale: 2, + isFacingAvatar: true, + drawInFront: true +}; + // ANIMATION // We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect // using an animation graph with a state that we turn on and off through the animation var defined with that state. @@ -64,29 +81,74 @@ function stopAwayAnimation() { // OVERLAY var overlay = Overlays.addOverlay("image", OVERLAY_DATA); +var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD); + +function moveCloserToCamera(positionAtHUD) { + // we don't actually want to render at the slerped look at... instead, we want to render + // slightly closer to the camera than that. + var MOVE_CLOSER_TO_CAMERA_BY = -0.25; + var cameraFront = Quat.getFront(Camera.orientation); + var closerToCamera = Vec3.multiply(cameraFront, MOVE_CLOSER_TO_CAMERA_BY); // slightly closer to camera + var slightlyCloserPosition = Vec3.sum(positionAtHUD, closerToCamera); + + return slightlyCloserPosition; +} + function showOverlay() { - var properties = {visible: true}, - // Update for current screen size, keeping overlay proportions constant. - screen = Controller.getViewportDimensions(), - screenRatio = screen.x / screen.y; - if (screenRatio < OVERLAY_RATIO) { - properties.width = screen.x; - properties.height = screen.x / OVERLAY_RATIO; - properties.x = 0; - properties.y = (screen.y - properties.height) / 2; + var properties = {visible: true}; + + if (HMD.active) { + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + + lastOverlayPosition = HMD.getHUDLookAtPosition3D(); + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); } else { - properties.height = screen.y; - properties.width = screen.y * OVERLAY_RATIO; - properties.y = 0; - properties.x = (screen.x - properties.width) / 2; + // make sure HMD is hidden + Overlays.editOverlay(overlayHMD, { visible: false }); + + // Update for current screen size, keeping overlay proportions constant. + var screen = Controller.getViewportDimensions(); + + // keep the overlay it's natural size and always center it... + Overlays.editOverlay(overlay, { visible: true, + x: ((screen.x - OVERLAY_WIDTH) / 2), + y: ((screen.y - OVERLAY_HEIGHT) / 2) }); } - Overlays.editOverlay(overlay, properties); } function hideOverlay() { Overlays.editOverlay(overlay, {visible: false}); + Overlays.editOverlay(overlayHMD, {visible: false}); } hideOverlay(); +function maybeMoveOverlay() { + if (isAway) { + // if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the + // desktop overlay + if (!HMD.active) { + showOverlay(); // this will also recenter appropriately + } + + if (HMD.active) { + // Note: instead of moving it directly to the lookAt, we will move it slightly toward the + // new look at. This will result in a more subtle slerp toward the look at and reduce jerkiness + var EASE_BY_RATIO = 0.1; + var lookAt = HMD.getHUDLookAtPosition3D(); + var lookAtChange = Vec3.subtract(lookAt, lastOverlayPosition); + var halfWayBetweenOldAndLookAt = Vec3.multiply(lookAtChange, EASE_BY_RATIO); + var newOverlayPosition = Vec3.sum(lastOverlayPosition, halfWayBetweenOldAndLookAt); + lastOverlayPosition = newOverlayPosition; + + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); + + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + } + } +} // MAIN CONTROL var wasMuted, isAway; @@ -106,6 +168,12 @@ function goAway() { MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view playAwayAnimation(); // animation is still seen by others showOverlay(); + + // tell the Reticle, we want to stop capturing the mouse until we come back + Reticle.allowMouseCapture = false; + if (HMD.active) { + Reticle.visible = false; + } } function goActive() { if (!isAway) { @@ -119,13 +187,20 @@ function goActive() { MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting. stopAwayAnimation(); hideOverlay(); + + // tell the Reticle, we are ready to capture the mouse again and it should be visible + Reticle.allowMouseCapture = true; + Reticle.visible = true; + if (HMD.active) { + Reticle.position = HMD.getHUDLookAtPosition2D(); + } } function maybeGoActive(event) { if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) return; } - if (!isAway && (event.text === '.')) { + if (!isAway && (event.text == 'ESC')) { goAway(); } else { goActive(); @@ -141,10 +216,8 @@ function maybeGoAway() { } } - // If the mouse has gone from captured, to non-captured state, - // then it likely means the person is still in the HMD, but has - // tabbed away from the application (meaning they don't have mouse - // control) and they likely want to go into an away state + // If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but + // tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state if (Reticle.mouseCaptured !== wasMouseCaptured) { wasMouseCaptured = !wasMouseCaptured; if (!wasMouseCaptured) { @@ -153,6 +226,8 @@ function maybeGoAway() { } } +Script.update.connect(maybeMoveOverlay); + Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); diff --git a/examples/depthReticle.js b/examples/depthReticle.js index 14a5ba5ff3..14215bff3d 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -35,8 +35,8 @@ var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50; Controller.mouseMoveEvent.connect(function(mouseEvent) { var now = Date.now(); - // if the reticle is hidden, show it... - if (!Reticle.visible) { + // if the reticle is hidden, and we're not in away mode... + if (!Reticle.visible && Reticle.allowMouseCapture) { Reticle.visible = true; if (HMD.active) { shouldSeekToLookAt = true; @@ -44,7 +44,7 @@ Controller.mouseMoveEvent.connect(function(mouseEvent) { } else { // even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it) // then they are probably looking for it, and we should move into seekToLookAt mode - if (HMD.active && !shouldSeekToLookAt) { + if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) { var dx = Reticle.position.x - lastMouseX; var dy = Reticle.position.y - lastMouseY; var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 21377fa945..15b1836173 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -661,15 +661,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + #ifdef Q_OS_MAC - // OSX doesn't seem to provide for hiding the cursor only on the GL widget - _window->setCursor(Qt::BlankCursor); + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget #else - // On windows and linux, hiding the top level cursor also means it's invisible - // when hovering over the window menu, which is a pain, so only hide it for - // the GL surface - _glWidget->setCursor(Qt::BlankCursor); + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; #endif + cursorTarget->setCursor(Qt::BlankCursor); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); @@ -981,6 +981,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _idleTimer->start(0); } + +void Application::checkChangeCursor() { + QMutexLocker locker(&_changeCursorLock); + if (_cursorNeedsChanging) { +#ifdef Q_OS_MAC + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget +#else + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; +#endif + cursorTarget->setCursor(_desiredCursor); + + _cursorNeedsChanging = false; + } +} + +void Application::showCursor(const QCursor& cursor) { + QMutexLocker locker(&_changeCursorLock); + _desiredCursor = cursor; + _cursorNeedsChanging = true; +} + void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -2431,6 +2454,9 @@ void Application::idle(uint64_t now) { return; // bail early, nothing to do here. } + + checkChangeCursor(); + Stats::getInstance()->updateStats(); AvatarInputs::getInstance()->update(); diff --git a/interface/src/Application.h b/interface/src/Application.h index d205ce8041..b5a0894c3a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -120,6 +120,8 @@ public: QSize getDeviceSize() const; bool hasFocus() const; + void showCursor(const QCursor& cursor); + bool isThrottleRendering() const; Camera* getCamera() { return &_myCamera; } @@ -515,6 +517,11 @@ private: QTimer* _idleTimer { nullptr }; bool _fakedMouseEvent { false }; + + void checkChangeCursor(); + mutable QMutex _changeCursorLock { QMutex::Recursive }; + QCursor _desiredCursor{ Qt::BlankCursor }; + bool _cursorNeedsChanging { false }; }; #endif // hifi_Application_h diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index e8e6a0a956..efd69c7859 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -337,9 +337,21 @@ QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) { bool ApplicationCompositor::shouldCaptureMouse() const { // if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu - return qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); + return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); } +void ApplicationCompositor::setAllowMouseCapture(bool capture) { + if (qApp->isHMDMode()) { + if (capture) { + qApp->showCursor(Qt::BlankCursor); + } else { + qApp->showCursor(Qt::ArrowCursor); + } + } + _allowMouseCapture = capture; +} + + void ApplicationCompositor::handleLeaveEvent() { if (shouldCaptureMouse()) { diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 32835ace5a..324250deb1 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -106,6 +106,9 @@ public: bool shouldCaptureMouse() const; + bool getAllowMouseCapture() const { return _allowMouseCapture; } + void setAllowMouseCapture(bool capture); + /// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false bool getReticleOverDesktop() const; void setReticleOverDesktop(bool value) { _isOverDesktop = value; } @@ -162,6 +165,8 @@ private: bool _reticleOverQml { false }; + bool _allowMouseCapture { true }; + ReticleInterface* _reticleInterface; }; @@ -173,12 +178,17 @@ class ReticleInterface : public QObject { Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) + Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay) public: ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {} Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); } + + Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); } + Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); } + Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); }