From 2c3fdc995f70c9b0e4245d440dd89d629aaacbf1 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 29 Feb 2016 20:23:06 -0800 Subject: [PATCH 1/5] Away improvements --- examples/away.js | 21 +++++++++--- interface/src/Application.cpp | 38 ++++++++++++++++++---- interface/src/Application.h | 7 ++++ interface/src/ui/ApplicationCompositor.cpp | 14 +++++++- interface/src/ui/ApplicationCompositor.h | 10 ++++++ 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/examples/away.js b/examples/away.js index 18a3fddfce..f22f9662a7 100644 --- a/examples/away.js +++ b/examples/away.js @@ -106,6 +106,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 +125,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 +154,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) { 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 59d794d7cb..ff37d07e15 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -335,9 +335,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(); } From 95c92f7a2793b47dc928120430cd6f8bec88ed24 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 29 Feb 2016 20:35:25 -0800 Subject: [PATCH 2/5] tweak depthReticle to work better with away --- examples/depthReticle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 501d23021ffef607d83e75c7c15916dfd2c90f21 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 29 Feb 2016 20:53:18 -0800 Subject: [PATCH 3/5] slight improvement to Away to have the paused overlay follow your view in HMD --- examples/away.js | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/examples/away.js b/examples/away.js index f22f9662a7..9b84f46742 100644 --- a/examples/away.js +++ b/examples/away.js @@ -13,7 +13,9 @@ // // 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 = { imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", color: {red: 255, green: 255, blue: 255}, @@ -69,16 +71,25 @@ function showOverlay() { // 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; + + if (HMD.active) { + var lookAt = HMD.getHUDLookAtPosition2D(); + properties.width = OVERLAY_WIDTH; + properties.height = OVERLAY_HEIGHT; + properties.x = lookAt.x - OVERLAY_WIDTH / 2; + properties.y = lookAt.y - OVERLAY_HEIGHT / 2; } else { - properties.height = screen.y; - properties.width = screen.y * OVERLAY_RATIO; - properties.y = 0; - properties.x = (screen.x - properties.width) / 2; + if (screenRatio < OVERLAY_RATIO) { + properties.width = screen.x; + properties.height = screen.x / OVERLAY_RATIO; + properties.x = 0; + properties.y = (screen.y - properties.height) / 2; + } else { + properties.height = screen.y; + properties.width = screen.y * OVERLAY_RATIO; + properties.y = 0; + properties.x = (screen.x - properties.width) / 2; + } } Overlays.editOverlay(overlay, properties); } @@ -87,6 +98,17 @@ function hideOverlay() { } hideOverlay(); +function maybeMoveOverlay() { + if (HMD.active && isAway) { + var lookAt = HMD.getHUDLookAtPosition2D(); + var properties = {visible: true}; + properties.width = OVERLAY_WIDTH; + properties.height = OVERLAY_HEIGHT; + properties.x = lookAt.x - OVERLAY_WIDTH / 2; + properties.y = lookAt.y - OVERLAY_HEIGHT / 2; + Overlays.editOverlay(overlay, properties); + } +} // MAIN CONTROL var wasMuted, isAway; @@ -164,6 +186,8 @@ function maybeGoAway() { } } +Script.update.connect(maybeMoveOverlay); + Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); From 52c38e56a493b0a1b02457808e19ca35ad7e4e8c Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 29 Feb 2016 21:25:25 -0800 Subject: [PATCH 4/5] make the HMD away overlay 3d --- examples/away.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/examples/away.js b/examples/away.js index 9b84f46742..69d2f3fd12 100644 --- a/examples/away.js +++ b/examples/away.js @@ -22,6 +22,19 @@ var OVERLAY_DATA = { alpha: 1 }; +var OVERLAY_DATA_HMD = { + position: { x: 0, y: 0, z: 0}, + 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. @@ -66,19 +79,17 @@ function stopAwayAnimation() { // OVERLAY var overlay = Overlays.addOverlay("image", OVERLAY_DATA); +var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD); function showOverlay() { - var properties = {visible: true}, - // Update for current screen size, keeping overlay proportions constant. - screen = Controller.getViewportDimensions(), - screenRatio = screen.x / screen.y; + var properties = {visible: true}; if (HMD.active) { - var lookAt = HMD.getHUDLookAtPosition2D(); - properties.width = OVERLAY_WIDTH; - properties.height = OVERLAY_HEIGHT; - properties.x = lookAt.x - OVERLAY_WIDTH / 2; - properties.y = lookAt.y - OVERLAY_HEIGHT / 2; + Overlays.editOverlay(overlayHMD, { visible: true, position : HMD.getHUDLookAtPosition3D() }); } else { + // Update for current screen size, keeping overlay proportions constant. + var screen = Controller.getViewportDimensions(), + screenRatio = screen.x / screen.y; + if (screenRatio < OVERLAY_RATIO) { properties.width = screen.x; properties.height = screen.x / OVERLAY_RATIO; @@ -90,23 +101,18 @@ function showOverlay() { properties.y = 0; properties.x = (screen.x - properties.width) / 2; } + Overlays.editOverlay(overlay, properties); } - Overlays.editOverlay(overlay, properties); } function hideOverlay() { Overlays.editOverlay(overlay, {visible: false}); + Overlays.editOverlay(overlayHMD, {visible: false}); } hideOverlay(); function maybeMoveOverlay() { if (HMD.active && isAway) { - var lookAt = HMD.getHUDLookAtPosition2D(); - var properties = {visible: true}; - properties.width = OVERLAY_WIDTH; - properties.height = OVERLAY_HEIGHT; - properties.x = lookAt.x - OVERLAY_WIDTH / 2; - properties.y = lookAt.y - OVERLAY_HEIGHT / 2; - Overlays.editOverlay(overlay, properties); + Overlays.editOverlay(overlayHMD, { position : HMD.getHUDLookAtPosition3D() }); } } From ba7b5c6e23529c540242a4c8377c3200c7883c2d Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 1 Mar 2016 10:43:49 -0800 Subject: [PATCH 5/5] some improvements --- examples/away.js | 78 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/examples/away.js b/examples/away.js index 69d2f3fd12..8d561f2623 100644 --- a/examples/away.js +++ b/examples/away.js @@ -17,13 +17,16 @@ 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: { x: 0, y: 0, z: 0}, + position: lastOverlayPosition, width: OVERLAY_WIDTH, height: OVERLAY_HEIGHT, url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", @@ -31,10 +34,9 @@ var OVERLAY_DATA_HMD = { alpha: 1, scale: 2, isFacingAvatar: true, - drawInFront: 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. @@ -80,28 +82,39 @@ 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}; if (HMD.active) { - Overlays.editOverlay(overlayHMD, { visible: true, position : HMD.getHUDLookAtPosition3D() }); - } else { - // Update for current screen size, keeping overlay proportions constant. - var screen = Controller.getViewportDimensions(), - screenRatio = screen.x / screen.y; + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); - if (screenRatio < OVERLAY_RATIO) { - properties.width = screen.x; - properties.height = screen.x / OVERLAY_RATIO; - properties.x = 0; - properties.y = (screen.y - properties.height) / 2; - } else { - properties.height = screen.y; - properties.width = screen.y * OVERLAY_RATIO; - properties.y = 0; - properties.x = (screen.x - properties.width) / 2; - } - Overlays.editOverlay(overlay, properties); + lastOverlayPosition = HMD.getHUDLookAtPosition3D(); + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); + } else { + // 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) }); } } function hideOverlay() { @@ -111,8 +124,29 @@ function hideOverlay() { hideOverlay(); function maybeMoveOverlay() { - if (HMD.active && isAway) { - Overlays.editOverlay(overlayHMD, { position : HMD.getHUDLookAtPosition3D() }); + 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 }); + } } }